From df28c1eaccab27a1aaea3f7ba4ab707a00f6a0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 24 Sep 2026 15:32:51 +0200 Subject: [PATCH 1/8] feat: add Rapier C bindings --- Cargo.toml | 9 +- README.md | 5 + c/CMakeLists.txt | 197 + c/README.md | 591 ++ c/VERSION | 1 + c/build.rs | 23 + c/cbindgen.toml | 59 + c/cmake/RapierConfig.cmake.in | 20 + c/docs/coverage.md | 77 + c/docs/engines.md | 79 + c/docs/validation.md | 471 ++ c/examples/RapierNative.cs | 89 + c/examples/compare_steps.rs | 61 + c/examples/falling_ball.c | 49 + c/include/rapier.h | 10344 ++++++++++++++++++++++++++++++ c/include/rapier.hpp | 84 + c/include/rapier_helpers.h | 15 + c/include/rapier_math.h | 155 + c/rapier-c-macros/Cargo.toml | 10 + c/rapier-c-macros/src/lib.rs | 157 + c/rapier2d-f64-ffi/Cargo.toml | 32 + c/rapier2d-ffi/Cargo.toml | 37 + c/rapier3d-f64-ffi/Cargo.toml | 32 + c/rapier3d-ffi/Cargo.toml | 40 + c/src/array_views.rs | 417 ++ c/src/config_data.rs | 496 ++ c/src/control.rs | 745 +++ c/src/descriptors.rs | 597 ++ c/src/dynamics.rs | 792 +++ c/src/error.rs | 235 + c/src/extra.rs | 609 ++ c/src/geometry.rs | 799 +++ c/src/geometry_views.rs | 197 + c/src/handle_access.rs | 1341 ++++ c/src/handle_world.rs | 96 + c/src/joint_access.rs | 805 +++ c/src/joint_desc.rs | 251 + c/src/joints.rs | 512 ++ c/src/lib.rs | 91 + c/src/objects.rs | 346 + c/src/owner_handle_tests.rs | 263 + c/src/pipeline.rs | 2153 +++++++ c/src/pod_tests.rs | 390 ++ c/src/queries.rs | 129 + c/src/read_access.rs | 1154 ++++ c/src/render.rs | 321 + c/src/return_values.rs | 64 + c/src/robotics.rs | 1067 +++ c/src/scoped_access.rs | 3446 ++++++++++ c/src/shape_desc.rs | 184 + c/src/soft_body.rs | 1222 ++++ c/src/soft_desc.rs | 551 ++ c/src/soft_recipes.rs | 188 + c/src/tests.rs | 614 ++ c/src/types.rs | 586 ++ c/src/world.rs | 116 + c/src/world_queries.rs | 417 ++ c/src/world_tests.rs | 223 + c/tests/array_views.c | 170 + c/tests/array_views.cpp | 1 + c/tests/consumer/CMakeLists.txt | 12 + c/tests/consumer/main.cpp | 6 + c/tests/cpp.cpp | 73 + c/tests/handles.c | 125 + c/tests/initializers.c | 56 + c/tests/initializers.cpp | 2 + c/tests/integration.c | 456 ++ c/tests/pod.c | 252 + c/tools/export_names.py | 52 + c/tools/generate-header.py | 78 + c/tools/generate-header.sh | 3 + c/tools/test-native.py | 159 + src/pipeline/physics_world.rs | 37 +- 73 files changed, 35533 insertions(+), 3 deletions(-) create mode 100644 c/CMakeLists.txt create mode 100644 c/README.md create mode 100644 c/VERSION create mode 100644 c/build.rs create mode 100644 c/cbindgen.toml create mode 100644 c/cmake/RapierConfig.cmake.in create mode 100644 c/docs/coverage.md create mode 100644 c/docs/engines.md create mode 100644 c/docs/validation.md create mode 100644 c/examples/RapierNative.cs create mode 100644 c/examples/compare_steps.rs create mode 100644 c/examples/falling_ball.c create mode 100644 c/include/rapier.h create mode 100644 c/include/rapier.hpp create mode 100644 c/include/rapier_helpers.h create mode 100644 c/include/rapier_math.h create mode 100644 c/rapier-c-macros/Cargo.toml create mode 100644 c/rapier-c-macros/src/lib.rs create mode 100644 c/rapier2d-f64-ffi/Cargo.toml create mode 100644 c/rapier2d-ffi/Cargo.toml create mode 100644 c/rapier3d-f64-ffi/Cargo.toml create mode 100644 c/rapier3d-ffi/Cargo.toml create mode 100644 c/src/array_views.rs create mode 100644 c/src/config_data.rs create mode 100644 c/src/control.rs create mode 100644 c/src/descriptors.rs create mode 100644 c/src/dynamics.rs create mode 100644 c/src/error.rs create mode 100644 c/src/extra.rs create mode 100644 c/src/geometry.rs create mode 100644 c/src/geometry_views.rs create mode 100644 c/src/handle_access.rs create mode 100644 c/src/handle_world.rs create mode 100644 c/src/joint_access.rs create mode 100644 c/src/joint_desc.rs create mode 100644 c/src/joints.rs create mode 100644 c/src/lib.rs create mode 100644 c/src/objects.rs create mode 100644 c/src/owner_handle_tests.rs create mode 100644 c/src/pipeline.rs create mode 100644 c/src/pod_tests.rs create mode 100644 c/src/queries.rs create mode 100644 c/src/read_access.rs create mode 100644 c/src/render.rs create mode 100644 c/src/return_values.rs create mode 100644 c/src/robotics.rs create mode 100644 c/src/scoped_access.rs create mode 100644 c/src/shape_desc.rs create mode 100644 c/src/soft_body.rs create mode 100644 c/src/soft_desc.rs create mode 100644 c/src/soft_recipes.rs create mode 100644 c/src/tests.rs create mode 100644 c/src/types.rs create mode 100644 c/src/world.rs create mode 100644 c/src/world_queries.rs create mode 100644 c/src/world_tests.rs create mode 100644 c/tests/array_views.c create mode 100644 c/tests/array_views.cpp create mode 100644 c/tests/consumer/CMakeLists.txt create mode 100644 c/tests/consumer/main.cpp create mode 100644 c/tests/cpp.cpp create mode 100644 c/tests/handles.c create mode 100644 c/tests/initializers.c create mode 100644 c/tests/initializers.cpp create mode 100644 c/tests/integration.c create mode 100644 c/tests/pod.c create mode 100644 c/tools/export_names.py create mode 100644 c/tools/generate-header.py create mode 100644 c/tools/generate-header.sh create mode 100644 c/tools/test-native.py diff --git a/Cargo.toml b/Cargo.toml index 6b54e9610..48cd43870 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,11 @@ [workspace] members = [ + "c/testbed/tools/logo-mesh", + "c/rapier-c-macros", + "c/rapier2d-ffi", + "c/rapier3d-ffi", + "c/rapier2d-f64-ffi", + "c/rapier3d-f64-ffi", "crates/rapier2d", "crates/rapier2d-f64", "crates/rapier_testbed2d", @@ -21,7 +27,8 @@ members = [ # pure-Rust crates. The Python-binding crates need a Python build env # (pyo3 `extension-module`) and are exercised by the dedicated # `.github/workflows/python-bindings.yml` workflow via explicit -# `-p rapier-py-*` invocations. +# `-p rapier-py-*` invocations. C ABI crates are tested separately by +# `.github/workflows/c-bindings.yml`, including native C/C++ consumers. default-members = [ "crates/rapier2d", "crates/rapier2d-f64", diff --git a/README.md b/README.md index 40ab5ffe8..67c247978 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,11 @@ also enable: rapier3d = { version = "*", features = ["parallel"] } ``` +## C and C++ bindings + +See [`c/README.md`](c/README.md) for the C ABI, C++ ownership helpers, native build instructions, +and Unity/Unreal integration guidance. The bindings cover 2D/3D and f32/f64, including soft bodies. + ## Python bindings Python bindings are under development. They ship as four PyPI packages — diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt new file mode 100644 index 000000000..de917b986 --- /dev/null +++ b/c/CMakeLists.txt @@ -0,0 +1,197 @@ +cmake_minimum_required(VERSION 3.20) +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" RAPIER_C_VERSION) +string(STRIP "${RAPIER_C_VERSION}" RAPIER_C_VERSION) +if(NOT RAPIER_C_VERSION MATCHES "^([0-9]+[.][0-9]+[.][0-9]+)[+]c[.](0|[1-9][0-9]*)$") + message(FATAL_ERROR "VERSION must be +c.") +endif() +# CMake package version comparisons use the numeric Rust version only. +project(RapierC VERSION ${CMAKE_MATCH_1} LANGUAGES C CXX) +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION") +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) +set(RAPIER_DIMENSION "3" CACHE STRING "2 or 3") +set_property(CACHE RAPIER_DIMENSION PROPERTY STRINGS 2 3) +set(RAPIER_PRECISION "32" CACHE STRING "32 or 64") +set_property(CACHE RAPIER_PRECISION PROPERTY STRINGS 32 64) +option(RAPIER_SHARED "Link the shared library (otherwise static)" ON) +option(RAPIER_BUILD_TESTS "Build C/C++ integration tests" ON) +option(RAPIER_BUILD_EXAMPLES "Build the falling-ball example" ON) +set(RAPIER_PROFILE "release" CACHE STRING "Cargo release or debug profile") +set_property(CACHE RAPIER_PROFILE PROPERTY STRINGS release debug) +set(RAPIER_FEATURES "" CACHE STRING "Additional Cargo features: enhanced-determinism,fem,profiler,robotics") +# Accept the earlier parallel/simd8 feature spelling as defaults, while the +# explicit options below determine the final effective feature set. +set(RAPIER_DEFAULT_PARALLEL OFF) +set(RAPIER_DEFAULT_SIMD_LANES "4") +if(RAPIER_FEATURES MATCHES "(^|,)parallel(,|$)") + set(RAPIER_DEFAULT_PARALLEL ON) +endif() +if(RAPIER_FEATURES MATCHES "(^|,)simd8(,|$)") + set(RAPIER_DEFAULT_SIMD_LANES "8") +endif() +option(RAPIER_ENABLE_PARALLEL "Enable Rapier parallel execution and thread-pool control" ${RAPIER_DEFAULT_PARALLEL}) +set(RAPIER_SIMD_LANES "${RAPIER_DEFAULT_SIMD_LANES}" CACHE STRING "Solver SIMD width: 4 or 8 (8 requires f32)") +set_property(CACHE RAPIER_SIMD_LANES PROPERTY STRINGS 4 8) +if(NOT RAPIER_SIMD_LANES MATCHES "^(4|8)$") + message(FATAL_ERROR "RAPIER_SIMD_LANES must be 4 or 8; this Rapier branch has no scalar solver build") +endif() +if(RAPIER_SIMD_LANES STREQUAL "8" AND RAPIER_PRECISION STREQUAL "64") + message(FATAL_ERROR "8-lane SIMD requires RAPIER_PRECISION=32; f64 uses 4 lanes") +endif() +if(RAPIER_SIMD_LANES STREQUAL "8" AND RAPIER_FEATURES MATCHES "(^|,)enhanced-determinism(,|$)") + message(FATAL_ERROR "8-lane SIMD is incompatible with enhanced-determinism; use RAPIER_SIMD_LANES=4") +endif() +string(REPLACE "," ";" RAPIER_FEATURE_LIST "${RAPIER_FEATURES}") +list(REMOVE_ITEM RAPIER_FEATURE_LIST parallel simd8) +if(RAPIER_ENABLE_PARALLEL) + list(APPEND RAPIER_FEATURE_LIST parallel) +endif() +if(RAPIER_SIMD_LANES STREQUAL "8") + list(APPEND RAPIER_FEATURE_LIST simd8) +endif() +if(RAPIER_BUILD_TESTBED) + list(APPEND RAPIER_FEATURE_LIST profiler) +endif() +list(REMOVE_DUPLICATES RAPIER_FEATURE_LIST) +list(JOIN RAPIER_FEATURE_LIST "," RAPIER_EFFECTIVE_FEATURES) +message(STATUS "Rapier physics: ${RAPIER_PROFILE}; SIMD ${RAPIER_SIMD_LANES} lanes; parallel ${RAPIER_ENABLE_PARALLEL}") +set(RAPIER_TARGET "" CACHE STRING "Optional Rust target triple for cross compilation") +if(NOT RAPIER_DIMENSION MATCHES "^[23]$" OR NOT RAPIER_PRECISION MATCHES "^(32|64)$") + message(FATAL_ERROR "RAPIER_DIMENSION must be 2/3 and RAPIER_PRECISION must be 32/64") +endif() +if(NOT RAPIER_PROFILE MATCHES "^(release|debug)$") + message(FATAL_ERROR "RAPIER_PROFILE must be release/debug") +endif() +find_program(CARGO_EXECUTABLE cargo REQUIRED) +get_filename_component(RAPIER_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) +set(RAPIER_CRATE "rapier${RAPIER_DIMENSION}d") +if(RAPIER_PRECISION STREQUAL "64") + string(APPEND RAPIER_CRATE "-f64") +endif() +string(APPEND RAPIER_CRATE "-ffi") +string(REPLACE "-" "_" RAPIER_LIBNAME "${RAPIER_CRATE}") +set(RAPIER_CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/cargo" CACHE PATH "Cargo target directory") +set(RAPIER_CARGO_ARGS build --manifest-path "${RAPIER_ROOT}/Cargo.toml" -p "${RAPIER_CRATE}" --target-dir "${RAPIER_CARGO_TARGET_DIR}") +set(RAPIER_OUTPUT "${RAPIER_CARGO_TARGET_DIR}") +if(RAPIER_TARGET) + list(APPEND RAPIER_CARGO_ARGS --target "${RAPIER_TARGET}") + string(APPEND RAPIER_OUTPUT "/${RAPIER_TARGET}") +endif() +if(RAPIER_PROFILE STREQUAL "release") + list(APPEND RAPIER_CARGO_ARGS --release) +endif() +if(RAPIER_EFFECTIVE_FEATURES) + list(APPEND RAPIER_CARGO_ARGS --features "${RAPIER_EFFECTIVE_FEATURES}") +endif() +string(APPEND RAPIER_OUTPUT "/${RAPIER_PROFILE}") +set(RAPIER_DEFINITIONS "RAPIER_DIM${RAPIER_DIMENSION};RAPIER_F${RAPIER_PRECISION}") +if(RAPIER_EFFECTIVE_FEATURES MATCHES "(^|,)fem(,|$)") + list(APPEND RAPIER_DEFINITIONS RAPIER_FEM) +endif() +if(RAPIER_EFFECTIVE_FEATURES MATCHES "(^|,)robotics(,|$)") + if(NOT RAPIER_DIMENSION STREQUAL "3" OR NOT RAPIER_PRECISION STREQUAL "32") + message(FATAL_ERROR "The native URDF/MJCF importers currently support only 3D f32") + endif() + list(APPEND RAPIER_DEFINITIONS RAPIER_ROBOTICS) +endif() +if(RAPIER_ENABLE_PARALLEL) + list(APPEND RAPIER_DEFINITIONS RAPIER_PARALLEL) +endif() +set(RAPIER_SYSTEM_LIBS "") +if(UNIX) + list(APPEND RAPIER_SYSTEM_LIBS m) # Inline functions in rapier_math.h. +endif() +if(RAPIER_SHARED) + set(RAPIER_KIND SHARED) + if(WIN32) + set(RAPIER_FILENAME "${RAPIER_LIBNAME}.dll") + set(RAPIER_IMPLIB "${RAPIER_LIBNAME}.dll.lib") + elseif(APPLE) + set(RAPIER_FILENAME "lib${RAPIER_LIBNAME}.dylib") + else() + set(RAPIER_FILENAME "lib${RAPIER_LIBNAME}.so") + endif() +else() + set(RAPIER_KIND STATIC) + list(APPEND RAPIER_DEFINITIONS RAPIER_STATIC) + if(WIN32) + set(RAPIER_FILENAME "${RAPIER_LIBNAME}.lib") + set(RAPIER_SYSTEM_LIBS ws2_32 userenv bcrypt ntdll advapi32) + else() + set(RAPIER_FILENAME "lib${RAPIER_LIBNAME}.a") + find_package(Threads REQUIRED) + set(RAPIER_SYSTEM_LIBS Threads::Threads "${CMAKE_DL_LIBS}" m) + if(APPLE) + list(APPEND RAPIER_SYSTEM_LIBS "-framework Security" "-framework CoreFoundation") + endif() + endif() +endif() +set(RAPIER_LIBRARY "${RAPIER_OUTPUT}/${RAPIER_FILENAME}") +set(RAPIER_BYPRODUCTS "${RAPIER_LIBRARY}") +if(RAPIER_IMPLIB) + list(APPEND RAPIER_BYPRODUCTS "${RAPIER_OUTPUT}/${RAPIER_IMPLIB}") +endif() +add_custom_target(rapier_cargo_build + COMMAND "${CARGO_EXECUTABLE}" ${RAPIER_CARGO_ARGS} + WORKING_DIRECTORY "${RAPIER_ROOT}" + BYPRODUCTS ${RAPIER_BYPRODUCTS} + VERBATIM USES_TERMINAL) +add_library(rapier_native ${RAPIER_KIND} IMPORTED GLOBAL) +add_library(Rapier::rapier ALIAS rapier_native) +set_target_properties(rapier_native PROPERTIES + IMPORTED_LOCATION "${RAPIER_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/include" + INTERFACE_COMPILE_DEFINITIONS "${RAPIER_DEFINITIONS}" + INTERFACE_LINK_LIBRARIES "${RAPIER_SYSTEM_LIBS}") +if(RAPIER_IMPLIB) + set_property(TARGET rapier_native PROPERTY IMPORTED_IMPLIB "${RAPIER_OUTPUT}/${RAPIER_IMPLIB}") +endif() +if(APPLE AND RAPIER_SHARED) + set_property(TARGET rapier_native PROPERTY IMPORTED_SONAME "@rpath/${RAPIER_FILENAME}") +endif() +add_dependencies(rapier_native rapier_cargo_build) +function(rapier_executable name source) + add_executable(${name} ${source}) + target_link_libraries(${name} PRIVATE Rapier::rapier) + target_compile_features(${name} PRIVATE c_std_11 cxx_std_17) + if(MSVC) + target_compile_options(${name} PRIVATE /W4 /WX /UNDEBUG) + else() + target_compile_options(${name} PRIVATE -Wall -Wextra -Werror -UNDEBUG) + endif() + if(WIN32 AND RAPIER_SHARED) + add_custom_command(TARGET ${name} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${RAPIER_LIBRARY}" "$" VERBATIM) + endif() +endfunction() +if(RAPIER_BUILD_TESTS) + enable_testing() + rapier_executable(rapier_c_integration tests/integration.c) + target_compile_definitions(rapier_c_integration PRIVATE RAPIER_EXPECTED_VERSION="${RAPIER_C_VERSION}" RAPIER_EXPECTED_PROFILE="${RAPIER_PROFILE}" RAPIER_EXPECTED_SIMD_LANES=${RAPIER_SIMD_LANES}) + rapier_executable(rapier_cpp_integration tests/cpp.cpp) + rapier_executable(rapier_pod_integration tests/pod.c) + rapier_executable(rapier_handle_integration tests/handles.c) + rapier_executable(rapier_initializer_integration tests/initializers.c) + add_test(NAME initializer_abi COMMAND rapier_initializer_integration) + rapier_executable(rapier_array_view_integration tests/array_views.c) + add_test(NAME array_view_abi COMMAND rapier_array_view_integration) + add_test(NAME handle_abi COMMAND rapier_handle_integration) + add_test(NAME pod_abi COMMAND rapier_pod_integration) + add_test(NAME c_abi COMMAND rapier_c_integration) + add_test(NAME cpp_abi COMMAND rapier_cpp_integration) +endif() +if(RAPIER_BUILD_EXAMPLES) + rapier_executable(rapier_falling_ball examples/falling_ball.c) +endif() +if(WIN32 AND RAPIER_SHARED) + set(RAPIER_INSTALL_LIBRARY_DIR "${CMAKE_INSTALL_BINDIR}") + install(FILES "${RAPIER_OUTPUT}/${RAPIER_IMPLIB}" DESTINATION "${CMAKE_INSTALL_LIBDIR}") +else() + set(RAPIER_INSTALL_LIBRARY_DIR "${CMAKE_INSTALL_LIBDIR}") +endif() +install(FILES "${RAPIER_LIBRARY}" DESTINATION "${RAPIER_INSTALL_LIBRARY_DIR}") +install(FILES include/rapier.h include/rapier.hpp include/rapier_math.h include/rapier_helpers.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +set(RAPIER_INSTALL_NAME "@rpath/${RAPIER_FILENAME}") +configure_package_config_file(cmake/RapierConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/RapierConfig.cmake" INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Rapier") +write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/RapierConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY ExactVersion) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/RapierConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/RapierConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Rapier") + diff --git a/c/README.md b/c/README.md new file mode 100644 index 000000000..81d9c1154 --- /dev/null +++ b/c/README.md @@ -0,0 +1,591 @@ +# Rapier C bindings + +C11 ABI for Rapier, with optional C++17 ownership helpers. A world owns all +simulation components. Live bodies, colliders, joints, and soft bodies are accessed +through generational handles that contain their owning world pointer. Construction descriptions and query +options are plain C values; no builders or component pointers need cleanup. + +This implementation targets Rapier’s `master` branch. ABI version **1** uses +explicit operation names, collection counts, and `TimeStep` accessors. Entity +handles embed a borrowed world pointer, avoiding redundant world parameters. Newly +produced values return directly. A single world owns the simulation; +callback-scoped read access and runtime borrow checks protect access to it. +There are no compatibility aliases. Rebuild consumers using the matching header, +library, dimension, precision, and feature configuration. + +## C names + +Functions use a dimension prefix and camelCase: `r2NewWorld` in 2D and +`r3NewWorld` in 3D. Instance methods separate the receiver type and method with +an underscore: `r3RigidBody_Position`, `r3Collider_SetFriction`, and `r3JointDesc_SetMotorPosition`. +Creation and destruction are lifecycle exceptions: `r3NewWorld()` and +`r3FreeWorld(world)`. Named constructors put the qualifier before the type, such as +`r3DynamicRigidBodyDesc()`, `r3CuboidColliderDesc(halfExtents)`, and +`r3DefaultQueryOptions()`. Loading and conversion constructors retain their +`TypeFromSource` spelling, for example `r3UrdfRobotFromFile(path, &options)`. +Static math helpers and whole-world operations omit the receiver separator, +for example `r3VectorAdd`, `r3Step`, and `r3InsertRigidBody`. Documentation below +uses 3D names unless noted; +2D uses the same suffix with `r2`. Types use PascalCase with the corresponding +`R2` or `R3` prefix, for example `R2Vector` and `R3ColliderDesc`. Constants use +`R2_` or `R3_`, for example `R2_OK` and `R3_DYNAMIC`. There are no legacy type +or constant aliases. New construction/configuration POD fields +use camelCase. Existing ABI fields retain their original Rust spelling. + +Property setters include `Set`, for example `r3RigidBody_SetTranslation` and +`r3Collider_SetFriction`. Entity operations take a handle that identifies its world. Whole-world operations +and standalone insertion still take an explicit world. Operations combining entities +reject handles from different worlds. `World` appears only in lifecycle operations: +`r3NewWorld`, `r3FreeWorld`. Simulation uses `r3Step`, queries use `r3CastRay`, and +insertion uses `r3InsertRigidBody` followed by `r3InsertCollider` to attach a collider. Descriptions expose fields directly and use setters for compound operations, +such as `JointDesc_SetMotorPosition` and `ShapeDesc_SetTrimesh`. + +Dimension-specific examples call the concrete functions directly. Shared C/C++ +code compiled for either dimension can use `RAPIER_FN(NewWorld)`, +`RAPIER_FN(RigidBody_SetTranslation)`, +`RAPIER_TYPE(World)`, and `RAPIER_CONST(OK)`. These select the corresponding +function, type, and constant using `RAPIER_DIM2`/`RAPIER_DIM3`, with no wrapper +function or extra call. Dimension-neutral import and calling-convention macros +are named `RAPIER_API` and `RAPIER_CALL`. Inline math functions use +the same convention, for example `r2Vector`, `r3Vector`, and `r3TranslationPose`. + +Rust implementations use snake_case identifiers. The +`#[rapier_export]` attribute in `rapier-c-macros` chooses the exported C symbol. +Instance methods specify the receiver, for example `#[rapier_export(rigid_body)]`; +constructors, destructors, and static helpers leave the attribute empty. Callback read methods +retain their `Read` prefix, for example `r3ReadRigidBody_Position`. The macro +has no third-party dependencies. The header generator translates the same +function names and receiver annotations, and maps the shared Rust `Rpr`/`RPR_` type and constant names to +each C dimension. This keeps the Rust implementation shared. ABI tests compare +every generated declaration with the binary exports. The dimension-specific type and constant prefixes do not affect binary layouts +or function symbols. + +## Build + +From the repository root: + +```sh +cargo build --release -p rapier3d-ffi +# Also available: rapier2d-ffi, rapier3d-f64-ffi, rapier2d-f64-ffi. +``` + +Each crate produces a shared library and static library in `target/release`. +Library names replace hyphens with underscores, e.g. `librapier3d_ffi.so`, +`librapier3d_ffi.dylib`, or `rapier3d_ffi.dll`. On Windows use the MSVC Rust target +with MSVC C/C++ consumers. The dynamic import library is `rapier3d_ffi.dll.lib`; +the static library is `rapier3d_ffi.lib`. + +The checked-in `include/rapier.h` needs no generator when consumed. Define one +of `RAPIER_DIM2` / `RAPIER_DIM3` and one of `RAPIER_F32` / `RAPIER_F64` before +including it. Defaults are 3D and f32. For static linkage define `RAPIER_STATIC`. +2D and 3D can be linked together, with each dimension's header configuration in +separate translation units. Select one scalar precision per dimension: f32 and +f64 of the same dimension share symbol names. Objects must only be passed to +the dimension and precision that created them. + +The bindings are unreleased and use ABI version 1 throughout initial development. +Headers and libraries must come from the same revision; the ABI number does not +distinguish development revisions. After the first release, incompatible releases +will increment the ABI version. + +Before any other call that passes vectors or poses, check the build: + +```c +r3CheckAbi(R3_ABI_VERSION, R3_DIMENSION, + sizeof(R3Real), sizeof(R3Vector), sizeof(R3Pose)); +``` + +Check its return status. `r3Version()` returns the loaded C library's release +version, currently `"0.35.3+c.2"` (`r2Version()` in 2D). The returned string is +borrowed and must not be freed. `c/VERSION` is the source of this version for Rust +and CMake builds: keep the Rust crate version as the base and increment `c.N` for +C bindings releases against that version. The build rejects mismatched Rust versions. +This release identifier is separate from ABI version 1. SemVer treats `+c.N` as +build metadata, so it does not establish dependency upgrade ordering. CMake uses +the numeric base for `find_package` comparisons and exposes the full string as +`Rapier_BINDINGS_VERSION` in the installed package. + +`r3BuildInfo` reports dimension, scalar width, pointer +width, and ABI version without using dimension-dependent arguments. +`r3BuildProfile()` returns a borrowed, static string containing the loaded +library's Cargo profile category (`"release"` or `"debug"`). It is independent of the +C/C++ consumer's build mode. Custom Cargo profiles report their inherited category; +per-package optimization overrides do not change that profile name. +`r3BuildFeatures` reports the solver SIMD lane count and whether parallel +execution and profiling are available through the loaded C library. + +### CMake and installation + +```sh +cmake -S c -B build/c -DRAPIER_DIMENSION=3 -DRAPIER_PRECISION=32 +cmake --build build/c --config Release +ctest --test-dir build/c -C Release --output-on-failure +cmake --install build/c --prefix /your/sdk/rapier +``` + +Use `-DRAPIER_SHARED=OFF` for static linkage, `-DRAPIER_PROFILE=debug` for a debug +Cargo build, and `-DRAPIER_FEATURES=fem,enhanced-determinism` for additional Rust +features. Select execution features explicitly: + +- `-DRAPIER_ENABLE_PARALLEL=ON` or `OFF`: enables Rayon and thread-pool control. + Defaults to ON when building the testbed, OFF for bindings alone. +- `-DRAPIER_SIMD_LANES=4` (default) or `8`: this branch always uses SIMD and has no + scalar solver build. Eight lanes require f32 and exclude `enhanced-determinism`. + Hardware instruction width depends on the target CPU; eight lanes do not imply + native eight-lane instructions or better performance on every machine. + +For direct Cargo builds, use `--features parallel` and optionally `simd8` on the +f32 crates. The older `RAPIER_FEATURES=parallel,simd8` spelling initializes the +explicit options on the first CMake configuration; the explicit cache options +control subsequent configurations. + +`r3SetNumThreads(world, count)` sets a dedicated pool per +world: 0 selects Rayon's automatic count, 1 selects one worker. Changes take effect +on the next step and must be made between steps. `r3NumThreads` +reports its actual size (0 means no dedicated pool; 1 in a build without parallel +support). `r3ClearThreadPool` returns to the calling/global Rayon +pool; it does not force serial execution. Configuration APIs return +`R3_UNSUPPORTED` when parallel support is compiled out. Pools are not serialized; +configure them again after restoring a snapshot. + +`r3SetCountersEnabled` enables native profiling, and +`r3StepTimeMs` reports the last engine step using the same +counter as the Rust testbed. This requires `--features profiler` (or +`RAPIER_FEATURES=profiler`); CMake enables it automatically for the testbed. +Counters are disabled by default in newly created worlds. The timer excludes +rendering, callbacks outside the physics step, and dispatch into the dedicated pool. + +`RAPIER_TARGET` accepts a Rust target +triple for cross builds; install that target and configure CMake's matching +compiler/toolchain. Rust's linker must also be configured for the target. Cross +builds are supported by the build configuration, not locally verified for every +target. Disable tests/examples for targets that cannot run on the build host. + +CMake builds Cargo automatically and provides `Rapier::rapier`, with the selected +ABI definitions and native system libraries. It supports `add_subdirectory(c)` +or an installed package: + +```cmake +find_package(Rapier CONFIG REQUIRED) +target_link_libraries(your_game PRIVATE Rapier::rapier) +``` + +Install each dimension/precision/configuration to a separate prefix. CMake builds +into its own `cargo` directory by default; `RAPIER_CARGO_TARGET_DIR` can reuse an +existing Cargo target directory. On macOS the shared library has an `@rpath` +install name; configure your application's runtime library search path for +redistribution. On Windows deploy the DLL beside the executable or plugin. + +## Construction with caller-owned descriptions + +Prefer caller-owned descriptions for construction. Initialize them using the API, +edit their fields, then insert directly into a world: + +```c +R3RigidBodyDesc body = r3DynamicRigidBodyDesc(); +R3ColliderDesc collider = r3BallColliderDesc(0.5f); +body.position.translation.y = 5.0f; +body.canSleep = 0; +R3RigidBodyHandle handle = r3InsertRigidBody(world, &body); +// Check r3LastStatus() before using handle unless a fail-fast error handler is installed. +R3ColliderHandle colliderHandle = r3InsertCollider(handle, &collider); +// Check r3LastStatus() again. +// No builder, body, or collider temporary needs freeing. +``` + +Insert the rigid body first, then pass its handle by value to +`r3InsertCollider(body, &desc)`. The function uses the world stored in that handle. +Repeat collider insertion to attach multiple colliders to the same body. +For a collider without a parent, use `r3InsertColliderWithoutParent(world, &desc)`. Each call validates its own input; +if collider insertion fails, the rigid body remains in the world and may be +reused or removed with `r3RemoveRigidBody`. + +Description constructors and copying descriptions need no heap allocation. +Constructors return plain values and defer validation to build/insert (or a +soft-geometry preview). They do not report errors or substitute valid defaults +for invalid arguments. You can edit a description before passing it to a +fallible operation. Invalid joint axes produce invalid frames that are +rejected during description validation. Insertion still +allocates the native simulation objects and geometry it needs. + +These are ordinary C values. Assignment copies them; they can live on the stack, +in an engine component, or in a language's blittable struct. Always call the +matching initializer: `{0}` does not produce Rapier defaults (in particular, +rotations, collision groups, and query exclusions need initialization). + +| Value | Initialization and use | +| --- | --- | +| `R3RigidBodyDesc` | `DynamicRigidBodyDesc`, `FixedRigidBodyDesc`, `KinematicPositionBasedRigidBodyDesc`, `KinematicVelocityBasedRigidBodyDesc`; `InsertRigidBody` | +| `R3ColliderDesc` | `DefaultColliderDesc`, `BallColliderDesc`, `CuboidColliderDesc`; world insertion | +| `R3ShapeDesc` | Inline in a collider; primitives, borrowed mesh/heightfield arrays, compound children, or a borrowed shared-shape reference | +| `R3JointDesc` | `DefaultJointDesc` or `FixedJointDesc`, `RevoluteJointDesc`, `PrismaticJointDesc`, `RopeJointDesc`, `SpringJointDesc` (plus dimension-specific joints); world insertion | +| `R3SoftBodyDesc` | `DefaultSoftBodyDesc`; particles, procedural generators, or borrowed surface/volume meshes; world insertion | +| `R3SoftBodyMaterial` | `DefaultSoftBodyMaterial`; inline in a soft recipe, or live `SoftBody_Material` / `SoftBody_SetMaterial` | +| `R3SoftMeshBindingDesc` | `DefaultSoftMeshBindingDesc`; `InsertDeformableCollider` | +| `R3IntegrationParameters` | `DefaultIntegrationParameters` or `IntegrationParameters`; edit then `SetIntegrationParameters` | +| `R3QueryOptions` | `DefaultQueryOptions`; reusable filter/predicate settings passed alongside the world to ray, point, shape, and intersection queries | + +`R3ShapeDesc` and `R3SoftBodyDesc` borrow their array views until the +build/insertion call returns. Insertion copies arrays and retains shared geometry; +you can then release or reuse input buffers. Copying a description alone does +**not** extend the lifetime of its arrays or shared-shape handle. Geometry view counts always count elements: vectors, edges, triangles, or +tetrahedra. `R3ShapeDesc.triangles` and `.edges` replace flattened indices. + +Soft recipes retain generator-specific radius and shape-matching defaults. +Override them explicitly, for example `soft.particleRadius = (R3OptionalReal){1, 0.05f}` +or `soft.shapeMatching = (R3OptionalBool){1, 0}`. Other material options use the +same `{enabled, value}` representation. Nonempty topology arrays override generated +topology. Procedural recipes include ropes, grids, disks, cloth, cloth tubes, +cuboids, spheres, and volumetric meshes. `SoftBodyDesc_ParticlePositions` and +`SoftBodyDesc_CellIndices` copy generated geometry into caller storage for editing; +each preview regenerates the recipe. For counts after insertion, query the soft +body by handle instead of generating it again. + +Query options hold a filter, predicate, and user pointer. They have no fixed world owner +and need no destructor. Filters that exclude specific bodies or colliders must use +handles from the queried world; rebind those after snapshot restoration. Keep predicate data +alive during each query. Pass NULL options to use the defaults. Queries observe the +broad phase from the latest `Step` or `DetectCollisions`; the latter updates +collision detection without advancing time. Some queries allocate internal scratch +buffers. + +`ImpulseJoint_Desc` / `ImpulseJoint_SetDesc` copy and apply joint configuration. They exclude +solver impulses; applying a description resets cached limit and motor impulses. Configuration +apply functions validate the complete value before replacing live settings. + +POD layouts depend on dimension/precision and, for integration settings, FEM. +`PodLayout` reports sizes for language-wrapper checks; use matching headers and +feature definitions. C++ value factories live alongside the RAII owners in +`rapier.hpp`. The C# example uses a blittable body description and only disposes +the world. + +## Typed array views + +Use typed views to keep pointers and counts together and express mesh topology +with named element types. A view's count always means **elements**: two triangles +have a count of two, irrespective of their six vertex indices. + +```c +#include "rapier_helpers.h" + +R3Vector vertices[] = {{0, 0, 0}, {1, 0, 0}, {0, 0, 1}, {1, 0, 1}}; +R3Triangle triangles[] = {{0, 2, 1}, {1, 2, 3}}; +R3ColliderDesc collider = r3DefaultColliderDesc(); +R3VectorView points = {vertices, 4}; +R3TriangleView faces = {triangles, 2}; +R3Status status = r3ShapeDesc_SetTrimesh(&collider.shape, points, faces, 0); +if (status == R3_OK) { + R3ColliderHandle handle = r3InsertColliderWithoutParent(world, &collider); + status = r3LastStatus(); +} +// After insertion returns, the arrays may be freed or leave scope. +``` + +`ShapeDesc_SetPolyline` accepts `R3EdgeView`; `ShapeDesc_SetConvexHull` accepts +`R3VectorView`. Shape setters replace the complete shape description with the +selected geometry and defaults; the surrounding collider configuration is preserved. + +Soft descriptions have setters for particles, surface meshes, edges, bend edges, +cells, surface elements, skin, masses, pinned particles, and tension-only edge +indices; 3D also exposes dihedrals and wire edges. `R3CellView` means triangle +cells in 2D and tetrahedra in 3D. `R3SurfaceElementView` means edges in 2D and +triangles in 3D. `R3RealView` and `R3IndexView` represent scalar arrays. +Soft setters preserve other fields. `SetParticles` and `SetSurfaceMesh` also select +the corresponding recipe kind. Zero topology counts retain generated topology, +just as the underlying description fields do. + +Views and descriptors own no arrays and need no destructor. Setters store pointers +without allocating or copying elements. **Keep the arrays alive and unmodified +until build/insert returns.** Copying a view or description does not extend that +lifetime. Insertion copies the required data into Rapier-owned storage. Setters +reject null nonempty views, misalignment, and unrepresentable lengths without +modifying the description. Element values, topology bounds, and geometry flags +are validated during build/insert; check `LastStatus()` too. As with all C pointer +inputs, the caller must supply valid storage for the declared extent. + +Description geometry fields are typed views too. Shared-shape mesh, compound, and heightfield constructors +also accept views; the former pointer/count overloads have been removed. + +## Value initialization helpers + +Include `rapier_helpers.h` for single-expression initialization in C11 or C++17: + +```c +R3RigidBodyDesc body = r3DynamicRigidBodyDesc(); +R3ColliderDesc collider = r3DefaultColliderDesc(); +R3SoftBodyDesc soft = r3DefaultSoftBodyDesc(); +R3QueryFilter filter = r3DefaultQueryFilter(); +R3RigidBodyHandle handle = R3_INVALID_RIGID_BODY_HANDLE; +``` + +Body helpers cover dynamic, fixed, and both kinematic types. Other descriptions +and configuration data have `Default...()` helpers; an unconstrained joint uses +`DefaultJointDesc()`. `DefaultShapeCastOptions()` initializes cast options. They +are exported native value-returning functions, usable from C and other language +bindings without compiling an inline shim. The same applies to parameterized +constructors such as `CuboidColliderDesc`, `SpringJointDesc`, and `RopeSoftBodyDesc`. +They require no cleanup; build/insert validates their contents and reports failures through `LastStatus()`. +`r2RevoluteJointDesc()` takes no axis, while `r3RevoluteJointDesc(axis)` does. +The replaced output-pointer constructors are removed. + +Explicit invalid constants are provided for body, collider, impulse-joint, +multibody-joint, and soft-body handles. Use them for local initialization and +assignment; none retains an owner. Check the ABI and match FEM/dimension/precision +configuration as with other calls. The helpers are installed with the SDK and +included by `rapier.hpp`. + +## Handle-based access + +For runtime element access, pass the generational handle; it includes the world pointer. +Each call resolves the element internally; no borrowed element pointer escapes. +For example, `r3RigidBody_SetTranslation(body, position, 1)` and +`position = r3RigidBody_Translation(body)` work across steps and storage growth. +`RigidBodyReadStates` copies an ordered batch into caller-owned storage, validates +all handles before writing, and leaves the buffer untouched on failure. + +Removed or stale handles return `R3_INVALID_HANDLE`, including after slot reuse. +Handles identify their original world but do not retain it. Each contains a `world` +pointer, `index`, and `generation` (16 bytes on 64-bit targets). Handle equality +compares all three fields. Joint creation and other operations involving multiple +entities reject mixed-world handles with `R3_INVALID_HANDLE` before mutation. The invalid +sentinel is `{NULL, UINT32_MAX, UINT32_MAX}`, not a zero-initialized handle. + +The world pointer is process-local and must not be persisted as part of a handle. +Do not fabricate or change it except when deliberately rebinding indices from a matching +snapshot. Stale entity generations are checked while the world is alive; a freed world +cannot be detected safely. Language wrappers must keep their owning world object alive +through every handle-based call (for example, `GC.KeepAlive(world)` for a C# SafeHandle). + +## Loader options + +URDF and MJCF loading uses copyable configuration values. Initialize defaults and +edit fields directly; options own no resources and need no setters or destructor: + +```c +R3UrdfLoaderOptions options = r3DefaultUrdfLoaderOptions(); +options.makeRootsFixed = 1; +options.rigidBodyBlueprint.canSleep = 0; +R3UrdfRobot *robot = r3UrdfRobotFromFile(path, &options); +// Check r3LastStatus(), use robot, then r3FreeUrdfRobot(robot). +``` + +`r3DefaultMjcfLoaderOptions()` works the same way. Blueprints are embedded +`RigidBodyDesc` and `ColliderDesc` values; geometry referenced by a collider +blueprint is borrowed until loading returns. Copying options does not retain +that geometry. Invalid fields fail during loading, before file I/O. +The defaults preserve native behavior, including zero collider density and +dynamic body blueprints. `PodLayout` reports both option sizes when robotics is +enabled, and zero otherwise. Robotics requires 3D f32. + +## Ownership and borrowing + +- Descriptions, configuration data, and `R3QueryOptions` are caller-owned values + and need no destructor. Description arrays must remain valid through insertion; + insertion copies them and retains any shared geometry it needs. +- `Collider_CloneShape`, `ReadCollider_CloneShape`, and `MjcfVisualMesh_CloneShape` + return owned wrappers sharing geometry. Release them with `FreeSharedShape`. + Ordinary value getters such as `SoftBody_Material` and `ImpulseJoint_Desc` + return POD copies that need no destructor. +- Worlds, controllers, shared shapes, mesh assets, event collectors, and snapshots + are owned resources. Use the matching `Free`, never C `free` or C++ `delete`. + `Free(NULL)` succeeds. Each owned pointer must be freed exactly once. +- A world owns its sets, pipelines, and integration settings. There are no public + component pointers, independent component constructors, or component destructors. + Configuration is accessed through world functions or copied POD snapshots. +- Ordinary world reads may overlap, including nested reads from a query predicate. + Mutation and stepping require exclusive access. Conflicting calls report + `R3_WORLD_BUSY` before borrowing native simulation state; they do not block. +- A physics hook receives a borrowed `R3ReadContext`. Use `ReadRigidBody*` and + `ReadCollider*` to inspect the callback-visible state. Ordinary calls on the + stepping world report `R3_WORLD_BUSY`. Contact context setters remain available. + Never retain either context after the callback returns. +- Perform additions, removals, and body changes after the active step/query returns. + The event collector supports this workflow. Applications may also record their + own commands during callbacks and apply them afterward; there is no implicit queue. +- Synchronize world destruction externally: no other thread may start an operation + during or after `FreeWorld`. Every entity handle becomes dangling when its world + is freed; even a `Contains` call is then invalid. Copying handles never retains a world. The access gate rejects freeing from an active + callback, but cannot make a dangling pointer safe. Controllers and other separately + owned objects still require caller synchronization. +- Mutating or removing a soft body's hidden rigid root/proxy through ordinary body + APIs is rejected. Use soft-body and cluster operations instead. +- Output buffers belong to the caller. Passing `(NULL, 0)` returns the element count. + Insufficient capacity reports `R3_BUFFER_TOO_SMALL`, returns the required count, + and leaves the buffer untouched. Counts are elements unless specified as bytes. +- Snapshot bytes belong to `R3Bytes`. Serialization copies state; restoration + creates an independent world with the serialized entity indices and generations. + Enumerate handles from the restored world to obtain its new pointers. If preserving + application references to a matching snapshot, rebind their `world` field explicitly + to the restored world; do not reuse pointers from the source world. Handles returned + by queries, events, callbacks, and controller results already carry their owner. + +C pointers must refer to live, aligned allocations of the documented type and +extent. Output buffers must not alias inputs or one another. Null/alignment checks +cannot establish allocation validity. `rapier.hpp` provides `unique_ptr` aliases +for owned objects and a `check` helper that converts statuses to C++ exceptions. + +## Errors, threads, and callbacks + +Operations that produce values return them directly: pointers for owned resources, +handles for inserted objects, PODs for getters, and counts for buffer fills. Related +outputs are grouped into structs such as `R3OptionalRayHit`, +`R3VelocityCorrection`, and `R3ByteView`. Returned structs need no cleanup, but +owned pointers inside or returned separately retain their documented ownership. +Setters, stepping, and operations without a produced value return `R3Status`. + +Every fallible call records its status and diagnostic on the calling thread. +Read `r3LastStatus()` immediately after the call when recovering from errors; +`R3_OK` means success. Infallible constructors and reads of `LastStatus`/`LastError` +do not change the recorded status. A successful fallible call clears it. + +On failure, value-returning calls return a default value: null owned pointers, +invalid handles, zero scalars/vectors, or the POD type's default configuration. +These are placeholders, not a substitute for checking the status. Array APIs keep +caller-provided buffers and return the count directly. A size query uses a null +buffer and zero capacity; `BUFFER_TOO_SMALL` returns the required count without +partially writing the buffer. Other errors return zero and preserve the buffer. +`NOT_FOUND` reports a query miss; `TryCastRay` instead returns a successful value +with `found == 0` for misses. + +```c +R3Vector position = r3RigidBody_Translation(body); +if (r3LastStatus() != R3_OK) { + fprintf(stderr, "%s\n", r3LastError()); +} +``` + +`r3LastError()` returns a thread-local UTF-8 message valid until the next fallible +call on the same thread. Copy it before another call. After an error handler makes +nested calls, the original operation's status and diagnostic are restored. +Rust panics are caught at the boundary when built with unwinding and reported as +`R3_PANIC`; discard objects mutated by that call because rollback is not promised. +Do not build these bindings with `panic=abort` if you depend on panic containment. +Allocation failure and invalid/dangling C pointers are not recoverable statuses. + +`r3SetErrorHandler` optionally installs a handler on the calling thread and +returns the previous handler for scoped restoration. The default handler is null; +both status-returning and value-returning calls report failures to the handler. A reporting handler may return normally, +or a fail-fast application may print the diagnostic and terminate the process. +It must never throw or `longjmp` through Rust frames. The testbed installs a +fail-fast handler around each example so its physics calls need no checking +macros. Handlers also receive `R3_NOT_FOUND` query misses, so applications using +expected misses should handle that status accordingly. The diagnostic is borrowed +only for the callback; the callback and its user data must remain alive until the +handler is replaced. Handler state is thread-local, not inherited by workers. + +Independent worlds may run on independent threads. Shared reads on one world +are allowed; conflicting access reports `R3_WORLD_BUSY`. Callbacks use the C +calling convention and must never throw, unwind, or longjmp across Rust frames. +With `parallel`, callbacks and their user data must support concurrent invocation. +Keep callbacks and their data alive until the synchronous operation returns. + +Pass NULL hooks/events for default behavior. Enable `activeEvents` and +`activeHooks` on colliders to request the corresponding callbacks/collections. +The event collector accumulates collision, force, and tear events until `clear`; +copying events does not consume them. Tear-event getters return owned copies. +`ContactForceEvent.started` preserves Rapier's threshold-crossing semantics. +The contact modification callback currently edits rigid-manifold material, +normal, user data, and enabled status; soft-contact candidate editing is not +exposed. Invalid callback material/normal values leave the manifold unchanged. + +`modify_solver_contacts_context` additionally receives a borrowed native contact +context. Its accessors support `update_as_oneway_platform` and tangent velocity; +the context is only valid during that callback. Query predicates also receive a scoped read context. They may +perform nested world reads/queries but cannot mutate the world during traversal. + +## Math and configuration + +`R3Real` is float or double; vectors contain two or three packed scalar fields, +without Rust SIMD alignment. A 2D rotation is one angle in radians. A 3D rotation +is an `(x,y,z,w)` quaternion, normalized on input; initialize identity with w=1. +All boolean inputs/outputs are `uint32_t` values 0 or 1. Enum inputs and flags are +integers validated before conversion into Rust values. User data preserves all +128 bits as `{low, high}`. `size_t` is pointer-sized, including in language bindings. +There are no C varargs, C++ types, Rust references, slices, strings, or enums in +the binary interface. Callback function pointers are explicitly `cdecl` on Windows. + +Defaults come from the native Rust constructors. Gravity, integration parameters, +soft-body recovery settings, joint motor models, collision/solver groups, and +material combine rules retain their Rust semantics. Generic joints cover the +standard fixed, revolute, prismatic, rope, spring, 2D pin-slot, and 3D spherical families. +3D heightfield samples are **column-major**, matching Parry `Array2`. + +## Coverage and examples + +See [docs/coverage.md](docs/coverage.md) for the API surface and explicit gaps, +[docs/validation.md](docs/validation.md) for the local validation record, +and [docs/engines.md](docs/engines.md) for Unity/Unreal integration notes. + +- [testbed/README.md](testbed/README.md): raylib/Dear ImGui viewer and headless C scenes. +- [examples/falling_ball.c](examples/falling_ball.c): complete C simulation. +- [include/rapier.hpp](include/rapier.hpp): optional C++ ownership helpers. +- [examples/RapierNative.cs](examples/RapierNative.cs): executable P/Invoke + example using a SafeHandle for the world, also suitable for a Unity wrapper. +- [tests/integration.c](tests/integration.c): world ownership and collision-only updates, + events/hooks, queries, joints, snapshots, controllers, soft-body meshes and cuts. + +The engine examples are integration starting points, not complete Unity or Unreal +plugins. Physics, game-object synchronization, editor tooling, and deployment +policies belong in those engine-specific layers. + +## Development and verification + +```sh +cargo test -p rapier-c-macros -p rapier2d-ffi -p rapier3d-ffi -p rapier2d-f64-ffi -p rapier3d-f64-ffi +python3 c/tools/test-native.py # Unix: all four variants, shared and static, C and C++ +# Header generation requires Python 3 and cbindgen 0.29.4: +cargo install cbindgen --version 0.29.4 --locked +sh c/tools/generate-header.sh +``` + +The generator uses Rust declarations and makes the transparent Rust wrapper types +opaque to C. The symbol test links every function declared for each selected ABI. +A CI workflow additionally builds the CMake tests on Linux, macOS, and Windows and +checks the generated header. The shared source is in `src/`; the four tiny crates +select Rapier's matching dimension and scalar precision. + +The optional `robotics` feature (3D/f32 only, matching the native importer crates) +adds URDF/MJCF loading, insertion, keyframes, actuator controls, and visual mesh +access. Enable it with `-DRAPIER_FEATURES=robotics` or Cargo `--features robotics`. +It uses the workspace Rust importers and their mesh readers; no additional C +libraries are required. See the [robot examples](testbed/README.md#optional-examples). + +Snapshots are limited to 256 MiB. Their 16-byte header contains `RPRS` followed +by the ABI version, dimension, and scalar byte size as little-endian `uint32_t` +values. Older six-byte headers are rejected. Load +**trusted snapshots from the identical Rapier build only**. The Rust serialized +world representation is neither an untrusted asset format nor a versioned storage +format; the tag cannot establish source-revision compatibility or data integrity. + +### Direct builder and world APIs + +`r3BallColliderDesc`, `r3CuboidColliderDesc`, and other shape constructors return +ordinary collider descriptions by value. `r3InsertRigidBody`, +`r3InsertCollider`, and `r3InsertSoftBody` insert descriptions into the world +and return handles directly. `r3InsertCollider` takes a parent handle by value; +`r3InsertColliderWithoutParent` takes the world explicitly for standalone colliders. + +The optional `rapier_math.h` header supplies C11/C++17 value constructors and +arithmetic for vectors, rotations, and poses. It has no third-party dependencies; +on Unix, manual consumers using its trigonometric operations should link `libm`. +The CMake target supplies that system library automatically. + +### C++ ownership helpers + +`rapier.hpp` provides movable `std::unique_ptr` owners for all 14 owned resource +types: `World`, `Shape`, `EventCollector`, `Snapshot`, `SoftBodyTearEvent`, +`ShapeMesh`, `KinematicCharacterController`, `PidController`, and, in 3D, +`DynamicRayCastVehicleController` and `TriMeshData`. Robotics adds `UrdfRobot`, +`UrdfRobotHandles`, `MjcfRobot`, and `MjcfRobotHandles`. + +```cpp +rapier::Shape shape(r3Collider_CloneShape(collider)); +rapier::check(r3LastStatus()); +rapier::ShapeMesh mesh(r3SharedShape_Tessellate(shape.get(), 16)); +rapier::check(r3LastStatus()); +// Each owner frees its resource when it leaves scope, including during exceptions. +``` + +Check errors after each fallible call. These owners wrap only owned pointers; +borrowed callback contexts and MJCF visual meshes must not be wrapped or freed. +Owners of resources referencing a world must be destroyed before that world. diff --git a/c/VERSION b/c/VERSION new file mode 100644 index 000000000..aee1388f5 --- /dev/null +++ b/c/VERSION @@ -0,0 +1 @@ +0.35.3+c.2 diff --git a/c/build.rs b/c/build.rs new file mode 100644 index 000000000..88fe52a2d --- /dev/null +++ b/c/build.rs @@ -0,0 +1,23 @@ +fn main() { + let version = std::fs::read_to_string("../VERSION").expect("read C bindings VERSION"); + let version = version.trim(); + let rust_version = std::env::var("CARGO_PKG_VERSION").expect("Cargo provides package version"); + let prefix = format!("{rust_version}+c."); + let revision = version + .strip_prefix(&prefix) + .and_then(|value| value.parse::().ok()) + .expect("C bindings VERSION must be +c."); + assert_eq!(version, format!("{prefix}{revision}"), "invalid C revision"); + println!("cargo:rustc-env=RAPIER_C_VERSION={version}"); + println!("cargo:rerun-if-changed=../VERSION"); + + // Report the library's Cargo profile, independent of its C/C++ consumer. + let profile = std::env::var("PROFILE").expect("Cargo provides PROFILE"); + println!("cargo:rustc-env=RAPIER_CARGO_PROFILE={profile}"); + // A distributable dylib must not retain Cargo's absolute target/deps install name. + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") { + let name = std::env::var("CARGO_PKG_NAME").unwrap().replace('-', "_"); + println!("cargo:rustc-link-arg-cdylib=-Wl,-install_name,@rpath/lib{name}.dylib"); + } + println!("cargo:rerun-if-changed=../build.rs"); +} diff --git a/c/cbindgen.toml b/c/cbindgen.toml new file mode 100644 index 000000000..a283af715 --- /dev/null +++ b/c/cbindgen.toml @@ -0,0 +1,59 @@ +language = "C" +include_guard = "RAPIER_H" +cpp_compat = true +usize_is_size_t = true +style = "both" +documentation_style = "doxy" +autogen_warning = "/* Generated by cbindgen. Regenerate with c/tools/generate-header.sh. */" +header = """ +/* Rapier C ABI. Select dimension and precision before including this file. + * Select one variant per translation unit; flags must match its linked library. + * Read README.md for ownership, pointer lifetime, callbacks and threading. */ +#if !defined(RAPIER_DIM2) && !defined(RAPIER_DIM3) +#define RAPIER_DIM3 +#endif +#if defined(RAPIER_DIM2) && defined(RAPIER_DIM3) +#error Select exactly one Rapier dimension +#endif +#if !defined(RAPIER_F32) && !defined(RAPIER_F64) +#define RAPIER_F32 +#endif +#if defined(RAPIER_F32) && defined(RAPIER_F64) +#error Select exactly one Rapier precision +#endif +#if defined(RAPIER_DIM2) +#define R2_DIMENSION 2 +#define RAPIER_TYPE(name) R2##name +#define RAPIER_CONST(name) R2_##name +#define RAPIER_FN(name) r2##name +#else +#define R3_DIMENSION 3 +#define RAPIER_TYPE(name) R3##name +#define RAPIER_CONST(name) R3_##name +#define RAPIER_FN(name) r3##name +#endif +#if defined(_WIN32) +#define RAPIER_CALL __cdecl +#else +#define RAPIER_CALL +#endif +#ifndef RAPIER_API +#if defined(_WIN32) && !defined(RAPIER_STATIC) +#define RAPIER_API __declspec(dllimport) +#else +#define RAPIER_API +#endif +#endif +""" +[defines] +"feature = dim2" = "RAPIER_DIM2" +"feature = dim3" = "RAPIER_DIM3" +"feature = f32" = "RAPIER_F32" +"feature = f64" = "RAPIER_F64" +"feature = fem" = "RAPIER_FEM" +"feature = parallel" = "RAPIER_PARALLEL" +"feature = robotics" = "RAPIER_ROBOTICS" +[fn] +prefix = "RAPIER_API RAPIER_CALL" +[parse] +parse_deps = false diff --git a/c/cmake/RapierConfig.cmake.in b/c/cmake/RapierConfig.cmake.in new file mode 100644 index 000000000..912c44088 --- /dev/null +++ b/c/cmake/RapierConfig.cmake.in @@ -0,0 +1,20 @@ +@PACKAGE_INIT@ +set(Rapier_BINDINGS_VERSION "@RAPIER_C_VERSION@") +if(NOT TARGET Rapier::rapier) + include(CMakeFindDependencyMacro) + if(NOT "@RAPIER_SHARED@" AND NOT WIN32) + find_dependency(Threads) + endif() + add_library(Rapier::rapier @RAPIER_KIND@ IMPORTED) + set_target_properties(Rapier::rapier PROPERTIES + IMPORTED_LOCATION "${PACKAGE_PREFIX_DIR}/@RAPIER_INSTALL_LIBRARY_DIR@/@RAPIER_FILENAME@" + INTERFACE_INCLUDE_DIRECTORIES "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_INCLUDEDIR@" + INTERFACE_COMPILE_DEFINITIONS "@RAPIER_DEFINITIONS@" + INTERFACE_LINK_LIBRARIES "@RAPIER_SYSTEM_LIBS@") + if(APPLE AND "@RAPIER_SHARED@") + set_property(TARGET Rapier::rapier PROPERTY IMPORTED_SONAME "@RAPIER_INSTALL_NAME@") + endif() + if(NOT "@RAPIER_IMPLIB@" STREQUAL "") + set_property(TARGET Rapier::rapier PROPERTY IMPORTED_IMPLIB "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_LIBDIR@/@RAPIER_IMPLIB@") + endif() +endif() diff --git a/c/docs/coverage.md b/c/docs/coverage.md new file mode 100644 index 000000000..470135e20 --- /dev/null +++ b/c/docs/coverage.md @@ -0,0 +1,77 @@ +# Rust API correspondence + +The header is generated from Rust. The C API preserves Rapier's operation and +handle semantics while making `R3World` the sole owner of simulation components. +It is a broad runtime binding, not a binding of every Rust public item, solver +implementation detail, or Parry re-export. + +| Rust area | C surface | +| --- | --- | +| Math | Dimension-specific vectors, angular vectors, rotations, poses; optional C11/C++17 math constructors and arithmetic; AABBs, mass properties, groups, 128-bit user data, spring coefficients | +| `PhysicsWorld` | Construction, insertion from reusable descriptions, gravity, stepping, collision-only updates, snapshots, queries, debug lines | +| `PhysicsPipeline`, `CollisionPipeline` | World-owned workspaces; `Step` and `DetectCollisions`, hooks/events, dedicated parallel thread pools, native step timing and counter enablement | +| `IntegrationParameters` | Timestep, CCD, solver iterations, contact softness, length scale, warmstarting, clustering, recycling, friction bias; soft-body recovery and optional FEM scalar settings | +| `RigidBodyBuilder`, `RigidBody` | Types, transforms, kinematic targets, velocities, forces, impulses, torque, damping, gravity, mass/inertia, locks, sleep/wake, CCD, gyroscopic forces, dominance, iteration counts, user data, collider handles | +| `RigidBodySet`, `ColliderSet` | World-based insertion, handle lookup, enumeration, containment, coordinated removal, body-to-collider pose propagation | +| `SharedShape` | Balls, boxes, rounded boxes, capsules, segments, triangles, halfspaces, 3D cylinders/cones, convex hulls, convex decomposition, polylines, triangle meshes, compounds, heightfields with flags, triangle-mesh flags, voxels from points/meshes and voxel editing; bounds, point containment, mass properties | +| `ColliderBuilder`, `Collider` | Direct shape constructors, pose/local pose, density/mass, friction/restitution and combine rules, sensor/enabled, groups, collision types, events/hooks, contact skin, force threshold, parent, user data, bounds | +| Joints | Generic joints; fixed/revolute/prismatic/rope/spring/spherical/pin-slot constructors; frames, axes, limits, motors, motor models/force, contacts, softness, user data | +| `ImpulseJointSet` | Insertion/removal, handles, connected bodies, copied joint descriptions and setters with wake control | +| `MultibodyJointSet` | Insertion/removal, handles, joint data, tuning updates preserving degrees of freedom, articulation velocity read/write, inverse kinematics, generalized displacements | +| `QueryPipeline` | World queries with reusable POD options, filters and scoped C predicates, ray and linear shape casts, point projection, point/shape/AABB intersections | +| `NarrowPhase` | Contact pair lookup/enumeration, aggregate impulses, rigid geometric contact points, sensor intersection pairs | +| Events/hooks | Thread-safe event collection for collisions, contact forces and tears; contact/intersection filtering; rigid manifold material/normal/enabled modification, native one-way-platform context and tangent velocity | +| `SoftBodyBuilder`, `SoftBodySet` | Custom particles/edges/cells/surfaces/masses; rope, cloth/tube, cuboid/grid, triangle-mesh/polyline and volumetric generators; particle settings, material, cell model, shape matching, self-contact, surface collider, insertion/removal | +| `SoftBody` | Particle positions/velocities/targets/pinning, forces/impulses, rigid attachments, material, enable/wake, topology, volume, clusters/proxies, piece handles, deformed mesh vertices/indices by stable mesh ID, including collider-free skins | +| Soft topology | Immediate cuts/tears, owned tear events, particle destinations, torn/removed/inserted elements, pieces, cluster splits and moved joints; adding/removing/configuring clusters; direct/by-position/skinned deformable collider bindings | +| `KinematicCharacterController` | Up/offset/sliding/slopes/autostep/ground snapping; shape movement, collision output and collision impulse solving | +| `DynamicRayCastVehicleController` | 3D chassis, wheel creation/tuning, controls, vehicle axes, stepping, speed and wheel/contact state | +| PID controller | Native gains, controlled axes, rigid-body velocity corrections | +| URDF/MJCF | Optional 3D/f32 importers, options, both joint insertion paths, body handles, keyframes, scaled actuator controls, visual shapes/UVs/normals/textures/materials | +| Debug rendering | Caller-owned HSLA line buffers using Rapier debug mode flags | + +## Deliberate adaptations + +- Descriptions and configuration snapshots are POD values. Constructors return them + directly; insertion copies them into the world. Owned builders and independent + sets/pipelines are removed, with no compatibility aliases. +- Insertions clone inputs and removals discard removed objects. This avoids ambiguous + ownership transfer. A shape clone shares its immutable geometry through an Arc. +- Runtime Rust references become world-and-handle operations. Iterators/slices become + caller-owned buffers or explicit accessor calls. Error/Option results become + statuses, invalid handles, or documented nullable outputs. +- Standard joint constructors return `R3JointDesc`, preserving the Rust + `Into` relationship without an additional C allocation layer for + each joint-builder family. +- The character controller retains the last movement's collisions for buffer access. + Its impulse helper and the vehicle's update helper accept `PhysicsWorld` to avoid + exporting a mutable query view with difficult cross-language lifetime rules. +- Physics callbacks receive a scoped read context, plus their user pointer. World + reentrancy conflicts return `R3_WORLD_BUSY`; contact edits use the contact context. + Events are collected for polling and mutations after stepping. + +## Explicit gaps + +These are not exposed in this initial ABI: + +- Every specialized shape parameter/flag, direct editable heightfield data, + detailed shape-type introspection, standalone Parry pairwise queries, nonlinear + shape casts. +- Per-contact anchor edits, soft-contact candidate modification callbacks, + and full solver-manifold/soft-contact internal views. The exposed contact points + are geometric manifolds; contact-pair totals handle clustering correctly. +- Link/Jacobian internals, standalone passive-spring configuration, every robotics + loader/sensor option, and detailed profiler data. +- Every soft-body meshing/generator option, all per-element material overrides, custom + skin-mapping internals, and the full graph of diagnostic soft-body recovery data. + Arbitrary particle/cell/surface inputs and common mesh/cluster workflows are covered. +- Individual serialization APIs for each set/type, a stable cross-version snapshot + format, simultaneous f32/f64 variants of the same dimension, and engine editor + plugins or full generated language wrappers. 2D and 3D already have distinct + exported namespaces. + +New wrappers can be added without changing existing object layouts. Incompatible +changes to released exports, public POD layouts, enum meanings, or signatures +require an ABI version change and coordinated consumer updates. During initial +unreleased development, the ABI stays at version 1 and consumers must use matching +header and library revisions. diff --git a/c/docs/engines.md b/c/docs/engines.md new file mode 100644 index 000000000..8378ba017 --- /dev/null +++ b/c/docs/engines.md @@ -0,0 +1,79 @@ +# Engine and language integration + +Keep one physics world per scene/simulation, and store generational handles in +engine components. Build shapes once and share them across colliders. Stepping +and entity creation/removal belong on an exclusive physics thread or behind the +engine's synchronization boundary. Copy transforms and events into engine-owned +buffers before exposing them to other threads; never persist borrowed body or +collider pointers across a frame. + +## Unity / C# + +`examples/RapierNative.cs` is a small executable 3D/f32 P/Invoke example. Native +structs use `LayoutKind.Sequential`, uint for Rapier booleans/enums, and pointer- +sized `UIntPtr` for `size_t`. Always declare `CallingConvention.Cdecl`. Use +`SafeHandle` for owned worlds; borrowed set/element pointers must not have owning +finalizers. Keep the owning SafeHandle alive while using borrowed pointers. + +Build the matching native target and place the DLL/shared library in the project's +native plugin location for that platform/architecture. Configure Unity's plugin +importer accordingly. For iOS/static builds, an application may need `__Internal` +as its import library and platform-specific native-link setup. The provided +example does not perform platform packaging or editor configuration. + +Call physics at a fixed timestep and set Rapier's `IntegrationParameters.dt` to +that interval. Synchronize dynamic body results into Transform objects after +stepping; author kinematic targets before stepping. Choose an explicit basis and +unit mapping. For a Unity mapping that reflects Z, convert positions with +`(x,y,-z)` and quaternion components with `(-x,-y,z,w)` in both directions. The +sample only uses vertical translation and does not impose an engine-wide mapping. +When rendering soft bodies, copy collision-mesh vertices and indices, and rebuild +topology when its version changes. Process tear piece/particle remaps to preserve +render attributes attached to particles. + +Keep delegates rooted for the whole step when using hooks. Parallel builds can +invoke hooks on worker threads: do not access Unity objects from those callbacks. +Event polling after the step is usually simpler. A full generated C# wrapper can +be generated from `rapier.h`; this example intentionally declares only its used +entry points. + +## Unreal / C++ + +Add the installed include directory, link the selected static or import library +from the module's Build.cs, and stage the shared runtime library through Unreal's +normal runtime dependency mechanism if dynamically linked. Define +`RAPIER_DIM3`, `RAPIER_F32` or `RAPIER_F64`, and `RAPIER_STATIC` for static linkage. +The C API does not require C++ exceptions; Unreal projects with exceptions disabled +can check statuses directly instead of calling the throwing `rapier::check` helper. + +Use a subsystem/scene object to own the world. Store `R3RigidBodyHandle` and +`R3ColliderHandle` in components, and translate Rapier user data into engine IDs. +Do not store an engine UObject pointer in a physics object without an engine-side +lifetime strategy. Event collection allows game-thread dispatch without invoking +Unreal object APIs from solver callbacks. + +Choose a unit and basis conversion consistently for positions, normals, forces, +velocities, rotations and inertia. Rapier defaults to meters and gravity along -Y; +Unreal commonly uses centimeters and Z-up. You can keep physics in meters and +convert at the boundary, or configure `length_unit` and gravity for your selected +units. A reflection changes angular-vector handedness as well as positions; use +a basis transform for rotations rather than merely swapping quaternion fields. + +## Build/ABI checklist for wrapper authors + +1. Select one dimension and scalar precision. Check `r3CheckAbi` before passing + POD math structures; reject an incompatible runtime library. +2. Generate bindings from the preprocessed header with those definitions. Optional + FEM and parallel declarations also require `RAPIER_FEM` / `RAPIER_PARALLEL`. +3. Preserve C layout, pointer width, constness, and the documented pointer lifetimes. + Do not use a language's default bool marshaling for `R3Bool`. +4. Copy the thread-local error text before another API call. Treat query misses and + buffer sizing distinctly from fatal errors. +5. Make owned/borrowed pointer distinctions visible in the wrapper. Prefer handles + and reacquisition for long-lived engine components. +6. Convert coordinate systems and units in one shared layer. Cover rotations, + angular velocities, inertia and soft meshes as well as translations. + +The repository tests exercise the C ABI and C++ ownership helpers. Engine editor, +IL2CPP/AOT, consoles, mobile signing, and Unreal build integration need validation +in the target projects. diff --git a/c/docs/validation.md b/c/docs/validation.md new file mode 100644 index 000000000..a985e9c74 --- /dev/null +++ b/c/docs/validation.md @@ -0,0 +1,471 @@ +# Validation record + +Validated locally on 2026-09-18 on macOS arm64, with Rust 1.98.0, +Apple Clang 17, CMake 3.31.3, and cbindgen 0.29.4. + +## Mainline rebase (2026-09-24) + +- Rebased the 23 C-binding commits onto `master` at `79fd711a9`, excluding the + soft-body commits already merged upstream. The normal `c-bindings` branch is + checked out in the main repository. +- All 130 default-feature Rust binding tests and 36 full-feature 3D tests pass. + All four native ABI variants pass C/C++ shared/static linkage, symbol checks, + version checks, and dimension coexistence. Header regeneration is unchanged. +- Both graphical testbeds build with FEM and parallelism (3D also with robotics), + and all 28 CTests pass. One-step, no-sleep, single-worker smoke runs pass 88 2D + and 113 3D scenes. The remaining `debug_deserialize3` scene needs its external + `snapshot0.bincode` fixture. +- Audited all 214 upstream vendored files against pinned upstream Git blobs and + reviewed nested licenses. Supplemental license texts and collected notices + are included, and both viewers copy these plus LGPL fallback-header sources + into their redistribution directories. See `testbed/vendor/README.md`. + +## C release version reporting (2026-09-24) + +- `c/VERSION` defines `0.35.3+c.2` independently of ABI version 1. The shared + Cargo build script validates the Rust-version prefix and embeds the exact + release string. `r2Version()` / `r3Version()` return borrowed static C strings. +- CMake reads the same file, compares the numeric base for package discovery, + and exports the full release string as `Rapier_BINDINGS_VERSION`. +- All four native ABI variants pass C/C++ shared/static linking and exact + version-string assertions, with 538 exports in 2D and 563 in 3D. Dimension + coexistence, strict full-feature FFI Clippy, and formatting pass. +- A fresh Release 3D CMake build passes all six integration tests. A separate + C consumer finds the installed package and verifies its version metadata + matches the runtime string. Header regeneration and `git diff --check` pass. + +## Full-width snapshot headers (2026-09-24) + +- Snapshot headers now store ABI, dimension, and scalar byte size as little-endian + `u32` values after the `RPRS` magic. No snapshot header field is narrowed to a + byte. The payload begins after the shared 16-byte header length. +- Regression tests cover ABI values through `u32::MAX`, including the former + 1/257 collision, every mutated header byte, truncated headers/payloads, round + trips, and rejection of the obsolete six-byte format. Public ABI remains 1. +- All 130 default-feature Rust tests and 36 full-feature 3D tests pass. Strict + full-feature FFI Clippy, formatting, and all four native C/C++ ABI variants + pass, including shared/static linkage and dimension coexistence. Header + regeneration is unchanged and `git diff --check` passes. + +## Initial ABI version (2026-09-20) + +The unreleased bindings now use ABI version 1. Earlier ABI numbers below record +development checkpoints, not published compatibility guarantees. Future ABI +version increments apply to incompatible releases. + +All four native variants pass C/C++ shared/static linking, export checks, and +dimension coexistence with ABI 1. The 2D/3D legacy snapshot tests and C# example +pass. Header regeneration is reproducible, and `git diff --check` passes. + +## ABI 15: explicit free-function names (2026-09-20) + +- Renamed `RemoveBody`, `InsertDeformable`, and `ActiveBodies` to + `RemoveRigidBody`, `InsertDeformableCollider`, and `ActiveRigidBodies`. + Integration-parameter accessors and world serialization now name their subject. + `TimeStep` / `SetTimeStep` replace `Dt` / `SetDt`. +- All five collection `Len` functions now use `Count`, including callback-scoped + rigid-body and collider queries. All 14 renames apply to both dimensions; + replaced exports are absent and the ABI checks explicitly reject them. +- Migrated examples, tests, helpers, tools, and current documentation. Internal + Rust binding entry points follow the same names; native Rapier methods retain + their Rust naming conventions. +- All 122 default-feature Rust binding tests, three naming tests, and 34 + full-feature 3D tests pass. Formatting and strict full-feature FFI Clippy pass. +- Four native ABI variants pass C11/C++17 shared/static linking, exact export + checks (537 in 2D, 562 in 3D), and dimension coexistence. +- Both Release viewers rebuild with all demos; all 28 CTests pass. The C# + example passes with ABI 15. Header regeneration is byte-for-byte reproducible, + and `git diff --check` passes. + +## ABI 14: collider insertion from parent handles (2026-09-20) + +- `InsertCollider(parent, &desc)` takes the parent handle by value and uses its + world pointer. `InsertColliderWithoutParent(world, &desc)` creates a collider + without a rigid-body parent. The nullable parent-pointer signature is removed. +- All demos, native tests, and documentation use the new signatures. Regression + tests cover parent ownership, independent worlds, standalone colliders, null + worlds, and invalid or removed parents without unintended insertion. +- All 122 default-feature Rust binding tests and three naming tests pass. All 34 + full-feature 3D tests, formatting, and strict full-feature FFI Clippy pass. +- Four native ABI variants pass C11/C++17 shared/static linking, exact export + checks (537 in 2D, 562 in 3D), and dimension coexistence. +- Both Release viewers build and all 28 CTests pass. One-step, no-sleep, + single-worker smoke runs pass all 88 2D demos and 113 3D demos; the remaining + `debug_deserialize3` demo requires the external `snapshot0.bincode` fixture. +- The C# example passes with ABI 14. Header regeneration is byte-for-byte + reproducible, and `git diff --check` passes. + +## ABI 13: separate rigid-body and collider insertion (2026-09-20) + +- Removed `r2Insert` / `r3Insert` and the `BodyCollider` result type. The body-only + function is now `InsertRigidBody`. Examples insert the body, then call + `InsertCollider` with its handle. No combined helper or compatibility alias remains. +- All demos, native tests, C++ helpers, the C# example, and documentation use the + new sequence. Errors are checked between dependent calls in standalone examples + and native tests. Collider insertion failure leaves an already-created body intact; + an updated regression test verifies this behavior. +- All 118 default-feature Rust binding tests and three naming tests pass; all 33 + full-feature 3D tests and strict Clippy pass. Four native ABI variants pass + C11/C++17 shared/static linkage, exact symbol checks (536 in 2D, 561 in 3D), + and dimension coexistence. The ABI audit rejects the removed function/type. +- Both Release viewers build, and all 28 CTests pass. One-step, no-sleep, + single-worker smoke runs pass all 88 2D demos and 113 3D demos. The remaining + `debug_deserialize3` demo requires the existing external `snapshot0.bincode` fixture. +- C# P/Invoke passes with ABI 13. Header regeneration reproduces byte-for-byte, + and `git diff --check` passes. + +## ABI 12: configuration values and resource ownership (2026-09-20) + +- URDF/MJCF options are copyable PODs with embedded body/collider descriptions. + Default factories replace allocation, setters, and destructors. Options validate + before file I/O and borrow blueprint geometry only through loading. `PodLayout` + exposes their sizes (zero when robotics is unavailable). +- Removed redundant `Data` suffixes from nine configuration types and normalized + material/description getter-setter pairs. `UserData` and the owned `TriMeshData` + mesh buffer retain meaningful names. Public compatibility aliases are absent. +- Collider, callback collider, and MJCF visual shape getters now say `CloneShape` + and document the owned wrapper. C++ owners cover all 14 remaining resource types. + Demos, helpers, tests, documentation, and C# layout declarations are migrated. +- All 118 default-feature Rust binding tests and three naming tests pass. All 33 + full-feature 3D tests pass, including POD default preservation, independent copies, + validation before I/O, borrowed geometry retention, URDF blueprint loading, and + MJCF keyframes/actuators. Formatting and strict full-feature FFI Clippy pass. +- Four native ABIs pass C11/C++17 shared/static linking and dimension coexistence. + Default exports remain 537 in 2D and 562 in 3D; the full-feature 3D header exactly + matches all 601 exports. All 14 destructors have matching C++ owner aliases. +- Both Release viewers build with all demos; all 28 CTests pass. The final C++ + tests verify shape-clone lifetime after collider removal in both dimensions. + URDF and MJCF demos pass one-step, no-sleep smoke tests with one worker. +- The .NET 8 example passes with ABI 12. Header regeneration is byte-for-byte + reproducible, and `git diff --check` passes. + +## ABI 11: constructor and destructor names (2026-09-20) + +- Renamed 94 exported constructors/destructors, with no compatibility aliases: + `NewWorld`, `FreeWorld`, `DynamicRigidBodyDesc`, `CuboidColliderDesc`, and + `DefaultQueryOptions` illustrate the convention. All 16 destructors use `FreeType`. + The inline pure-translation constructor is now `TranslationPose`. +- Instance methods retain `Type_Method`; loading constructors retain `TypeFromFile`. + All demos, C/C++ helpers, tests, and the C# sample use the new names. The Rust + implementation uses the same word order without additional export-macro logic. +- All 118 Rust binding tests across 2D/3D and f32/f64 pass, along with three naming + tests. Formatting and strict FFI/macro Clippy checks pass. +- All four native variants pass C11/C++17 shared/static linking, exact export + checks (537 in 2D, 562 in 3D), and 2D+3D coexistence. Header regeneration is + byte-for-byte reproducible; no old lifecycle references remain in current source. +- Both Release viewers build with all demos and pass all 28 CTests. The .NET 8 + sample passes with ABI 11, reporting y=0.074563645 after 60 steps. + +## ABI 10: instance-method names (2026-09-20) + +- 414 instance methods use a separator between their receiver and method, for example + `r3RigidBody_SetTranslation` and `r3JointDesc_SetMotorPosition`. Constructors, + static helpers, and short world functions retain their existing names. +- The Rust export attribute records the receiver explicitly. The header generator + reads the same annotation; obsolete method exports and aliases are absent. + All C demos, shared testbed code, C++ helpers, and the C# example are migrated. +- Three macro tests cover method names, unchanged constructors/helpers, and invalid + annotations. All four dimension/precision variants pass C11/C++17 shared/static + linking, exact export checks (537 in 2D, 562 in 3D), and 2D+3D coexistence. +- Both Release testbeds build with all demos; all 28 CTests pass. The .NET 8 + example passes with ABI 10, reporting y=0.074563645 after 60 steps. +- Header regeneration is byte-for-byte reproducible. No obsolete method names + remain in tracked source consumers. `git diff --check` passes. + +## ABI 8: direct value returns (2026-09-19) + +- 313 value-producing operations now return their results directly. Related outputs + use POD aggregates; array fills retain caller buffers and return the element count. + Mutators without a produced value still return status codes. Old signatures are removed. +- `LastStatus` records the most recent fallible call on each thread. Error callbacks + cover both calling conventions, and nested callbacks preserve the original status + and diagnostic. Failures return type defaults; short-buffer errors retain the required + count. Infallible constructors and status/diagnostic reads preserve the recorded error. +- All 98 Rust binding tests pass across 2D/3D and f32/f64, both with default features + and with FEM + parallel. Strict FFI Clippy passes in both configurations. Tests cover + panic fallback, stale handles, thread-local status, callback reentrancy, and buffers. +- All four native variants pass C11/C++17 shared/static linking, complete symbol checks, + and 2D+3D coexistence. Default exports: 537 in 2D and 562 in 3D. The generated header + reproduces byte-for-byte; the ABI audit rejects scalar output-pointer declarations. +- The .NET 8 consumer passes with ABI 8, reporting y=0.074563645 after 60 steps. + Both Release viewers and step benchmarks build; all 28 testbed tests pass. +- Independent three-step, no-sleep, one-worker runs pass all 88 2D demos and 113/114 + 3D demos. `debug_deserialize3` still requires the external `snapshot0.bincode` fixture. + +## Dimension-specific C names (2026-09-19) + +- Public types and constants use `R2`/`R2_` or `R3`/`R3_`, with no old-name + aliases. All demos and consumers use the new names. Shared C/C++ support uses + `RAPIER_FN`, `RAPIER_TYPE`, and `RAPIER_CONST` selectors. +- The generator translates the shared Rust names without duplicating the Rust + implementation. This source rename preserves layouts, symbols, and ABI 7. +- All four dimension/precision variants pass C11/C++17 shared/static linking, + export checks, and 2D+3D coexistence. The audit rejects obsolete or wrong-dimension + type and constant names. Header regeneration is reproducible. +- Both Release testbeds build with all demos and pass all 28 CTests. + +## ABI 7: world ownership and scoped callback access (2026-09-19) + +- The public C API exposes one simulation owner, `RprWorld`. Sets, pipelines, + component getters, duplicate world accessors, and borrowed query views are + removed. New entry points include `WorldNew`, `Step`, `InsertBody`, + `RigidBodySetTranslation`, and `CastRay`; query options are owner-independent PODs. +- All C demos, the viewer, benchmarks, tests, C++ helpers, and the C# example use + the new interface. Export checks explicitly reject obsolete public types and + symbol prefixes; the generated header reproduces byte-for-byte. +- The world uses a nonblocking shared/exclusive access gate. Conflicting calls + return `RPR_WORLD_BUSY` before native borrows are created. Tests cover hook-scoped + reads, contact edits, rejected recursive stepping/mutation/destruction, nested + read-only queries, cross-thread conflicts, and guard release after errors/panics. +- Rust `PhysicsWorld` now owns collision-only workspace and provides + `detect_collisions` and `remove_body_with_colliders`. The C API uses these methods; + collider-preserving removal and collision refresh without time advancement are tested. +- All 90 Rust binding tests pass across 2D/3D and f32/f64 with default features and + with FEM + parallel. Strict FFI Clippy passes for both configurations. +- C11/C++17 shared/static behavior, POD layouts, all exports, and 2D+3D coexistence + pass for all four native variants. Default export counts are 536 in 2D and 561 in 3D. + C callbacks exercise scoped reads. The .NET 8 C# consumer passes with ABI 7, + reporting y=0.074563645 after 60 steps. +- Both Release viewers and the standalone step benchmarks build. Both viewers pass + all 14 CTests. Three-step no-sleep, one-worker smoke runs pass 88/88 2D and 113/114 + 3D demos. The remaining `debug_deserialize3` demo needs the external + `snapshot0.bincode` fixture; it is not counted as a pass. + +## ABI 6: description constructors return values (2026-09-19) + +- Collider, joint, and soft-body description constructors return POD values + directly. Uniform material and volume-meshing parameter constructors do too. + The 2D revolute constructor takes no axis, matching native Rust. +- All demos, standalone C/C++ examples, tests, and documentation use the new + signatures. Output-pointer constructor signatures are removed. Invalid input + is preserved in descriptions and rejected during build/insert or preview; + construction does not allocate geometry or invoke the error callback. +- All 74 Rust tests pass across 2D/3D and f32/f64. Added coverage compares native + constructor values and checks deferred validation of invalid shapes and axes. + Strict FFI Clippy passes in all four default variants. +- All four variants pass C11/C++17 shared/static tests, symbol checks, layout + checks, and dimensional coexistence. The C# example runs with the ABI 6 check. +- Both Release viewers build and pass all 14 CTests. Independent three-step, + no-sleep, one-worker runs pass all 88 2D demos and 113/114 3D demos; the remaining + snapshot demo requires the external `snapshot0.bincode` fixture. + +## ABI 5: complete example migration and API cleanup (2026-09-19) + +- Every C testbed example now uses POD construction, set-and-handle element + access, native value initializers, and typed geometry inputs where applicable. + The public header contains no owned builders, element-pointer accessors, or + allocated query wrappers. Their replacement is mandatory, with no aliases. +- Shape, soft-body, binding, compound, and heightfield descriptions use typed + views. Shared-shape geometry constructors use the same view types. Insertion + copies borrowed inputs; examples retain temporary arrays and shared shapes + through the last insertion that reads them. +- All four default variants pass C11/C++17 shared/static linking, ABI and POD + layout checks, export checks, and 2D+3D coexistence. Default exported function + counts are 560 in 2D and 584 in 3D. +- All 70 Rust tests pass with default features and again with FEM + parallel. + Tests compare procedural recipes against native Rust defaults, check stale + handles and failure atomicity, and cover infinite one-sided joint limits. + Strict FFI Clippy passes for all variants with both feature configurations. +- Both Release viewers build and pass all 14 CTests, including dragging, + deformable sensor rendering, UI, and threading. Builds use SIMD4 + parallel + + FEM; 3D also enables robotics. +- Independent catalog runs pass 88/88 2D and 113/114 3D demos for three steps, + with sleeping disabled and one physics worker. `debug_deserialize3` reports + the missing external `snapshot0.bincode` fixture. These are smoke tests for + stepping and finite state, not proof of numerical identity to every Rust demo. +- The .NET 8 C# example passes with ABI 5, reporting y=0.074563645 after 60 steps. + Generated header reproduction and whitespace checks pass. + +The entries below describe historical validation before ABI 5; their old API +names and export counts do not describe the current interface. + +## Handle-based access (2026-09-19) + +- All four native variants pass C/C++ tests, shared/static linkage, export checks, + and dimension coexistence (758 exports in 2D, 781 in 3D). +- Handle tests cover storage growth, stepping, slot reuse, collider removal, + soft-proxy restrictions, error callback delivery, invalid input preservation, + batch capacity checks and all-or-nothing output on stale handles. +- Strict FFI Clippy passes in all variants. The C# example uses no borrowed body + pointer and runs successfully; generated header reproduction passes. + +## ABI 4: explicit setter names (2026-09-19) + +- Property setters consistently include `Set`, including all owned builders and + bulk POD configuration. Incremental translation and clearing use action verbs. +- Updated exports, header, examples, testbed, tests, C# ABI check, and documentation. + Source audit found no references to replaced names; no legacy aliases remain. +- All four variants pass shared/static C/C++ ABI tests and dimension coexistence; + 62 Rust unit tests and strict FFI Clippy checks pass. Header generation is stable. +- Both release viewers build and pass all 11 CTests; the C# example runs against + ABI 4. This is a development ABI change requiring consumers to rebuild. + +## POD construction and configuration (2026-09-19) + +- All four default ABIs pass C11/C++17 behavior tests, POD layout checks, every + declared symbol, shared/static linking, and 2D+3D coexistence. Default exports + are now **670 in 2D** and **693 in 3D**. +- 62 Rust tests pass across the four variants, both with default features and + with FEM + parallel enabled. New tests compare construction + defaults and shape/soft recipes against native Rust values, verify copied array + lifetimes, reject recursive compound descriptions, and check failure atomicity. +- The C POD suite exercises direct set/world insertion, joint descriptions, + integration read/apply, material read/apply, borrowed query predicates, shared + shape retention, and deformable binding/vertex input lifetimes. +- Both Release f32 viewers pass all **11 CTests**, including mouse dragging, + soft sensor rendering and UI behavior. Builds include parallel + SIMD4 + FEM; + the 3D build also includes robotics. +- Full catalog smoke run: **88/88 2D** and **113/114 3D** demos pass three steps + with sleeping disabled and one worker. The remaining `debug_deserialize3` + requires the external `snapshot0.bincode` input; its failure reports the missing + file. This smoke test checks successful stepping and finite state, not numerical + identity of every demo with Rust. +- The updated C# example compiles/runs under .NET 8 using a blittable body + description, verifies its native size, and produces y=0.074563645 after 60 steps. +- Strict default and FEM/parallel FFI Clippy checks pass for all four variants. + Header regeneration is reproducible. No engine Rust source or existing + object layout was changed; owned builder APIs remain available. ABI 4 subsequently + renames setters to include `Set`. + +## Deformable sensor transparency + +- Deformable triangles and polyline ribbons preserve alpha and share the sorted + transparency pass with rigid sensors, with depth writes disabled until flush. +- Both Release viewers pass all ten CTests. The renderer regression now checks + the real Cluster meshes sensor and synthetic 2D deformable sensors: alpha 102, + no transparent draws in the opaque pass, depth ordering, and depth-write restore. +- Inspected a 30-frame Cluster meshes capture: the deformable sensor shell reveals + its enclosed solid mesh. + +## Testbed controls and rendering + +- Both Release f32 viewers pass all ten CTests. Mouse-grab coverage includes dynamic + rigid bodies, articulated links, soft particles, sensor/fixed-body exclusion, + release cleanup, and deletion during a drag. The UI test also checks filtered + previous/next navigation and orbit-camera distance, target, panning, and zoom. +- Inspected rendered 2D deformable polylines and the 3D sensor demo with its solid + collider visible through the transparent sensor surface. +- All four default variants pass shared/static C/C++ linkage, export comparison, + and dimension coexistence. Default exports are 617 in 2D and 640 in 3D after + adding soft-body ownership, cluster-proxy, and closed-mesh accessors. +- Clippy with `--no-deps -- -D warnings` passes for all four default FFI variants. + +## Soft-mesh renderer follow-up + +- Fixed renderer cache indexing for meshes without a collider. Mesh enumeration + now exposes native mesh IDs so render-only skins remain independently accessible. +- Both Release viewers pass all nine CTests. The added renderer regression runs + actual soft scenes without a GPU, bounds cache allocation, checks finite geometry, + and verifies that queued triangles flush before back-face culling is restored. +- The Cluster meshes and Soft trimeshes windows ran for 30 and 90 frames, + respectively; the latter screenshot was inspected after the two-sided fix. +- Both 3D precision variants pass all 12 Rust boundary/regression tests, including + direct comparison of render-only mesh vertices and indices with the native API. +- All four default variants pass shared/static C/C++ linkage, export comparison, + and dimension coexistence. Default exports are now 614 in 2D and 637 in 3D. +- Formatting and Clippy with `--no-deps -- -D warnings` pass for the updated bindings. + +## ABI 3: complete example catalog + +- All 202 catalog entries now have C ports: 88 2D and 114 3D. Added 62 entries. +- Release f32, SIMD4, one worker, sleeping disabled: all 201 scenes with available + inputs passed three steps. This includes 2D/3D FEM, URDF, Cassie MJCF, and a + locally installed Menagerie model. `debug_deserialize3` needs external snapshot + files; its legacy rigid-state reader passed a generated-state round-trip and + subsequent-step comparison in all four native variants. +- Longer runs exercised tearing (900 steps), self-intersection (260 steps), + one-way platforms, soft joints, vehicle controllers/joints, shape replacement, + articulated joints, ray casting, and OBJ-based scenes. These check execution + and finite state; they are not a blanket proof of C/Rust numerical identity. +- All four FFI variants passed 11 Rust boundary/regression tests each. The 3D/f32 + `fem,robotics` configuration passed 12, including imported keyframes and + actuator controls. +- Both Release viewers built with their optional features. All eight CTests + passed per dimension, including native C/C++, ImGui input, worker/snapshot + handling, and the new tests driving real IK, kinematic/PID, and voxel-edit loops. +- All four default ABIs passed shared/static C and C++ linkage, all-symbol + linkage, export comparison, and 2D/3D coexistence. Default exports: 611 in 2D, + 634 in 3D. Optional-feature exports also matched their headers: 619 for 2D/FEM, + 699 for 3D/FEM/robotics/parallel. +- All 2D and 3D scene sources passed strict f64 C11 compilation with FEM enabled. + Robotics stays 3D/f32, matching the native importer crates. +- Clippy passed with `--no-deps -- -D warnings` for all four default variants + and for 3D/f32 with FEM/robotics. Existing engine dependency warnings remain. +- The raylib Menagerie viewer was run and its screenshot inspected for model + framing, smooth normals, materials, and texture rendering. + +The catalog counts implemented scenes, not external asset availability. FEM and +robotics remain opt-in build features. No new platform/engine certification is +implied by these local macOS checks. + +## ABI 2: dimension prefixes and camelCase + +- All four FFI crates: 28 boundary/validation tests passed. The export macro's + 2 naming tests also passed. +- `cargo clippy` for the export macro and all four FFI crates with + `--no-deps -- -D warnings`: passed. Existing engine dependency warnings remain. +- `cargo fmt -p rapier-c-macros -p rapier3d-ffi -- --check`: passed. +- Regenerating `rapier.h` produces an identical header. +- `python3 c/tools/test-native.py`: every variant passed the C behavioral suite, + C++ ownership suite, and declared-symbol linking with shared and static + libraries. The default headers declare **564 functions for 2D** and + **582 functions for 3D**; optional features add more. +- Dynamic exports match the preprocessed declarations exactly. No old `rpr_*` + exports or opposite-dimension exports remain. Both 2D and 3D link and run in + one executable, tested with shared/static linkage and f32/f64 separately. +- Inline math headers: all 8 combinations of C11/C++17, 2D/3D, and f32/f64 passed + strict compilation and execution (`-pedantic -Wall -Wextra -Werror`). +- `cargo check` with `parallel,fem,enhanced-determinism` passed for all four FFI + crates. This checks feature compatibility, not solver numerical behavior. +- Release 3D/f32 parallel + SIMD4: all 7 CTests passed, including ImGui input, + example-owned loops, pause/step, worker changes, snapshots, and error reporting. +- Release 2D/f32 parallel + SIMD4, headless: all 6 CTests passed. +- The installed shared CMake package was consumed by a separate C++ application. +- `examples/RapierNative.cs` compiled under .NET 8 and ran through the renamed + P/Invoke entry points against the rebuilt 3D/f32 library; y=0.074563645 after + 60 default steps. A previous native library beside the application had to be + replaced with the ABI 2 build, as required for renamed entry points. + +The C behavioral tests also cover explicit pipeline construction, ownership, +collision/force events, hooks, contact inspection, ray/shape queries, filtering, +heightfields, mass properties, buffer sizing, invalid inputs, snapshots, joints, +soft-body particles and meshes, cutting/tear events, character movement, +3D vehicles, debug lines, removal cascades, and stale handles. + +## Earlier execution and packaging checks + +The following checks predate the ABI 2 naming change: + +- Installed shared/static CMake packages, including relocation of the shared + package before consumption, passed on macOS with `@rpath` loading. +- Debug 2D/f32 serial + SIMD4: all 6 CTests passed. +- Debug 3D/f32 parallel + SIMD8, headless: all 5 CTests passed. +- CMake rejected unsupported SIMD widths, SIMD8/f64, and SIMD8 with enhanced + determinism before invoking Cargo. +- All 140 existing demo ports preserved their initial/final physics state when + moved to direct API calls and example-owned loops. Animated scenes ran for + 260 steps; the other scenes ran for 3 steps. +- The Keva viewer's per-step timing uses the same native counter as the Rust UI. + See the [C/Rust comparison](../testbed/tools/timing-results.md) for measurements, + raw runs, and reproduction instructions. + +The workflow covers Linux, macOS, and Windows builds plus installed-package +consumers. Remote CI has not been run for this change. Unity editor/IL2CPP, +Unreal, mobile targets, and consoles need tests in their target projects. + +## Value initialization helpers (2026-09-19) + +The C11 and C++17 initialization tests pass with shared and static libraries for all four dimension/precision variants. Release CMake initializer tests also pass in both dimensions with FEM enabled. These exercise descriptor insertion, configuration application, invalid handles, and query defaults. + +## Typed array views (2026-09-19) + +- C11 and C++17 tests pass for all four dimension/precision variants with shared + and static linkage, export checks (771 in 2D, 796 in 3D), and dimension coexistence. +- View tests exercise edge/triangle/cell counts, soft surface/skin insertion, + copied input lifetimes, null/alignment/length errors, unchanged descriptions on + failure, and topology validation at insertion. +- All 62 Rust binding tests and strict FFI Clippy pass. Header regeneration is + reproducible; the existing ABI and descriptor layouts are unchanged. +- Both release viewers rebuild with FEM, parallelism and SIMD4 and pass all + 14 CTests per dimension. The updated polyline2 and debug_trimesh3 examples each + pass 120 steps with sleeping disabled and one worker. diff --git a/c/examples/RapierNative.cs b/c/examples/RapierNative.cs new file mode 100644 index 000000000..0bcc4daa9 --- /dev/null +++ b/c/examples/RapierNative.cs @@ -0,0 +1,89 @@ +// Minimal standalone C# / Unity-compatible 3D-f32 example. Uses only blittable C ABI types. +using System; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +public static class RapierNative +{ + const string Library = "rapier3d_ffi"; + [StructLayout(LayoutKind.Sequential)] public struct Vector { public float x, y, z; } + [StructLayout(LayoutKind.Sequential)] public struct Rotation { public float x, y, z, w; } + [StructLayout(LayoutKind.Sequential)] public struct Pose { public Vector translation; public Rotation rotation; } + [StructLayout(LayoutKind.Sequential)] public struct BodyHandle { public IntPtr world; public uint index, generation; } + [StructLayout(LayoutKind.Sequential)] public struct UserData { public ulong low, high; } + [StructLayout(LayoutKind.Sequential)] public struct MassProperties + { + public Vector localCom; + public float mass; + public Vector principalInertia; + public Rotation principalInertiaLocalFrame; + } + // R3Bool is uint32_t. Do not marshal these fields as C# bool. + [StructLayout(LayoutKind.Sequential)] public struct RigidBodyDesc + { + public Pose position; + public Vector linvel, angvel; + public uint bodyType; + public float gravityScale, linearDamping, angularDamping, additionalMass; + public uint useAdditionalMassProperties; + public MassProperties additionalMassProperties; + public byte lockedAxes; + public uint canSleep, sleeping, ccdEnabled; + public float softCcdPrediction; + public uint allowFastRotation, enabled; + public sbyte dominanceGroup; + public UIntPtr additionalSolverIterations, additionalPgsIterations; + public uint gyroscopicForcesEnabled; + public UserData userData; + } + [StructLayout(LayoutKind.Sequential)] struct PodLayout + { + public UIntPtr rigidBodyDesc, colliderDesc, shapeDesc, jointDesc; + public UIntPtr softBodyMaterial, integrationParameters, softBodyDesc; + public UIntPtr softMeshBindingDesc, queryOptions, urdfLoaderOptions, mjcfLoaderOptions; + } + public sealed class World : SafeHandleZeroOrMinusOneIsInvalid + { + public World() : base(true) { } + protected override bool ReleaseHandle() { return r3FreeWorld(handle) == 0; } + } + [DllImport(Library, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true)] static extern uint r3CheckAbi(uint version,uint dimension,UIntPtr realSize,UIntPtr vectorSize,UIntPtr poseSize); + [DllImport(Library, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true)] static extern IntPtr r3LastError(); + [DllImport(Library, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true)] static extern uint r3LastStatus(); + [DllImport(Library, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true)] static extern World r3NewWorld(); + [DllImport(Library, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true)] static extern uint r3FreeWorld(IntPtr world); + [DllImport(Library, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true)] static extern uint r3Step(World world,IntPtr hooks,IntPtr events); + [DllImport(Library, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true)] static extern PodLayout r3PodLayout(); + [DllImport(Library, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true)] static extern RigidBodyDesc r3DynamicRigidBodyDesc(); + [DllImport(Library, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true)] static extern BodyHandle r3InsertRigidBody(World world,in RigidBodyDesc desc); + [DllImport(Library, CallingConvention=CallingConvention.Cdecl, ExactSpelling=true)] static extern Vector r3RigidBody_Translation(BodyHandle handle); + static void Check(uint status) + { + if(status!=0) throw new InvalidOperationException(Marshal.PtrToStringUTF8(r3LastError())); + } + // Call from Unity's main thread, or any thread with exclusive access to this world. + // The native library must be installed for the process architecture before invoking this method. + public static Vector SimulateOneSecond() + { + Check(r3CheckAbi(1,3,(UIntPtr)4,(UIntPtr)Marshal.SizeOf(),(UIntPtr)Marshal.SizeOf())); + PodLayout layout = r3PodLayout(); + if (layout.rigidBodyDesc != (UIntPtr)Marshal.SizeOf()) + throw new InvalidOperationException("RigidBodyDesc layout does not match the native library."); + World world = r3NewWorld(); + Check(r3LastStatus()); + using(world) + { + RigidBodyDesc body = r3DynamicRigidBodyDesc(); + body.additionalMass = 1; + body.position.translation.y = 5; + BodyHandle handle = r3InsertRigidBody(world,in body); + Check(r3LastStatus()); + // The description is a value. Only the world needs disposal. + for(int i=0;i<60;++i) Check(r3Step(world,IntPtr.Zero,IntPtr.Zero)); + Vector position = r3RigidBody_Translation(handle); + // Handles contain a raw pointer; keep the owning SafeHandle alive through the call. + GC.KeepAlive(world); + Check(r3LastStatus()); + return position; + } + } +} diff --git a/c/examples/compare_steps.rs b/c/examples/compare_steps.rs new file mode 100644 index 000000000..ed25fe160 --- /dev/null +++ b/c/examples/compare_steps.rs @@ -0,0 +1,61 @@ +//! Replay trusted C-testbed snapshots with the native Rust API, in the same Cargo +//! configuration. See c/testbed/tools/README.md. This is a manual benchmark. +use bincode::Options; +use rapier::prelude::*; +use std::{path::Path, time::Instant}; + +fn read(path: &Path) -> PhysicsWorld { + let data = std::fs::read(path).unwrap(); + assert_eq!(&data[..3], b"RPR"); + assert_eq!(data[3], 1); + assert_eq!(data[4], rapier::math::DIM as u8); + assert_eq!(data[5], std::mem::size_of::() as u8); + bincode::DefaultOptions::new() + .with_fixint_encoding() + .with_limit(256 * 1024 * 1024) + .reject_trailing_bytes() + .deserialize(&data[6..]) + .unwrap() +} +fn main() { + let args: Vec<_> = std::env::args_os().collect(); + assert_eq!(args.len(), 3, "arguments: INITIAL_SNAPSHOT FINAL_SNAPSHOT"); + let mut world = read(Path::new(&args[1])); + let expected = read(Path::new(&args[2])); + #[cfg(feature = "parallel")] + world.configure_thread_pool(1).unwrap(); + world.physics_pipeline.counters.enable(); + let mut wall = 0.0; + let mut engine = 0.0; + for step in 0..420 { + let start = Instant::now(); + world.step(); + let elapsed = start.elapsed().as_secs_f64() * 1000.0; + if step >= 120 { + wall += elapsed; + engine += world.physics_pipeline.counters.step_time_ms(); + } + } + assert_eq!(world.bodies.len(), expected.bodies.len()); + let mut active = 0; + // Exact final poses and velocities verify the same physical trajectory. + for (handle, body) in world.bodies.iter() { + let other = &expected.bodies[handle]; + assert_eq!(body.position(), other.position(), "pose {handle:?}"); + assert_eq!(body.linvel(), other.linvel(), "linear velocity {handle:?}"); + assert_eq!(body.angvel(), other.angvel(), "angular velocity {handle:?}"); + if body.is_dynamic() { + assert!(!body.is_sleeping()); + assert!(body.activation().normalized_linear_threshold < 0.0); + active += 1; + } + } + println!( + "Rust bodies={} awake_dynamic={} SIMD={} workers=1 warmup=120 measured=300 wall_ms={:.6} engine_ms={:.6} final_state=exact_match", + world.bodies.len(), + active, + rapier::math::SIMD_WIDTH, + wall / 300.0, + engine / 300.0 + ); +} diff --git a/c/examples/falling_ball.c b/c/examples/falling_ball.c new file mode 100644 index 000000000..741e6f95f --- /dev/null +++ b/c/examples/falling_ball.c @@ -0,0 +1,49 @@ +#include "rapier_helpers.h" +#include "rapier_math.h" +#include +#include + +#define CHECK(call) \ + do { \ + if ((call) != RAPIER_CONST(OK)) { \ + fprintf(stderr, "%s: %s\n", #call, RAPIER_FN(LastError)()); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +int main(void) { + CHECK(RAPIER_FN(CheckAbi)(RAPIER_CONST(ABI_VERSION), RAPIER_CONST(DIMENSION), + sizeof(RAPIER_TYPE(Real)), sizeof(RAPIER_TYPE(Vector)), + sizeof(RAPIER_TYPE(Pose)))); + + RAPIER_TYPE(World) *world = RAPIER_FN(NewWorld)(); + CHECK(RAPIER_FN(LastStatus)()); + + RAPIER_TYPE(RigidBodyDesc) rigid_body = RAPIER_FN(DynamicRigidBodyDesc)(); +#if defined(RAPIER_DIM2) + RAPIER_TYPE(Vector) position = RAPIER_FN(Vector)(0.0, 5.0); +#else + RAPIER_TYPE(Vector) position = RAPIER_FN(Vector)(0.0, 5.0, 0.0); +#endif + rigid_body.position.translation = position; + + RAPIER_TYPE(ColliderDesc) collider = RAPIER_FN(BallColliderDesc)(0.5); + RAPIER_TYPE(RigidBodyHandle) handle = RAPIER_FN(InsertRigidBody)(world, &rigid_body); + CHECK(RAPIER_FN(LastStatus)()); + RAPIER_FN(InsertCollider)(handle, &collider); + CHECK(RAPIER_FN(LastStatus)()); + + /* Descriptions own no resources. The world owns the inserted objects. */ + + for (int i = 0; i < 60; i++) { + CHECK(RAPIER_FN(Step)(world, NULL, NULL)); + } + + position = RAPIER_FN(RigidBody_Translation)(handle); + CHECK(RAPIER_FN(LastStatus)()); + printf("Ball y after one second: %.3f\n", (double)position.y); + + /* Only the world owns native resources. */ + CHECK(RAPIER_FN(FreeWorld)(world)); + return EXIT_SUCCESS; +} diff --git a/c/include/rapier.h b/c/include/rapier.h new file mode 100644 index 000000000..80b798b4f --- /dev/null +++ b/c/include/rapier.h @@ -0,0 +1,10344 @@ +/* Rapier C ABI. Select dimension and precision before including this file. + * Select one variant per translation unit; flags must match its linked library. + * Read README.md for ownership, pointer lifetime, callbacks and threading. */ +#if !defined(RAPIER_DIM2) && !defined(RAPIER_DIM3) +#define RAPIER_DIM3 +#endif +#if defined(RAPIER_DIM2) && defined(RAPIER_DIM3) +#error Select exactly one Rapier dimension +#endif +#if !defined(RAPIER_F32) && !defined(RAPIER_F64) +#define RAPIER_F32 +#endif +#if defined(RAPIER_F32) && defined(RAPIER_F64) +#error Select exactly one Rapier precision +#endif +#if defined(RAPIER_DIM2) +#define R2_DIMENSION 2 +#define RAPIER_TYPE(name) R2##name +#define RAPIER_CONST(name) R2_##name +#define RAPIER_FN(name) r2##name +#else +#define R3_DIMENSION 3 +#define RAPIER_TYPE(name) R3##name +#define RAPIER_CONST(name) R3_##name +#define RAPIER_FN(name) r3##name +#endif +#if defined(_WIN32) +#define RAPIER_CALL __cdecl +#else +#define RAPIER_CALL +#endif +#ifndef RAPIER_API +#if defined(_WIN32) && !defined(RAPIER_STATIC) +#define RAPIER_API __declspec(dllimport) +#else +#define RAPIER_API +#endif +#endif + + +#ifndef RAPIER_H +#define RAPIER_H + +/* Generated by cbindgen. Regenerate with c/tools/generate-header.sh. */ + +#include +#include +#include +#include +#include + +#if defined(RAPIER_DIM2) + +#if defined(RAPIER_DIM2) +#define R2_JOINT_DOF_COUNT 3 +#endif + +#if defined(RAPIER_DIM3) +#define R2_JOINT_DOF_COUNT 6 +#endif + +#define R2_SOFT_DESC_PARTICLES 0 + +#define R2_SOFT_DESC_ROPE 1 + +#define R2_SOFT_DESC_GRID 2 + +#define R2_SOFT_DESC_CLOTH 3 + +#define R2_SOFT_DESC_CUBOID 4 + +#define R2_SOFT_DESC_SURFACE 5 + +#define R2_SOFT_DESC_DISK 6 + +#define R2_SOFT_DESC_SPHERE 7 + +#define R2_SOFT_DESC_CLOTH_TUBE 8 + +#define R2_SOFT_DESC_VOLUMETRIC 9 + +#define R2_SOFT_BINDING_SKINNED 0 + +#define R2_SOFT_BINDING_DIRECT 1 + +#define R2_SOFT_BINDING_DIRECT_BY_POSITION 2 + +#if defined(RAPIER_DIM2) +#define R2_POLYLINE_ORIENTED 1 +#endif + +#define R2_POLYLINE_DEFORMABLE 2 + +#define R2_SHAPE_DESC_BALL 0 + +#define R2_SHAPE_DESC_CUBOID 1 + +#define R2_SHAPE_DESC_ROUND_CUBOID 2 + +#define R2_SHAPE_DESC_CAPSULE 3 + +#define R2_SHAPE_DESC_SEGMENT 4 + +#define R2_SHAPE_DESC_TRIANGLE 5 + +#define R2_SHAPE_DESC_HALFSPACE 6 + +#define R2_SHAPE_DESC_CONVEX_HULL 7 + +#define R2_SHAPE_DESC_TRIMESH 8 + +#define R2_SHAPE_DESC_POLYLINE 9 + +#define R2_SHAPE_DESC_SHARED 10 + +#define R2_SHAPE_DESC_HEIGHTFIELD 11 + +#define R2_SHAPE_DESC_CYLINDER 12 + +#define R2_SHAPE_DESC_CONE 13 + +#define R2_SHAPE_DESC_COMPOUND 14 + +#define R2_SHAPE_DESC_ROUND_CYLINDER 15 + +#define R2_MASS_DENSITY 0 + +#define R2_MASS_TOTAL 1 + +#define R2_MASS_PROPERTIES 2 + +#define R2_ABI_VERSION 1 + +#define R2_DYNAMIC 0 + +#define R2_FIXED 1 + +#define R2_KINEMATIC_POSITION_BASED 2 + +#define R2_KINEMATIC_VELOCITY_BASED 3 + +#define R2_COLLISION_EVENTS 1 + +#define R2_CONTACT_FORCE_EVENTS 2 + +#define R2_COMBINE_AVERAGE 0 + +#define R2_COMBINE_MIN 1 + +#define R2_COMBINE_MULTIPLY 2 + +#define R2_COMBINE_MAX 3 + +#define R2_FILTER_CONTACT_PAIRS 1 + +#define R2_FILTER_INTERSECTION_PAIR 2 + +#define R2_MODIFY_SOLVER_CONTACTS 4 + +#define R2_QUERY_EXCLUDE_FIXED 1 + +#define R2_QUERY_EXCLUDE_KINEMATIC 2 + +#define R2_QUERY_EXCLUDE_DYNAMIC 4 + +#define R2_QUERY_EXCLUDE_SENSORS 8 + +#define R2_QUERY_EXCLUDE_SOLIDS 16 + +#define R2_QUERY_ONLY_DYNAMIC 3 + +#define R2_QUERY_ONLY_KINEMATIC 5 + +#define R2_QUERY_ONLY_FIXED 6 + +#define R2_DEBUG_COLLIDER_SHAPES 1 + +#define R2_DEBUG_RIGID_BODY_AXES 2 + +#define R2_DEBUG_MULTIBODY_JOINTS 4 + +#define R2_DEBUG_IMPULSE_JOINTS 8 + +#define R2_DEBUG_SOLVER_CONTACTS 16 + +#define R2_DEBUG_CONTACTS 32 + +#define R2_DEBUG_COLLIDER_AABBS 64 + +#define R2_DEBUG_SOFT_BODIES 128 + +#define R2_DEBUG_PSEUDO_NORMALS 256 + +#define R2_DEBUG_SOFT_VOLUME_CONTACTS 512 + +#define R2_DEBUG_SOFT_BODY_STRESS 1024 + +#define R2_GROUPS_AND 0 + +#define R2_GROUPS_OR 1 + +#define R2_MOTOR_ACCELERATION_BASED 0 + +#define R2_MOTOR_FORCE_BASED 1 + +#define R2_SOFT_CELL_VOLUME 0 + +#define R2_SOFT_CELL_COROTATIONAL 1 + +#define R2_SOFT_CELL_NEO_HOOKEAN 2 + +#define R2_SOFT_SOLVER_CONSTRAINTS 0 + +#define R2_SOFT_SOLVER_FEM 1 + +#define R2_AXIS_LIN_X 0 + +#define R2_AXIS_LIN_Y 1 + +#define R2_LOCK_TRANSLATION_X 1 + +#define R2_LOCK_TRANSLATION_Y 2 + +#define R2_LOCK_TRANSLATION_Z 4 + +#define R2_LOCK_ROTATION_X 8 + +#define R2_LOCK_ROTATION_Y 16 + +#define R2_LOCK_ROTATION_Z 32 + +#if defined(RAPIER_DIM2) +#define R2_AXIS_ANG_X 2 +#endif + +#if defined(RAPIER_DIM3) +#define R2_AXIS_ANG_X 3 +#endif + +#if defined(RAPIER_DIM2) +#define R2_JOINT_FIXED_AXES 7 +#endif + +#if defined(RAPIER_DIM3) +#define R2_JOINT_FIXED_AXES 63 +#endif + +#if defined(RAPIER_DIM2) +#define R2_JOINT_REVOLUTE_AXES 3 +#endif + +#if defined(RAPIER_DIM3) +#define R2_JOINT_REVOLUTE_AXES 55 +#endif + +#if defined(RAPIER_DIM2) +#define R2_JOINT_PRISMATIC_AXES 6 +#endif + +#if defined(RAPIER_DIM3) +#define R2_JOINT_PRISMATIC_AXES 62 +#endif + +#if defined(RAPIER_DIM3) +#define R2_AXIS_LIN_Z 2 +#endif + +#if defined(RAPIER_DIM3) +#define R2_AXIS_ANG_Y 4 +#endif + +#if defined(RAPIER_DIM3) +#define R2_AXIS_ANG_Z 5 +#endif + +#if defined(RAPIER_DIM3) +#define R2_JOINT_SPHERICAL_AXES 7 +#endif + +/** + * Read-only body type used by soft-body cluster proxies; not valid for builder_new. + */ +#define R2_SOFT_FRAME 4 + +#define R2_SHAPE_CAST_OUT_OF_ITERATIONS 0 + +#define R2_SHAPE_CAST_CONVERGED 1 + +#define R2_SHAPE_CAST_FAILED 2 + +#define R2_SHAPE_CAST_PENETRATING 3 + +#define R2_FEATURE_UNKNOWN 0 + +#define R2_FEATURE_VERTEX 1 + +#define R2_FEATURE_EDGE 2 + +#define R2_FEATURE_FACE 3 + +/** + * Native URDF/MJCF multibody insertion flags. + */ +#define R2_MULTIBODY_JOINTS_ARE_KINEMATIC 1 + +#define R2_MULTIBODY_DISABLE_SELF_CONTACTS 2 + +#define R2_MULTIBODY_SKIP_LOOP_CLOSURES 4 + +#define R2_MULTIBODY_SKIP_JOINT_MOTORS 8 + +#define R2_MULTIBODY_SKIP_JOINT_LIMITS 16 + +#define R2_MULTIBODY_SKIP_JOINT_SPRINGS 32 + +/** + * Parry triangle-mesh and heightfield flags used by the public shape constructors. + */ +#define R2_TRIMESH_MERGE_DUPLICATE_VERTICES 16 + +#define R2_TRIMESH_FIX_INTERNAL_EDGES 144 + +#define R2_TRIMESH_DEFORMABLE 256 + +#define R2_TRIMESH_FIX_INTERNAL_EDGES_TWO_SIDED 656 + +#define R2_HEIGHTFIELD_FIX_INTERNAL_EDGES 1 + +/** + * Immutable owned byte buffer. Release with the matching FreeBytes function. + */ +typedef struct R2Bytes R2Bytes; + +/** + * Borrowed native contact context. Valid only during its callback; never retain or free it. + */ +typedef struct R2ContactModificationContext R2ContactModificationContext; + +#if defined(RAPIER_DIM3) +typedef struct R2DynamicRayCastVehicleController R2DynamicRayCastVehicleController; +#endif + +/** + * Events accumulate until clear. Copying events never drains them, allowing two-call buffer sizing. + */ +typedef struct R2EventCollector R2EventCollector; + +/** + * Controller plus reusable collision output from the last move_shape call. + */ +typedef struct R2KinematicCharacterController R2KinematicCharacterController; + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R2MjcfRobot R2MjcfRobot; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R2MjcfRobotHandles R2MjcfRobotHandles; +#endif + +/** + * PID controller with persistent integral state. + */ +typedef struct R2PidController R2PidController; + +/** + * Callback-scoped read access to bodies and colliders. Never retain or free it. + * Only the Read* functions accept this context; it cannot mutate the world. + */ +typedef struct R2ReadContext R2ReadContext; + +/** + * Owned tessellated shape: flat triangle vertices and independent line segments, in local space. + * Rounded 3D shapes use their inner surface (as in the Rust testbed). Halfspaces use a finite patch. + */ +typedef struct R2ShapeMesh R2ShapeMesh; + +/** + * Owned copy of a tear event. Read particle remapping before rebuilding render meshes. + */ +typedef struct R2SoftBodyTearEvent R2SoftBodyTearEvent; + +#if defined(RAPIER_DIM3) +/** + * Owned indexed geometry from Parry's shape tessellation, preserving its vertex order. + */ +typedef struct R2TriMeshData R2TriMeshData; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R2UrdfRobot R2UrdfRobot; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R2UrdfRobotHandles R2UrdfRobotHandles; +#endif + +/** + * Sole owner of simulation state. Handles belong to the world that created them. + * Ordinary reads may overlap. A mutation or step requires exclusive access. + * Destruction must be externally synchronized with all users of this pointer. + */ +typedef struct R2World R2World; + +#if defined(RAPIER_F32) +typedef float R2Real; +#endif + +#if defined(RAPIER_F64) +typedef double R2Real; +#endif + +typedef struct R2SpringCoefficients { + R2Real natural_frequency; + R2Real damping_ratio; +} R2SpringCoefficients; + +/** + * ABI booleans are uint32_t: zero is false, one is true. + */ +typedef uint32_t R2Bool; + +typedef struct R2OptionalReal { + R2Bool enabled; + R2Real value; +} R2OptionalReal; + +typedef struct R2OptionalU32 { + R2Bool enabled; + uint32_t value; +} R2OptionalU32; + +/** + * Plain configuration data; initialize defaults, edit, then apply. No destructor. + */ +typedef struct R2SoftBodyMaterial { + struct R2SpringCoefficients edgeSoftness; + struct R2SpringCoefficients bendSoftness; + struct R2SpringCoefficients volumeSoftness; + struct R2SpringCoefficients shapeMatchingSoftness; + R2Real youngModulus; + R2Real poissonRatio; + R2Real elasticDampingRatio; + R2Real plasticYield; + R2Real plasticCreep; + R2Real plasticMax; + R2Real deformationDamping; + R2Real edgePlasticYield; + R2Real edgePlasticCreep; + R2Real edgePlasticMax; + uint32_t edgePlasticFlow; + struct R2OptionalReal tearStrain; + struct R2OptionalReal tearForce; + R2Real tearSmoothing; + R2Real interiorStrength; + uint32_t maxTearsPerStep; + struct R2OptionalU32 minPiece; +} R2SoftBodyMaterial; + +/** + * Plain configuration data; initialize defaults, edit, then apply. No destructor. + */ +typedef struct R2SoftRecoverySettings { + R2Bool authoredVelocityMargin; + R2Bool edgeSpeculation; + R2Bool invertedCellDetection; + R2Bool selfCrossingDetection; + R2Bool detectionMotionGating; + R2Bool crossBodyDetection; + R2Bool selfStandDown; + R2Bool crossBodyExpelGate; + R2Bool edgeStandDown; + R2Bool crossingRepulsion; + R2Bool crossingRepulsionGuide; + R2Bool crossingRepulsionSelfGuide; + R2Real recoveryPace; + R2Bool overlapConstraints; + R2Bool overlapRigid; + R2Bool overlapSkipSelfTangled; + R2Bool overlapEdgeStandDown; + R2Real overlapConstraintPace; + uint32_t overlapPatchConstraints; + R2Bool overlapSkinVolume; + R2Real overlapKeptDepth; + R2Bool overlapSelfRegions; + R2Bool overlapNormalPush; + R2Bool overlapMultiVolume; + uint32_t overlapSplit; + uint32_t overlapPatience; + R2Real overlapProgressMargin; +} R2SoftRecoverySettings; + +#if defined(RAPIER_FEM) +/** + * Plain configuration data; initialize defaults, edit, then apply. No destructor. + */ +typedef struct R2SoftFemParameters { + R2Real linearTolerance; + size_t maxLinearIterations; + size_t maxDenseDofs; +} R2SoftFemParameters; +#endif + +/** + * Plain configuration data; initialize defaults, edit, then apply. No destructor. + */ +typedef struct R2SoftBodiesSettings { + struct R2SoftRecoverySettings recovery; + R2Real resweepStrain; + size_t maxExtraSubsteps; + R2Real contactStiffening; +#if defined(RAPIER_FEM) + struct R2SoftFemParameters fem; +#endif +} R2SoftBodiesSettings; + +/** + * Plain configuration data; initialize defaults, edit, then apply. No destructor. + */ +typedef struct R2IntegrationParameters { + R2Real dt; + R2Real minCcdDt; + struct R2SpringCoefficients contactSoftness; + struct R2SpringCoefficients staticContactSoftness; + R2Real warmstartCoefficient; + R2Real lengthUnit; + struct R2SoftBodiesSettings softBodies; + R2Real normalizedAllowedLinearError; + R2Real normalizedMaxCorrectiveVelocity; + R2Real normalizedPredictionDistance; + R2Real normalizedMaxLinearVelocity; + size_t numSolverIterations; + size_t numInternalPgsIterations; + size_t numInternalStabilizationIterations; + size_t maxCcdSubsteps; + R2Bool contactClustering; + R2Bool contactRecycling; + R2Real normalizedContactRecycleDistance; + R2Bool frictionInBiasPass; + R2Bool warmstartJoints; +#if defined(RAPIER_DIM3) + uint32_t frictionModel; +#endif +} R2IntegrationParameters; + +/** + * Status-returning operations use these integer codes. + */ +typedef uint32_t R2Status; + +typedef struct R2Vector { + R2Real x; + R2Real y; +#if defined(RAPIER_DIM3) + R2Real z; +#endif +} R2Vector; + +/** + * 2D: angle in radians. 3D: unit quaternion in x,y,z,w order (normalized on input). + */ +typedef struct R2Rotation { +#if defined(RAPIER_DIM2) + R2Real angle; +#endif +#if defined(RAPIER_DIM3) + R2Real x; +#endif +#if defined(RAPIER_DIM3) + R2Real y; +#endif +#if defined(RAPIER_DIM3) + R2Real z; +#endif +#if defined(RAPIER_DIM3) + R2Real w; +#endif +} R2Rotation; + +typedef struct R2Pose { + struct R2Vector translation; + struct R2Rotation rotation; +} R2Pose; + +typedef struct R2JointLimits { + R2Real min; + R2Real max; +} R2JointLimits; + +typedef struct R2JointMotor { + R2Real targetVel; + R2Real targetPos; + R2Real stiffness; + R2Real damping; + R2Real maxForce; + uint32_t model; +} R2JointMotor; + +typedef struct R2UserData { + uint64_t low; + uint64_t high; +} R2UserData; + +/** + * Copyable joint configuration. Limits/motors take effect when their axis mask is enabled. + * Solver impulses are deliberately excluded. Applying data resets cached limit and motor impulses. + */ +typedef struct R2JointDesc { + struct R2Pose localFrame1; + struct R2Pose localFrame2; + uint8_t lockedAxes; + uint8_t limitAxes; + uint8_t motorAxes; + uint8_t coupledAxes; + struct R2JointLimits limits[R2_JOINT_DOF_COUNT]; + struct R2JointMotor motors[R2_JOINT_DOF_COUNT]; + struct R2SpringCoefficients softness; + R2Bool contactsEnabled; + R2Bool enabled; + struct R2UserData userData; +} R2JointDesc; + +/** + * Copyable non-owning handle: world pointer plus entity index and generation. + * The world must remain alive throughout every use. Copying does not retain it. + * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + */ +typedef struct R2ImpulseJointHandle { + /** + * Borrowed owning world. Never use this handle after freeing that world. + */ + struct R2World *world; + uint32_t index; + uint32_t generation; +} R2ImpulseJointHandle; + +/** + * Copyable non-owning handle: world pointer plus entity index and generation. + * The world must remain alive throughout every use. Copying does not retain it. + * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + */ +typedef struct R2RigidBodyHandle { + /** + * Borrowed owning world. Never use this handle after freeing that world. + */ + struct R2World *world; + uint32_t index; + uint32_t generation; +} R2RigidBodyHandle; + +/** + * Copyable non-owning handle: world pointer plus entity index and generation. + * The world must remain alive throughout every use. Copying does not retain it. + * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + */ +typedef struct R2MultibodyJointHandle { + /** + * Borrowed owning world. Never use this handle after freeing that world. + */ + struct R2World *world; + uint32_t index; + uint32_t generation; +} R2MultibodyJointHandle; + +/** + * Parameters for the native volumetric mesher. Enclosure: 0 cover, 1 crust (3D). + */ +typedef struct R2VolumeMeshParameters { + R2Real cell_size; +#if defined(RAPIER_DIM2) + R2Real min_angle; +#endif +#if defined(RAPIER_DIM3) + uint32_t enclosure; +#endif +#if defined(RAPIER_DIM3) + uint32_t cover_smoothing; +#endif +#if defined(RAPIER_DIM3) + R2Real cover_guard; +#endif +#if defined(RAPIER_DIM3) + uint32_t cover_subdivisions; +#endif +} R2VolumeMeshParameters; + +/** + * Borrowed array of vector elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R2VectorView { + const struct R2Vector *data; + size_t count; +} R2VectorView; + +/** + * Borrowed array of real elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R2RealView { + const R2Real *data; + size_t count; +} R2RealView; + +/** + * Borrowed array of index elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R2IndexView { + const uint32_t *data; + size_t count; +} R2IndexView; + +/** + * Vertex indices for one edge; contiguous u32 fields with no padding. + */ +typedef struct R2Edge { + uint32_t a; + uint32_t b; +} R2Edge; + +/** + * Borrowed array of edge elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R2EdgeView { + const struct R2Edge *data; + size_t count; +} R2EdgeView; + +typedef struct R2SoftEdgeSoftness { + uint32_t edge; + struct R2SpringCoefficients softness; +} R2SoftEdgeSoftness; + +/** + * Borrowed elements; count counts elements. Data must remain live through insertion. + */ +typedef struct R2SoftEdgeSoftnessView { + const struct R2SoftEdgeSoftness *data; + size_t count; +} R2SoftEdgeSoftnessView; + +typedef struct R2SoftEdgeTear { + uint32_t edge; + R2Real resistance; +} R2SoftEdgeTear; + +/** + * Borrowed elements; count counts elements. Data must remain live through insertion. + */ +typedef struct R2SoftEdgeTearView { + const struct R2SoftEdgeTear *data; + size_t count; +} R2SoftEdgeTearView; + +/** + * Vertex indices for one triangle; contiguous u32 fields with no padding. + */ +typedef struct R2Triangle { + uint32_t a; + uint32_t b; + uint32_t c; +} R2Triangle; + +/** + * Borrowed array of triangle elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R2TriangleView { + const struct R2Triangle *data; + size_t count; +} R2TriangleView; + +/** + * Vertex indices for one tetrahedron; contiguous u32 fields with no padding. + */ +typedef struct R2Tetrahedron { + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; +} R2Tetrahedron; + +/** + * Borrowed array of tetrahedron elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R2TetrahedronView { + const struct R2Tetrahedron *data; + size_t count; +} R2TetrahedronView; + +#if defined(RAPIER_DIM2) +typedef struct R2TriangleView R2CellView; +#endif + +#if defined(RAPIER_DIM3) +typedef struct R2TetrahedronView R2CellView; +#endif + +#if defined(RAPIER_DIM2) +typedef struct R2EdgeView R2SurfaceElementView; +#endif + +#if defined(RAPIER_DIM3) +typedef struct R2TriangleView R2SurfaceElementView; +#endif + +/** + * Vertex indices for one dihedral; contiguous u32 fields with no padding. + */ +typedef struct R2Dihedral { + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; +} R2Dihedral; + +/** + * Borrowed array of dihedral elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R2DihedralView { + const struct R2Dihedral *data; + size_t count; +} R2DihedralView; + +/** + * Optional boolean override. When disabled, retain the recipe's native default. + */ +typedef struct R2OptionalBool { + R2Bool enabled; + R2Bool value; +} R2OptionalBool; + +/** + * Opaque SharedShape. See the ownership and borrowing contract in README.md. + */ +typedef struct R2SharedShape R2SharedShape; + + +/** + * Borrowed elements; count counts elements. Data must remain live through insertion. + */ +typedef struct R2CompoundShapeView { + const struct R2CompoundShapeDesc *data; + size_t count; +} R2CompoundShapeView; + +/** + * Non-owning shape description. Only fields selected by kind are read. + * a = cuboid half extents, capsule/segment endpoint, triangle vertex, or halfspace normal. + * b/c = remaining endpoints/vertices. radius is also the rounded-cuboid border radius. + * Mesh views count edges or triangles; heightfields are column-major. + * Arrays, compound children, and sharedShape remain borrowed until build/insert returns. + */ +typedef struct R2ShapeDesc { + uint32_t kind; + struct R2Vector a; + struct R2Vector b; + struct R2Vector c; + R2Real radius; + R2Real halfHeight; + R2Real borderRadius; + struct R2VectorView vertices; + struct R2TriangleView triangles; + struct R2EdgeView edges; + uint32_t flags; + struct R2RealView heights; + size_t rows; + size_t columns; + struct R2Vector scale; + const R2SharedShape *sharedShape; + struct R2CompoundShapeView children; +} R2ShapeDesc; + +typedef struct R2CompoundShapeDesc { + struct R2Pose pose; + struct R2ShapeDesc shape; +} R2CompoundShapeDesc; + + +#if defined(RAPIER_DIM2) +typedef R2Real R2AngVector; +#endif + +#if defined(RAPIER_DIM3) +typedef struct R2Vector R2AngVector; +#endif + +/** + * Explicit mass and principal inertia, matching MassProperties constructors. Zero mass/inertia means infinite. + */ +typedef struct R2MassProperties { + struct R2Vector local_com; + R2Real mass; + R2AngVector principal_inertia; +#if defined(RAPIER_DIM3) + struct R2Rotation principal_inertia_local_frame; +#endif +} R2MassProperties; + +typedef struct R2InteractionGroups { + uint32_t memberships; + uint32_t filter; + uint32_t test_mode; +} R2InteractionGroups; + +/** + * Copyable collider construction data. Shape inputs are borrowed, never owned. + */ +typedef struct R2ColliderDesc { + struct R2ShapeDesc shape; + struct R2Pose position; + uint32_t massMode; + R2Real density; + R2Real mass; + struct R2MassProperties massProperties; + R2Real friction; + R2Real restitution; + uint32_t frictionCombineRule; + uint32_t restitutionCombineRule; + R2Bool isSensor; + R2Bool enabled; + struct R2InteractionGroups collisionGroups; + struct R2InteractionGroups solverGroups; + uint16_t activeCollisionTypes; + uint32_t activeHooks; + uint32_t activeEvents; + R2Real contactForceEventThreshold; + R2Real contactSkin; + struct R2UserData userData; +} R2ColliderDesc; + +/** + * Copyable recipe, not an owned procedural builder. Initialize before editing. + * All array views borrow caller data until build/insert returns; counts + * for topology arrays are element counts (edges, triangles, or tetrahedra). + * Nonempty topology overrides the generator's topology. Zero counts retain it. + * Generator inputs: a/b are rope ends or center/half-extents; cloth uses a/du/dv. + */ +typedef struct R2SoftBodyDesc { + uint32_t kind; + struct R2Vector a; + struct R2Vector b; + struct R2Vector du; + struct R2Vector dv; + size_t nx; + size_t ny; + size_t nz; + R2Real radius; + R2Real radiusEnd; + struct R2Vector translation; + struct R2OptionalReal totalMass; + struct R2VolumeMeshParameters meshing; + struct R2VectorView positions; + struct R2RealView masses; + struct R2IndexView pinned; + struct R2EdgeView edges; + struct R2EdgeView bendEdges; + struct R2IndexView tensionOnlyEdges; + struct R2SoftEdgeSoftnessView edgeSoftness; + struct R2SoftEdgeTearView edgeTearResistance; + R2CellView cells; + R2SurfaceElementView surface; +#if defined(RAPIER_DIM3) + struct R2DihedralView dihedrals; +#endif +#if defined(RAPIER_DIM3) + struct R2EdgeView wire; +#endif + struct R2VectorView skinVertices; + R2SurfaceElementView skinIndices; + struct R2SoftBodyMaterial material; + uint32_t cellModel; + /** + * 0 = constraints, 1 = FEM (requires a library built with FEM). + */ + uint32_t solver; + R2Real particleMass; + /** + * Disabled by default: retain the radius computed by the generator. + */ + struct R2OptionalReal particleRadius; + R2Bool volumePreservation; + R2Real volumeFactor; + struct R2OptionalBool shapeMatching; + R2Bool selfContacts; + R2Bool skinCollision; + R2Bool collisionEnabled; + struct R2ColliderDesc collider; + R2Real linearDamping; + R2Real gravityScale; + size_t additionalSolverIterations; + size_t additionalPgsIterations; + R2Bool canSleep; + int8_t dominanceGroup; + struct R2UserData userData; +} R2SoftBodyDesc; + +/** + * Copyable non-owning handle: world pointer plus entity index and generation. + * The world must remain alive throughout every use. Copying does not retain it. + * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + */ +typedef struct R2SoftBodyHandle { + /** + * Borrowed owning world. Never use this handle after freeing that world. + */ + struct R2World *world; + uint32_t index; + uint32_t generation; +} R2SoftBodyHandle; + +/** + * Non-owning deformable binding description. Direct particle indices are borrowed. + */ +typedef struct R2SoftMeshBindingDesc { + uint32_t kind; + struct R2IndexView particles; + R2Real epsilon; + R2Bool selfContacts; +} R2SoftMeshBindingDesc; + +/** + * Copyable non-owning handle: world pointer plus entity index and generation. + * The world must remain alive throughout every use. Copying does not retain it. + * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + */ +typedef struct R2ColliderHandle { + /** + * Borrowed owning world. Never use this handle after freeing that world. + */ + struct R2World *world; + uint32_t index; + uint32_t generation; +} R2ColliderHandle; + +typedef struct R2QueryFilter { + uint32_t flags; + R2Bool use_groups; + struct R2InteractionGroups groups; + struct R2ColliderHandle exclude_collider; + struct R2RigidBodyHandle exclude_rigid_body; +} R2QueryFilter; + +/** + * Called with scoped read access and a collider handle. Shared queries may nest; + * world mutations are rejected until the outer query returns. Never retain the context. + */ +typedef R2Bool (RAPIER_CALL *R2QueryPredicate)(void *user_data, + const struct R2ReadContext *read, + struct R2ColliderHandle handle); + +/** + * Copyable query settings. They borrow callback data, never world components. + */ +typedef struct R2QueryOptions { + struct R2QueryFilter filter; + R2QueryPredicate predicate; + void *userData; +} R2QueryOptions; + +typedef struct R2RayHit { + struct R2ColliderHandle collider; + R2Real time_of_impact; + struct R2Vector normal; + uint32_t feature_type; + uint32_t feature_id; +} R2RayHit; + +typedef struct R2PointProjection { + struct R2ColliderHandle collider; + struct R2Vector point; + R2Bool is_inside; +} R2PointProjection; + +typedef struct R2ShapeCastHit { + struct R2ColliderHandle collider; + R2Real time_of_impact; + struct R2Vector witness1; + struct R2Vector witness2; + struct R2Vector normal1; + struct R2Vector normal2; + uint32_t status; +} R2ShapeCastHit; + +typedef struct R2ShapeCastOptions { + R2Real max_time_of_impact; + R2Real target_distance; + R2Bool stop_at_penetration; + R2Bool compute_impact_geometry_on_penetration; +} R2ShapeCastOptions; + +typedef struct R2Aabb { + struct R2Vector mins; + struct R2Vector maxs; +} R2Aabb; + +typedef struct R2RayToi { + struct R2ColliderHandle collider; + R2Real toi; + R2Bool found; +} R2RayToi; + +typedef struct R2OptionalRayHit { + struct R2RayHit hit; + R2Bool found; +} R2OptionalRayHit; + +/** + * Stack-allocated rigid-body construction data. Initialize with RigidBodyDescInit. + * Copying this value is safe; it owns no resources and must never be freed by Rapier. + */ +typedef struct R2RigidBodyDesc { + struct R2Pose position; + struct R2Vector linvel; + R2AngVector angvel; + uint32_t bodyType; + R2Real gravityScale; + R2Real linearDamping; + R2Real angularDamping; + R2Real additionalMass; + R2Bool useAdditionalMassProperties; + struct R2MassProperties additionalMassProperties; + uint8_t lockedAxes; + R2Bool canSleep; + R2Bool sleeping; + R2Bool ccdEnabled; + R2Real softCcdPrediction; + R2Bool allowFastRotation; + R2Bool enabled; + int8_t dominanceGroup; + size_t additionalSolverIterations; + size_t additionalPgsIterations; + R2Bool gyroscopicForcesEnabled; + struct R2UserData userData; +} R2RigidBodyDesc; + +/** + * Sizes of the POD types in this library build, for foreign-language layout checks. + */ +typedef struct R2PodLayout { + size_t rigidBodyDesc; + size_t colliderDesc; + size_t shapeDesc; + size_t jointDesc; + size_t softBodyMaterial; + size_t integrationParameters; + size_t softBodyDesc; + size_t softMeshBindingDesc; + size_t queryOptions; + /** + * Zero unless 3D f32 robotics is enabled. + */ + size_t urdfLoaderOptions; + /** + * Zero unless 3D f32 robotics is enabled. + */ + size_t mjcfLoaderOptions; +} R2PodLayout; + +/** + * CharacterLength counterpart: relative=1 scales with character height, relative=0 uses world units. + */ +typedef struct R2CharacterLength { + R2Real value; + R2Bool relative; +} R2CharacterLength; + +typedef struct R2CharacterMovement { + struct R2Vector translation; + R2Bool grounded; + R2Bool is_sliding_down_slope; +} R2CharacterMovement; + +typedef struct R2CharacterCollision { + struct R2ColliderHandle collider; + struct R2Pose character_pos; + struct R2Vector translation_applied; + struct R2Vector translation_remaining; + struct R2ShapeCastHit hit; +} R2CharacterCollision; + +typedef struct R2PidGains { + struct R2Vector lin_kp; + struct R2Vector lin_ki; + struct R2Vector lin_kd; + R2AngVector ang_kp; + R2AngVector ang_ki; + R2AngVector ang_kd; +} R2PidGains; + +typedef struct R2VelocityCorrection { + struct R2Vector linear; + R2AngVector angularVelocity; +} R2VelocityCorrection; + +typedef struct R2CharacterControllerSettings { + R2Bool slide; + R2Real max_slope_climb_angle; + R2Real min_slope_slide_angle; + R2Bool snap_to_ground; + struct R2CharacterLength snap_distance; +} R2CharacterControllerSettings; + +#if defined(RAPIER_DIM3) +typedef struct R2WheelTuning { + R2Real suspension_stiffness; + R2Real suspension_compression; + R2Real suspension_damping; + R2Real max_suspension_travel; + R2Real friction_slip; + R2Real max_suspension_force; + R2Real side_friction_stiffness; +} R2WheelTuning; +#endif + +#if defined(RAPIER_DIM3) +typedef struct R2WheelState { + struct R2Vector center; + struct R2Vector suspension; + struct R2Vector axle; + R2Real rotation; + R2Real suspension_force; + R2Real suspension_length; + R2Bool is_in_contact; + struct R2ColliderHandle ground_object; + struct R2Vector contact_point; + struct R2Vector contact_normal; +} R2WheelState; +#endif + +/** + * Called synchronously on the calling thread when an operation reports an error. + * The diagnostic is borrowed for the duration of the callback. The callback + * must return normally or terminate the process: never throw or longjmp across + * the Rust/C boundary. Nested failing calls do not invoke the handler recursively. + */ +typedef void (RAPIER_CALL *R2ErrorCallback)(R2Status, const char*, void*); + +/** + * An optional thread-local error handler. A null callback disables reporting. + * Keep the callback and user_data alive until the handler is replaced. + */ +typedef struct R2ErrorHandler { + R2ErrorCallback callback; + void *user_data; +} R2ErrorHandler; + +typedef struct R2JointBodies { + struct R2RigidBodyHandle body1; + struct R2RigidBodyHandle body2; +} R2JointBodies; + +typedef struct R2InverseKinematicsOptions { + R2Real damping; + size_t max_iters; + uint8_t constrained_axes; + R2Real epsilon_linear; + R2Real epsilon_angular; +} R2InverseKinematicsOptions; + +/** + * Optional per-link filter, called synchronously. Must not reenter or retain physics objects. + */ +typedef R2Bool (RAPIER_CALL *R2IkJointCanMove)(void*, struct R2RigidBodyHandle); + +/** + * Collision start/stop flags match Rapier CollisionEventFlags. + */ +typedef struct R2CollisionEvent { + struct R2ColliderHandle collider1; + struct R2ColliderHandle collider2; + R2Bool started; + uint32_t flags; +} R2CollisionEvent; + +typedef struct R2ContactForceEvent { + struct R2ColliderHandle collider1; + struct R2ColliderHandle collider2; + struct R2Vector total_force; + R2Real total_force_magnitude; + struct R2Vector max_force_direction; + R2Real max_force_magnitude; + R2Bool started; +} R2ContactForceEvent; + +/** + * Pair callback: -1 rejects a contact pair; 0 detects contacts without impulses; 1 computes impulses. + * For sensor intersections only, zero rejects and any positive value accepts. + */ +typedef int32_t (RAPIER_CALL *R2PairFilter)(void *user_data, + const struct R2ReadContext *read, + struct R2ColliderHandle collider1, + struct R2ColliderHandle collider2, + struct R2RigidBodyHandle body1, + struct R2RigidBodyHandle body2); + +/** + * Mutable per-manifold properties. Set enabled=0 to discard all its solver contacts. + */ +typedef struct R2ContactModification { + struct R2Vector normal; + R2Real friction; + R2Real restitution; + uint32_t user_data; + R2Bool enabled; +} R2ContactModification; + +typedef void (RAPIER_CALL *R2ModifyContacts)(void *user_data, + const struct R2ReadContext *read, + struct R2ColliderHandle collider1, + struct R2ColliderHandle collider2, + struct R2ContactModification *contact); + +typedef void (RAPIER_CALL *R2ModifyContactContext)(void *user_data, + const struct R2ReadContext *read, + struct R2ColliderHandle collider1, + struct R2ColliderHandle collider2, + struct R2ContactModificationContext *context); + +/** + * Callbacks must not unwind or retain arguments. Use their ReadContext to inspect bodies and + * colliders; ordinary access to the stepping world returns WORLD_BUSY. Mutations must be + * performed after stepping. With parallel builds + * callbacks and their user_data must be safe for concurrent invocation. NULL callbacks use defaults. + */ +typedef struct R2PhysicsHooks { + void *user_data; + R2PairFilter filter_contact_pair; + R2PairFilter filter_intersection_pair; + R2ModifyContacts modify_solver_contacts; + /** + * Runs after the legacy property callback. Context accessors may be called here. + */ + R2ModifyContactContext modify_solver_contacts_context; +} R2PhysicsHooks; + +/** + * Borrowed bytes. Valid while the source Bytes object remains alive; never free data. + */ +typedef struct R2ByteView { + const uint8_t *data; + size_t count; +} R2ByteView; + +typedef struct R2DebugLine { + struct R2Vector a; + struct R2Vector b; + float color[4]; +} R2DebugLine; + +typedef struct R2ParticleDestination { + struct R2SoftBodyHandle body; + uint32_t index; +} R2ParticleDestination; + +typedef struct R2SoftClusterSplit { + uint32_t source_cluster; + struct R2SoftBodyHandle soft_body; + uint32_t cluster; + struct R2RigidBodyHandle proxy; + R2Bool keeps_proxy; +} R2SoftClusterSplit; + +typedef struct R2SoftJointMove { + struct R2ImpulseJointHandle joint; + struct R2RigidBodyHandle from; + struct R2RigidBodyHandle to; +} R2SoftJointMove; + +typedef struct R2OptionalParticleDestination { + struct R2SoftBodyHandle body; + uint32_t index; + R2Bool found; +} R2OptionalParticleDestination; + +typedef struct R2BuildInfo { + uint32_t abi_version; + uint32_t dimension; + uint32_t real_size; + uint32_t pointer_size; +} R2BuildInfo; + +/** + * Features available through the loaded C library, independent of consumer defines. + */ +typedef struct R2BuildFeatures { + /** + * Whether native profiling timers were compiled in. + */ + R2Bool profiling; + /** + * Solver SIMD lane count. Hardware instruction width depends on the target CPU. + */ + uint32_t simd_lanes; + /** + * Whether this library exposes Rapier's parallel execution and thread-pool APIs. + */ + R2Bool parallel; +} R2BuildFeatures; + +typedef struct R2ContactPair { + struct R2ColliderHandle collider1; + struct R2ColliderHandle collider2; + R2Bool has_any_active_contact; + struct R2Vector total_impulse; + R2Real total_impulse_magnitude; + R2Real max_impulse; + struct R2Vector max_impulse_direction; +} R2ContactPair; + +typedef struct R2IntersectionPair { + struct R2ColliderHandle collider1; + struct R2ColliderHandle collider2; + R2Bool intersecting; +} R2IntersectionPair; + +typedef struct R2ContactPoint { + size_t manifold_index; + struct R2Vector local_p1; + struct R2Vector local_p2; + struct R2Vector normal; + R2Real distance; + R2Real impulse; +} R2ContactPoint; + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Loader configuration. Initialize with DefaultUrdfLoaderOptions; no destructor. + * Blueprint array views and shared shapes are borrowed through the load call. + */ +typedef struct R2UrdfLoaderOptions { + R2Bool createCollidersFromCollisionShapes; + R2Bool createCollidersFromVisualShapes; + R2Bool applyImportedMassProps; + R2Bool enableJointCollisions; + R2Bool makeRootsFixed; + R2Bool squeezeEmptyFixedLinks; + struct R2Pose shift; + R2Real scale; + struct R2ColliderDesc colliderBlueprint; + struct R2RigidBodyDesc rigidBodyBlueprint; +} R2UrdfLoaderOptions; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Loader configuration. Initialize with DefaultMjcfLoaderOptions; no destructor. + * Blueprint array views and shared shapes are borrowed through the load call. + */ +typedef struct R2MjcfLoaderOptions { + R2Bool createCollidersFromCollisionShapes; + R2Bool createCollidersFromVisualShapes; + R2Bool applyImportedMassProps; + R2Bool enableJointCollisions; + R2Bool makeRootsFixed; + R2Bool skipPlaneGeoms; + R2Bool disableJointMotors; + struct R2Pose shift; + R2Real scale; + struct R2ColliderDesc colliderBlueprint; + struct R2RigidBodyDesc rigidBodyBlueprint; +} R2MjcfLoaderOptions; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * A borrowed visual declaration, valid until its robot is freed or its body storage changes. + */ +typedef struct R2MjcfVisualMesh R2MjcfVisualMesh; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R2RenderMaterial { + float metallic; + float roughness; + float reflectance; + float emissive[3]; +} R2RenderMaterial; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R2MjcfVisualMeshInfo { + struct R2Pose local_pose; + float rgba[4]; + struct R2RenderMaterial material; + R2Bool has_color; + R2Bool has_material; + R2Bool is_trimesh; +} R2MjcfVisualMeshInfo; +#endif + +/** + * A copied state snapshot, with no pointers or ownership obligations. + */ +typedef struct R2RigidBodyState { + struct R2Pose position; + struct R2Vector linvel; + R2AngVector angvel; + R2Bool sleeping; + R2Bool enabled; + struct R2UserData userData; +} R2RigidBodyState; + +/** + * Stable identity of a live mesh within one soft body; matches Rapier's SoftMeshId. + */ +typedef struct R2SoftMeshId { + uint32_t cluster; + uint32_t mesh; +} R2SoftMeshId; + +/** + * Mesh identity and rendering metadata. A render-only mesh has an invalid collider handle. + */ +typedef struct R2SoftMeshInfo { + struct R2SoftMeshId id; + struct R2ColliderHandle collider; + size_t arity; + R2Bool is_skinned; + R2Bool collision_enabled; +} R2SoftMeshInfo; + +#if defined(RAPIER_F32) +/** + * Voxel coordinates have DIM signed integer components. + */ +typedef int32_t R2VoxelCoord; +#endif + +#if defined(RAPIER_F64) +typedef int64_t R2VoxelCoord; +#endif + +typedef struct R2VoxelKey { + R2VoxelCoord x; + R2VoxelCoord y; +#if defined(RAPIER_DIM3) + R2VoxelCoord z; +#endif +} R2VoxelKey; + +typedef struct R2VoxelQuery { + struct R2VoxelKey key; + struct R2Vector center; + struct R2Vector size; + R2Bool found; +} R2VoxelQuery; + +#define R2_OK 0 + +#define R2_NULL_POINTER 1 + +#define R2_INVALID_ARGUMENT 2 + +#define R2_INVALID_HANDLE 3 + +#define R2_BUFFER_TOO_SMALL 4 + +#define R2_UNSUPPORTED 5 + +#define R2_PANIC 6 + +#define R2_NOT_FOUND 7 + +/** + * Conflicting or reentrant access to simulation state. No mutation was performed. + */ +#define R2_WORLD_BUSY 8 + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +RAPIER_API RAPIER_CALL struct R2SoftBodyMaterial r2DefaultSoftBodyMaterial(void); + +RAPIER_API RAPIER_CALL struct R2SoftRecoverySettings r2DefaultSoftRecoverySettings(void); + +#if defined(RAPIER_FEM) +RAPIER_API RAPIER_CALL struct R2SoftFemParameters r2DefaultSoftFemParameters(void); +#endif + +RAPIER_API RAPIER_CALL struct R2SoftBodiesSettings r2DefaultSoftBodiesSettings(void); + +RAPIER_API RAPIER_CALL struct R2IntegrationParameters r2DefaultIntegrationParameters(void); + +RAPIER_API RAPIER_CALL +struct R2IntegrationParameters r2IntegrationParameters(const struct R2World *world); + +/** + * Copies validated values; does not expose a writable alias to Rust memory. + */ +RAPIER_API RAPIER_CALL +R2Status r2SetIntegrationParameters(struct R2World *world, + const struct R2IntegrationParameters *data); + +RAPIER_API RAPIER_CALL struct R2JointDesc r2DefaultJointDesc(void); + +RAPIER_API RAPIER_CALL struct R2JointDesc r2FixedJointDesc(void); + +#if defined(RAPIER_DIM2) +RAPIER_API RAPIER_CALL struct R2JointDesc r2RevoluteJointDesc(void); +#endif + +#if defined(RAPIER_DIM3) +/** + * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + */ +RAPIER_API RAPIER_CALL struct R2JointDesc r2RevoluteJointDesc(struct R2Vector axis_vector); +#endif + +/** + * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + */ +RAPIER_API RAPIER_CALL struct R2JointDesc r2PrismaticJointDesc(struct R2Vector axis_vector); + +RAPIER_API RAPIER_CALL struct R2JointDesc r2RopeJointDesc(R2Real length); + +RAPIER_API RAPIER_CALL +struct R2JointDesc r2SpringJointDesc(R2Real length, + R2Real stiffness, + R2Real damping); + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL struct R2JointDesc r2SphericalJointDesc(void); +#endif + +#if defined(RAPIER_DIM2) +/** + * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + */ +RAPIER_API RAPIER_CALL struct R2JointDesc r2PinSlotJointDesc(struct R2Vector axis_vector); +#endif + +RAPIER_API RAPIER_CALL +struct R2ImpulseJointHandle r2InsertImpulseJoint(struct R2RigidBodyHandle body1, + struct R2RigidBodyHandle body2, + const struct R2JointDesc *joint); + +RAPIER_API RAPIER_CALL +struct R2MultibodyJointHandle r2InsertMultibodyJoint(struct R2RigidBodyHandle body1, + struct R2RigidBodyHandle body2, + const struct R2JointDesc *joint); + +RAPIER_API RAPIER_CALL struct R2SoftBodyDesc r2DefaultSoftBodyDesc(void); + +/** + * Consumes no caller-owned resources. All borrowed arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyHandle r2InsertSoftBody(struct R2World *world, + const struct R2SoftBodyDesc *desc); + +RAPIER_API RAPIER_CALL struct R2SoftMeshBindingDesc r2DefaultSoftMeshBindingDesc(void); + +RAPIER_API RAPIER_CALL +struct R2ColliderHandle r2InsertDeformableCollider(const struct R2ColliderDesc *collider, + const struct R2SoftMeshBindingDesc *binding, + struct R2RigidBodyHandle parent); + +RAPIER_API RAPIER_CALL struct R2QueryOptions r2DefaultQueryOptions(void); + +RAPIER_API RAPIER_CALL +struct R2RayHit r2CastRay(const struct R2World *world, + const struct R2QueryOptions *query_options, + struct R2Vector origin, + struct R2Vector direction, + R2Real max_toi, + R2Bool solid); + +RAPIER_API RAPIER_CALL +struct R2PointProjection r2ProjectPoint(const struct R2World *world, + const struct R2QueryOptions *query_options, + struct R2Vector point, + R2Real max_distance, + R2Bool solid); + +RAPIER_API RAPIER_CALL +struct R2ShapeCastHit r2CastShape(const struct R2World *world, + const struct R2QueryOptions *query_options, + struct R2Pose pose, + struct R2Vector velocity, + const R2SharedShape *shape, + struct R2ShapeCastOptions options); + +RAPIER_API RAPIER_CALL +size_t r2IntersectPoint(const struct R2World *world, + const struct R2QueryOptions *query_options, + struct R2Vector point, + struct R2ColliderHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r2IntersectShape(const struct R2World *world, + const struct R2QueryOptions *query_options, + struct R2Pose pose, + const R2SharedShape *shape, + struct R2ColliderHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r2IntersectAabbConservative(const struct R2World *world, + const struct R2QueryOptions *query_options, + struct R2Aabb aabb, + struct R2ColliderHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +struct R2RayToi r2CastRayToi(const struct R2World *world, + const struct R2QueryOptions *query_options, + struct R2Vector origin, + struct R2Vector direction, + R2Real max_toi, + R2Bool solid); + +RAPIER_API RAPIER_CALL +struct R2OptionalRayHit r2TryCastRay(const struct R2World *world, + const struct R2QueryOptions *query_options, + struct R2Vector origin, + struct R2Vector direction, + R2Real max_toi, + R2Bool solid); + +RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2DynamicRigidBodyDesc(void); + +RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2FixedRigidBodyDesc(void); + +RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2KinematicPositionBasedRigidBodyDesc(void); + +RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2KinematicVelocityBasedRigidBodyDesc(void); + +RAPIER_API RAPIER_CALL struct R2ShapeDesc r2DefaultShapeDesc(void); + +RAPIER_API RAPIER_CALL R2SharedShape *r2ShapeDesc_Build(const struct R2ShapeDesc *desc); + +RAPIER_API RAPIER_CALL struct R2ColliderDesc r2DefaultColliderDesc(void); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL struct R2ColliderDesc r2BallColliderDesc(R2Real radius); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderDesc r2CuboidColliderDesc(struct R2Vector half_extents); + +RAPIER_API RAPIER_CALL +struct R2RigidBodyHandle r2InsertRigidBody(struct R2World *world, + const struct R2RigidBodyDesc *desc); + +/** + * Insert a collider attached to a rigid body, using the world stored in its handle. + * The parent handle is copied by value. The description is borrowed through this call. + * Invalid or removed parents fail without inserting a collider. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderHandle r2InsertCollider(struct R2RigidBodyHandle parent, + const struct R2ColliderDesc *desc); + +/** + * Insert a collider without a rigid-body parent. The world owns the collider. + * The description is borrowed through this call. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderHandle r2InsertColliderWithoutParent(struct R2World *world, + const struct R2ColliderDesc *desc); + +RAPIER_API RAPIER_CALL struct R2PodLayout r2PodLayout(void); + +RAPIER_API RAPIER_CALL +struct R2KinematicCharacterController *r2NewKinematicCharacterController(void); + +RAPIER_API RAPIER_CALL +R2Status r2FreeKinematicCharacterController(struct R2KinematicCharacterController *controller); + +RAPIER_API RAPIER_CALL +R2Status r2KinematicCharacterController_SetUp(struct R2KinematicCharacterController *controller, + struct R2Vector up); + +RAPIER_API RAPIER_CALL +R2Status r2KinematicCharacterController_SetOffset(struct R2KinematicCharacterController *controller, + struct R2CharacterLength offset); + +RAPIER_API RAPIER_CALL +R2Status r2KinematicCharacterController_SetSlide(struct R2KinematicCharacterController *controller, + R2Bool enabled); + +RAPIER_API RAPIER_CALL +R2Status r2KinematicCharacterController_SetSlopes(struct R2KinematicCharacterController *controller, + R2Real max_climb_angle, + R2Real min_slide_angle); + +RAPIER_API RAPIER_CALL +R2Status r2KinematicCharacterController_SetAutostep(struct R2KinematicCharacterController *controller, + R2Bool enabled, + struct R2CharacterLength max_height, + struct R2CharacterLength min_width, + R2Bool include_dynamic_bodies); + +RAPIER_API RAPIER_CALL +R2Status r2KinematicCharacterController_SetSnapToGround(struct R2KinematicCharacterController *controller, + R2Bool enabled, + struct R2CharacterLength distance); + +/** + * Computes movement without moving any collider. Use the returned translation to set the character target. + */ +RAPIER_API RAPIER_CALL +struct R2CharacterMovement r2KinematicCharacterController_MoveShape(const struct R2World *world, + const struct R2QueryOptions *options, + struct R2KinematicCharacterController *controller, + R2Real dt, + const R2SharedShape *shape, + struct R2Pose pose, + struct R2Vector desired_translation); + +RAPIER_API RAPIER_CALL +size_t r2KinematicCharacterController_Collisions(const struct R2KinematicCharacterController *controller, + struct R2CharacterCollision *buffer, + size_t capacity); + +/** + * Applies impulses for the most recent move_shape collisions. Use the same world, shape, dt and filter. + */ +RAPIER_API RAPIER_CALL +R2Status r2KinematicCharacterController_SolveCharacterCollisionImpulses(const struct R2KinematicCharacterController *controller, + const R2SharedShape *shape, + R2Real dt, + R2Real mass, + const struct R2QueryFilter *filter); + +RAPIER_API RAPIER_CALL struct R2PidController *r2NewPidController(void); + +RAPIER_API RAPIER_CALL R2Status r2FreePidController(struct R2PidController *controller); + +RAPIER_API RAPIER_CALL +struct R2PidGains r2PidController_Gains(const struct R2PidController *controller); + +RAPIER_API RAPIER_CALL +R2Status r2PidController_SetGains(struct R2PidController *controller, + struct R2PidGains gains); + +/** + * AxesMask bits match Rapier: linear X/Y/Z are 1/2/4, angular X/Y/Z are 8/16/32. + */ +RAPIER_API RAPIER_CALL +R2Status r2PidController_SetAxes(struct R2PidController *controller, + uint32_t axes); + +/** + * Compute a velocity correction, preserving the body's state and updating PID integrals. + */ +RAPIER_API RAPIER_CALL +struct R2VelocityCorrection r2PidController_RigidBodyCorrection(struct R2PidController *controller, + R2Real dt, + struct R2RigidBodyHandle body, + struct R2Pose target_pose, + struct R2Vector target_linvel, + R2AngVector target_angvel); + +RAPIER_API RAPIER_CALL +struct R2CharacterControllerSettings r2KinematicCharacterController_Settings(const struct R2KinematicCharacterController *controller); + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL struct R2WheelTuning r2DefaultWheelTuning(void); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +struct R2DynamicRayCastVehicleController *r2NewDynamicRayCastVehicleController(struct R2RigidBodyHandle chassis); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R2Status r2FreeDynamicRayCastVehicleController(struct R2DynamicRayCastVehicleController *controller); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +size_t r2DynamicRayCastVehicleController_AddWheel(struct R2DynamicRayCastVehicleController *controller, + struct R2Vector connection, + struct R2Vector direction, + struct R2Vector axle, + R2Real rest_length, + R2Real radius, + const struct R2WheelTuning *tuning); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R2Status r2DynamicRayCastVehicleController_SetAxes(struct R2DynamicRayCastVehicleController *controller, + size_t up, + size_t forward); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R2Status r2DynamicRayCastVehicleController_SetWheelControls(struct R2DynamicRayCastVehicleController *controller, + size_t index, + R2Real steering, + R2Real engine_force, + R2Real brake); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R2Status r2DynamicRayCastVehicleController_UpdateVehicle(struct R2DynamicRayCastVehicleController *controller, + R2Real dt, + const struct R2QueryFilter *filter); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R2Real r2DynamicRayCastVehicleController_CurrentVehicleSpeed(const struct R2DynamicRayCastVehicleController *controller); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +size_t r2DynamicRayCastVehicleController_Wheels(const struct R2DynamicRayCastVehicleController *controller, + struct R2WheelState *buffer, + size_t capacity); +#endif + +RAPIER_API RAPIER_CALL +R2Status r2RigidBodyPropagateModifiedBodyPositionsToColliders(struct R2World *world); + +/** + * Copies the island manager's active body handles. + */ +RAPIER_API RAPIER_CALL +size_t r2ActiveRigidBodies(const struct R2World *world, + struct R2RigidBodyHandle *buffer, + size_t capacity); + +/** + * Wake a body by handle, including a soft-body cluster proxy. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_WakeUp(struct R2RigidBodyHandle handle, + R2Bool strong); + +/** + * Replace this thread's error handler and return the previous handler so it can + * be restored at the end of a scope. Status returns are unchanged. A handler + * that returns lets the caller recover by checking the status; a fail-fast + * handler may terminate the process. Includes R2_NOT_FOUND query misses. + */ +RAPIER_API RAPIER_CALL struct R2ErrorHandler r2SetErrorHandler(struct R2ErrorHandler handler); + +/** + * Status of the most recent fallible operation on this thread. Reading this or + * LastError does not clear it. Infallible value constructors do not change it. + * Check immediately after a fallible value-returning operation when recovering + * from errors instead of using a fail-fast error callback. + */ +RAPIER_API RAPIER_CALL R2Status r2LastStatus(void); + +/** + * Thread-local UTF-8 diagnostic, valid until the next fallible call on this thread. + */ +RAPIER_API RAPIER_CALL const char *r2LastError(void); + +RAPIER_API RAPIER_CALL R2SharedShape *r2BallSharedShape(R2Real radius); + +RAPIER_API RAPIER_CALL R2SharedShape *r2CuboidSharedShape(struct R2Vector half_extents); + +RAPIER_API RAPIER_CALL +R2SharedShape *r2RoundCuboidSharedShape(struct R2Vector half_extents, + R2Real border_radius); + +RAPIER_API RAPIER_CALL +R2SharedShape *r2CapsuleSharedShape(struct R2Vector a, + struct R2Vector b, + R2Real radius); + +RAPIER_API RAPIER_CALL +R2SharedShape *r2SegmentSharedShape(struct R2Vector a, + struct R2Vector b); + +RAPIER_API RAPIER_CALL +R2SharedShape *r2TriangleSharedShape(struct R2Vector a, + struct R2Vector b, + struct R2Vector c); + +RAPIER_API RAPIER_CALL R2SharedShape *r2HalfspaceSharedShape(struct R2Vector normal); + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R2SharedShape *r2CylinderSharedShape(R2Real half_height, + R2Real radius); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL R2SharedShape *r2ConeSharedShape(R2Real half_height, R2Real radius); +#endif + +RAPIER_API RAPIER_CALL +R2SharedShape *r2CompoundSharedShape(struct R2CompoundShapeView children); + +RAPIER_API RAPIER_CALL +R2Status r2RemoveCollider(struct R2ColliderHandle handle, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2RemoveImpulseJoint(struct R2ImpulseJointHandle handle, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +size_t r2ImpulseJointHandles(const struct R2World *world, + struct R2ImpulseJointHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +R2Status r2RemoveMultibodyJoint(struct R2MultibodyJointHandle handle, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +size_t r2MultibodyJointHandles(const struct R2World *world, + struct R2MultibodyJointHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +struct R2JointBodies r2ImpulseJoint_Bodies(struct R2ImpulseJointHandle handle); + +RAPIER_API RAPIER_CALL +struct R2InverseKinematicsOptions r2DefaultInverseKinematicsOptions(void); + +RAPIER_API RAPIER_CALL size_t r2MultibodyJoint_Ndofs(struct R2MultibodyJointHandle handle); + +/** + * Read/write displacement buffer must contain exactly ndofs entries; zero it for a fresh solve. + */ +RAPIER_API RAPIER_CALL +R2Status r2MultibodyJoint_InverseKinematics(struct R2MultibodyJointHandle handle, + const struct R2InverseKinematicsOptions *options, + struct R2Pose target, + R2IkJointCanMove can_move, + void *user_data, + R2Real *displacements, + size_t count); + +RAPIER_API RAPIER_CALL +R2Status r2MultibodyJoint_ApplyDisplacements(struct R2MultibodyJointHandle handle, + const R2Real *displacements, + size_t count); + +/** + * Frees an owned object; NULL is allowed. Never free a borrowed pointer. + */ +RAPIER_API RAPIER_CALL R2Status r2FreeSharedShape(R2SharedShape *object); + +/** + * Creates an independent owned copy. + */ +RAPIER_API RAPIER_CALL R2SharedShape *r2SharedShape_Clone(const R2SharedShape *object); + +RAPIER_API RAPIER_CALL size_t r2RigidBodyCount(const struct R2World *world); + +RAPIER_API RAPIER_CALL +size_t r2RigidBodyHandles(const struct R2World *world, + struct R2RigidBodyHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL R2Bool r2RigidBody_Contains(struct R2RigidBodyHandle handle); + +RAPIER_API RAPIER_CALL size_t r2ColliderCount(const struct R2World *world); + +RAPIER_API RAPIER_CALL +size_t r2ColliderHandles(const struct R2World *world, + struct R2ColliderHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL R2Bool r2Collider_Contains(struct R2ColliderHandle handle); + +RAPIER_API RAPIER_CALL size_t r2SoftBodyCount(const struct R2World *world); + +RAPIER_API RAPIER_CALL +size_t r2SoftBodyHandles(const struct R2World *world, + struct R2SoftBodyHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL R2Bool r2SoftBody_Contains(struct R2SoftBodyHandle handle); + +/** + * Remove a body and its joints, optionally keeping colliders as standalone objects. + * Returns whether a body was removed; a stale handle returns false without error. + */ +RAPIER_API RAPIER_CALL +R2Bool r2RemoveRigidBody(struct R2RigidBodyHandle handle, + R2Bool remove_attached_colliders); + +RAPIER_API RAPIER_CALL R2Real r2TimeStep(const struct R2World *world); + +RAPIER_API RAPIER_CALL R2Status r2SetTimeStep(struct R2World *world, R2Real value); + +RAPIER_API RAPIER_CALL R2Real r2MinCcdDt(const struct R2World *world); + +RAPIER_API RAPIER_CALL R2Status r2SetMinCcdDt(struct R2World *world, R2Real value); + +RAPIER_API RAPIER_CALL R2Real r2LengthUnit(const struct R2World *world); + +RAPIER_API RAPIER_CALL R2Status r2SetLengthUnit(struct R2World *world, R2Real value); + +RAPIER_API RAPIER_CALL R2Real r2WarmstartCoefficient(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetWarmstartCoefficient(struct R2World *world, + R2Real value); + +RAPIER_API RAPIER_CALL R2Real r2NormalizedAllowedLinearError(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetNormalizedAllowedLinearError(struct R2World *world, + R2Real value); + +RAPIER_API RAPIER_CALL R2Real r2NormalizedMaxCorrectiveVelocity(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetNormalizedMaxCorrectiveVelocity(struct R2World *world, + R2Real value); + +RAPIER_API RAPIER_CALL R2Real r2NormalizedPredictionDistance(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetNormalizedPredictionDistance(struct R2World *world, + R2Real value); + +RAPIER_API RAPIER_CALL R2Real r2NormalizedMaxLinearVelocity(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetNormalizedMaxLinearVelocity(struct R2World *world, + R2Real value); + +RAPIER_API RAPIER_CALL +R2Real r2NormalizedContactRecycleDistance(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetNormalizedContactRecycleDistance(struct R2World *world, + R2Real value); + +RAPIER_API RAPIER_CALL size_t r2NumSolverIterations(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetNumSolverIterations(struct R2World *world, + size_t value); + +RAPIER_API RAPIER_CALL size_t r2NumInternalPgsIterations(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetNumInternalPgsIterations(struct R2World *world, + size_t value); + +RAPIER_API RAPIER_CALL +size_t r2NumInternalStabilizationIterations(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetNumInternalStabilizationIterations(struct R2World *world, + size_t value); + +RAPIER_API RAPIER_CALL size_t r2MaxCcdSubsteps(const struct R2World *world); + +RAPIER_API RAPIER_CALL R2Status r2SetMaxCcdSubsteps(struct R2World *world, size_t value); + +RAPIER_API RAPIER_CALL R2Bool r2ContactClustering(const struct R2World *world); + +RAPIER_API RAPIER_CALL R2Status r2SetContactClustering(struct R2World *world, R2Bool value); + +RAPIER_API RAPIER_CALL R2Bool r2ContactRecycling(const struct R2World *world); + +RAPIER_API RAPIER_CALL R2Status r2SetContactRecycling(struct R2World *world, R2Bool value); + +RAPIER_API RAPIER_CALL R2Bool r2FrictionInBiasPass(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetFrictionInBiasPass(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL R2Bool r2WarmstartJoints(const struct R2World *world); + +RAPIER_API RAPIER_CALL R2Status r2SetWarmstartJoints(struct R2World *world, R2Bool value); + +RAPIER_API RAPIER_CALL +struct R2SpringCoefficients r2ContactSoftness(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetContactSoftness(struct R2World *world, + struct R2SpringCoefficients value); + +RAPIER_API RAPIER_CALL +struct R2SpringCoefficients r2StaticContactSoftness(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SetStaticContactSoftness(struct R2World *world, + struct R2SpringCoefficients value); + +/** + * Applies Rapier's persistent one-way platform logic to the borrowed manifold. + */ +RAPIER_API RAPIER_CALL +R2Status r2ContactModificationContext_UpdateAsOnewayPlatform(struct R2ContactModificationContext *context, + struct R2Vector allowed_local_n1, + R2Real allowed_angle); + +/** + * Sets the tangent velocity of every rigid solver contact in this manifold. + */ +RAPIER_API RAPIER_CALL +R2Status r2ContactModificationContext_SetTangentVelocity(struct R2ContactModificationContext *context, + struct R2Vector velocity); + +RAPIER_API RAPIER_CALL struct R2EventCollector *r2NewEventCollector(void); + +RAPIER_API RAPIER_CALL R2Status r2FreeEventCollector(struct R2EventCollector *events); + +RAPIER_API RAPIER_CALL R2Status r2EventCollector_Clear(struct R2EventCollector *events); + +RAPIER_API RAPIER_CALL +size_t r2EventCollector_CollisionEvents(const struct R2EventCollector *events, + struct R2CollisionEvent *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r2EventCollector_ContactForceEvents(const struct R2EventCollector *events, + struct R2ContactForceEvent *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r2EventCollector_TearEventCount(const struct R2EventCollector *events); + +RAPIER_API RAPIER_CALL +struct R2SoftBodyTearEvent *r2EventCollector_TearEvent(const struct R2EventCollector *events, + size_t index); + +RAPIER_API RAPIER_CALL struct R2Vector r2Gravity(const struct R2World *world); + +RAPIER_API RAPIER_CALL R2Status r2SetGravity(struct R2World *world, struct R2Vector value); + +/** + * Hooks and events may be NULL. This call invalidates all borrowed set-element pointers. + * Advance simulation by one timestep. Hooks and events may be NULL. + */ +RAPIER_API RAPIER_CALL +R2Status r2Step(struct R2World *world, + const struct R2PhysicsHooks *hooks, + const struct R2EventCollector *events); + +/** + * Refresh collision detection without advancing simulation. Hooks and events may be NULL. + */ +RAPIER_API RAPIER_CALL +R2Status r2DetectCollisions(struct R2World *world, + const struct R2PhysicsHooks *hooks, + const struct R2EventCollector *events); + +RAPIER_API RAPIER_CALL struct R2ByteView r2Bytes_Data(const struct R2Bytes *bytes); + +RAPIER_API RAPIER_CALL R2Status r2FreeBytes(struct R2Bytes *bytes); + +RAPIER_API RAPIER_CALL struct R2Bytes *r2SerializeWorld(const struct R2World *world); + +/** + * Restore ONLY trusted snapshots produced by the identical Rapier build. Snapshots are not a stable file format. + */ +RAPIER_API RAPIER_CALL +struct R2World *r2DeserializeWorld(const uint8_t *data, + size_t count); + +/** + * Color is HSLA (hue in degrees), matching Rapier DebugColor. mode uses DebugRenderMode bits. + */ +RAPIER_API RAPIER_CALL +size_t r2DebugRender(const struct R2World *world, + uint32_t mode, + struct R2DebugLine *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +R2Status r2SoftBodiesSetResweepStrain(struct R2World *world, + R2Real value); + +RAPIER_API RAPIER_CALL R2Real r2SoftBodiesResweepStrain(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SoftBodiesSetContactStiffening(struct R2World *world, + R2Real value); + +RAPIER_API RAPIER_CALL R2Real r2SoftBodiesContactStiffening(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2SoftBodiesSetMaxExtraSubsteps(struct R2World *world, + size_t value); + +RAPIER_API RAPIER_CALL size_t r2SoftBodiesMaxExtraSubsteps(const struct R2World *world); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetAuthoredVelocityMargin(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetEdgeSpeculation(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetInvertedCellDetection(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetSelfCrossingDetection(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetDetectionMotionGating(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetCrossBodyDetection(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetSelfStandDown(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetCrossBodyExpelGate(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetEdgeStandDown(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetCrossingRepulsion(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetCrossingRepulsionGuide(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetCrossingRepulsionSelfGuide(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetRecoveryPace(struct R2World *world, + R2Real value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetOverlapConstraints(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetOverlapRigid(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetOverlapSkipSelfTangled(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetOverlapEdgeStandDown(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetOverlapConstraintPace(struct R2World *world, + R2Real value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetOverlapSkinVolume(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetOverlapKeptDepth(struct R2World *world, + R2Real value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetOverlapSelfRegions(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetOverlapNormalPush(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetOverlapMultiVolume(struct R2World *world, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2RecoverySetOverlapProgressMargin(struct R2World *world, + R2Real value); + +#if defined(RAPIER_FEM) +RAPIER_API RAPIER_CALL +R2Status r2FemSetLinearTolerance(struct R2World *world, + R2Real value); +#endif + +#if defined(RAPIER_FEM) +RAPIER_API RAPIER_CALL +R2Status r2FemSetMaxLinearIterations(struct R2World *world, + size_t value); +#endif + +#if defined(RAPIER_FEM) +RAPIER_API RAPIER_CALL R2Status r2FemSetMaxDenseDofs(struct R2World *world, size_t value); +#endif + +/** + * Configures a dedicated pool for this world's parallel work. Zero selects Rayon's default. + * Takes effect on the next step. Reconfiguration must not race with a step or callback. + * Returns R2_UNSUPPORTED in builds without the parallel feature; keeps the previous + * pool when constructing the new one fails. The pool is not included in snapshots. + */ +RAPIER_API RAPIER_CALL R2Status r2SetNumThreads(struct R2World *world, size_t num_threads); + +/** + * Removes the world's dedicated pool. A parallel build then uses the calling + * context's Rayon pool (normally the global pool), not a single worker. + * Returns R2_UNSUPPORTED in a build without the parallel feature. + */ +RAPIER_API RAPIER_CALL R2Status r2ClearThreadPool(struct R2World *world); + +/** + * Size of the world's dedicated pool, or zero if a parallel build has no dedicated + * pool configured. Returns one for a build without the parallel feature. + */ +RAPIER_API RAPIER_CALL size_t r2NumThreads(const struct R2World *world); + +/** + * Enable or disable the native pipeline profiling counters. Enabling returns + * R2_UNSUPPORTED if the library was built without the profiler feature. + */ +RAPIER_API RAPIER_CALL R2Status r2SetCountersEnabled(struct R2World *world, R2Bool enabled); + +/** + * Native engine time of the most recent step, in milliseconds, as in the Rust testbed. + * Enable counters before stepping. Excludes C callbacks outside the step, rendering, + * and dispatch into a dedicated thread pool; remains unchanged while paused. + */ +RAPIER_API RAPIER_CALL double r2StepTimeMs(const struct R2World *world); + +/** + * Read ONLY trusted legacy rigid-world snapshots from debug_deserialize3.rs, + * produced by the identical Rapier build. This is not a stable interchange format. + */ +RAPIER_API RAPIER_CALL +struct R2World *r2DeserializeRigidState(const uint8_t *data, + size_t count); + +RAPIER_API RAPIER_CALL struct R2QueryFilter r2DefaultQueryFilter(void); + +RAPIER_API RAPIER_CALL struct R2ShapeCastOptions r2DefaultShapeCastOptions(void); + +RAPIER_API RAPIER_CALL R2Status r2RemoveSoftBody(struct R2SoftBodyHandle handle); + +RAPIER_API RAPIER_CALL R2Status r2SoftBody_WakeUp(struct R2SoftBodyHandle handle); + +RAPIER_API RAPIER_CALL R2Status r2FreeSoftBodyTearEvent(struct R2SoftBodyTearEvent *event); + +RAPIER_API RAPIER_CALL +struct R2SoftBodyHandle r2SoftBodyTearEvent_SoftBody(const struct R2SoftBodyTearEvent *event); + +RAPIER_API RAPIER_CALL +size_t r2SoftBodyTearEvent_Bodies(const struct R2SoftBodyTearEvent *event, + struct R2SoftBodyHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +struct R2ParticleDestination r2SoftBodyTearEvent_ParticleDestination(const struct R2SoftBodyTearEvent *event, + uint32_t particle); + +/** + * Flat indices; element arity follows the corresponding Rust event field. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBodyTearEvent_TornEdges(const struct R2SoftBodyTearEvent *event, + uint32_t *buffer, + size_t capacity); + +/** + * Flat indices; element arity follows the corresponding Rust event field. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBodyTearEvent_TornCells(const struct R2SoftBodyTearEvent *event, + uint32_t *buffer, + size_t capacity); + +/** + * Flat indices; element arity follows the corresponding Rust event field. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBodyTearEvent_RemovedEdges(const struct R2SoftBodyTearEvent *event, + uint32_t *buffer, + size_t capacity); + +/** + * Flat indices; element arity follows the corresponding Rust event field. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBodyTearEvent_SplitParticles(const struct R2SoftBodyTearEvent *event, + uint32_t *buffer, + size_t capacity); + +/** + * Flat indices; element arity follows the corresponding Rust event field. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBodyTearEvent_InsertedParticles(const struct R2SoftBodyTearEvent *event, + uint32_t *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r2SoftBodyTearEvent_PieceParticles(const struct R2SoftBodyTearEvent *event, + size_t piece_index, + uint32_t *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r2SoftBodyTearEvent_Clusters(const struct R2SoftBodyTearEvent *event, + struct R2SoftClusterSplit *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r2SoftBodyTearEvent_MovedJoints(const struct R2SoftBodyTearEvent *event, + struct R2SoftJointMove *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +struct R2SoftBodyTearEvent *r2SoftBody_Tear(struct R2SoftBodyHandle handle, + const uint32_t *edges, + size_t edge_count, + const uint32_t *cells, + size_t cell_count); + +RAPIER_API RAPIER_CALL +uint32_t r2SoftBody_AddCluster(struct R2SoftBodyHandle handle, + const uint32_t *particles, + size_t count); + +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_RemoveCluster(struct R2SoftBodyHandle handle, + uint32_t cluster); + +/** + * Optional particle destination after a tear. Missing destinations are normal and set + * found to false; body/index are only written when a destination exists. + */ +RAPIER_API RAPIER_CALL +struct R2OptionalParticleDestination r2SoftBodyTearEvent_TryParticleDestination(const struct R2SoftBodyTearEvent *event, + uint32_t particle); + +/** + * Cut using DIM points (a segment in 2D, triangle in 3D). A no-op returns a null event. + * The optional owned event must be freed with FreeSoftBodyTearEvent. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyTearEvent *r2CutSoftBody(struct R2SoftBodyHandle handle, + const struct R2Vector *blade); + +RAPIER_API RAPIER_CALL +struct R2VolumeMeshParameters r2NewVolumeMeshParameters(R2Real cell_size); + +RAPIER_API RAPIER_CALL struct R2BuildInfo r2BuildInfo(void); + +/** + * Release version of the loaded C bindings, e.g. "0.35.3+c.2". + * The suffix identifies the C bindings revision for the Rust crate version. + * The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. + * This release identifier is independent of the ABI compatibility version. + */ +RAPIER_API RAPIER_CALL const char *r2Version(void); + +/** + * Cargo profile of the loaded physics library: "debug" or "release". + * Custom profiles report the corresponding inherited Cargo profile category. + * The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. + * This is independent of the consumer's build mode and of per-package optimization overrides. + */ +RAPIER_API RAPIER_CALL const char *r2BuildProfile(void); + +RAPIER_API RAPIER_CALL struct R2BuildFeatures r2BuildFeatures(void); + +RAPIER_API RAPIER_CALL +R2SharedShape *r2HeightfieldSharedShape(struct R2RealView heights, + size_t rows, + size_t columns, + struct R2Vector scale); + +RAPIER_API RAPIER_CALL +struct R2Aabb r2SharedShape_ComputeAabb(const R2SharedShape *shape, + struct R2Pose pose); + +RAPIER_API RAPIER_CALL +struct R2MassProperties r2SharedShape_MassProperties(const R2SharedShape *shape, + R2Real density); + +RAPIER_API RAPIER_CALL +R2Bool r2SharedShape_ContainsPoint(const R2SharedShape *shape, + struct R2Pose pose, + struct R2Vector point); + +RAPIER_API RAPIER_CALL +size_t r2ContactPairs(const struct R2World *world, + struct R2ContactPair *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +struct R2ContactPair r2ContactPair(struct R2ColliderHandle collider1, + struct R2ColliderHandle collider2); + +RAPIER_API RAPIER_CALL +size_t r2IntersectionPairs(const struct R2World *world, + struct R2IntersectionPair *buffer, + size_t capacity); + +/** + * Contact points in collider-local space; normal in world space. Geometric manifolds may be recycled. + * For clustered solver impulses use contact pair totals. Soft pairs have no rigid manifolds. + */ +RAPIER_API RAPIER_CALL +size_t r2ContactPoints(struct R2ColliderHandle collider1, + struct R2ColliderHandle collider2, + struct R2ContactPoint *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r2MultibodyJoint_GeneralizedVelocity(struct R2MultibodyJointHandle handle, + R2Real *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +R2Status r2MultibodyJoint_SetGeneralizedVelocity(struct R2MultibodyJointHandle handle, + const R2Real *values, + size_t count); + +/** + * Check this before passing any dimension/precision-dependent structs across the ABI. + */ +RAPIER_API RAPIER_CALL +R2Status r2CheckAbi(uint32_t version, + uint32_t dimension, + size_t real_size, + size_t vector_size, + size_t pose_size); + +RAPIER_API RAPIER_CALL +struct R2ShapeMesh *r2SharedShape_Tessellate(const R2SharedShape *shape, + uint32_t subdivisions); + +/** + * Flat groups of three vertices. Standard output-buffer convention. + */ +RAPIER_API RAPIER_CALL +size_t r2ShapeMesh_Triangles(const struct R2ShapeMesh *mesh, + struct R2Vector *buffer, + size_t capacity); + +/** + * Flat groups of two vertices. Standard output-buffer convention. + */ +RAPIER_API RAPIER_CALL +size_t r2ShapeMesh_Lines(const struct R2ShapeMesh *mesh, + struct R2Vector *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL R2Status r2FreeShapeMesh(struct R2ShapeMesh *mesh); + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R2SharedShape *r2RoundCylinderSharedShape(R2Real half_height, + R2Real radius, + R2Real border_radius); +#endif + +#if defined(RAPIER_DIM3) +/** + * Tessellate a ball or capsule with independent longitude/latitude subdivision counts. + * Cuboids, cones, cylinders, convex polyhedra, trimeshes, and heightfields are also supported. + */ +RAPIER_API RAPIER_CALL +struct R2TriMeshData *r2SharedShape_ToTrimesh(const R2SharedShape *shape, + uint32_t ntheta, + uint32_t nphi); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +size_t r2TriMeshData_Vertices(const struct R2TriMeshData *mesh, + struct R2Vector *buffer, + size_t capacity); +#endif + +#if defined(RAPIER_DIM3) +/** + * Flat triangle indices; count and capacity are numbers of u32 entries. + */ +RAPIER_API RAPIER_CALL +size_t r2TriMeshData_Indices(const struct R2TriMeshData *mesh, + uint32_t *buffer, + size_t capacity); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL R2Status r2FreeTriMeshData(struct R2TriMeshData *mesh); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL struct R2UrdfLoaderOptions r2DefaultUrdfLoaderOptions(void); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL R2Status r2FreeUrdfRobot(struct R2UrdfRobot *object); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Load from a UTF-8 path. Validates options before reading the file. + * Options and their blueprint resources are borrowed through this call; the robot is owned. + */ +RAPIER_API RAPIER_CALL +struct R2UrdfRobot *r2UrdfRobotFromFile(const char *path, + const struct R2UrdfLoaderOptions *options); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +R2Status r2UrdfRobot_AppendTransform(struct R2UrdfRobot *robot, + struct R2Pose transform); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL R2Status r2FreeUrdfRobotHandles(struct R2UrdfRobotHandles *handles); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + */ +RAPIER_API RAPIER_CALL +struct R2UrdfRobotHandles *r2UrdfRobot_InsertUsingImpulseJoints(struct R2World *world, + const struct R2UrdfRobot *robot); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + */ +RAPIER_API RAPIER_CALL +struct R2UrdfRobotHandles *r2UrdfRobot_InsertUsingMultibodyJoints(struct R2World *world, + const struct R2UrdfRobot *robot, + uint8_t options); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Body handles in source order; absent MJCF bodies have invalid handles. + */ +RAPIER_API RAPIER_CALL +size_t r2UrdfRobotHandles_Bodies(const struct R2UrdfRobotHandles *handles, + struct R2RigidBodyHandle *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL struct R2MjcfLoaderOptions r2DefaultMjcfLoaderOptions(void); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL R2Status r2FreeMjcfRobot(struct R2MjcfRobot *object); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Load from a UTF-8 path. Validates options before reading the file. + * Options and their blueprint resources are borrowed through this call; the robot is owned. + */ +RAPIER_API RAPIER_CALL +struct R2MjcfRobot *r2MjcfRobotFromFile(const char *path, + const struct R2MjcfLoaderOptions *options); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +R2Status r2MjcfRobot_AppendTransform(struct R2MjcfRobot *robot, + struct R2Pose transform); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL R2Status r2FreeMjcfRobotHandles(struct R2MjcfRobotHandles *handles); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + */ +RAPIER_API RAPIER_CALL +struct R2MjcfRobotHandles *r2MjcfRobot_InsertUsingImpulseJoints(struct R2World *world, + const struct R2MjcfRobot *robot); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + */ +RAPIER_API RAPIER_CALL +struct R2MjcfRobotHandles *r2MjcfRobot_InsertUsingMultibodyJoints(struct R2World *world, + const struct R2MjcfRobot *robot, + uint8_t options); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Body handles in source order; absent MJCF bodies have invalid handles. + */ +RAPIER_API RAPIER_CALL +size_t r2MjcfRobotHandles_Bodies(const struct R2MjcfRobotHandles *handles, + struct R2RigidBodyHandle *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Resolved model gravity before the caller chooses a world convention. + */ +RAPIER_API RAPIER_CALL struct R2Vector r2MjcfRobot_Gravity(const struct R2MjcfRobot *robot); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL size_t r2MjcfRobot_BodyCount(const struct R2MjcfRobot *robot); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +size_t r2MjcfRobot_BodyColliderCount(const struct R2MjcfRobot *robot, + size_t body); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Borrowed collider; invalidated by freeing or mutating the robot's storage. + */ +RAPIER_API RAPIER_CALL +R2Status r2MjcfRobot_SetBodyColliderCollisionGroups(struct R2MjcfRobot *robot, + size_t body, + size_t collider, + struct R2InteractionGroups groups); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL size_t r2MjcfRobot_KeyframeCount(const struct R2MjcfRobot *robot); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copies a NUL-terminated UTF-8 name. Count includes NUL; unnamed keys return an empty string. + */ +RAPIER_API RAPIER_CALL +size_t r2MjcfRobot_KeyframeName(const struct R2MjcfRobot *robot, + size_t key, + char *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +R2Status r2MjcfRobot_AppendKeyframe(struct R2MjcfRobot *robot, + const struct R2MjcfRobot *source, + size_t key); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +size_t r2MjcfRobot_KeyframeControls(const struct R2MjcfRobot *robot, + size_t key, + R2Real *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +size_t r2MjcfRobotHandles_ActuatorCount(const struct R2MjcfRobotHandles *handles); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +R2Status r2MjcfRobotHandles_ApplyKeyframe(const struct R2MjcfRobotHandles *handles, + const struct R2MjcfRobot *robot, + size_t key); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +R2Status r2MjcfRobotHandles_ApplyControlsScaled(const struct R2MjcfRobotHandles *handles, + const R2Real *controls, + size_t count, + R2Real gain); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +size_t r2MjcfRobot_BodyVisualCount(const struct R2MjcfRobot *robot, + size_t body); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +const R2MjcfVisualMesh *r2MjcfRobot_BodyVisual(const struct R2MjcfRobot *robot, + size_t body, + size_t visual); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +struct R2MjcfVisualMeshInfo r2MjcfVisualMesh_Info(const R2MjcfVisualMesh *visual); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Returns an owned shared shape reference. + * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + */ +RAPIER_API RAPIER_CALL +R2SharedShape *r2MjcfVisualMesh_CloneShape(const R2MjcfVisualMesh *visual); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copies flattened pairs of per-vertex UV coordinates. + */ +RAPIER_API RAPIER_CALL +size_t r2MjcfVisualMesh_Uvs(const R2MjcfVisualMesh *visual, + float *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copies flattened triples of per-vertex normals. + */ +RAPIER_API RAPIER_CALL +size_t r2MjcfVisualMesh_Normals(const R2MjcfVisualMesh *visual, + float *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copies a NUL-terminated texture path, or an empty string for untextured meshes. + */ +RAPIER_API RAPIER_CALL +size_t r2MjcfVisualMesh_Texture(const R2MjcfVisualMesh *visual, + char *buffer, + size_t capacity); +#endif + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL struct R2Pose r2RigidBody_Position(struct R2RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2RigidBody_Translation(struct R2RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL struct R2Vector r2RigidBody_Linvel(struct R2RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R2AngVector r2RigidBody_Angvel(struct R2RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsSleeping(struct R2RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsEnabled(struct R2RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +struct R2UserData r2RigidBody_UserData(struct R2RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetPosition(struct R2RigidBodyHandle handle, + struct R2Pose value, + R2Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetTranslation(struct R2RigidBodyHandle handle, + struct R2Vector value, + R2Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetLinvel(struct R2RigidBodyHandle handle, + struct R2Vector value, + R2Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetAngvel(struct R2RigidBodyHandle handle, + R2AngVector value, + R2Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetNextKinematicPosition(struct R2RigidBodyHandle handle, + struct R2Pose value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetNextKinematicTranslation(struct R2RigidBodyHandle handle, + struct R2Vector value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetGravityScale(struct R2RigidBodyHandle handle, + R2Real value, + R2Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetLinearDamping(struct R2RigidBodyHandle handle, + R2Real value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetAngularDamping(struct R2RigidBodyHandle handle, + R2Real value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetEnabled(struct R2RigidBodyHandle handle, + R2Bool value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetUserData(struct R2RigidBodyHandle handle, + struct R2UserData value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_ApplyImpulse(struct R2RigidBodyHandle handle, + struct R2Vector value, + R2Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_ApplyImpulseAtPoint(struct R2RigidBodyHandle handle, + struct R2Vector value, + struct R2Vector point, + R2Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_AddForce(struct R2RigidBodyHandle handle, + struct R2Vector value, + R2Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_ResetForces(struct R2RigidBodyHandle handle, + R2Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R2Status r2RigidBody_Sleep(struct R2RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL struct R2Pose r2Collider_Position(struct R2ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL struct R2Vector r2Collider_Translation(struct R2ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R2Real r2Collider_Friction(struct R2ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R2Real r2Collider_Restitution(struct R2ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R2Bool r2Collider_IsSensor(struct R2ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +struct R2RigidBodyHandle r2Collider_Parent(struct R2ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetPosition(struct R2ColliderHandle handle, + struct R2Pose value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetTranslation(struct R2ColliderHandle handle, + struct R2Vector value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetFriction(struct R2ColliderHandle handle, + R2Real value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetRestitution(struct R2ColliderHandle handle, + R2Real value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetSensor(struct R2ColliderHandle handle, + R2Bool value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetCollisionGroups(struct R2ColliderHandle handle, + struct R2InteractionGroups value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetUserData(struct R2ColliderHandle handle, + struct R2UserData value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2SoftBody_ParticlePosition(struct R2SoftBodyHandle handle, + size_t index); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_ParticlePositions(struct R2SoftBodyHandle handle, + struct R2Vector *buffer, + size_t capacity); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyMaterial r2SoftBody_Material(struct R2SoftBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetParticlePosition(struct R2SoftBodyHandle handle, + size_t index, + struct R2Vector value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetMaterial(struct R2SoftBodyHandle handle, + const struct R2SoftBodyMaterial *data); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_AddParticleForce(struct R2SoftBodyHandle handle, + size_t index, + struct R2Vector value, + R2Bool wake_up); + +/** + * Copies states in the same order as handles, without allocating temporary storage. + * All handles are validated before writing. On INVALID_HANDLE outputs are unchanged. + * NULL/0 is a size query. BUFFER_TOO_SMALL updates count but leaves states untouched. + */ +RAPIER_API RAPIER_CALL +size_t r2RigidBodyReadStates(const struct R2World *world, + const struct R2RigidBodyHandle *handles, + size_t handle_count, + struct R2RigidBodyState *states, + size_t capacity); + +/** + * Copies joint configuration without returning a borrowed joint pointer. + */ +RAPIER_API RAPIER_CALL +struct R2JointDesc r2ImpulseJoint_Desc(struct R2ImpulseJointHandle handle); + +/** + * Replaces configuration after validation, resetting cached limit/motor impulses. + */ +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetDesc(struct R2ImpulseJointHandle handle, + const struct R2JointDesc *desc, + R2Bool wake_up); + +/** + * Replace the shape geometry with a borrowed tri mesh. Counts are elements. + * Copies no arrays. Invalid view metadata leaves the description unchanged. + * Geometry and flags are validated when the description is built or inserted. + */ +RAPIER_API RAPIER_CALL +R2Status r2ShapeDesc_SetTrimesh(struct R2ShapeDesc *desc, + struct R2VectorView vertices, + struct R2TriangleView indices, + uint32_t flags); + +/** + * Replace the shape geometry with a borrowed polyline. Counts are elements. + * Copies no arrays. Invalid view metadata leaves the description unchanged. + * Geometry and flags are validated when the description is built or inserted. + */ +RAPIER_API RAPIER_CALL +R2Status r2ShapeDesc_SetPolyline(struct R2ShapeDesc *desc, + struct R2VectorView vertices, + struct R2EdgeView indices, + uint32_t flags); + +/** + * Replace the shape geometry with a borrowed convex hull point cloud. + */ +RAPIER_API RAPIER_CALL +R2Status r2ShapeDesc_SetConvexHull(struct R2ShapeDesc *desc, + struct R2VectorView vertices); + +/** + * Select an explicit particle recipe and borrow its positions. Other fields are preserved. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetParticles(struct R2SoftBodyDesc *desc, + struct R2VectorView positions); + +/** + * Select a surface recipe and borrow its vertices and elements. Other fields are preserved. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetSurfaceMesh(struct R2SoftBodyDesc *desc, + struct R2VectorView vertices, + R2SurfaceElementView elements); + +/** + * Borrow skin geometry. Other fields, including skinCollision, are preserved. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetSkin(struct R2SoftBodyDesc *desc, + struct R2VectorView vertices, + R2SurfaceElementView elements); + +/** + * Borrow masses; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetMasses(struct R2SoftBodyDesc *desc, + struct R2RealView view); + +/** + * Borrow pinned particles; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetPinnedParticles(struct R2SoftBodyDesc *desc, + struct R2IndexView view); + +/** + * Borrow edges; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetEdges(struct R2SoftBodyDesc *desc, + struct R2EdgeView view); + +/** + * Borrow bend edges; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetBendEdges(struct R2SoftBodyDesc *desc, + struct R2EdgeView view); + +/** + * Borrow cells; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetCells(struct R2SoftBodyDesc *desc, + R2CellView view); + +/** + * Borrow surface; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetSurface(struct R2SoftBodyDesc *desc, + R2SurfaceElementView view); + +/** + * Borrow tension only edges; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetTensionOnlyEdges(struct R2SoftBodyDesc *desc, + struct R2IndexView view); + +#if defined(RAPIER_DIM3) +/** + * Borrow dihedrals; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetDihedrals(struct R2SoftBodyDesc *desc, + struct R2DihedralView view); +#endif + +#if defined(RAPIER_DIM3) +/** + * Borrow wire; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBodyDesc_SetWire(struct R2SoftBodyDesc *desc, + struct R2EdgeView view); +#endif + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderDesc r2RoundCuboidColliderDesc(struct R2Vector half_extents, + R2Real border_radius); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderDesc r2CapsuleColliderDesc(struct R2Vector a, + struct R2Vector b, + R2Real radius); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderDesc r2SegmentColliderDesc(struct R2Vector a, + struct R2Vector b); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderDesc r2TriangleColliderDesc(struct R2Vector a, + struct R2Vector b, + struct R2Vector c); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL struct R2ColliderDesc r2HalfspaceColliderDesc(struct R2Vector normal); + +#if defined(RAPIER_DIM3) +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderDesc r2CylinderColliderDesc(R2Real half_height, + R2Real radius); +#endif + +#if defined(RAPIER_DIM3) +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderDesc r2ConeColliderDesc(R2Real half_height, + R2Real radius); +#endif + +#if defined(RAPIER_DIM3) +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderDesc r2RoundCylinderColliderDesc(R2Real half_height, + R2Real radius, + R2Real border_radius); +#endif + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderDesc r2CapsuleXColliderDesc(R2Real half_height, + R2Real radius); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderDesc r2CapsuleYColliderDesc(R2Real half_height, + R2Real radius); + +#if defined(RAPIER_DIM3) +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R2ColliderDesc r2CapsuleZColliderDesc(R2Real half_height, + R2Real radius); +#endif + +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyDesc r2RopeSoftBodyDesc(struct R2Vector a, + struct R2Vector b, + size_t particles); + +#if defined(RAPIER_DIM2) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyDesc r2GridSoftBodyDesc(struct R2Vector center, + struct R2Vector half_extents, + size_t nx, + size_t ny); +#endif + +#if defined(RAPIER_DIM3) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyDesc r2CuboidSoftBodyDesc(struct R2Vector center, + struct R2Vector half_extents, + size_t nx, + size_t ny, + size_t nz); +#endif + +#if defined(RAPIER_DIM3) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyDesc r2ClothSoftBodyDesc(struct R2Vector origin, + struct R2Vector du, + struct R2Vector dv, + size_t nu, + size_t nv); +#endif + +#if defined(RAPIER_DIM2) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyDesc r2DiskSoftBodyDesc(struct R2Vector center, + R2Real radius, + size_t particles); +#endif + +#if defined(RAPIER_DIM3) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyDesc r2SphereSoftBodyDesc(struct R2Vector center, + R2Real radius, + uint32_t subdivisions); +#endif + +#if defined(RAPIER_DIM3) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyDesc r2ClothTubeSoftBodyDesc(struct R2Vector origin, + struct R2Vector axis, + R2Real radius_start, + R2Real radius_end, + size_t num_around, + size_t num_along); +#endif + +/** + * Initializes a borrowed meshing recipe. Mesh generation happens on preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyDesc r2VolumetricSoftBodyDesc(struct R2VectorView vertices, + R2SurfaceElementView surface, + struct R2VolumeMeshParameters parameters); + +/** + * Returns a material with the same softness for each constraint family. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyMaterial r2UniformSoftBodyMaterial(struct R2SpringCoefficients value); + +/** + * Copies generated particle positions into caller-owned storage; no persistent builder. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBodyDesc_ParticlePositions(const struct R2SoftBodyDesc *desc, + struct R2Vector *buffer, + size_t capacity); + +/** + * Copies generated cell indices into caller-owned storage. Counts scalar indices. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBodyDesc_CellIndices(const struct R2SoftBodyDesc *desc, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL size_t r2Collider_ShapeIdentity(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL size_t r2SoftBody_NumParticles(struct R2SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL uint32_t r2SoftBody_TopologyVersion(struct R2SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2SoftBody_Mass(struct R2SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2SoftBody_Volume(struct R2SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2SoftBody_RestVolume(struct R2SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2SoftBody_VolumeFactor(struct R2SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2SoftBody_CenterOfMass(struct R2SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2RigidBodyHandle r2SoftBody_RootBody(struct R2SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Bool r2SoftBody_IsEnabled(struct R2SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Bool r2SoftBody_IsSleeping(struct R2SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_ParticleVelocities(struct R2SoftBodyHandle handle, + struct R2Vector *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_Edges(struct R2SoftBodyHandle handle, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_Cells(struct R2SoftBodyHandle handle, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_Boundary(struct R2SoftBodyHandle handle, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_Pieces(struct R2SoftBodyHandle handle, + struct R2SoftBodyHandle *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetParticleVelocity(struct R2SoftBodyHandle handle, + size_t index, + struct R2Vector value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetParticleKinematicTarget(struct R2SoftBodyHandle handle, + size_t index, + struct R2Vector value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetParticlePinned(struct R2SoftBodyHandle handle, + size_t index, + R2Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_ApplyParticleImpulse(struct R2SoftBodyHandle handle, + size_t index, + struct R2Vector value, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_AddForce(struct R2SoftBodyHandle handle, + struct R2Vector value, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_ApplyImpulse(struct R2SoftBodyHandle handle, + struct R2Vector value, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_ResetForces(struct R2SoftBodyHandle handle, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetEnabled(struct R2SoftBodyHandle handle, + R2Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetVolumeFactor(struct R2SoftBodyHandle handle, + R2Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_AttachParticle(struct R2SoftBodyHandle handle, + size_t index, + struct R2RigidBodyHandle rigid_body); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_DetachParticle(struct R2SoftBodyHandle handle, + size_t index); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_Clusters(struct R2SoftBodyHandle handle, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2RigidBodyHandle r2SoftBody_ClusterProxy(struct R2SoftBodyHandle handle, + uint32_t cluster); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_ClusterParticles(struct R2SoftBodyHandle handle, + uint32_t cluster, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetClusterPinned(struct R2SoftBodyHandle handle, + uint32_t cluster, + R2Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetClusterKinematicTarget(struct R2SoftBodyHandle handle, + uint32_t cluster, + struct R2Pose value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetClusterShapeMatchingEnabled(struct R2SoftBodyHandle handle, + uint32_t cluster, + R2Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetClusterStiffnessScale(struct R2SoftBodyHandle handle, + uint32_t cluster, + R2Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetClusterTearResistance(struct R2SoftBodyHandle handle, + uint32_t cluster, + R2Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_Meshes(struct R2SoftBodyHandle handle, + struct R2SoftMeshInfo *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_MeshVerticesById(struct R2SoftBodyHandle handle, + struct R2SoftMeshId id, + struct R2Vector *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_MeshIndicesById(struct R2SoftBodyHandle handle, + struct R2SoftMeshId id, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_MeshColliders(struct R2SoftBodyHandle handle, + struct R2ColliderHandle *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_MeshVertices(struct R2SoftBodyHandle handle, + struct R2ColliderHandle collider, + struct R2Vector *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_MeshIndices(struct R2SoftBodyHandle handle, + struct R2ColliderHandle collider, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2SoftBody_MeshArity(struct R2SoftBodyHandle handle, + struct R2ColliderHandle collider); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +uint32_t r2SoftBody_MeshTopologyVersion(struct R2SoftBodyHandle handle, + struct R2ColliderHandle collider); + +#if defined(RAPIER_FEM) +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetSolver(struct R2SoftBodyHandle handle, + uint32_t solver); +#endif + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetClusterShapeMatchingTarget(struct R2SoftBodyHandle handle, + uint32_t cluster, + const struct R2Pose *target); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2SoftBody_SetEdgeTearResistance(struct R2SoftBodyHandle handle, + size_t index, + R2Real resistance); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Bool r2SoftBody_MeshIsClosed(struct R2SoftBodyHandle handle, + struct R2ColliderHandle collider); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetAdditionalMassProperties(struct R2RigidBodyHandle handle, + struct R2MassProperties properties, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_RecomputeMassPropertiesFromColliders(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetMassProperties(struct R2ColliderHandle handle, + struct R2MassProperties properties); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2MassProperties r2Collider_MassProperties(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetLockedAxes(struct R2RigidBodyHandle handle, + uint8_t axes, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL uint8_t r2RigidBody_LockedAxes(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Bool r2Collider_IsVoxels(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2VoxelQuery r2Collider_VoxelAtFlatId(struct R2ColliderHandle handle, + uint32_t id); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetVoxel(struct R2ColliderHandle handle, + struct R2VoxelKey key, + R2Bool filled); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2Pose r2RigidBody_NextPosition(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL struct R2Rotation r2RigidBody_Rotation(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2RigidBody_CenterOfMass(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2RigidBody_LocalCenterOfMass(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL struct R2Vector r2RigidBody_UserForce(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2AngVector r2RigidBody_UserTorque(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL uint32_t r2RigidBody_BodyType(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2RigidBody_Mass(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2RigidBody_GravityScale(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2RigidBody_LinearDamping(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2RigidBody_AngularDamping(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2RigidBody_KineticEnergy(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2RigidBody_SoftCcdPrediction(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsCcdEnabled(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsDynamic(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyHandle r2RigidBody_SoftBody(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsSoftFrame(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsFixed(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsKinematic(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsMoving(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsCcdActive(struct R2RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetRotation(struct R2RigidBodyHandle handle, + struct R2Rotation value, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetBodyType(struct R2RigidBodyHandle handle, + uint32_t value, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetNextKinematicRotation(struct R2RigidBodyHandle handle, + struct R2Rotation value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetAdditionalMass(struct R2RigidBodyHandle handle, + R2Real value, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetSoftCcdPrediction(struct R2RigidBodyHandle handle, + R2Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetCcdEnabled(struct R2RigidBodyHandle handle, + R2Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetTranslationsLocked(struct R2RigidBodyHandle handle, + R2Bool value, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetRotationsLocked(struct R2RigidBodyHandle handle, + R2Bool value, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetDominanceGroup(struct R2RigidBodyHandle handle, + int8_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetAdditionalSolverIterations(struct R2RigidBodyHandle handle, + size_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetAdditionalPgsIterations(struct R2RigidBodyHandle handle, + size_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_AddTorque(struct R2RigidBodyHandle handle, + R2AngVector value, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_ApplyTorqueImpulse(struct R2RigidBodyHandle handle, + R2AngVector value, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_AddForceAtPoint(struct R2RigidBodyHandle handle, + struct R2Vector value, + struct R2Vector point, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_ResetTorques(struct R2RigidBodyHandle handle, + R2Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2RigidBody_VelocityAtPoint(struct R2RigidBodyHandle handle, + struct R2Vector point); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r2RigidBody_Colliders(struct R2RigidBodyHandle handle, + struct R2ColliderHandle *buffer, + size_t capacity); + +#if defined(RAPIER_DIM3) +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Bool r2RigidBody_GyroscopicForcesEnabled(struct R2RigidBodyHandle handle); +#endif + +#if defined(RAPIER_DIM3) +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2RigidBody_SetGyroscopicForcesEnabled(struct R2RigidBodyHandle handle, + R2Bool enabled); +#endif + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetDensity(struct R2ColliderHandle handle, + R2Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetMass(struct R2ColliderHandle handle, + R2Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetEnabled(struct R2ColliderHandle handle, + R2Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetSolverGroups(struct R2ColliderHandle handle, + struct R2InteractionGroups value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetFrictionCombineRule(struct R2ColliderHandle handle, + uint32_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetRestitutionCombineRule(struct R2ColliderHandle handle, + uint32_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetContactSkin(struct R2ColliderHandle handle, + R2Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetContactForceEventThreshold(struct R2ColliderHandle handle, + R2Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetActiveEvents(struct R2ColliderHandle handle, + uint32_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetActiveHooks(struct R2ColliderHandle handle, + uint32_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetActiveCollisionTypes(struct R2ColliderHandle handle, + uint16_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL struct R2Rotation r2Collider_Rotation(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2InteractionGroups r2Collider_CollisionGroups(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R2InteractionGroups r2Collider_SolverGroups(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL struct R2UserData r2Collider_UserData(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL uint32_t r2Collider_ActiveEvents(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2Collider_Mass(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2Collider_Density(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2Collider_Volume(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Real r2Collider_ContactSkin(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Real r2Collider_ContactForceEventThreshold(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R2Bool r2Collider_IsEnabled(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL struct R2Aabb r2Collider_ComputeAabb(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + */ +RAPIER_API RAPIER_CALL R2SharedShape *r2Collider_CloneShape(struct R2ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetShape(struct R2ColliderHandle handle, + const R2SharedShape *shape); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R2Status r2Collider_SetPositionWrtParent(struct R2ColliderHandle handle, + struct R2Pose value); + +RAPIER_API RAPIER_CALL R2Status r2RigidBody_ValidateHandle(struct R2RigidBodyHandle handle); + +RAPIER_API RAPIER_CALL R2Status r2Collider_ValidateHandle(struct R2ColliderHandle handle); + +RAPIER_API RAPIER_CALL R2Status r2SoftBody_ValidateHandle(struct R2SoftBodyHandle handle); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetLocalFrame1(struct R2JointDesc *desc, + struct R2Pose value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetLocalFrame1(struct R2ImpulseJointHandle handle, + struct R2Pose value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetLocalFrame2(struct R2JointDesc *desc, + struct R2Pose value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetLocalFrame2(struct R2ImpulseJointHandle handle, + struct R2Pose value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetLocalAnchor1(struct R2JointDesc *desc, + struct R2Vector value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetLocalAnchor1(struct R2ImpulseJointHandle handle, + struct R2Vector value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetLocalAnchor2(struct R2JointDesc *desc, + struct R2Vector value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetLocalAnchor2(struct R2ImpulseJointHandle handle, + struct R2Vector value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetContactsEnabled(struct R2JointDesc *desc, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetContactsEnabled(struct R2ImpulseJointHandle handle, + R2Bool value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetEnabled(struct R2JointDesc *desc, + R2Bool value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetEnabled(struct R2ImpulseJointHandle handle, + R2Bool value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetSoftness(struct R2JointDesc *desc, + struct R2SpringCoefficients value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetSoftness(struct R2ImpulseJointHandle handle, + struct R2SpringCoefficients value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetLockedAxes(struct R2JointDesc *desc, + uint8_t value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetLockedAxes(struct R2ImpulseJointHandle handle, + uint8_t value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetLimitAxes(struct R2JointDesc *desc, + uint8_t value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetLimitAxes(struct R2ImpulseJointHandle handle, + uint8_t value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetMotorAxes(struct R2JointDesc *desc, + uint8_t value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetMotorAxes(struct R2ImpulseJointHandle handle, + uint8_t value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetCoupledAxes(struct R2JointDesc *desc, + uint8_t value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetCoupledAxes(struct R2ImpulseJointHandle handle, + uint8_t value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetLocalAxis1(struct R2JointDesc *desc, + struct R2Vector value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetLocalAxis1(struct R2ImpulseJointHandle handle, + struct R2Vector value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetLocalAxis2(struct R2JointDesc *desc, + struct R2Vector value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetLocalAxis2(struct R2ImpulseJointHandle handle, + struct R2Vector value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetLimits(struct R2JointDesc *desc, + uint32_t joint_axis, + R2Real min, + R2Real max); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetLimits(struct R2ImpulseJointHandle handle, + uint32_t joint_axis, + R2Real min, + R2Real max, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetMotor(struct R2JointDesc *desc, + uint32_t joint_axis, + R2Real target_position, + R2Real target_velocity, + R2Real stiffness, + R2Real damping); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetMotor(struct R2ImpulseJointHandle handle, + uint32_t joint_axis, + R2Real target_position, + R2Real target_velocity, + R2Real stiffness, + R2Real damping, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetMotorMaxForce(struct R2JointDesc *desc, + uint32_t joint_axis, + R2Real max_force); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetMotorMaxForce(struct R2ImpulseJointHandle handle, + uint32_t joint_axis, + R2Real max_force, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetMotorModel(struct R2JointDesc *desc, + uint32_t joint_axis, + uint32_t model); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetMotorModel(struct R2ImpulseJointHandle handle, + uint32_t joint_axis, + uint32_t model, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetUserData(struct R2JointDesc *desc, + struct R2UserData value); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetUserData(struct R2ImpulseJointHandle handle, + struct R2UserData value, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetMotorPosition(struct R2JointDesc *desc, + uint32_t joint_axis, + R2Real target_position, + R2Real stiffness, + R2Real damping); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetMotorPosition(struct R2ImpulseJointHandle handle, + uint32_t joint_axis, + R2Real target_position, + R2Real stiffness, + R2Real damping, + R2Bool wake_up); + +RAPIER_API RAPIER_CALL +R2Status r2JointDesc_SetMotorVelocity(struct R2JointDesc *desc, + uint32_t joint_axis, + R2Real target_velocity, + R2Real factor); + +RAPIER_API RAPIER_CALL +R2Status r2ImpulseJoint_SetMotorVelocity(struct R2ImpulseJointHandle handle, + uint32_t joint_axis, + R2Real target_velocity, + R2Real factor, + R2Bool wake_up); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R2SharedShape *r2ConvexDecompositionSharedShape(struct R2VectorView vertices, + R2SurfaceElementView indices); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R2SharedShape *r2VoxelsSharedShapeFromPoints(struct R2Vector voxel_size, + struct R2VectorView points); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R2SharedShape *r2VoxelizedMeshSharedShape(struct R2VectorView vertices, + R2SurfaceElementView indices, + R2Real voxel_size); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL R2SharedShape *r2ConvexHullSharedShape(struct R2VectorView vertices); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R2SharedShape *r2TrimeshSharedShape(struct R2VectorView vertices, + struct R2TriangleView indices); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R2SharedShape *r2PolylineSharedShape(struct R2VectorView vertices, + struct R2EdgeView indices); + +#if defined(RAPIER_DIM2) +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R2SharedShape *r2OrientedPolylineSharedShape(struct R2VectorView vertices, + struct R2EdgeView indices); +#endif + +#if defined(RAPIER_DIM2) +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R2SharedShape *r2ConvexPolylineSharedShape(struct R2VectorView vertices); +#endif + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R2SharedShape *r2RoundConvexHullSharedShape(struct R2VectorView vertices, + R2Real border_radius); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R2SharedShape *r2TrimeshSharedShapeWithFlags(struct R2VectorView vertices, + struct R2TriangleView indices, + uint32_t flags); + +/** + * Create an owned world. Release it with FreeWorld. + */ +RAPIER_API RAPIER_CALL struct R2World *r2NewWorld(void); + +/** + * Free a world. NULL is allowed. Rejects destruction from an active callback. + * The caller must prevent other threads from starting calls during destruction. + */ +RAPIER_API RAPIER_CALL R2Status r2FreeWorld(struct R2World *world); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2VelocityCorrection r2ReadPidController_RigidBodyCorrection(const struct R2ReadContext *context, + struct R2PidController *controller, + R2Real dt, + struct R2RigidBodyHandle body, + struct R2Pose target_pose, + struct R2Vector target_linvel, + R2AngVector target_angvel); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL size_t r2ReadRigidBodyCount(const struct R2ReadContext *context); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +size_t r2ReadRigidBodyHandles(const struct R2ReadContext *context, + struct R2RigidBodyHandle *buffer, + size_t capacity); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadRigidBody_Contains(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL size_t r2ReadColliderCount(const struct R2ReadContext *context); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +size_t r2ReadColliderHandles(const struct R2ReadContext *context, + struct R2ColliderHandle *buffer, + size_t capacity); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadCollider_Contains(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +size_t r2ReadCollider_ShapeIdentity(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2MassProperties r2ReadCollider_MassProperties(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +uint8_t r2ReadRigidBody_LockedAxes(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadCollider_IsVoxels(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2VoxelQuery r2ReadCollider_VoxelAtFlatId(const struct R2ReadContext *context, + struct R2ColliderHandle handle, + uint32_t id); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Pose r2ReadRigidBody_NextPosition(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Rotation r2ReadRigidBody_Rotation(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2ReadRigidBody_CenterOfMass(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2ReadRigidBody_LocalCenterOfMass(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2ReadRigidBody_UserForce(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2AngVector r2ReadRigidBody_UserTorque(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +uint32_t r2ReadRigidBody_BodyType(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadRigidBody_Mass(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadRigidBody_GravityScale(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadRigidBody_LinearDamping(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadRigidBody_AngularDamping(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadRigidBody_KineticEnergy(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadRigidBody_SoftCcdPrediction(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadRigidBody_IsCcdEnabled(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadRigidBody_IsDynamic(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2SoftBodyHandle r2ReadRigidBody_SoftBody(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadRigidBody_IsSoftFrame(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadRigidBody_IsFixed(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadRigidBody_IsKinematic(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadRigidBody_IsMoving(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadRigidBody_IsCcdActive(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2ReadRigidBody_VelocityAtPoint(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle, + struct R2Vector point); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +size_t r2ReadRigidBody_Colliders(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle, + struct R2ColliderHandle *buffer, + size_t capacity); + +#if defined(RAPIER_DIM3) +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadRigidBody_GyroscopicForcesEnabled(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); +#endif + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Rotation r2ReadCollider_Rotation(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2InteractionGroups r2ReadCollider_CollisionGroups(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2InteractionGroups r2ReadCollider_SolverGroups(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2UserData r2ReadCollider_UserData(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +uint32_t r2ReadCollider_ActiveEvents(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadCollider_Mass(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadCollider_Density(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadCollider_Volume(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadCollider_ContactSkin(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadCollider_ContactForceEventThreshold(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadCollider_IsEnabled(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Aabb r2ReadCollider_ComputeAabb(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + */ +RAPIER_API RAPIER_CALL +R2SharedShape *r2ReadCollider_CloneShape(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Status r2ReadRigidBody_ValidateHandle(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Status r2ReadCollider_ValidateHandle(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Pose r2ReadRigidBody_Position(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2ReadRigidBody_Translation(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2ReadRigidBody_Linvel(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2AngVector r2ReadRigidBody_Angvel(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadRigidBody_IsSleeping(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadRigidBody_IsEnabled(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2UserData r2ReadRigidBody_UserData(const struct R2ReadContext *context, + struct R2RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Pose r2ReadCollider_Position(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2Vector r2ReadCollider_Translation(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadCollider_Friction(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Real r2ReadCollider_Restitution(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R2Bool r2ReadCollider_IsSensor(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R2RigidBodyHandle r2ReadCollider_Parent(const struct R2ReadContext *context, + struct R2ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +size_t r2ReadRigidBodyReadStates(const struct R2ReadContext *context, + const struct R2RigidBodyHandle *handles, + size_t handle_count, + struct R2RigidBodyState *states, + size_t capacity); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#else /* RAPIER_DIM3 */ + +#if defined(RAPIER_DIM2) +#define R3_JOINT_DOF_COUNT 3 +#endif + +#if defined(RAPIER_DIM3) +#define R3_JOINT_DOF_COUNT 6 +#endif + +#define R3_SOFT_DESC_PARTICLES 0 + +#define R3_SOFT_DESC_ROPE 1 + +#define R3_SOFT_DESC_GRID 2 + +#define R3_SOFT_DESC_CLOTH 3 + +#define R3_SOFT_DESC_CUBOID 4 + +#define R3_SOFT_DESC_SURFACE 5 + +#define R3_SOFT_DESC_DISK 6 + +#define R3_SOFT_DESC_SPHERE 7 + +#define R3_SOFT_DESC_CLOTH_TUBE 8 + +#define R3_SOFT_DESC_VOLUMETRIC 9 + +#define R3_SOFT_BINDING_SKINNED 0 + +#define R3_SOFT_BINDING_DIRECT 1 + +#define R3_SOFT_BINDING_DIRECT_BY_POSITION 2 + +#if defined(RAPIER_DIM2) +#define R3_POLYLINE_ORIENTED 1 +#endif + +#define R3_POLYLINE_DEFORMABLE 2 + +#define R3_SHAPE_DESC_BALL 0 + +#define R3_SHAPE_DESC_CUBOID 1 + +#define R3_SHAPE_DESC_ROUND_CUBOID 2 + +#define R3_SHAPE_DESC_CAPSULE 3 + +#define R3_SHAPE_DESC_SEGMENT 4 + +#define R3_SHAPE_DESC_TRIANGLE 5 + +#define R3_SHAPE_DESC_HALFSPACE 6 + +#define R3_SHAPE_DESC_CONVEX_HULL 7 + +#define R3_SHAPE_DESC_TRIMESH 8 + +#define R3_SHAPE_DESC_POLYLINE 9 + +#define R3_SHAPE_DESC_SHARED 10 + +#define R3_SHAPE_DESC_HEIGHTFIELD 11 + +#define R3_SHAPE_DESC_CYLINDER 12 + +#define R3_SHAPE_DESC_CONE 13 + +#define R3_SHAPE_DESC_COMPOUND 14 + +#define R3_SHAPE_DESC_ROUND_CYLINDER 15 + +#define R3_MASS_DENSITY 0 + +#define R3_MASS_TOTAL 1 + +#define R3_MASS_PROPERTIES 2 + +#define R3_ABI_VERSION 1 + +#define R3_DYNAMIC 0 + +#define R3_FIXED 1 + +#define R3_KINEMATIC_POSITION_BASED 2 + +#define R3_KINEMATIC_VELOCITY_BASED 3 + +#define R3_COLLISION_EVENTS 1 + +#define R3_CONTACT_FORCE_EVENTS 2 + +#define R3_COMBINE_AVERAGE 0 + +#define R3_COMBINE_MIN 1 + +#define R3_COMBINE_MULTIPLY 2 + +#define R3_COMBINE_MAX 3 + +#define R3_FILTER_CONTACT_PAIRS 1 + +#define R3_FILTER_INTERSECTION_PAIR 2 + +#define R3_MODIFY_SOLVER_CONTACTS 4 + +#define R3_QUERY_EXCLUDE_FIXED 1 + +#define R3_QUERY_EXCLUDE_KINEMATIC 2 + +#define R3_QUERY_EXCLUDE_DYNAMIC 4 + +#define R3_QUERY_EXCLUDE_SENSORS 8 + +#define R3_QUERY_EXCLUDE_SOLIDS 16 + +#define R3_QUERY_ONLY_DYNAMIC 3 + +#define R3_QUERY_ONLY_KINEMATIC 5 + +#define R3_QUERY_ONLY_FIXED 6 + +#define R3_DEBUG_COLLIDER_SHAPES 1 + +#define R3_DEBUG_RIGID_BODY_AXES 2 + +#define R3_DEBUG_MULTIBODY_JOINTS 4 + +#define R3_DEBUG_IMPULSE_JOINTS 8 + +#define R3_DEBUG_SOLVER_CONTACTS 16 + +#define R3_DEBUG_CONTACTS 32 + +#define R3_DEBUG_COLLIDER_AABBS 64 + +#define R3_DEBUG_SOFT_BODIES 128 + +#define R3_DEBUG_PSEUDO_NORMALS 256 + +#define R3_DEBUG_SOFT_VOLUME_CONTACTS 512 + +#define R3_DEBUG_SOFT_BODY_STRESS 1024 + +#define R3_GROUPS_AND 0 + +#define R3_GROUPS_OR 1 + +#define R3_MOTOR_ACCELERATION_BASED 0 + +#define R3_MOTOR_FORCE_BASED 1 + +#define R3_SOFT_CELL_VOLUME 0 + +#define R3_SOFT_CELL_COROTATIONAL 1 + +#define R3_SOFT_CELL_NEO_HOOKEAN 2 + +#define R3_SOFT_SOLVER_CONSTRAINTS 0 + +#define R3_SOFT_SOLVER_FEM 1 + +#define R3_AXIS_LIN_X 0 + +#define R3_AXIS_LIN_Y 1 + +#define R3_LOCK_TRANSLATION_X 1 + +#define R3_LOCK_TRANSLATION_Y 2 + +#define R3_LOCK_TRANSLATION_Z 4 + +#define R3_LOCK_ROTATION_X 8 + +#define R3_LOCK_ROTATION_Y 16 + +#define R3_LOCK_ROTATION_Z 32 + +#if defined(RAPIER_DIM2) +#define R3_AXIS_ANG_X 2 +#endif + +#if defined(RAPIER_DIM3) +#define R3_AXIS_ANG_X 3 +#endif + +#if defined(RAPIER_DIM2) +#define R3_JOINT_FIXED_AXES 7 +#endif + +#if defined(RAPIER_DIM3) +#define R3_JOINT_FIXED_AXES 63 +#endif + +#if defined(RAPIER_DIM2) +#define R3_JOINT_REVOLUTE_AXES 3 +#endif + +#if defined(RAPIER_DIM3) +#define R3_JOINT_REVOLUTE_AXES 55 +#endif + +#if defined(RAPIER_DIM2) +#define R3_JOINT_PRISMATIC_AXES 6 +#endif + +#if defined(RAPIER_DIM3) +#define R3_JOINT_PRISMATIC_AXES 62 +#endif + +#if defined(RAPIER_DIM3) +#define R3_AXIS_LIN_Z 2 +#endif + +#if defined(RAPIER_DIM3) +#define R3_AXIS_ANG_Y 4 +#endif + +#if defined(RAPIER_DIM3) +#define R3_AXIS_ANG_Z 5 +#endif + +#if defined(RAPIER_DIM3) +#define R3_JOINT_SPHERICAL_AXES 7 +#endif + +/** + * Read-only body type used by soft-body cluster proxies; not valid for builder_new. + */ +#define R3_SOFT_FRAME 4 + +#define R3_SHAPE_CAST_OUT_OF_ITERATIONS 0 + +#define R3_SHAPE_CAST_CONVERGED 1 + +#define R3_SHAPE_CAST_FAILED 2 + +#define R3_SHAPE_CAST_PENETRATING 3 + +#define R3_FEATURE_UNKNOWN 0 + +#define R3_FEATURE_VERTEX 1 + +#define R3_FEATURE_EDGE 2 + +#define R3_FEATURE_FACE 3 + +/** + * Native URDF/MJCF multibody insertion flags. + */ +#define R3_MULTIBODY_JOINTS_ARE_KINEMATIC 1 + +#define R3_MULTIBODY_DISABLE_SELF_CONTACTS 2 + +#define R3_MULTIBODY_SKIP_LOOP_CLOSURES 4 + +#define R3_MULTIBODY_SKIP_JOINT_MOTORS 8 + +#define R3_MULTIBODY_SKIP_JOINT_LIMITS 16 + +#define R3_MULTIBODY_SKIP_JOINT_SPRINGS 32 + +/** + * Parry triangle-mesh and heightfield flags used by the public shape constructors. + */ +#define R3_TRIMESH_MERGE_DUPLICATE_VERTICES 16 + +#define R3_TRIMESH_FIX_INTERNAL_EDGES 144 + +#define R3_TRIMESH_DEFORMABLE 256 + +#define R3_TRIMESH_FIX_INTERNAL_EDGES_TWO_SIDED 656 + +#define R3_HEIGHTFIELD_FIX_INTERNAL_EDGES 1 + +/** + * Immutable owned byte buffer. Release with the matching FreeBytes function. + */ +typedef struct R3Bytes R3Bytes; + +/** + * Borrowed native contact context. Valid only during its callback; never retain or free it. + */ +typedef struct R3ContactModificationContext R3ContactModificationContext; + +#if defined(RAPIER_DIM3) +typedef struct R3DynamicRayCastVehicleController R3DynamicRayCastVehicleController; +#endif + +/** + * Events accumulate until clear. Copying events never drains them, allowing two-call buffer sizing. + */ +typedef struct R3EventCollector R3EventCollector; + +/** + * Controller plus reusable collision output from the last move_shape call. + */ +typedef struct R3KinematicCharacterController R3KinematicCharacterController; + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R3MjcfRobot R3MjcfRobot; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R3MjcfRobotHandles R3MjcfRobotHandles; +#endif + +/** + * PID controller with persistent integral state. + */ +typedef struct R3PidController R3PidController; + +/** + * Callback-scoped read access to bodies and colliders. Never retain or free it. + * Only the Read* functions accept this context; it cannot mutate the world. + */ +typedef struct R3ReadContext R3ReadContext; + +/** + * Owned tessellated shape: flat triangle vertices and independent line segments, in local space. + * Rounded 3D shapes use their inner surface (as in the Rust testbed). Halfspaces use a finite patch. + */ +typedef struct R3ShapeMesh R3ShapeMesh; + +/** + * Owned copy of a tear event. Read particle remapping before rebuilding render meshes. + */ +typedef struct R3SoftBodyTearEvent R3SoftBodyTearEvent; + +#if defined(RAPIER_DIM3) +/** + * Owned indexed geometry from Parry's shape tessellation, preserving its vertex order. + */ +typedef struct R3TriMeshData R3TriMeshData; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R3UrdfRobot R3UrdfRobot; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R3UrdfRobotHandles R3UrdfRobotHandles; +#endif + +/** + * Sole owner of simulation state. Handles belong to the world that created them. + * Ordinary reads may overlap. A mutation or step requires exclusive access. + * Destruction must be externally synchronized with all users of this pointer. + */ +typedef struct R3World R3World; + +#if defined(RAPIER_F32) +typedef float R3Real; +#endif + +#if defined(RAPIER_F64) +typedef double R3Real; +#endif + +typedef struct R3SpringCoefficients { + R3Real natural_frequency; + R3Real damping_ratio; +} R3SpringCoefficients; + +/** + * ABI booleans are uint32_t: zero is false, one is true. + */ +typedef uint32_t R3Bool; + +typedef struct R3OptionalReal { + R3Bool enabled; + R3Real value; +} R3OptionalReal; + +typedef struct R3OptionalU32 { + R3Bool enabled; + uint32_t value; +} R3OptionalU32; + +/** + * Plain configuration data; initialize defaults, edit, then apply. No destructor. + */ +typedef struct R3SoftBodyMaterial { + struct R3SpringCoefficients edgeSoftness; + struct R3SpringCoefficients bendSoftness; + struct R3SpringCoefficients volumeSoftness; + struct R3SpringCoefficients shapeMatchingSoftness; + R3Real youngModulus; + R3Real poissonRatio; + R3Real elasticDampingRatio; + R3Real plasticYield; + R3Real plasticCreep; + R3Real plasticMax; + R3Real deformationDamping; + R3Real edgePlasticYield; + R3Real edgePlasticCreep; + R3Real edgePlasticMax; + uint32_t edgePlasticFlow; + struct R3OptionalReal tearStrain; + struct R3OptionalReal tearForce; + R3Real tearSmoothing; + R3Real interiorStrength; + uint32_t maxTearsPerStep; + struct R3OptionalU32 minPiece; +} R3SoftBodyMaterial; + +/** + * Plain configuration data; initialize defaults, edit, then apply. No destructor. + */ +typedef struct R3SoftRecoverySettings { + R3Bool authoredVelocityMargin; + R3Bool edgeSpeculation; + R3Bool invertedCellDetection; + R3Bool selfCrossingDetection; + R3Bool detectionMotionGating; + R3Bool crossBodyDetection; + R3Bool selfStandDown; + R3Bool crossBodyExpelGate; + R3Bool edgeStandDown; + R3Bool crossingRepulsion; + R3Bool crossingRepulsionGuide; + R3Bool crossingRepulsionSelfGuide; + R3Real recoveryPace; + R3Bool overlapConstraints; + R3Bool overlapRigid; + R3Bool overlapSkipSelfTangled; + R3Bool overlapEdgeStandDown; + R3Real overlapConstraintPace; + uint32_t overlapPatchConstraints; + R3Bool overlapSkinVolume; + R3Real overlapKeptDepth; + R3Bool overlapSelfRegions; + R3Bool overlapNormalPush; + R3Bool overlapMultiVolume; + uint32_t overlapSplit; + uint32_t overlapPatience; + R3Real overlapProgressMargin; +} R3SoftRecoverySettings; + +#if defined(RAPIER_FEM) +/** + * Plain configuration data; initialize defaults, edit, then apply. No destructor. + */ +typedef struct R3SoftFemParameters { + R3Real linearTolerance; + size_t maxLinearIterations; + size_t maxDenseDofs; +} R3SoftFemParameters; +#endif + +/** + * Plain configuration data; initialize defaults, edit, then apply. No destructor. + */ +typedef struct R3SoftBodiesSettings { + struct R3SoftRecoverySettings recovery; + R3Real resweepStrain; + size_t maxExtraSubsteps; + R3Real contactStiffening; +#if defined(RAPIER_FEM) + struct R3SoftFemParameters fem; +#endif +} R3SoftBodiesSettings; + +/** + * Plain configuration data; initialize defaults, edit, then apply. No destructor. + */ +typedef struct R3IntegrationParameters { + R3Real dt; + R3Real minCcdDt; + struct R3SpringCoefficients contactSoftness; + struct R3SpringCoefficients staticContactSoftness; + R3Real warmstartCoefficient; + R3Real lengthUnit; + struct R3SoftBodiesSettings softBodies; + R3Real normalizedAllowedLinearError; + R3Real normalizedMaxCorrectiveVelocity; + R3Real normalizedPredictionDistance; + R3Real normalizedMaxLinearVelocity; + size_t numSolverIterations; + size_t numInternalPgsIterations; + size_t numInternalStabilizationIterations; + size_t maxCcdSubsteps; + R3Bool contactClustering; + R3Bool contactRecycling; + R3Real normalizedContactRecycleDistance; + R3Bool frictionInBiasPass; + R3Bool warmstartJoints; +#if defined(RAPIER_DIM3) + uint32_t frictionModel; +#endif +} R3IntegrationParameters; + +/** + * Status-returning operations use these integer codes. + */ +typedef uint32_t R3Status; + +typedef struct R3Vector { + R3Real x; + R3Real y; +#if defined(RAPIER_DIM3) + R3Real z; +#endif +} R3Vector; + +/** + * 2D: angle in radians. 3D: unit quaternion in x,y,z,w order (normalized on input). + */ +typedef struct R3Rotation { +#if defined(RAPIER_DIM2) + R3Real angle; +#endif +#if defined(RAPIER_DIM3) + R3Real x; +#endif +#if defined(RAPIER_DIM3) + R3Real y; +#endif +#if defined(RAPIER_DIM3) + R3Real z; +#endif +#if defined(RAPIER_DIM3) + R3Real w; +#endif +} R3Rotation; + +typedef struct R3Pose { + struct R3Vector translation; + struct R3Rotation rotation; +} R3Pose; + +typedef struct R3JointLimits { + R3Real min; + R3Real max; +} R3JointLimits; + +typedef struct R3JointMotor { + R3Real targetVel; + R3Real targetPos; + R3Real stiffness; + R3Real damping; + R3Real maxForce; + uint32_t model; +} R3JointMotor; + +typedef struct R3UserData { + uint64_t low; + uint64_t high; +} R3UserData; + +/** + * Copyable joint configuration. Limits/motors take effect when their axis mask is enabled. + * Solver impulses are deliberately excluded. Applying data resets cached limit and motor impulses. + */ +typedef struct R3JointDesc { + struct R3Pose localFrame1; + struct R3Pose localFrame2; + uint8_t lockedAxes; + uint8_t limitAxes; + uint8_t motorAxes; + uint8_t coupledAxes; + struct R3JointLimits limits[R3_JOINT_DOF_COUNT]; + struct R3JointMotor motors[R3_JOINT_DOF_COUNT]; + struct R3SpringCoefficients softness; + R3Bool contactsEnabled; + R3Bool enabled; + struct R3UserData userData; +} R3JointDesc; + +/** + * Copyable non-owning handle: world pointer plus entity index and generation. + * The world must remain alive throughout every use. Copying does not retain it. + * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + */ +typedef struct R3ImpulseJointHandle { + /** + * Borrowed owning world. Never use this handle after freeing that world. + */ + struct R3World *world; + uint32_t index; + uint32_t generation; +} R3ImpulseJointHandle; + +/** + * Copyable non-owning handle: world pointer plus entity index and generation. + * The world must remain alive throughout every use. Copying does not retain it. + * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + */ +typedef struct R3RigidBodyHandle { + /** + * Borrowed owning world. Never use this handle after freeing that world. + */ + struct R3World *world; + uint32_t index; + uint32_t generation; +} R3RigidBodyHandle; + +/** + * Copyable non-owning handle: world pointer plus entity index and generation. + * The world must remain alive throughout every use. Copying does not retain it. + * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + */ +typedef struct R3MultibodyJointHandle { + /** + * Borrowed owning world. Never use this handle after freeing that world. + */ + struct R3World *world; + uint32_t index; + uint32_t generation; +} R3MultibodyJointHandle; + +/** + * Parameters for the native volumetric mesher. Enclosure: 0 cover, 1 crust (3D). + */ +typedef struct R3VolumeMeshParameters { + R3Real cell_size; +#if defined(RAPIER_DIM2) + R3Real min_angle; +#endif +#if defined(RAPIER_DIM3) + uint32_t enclosure; +#endif +#if defined(RAPIER_DIM3) + uint32_t cover_smoothing; +#endif +#if defined(RAPIER_DIM3) + R3Real cover_guard; +#endif +#if defined(RAPIER_DIM3) + uint32_t cover_subdivisions; +#endif +} R3VolumeMeshParameters; + +/** + * Borrowed array of vector elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R3VectorView { + const struct R3Vector *data; + size_t count; +} R3VectorView; + +/** + * Borrowed array of real elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R3RealView { + const R3Real *data; + size_t count; +} R3RealView; + +/** + * Borrowed array of index elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R3IndexView { + const uint32_t *data; + size_t count; +} R3IndexView; + +/** + * Vertex indices for one edge; contiguous u32 fields with no padding. + */ +typedef struct R3Edge { + uint32_t a; + uint32_t b; +} R3Edge; + +/** + * Borrowed array of edge elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R3EdgeView { + const struct R3Edge *data; + size_t count; +} R3EdgeView; + +typedef struct R3SoftEdgeSoftness { + uint32_t edge; + struct R3SpringCoefficients softness; +} R3SoftEdgeSoftness; + +/** + * Borrowed elements; count counts elements. Data must remain live through insertion. + */ +typedef struct R3SoftEdgeSoftnessView { + const struct R3SoftEdgeSoftness *data; + size_t count; +} R3SoftEdgeSoftnessView; + +typedef struct R3SoftEdgeTear { + uint32_t edge; + R3Real resistance; +} R3SoftEdgeTear; + +/** + * Borrowed elements; count counts elements. Data must remain live through insertion. + */ +typedef struct R3SoftEdgeTearView { + const struct R3SoftEdgeTear *data; + size_t count; +} R3SoftEdgeTearView; + +/** + * Vertex indices for one triangle; contiguous u32 fields with no padding. + */ +typedef struct R3Triangle { + uint32_t a; + uint32_t b; + uint32_t c; +} R3Triangle; + +/** + * Borrowed array of triangle elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R3TriangleView { + const struct R3Triangle *data; + size_t count; +} R3TriangleView; + +/** + * Vertex indices for one tetrahedron; contiguous u32 fields with no padding. + */ +typedef struct R3Tetrahedron { + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; +} R3Tetrahedron; + +/** + * Borrowed array of tetrahedron elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R3TetrahedronView { + const struct R3Tetrahedron *data; + size_t count; +} R3TetrahedronView; + +#if defined(RAPIER_DIM2) +typedef struct R3TriangleView R3CellView; +#endif + +#if defined(RAPIER_DIM3) +typedef struct R3TetrahedronView R3CellView; +#endif + +#if defined(RAPIER_DIM2) +typedef struct R3EdgeView R3SurfaceElementView; +#endif + +#if defined(RAPIER_DIM3) +typedef struct R3TriangleView R3SurfaceElementView; +#endif + +/** + * Vertex indices for one dihedral; contiguous u32 fields with no padding. + */ +typedef struct R3Dihedral { + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; +} R3Dihedral; + +/** + * Borrowed array of dihedral elements. count always counts elements, not scalars. + * Copying this view does not copy its data or extend its lifetime. No Free is needed. + * Data must remain live through the build/insert call that reads the description. + * NULL is permitted only when count is zero. + */ +typedef struct R3DihedralView { + const struct R3Dihedral *data; + size_t count; +} R3DihedralView; + +/** + * Optional boolean override. When disabled, retain the recipe's native default. + */ +typedef struct R3OptionalBool { + R3Bool enabled; + R3Bool value; +} R3OptionalBool; + +/** + * Opaque SharedShape. See the ownership and borrowing contract in README.md. + */ +typedef struct R3SharedShape R3SharedShape; + + +/** + * Borrowed elements; count counts elements. Data must remain live through insertion. + */ +typedef struct R3CompoundShapeView { + const struct R3CompoundShapeDesc *data; + size_t count; +} R3CompoundShapeView; + +/** + * Non-owning shape description. Only fields selected by kind are read. + * a = cuboid half extents, capsule/segment endpoint, triangle vertex, or halfspace normal. + * b/c = remaining endpoints/vertices. radius is also the rounded-cuboid border radius. + * Mesh views count edges or triangles; heightfields are column-major. + * Arrays, compound children, and sharedShape remain borrowed until build/insert returns. + */ +typedef struct R3ShapeDesc { + uint32_t kind; + struct R3Vector a; + struct R3Vector b; + struct R3Vector c; + R3Real radius; + R3Real halfHeight; + R3Real borderRadius; + struct R3VectorView vertices; + struct R3TriangleView triangles; + struct R3EdgeView edges; + uint32_t flags; + struct R3RealView heights; + size_t rows; + size_t columns; + struct R3Vector scale; + const R3SharedShape *sharedShape; + struct R3CompoundShapeView children; +} R3ShapeDesc; + +typedef struct R3CompoundShapeDesc { + struct R3Pose pose; + struct R3ShapeDesc shape; +} R3CompoundShapeDesc; + + +#if defined(RAPIER_DIM2) +typedef R3Real R3AngVector; +#endif + +#if defined(RAPIER_DIM3) +typedef struct R3Vector R3AngVector; +#endif + +/** + * Explicit mass and principal inertia, matching MassProperties constructors. Zero mass/inertia means infinite. + */ +typedef struct R3MassProperties { + struct R3Vector local_com; + R3Real mass; + R3AngVector principal_inertia; +#if defined(RAPIER_DIM3) + struct R3Rotation principal_inertia_local_frame; +#endif +} R3MassProperties; + +typedef struct R3InteractionGroups { + uint32_t memberships; + uint32_t filter; + uint32_t test_mode; +} R3InteractionGroups; + +/** + * Copyable collider construction data. Shape inputs are borrowed, never owned. + */ +typedef struct R3ColliderDesc { + struct R3ShapeDesc shape; + struct R3Pose position; + uint32_t massMode; + R3Real density; + R3Real mass; + struct R3MassProperties massProperties; + R3Real friction; + R3Real restitution; + uint32_t frictionCombineRule; + uint32_t restitutionCombineRule; + R3Bool isSensor; + R3Bool enabled; + struct R3InteractionGroups collisionGroups; + struct R3InteractionGroups solverGroups; + uint16_t activeCollisionTypes; + uint32_t activeHooks; + uint32_t activeEvents; + R3Real contactForceEventThreshold; + R3Real contactSkin; + struct R3UserData userData; +} R3ColliderDesc; + +/** + * Copyable recipe, not an owned procedural builder. Initialize before editing. + * All array views borrow caller data until build/insert returns; counts + * for topology arrays are element counts (edges, triangles, or tetrahedra). + * Nonempty topology overrides the generator's topology. Zero counts retain it. + * Generator inputs: a/b are rope ends or center/half-extents; cloth uses a/du/dv. + */ +typedef struct R3SoftBodyDesc { + uint32_t kind; + struct R3Vector a; + struct R3Vector b; + struct R3Vector du; + struct R3Vector dv; + size_t nx; + size_t ny; + size_t nz; + R3Real radius; + R3Real radiusEnd; + struct R3Vector translation; + struct R3OptionalReal totalMass; + struct R3VolumeMeshParameters meshing; + struct R3VectorView positions; + struct R3RealView masses; + struct R3IndexView pinned; + struct R3EdgeView edges; + struct R3EdgeView bendEdges; + struct R3IndexView tensionOnlyEdges; + struct R3SoftEdgeSoftnessView edgeSoftness; + struct R3SoftEdgeTearView edgeTearResistance; + R3CellView cells; + R3SurfaceElementView surface; +#if defined(RAPIER_DIM3) + struct R3DihedralView dihedrals; +#endif +#if defined(RAPIER_DIM3) + struct R3EdgeView wire; +#endif + struct R3VectorView skinVertices; + R3SurfaceElementView skinIndices; + struct R3SoftBodyMaterial material; + uint32_t cellModel; + /** + * 0 = constraints, 1 = FEM (requires a library built with FEM). + */ + uint32_t solver; + R3Real particleMass; + /** + * Disabled by default: retain the radius computed by the generator. + */ + struct R3OptionalReal particleRadius; + R3Bool volumePreservation; + R3Real volumeFactor; + struct R3OptionalBool shapeMatching; + R3Bool selfContacts; + R3Bool skinCollision; + R3Bool collisionEnabled; + struct R3ColliderDesc collider; + R3Real linearDamping; + R3Real gravityScale; + size_t additionalSolverIterations; + size_t additionalPgsIterations; + R3Bool canSleep; + int8_t dominanceGroup; + struct R3UserData userData; +} R3SoftBodyDesc; + +/** + * Copyable non-owning handle: world pointer plus entity index and generation. + * The world must remain alive throughout every use. Copying does not retain it. + * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + */ +typedef struct R3SoftBodyHandle { + /** + * Borrowed owning world. Never use this handle after freeing that world. + */ + struct R3World *world; + uint32_t index; + uint32_t generation; +} R3SoftBodyHandle; + +/** + * Non-owning deformable binding description. Direct particle indices are borrowed. + */ +typedef struct R3SoftMeshBindingDesc { + uint32_t kind; + struct R3IndexView particles; + R3Real epsilon; + R3Bool selfContacts; +} R3SoftMeshBindingDesc; + +/** + * Copyable non-owning handle: world pointer plus entity index and generation. + * The world must remain alive throughout every use. Copying does not retain it. + * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + */ +typedef struct R3ColliderHandle { + /** + * Borrowed owning world. Never use this handle after freeing that world. + */ + struct R3World *world; + uint32_t index; + uint32_t generation; +} R3ColliderHandle; + +typedef struct R3QueryFilter { + uint32_t flags; + R3Bool use_groups; + struct R3InteractionGroups groups; + struct R3ColliderHandle exclude_collider; + struct R3RigidBodyHandle exclude_rigid_body; +} R3QueryFilter; + +/** + * Called with scoped read access and a collider handle. Shared queries may nest; + * world mutations are rejected until the outer query returns. Never retain the context. + */ +typedef R3Bool (RAPIER_CALL *R3QueryPredicate)(void *user_data, + const struct R3ReadContext *read, + struct R3ColliderHandle handle); + +/** + * Copyable query settings. They borrow callback data, never world components. + */ +typedef struct R3QueryOptions { + struct R3QueryFilter filter; + R3QueryPredicate predicate; + void *userData; +} R3QueryOptions; + +typedef struct R3RayHit { + struct R3ColliderHandle collider; + R3Real time_of_impact; + struct R3Vector normal; + uint32_t feature_type; + uint32_t feature_id; +} R3RayHit; + +typedef struct R3PointProjection { + struct R3ColliderHandle collider; + struct R3Vector point; + R3Bool is_inside; +} R3PointProjection; + +typedef struct R3ShapeCastHit { + struct R3ColliderHandle collider; + R3Real time_of_impact; + struct R3Vector witness1; + struct R3Vector witness2; + struct R3Vector normal1; + struct R3Vector normal2; + uint32_t status; +} R3ShapeCastHit; + +typedef struct R3ShapeCastOptions { + R3Real max_time_of_impact; + R3Real target_distance; + R3Bool stop_at_penetration; + R3Bool compute_impact_geometry_on_penetration; +} R3ShapeCastOptions; + +typedef struct R3Aabb { + struct R3Vector mins; + struct R3Vector maxs; +} R3Aabb; + +typedef struct R3RayToi { + struct R3ColliderHandle collider; + R3Real toi; + R3Bool found; +} R3RayToi; + +typedef struct R3OptionalRayHit { + struct R3RayHit hit; + R3Bool found; +} R3OptionalRayHit; + +/** + * Stack-allocated rigid-body construction data. Initialize with RigidBodyDescInit. + * Copying this value is safe; it owns no resources and must never be freed by Rapier. + */ +typedef struct R3RigidBodyDesc { + struct R3Pose position; + struct R3Vector linvel; + R3AngVector angvel; + uint32_t bodyType; + R3Real gravityScale; + R3Real linearDamping; + R3Real angularDamping; + R3Real additionalMass; + R3Bool useAdditionalMassProperties; + struct R3MassProperties additionalMassProperties; + uint8_t lockedAxes; + R3Bool canSleep; + R3Bool sleeping; + R3Bool ccdEnabled; + R3Real softCcdPrediction; + R3Bool allowFastRotation; + R3Bool enabled; + int8_t dominanceGroup; + size_t additionalSolverIterations; + size_t additionalPgsIterations; + R3Bool gyroscopicForcesEnabled; + struct R3UserData userData; +} R3RigidBodyDesc; + +/** + * Sizes of the POD types in this library build, for foreign-language layout checks. + */ +typedef struct R3PodLayout { + size_t rigidBodyDesc; + size_t colliderDesc; + size_t shapeDesc; + size_t jointDesc; + size_t softBodyMaterial; + size_t integrationParameters; + size_t softBodyDesc; + size_t softMeshBindingDesc; + size_t queryOptions; + /** + * Zero unless 3D f32 robotics is enabled. + */ + size_t urdfLoaderOptions; + /** + * Zero unless 3D f32 robotics is enabled. + */ + size_t mjcfLoaderOptions; +} R3PodLayout; + +/** + * CharacterLength counterpart: relative=1 scales with character height, relative=0 uses world units. + */ +typedef struct R3CharacterLength { + R3Real value; + R3Bool relative; +} R3CharacterLength; + +typedef struct R3CharacterMovement { + struct R3Vector translation; + R3Bool grounded; + R3Bool is_sliding_down_slope; +} R3CharacterMovement; + +typedef struct R3CharacterCollision { + struct R3ColliderHandle collider; + struct R3Pose character_pos; + struct R3Vector translation_applied; + struct R3Vector translation_remaining; + struct R3ShapeCastHit hit; +} R3CharacterCollision; + +typedef struct R3PidGains { + struct R3Vector lin_kp; + struct R3Vector lin_ki; + struct R3Vector lin_kd; + R3AngVector ang_kp; + R3AngVector ang_ki; + R3AngVector ang_kd; +} R3PidGains; + +typedef struct R3VelocityCorrection { + struct R3Vector linear; + R3AngVector angularVelocity; +} R3VelocityCorrection; + +typedef struct R3CharacterControllerSettings { + R3Bool slide; + R3Real max_slope_climb_angle; + R3Real min_slope_slide_angle; + R3Bool snap_to_ground; + struct R3CharacterLength snap_distance; +} R3CharacterControllerSettings; + +#if defined(RAPIER_DIM3) +typedef struct R3WheelTuning { + R3Real suspension_stiffness; + R3Real suspension_compression; + R3Real suspension_damping; + R3Real max_suspension_travel; + R3Real friction_slip; + R3Real max_suspension_force; + R3Real side_friction_stiffness; +} R3WheelTuning; +#endif + +#if defined(RAPIER_DIM3) +typedef struct R3WheelState { + struct R3Vector center; + struct R3Vector suspension; + struct R3Vector axle; + R3Real rotation; + R3Real suspension_force; + R3Real suspension_length; + R3Bool is_in_contact; + struct R3ColliderHandle ground_object; + struct R3Vector contact_point; + struct R3Vector contact_normal; +} R3WheelState; +#endif + +/** + * Called synchronously on the calling thread when an operation reports an error. + * The diagnostic is borrowed for the duration of the callback. The callback + * must return normally or terminate the process: never throw or longjmp across + * the Rust/C boundary. Nested failing calls do not invoke the handler recursively. + */ +typedef void (RAPIER_CALL *R3ErrorCallback)(R3Status, const char*, void*); + +/** + * An optional thread-local error handler. A null callback disables reporting. + * Keep the callback and user_data alive until the handler is replaced. + */ +typedef struct R3ErrorHandler { + R3ErrorCallback callback; + void *user_data; +} R3ErrorHandler; + +typedef struct R3JointBodies { + struct R3RigidBodyHandle body1; + struct R3RigidBodyHandle body2; +} R3JointBodies; + +typedef struct R3InverseKinematicsOptions { + R3Real damping; + size_t max_iters; + uint8_t constrained_axes; + R3Real epsilon_linear; + R3Real epsilon_angular; +} R3InverseKinematicsOptions; + +/** + * Optional per-link filter, called synchronously. Must not reenter or retain physics objects. + */ +typedef R3Bool (RAPIER_CALL *R3IkJointCanMove)(void*, struct R3RigidBodyHandle); + +/** + * Collision start/stop flags match Rapier CollisionEventFlags. + */ +typedef struct R3CollisionEvent { + struct R3ColliderHandle collider1; + struct R3ColliderHandle collider2; + R3Bool started; + uint32_t flags; +} R3CollisionEvent; + +typedef struct R3ContactForceEvent { + struct R3ColliderHandle collider1; + struct R3ColliderHandle collider2; + struct R3Vector total_force; + R3Real total_force_magnitude; + struct R3Vector max_force_direction; + R3Real max_force_magnitude; + R3Bool started; +} R3ContactForceEvent; + +/** + * Pair callback: -1 rejects a contact pair; 0 detects contacts without impulses; 1 computes impulses. + * For sensor intersections only, zero rejects and any positive value accepts. + */ +typedef int32_t (RAPIER_CALL *R3PairFilter)(void *user_data, + const struct R3ReadContext *read, + struct R3ColliderHandle collider1, + struct R3ColliderHandle collider2, + struct R3RigidBodyHandle body1, + struct R3RigidBodyHandle body2); + +/** + * Mutable per-manifold properties. Set enabled=0 to discard all its solver contacts. + */ +typedef struct R3ContactModification { + struct R3Vector normal; + R3Real friction; + R3Real restitution; + uint32_t user_data; + R3Bool enabled; +} R3ContactModification; + +typedef void (RAPIER_CALL *R3ModifyContacts)(void *user_data, + const struct R3ReadContext *read, + struct R3ColliderHandle collider1, + struct R3ColliderHandle collider2, + struct R3ContactModification *contact); + +typedef void (RAPIER_CALL *R3ModifyContactContext)(void *user_data, + const struct R3ReadContext *read, + struct R3ColliderHandle collider1, + struct R3ColliderHandle collider2, + struct R3ContactModificationContext *context); + +/** + * Callbacks must not unwind or retain arguments. Use their ReadContext to inspect bodies and + * colliders; ordinary access to the stepping world returns WORLD_BUSY. Mutations must be + * performed after stepping. With parallel builds + * callbacks and their user_data must be safe for concurrent invocation. NULL callbacks use defaults. + */ +typedef struct R3PhysicsHooks { + void *user_data; + R3PairFilter filter_contact_pair; + R3PairFilter filter_intersection_pair; + R3ModifyContacts modify_solver_contacts; + /** + * Runs after the legacy property callback. Context accessors may be called here. + */ + R3ModifyContactContext modify_solver_contacts_context; +} R3PhysicsHooks; + +/** + * Borrowed bytes. Valid while the source Bytes object remains alive; never free data. + */ +typedef struct R3ByteView { + const uint8_t *data; + size_t count; +} R3ByteView; + +typedef struct R3DebugLine { + struct R3Vector a; + struct R3Vector b; + float color[4]; +} R3DebugLine; + +typedef struct R3ParticleDestination { + struct R3SoftBodyHandle body; + uint32_t index; +} R3ParticleDestination; + +typedef struct R3SoftClusterSplit { + uint32_t source_cluster; + struct R3SoftBodyHandle soft_body; + uint32_t cluster; + struct R3RigidBodyHandle proxy; + R3Bool keeps_proxy; +} R3SoftClusterSplit; + +typedef struct R3SoftJointMove { + struct R3ImpulseJointHandle joint; + struct R3RigidBodyHandle from; + struct R3RigidBodyHandle to; +} R3SoftJointMove; + +typedef struct R3OptionalParticleDestination { + struct R3SoftBodyHandle body; + uint32_t index; + R3Bool found; +} R3OptionalParticleDestination; + +typedef struct R3BuildInfo { + uint32_t abi_version; + uint32_t dimension; + uint32_t real_size; + uint32_t pointer_size; +} R3BuildInfo; + +/** + * Features available through the loaded C library, independent of consumer defines. + */ +typedef struct R3BuildFeatures { + /** + * Whether native profiling timers were compiled in. + */ + R3Bool profiling; + /** + * Solver SIMD lane count. Hardware instruction width depends on the target CPU. + */ + uint32_t simd_lanes; + /** + * Whether this library exposes Rapier's parallel execution and thread-pool APIs. + */ + R3Bool parallel; +} R3BuildFeatures; + +typedef struct R3ContactPair { + struct R3ColliderHandle collider1; + struct R3ColliderHandle collider2; + R3Bool has_any_active_contact; + struct R3Vector total_impulse; + R3Real total_impulse_magnitude; + R3Real max_impulse; + struct R3Vector max_impulse_direction; +} R3ContactPair; + +typedef struct R3IntersectionPair { + struct R3ColliderHandle collider1; + struct R3ColliderHandle collider2; + R3Bool intersecting; +} R3IntersectionPair; + +typedef struct R3ContactPoint { + size_t manifold_index; + struct R3Vector local_p1; + struct R3Vector local_p2; + struct R3Vector normal; + R3Real distance; + R3Real impulse; +} R3ContactPoint; + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Loader configuration. Initialize with DefaultUrdfLoaderOptions; no destructor. + * Blueprint array views and shared shapes are borrowed through the load call. + */ +typedef struct R3UrdfLoaderOptions { + R3Bool createCollidersFromCollisionShapes; + R3Bool createCollidersFromVisualShapes; + R3Bool applyImportedMassProps; + R3Bool enableJointCollisions; + R3Bool makeRootsFixed; + R3Bool squeezeEmptyFixedLinks; + struct R3Pose shift; + R3Real scale; + struct R3ColliderDesc colliderBlueprint; + struct R3RigidBodyDesc rigidBodyBlueprint; +} R3UrdfLoaderOptions; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Loader configuration. Initialize with DefaultMjcfLoaderOptions; no destructor. + * Blueprint array views and shared shapes are borrowed through the load call. + */ +typedef struct R3MjcfLoaderOptions { + R3Bool createCollidersFromCollisionShapes; + R3Bool createCollidersFromVisualShapes; + R3Bool applyImportedMassProps; + R3Bool enableJointCollisions; + R3Bool makeRootsFixed; + R3Bool skipPlaneGeoms; + R3Bool disableJointMotors; + struct R3Pose shift; + R3Real scale; + struct R3ColliderDesc colliderBlueprint; + struct R3RigidBodyDesc rigidBodyBlueprint; +} R3MjcfLoaderOptions; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * A borrowed visual declaration, valid until its robot is freed or its body storage changes. + */ +typedef struct R3MjcfVisualMesh R3MjcfVisualMesh; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R3RenderMaterial { + float metallic; + float roughness; + float reflectance; + float emissive[3]; +} R3RenderMaterial; +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +typedef struct R3MjcfVisualMeshInfo { + struct R3Pose local_pose; + float rgba[4]; + struct R3RenderMaterial material; + R3Bool has_color; + R3Bool has_material; + R3Bool is_trimesh; +} R3MjcfVisualMeshInfo; +#endif + +/** + * A copied state snapshot, with no pointers or ownership obligations. + */ +typedef struct R3RigidBodyState { + struct R3Pose position; + struct R3Vector linvel; + R3AngVector angvel; + R3Bool sleeping; + R3Bool enabled; + struct R3UserData userData; +} R3RigidBodyState; + +/** + * Stable identity of a live mesh within one soft body; matches Rapier's SoftMeshId. + */ +typedef struct R3SoftMeshId { + uint32_t cluster; + uint32_t mesh; +} R3SoftMeshId; + +/** + * Mesh identity and rendering metadata. A render-only mesh has an invalid collider handle. + */ +typedef struct R3SoftMeshInfo { + struct R3SoftMeshId id; + struct R3ColliderHandle collider; + size_t arity; + R3Bool is_skinned; + R3Bool collision_enabled; +} R3SoftMeshInfo; + +#if defined(RAPIER_F32) +/** + * Voxel coordinates have DIM signed integer components. + */ +typedef int32_t R3VoxelCoord; +#endif + +#if defined(RAPIER_F64) +typedef int64_t R3VoxelCoord; +#endif + +typedef struct R3VoxelKey { + R3VoxelCoord x; + R3VoxelCoord y; +#if defined(RAPIER_DIM3) + R3VoxelCoord z; +#endif +} R3VoxelKey; + +typedef struct R3VoxelQuery { + struct R3VoxelKey key; + struct R3Vector center; + struct R3Vector size; + R3Bool found; +} R3VoxelQuery; + +#define R3_OK 0 + +#define R3_NULL_POINTER 1 + +#define R3_INVALID_ARGUMENT 2 + +#define R3_INVALID_HANDLE 3 + +#define R3_BUFFER_TOO_SMALL 4 + +#define R3_UNSUPPORTED 5 + +#define R3_PANIC 6 + +#define R3_NOT_FOUND 7 + +/** + * Conflicting or reentrant access to simulation state. No mutation was performed. + */ +#define R3_WORLD_BUSY 8 + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +RAPIER_API RAPIER_CALL struct R3SoftBodyMaterial r3DefaultSoftBodyMaterial(void); + +RAPIER_API RAPIER_CALL struct R3SoftRecoverySettings r3DefaultSoftRecoverySettings(void); + +#if defined(RAPIER_FEM) +RAPIER_API RAPIER_CALL struct R3SoftFemParameters r3DefaultSoftFemParameters(void); +#endif + +RAPIER_API RAPIER_CALL struct R3SoftBodiesSettings r3DefaultSoftBodiesSettings(void); + +RAPIER_API RAPIER_CALL struct R3IntegrationParameters r3DefaultIntegrationParameters(void); + +RAPIER_API RAPIER_CALL +struct R3IntegrationParameters r3IntegrationParameters(const struct R3World *world); + +/** + * Copies validated values; does not expose a writable alias to Rust memory. + */ +RAPIER_API RAPIER_CALL +R3Status r3SetIntegrationParameters(struct R3World *world, + const struct R3IntegrationParameters *data); + +RAPIER_API RAPIER_CALL struct R3JointDesc r3DefaultJointDesc(void); + +RAPIER_API RAPIER_CALL struct R3JointDesc r3FixedJointDesc(void); + +#if defined(RAPIER_DIM2) +RAPIER_API RAPIER_CALL struct R3JointDesc r3RevoluteJointDesc(void); +#endif + +#if defined(RAPIER_DIM3) +/** + * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + */ +RAPIER_API RAPIER_CALL struct R3JointDesc r3RevoluteJointDesc(struct R3Vector axis_vector); +#endif + +/** + * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + */ +RAPIER_API RAPIER_CALL struct R3JointDesc r3PrismaticJointDesc(struct R3Vector axis_vector); + +RAPIER_API RAPIER_CALL struct R3JointDesc r3RopeJointDesc(R3Real length); + +RAPIER_API RAPIER_CALL +struct R3JointDesc r3SpringJointDesc(R3Real length, + R3Real stiffness, + R3Real damping); + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL struct R3JointDesc r3SphericalJointDesc(void); +#endif + +#if defined(RAPIER_DIM2) +/** + * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + */ +RAPIER_API RAPIER_CALL struct R3JointDesc r3PinSlotJointDesc(struct R3Vector axis_vector); +#endif + +RAPIER_API RAPIER_CALL +struct R3ImpulseJointHandle r3InsertImpulseJoint(struct R3RigidBodyHandle body1, + struct R3RigidBodyHandle body2, + const struct R3JointDesc *joint); + +RAPIER_API RAPIER_CALL +struct R3MultibodyJointHandle r3InsertMultibodyJoint(struct R3RigidBodyHandle body1, + struct R3RigidBodyHandle body2, + const struct R3JointDesc *joint); + +RAPIER_API RAPIER_CALL struct R3SoftBodyDesc r3DefaultSoftBodyDesc(void); + +/** + * Consumes no caller-owned resources. All borrowed arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyHandle r3InsertSoftBody(struct R3World *world, + const struct R3SoftBodyDesc *desc); + +RAPIER_API RAPIER_CALL struct R3SoftMeshBindingDesc r3DefaultSoftMeshBindingDesc(void); + +RAPIER_API RAPIER_CALL +struct R3ColliderHandle r3InsertDeformableCollider(const struct R3ColliderDesc *collider, + const struct R3SoftMeshBindingDesc *binding, + struct R3RigidBodyHandle parent); + +RAPIER_API RAPIER_CALL struct R3QueryOptions r3DefaultQueryOptions(void); + +RAPIER_API RAPIER_CALL +struct R3RayHit r3CastRay(const struct R3World *world, + const struct R3QueryOptions *query_options, + struct R3Vector origin, + struct R3Vector direction, + R3Real max_toi, + R3Bool solid); + +RAPIER_API RAPIER_CALL +struct R3PointProjection r3ProjectPoint(const struct R3World *world, + const struct R3QueryOptions *query_options, + struct R3Vector point, + R3Real max_distance, + R3Bool solid); + +RAPIER_API RAPIER_CALL +struct R3ShapeCastHit r3CastShape(const struct R3World *world, + const struct R3QueryOptions *query_options, + struct R3Pose pose, + struct R3Vector velocity, + const R3SharedShape *shape, + struct R3ShapeCastOptions options); + +RAPIER_API RAPIER_CALL +size_t r3IntersectPoint(const struct R3World *world, + const struct R3QueryOptions *query_options, + struct R3Vector point, + struct R3ColliderHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r3IntersectShape(const struct R3World *world, + const struct R3QueryOptions *query_options, + struct R3Pose pose, + const R3SharedShape *shape, + struct R3ColliderHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r3IntersectAabbConservative(const struct R3World *world, + const struct R3QueryOptions *query_options, + struct R3Aabb aabb, + struct R3ColliderHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +struct R3RayToi r3CastRayToi(const struct R3World *world, + const struct R3QueryOptions *query_options, + struct R3Vector origin, + struct R3Vector direction, + R3Real max_toi, + R3Bool solid); + +RAPIER_API RAPIER_CALL +struct R3OptionalRayHit r3TryCastRay(const struct R3World *world, + const struct R3QueryOptions *query_options, + struct R3Vector origin, + struct R3Vector direction, + R3Real max_toi, + R3Bool solid); + +RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3DynamicRigidBodyDesc(void); + +RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3FixedRigidBodyDesc(void); + +RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3KinematicPositionBasedRigidBodyDesc(void); + +RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3KinematicVelocityBasedRigidBodyDesc(void); + +RAPIER_API RAPIER_CALL struct R3ShapeDesc r3DefaultShapeDesc(void); + +RAPIER_API RAPIER_CALL R3SharedShape *r3ShapeDesc_Build(const struct R3ShapeDesc *desc); + +RAPIER_API RAPIER_CALL struct R3ColliderDesc r3DefaultColliderDesc(void); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL struct R3ColliderDesc r3BallColliderDesc(R3Real radius); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderDesc r3CuboidColliderDesc(struct R3Vector half_extents); + +RAPIER_API RAPIER_CALL +struct R3RigidBodyHandle r3InsertRigidBody(struct R3World *world, + const struct R3RigidBodyDesc *desc); + +/** + * Insert a collider attached to a rigid body, using the world stored in its handle. + * The parent handle is copied by value. The description is borrowed through this call. + * Invalid or removed parents fail without inserting a collider. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderHandle r3InsertCollider(struct R3RigidBodyHandle parent, + const struct R3ColliderDesc *desc); + +/** + * Insert a collider without a rigid-body parent. The world owns the collider. + * The description is borrowed through this call. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderHandle r3InsertColliderWithoutParent(struct R3World *world, + const struct R3ColliderDesc *desc); + +RAPIER_API RAPIER_CALL struct R3PodLayout r3PodLayout(void); + +RAPIER_API RAPIER_CALL +struct R3KinematicCharacterController *r3NewKinematicCharacterController(void); + +RAPIER_API RAPIER_CALL +R3Status r3FreeKinematicCharacterController(struct R3KinematicCharacterController *controller); + +RAPIER_API RAPIER_CALL +R3Status r3KinematicCharacterController_SetUp(struct R3KinematicCharacterController *controller, + struct R3Vector up); + +RAPIER_API RAPIER_CALL +R3Status r3KinematicCharacterController_SetOffset(struct R3KinematicCharacterController *controller, + struct R3CharacterLength offset); + +RAPIER_API RAPIER_CALL +R3Status r3KinematicCharacterController_SetSlide(struct R3KinematicCharacterController *controller, + R3Bool enabled); + +RAPIER_API RAPIER_CALL +R3Status r3KinematicCharacterController_SetSlopes(struct R3KinematicCharacterController *controller, + R3Real max_climb_angle, + R3Real min_slide_angle); + +RAPIER_API RAPIER_CALL +R3Status r3KinematicCharacterController_SetAutostep(struct R3KinematicCharacterController *controller, + R3Bool enabled, + struct R3CharacterLength max_height, + struct R3CharacterLength min_width, + R3Bool include_dynamic_bodies); + +RAPIER_API RAPIER_CALL +R3Status r3KinematicCharacterController_SetSnapToGround(struct R3KinematicCharacterController *controller, + R3Bool enabled, + struct R3CharacterLength distance); + +/** + * Computes movement without moving any collider. Use the returned translation to set the character target. + */ +RAPIER_API RAPIER_CALL +struct R3CharacterMovement r3KinematicCharacterController_MoveShape(const struct R3World *world, + const struct R3QueryOptions *options, + struct R3KinematicCharacterController *controller, + R3Real dt, + const R3SharedShape *shape, + struct R3Pose pose, + struct R3Vector desired_translation); + +RAPIER_API RAPIER_CALL +size_t r3KinematicCharacterController_Collisions(const struct R3KinematicCharacterController *controller, + struct R3CharacterCollision *buffer, + size_t capacity); + +/** + * Applies impulses for the most recent move_shape collisions. Use the same world, shape, dt and filter. + */ +RAPIER_API RAPIER_CALL +R3Status r3KinematicCharacterController_SolveCharacterCollisionImpulses(const struct R3KinematicCharacterController *controller, + const R3SharedShape *shape, + R3Real dt, + R3Real mass, + const struct R3QueryFilter *filter); + +RAPIER_API RAPIER_CALL struct R3PidController *r3NewPidController(void); + +RAPIER_API RAPIER_CALL R3Status r3FreePidController(struct R3PidController *controller); + +RAPIER_API RAPIER_CALL +struct R3PidGains r3PidController_Gains(const struct R3PidController *controller); + +RAPIER_API RAPIER_CALL +R3Status r3PidController_SetGains(struct R3PidController *controller, + struct R3PidGains gains); + +/** + * AxesMask bits match Rapier: linear X/Y/Z are 1/2/4, angular X/Y/Z are 8/16/32. + */ +RAPIER_API RAPIER_CALL +R3Status r3PidController_SetAxes(struct R3PidController *controller, + uint32_t axes); + +/** + * Compute a velocity correction, preserving the body's state and updating PID integrals. + */ +RAPIER_API RAPIER_CALL +struct R3VelocityCorrection r3PidController_RigidBodyCorrection(struct R3PidController *controller, + R3Real dt, + struct R3RigidBodyHandle body, + struct R3Pose target_pose, + struct R3Vector target_linvel, + R3AngVector target_angvel); + +RAPIER_API RAPIER_CALL +struct R3CharacterControllerSettings r3KinematicCharacterController_Settings(const struct R3KinematicCharacterController *controller); + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL struct R3WheelTuning r3DefaultWheelTuning(void); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +struct R3DynamicRayCastVehicleController *r3NewDynamicRayCastVehicleController(struct R3RigidBodyHandle chassis); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R3Status r3FreeDynamicRayCastVehicleController(struct R3DynamicRayCastVehicleController *controller); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +size_t r3DynamicRayCastVehicleController_AddWheel(struct R3DynamicRayCastVehicleController *controller, + struct R3Vector connection, + struct R3Vector direction, + struct R3Vector axle, + R3Real rest_length, + R3Real radius, + const struct R3WheelTuning *tuning); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R3Status r3DynamicRayCastVehicleController_SetAxes(struct R3DynamicRayCastVehicleController *controller, + size_t up, + size_t forward); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R3Status r3DynamicRayCastVehicleController_SetWheelControls(struct R3DynamicRayCastVehicleController *controller, + size_t index, + R3Real steering, + R3Real engine_force, + R3Real brake); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R3Status r3DynamicRayCastVehicleController_UpdateVehicle(struct R3DynamicRayCastVehicleController *controller, + R3Real dt, + const struct R3QueryFilter *filter); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R3Real r3DynamicRayCastVehicleController_CurrentVehicleSpeed(const struct R3DynamicRayCastVehicleController *controller); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +size_t r3DynamicRayCastVehicleController_Wheels(const struct R3DynamicRayCastVehicleController *controller, + struct R3WheelState *buffer, + size_t capacity); +#endif + +RAPIER_API RAPIER_CALL +R3Status r3RigidBodyPropagateModifiedBodyPositionsToColliders(struct R3World *world); + +/** + * Copies the island manager's active body handles. + */ +RAPIER_API RAPIER_CALL +size_t r3ActiveRigidBodies(const struct R3World *world, + struct R3RigidBodyHandle *buffer, + size_t capacity); + +/** + * Wake a body by handle, including a soft-body cluster proxy. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_WakeUp(struct R3RigidBodyHandle handle, + R3Bool strong); + +/** + * Replace this thread's error handler and return the previous handler so it can + * be restored at the end of a scope. Status returns are unchanged. A handler + * that returns lets the caller recover by checking the status; a fail-fast + * handler may terminate the process. Includes R3_NOT_FOUND query misses. + */ +RAPIER_API RAPIER_CALL struct R3ErrorHandler r3SetErrorHandler(struct R3ErrorHandler handler); + +/** + * Status of the most recent fallible operation on this thread. Reading this or + * LastError does not clear it. Infallible value constructors do not change it. + * Check immediately after a fallible value-returning operation when recovering + * from errors instead of using a fail-fast error callback. + */ +RAPIER_API RAPIER_CALL R3Status r3LastStatus(void); + +/** + * Thread-local UTF-8 diagnostic, valid until the next fallible call on this thread. + */ +RAPIER_API RAPIER_CALL const char *r3LastError(void); + +RAPIER_API RAPIER_CALL R3SharedShape *r3BallSharedShape(R3Real radius); + +RAPIER_API RAPIER_CALL R3SharedShape *r3CuboidSharedShape(struct R3Vector half_extents); + +RAPIER_API RAPIER_CALL +R3SharedShape *r3RoundCuboidSharedShape(struct R3Vector half_extents, + R3Real border_radius); + +RAPIER_API RAPIER_CALL +R3SharedShape *r3CapsuleSharedShape(struct R3Vector a, + struct R3Vector b, + R3Real radius); + +RAPIER_API RAPIER_CALL +R3SharedShape *r3SegmentSharedShape(struct R3Vector a, + struct R3Vector b); + +RAPIER_API RAPIER_CALL +R3SharedShape *r3TriangleSharedShape(struct R3Vector a, + struct R3Vector b, + struct R3Vector c); + +RAPIER_API RAPIER_CALL R3SharedShape *r3HalfspaceSharedShape(struct R3Vector normal); + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R3SharedShape *r3CylinderSharedShape(R3Real half_height, + R3Real radius); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL R3SharedShape *r3ConeSharedShape(R3Real half_height, R3Real radius); +#endif + +RAPIER_API RAPIER_CALL +R3SharedShape *r3CompoundSharedShape(struct R3CompoundShapeView children); + +RAPIER_API RAPIER_CALL +R3Status r3RemoveCollider(struct R3ColliderHandle handle, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3RemoveImpulseJoint(struct R3ImpulseJointHandle handle, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +size_t r3ImpulseJointHandles(const struct R3World *world, + struct R3ImpulseJointHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +R3Status r3RemoveMultibodyJoint(struct R3MultibodyJointHandle handle, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +size_t r3MultibodyJointHandles(const struct R3World *world, + struct R3MultibodyJointHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +struct R3JointBodies r3ImpulseJoint_Bodies(struct R3ImpulseJointHandle handle); + +RAPIER_API RAPIER_CALL +struct R3InverseKinematicsOptions r3DefaultInverseKinematicsOptions(void); + +RAPIER_API RAPIER_CALL size_t r3MultibodyJoint_Ndofs(struct R3MultibodyJointHandle handle); + +/** + * Read/write displacement buffer must contain exactly ndofs entries; zero it for a fresh solve. + */ +RAPIER_API RAPIER_CALL +R3Status r3MultibodyJoint_InverseKinematics(struct R3MultibodyJointHandle handle, + const struct R3InverseKinematicsOptions *options, + struct R3Pose target, + R3IkJointCanMove can_move, + void *user_data, + R3Real *displacements, + size_t count); + +RAPIER_API RAPIER_CALL +R3Status r3MultibodyJoint_ApplyDisplacements(struct R3MultibodyJointHandle handle, + const R3Real *displacements, + size_t count); + +/** + * Frees an owned object; NULL is allowed. Never free a borrowed pointer. + */ +RAPIER_API RAPIER_CALL R3Status r3FreeSharedShape(R3SharedShape *object); + +/** + * Creates an independent owned copy. + */ +RAPIER_API RAPIER_CALL R3SharedShape *r3SharedShape_Clone(const R3SharedShape *object); + +RAPIER_API RAPIER_CALL size_t r3RigidBodyCount(const struct R3World *world); + +RAPIER_API RAPIER_CALL +size_t r3RigidBodyHandles(const struct R3World *world, + struct R3RigidBodyHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL R3Bool r3RigidBody_Contains(struct R3RigidBodyHandle handle); + +RAPIER_API RAPIER_CALL size_t r3ColliderCount(const struct R3World *world); + +RAPIER_API RAPIER_CALL +size_t r3ColliderHandles(const struct R3World *world, + struct R3ColliderHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL R3Bool r3Collider_Contains(struct R3ColliderHandle handle); + +RAPIER_API RAPIER_CALL size_t r3SoftBodyCount(const struct R3World *world); + +RAPIER_API RAPIER_CALL +size_t r3SoftBodyHandles(const struct R3World *world, + struct R3SoftBodyHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL R3Bool r3SoftBody_Contains(struct R3SoftBodyHandle handle); + +/** + * Remove a body and its joints, optionally keeping colliders as standalone objects. + * Returns whether a body was removed; a stale handle returns false without error. + */ +RAPIER_API RAPIER_CALL +R3Bool r3RemoveRigidBody(struct R3RigidBodyHandle handle, + R3Bool remove_attached_colliders); + +RAPIER_API RAPIER_CALL R3Real r3TimeStep(const struct R3World *world); + +RAPIER_API RAPIER_CALL R3Status r3SetTimeStep(struct R3World *world, R3Real value); + +RAPIER_API RAPIER_CALL R3Real r3MinCcdDt(const struct R3World *world); + +RAPIER_API RAPIER_CALL R3Status r3SetMinCcdDt(struct R3World *world, R3Real value); + +RAPIER_API RAPIER_CALL R3Real r3LengthUnit(const struct R3World *world); + +RAPIER_API RAPIER_CALL R3Status r3SetLengthUnit(struct R3World *world, R3Real value); + +RAPIER_API RAPIER_CALL R3Real r3WarmstartCoefficient(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetWarmstartCoefficient(struct R3World *world, + R3Real value); + +RAPIER_API RAPIER_CALL R3Real r3NormalizedAllowedLinearError(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetNormalizedAllowedLinearError(struct R3World *world, + R3Real value); + +RAPIER_API RAPIER_CALL R3Real r3NormalizedMaxCorrectiveVelocity(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetNormalizedMaxCorrectiveVelocity(struct R3World *world, + R3Real value); + +RAPIER_API RAPIER_CALL R3Real r3NormalizedPredictionDistance(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetNormalizedPredictionDistance(struct R3World *world, + R3Real value); + +RAPIER_API RAPIER_CALL R3Real r3NormalizedMaxLinearVelocity(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetNormalizedMaxLinearVelocity(struct R3World *world, + R3Real value); + +RAPIER_API RAPIER_CALL +R3Real r3NormalizedContactRecycleDistance(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetNormalizedContactRecycleDistance(struct R3World *world, + R3Real value); + +RAPIER_API RAPIER_CALL size_t r3NumSolverIterations(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetNumSolverIterations(struct R3World *world, + size_t value); + +RAPIER_API RAPIER_CALL size_t r3NumInternalPgsIterations(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetNumInternalPgsIterations(struct R3World *world, + size_t value); + +RAPIER_API RAPIER_CALL +size_t r3NumInternalStabilizationIterations(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetNumInternalStabilizationIterations(struct R3World *world, + size_t value); + +RAPIER_API RAPIER_CALL size_t r3MaxCcdSubsteps(const struct R3World *world); + +RAPIER_API RAPIER_CALL R3Status r3SetMaxCcdSubsteps(struct R3World *world, size_t value); + +RAPIER_API RAPIER_CALL R3Bool r3ContactClustering(const struct R3World *world); + +RAPIER_API RAPIER_CALL R3Status r3SetContactClustering(struct R3World *world, R3Bool value); + +RAPIER_API RAPIER_CALL R3Bool r3ContactRecycling(const struct R3World *world); + +RAPIER_API RAPIER_CALL R3Status r3SetContactRecycling(struct R3World *world, R3Bool value); + +RAPIER_API RAPIER_CALL R3Bool r3FrictionInBiasPass(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetFrictionInBiasPass(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL R3Bool r3WarmstartJoints(const struct R3World *world); + +RAPIER_API RAPIER_CALL R3Status r3SetWarmstartJoints(struct R3World *world, R3Bool value); + +RAPIER_API RAPIER_CALL +struct R3SpringCoefficients r3ContactSoftness(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetContactSoftness(struct R3World *world, + struct R3SpringCoefficients value); + +RAPIER_API RAPIER_CALL +struct R3SpringCoefficients r3StaticContactSoftness(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SetStaticContactSoftness(struct R3World *world, + struct R3SpringCoefficients value); + +/** + * Applies Rapier's persistent one-way platform logic to the borrowed manifold. + */ +RAPIER_API RAPIER_CALL +R3Status r3ContactModificationContext_UpdateAsOnewayPlatform(struct R3ContactModificationContext *context, + struct R3Vector allowed_local_n1, + R3Real allowed_angle); + +/** + * Sets the tangent velocity of every rigid solver contact in this manifold. + */ +RAPIER_API RAPIER_CALL +R3Status r3ContactModificationContext_SetTangentVelocity(struct R3ContactModificationContext *context, + struct R3Vector velocity); + +RAPIER_API RAPIER_CALL struct R3EventCollector *r3NewEventCollector(void); + +RAPIER_API RAPIER_CALL R3Status r3FreeEventCollector(struct R3EventCollector *events); + +RAPIER_API RAPIER_CALL R3Status r3EventCollector_Clear(struct R3EventCollector *events); + +RAPIER_API RAPIER_CALL +size_t r3EventCollector_CollisionEvents(const struct R3EventCollector *events, + struct R3CollisionEvent *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r3EventCollector_ContactForceEvents(const struct R3EventCollector *events, + struct R3ContactForceEvent *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r3EventCollector_TearEventCount(const struct R3EventCollector *events); + +RAPIER_API RAPIER_CALL +struct R3SoftBodyTearEvent *r3EventCollector_TearEvent(const struct R3EventCollector *events, + size_t index); + +RAPIER_API RAPIER_CALL struct R3Vector r3Gravity(const struct R3World *world); + +RAPIER_API RAPIER_CALL R3Status r3SetGravity(struct R3World *world, struct R3Vector value); + +/** + * Hooks and events may be NULL. This call invalidates all borrowed set-element pointers. + * Advance simulation by one timestep. Hooks and events may be NULL. + */ +RAPIER_API RAPIER_CALL +R3Status r3Step(struct R3World *world, + const struct R3PhysicsHooks *hooks, + const struct R3EventCollector *events); + +/** + * Refresh collision detection without advancing simulation. Hooks and events may be NULL. + */ +RAPIER_API RAPIER_CALL +R3Status r3DetectCollisions(struct R3World *world, + const struct R3PhysicsHooks *hooks, + const struct R3EventCollector *events); + +RAPIER_API RAPIER_CALL struct R3ByteView r3Bytes_Data(const struct R3Bytes *bytes); + +RAPIER_API RAPIER_CALL R3Status r3FreeBytes(struct R3Bytes *bytes); + +RAPIER_API RAPIER_CALL struct R3Bytes *r3SerializeWorld(const struct R3World *world); + +/** + * Restore ONLY trusted snapshots produced by the identical Rapier build. Snapshots are not a stable file format. + */ +RAPIER_API RAPIER_CALL +struct R3World *r3DeserializeWorld(const uint8_t *data, + size_t count); + +/** + * Color is HSLA (hue in degrees), matching Rapier DebugColor. mode uses DebugRenderMode bits. + */ +RAPIER_API RAPIER_CALL +size_t r3DebugRender(const struct R3World *world, + uint32_t mode, + struct R3DebugLine *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +R3Status r3SoftBodiesSetResweepStrain(struct R3World *world, + R3Real value); + +RAPIER_API RAPIER_CALL R3Real r3SoftBodiesResweepStrain(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SoftBodiesSetContactStiffening(struct R3World *world, + R3Real value); + +RAPIER_API RAPIER_CALL R3Real r3SoftBodiesContactStiffening(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3SoftBodiesSetMaxExtraSubsteps(struct R3World *world, + size_t value); + +RAPIER_API RAPIER_CALL size_t r3SoftBodiesMaxExtraSubsteps(const struct R3World *world); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetAuthoredVelocityMargin(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetEdgeSpeculation(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetInvertedCellDetection(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetSelfCrossingDetection(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetDetectionMotionGating(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetCrossBodyDetection(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetSelfStandDown(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetCrossBodyExpelGate(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetEdgeStandDown(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetCrossingRepulsion(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetCrossingRepulsionGuide(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetCrossingRepulsionSelfGuide(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetRecoveryPace(struct R3World *world, + R3Real value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetOverlapConstraints(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetOverlapRigid(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetOverlapSkipSelfTangled(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetOverlapEdgeStandDown(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetOverlapConstraintPace(struct R3World *world, + R3Real value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetOverlapSkinVolume(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetOverlapKeptDepth(struct R3World *world, + R3Real value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetOverlapSelfRegions(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetOverlapNormalPush(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetOverlapMultiVolume(struct R3World *world, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3RecoverySetOverlapProgressMargin(struct R3World *world, + R3Real value); + +#if defined(RAPIER_FEM) +RAPIER_API RAPIER_CALL +R3Status r3FemSetLinearTolerance(struct R3World *world, + R3Real value); +#endif + +#if defined(RAPIER_FEM) +RAPIER_API RAPIER_CALL +R3Status r3FemSetMaxLinearIterations(struct R3World *world, + size_t value); +#endif + +#if defined(RAPIER_FEM) +RAPIER_API RAPIER_CALL R3Status r3FemSetMaxDenseDofs(struct R3World *world, size_t value); +#endif + +/** + * Configures a dedicated pool for this world's parallel work. Zero selects Rayon's default. + * Takes effect on the next step. Reconfiguration must not race with a step or callback. + * Returns R3_UNSUPPORTED in builds without the parallel feature; keeps the previous + * pool when constructing the new one fails. The pool is not included in snapshots. + */ +RAPIER_API RAPIER_CALL R3Status r3SetNumThreads(struct R3World *world, size_t num_threads); + +/** + * Removes the world's dedicated pool. A parallel build then uses the calling + * context's Rayon pool (normally the global pool), not a single worker. + * Returns R3_UNSUPPORTED in a build without the parallel feature. + */ +RAPIER_API RAPIER_CALL R3Status r3ClearThreadPool(struct R3World *world); + +/** + * Size of the world's dedicated pool, or zero if a parallel build has no dedicated + * pool configured. Returns one for a build without the parallel feature. + */ +RAPIER_API RAPIER_CALL size_t r3NumThreads(const struct R3World *world); + +/** + * Enable or disable the native pipeline profiling counters. Enabling returns + * R3_UNSUPPORTED if the library was built without the profiler feature. + */ +RAPIER_API RAPIER_CALL R3Status r3SetCountersEnabled(struct R3World *world, R3Bool enabled); + +/** + * Native engine time of the most recent step, in milliseconds, as in the Rust testbed. + * Enable counters before stepping. Excludes C callbacks outside the step, rendering, + * and dispatch into a dedicated thread pool; remains unchanged while paused. + */ +RAPIER_API RAPIER_CALL double r3StepTimeMs(const struct R3World *world); + +/** + * Read ONLY trusted legacy rigid-world snapshots from debug_deserialize3.rs, + * produced by the identical Rapier build. This is not a stable interchange format. + */ +RAPIER_API RAPIER_CALL +struct R3World *r3DeserializeRigidState(const uint8_t *data, + size_t count); + +RAPIER_API RAPIER_CALL struct R3QueryFilter r3DefaultQueryFilter(void); + +RAPIER_API RAPIER_CALL struct R3ShapeCastOptions r3DefaultShapeCastOptions(void); + +RAPIER_API RAPIER_CALL R3Status r3RemoveSoftBody(struct R3SoftBodyHandle handle); + +RAPIER_API RAPIER_CALL R3Status r3SoftBody_WakeUp(struct R3SoftBodyHandle handle); + +RAPIER_API RAPIER_CALL R3Status r3FreeSoftBodyTearEvent(struct R3SoftBodyTearEvent *event); + +RAPIER_API RAPIER_CALL +struct R3SoftBodyHandle r3SoftBodyTearEvent_SoftBody(const struct R3SoftBodyTearEvent *event); + +RAPIER_API RAPIER_CALL +size_t r3SoftBodyTearEvent_Bodies(const struct R3SoftBodyTearEvent *event, + struct R3SoftBodyHandle *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +struct R3ParticleDestination r3SoftBodyTearEvent_ParticleDestination(const struct R3SoftBodyTearEvent *event, + uint32_t particle); + +/** + * Flat indices; element arity follows the corresponding Rust event field. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBodyTearEvent_TornEdges(const struct R3SoftBodyTearEvent *event, + uint32_t *buffer, + size_t capacity); + +/** + * Flat indices; element arity follows the corresponding Rust event field. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBodyTearEvent_TornCells(const struct R3SoftBodyTearEvent *event, + uint32_t *buffer, + size_t capacity); + +/** + * Flat indices; element arity follows the corresponding Rust event field. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBodyTearEvent_RemovedEdges(const struct R3SoftBodyTearEvent *event, + uint32_t *buffer, + size_t capacity); + +/** + * Flat indices; element arity follows the corresponding Rust event field. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBodyTearEvent_SplitParticles(const struct R3SoftBodyTearEvent *event, + uint32_t *buffer, + size_t capacity); + +/** + * Flat indices; element arity follows the corresponding Rust event field. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBodyTearEvent_InsertedParticles(const struct R3SoftBodyTearEvent *event, + uint32_t *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r3SoftBodyTearEvent_PieceParticles(const struct R3SoftBodyTearEvent *event, + size_t piece_index, + uint32_t *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r3SoftBodyTearEvent_Clusters(const struct R3SoftBodyTearEvent *event, + struct R3SoftClusterSplit *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r3SoftBodyTearEvent_MovedJoints(const struct R3SoftBodyTearEvent *event, + struct R3SoftJointMove *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +struct R3SoftBodyTearEvent *r3SoftBody_Tear(struct R3SoftBodyHandle handle, + const uint32_t *edges, + size_t edge_count, + const uint32_t *cells, + size_t cell_count); + +RAPIER_API RAPIER_CALL +uint32_t r3SoftBody_AddCluster(struct R3SoftBodyHandle handle, + const uint32_t *particles, + size_t count); + +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_RemoveCluster(struct R3SoftBodyHandle handle, + uint32_t cluster); + +/** + * Optional particle destination after a tear. Missing destinations are normal and set + * found to false; body/index are only written when a destination exists. + */ +RAPIER_API RAPIER_CALL +struct R3OptionalParticleDestination r3SoftBodyTearEvent_TryParticleDestination(const struct R3SoftBodyTearEvent *event, + uint32_t particle); + +/** + * Cut using DIM points (a segment in 2D, triangle in 3D). A no-op returns a null event. + * The optional owned event must be freed with FreeSoftBodyTearEvent. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyTearEvent *r3CutSoftBody(struct R3SoftBodyHandle handle, + const struct R3Vector *blade); + +RAPIER_API RAPIER_CALL +struct R3VolumeMeshParameters r3NewVolumeMeshParameters(R3Real cell_size); + +RAPIER_API RAPIER_CALL struct R3BuildInfo r3BuildInfo(void); + +/** + * Release version of the loaded C bindings, e.g. "0.35.3+c.2". + * The suffix identifies the C bindings revision for the Rust crate version. + * The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. + * This release identifier is independent of the ABI compatibility version. + */ +RAPIER_API RAPIER_CALL const char *r3Version(void); + +/** + * Cargo profile of the loaded physics library: "debug" or "release". + * Custom profiles report the corresponding inherited Cargo profile category. + * The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. + * This is independent of the consumer's build mode and of per-package optimization overrides. + */ +RAPIER_API RAPIER_CALL const char *r3BuildProfile(void); + +RAPIER_API RAPIER_CALL struct R3BuildFeatures r3BuildFeatures(void); + +RAPIER_API RAPIER_CALL +R3SharedShape *r3HeightfieldSharedShape(struct R3RealView heights, + size_t rows, + size_t columns, + struct R3Vector scale); + +RAPIER_API RAPIER_CALL +struct R3Aabb r3SharedShape_ComputeAabb(const R3SharedShape *shape, + struct R3Pose pose); + +RAPIER_API RAPIER_CALL +struct R3MassProperties r3SharedShape_MassProperties(const R3SharedShape *shape, + R3Real density); + +RAPIER_API RAPIER_CALL +R3Bool r3SharedShape_ContainsPoint(const R3SharedShape *shape, + struct R3Pose pose, + struct R3Vector point); + +RAPIER_API RAPIER_CALL +size_t r3ContactPairs(const struct R3World *world, + struct R3ContactPair *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +struct R3ContactPair r3ContactPair(struct R3ColliderHandle collider1, + struct R3ColliderHandle collider2); + +RAPIER_API RAPIER_CALL +size_t r3IntersectionPairs(const struct R3World *world, + struct R3IntersectionPair *buffer, + size_t capacity); + +/** + * Contact points in collider-local space; normal in world space. Geometric manifolds may be recycled. + * For clustered solver impulses use contact pair totals. Soft pairs have no rigid manifolds. + */ +RAPIER_API RAPIER_CALL +size_t r3ContactPoints(struct R3ColliderHandle collider1, + struct R3ColliderHandle collider2, + struct R3ContactPoint *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +size_t r3MultibodyJoint_GeneralizedVelocity(struct R3MultibodyJointHandle handle, + R3Real *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL +R3Status r3MultibodyJoint_SetGeneralizedVelocity(struct R3MultibodyJointHandle handle, + const R3Real *values, + size_t count); + +/** + * Check this before passing any dimension/precision-dependent structs across the ABI. + */ +RAPIER_API RAPIER_CALL +R3Status r3CheckAbi(uint32_t version, + uint32_t dimension, + size_t real_size, + size_t vector_size, + size_t pose_size); + +RAPIER_API RAPIER_CALL +struct R3ShapeMesh *r3SharedShape_Tessellate(const R3SharedShape *shape, + uint32_t subdivisions); + +/** + * Flat groups of three vertices. Standard output-buffer convention. + */ +RAPIER_API RAPIER_CALL +size_t r3ShapeMesh_Triangles(const struct R3ShapeMesh *mesh, + struct R3Vector *buffer, + size_t capacity); + +/** + * Flat groups of two vertices. Standard output-buffer convention. + */ +RAPIER_API RAPIER_CALL +size_t r3ShapeMesh_Lines(const struct R3ShapeMesh *mesh, + struct R3Vector *buffer, + size_t capacity); + +RAPIER_API RAPIER_CALL R3Status r3FreeShapeMesh(struct R3ShapeMesh *mesh); + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +R3SharedShape *r3RoundCylinderSharedShape(R3Real half_height, + R3Real radius, + R3Real border_radius); +#endif + +#if defined(RAPIER_DIM3) +/** + * Tessellate a ball or capsule with independent longitude/latitude subdivision counts. + * Cuboids, cones, cylinders, convex polyhedra, trimeshes, and heightfields are also supported. + */ +RAPIER_API RAPIER_CALL +struct R3TriMeshData *r3SharedShape_ToTrimesh(const R3SharedShape *shape, + uint32_t ntheta, + uint32_t nphi); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL +size_t r3TriMeshData_Vertices(const struct R3TriMeshData *mesh, + struct R3Vector *buffer, + size_t capacity); +#endif + +#if defined(RAPIER_DIM3) +/** + * Flat triangle indices; count and capacity are numbers of u32 entries. + */ +RAPIER_API RAPIER_CALL +size_t r3TriMeshData_Indices(const struct R3TriMeshData *mesh, + uint32_t *buffer, + size_t capacity); +#endif + +#if defined(RAPIER_DIM3) +RAPIER_API RAPIER_CALL R3Status r3FreeTriMeshData(struct R3TriMeshData *mesh); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL struct R3UrdfLoaderOptions r3DefaultUrdfLoaderOptions(void); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL R3Status r3FreeUrdfRobot(struct R3UrdfRobot *object); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Load from a UTF-8 path. Validates options before reading the file. + * Options and their blueprint resources are borrowed through this call; the robot is owned. + */ +RAPIER_API RAPIER_CALL +struct R3UrdfRobot *r3UrdfRobotFromFile(const char *path, + const struct R3UrdfLoaderOptions *options); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +R3Status r3UrdfRobot_AppendTransform(struct R3UrdfRobot *robot, + struct R3Pose transform); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL R3Status r3FreeUrdfRobotHandles(struct R3UrdfRobotHandles *handles); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + */ +RAPIER_API RAPIER_CALL +struct R3UrdfRobotHandles *r3UrdfRobot_InsertUsingImpulseJoints(struct R3World *world, + const struct R3UrdfRobot *robot); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + */ +RAPIER_API RAPIER_CALL +struct R3UrdfRobotHandles *r3UrdfRobot_InsertUsingMultibodyJoints(struct R3World *world, + const struct R3UrdfRobot *robot, + uint8_t options); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Body handles in source order; absent MJCF bodies have invalid handles. + */ +RAPIER_API RAPIER_CALL +size_t r3UrdfRobotHandles_Bodies(const struct R3UrdfRobotHandles *handles, + struct R3RigidBodyHandle *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL struct R3MjcfLoaderOptions r3DefaultMjcfLoaderOptions(void); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL R3Status r3FreeMjcfRobot(struct R3MjcfRobot *object); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Load from a UTF-8 path. Validates options before reading the file. + * Options and their blueprint resources are borrowed through this call; the robot is owned. + */ +RAPIER_API RAPIER_CALL +struct R3MjcfRobot *r3MjcfRobotFromFile(const char *path, + const struct R3MjcfLoaderOptions *options); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +R3Status r3MjcfRobot_AppendTransform(struct R3MjcfRobot *robot, + struct R3Pose transform); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL R3Status r3FreeMjcfRobotHandles(struct R3MjcfRobotHandles *handles); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + */ +RAPIER_API RAPIER_CALL +struct R3MjcfRobotHandles *r3MjcfRobot_InsertUsingImpulseJoints(struct R3World *world, + const struct R3MjcfRobot *robot); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + */ +RAPIER_API RAPIER_CALL +struct R3MjcfRobotHandles *r3MjcfRobot_InsertUsingMultibodyJoints(struct R3World *world, + const struct R3MjcfRobot *robot, + uint8_t options); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Body handles in source order; absent MJCF bodies have invalid handles. + */ +RAPIER_API RAPIER_CALL +size_t r3MjcfRobotHandles_Bodies(const struct R3MjcfRobotHandles *handles, + struct R3RigidBodyHandle *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Resolved model gravity before the caller chooses a world convention. + */ +RAPIER_API RAPIER_CALL struct R3Vector r3MjcfRobot_Gravity(const struct R3MjcfRobot *robot); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL size_t r3MjcfRobot_BodyCount(const struct R3MjcfRobot *robot); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +size_t r3MjcfRobot_BodyColliderCount(const struct R3MjcfRobot *robot, + size_t body); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Borrowed collider; invalidated by freeing or mutating the robot's storage. + */ +RAPIER_API RAPIER_CALL +R3Status r3MjcfRobot_SetBodyColliderCollisionGroups(struct R3MjcfRobot *robot, + size_t body, + size_t collider, + struct R3InteractionGroups groups); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL size_t r3MjcfRobot_KeyframeCount(const struct R3MjcfRobot *robot); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copies a NUL-terminated UTF-8 name. Count includes NUL; unnamed keys return an empty string. + */ +RAPIER_API RAPIER_CALL +size_t r3MjcfRobot_KeyframeName(const struct R3MjcfRobot *robot, + size_t key, + char *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +R3Status r3MjcfRobot_AppendKeyframe(struct R3MjcfRobot *robot, + const struct R3MjcfRobot *source, + size_t key); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +size_t r3MjcfRobot_KeyframeControls(const struct R3MjcfRobot *robot, + size_t key, + R3Real *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +size_t r3MjcfRobotHandles_ActuatorCount(const struct R3MjcfRobotHandles *handles); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +R3Status r3MjcfRobotHandles_ApplyKeyframe(const struct R3MjcfRobotHandles *handles, + const struct R3MjcfRobot *robot, + size_t key); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +R3Status r3MjcfRobotHandles_ApplyControlsScaled(const struct R3MjcfRobotHandles *handles, + const R3Real *controls, + size_t count, + R3Real gain); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +size_t r3MjcfRobot_BodyVisualCount(const struct R3MjcfRobot *robot, + size_t body); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +const R3MjcfVisualMesh *r3MjcfRobot_BodyVisual(const struct R3MjcfRobot *robot, + size_t body, + size_t visual); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +RAPIER_API RAPIER_CALL +struct R3MjcfVisualMeshInfo r3MjcfVisualMesh_Info(const R3MjcfVisualMesh *visual); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Returns an owned shared shape reference. + * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + */ +RAPIER_API RAPIER_CALL +R3SharedShape *r3MjcfVisualMesh_CloneShape(const R3MjcfVisualMesh *visual); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copies flattened pairs of per-vertex UV coordinates. + */ +RAPIER_API RAPIER_CALL +size_t r3MjcfVisualMesh_Uvs(const R3MjcfVisualMesh *visual, + float *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copies flattened triples of per-vertex normals. + */ +RAPIER_API RAPIER_CALL +size_t r3MjcfVisualMesh_Normals(const R3MjcfVisualMesh *visual, + float *buffer, + size_t capacity); +#endif + +#if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copies a NUL-terminated texture path, or an empty string for untextured meshes. + */ +RAPIER_API RAPIER_CALL +size_t r3MjcfVisualMesh_Texture(const R3MjcfVisualMesh *visual, + char *buffer, + size_t capacity); +#endif + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL struct R3Pose r3RigidBody_Position(struct R3RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3RigidBody_Translation(struct R3RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL struct R3Vector r3RigidBody_Linvel(struct R3RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R3AngVector r3RigidBody_Angvel(struct R3RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsSleeping(struct R3RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsEnabled(struct R3RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +struct R3UserData r3RigidBody_UserData(struct R3RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetPosition(struct R3RigidBodyHandle handle, + struct R3Pose value, + R3Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetTranslation(struct R3RigidBodyHandle handle, + struct R3Vector value, + R3Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetLinvel(struct R3RigidBodyHandle handle, + struct R3Vector value, + R3Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetAngvel(struct R3RigidBodyHandle handle, + R3AngVector value, + R3Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetNextKinematicPosition(struct R3RigidBodyHandle handle, + struct R3Pose value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetNextKinematicTranslation(struct R3RigidBodyHandle handle, + struct R3Vector value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetGravityScale(struct R3RigidBodyHandle handle, + R3Real value, + R3Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetLinearDamping(struct R3RigidBodyHandle handle, + R3Real value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetAngularDamping(struct R3RigidBodyHandle handle, + R3Real value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetEnabled(struct R3RigidBodyHandle handle, + R3Bool value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetUserData(struct R3RigidBodyHandle handle, + struct R3UserData value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_ApplyImpulse(struct R3RigidBodyHandle handle, + struct R3Vector value, + R3Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_ApplyImpulseAtPoint(struct R3RigidBodyHandle handle, + struct R3Vector value, + struct R3Vector point, + R3Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_AddForce(struct R3RigidBodyHandle handle, + struct R3Vector value, + R3Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_ResetForces(struct R3RigidBodyHandle handle, + R3Bool wake_up); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R3Status r3RigidBody_Sleep(struct R3RigidBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL struct R3Pose r3Collider_Position(struct R3ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL struct R3Vector r3Collider_Translation(struct R3ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R3Real r3Collider_Friction(struct R3ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R3Real r3Collider_Restitution(struct R3ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL R3Bool r3Collider_IsSensor(struct R3ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +struct R3RigidBodyHandle r3Collider_Parent(struct R3ColliderHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetPosition(struct R3ColliderHandle handle, + struct R3Pose value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetTranslation(struct R3ColliderHandle handle, + struct R3Vector value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetFriction(struct R3ColliderHandle handle, + R3Real value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetRestitution(struct R3ColliderHandle handle, + R3Real value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetSensor(struct R3ColliderHandle handle, + R3Bool value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetCollisionGroups(struct R3ColliderHandle handle, + struct R3InteractionGroups value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetUserData(struct R3ColliderHandle handle, + struct R3UserData value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3SoftBody_ParticlePosition(struct R3SoftBodyHandle handle, + size_t index); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_ParticlePositions(struct R3SoftBodyHandle handle, + struct R3Vector *buffer, + size_t capacity); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyMaterial r3SoftBody_Material(struct R3SoftBodyHandle handle); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetParticlePosition(struct R3SoftBodyHandle handle, + size_t index, + struct R3Vector value); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetMaterial(struct R3SoftBodyHandle handle, + const struct R3SoftBodyMaterial *data); + +/** + * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_AddParticleForce(struct R3SoftBodyHandle handle, + size_t index, + struct R3Vector value, + R3Bool wake_up); + +/** + * Copies states in the same order as handles, without allocating temporary storage. + * All handles are validated before writing. On INVALID_HANDLE outputs are unchanged. + * NULL/0 is a size query. BUFFER_TOO_SMALL updates count but leaves states untouched. + */ +RAPIER_API RAPIER_CALL +size_t r3RigidBodyReadStates(const struct R3World *world, + const struct R3RigidBodyHandle *handles, + size_t handle_count, + struct R3RigidBodyState *states, + size_t capacity); + +/** + * Copies joint configuration without returning a borrowed joint pointer. + */ +RAPIER_API RAPIER_CALL +struct R3JointDesc r3ImpulseJoint_Desc(struct R3ImpulseJointHandle handle); + +/** + * Replaces configuration after validation, resetting cached limit/motor impulses. + */ +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetDesc(struct R3ImpulseJointHandle handle, + const struct R3JointDesc *desc, + R3Bool wake_up); + +/** + * Replace the shape geometry with a borrowed tri mesh. Counts are elements. + * Copies no arrays. Invalid view metadata leaves the description unchanged. + * Geometry and flags are validated when the description is built or inserted. + */ +RAPIER_API RAPIER_CALL +R3Status r3ShapeDesc_SetTrimesh(struct R3ShapeDesc *desc, + struct R3VectorView vertices, + struct R3TriangleView indices, + uint32_t flags); + +/** + * Replace the shape geometry with a borrowed polyline. Counts are elements. + * Copies no arrays. Invalid view metadata leaves the description unchanged. + * Geometry and flags are validated when the description is built or inserted. + */ +RAPIER_API RAPIER_CALL +R3Status r3ShapeDesc_SetPolyline(struct R3ShapeDesc *desc, + struct R3VectorView vertices, + struct R3EdgeView indices, + uint32_t flags); + +/** + * Replace the shape geometry with a borrowed convex hull point cloud. + */ +RAPIER_API RAPIER_CALL +R3Status r3ShapeDesc_SetConvexHull(struct R3ShapeDesc *desc, + struct R3VectorView vertices); + +/** + * Select an explicit particle recipe and borrow its positions. Other fields are preserved. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetParticles(struct R3SoftBodyDesc *desc, + struct R3VectorView positions); + +/** + * Select a surface recipe and borrow its vertices and elements. Other fields are preserved. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetSurfaceMesh(struct R3SoftBodyDesc *desc, + struct R3VectorView vertices, + R3SurfaceElementView elements); + +/** + * Borrow skin geometry. Other fields, including skinCollision, are preserved. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetSkin(struct R3SoftBodyDesc *desc, + struct R3VectorView vertices, + R3SurfaceElementView elements); + +/** + * Borrow masses; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetMasses(struct R3SoftBodyDesc *desc, + struct R3RealView view); + +/** + * Borrow pinned particles; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetPinnedParticles(struct R3SoftBodyDesc *desc, + struct R3IndexView view); + +/** + * Borrow edges; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetEdges(struct R3SoftBodyDesc *desc, + struct R3EdgeView view); + +/** + * Borrow bend edges; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetBendEdges(struct R3SoftBodyDesc *desc, + struct R3EdgeView view); + +/** + * Borrow cells; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetCells(struct R3SoftBodyDesc *desc, + R3CellView view); + +/** + * Borrow surface; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetSurface(struct R3SoftBodyDesc *desc, + R3SurfaceElementView view); + +/** + * Borrow tension only edges; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetTensionOnlyEdges(struct R3SoftBodyDesc *desc, + struct R3IndexView view); + +#if defined(RAPIER_DIM3) +/** + * Borrow dihedrals; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetDihedrals(struct R3SoftBodyDesc *desc, + struct R3DihedralView view); +#endif + +#if defined(RAPIER_DIM3) +/** + * Borrow wire; preserve all other fields. No allocation or element reads. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Invalid view metadata leaves the description unchanged. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBodyDesc_SetWire(struct R3SoftBodyDesc *desc, + struct R3EdgeView view); +#endif + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderDesc r3RoundCuboidColliderDesc(struct R3Vector half_extents, + R3Real border_radius); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderDesc r3CapsuleColliderDesc(struct R3Vector a, + struct R3Vector b, + R3Real radius); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderDesc r3SegmentColliderDesc(struct R3Vector a, + struct R3Vector b); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderDesc r3TriangleColliderDesc(struct R3Vector a, + struct R3Vector b, + struct R3Vector c); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL struct R3ColliderDesc r3HalfspaceColliderDesc(struct R3Vector normal); + +#if defined(RAPIER_DIM3) +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderDesc r3CylinderColliderDesc(R3Real half_height, + R3Real radius); +#endif + +#if defined(RAPIER_DIM3) +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderDesc r3ConeColliderDesc(R3Real half_height, + R3Real radius); +#endif + +#if defined(RAPIER_DIM3) +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderDesc r3RoundCylinderColliderDesc(R3Real half_height, + R3Real radius, + R3Real border_radius); +#endif + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderDesc r3CapsuleXColliderDesc(R3Real half_height, + R3Real radius); + +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderDesc r3CapsuleYColliderDesc(R3Real half_height, + R3Real radius); + +#if defined(RAPIER_DIM3) +/** + * Returns a description without allocating or validating. Build/insert validates its fields. + */ +RAPIER_API RAPIER_CALL +struct R3ColliderDesc r3CapsuleZColliderDesc(R3Real half_height, + R3Real radius); +#endif + +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyDesc r3RopeSoftBodyDesc(struct R3Vector a, + struct R3Vector b, + size_t particles); + +#if defined(RAPIER_DIM2) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyDesc r3GridSoftBodyDesc(struct R3Vector center, + struct R3Vector half_extents, + size_t nx, + size_t ny); +#endif + +#if defined(RAPIER_DIM3) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyDesc r3CuboidSoftBodyDesc(struct R3Vector center, + struct R3Vector half_extents, + size_t nx, + size_t ny, + size_t nz); +#endif + +#if defined(RAPIER_DIM3) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyDesc r3ClothSoftBodyDesc(struct R3Vector origin, + struct R3Vector du, + struct R3Vector dv, + size_t nu, + size_t nv); +#endif + +#if defined(RAPIER_DIM2) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyDesc r3DiskSoftBodyDesc(struct R3Vector center, + R3Real radius, + size_t particles); +#endif + +#if defined(RAPIER_DIM3) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyDesc r3SphereSoftBodyDesc(struct R3Vector center, + R3Real radius, + uint32_t subdivisions); +#endif + +#if defined(RAPIER_DIM3) +/** + * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyDesc r3ClothTubeSoftBodyDesc(struct R3Vector origin, + struct R3Vector axis, + R3Real radius_start, + R3Real radius_end, + size_t num_around, + size_t num_along); +#endif + +/** + * Initializes a borrowed meshing recipe. Mesh generation happens on preview/insertion. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyDesc r3VolumetricSoftBodyDesc(struct R3VectorView vertices, + R3SurfaceElementView surface, + struct R3VolumeMeshParameters parameters); + +/** + * Returns a material with the same softness for each constraint family. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyMaterial r3UniformSoftBodyMaterial(struct R3SpringCoefficients value); + +/** + * Copies generated particle positions into caller-owned storage; no persistent builder. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBodyDesc_ParticlePositions(const struct R3SoftBodyDesc *desc, + struct R3Vector *buffer, + size_t capacity); + +/** + * Copies generated cell indices into caller-owned storage. Counts scalar indices. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBodyDesc_CellIndices(const struct R3SoftBodyDesc *desc, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL size_t r3Collider_ShapeIdentity(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL size_t r3SoftBody_NumParticles(struct R3SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL uint32_t r3SoftBody_TopologyVersion(struct R3SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3SoftBody_Mass(struct R3SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3SoftBody_Volume(struct R3SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3SoftBody_RestVolume(struct R3SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3SoftBody_VolumeFactor(struct R3SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3SoftBody_CenterOfMass(struct R3SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3RigidBodyHandle r3SoftBody_RootBody(struct R3SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Bool r3SoftBody_IsEnabled(struct R3SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Bool r3SoftBody_IsSleeping(struct R3SoftBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_ParticleVelocities(struct R3SoftBodyHandle handle, + struct R3Vector *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_Edges(struct R3SoftBodyHandle handle, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_Cells(struct R3SoftBodyHandle handle, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_Boundary(struct R3SoftBodyHandle handle, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_Pieces(struct R3SoftBodyHandle handle, + struct R3SoftBodyHandle *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetParticleVelocity(struct R3SoftBodyHandle handle, + size_t index, + struct R3Vector value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetParticleKinematicTarget(struct R3SoftBodyHandle handle, + size_t index, + struct R3Vector value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetParticlePinned(struct R3SoftBodyHandle handle, + size_t index, + R3Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_ApplyParticleImpulse(struct R3SoftBodyHandle handle, + size_t index, + struct R3Vector value, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_AddForce(struct R3SoftBodyHandle handle, + struct R3Vector value, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_ApplyImpulse(struct R3SoftBodyHandle handle, + struct R3Vector value, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_ResetForces(struct R3SoftBodyHandle handle, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetEnabled(struct R3SoftBodyHandle handle, + R3Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetVolumeFactor(struct R3SoftBodyHandle handle, + R3Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_AttachParticle(struct R3SoftBodyHandle handle, + size_t index, + struct R3RigidBodyHandle rigid_body); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_DetachParticle(struct R3SoftBodyHandle handle, + size_t index); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_Clusters(struct R3SoftBodyHandle handle, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3RigidBodyHandle r3SoftBody_ClusterProxy(struct R3SoftBodyHandle handle, + uint32_t cluster); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_ClusterParticles(struct R3SoftBodyHandle handle, + uint32_t cluster, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetClusterPinned(struct R3SoftBodyHandle handle, + uint32_t cluster, + R3Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetClusterKinematicTarget(struct R3SoftBodyHandle handle, + uint32_t cluster, + struct R3Pose value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetClusterShapeMatchingEnabled(struct R3SoftBodyHandle handle, + uint32_t cluster, + R3Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetClusterStiffnessScale(struct R3SoftBodyHandle handle, + uint32_t cluster, + R3Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetClusterTearResistance(struct R3SoftBodyHandle handle, + uint32_t cluster, + R3Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_Meshes(struct R3SoftBodyHandle handle, + struct R3SoftMeshInfo *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_MeshVerticesById(struct R3SoftBodyHandle handle, + struct R3SoftMeshId id, + struct R3Vector *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_MeshIndicesById(struct R3SoftBodyHandle handle, + struct R3SoftMeshId id, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_MeshColliders(struct R3SoftBodyHandle handle, + struct R3ColliderHandle *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_MeshVertices(struct R3SoftBodyHandle handle, + struct R3ColliderHandle collider, + struct R3Vector *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_MeshIndices(struct R3SoftBodyHandle handle, + struct R3ColliderHandle collider, + uint32_t *buffer, + size_t capacity); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3SoftBody_MeshArity(struct R3SoftBodyHandle handle, + struct R3ColliderHandle collider); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +uint32_t r3SoftBody_MeshTopologyVersion(struct R3SoftBodyHandle handle, + struct R3ColliderHandle collider); + +#if defined(RAPIER_FEM) +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetSolver(struct R3SoftBodyHandle handle, + uint32_t solver); +#endif + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetClusterShapeMatchingTarget(struct R3SoftBodyHandle handle, + uint32_t cluster, + const struct R3Pose *target); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3SoftBody_SetEdgeTearResistance(struct R3SoftBodyHandle handle, + size_t index, + R3Real resistance); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Bool r3SoftBody_MeshIsClosed(struct R3SoftBodyHandle handle, + struct R3ColliderHandle collider); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetAdditionalMassProperties(struct R3RigidBodyHandle handle, + struct R3MassProperties properties, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_RecomputeMassPropertiesFromColliders(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetMassProperties(struct R3ColliderHandle handle, + struct R3MassProperties properties); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3MassProperties r3Collider_MassProperties(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetLockedAxes(struct R3RigidBodyHandle handle, + uint8_t axes, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL uint8_t r3RigidBody_LockedAxes(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Bool r3Collider_IsVoxels(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3VoxelQuery r3Collider_VoxelAtFlatId(struct R3ColliderHandle handle, + uint32_t id); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetVoxel(struct R3ColliderHandle handle, + struct R3VoxelKey key, + R3Bool filled); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3Pose r3RigidBody_NextPosition(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL struct R3Rotation r3RigidBody_Rotation(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3RigidBody_CenterOfMass(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3RigidBody_LocalCenterOfMass(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL struct R3Vector r3RigidBody_UserForce(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3AngVector r3RigidBody_UserTorque(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL uint32_t r3RigidBody_BodyType(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3RigidBody_Mass(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3RigidBody_GravityScale(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3RigidBody_LinearDamping(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3RigidBody_AngularDamping(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3RigidBody_KineticEnergy(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3RigidBody_SoftCcdPrediction(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsCcdEnabled(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsDynamic(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyHandle r3RigidBody_SoftBody(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsSoftFrame(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsFixed(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsKinematic(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsMoving(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsCcdActive(struct R3RigidBodyHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetRotation(struct R3RigidBodyHandle handle, + struct R3Rotation value, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetBodyType(struct R3RigidBodyHandle handle, + uint32_t value, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetNextKinematicRotation(struct R3RigidBodyHandle handle, + struct R3Rotation value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetAdditionalMass(struct R3RigidBodyHandle handle, + R3Real value, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetSoftCcdPrediction(struct R3RigidBodyHandle handle, + R3Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetCcdEnabled(struct R3RigidBodyHandle handle, + R3Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetTranslationsLocked(struct R3RigidBodyHandle handle, + R3Bool value, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetRotationsLocked(struct R3RigidBodyHandle handle, + R3Bool value, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetDominanceGroup(struct R3RigidBodyHandle handle, + int8_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetAdditionalSolverIterations(struct R3RigidBodyHandle handle, + size_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetAdditionalPgsIterations(struct R3RigidBodyHandle handle, + size_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_AddTorque(struct R3RigidBodyHandle handle, + R3AngVector value, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_ApplyTorqueImpulse(struct R3RigidBodyHandle handle, + R3AngVector value, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_AddForceAtPoint(struct R3RigidBodyHandle handle, + struct R3Vector value, + struct R3Vector point, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_ResetTorques(struct R3RigidBodyHandle handle, + R3Bool wake_up); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3RigidBody_VelocityAtPoint(struct R3RigidBodyHandle handle, + struct R3Vector point); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +size_t r3RigidBody_Colliders(struct R3RigidBodyHandle handle, + struct R3ColliderHandle *buffer, + size_t capacity); + +#if defined(RAPIER_DIM3) +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Bool r3RigidBody_GyroscopicForcesEnabled(struct R3RigidBodyHandle handle); +#endif + +#if defined(RAPIER_DIM3) +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3RigidBody_SetGyroscopicForcesEnabled(struct R3RigidBodyHandle handle, + R3Bool enabled); +#endif + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetDensity(struct R3ColliderHandle handle, + R3Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetMass(struct R3ColliderHandle handle, + R3Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetEnabled(struct R3ColliderHandle handle, + R3Bool value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetSolverGroups(struct R3ColliderHandle handle, + struct R3InteractionGroups value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetFrictionCombineRule(struct R3ColliderHandle handle, + uint32_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetRestitutionCombineRule(struct R3ColliderHandle handle, + uint32_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetContactSkin(struct R3ColliderHandle handle, + R3Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetContactForceEventThreshold(struct R3ColliderHandle handle, + R3Real value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetActiveEvents(struct R3ColliderHandle handle, + uint32_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetActiveHooks(struct R3ColliderHandle handle, + uint32_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetActiveCollisionTypes(struct R3ColliderHandle handle, + uint16_t value); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL struct R3Rotation r3Collider_Rotation(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3InteractionGroups r3Collider_CollisionGroups(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +struct R3InteractionGroups r3Collider_SolverGroups(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL struct R3UserData r3Collider_UserData(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL uint32_t r3Collider_ActiveEvents(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3Collider_Mass(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3Collider_Density(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3Collider_Volume(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Real r3Collider_ContactSkin(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Real r3Collider_ContactForceEventThreshold(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL R3Bool r3Collider_IsEnabled(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL struct R3Aabb r3Collider_ComputeAabb(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + */ +RAPIER_API RAPIER_CALL R3SharedShape *r3Collider_CloneShape(struct R3ColliderHandle handle); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetShape(struct R3ColliderHandle handle, + const R3SharedShape *shape); + +/** + * Resolves the generational handle for this call; rejects stale handles. + */ +RAPIER_API RAPIER_CALL +R3Status r3Collider_SetPositionWrtParent(struct R3ColliderHandle handle, + struct R3Pose value); + +RAPIER_API RAPIER_CALL R3Status r3RigidBody_ValidateHandle(struct R3RigidBodyHandle handle); + +RAPIER_API RAPIER_CALL R3Status r3Collider_ValidateHandle(struct R3ColliderHandle handle); + +RAPIER_API RAPIER_CALL R3Status r3SoftBody_ValidateHandle(struct R3SoftBodyHandle handle); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetLocalFrame1(struct R3JointDesc *desc, + struct R3Pose value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetLocalFrame1(struct R3ImpulseJointHandle handle, + struct R3Pose value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetLocalFrame2(struct R3JointDesc *desc, + struct R3Pose value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetLocalFrame2(struct R3ImpulseJointHandle handle, + struct R3Pose value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetLocalAnchor1(struct R3JointDesc *desc, + struct R3Vector value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetLocalAnchor1(struct R3ImpulseJointHandle handle, + struct R3Vector value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetLocalAnchor2(struct R3JointDesc *desc, + struct R3Vector value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetLocalAnchor2(struct R3ImpulseJointHandle handle, + struct R3Vector value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetContactsEnabled(struct R3JointDesc *desc, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetContactsEnabled(struct R3ImpulseJointHandle handle, + R3Bool value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetEnabled(struct R3JointDesc *desc, + R3Bool value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetEnabled(struct R3ImpulseJointHandle handle, + R3Bool value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetSoftness(struct R3JointDesc *desc, + struct R3SpringCoefficients value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetSoftness(struct R3ImpulseJointHandle handle, + struct R3SpringCoefficients value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetLockedAxes(struct R3JointDesc *desc, + uint8_t value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetLockedAxes(struct R3ImpulseJointHandle handle, + uint8_t value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetLimitAxes(struct R3JointDesc *desc, + uint8_t value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetLimitAxes(struct R3ImpulseJointHandle handle, + uint8_t value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetMotorAxes(struct R3JointDesc *desc, + uint8_t value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetMotorAxes(struct R3ImpulseJointHandle handle, + uint8_t value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetCoupledAxes(struct R3JointDesc *desc, + uint8_t value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetCoupledAxes(struct R3ImpulseJointHandle handle, + uint8_t value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetLocalAxis1(struct R3JointDesc *desc, + struct R3Vector value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetLocalAxis1(struct R3ImpulseJointHandle handle, + struct R3Vector value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetLocalAxis2(struct R3JointDesc *desc, + struct R3Vector value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetLocalAxis2(struct R3ImpulseJointHandle handle, + struct R3Vector value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetLimits(struct R3JointDesc *desc, + uint32_t joint_axis, + R3Real min, + R3Real max); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetLimits(struct R3ImpulseJointHandle handle, + uint32_t joint_axis, + R3Real min, + R3Real max, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetMotor(struct R3JointDesc *desc, + uint32_t joint_axis, + R3Real target_position, + R3Real target_velocity, + R3Real stiffness, + R3Real damping); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetMotor(struct R3ImpulseJointHandle handle, + uint32_t joint_axis, + R3Real target_position, + R3Real target_velocity, + R3Real stiffness, + R3Real damping, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetMotorMaxForce(struct R3JointDesc *desc, + uint32_t joint_axis, + R3Real max_force); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetMotorMaxForce(struct R3ImpulseJointHandle handle, + uint32_t joint_axis, + R3Real max_force, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetMotorModel(struct R3JointDesc *desc, + uint32_t joint_axis, + uint32_t model); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetMotorModel(struct R3ImpulseJointHandle handle, + uint32_t joint_axis, + uint32_t model, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetUserData(struct R3JointDesc *desc, + struct R3UserData value); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetUserData(struct R3ImpulseJointHandle handle, + struct R3UserData value, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetMotorPosition(struct R3JointDesc *desc, + uint32_t joint_axis, + R3Real target_position, + R3Real stiffness, + R3Real damping); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetMotorPosition(struct R3ImpulseJointHandle handle, + uint32_t joint_axis, + R3Real target_position, + R3Real stiffness, + R3Real damping, + R3Bool wake_up); + +RAPIER_API RAPIER_CALL +R3Status r3JointDesc_SetMotorVelocity(struct R3JointDesc *desc, + uint32_t joint_axis, + R3Real target_velocity, + R3Real factor); + +RAPIER_API RAPIER_CALL +R3Status r3ImpulseJoint_SetMotorVelocity(struct R3ImpulseJointHandle handle, + uint32_t joint_axis, + R3Real target_velocity, + R3Real factor, + R3Bool wake_up); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R3SharedShape *r3ConvexDecompositionSharedShape(struct R3VectorView vertices, + R3SurfaceElementView indices); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R3SharedShape *r3VoxelsSharedShapeFromPoints(struct R3Vector voxel_size, + struct R3VectorView points); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R3SharedShape *r3VoxelizedMeshSharedShape(struct R3VectorView vertices, + R3SurfaceElementView indices, + R3Real voxel_size); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL R3SharedShape *r3ConvexHullSharedShape(struct R3VectorView vertices); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R3SharedShape *r3TrimeshSharedShape(struct R3VectorView vertices, + struct R3TriangleView indices); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R3SharedShape *r3PolylineSharedShape(struct R3VectorView vertices, + struct R3EdgeView indices); + +#if defined(RAPIER_DIM2) +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R3SharedShape *r3OrientedPolylineSharedShape(struct R3VectorView vertices, + struct R3EdgeView indices); +#endif + +#if defined(RAPIER_DIM2) +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R3SharedShape *r3ConvexPolylineSharedShape(struct R3VectorView vertices); +#endif + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R3SharedShape *r3RoundConvexHullSharedShape(struct R3VectorView vertices, + R3Real border_radius); + +/** + * Copies typed input geometry into an owned shared shape; arrays may be released on return. + */ +RAPIER_API RAPIER_CALL +R3SharedShape *r3TrimeshSharedShapeWithFlags(struct R3VectorView vertices, + struct R3TriangleView indices, + uint32_t flags); + +/** + * Create an owned world. Release it with FreeWorld. + */ +RAPIER_API RAPIER_CALL struct R3World *r3NewWorld(void); + +/** + * Free a world. NULL is allowed. Rejects destruction from an active callback. + * The caller must prevent other threads from starting calls during destruction. + */ +RAPIER_API RAPIER_CALL R3Status r3FreeWorld(struct R3World *world); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3VelocityCorrection r3ReadPidController_RigidBodyCorrection(const struct R3ReadContext *context, + struct R3PidController *controller, + R3Real dt, + struct R3RigidBodyHandle body, + struct R3Pose target_pose, + struct R3Vector target_linvel, + R3AngVector target_angvel); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL size_t r3ReadRigidBodyCount(const struct R3ReadContext *context); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +size_t r3ReadRigidBodyHandles(const struct R3ReadContext *context, + struct R3RigidBodyHandle *buffer, + size_t capacity); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadRigidBody_Contains(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL size_t r3ReadColliderCount(const struct R3ReadContext *context); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +size_t r3ReadColliderHandles(const struct R3ReadContext *context, + struct R3ColliderHandle *buffer, + size_t capacity); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadCollider_Contains(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +size_t r3ReadCollider_ShapeIdentity(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3MassProperties r3ReadCollider_MassProperties(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +uint8_t r3ReadRigidBody_LockedAxes(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadCollider_IsVoxels(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3VoxelQuery r3ReadCollider_VoxelAtFlatId(const struct R3ReadContext *context, + struct R3ColliderHandle handle, + uint32_t id); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Pose r3ReadRigidBody_NextPosition(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Rotation r3ReadRigidBody_Rotation(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3ReadRigidBody_CenterOfMass(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3ReadRigidBody_LocalCenterOfMass(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3ReadRigidBody_UserForce(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3AngVector r3ReadRigidBody_UserTorque(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +uint32_t r3ReadRigidBody_BodyType(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadRigidBody_Mass(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadRigidBody_GravityScale(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadRigidBody_LinearDamping(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadRigidBody_AngularDamping(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadRigidBody_KineticEnergy(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadRigidBody_SoftCcdPrediction(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadRigidBody_IsCcdEnabled(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadRigidBody_IsDynamic(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3SoftBodyHandle r3ReadRigidBody_SoftBody(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadRigidBody_IsSoftFrame(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadRigidBody_IsFixed(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadRigidBody_IsKinematic(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadRigidBody_IsMoving(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadRigidBody_IsCcdActive(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3ReadRigidBody_VelocityAtPoint(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle, + struct R3Vector point); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +size_t r3ReadRigidBody_Colliders(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle, + struct R3ColliderHandle *buffer, + size_t capacity); + +#if defined(RAPIER_DIM3) +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadRigidBody_GyroscopicForcesEnabled(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); +#endif + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Rotation r3ReadCollider_Rotation(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3InteractionGroups r3ReadCollider_CollisionGroups(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3InteractionGroups r3ReadCollider_SolverGroups(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3UserData r3ReadCollider_UserData(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +uint32_t r3ReadCollider_ActiveEvents(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadCollider_Mass(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadCollider_Density(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadCollider_Volume(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadCollider_ContactSkin(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadCollider_ContactForceEventThreshold(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadCollider_IsEnabled(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Aabb r3ReadCollider_ComputeAabb(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + */ +RAPIER_API RAPIER_CALL +R3SharedShape *r3ReadCollider_CloneShape(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Status r3ReadRigidBody_ValidateHandle(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Status r3ReadCollider_ValidateHandle(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Pose r3ReadRigidBody_Position(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3ReadRigidBody_Translation(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3ReadRigidBody_Linvel(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3AngVector r3ReadRigidBody_Angvel(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadRigidBody_IsSleeping(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadRigidBody_IsEnabled(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3UserData r3ReadRigidBody_UserData(const struct R3ReadContext *context, + struct R3RigidBodyHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Pose r3ReadCollider_Position(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3Vector r3ReadCollider_Translation(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadCollider_Friction(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Real r3ReadCollider_Restitution(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +R3Bool r3ReadCollider_IsSensor(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +struct R3RigidBodyHandle r3ReadCollider_Parent(const struct R3ReadContext *context, + struct R3ColliderHandle handle); + +/** + * Read callback-visible state. The context is valid only until its callback returns. + */ +RAPIER_API RAPIER_CALL +size_t r3ReadRigidBodyReadStates(const struct R3ReadContext *context, + const struct R3RigidBodyHandle *handles, + size_t handle_count, + struct R3RigidBodyState *states, + size_t capacity); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif + +#endif /* RAPIER_H */ diff --git a/c/include/rapier.hpp b/c/include/rapier.hpp new file mode 100644 index 000000000..4ca6740b1 --- /dev/null +++ b/c/include/rapier.hpp @@ -0,0 +1,84 @@ +#ifndef RAPIER_HPP +#define RAPIER_HPP +#include "rapier_helpers.h" +#include +#include +#include + +namespace rapier { +inline void check(RAPIER_TYPE(Status) status) { + if (status != RAPIER_CONST(OK)) { + throw std::runtime_error(std::string(RAPIER_FN(LastError)())); + } +} + +// Use Owner only for owned pointers, never for a borrowed callback context. +template struct Deleter { + void operator()(T *value) const noexcept { + (void)Free(value); + } +}; +template +using Owner = std::unique_ptr>; +using World = Owner; +using Shape = Owner; +using EventCollector = Owner; +using Snapshot = Owner; + +using SoftBodyTearEvent = Owner; +using ShapeMesh = Owner; +using KinematicCharacterController = + Owner; +using PidController = Owner; +#if defined(RAPIER_DIM3) +using DynamicRayCastVehicleController = + Owner; +using TriMeshData = Owner; +#endif +#if defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32) +using UrdfRobot = Owner; +using UrdfRobotHandles = Owner; +using MjcfRobot = Owner; +using MjcfRobotHandles = Owner; +#endif + +// These factories return ordinary values. No allocation or deleter is needed. +inline RAPIER_TYPE(RigidBodyDesc) rigid_body(uint32_t kind = RAPIER_CONST(DYNAMIC)) { + RAPIER_TYPE(RigidBodyDesc) value = RAPIER_FN(DynamicRigidBodyDesc)(); + value.bodyType = kind; + return value; +} + +inline RAPIER_TYPE(ColliderDesc) ball(RAPIER_TYPE(Real) radius) { + return RAPIER_FN(BallColliderDesc)(radius); +} + +inline RAPIER_TYPE(ColliderDesc) cuboid(RAPIER_TYPE(Vector) half_extents) { + return RAPIER_FN(CuboidColliderDesc)(half_extents); +} + +inline RAPIER_TYPE(SoftBodyDesc) soft_body() { + return RAPIER_FN(DefaultSoftBodyDesc)(); +} + +inline RAPIER_TYPE(JointDesc) joint(uint8_t locked_axes = 0) { + RAPIER_TYPE(JointDesc) value = RAPIER_FN(DefaultJointDesc)(); + value.lockedAxes = locked_axes; + return value; +} + +inline RAPIER_TYPE(QueryOptions) queryOptions() { + return RAPIER_FN(DefaultQueryOptions)(); +} + +inline World make_world() { + check(RAPIER_FN(CheckAbi)(RAPIER_CONST(ABI_VERSION), RAPIER_CONST(DIMENSION), + sizeof(RAPIER_TYPE(Real)), sizeof(RAPIER_TYPE(Vector)), + sizeof(RAPIER_TYPE(Pose)))); + RAPIER_TYPE(World) *value = RAPIER_FN(NewWorld)(); + check(RAPIER_FN(LastStatus)()); + return World(value); +} +} // namespace rapier +#endif diff --git a/c/include/rapier_helpers.h b/c/include/rapier_helpers.h new file mode 100644 index 000000000..740ef7cce --- /dev/null +++ b/c/include/rapier_helpers.h @@ -0,0 +1,15 @@ +#ifndef RAPIER_HELPERS_H +#define RAPIER_HELPERS_H +#include "rapier.h" + +/* Value initialization functions are exported by rapier.h. */ + +/* Explicit invalid values; zero-initialized Rapier handles are not invalid. + * These constants have internal linkage and own no resources. */ +static const RAPIER_TYPE(RigidBodyHandle) RAPIER_CONST(INVALID_RIGID_BODY_HANDLE) = {NULL, UINT32_MAX, UINT32_MAX}; +static const RAPIER_TYPE(ColliderHandle) RAPIER_CONST(INVALID_COLLIDER_HANDLE) = {NULL, UINT32_MAX, UINT32_MAX}; +static const RAPIER_TYPE(ImpulseJointHandle) RAPIER_CONST(INVALID_IMPULSE_JOINT_HANDLE) = {NULL, UINT32_MAX, UINT32_MAX}; +static const RAPIER_TYPE(MultibodyJointHandle) RAPIER_CONST(INVALID_MULTIBODY_JOINT_HANDLE) = {NULL, UINT32_MAX, UINT32_MAX}; +static const RAPIER_TYPE(SoftBodyHandle) RAPIER_CONST(INVALID_SOFT_BODY_HANDLE) = {NULL, UINT32_MAX, UINT32_MAX}; + +#endif diff --git a/c/include/rapier_math.h b/c/include/rapier_math.h new file mode 100644 index 000000000..4e2a2ca93 --- /dev/null +++ b/c/include/rapier_math.h @@ -0,0 +1,155 @@ +#ifndef RAPIER_MATH_H +#define RAPIER_MATH_H +#include "rapier.h" +#include + +#if defined(RAPIER_DIM2) +#define R2_PI ((R2Real)3.14159265358979323846) +#else +#define R3_PI ((R3Real)3.14159265358979323846) +#endif + +/* Value constructors and arithmetic for Rapier's public C math types. */ +#if defined(RAPIER_DIM2) +static inline RAPIER_TYPE(Vector) RAPIER_FN(Vector)(RAPIER_TYPE(Real) x, RAPIER_TYPE(Real) y) { + RAPIER_TYPE(Vector) result = {x, y}; + return result; +} + +static inline RAPIER_TYPE(Rotation) RAPIER_FN(Rotation)(RAPIER_TYPE(Real) angle) { + RAPIER_TYPE(Rotation) result = {angle}; + return result; +} +#else +static inline RAPIER_TYPE(Vector) RAPIER_FN(Vector)(RAPIER_TYPE(Real) x, RAPIER_TYPE(Real) y, RAPIER_TYPE(Real) z) { + RAPIER_TYPE(Vector) result = {x, y, z}; + return result; +} + +static inline RAPIER_TYPE(Rotation) RAPIER_FN(RotationFromAxisAngle)(RAPIER_TYPE(Vector) axis, + RAPIER_TYPE(Real) angle) { + RAPIER_TYPE(Real) length = + (RAPIER_TYPE(Real))sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z); + if (length == 0) { + RAPIER_TYPE(Rotation) result = {0, 0, 0, 1}; + return result; + } + RAPIER_TYPE(Real) scale = (RAPIER_TYPE(Real))sin(angle / 2) / length; + RAPIER_TYPE(Rotation) result = {axis.x * scale, axis.y * scale, axis.z * scale, + (RAPIER_TYPE(Real))cos(angle / 2)}; + return result; +} +#endif + +static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorAdd)(RAPIER_TYPE(Vector) a, RAPIER_TYPE(Vector) b) { +#if defined(RAPIER_DIM2) + return RAPIER_FN(Vector)(a.x + b.x, a.y + b.y); +#else + return RAPIER_FN(Vector)(a.x + b.x, a.y + b.y, a.z + b.z); +#endif +} + +static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorSub)(RAPIER_TYPE(Vector) a, RAPIER_TYPE(Vector) b) { +#if defined(RAPIER_DIM2) + return RAPIER_FN(Vector)(a.x - b.x, a.y - b.y); +#else + return RAPIER_FN(Vector)(a.x - b.x, a.y - b.y, a.z - b.z); +#endif +} + +static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorScale)(RAPIER_TYPE(Vector) vector, RAPIER_TYPE(Real) scale) { +#if defined(RAPIER_DIM2) + return RAPIER_FN(Vector)(vector.x * scale, vector.y * scale); +#else + return RAPIER_FN(Vector)(vector.x * scale, vector.y * scale, vector.z * scale); +#endif +} + +static inline RAPIER_TYPE(Real) RAPIER_FN(VectorDot)(RAPIER_TYPE(Vector) a, RAPIER_TYPE(Vector) b) { +#if defined(RAPIER_DIM2) + return a.x * b.x + a.y * b.y; +#else + return a.x * b.x + a.y * b.y + a.z * b.z; +#endif +} +static inline RAPIER_TYPE(Real) RAPIER_FN(VectorLength)(RAPIER_TYPE(Vector) vector) { + return (RAPIER_TYPE(Real))sqrt(RAPIER_FN(VectorDot)(vector, vector)); +} +static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorNormalize)(RAPIER_TYPE(Vector) vector) { + RAPIER_TYPE(Real) length = RAPIER_FN(VectorLength)(vector); + return length > 0 ? RAPIER_FN(VectorScale)(vector, 1 / length) : vector; +} +#if defined(RAPIER_DIM3) +static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorCross)(RAPIER_TYPE(Vector) a, RAPIER_TYPE(Vector) b) { + return RAPIER_FN(Vector)(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x); +} +#endif + +static inline RAPIER_TYPE(Rotation) RAPIER_FN(RotationMul)(RAPIER_TYPE(Rotation) a, RAPIER_TYPE(Rotation) b) { +#if defined(RAPIER_DIM2) + RAPIER_TYPE(Rotation) result = {a.angle + b.angle}; + return result; +#else + RAPIER_TYPE(Rotation) result = {a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, + a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x, + a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w, + a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z}; + return result; +#endif +} + +/* Rotate a vector without changing its length. The rotation must be normalized. + */ +static inline RAPIER_TYPE(Vector) RAPIER_FN(RotationTransformVector)(RAPIER_TYPE(Rotation) rotation, + RAPIER_TYPE(Vector) vector) { +#if defined(RAPIER_DIM2) + const RAPIER_TYPE(Real) c = (RAPIER_TYPE(Real))cos(rotation.angle), + s = (RAPIER_TYPE(Real))sin(rotation.angle); + return RAPIER_FN(Vector)(c * vector.x - s * vector.y, + s * vector.x + c * vector.y); +#else + const RAPIER_TYPE(Vector) t = {2 * (rotation.y * vector.z - rotation.z * vector.y), + 2 * (rotation.z * vector.x - rotation.x * vector.z), + 2 * (rotation.x * vector.y - rotation.y * vector.x)}; + return RAPIER_FN(Vector)( + vector.x + rotation.w * t.x + rotation.y * t.z - rotation.z * t.y, + vector.y + rotation.w * t.y + rotation.z * t.x - rotation.x * t.z, + vector.z + rotation.w * t.z + rotation.x * t.y - rotation.y * t.x); +#endif +} + +static inline RAPIER_TYPE(Pose) RAPIER_FN(Pose)(RAPIER_TYPE(Vector) translation, + RAPIER_TYPE(Rotation) rotation) { + RAPIER_TYPE(Pose) result = {translation, rotation}; + return result; +} + +static inline RAPIER_TYPE(Pose) RAPIER_FN(TranslationPose)(RAPIER_TYPE(Vector) translation) { +#if defined(RAPIER_DIM2) + RAPIER_TYPE(Rotation) rotation = {0}; +#else + RAPIER_TYPE(Rotation) rotation = {0, 0, 0, 1}; +#endif + return RAPIER_FN(Pose)(translation, rotation); +} +static inline RAPIER_TYPE(Rotation) RAPIER_FN(RotationInverse)(RAPIER_TYPE(Rotation) rotation) { +#if defined(RAPIER_DIM2) + return RAPIER_FN(Rotation)(-rotation.angle); +#else + RAPIER_TYPE(Rotation) result = {-rotation.x, -rotation.y, -rotation.z, rotation.w}; + return result; +#endif +} +static inline RAPIER_TYPE(Vector) RAPIER_FN(PoseTransformPoint)(RAPIER_TYPE(Pose) pose, + RAPIER_TYPE(Vector) point) { + return RAPIER_FN(VectorAdd)( + pose.translation, RAPIER_FN(RotationTransformVector)(pose.rotation, point)); +} +static inline RAPIER_TYPE(Pose) RAPIER_FN(PoseInverse)(RAPIER_TYPE(Pose) pose) { + RAPIER_TYPE(Rotation) rotation = RAPIER_FN(RotationInverse)(pose.rotation); + return RAPIER_FN(Pose)(RAPIER_FN(RotationTransformVector)( + rotation, RAPIER_FN(VectorScale)(pose.translation, -1)), + rotation); +} +#endif diff --git a/c/rapier-c-macros/Cargo.toml b/c/rapier-c-macros/Cargo.toml new file mode 100644 index 000000000..4ef395320 --- /dev/null +++ b/c/rapier-c-macros/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rapier-c-macros" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +publish = false + +[lib] +proc-macro = true diff --git a/c/rapier-c-macros/src/lib.rs b/c/rapier-c-macros/src/lib.rs new file mode 100644 index 000000000..cf024cf07 --- /dev/null +++ b/c/rapier-c-macros/src/lib.rs @@ -0,0 +1,157 @@ +//! Export naming for the dimension-independent Rapier C implementation. +use proc_macro::{TokenStream, TokenTree}; + +fn pascal_case(name: &str) -> Result { + let mut result = String::new(); + for word in name.split('_') { + if word.is_empty() + || !word + .bytes() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()) + { + return Err("expected a nonempty snake_case function name after rpr_"); + } + let mut chars = word.chars(); + result.push(chars.next().unwrap().to_ascii_uppercase()); + result.extend(chars); + } + Ok(result) +} + +fn name_suffix(name: &str, receiver: Option<&str>) -> Result { + let suffix = name + .strip_prefix("rpr_") + .ok_or("expected an rpr_ function name")?; + match receiver { + Some(receiver) => { + let method = suffix + .strip_prefix(receiver) + .and_then(|method| method.strip_prefix('_')) + .ok_or( + "method name must start with the receiver prefix followed by an underscore", + )?; + Ok(format!( + "{}_{}", + pascal_case(receiver)?, + pascal_case(method)? + )) + } + None => pascal_case(suffix), + } +} + +/// Export a dimension-specific C symbol, keeping the Rust identifier unchanged. +/// +/// `#[rapier_export]` exports `rpr_new_world` as `r2NewWorld` / `r3NewWorld`. +/// Instance methods specify their receiver prefix: `#[rapier_export(rigid_body)]` +/// exports `rpr_rigid_body_position` as `r2RigidBody_Position` / `r3RigidBody_Position`. +/// Constructors, destructors, static helpers, and short world functions omit the receiver. +#[proc_macro_attribute] +pub fn rapier_export(args: TokenStream, item: TokenStream) -> TokenStream { + fn expand(args: TokenStream, item: TokenStream) -> Result { + let mut args = args.into_iter(); + let receiver = match (args.next(), args.next()) { + (None, None) => None, + (Some(TokenTree::Ident(receiver)), None) => Some(receiver.to_string()), + _ => return Err("expected no arguments or one snake_case receiver prefix"), + }; + let mut tokens = item.clone().into_iter(); + // Groups are opaque here, so a `fn` token in a body or attribute cannot match. + let found = + tokens.any(|token| matches!(token, TokenTree::Ident(id) if id.to_string() == "fn")); + if !found { + return Err("rapier_export requires a function"); + } + let Some(TokenTree::Ident(name)) = tokens.next() else { + return Err("expected a function name"); + }; + let suffix = name_suffix(&name.to_string(), receiver.as_deref())?; + let attributes = format!( + r#"#[cfg_attr(feature = "dim2", unsafe(export_name = "r2{suffix}"))] + #[cfg_attr(feature = "dim3", unsafe(export_name = "r3{suffix}"))]"# + ); + let mut output: TokenStream = attributes.parse().unwrap(); + output.extend(item); + Ok(output) + } + expand(args, item) + .unwrap_or_else(|message| format!("compile_error!({message:?});").parse().unwrap()) +} + +#[cfg(test)] +mod tests { + use super::name_suffix; + + #[test] + fn lifecycle_functions_and_helpers_use_natural_word_order() { + for (rust, c) in [ + ("rpr_new_world", "NewWorld"), + ("rpr_free_world", "FreeWorld"), + ("rpr_cuboid_collider_desc", "CuboidColliderDesc"), + ("rpr_default_query_options", "DefaultQueryOptions"), + ("rpr_urdf_robot_from_file", "UrdfRobotFromFile"), + ("rpr_dynamic_rigid_body_desc", "DynamicRigidBodyDesc"), + ("rpr_ball_shared_shape", "BallSharedShape"), + ("rpr_step", "Step"), + ("rpr_insert_rigid_body", "InsertRigidBody"), + ("rpr_check_abi", "CheckAbi"), + ("rpr_matrix_3x3", "Matrix3x3"), + ] { + assert_eq!(name_suffix(rust, None), Ok(c.into())); + } + } + + #[test] + fn instance_methods_separate_the_receiver_from_the_method() { + for (rust, receiver, c) in [ + ( + "rpr_rigid_body_is_ccd_enabled", + "rigid_body", + "RigidBody_IsCcdEnabled", + ), + ( + "rpr_joint_desc_set_local_frame1", + "joint_desc", + "JointDesc_SetLocalFrame1", + ), + ( + "rpr_soft_body_tear_event_bodies", + "soft_body_tear_event", + "SoftBodyTearEvent_Bodies", + ), + ( + "rpr_read_rigid_body_position", + "read_rigid_body", + "ReadRigidBody_Position", + ), + ] { + assert_eq!(name_suffix(rust, Some(receiver)), Ok(c.into())); + } + } + + #[test] + fn reject_names_outside_the_export_convention() { + for name in [ + "physics_world_new", + "rpr_", + "rpr__new", + "rpr_world_", + "rpr_World", + ] { + assert!(name_suffix(name, None).is_err(), "{name}"); + } + for (name, receiver) in [ + ("rpr_free_world", "rigid_body"), + ("rpr_free_world", "wor"), + ("rpr_free_world", ""), + ("rpr_free_world", "World"), + ("rpr_world_", "world"), + ("rpr_world", "world"), + ] { + assert!( + name_suffix(name, Some(receiver)).is_err(), + "{name}: {receiver}" + ); + } + } +} diff --git a/c/rapier2d-f64-ffi/Cargo.toml b/c/rapier2d-f64-ffi/Cargo.toml new file mode 100644 index 000000000..cab6836d4 --- /dev/null +++ b/c/rapier2d-f64-ffi/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "rapier2d-f64-ffi" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "C ABI for rapier2d-f64" +publish = false +build = "../build.rs" + +[lib] +name = "rapier2d_f64_ffi" +path = "../src/lib.rs" +crate-type = ["staticlib", "cdylib", "rlib"] + +[features] +default = ["dim2", "f64"] +dim2 = [] +f64 = [] +parallel = ["rapier/parallel"] +profiler = ["rapier/profiler"] +enhanced-determinism = ["rapier/enhanced-determinism"] +fem = ["rapier/fem"] + +[dependencies] +rapier-c-macros = { path = "../rapier-c-macros" } +rapier = { package = "rapier2d-f64", path = "../../crates/rapier2d-f64", features = ["serde-serialize", "debug-render"] } +bincode.workspace = true +serde.workspace = true + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("dim2", "dim3", "f32", "f64", "robotics"))'] } diff --git a/c/rapier2d-ffi/Cargo.toml b/c/rapier2d-ffi/Cargo.toml new file mode 100644 index 000000000..69fe0680e --- /dev/null +++ b/c/rapier2d-ffi/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "rapier2d-ffi" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "C ABI for rapier2d" +publish = false +build = "../build.rs" + +[lib] +name = "rapier2d_ffi" +path = "../src/lib.rs" +crate-type = ["staticlib", "cdylib", "rlib"] + +[features] +default = ["dim2", "f32"] +dim2 = [] +f32 = [] +parallel = ["rapier/parallel"] +profiler = ["rapier/profiler"] +simd8 = ["rapier/simd8"] +enhanced-determinism = ["rapier/enhanced-determinism"] +fem = ["rapier/fem"] + +[dependencies] +rapier-c-macros = { path = "../rapier-c-macros" } +rapier = { package = "rapier2d", path = "../../crates/rapier2d", features = ["serde-serialize", "debug-render"] } +bincode.workspace = true +serde.workspace = true + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("dim2", "dim3", "f32", "f64", "robotics"))'] } + +[[example]] +name = "compare_steps" +path = "../examples/compare_steps.rs" diff --git a/c/rapier3d-f64-ffi/Cargo.toml b/c/rapier3d-f64-ffi/Cargo.toml new file mode 100644 index 000000000..1c8f4c0b5 --- /dev/null +++ b/c/rapier3d-f64-ffi/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "rapier3d-f64-ffi" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "C ABI for rapier3d-f64" +publish = false +build = "../build.rs" + +[lib] +name = "rapier3d_f64_ffi" +path = "../src/lib.rs" +crate-type = ["staticlib", "cdylib", "rlib"] + +[features] +default = ["dim3", "f64"] +dim3 = [] +f64 = [] +parallel = ["rapier/parallel"] +profiler = ["rapier/profiler"] +enhanced-determinism = ["rapier/enhanced-determinism"] +fem = ["rapier/fem"] + +[dependencies] +rapier-c-macros = { path = "../rapier-c-macros" } +rapier = { package = "rapier3d-f64", path = "../../crates/rapier3d-f64", features = ["serde-serialize", "debug-render"] } +bincode.workspace = true +serde.workspace = true + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("dim2", "dim3", "f32", "f64", "robotics"))'] } diff --git a/c/rapier3d-ffi/Cargo.toml b/c/rapier3d-ffi/Cargo.toml new file mode 100644 index 000000000..94807de27 --- /dev/null +++ b/c/rapier3d-ffi/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "rapier3d-ffi" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "C ABI for rapier3d" +publish = false +build = "../build.rs" + +[lib] +name = "rapier3d_ffi" +path = "../src/lib.rs" +crate-type = ["staticlib", "cdylib", "rlib"] + +[features] +default = ["dim3", "f32"] +dim3 = [] +f32 = [] +parallel = ["rapier/parallel"] +profiler = ["rapier/profiler"] +simd8 = ["rapier/simd8"] +enhanced-determinism = ["rapier/enhanced-determinism"] +fem = ["rapier/fem"] +robotics = ["dep:rapier3d-urdf", "dep:rapier3d-mjcf"] + +[dependencies] +rapier-c-macros = { path = "../rapier-c-macros" } +rapier = { package = "rapier3d", path = "../../crates/rapier3d", features = ["serde-serialize", "debug-render"] } +rapier3d-urdf = { workspace = true, optional = true, features = ["stl", "collada", "wavefront"] } +rapier3d-mjcf = { workspace = true, optional = true, features = ["stl", "wavefront", "msh"] } +bincode.workspace = true +serde.workspace = true + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("dim2", "dim3", "f32", "f64", "robotics"))'] } + +[[example]] +name = "compare_steps" +path = "../examples/compare_steps.rs" diff --git a/c/src/array_views.rs b/c/src/array_views.rs new file mode 100644 index 000000000..64757b87f --- /dev/null +++ b/c/src/array_views.rs @@ -0,0 +1,417 @@ +//! Borrowed, typed array inputs. Setters do not allocate or retain ownership. +#![allow(non_snake_case)] +use crate::*; + +/// Vertex indices for one edge; contiguous u32 fields with no padding. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprEdge { + pub a: u32, + pub b: u32, +} +const _: () = assert!(size_of::() == 2 * size_of::()); +/// Vertex indices for one triangle; contiguous u32 fields with no padding. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprTriangle { + pub a: u32, + pub b: u32, + pub c: u32, +} +const _: () = assert!(size_of::() == 3 * size_of::()); +/// Vertex indices for one tetrahedron; contiguous u32 fields with no padding. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprTetrahedron { + pub a: u32, + pub b: u32, + pub c: u32, + pub d: u32, +} +const _: () = assert!(size_of::() == 4 * size_of::()); +/// Vertex indices for one dihedral; contiguous u32 fields with no padding. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprDihedral { + pub a: u32, + pub b: u32, + pub c: u32, + pub d: u32, +} +const _: () = assert!(size_of::() == 4 * size_of::()); +/// Borrowed array of vector elements. count always counts elements, not scalars. +/// Copying this view does not copy its data or extend its lifetime. No Free is needed. +/// Data must remain live through the build/insert call that reads the description. +/// NULL is permitted only when count is zero. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprVectorView { + pub data: *const RprVector, + pub count: usize, +} +/// Borrowed array of real elements. count always counts elements, not scalars. +/// Copying this view does not copy its data or extend its lifetime. No Free is needed. +/// Data must remain live through the build/insert call that reads the description. +/// NULL is permitted only when count is zero. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprRealView { + pub data: *const RprReal, + pub count: usize, +} +/// Borrowed array of index elements. count always counts elements, not scalars. +/// Copying this view does not copy its data or extend its lifetime. No Free is needed. +/// Data must remain live through the build/insert call that reads the description. +/// NULL is permitted only when count is zero. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprIndexView { + pub data: *const u32, + pub count: usize, +} +/// Borrowed array of edge elements. count always counts elements, not scalars. +/// Copying this view does not copy its data or extend its lifetime. No Free is needed. +/// Data must remain live through the build/insert call that reads the description. +/// NULL is permitted only when count is zero. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprEdgeView { + pub data: *const RprEdge, + pub count: usize, +} +/// Borrowed array of triangle elements. count always counts elements, not scalars. +/// Copying this view does not copy its data or extend its lifetime. No Free is needed. +/// Data must remain live through the build/insert call that reads the description. +/// NULL is permitted only when count is zero. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprTriangleView { + pub data: *const RprTriangle, + pub count: usize, +} +/// Borrowed array of tetrahedron elements. count always counts elements, not scalars. +/// Copying this view does not copy its data or extend its lifetime. No Free is needed. +/// Data must remain live through the build/insert call that reads the description. +/// NULL is permitted only when count is zero. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprTetrahedronView { + pub data: *const RprTetrahedron, + pub count: usize, +} +/// Borrowed array of dihedral elements. count always counts elements, not scalars. +/// Copying this view does not copy its data or extend its lifetime. No Free is needed. +/// Data must remain live through the build/insert call that reads the description. +/// NULL is permitted only when count is zero. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprDihedralView { + pub data: *const RprDihedral, + pub count: usize, +} +#[cfg(feature = "dim2")] +pub type RprCellView = RprTriangleView; +#[cfg(feature = "dim3")] +pub type RprCellView = RprTetrahedronView; +#[cfg(feature = "dim2")] +pub type RprSurfaceElementView = RprEdgeView; +#[cfg(feature = "dim3")] +pub type RprSurfaceElementView = RprTriangleView; + +// Validate metadata without reading elements or allocating. Numerical values and +// topology bounds are validated by the existing build/insert path. +pub(crate) fn validate_view(data: *const T, count: usize) -> Result { + if count == 0 { + return Ok(()); + } + ensure( + count <= isize::MAX as usize / size_of::(), + "array view is too large", + )?; + if data.is_null() { + return Err((RPR_NULL_POINTER, "nonempty array view has null data".into())); + } + ensure(data.is_aligned(), "misaligned array view") +} + +/// Replace the shape geometry with a borrowed tri mesh. Counts are elements. +/// Copies no arrays. Invalid view metadata leaves the description unchanged. +/// Geometry and flags are validated when the description is built or inserted. +#[rapier_export(shape_desc)] +pub unsafe extern "C" fn rpr_shape_desc_set_trimesh( + desc: *mut RprShapeDesc, + vertices: RprVectorView, + indices: RprTriangleView, + flags: u32, +) -> RprStatus { + ffi(|| unsafe { + validate_view(vertices.data, vertices.count)?; + validate_view(indices.data, indices.count)?; + let shape = RprShapeDesc { + kind: RPR_SHAPE_DESC_TRIMESH, + vertices, + triangles: indices, + flags, + ..RprShapeDesc::default() + }; + output(desc, shape) + }) +} +/// Replace the shape geometry with a borrowed polyline. Counts are elements. +/// Copies no arrays. Invalid view metadata leaves the description unchanged. +/// Geometry and flags are validated when the description is built or inserted. +#[rapier_export(shape_desc)] +pub unsafe extern "C" fn rpr_shape_desc_set_polyline( + desc: *mut RprShapeDesc, + vertices: RprVectorView, + indices: RprEdgeView, + flags: u32, +) -> RprStatus { + ffi(|| unsafe { + validate_view(vertices.data, vertices.count)?; + validate_view(indices.data, indices.count)?; + let shape = RprShapeDesc { + kind: RPR_SHAPE_DESC_POLYLINE, + vertices, + edges: indices, + flags, + ..RprShapeDesc::default() + }; + output(desc, shape) + }) +} +/// Replace the shape geometry with a borrowed convex hull point cloud. +#[rapier_export(shape_desc)] +pub unsafe extern "C" fn rpr_shape_desc_set_convex_hull( + desc: *mut RprShapeDesc, + vertices: RprVectorView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(vertices.data, vertices.count)?; + output( + desc, + RprShapeDesc { + kind: RPR_SHAPE_DESC_CONVEX_HULL, + vertices, + ..RprShapeDesc::default() + }, + ) + }) +} +/// Select an explicit particle recipe and borrow its positions. Other fields are preserved. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_particles( + desc: *mut RprSoftBodyDesc, + positions: RprVectorView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(positions.data, positions.count)?; + let desc = get_mut(desc)?; + desc.kind = RPR_SOFT_DESC_PARTICLES; + desc.positions = positions; + Ok(()) + }) +} +/// Select a surface recipe and borrow its vertices and elements. Other fields are preserved. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_surface_mesh( + desc: *mut RprSoftBodyDesc, + vertices: RprVectorView, + elements: RprSurfaceElementView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(vertices.data, vertices.count)?; + validate_view(elements.data, elements.count)?; + let desc = get_mut(desc)?; + desc.kind = RPR_SOFT_DESC_SURFACE; + desc.positions = vertices; + desc.surface = elements; + Ok(()) + }) +} +/// Borrow skin geometry. Other fields, including skinCollision, are preserved. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_skin( + desc: *mut RprSoftBodyDesc, + vertices: RprVectorView, + elements: RprSurfaceElementView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(vertices.data, vertices.count)?; + validate_view(elements.data, elements.count)?; + let desc = get_mut(desc)?; + desc.skinVertices = vertices; + desc.skinIndices = elements; + Ok(()) + }) +} +/// Borrow masses; preserve all other fields. No allocation or element reads. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Invalid view metadata leaves the description unchanged. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_masses( + desc: *mut RprSoftBodyDesc, + view: RprRealView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(view.data, view.count)?; + let desc = get_mut(desc)?; + desc.masses = view; + Ok(()) + }) +} +/// Borrow pinned particles; preserve all other fields. No allocation or element reads. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Invalid view metadata leaves the description unchanged. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_pinned_particles( + desc: *mut RprSoftBodyDesc, + view: RprIndexView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(view.data, view.count)?; + let desc = get_mut(desc)?; + desc.pinned = view; + Ok(()) + }) +} +/// Borrow edges; preserve all other fields. No allocation or element reads. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Invalid view metadata leaves the description unchanged. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_edges( + desc: *mut RprSoftBodyDesc, + view: RprEdgeView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(view.data, view.count)?; + let desc = get_mut(desc)?; + desc.edges = view; + Ok(()) + }) +} +/// Borrow bend edges; preserve all other fields. No allocation or element reads. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Invalid view metadata leaves the description unchanged. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_bend_edges( + desc: *mut RprSoftBodyDesc, + view: RprEdgeView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(view.data, view.count)?; + let desc = get_mut(desc)?; + desc.bendEdges = view; + Ok(()) + }) +} +/// Borrow cells; preserve all other fields. No allocation or element reads. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Invalid view metadata leaves the description unchanged. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_cells( + desc: *mut RprSoftBodyDesc, + view: RprCellView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(view.data, view.count)?; + let desc = get_mut(desc)?; + desc.cells = view; + Ok(()) + }) +} +/// Borrow surface; preserve all other fields. No allocation or element reads. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Invalid view metadata leaves the description unchanged. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_surface( + desc: *mut RprSoftBodyDesc, + view: RprSurfaceElementView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(view.data, view.count)?; + let desc = get_mut(desc)?; + desc.surface = view; + Ok(()) + }) +} +/// Borrow tension only edges; preserve all other fields. No allocation or element reads. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Invalid view metadata leaves the description unchanged. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_tension_only_edges( + desc: *mut RprSoftBodyDesc, + view: RprIndexView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(view.data, view.count)?; + let desc = get_mut(desc)?; + desc.tensionOnlyEdges = view; + Ok(()) + }) +} +/// Borrow dihedrals; preserve all other fields. No allocation or element reads. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Invalid view metadata leaves the description unchanged. +#[cfg(feature = "dim3")] +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_dihedrals( + desc: *mut RprSoftBodyDesc, + view: RprDihedralView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(view.data, view.count)?; + let desc = get_mut(desc)?; + desc.dihedrals = view; + Ok(()) + }) +} +/// Borrow wire; preserve all other fields. No allocation or element reads. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Invalid view metadata leaves the description unchanged. +#[cfg(feature = "dim3")] +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_set_wire( + desc: *mut RprSoftBodyDesc, + view: RprEdgeView, +) -> RprStatus { + ffi(|| unsafe { + validate_view(view.data, view.count)?; + let desc = get_mut(desc)?; + desc.wire = view; + Ok(()) + }) +} + +#[cfg(feature = "dim2")] +pub type RprCell = RprTriangle; +#[cfg(feature = "dim3")] +pub type RprCell = RprTetrahedron; +#[cfg(feature = "dim2")] +pub type RprSurfaceElement = RprEdge; +#[cfg(feature = "dim3")] +pub type RprSurfaceElement = RprTriangle; + +/// Borrowed elements; count counts elements. Data must remain live through insertion. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprSoftEdgeSoftnessView { + pub data: *const RprSoftEdgeSoftness, + pub count: usize, +} + +/// Borrowed elements; count counts elements. Data must remain live through insertion. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprSoftEdgeTearView { + pub data: *const RprSoftEdgeTear, + pub count: usize, +} + +/// Borrowed elements; count counts elements. Data must remain live through insertion. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprCompoundShapeView { + pub data: *const RprCompoundShapeDesc, + pub count: usize, +} diff --git a/c/src/config_data.rs b/c/src/config_data.rs new file mode 100644 index 000000000..c65188880 --- /dev/null +++ b/c/src/config_data.rs @@ -0,0 +1,496 @@ +//! Copyable configuration snapshots. Apply functions validate before replacing native state. +#![allow(non_snake_case)] +use crate::*; +#[cfg(feature = "dim3")] +use rapier::dynamics::FrictionModel; +#[cfg(feature = "fem")] +use rapier::dynamics::SoftFemParameters; +use rapier::dynamics::{ + SoftBodiesSettings, SoftEdgePlasticFlow, SoftPatchConstraints, SoftRecoverySettings, +}; + +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprOptionalReal { + pub enabled: RprBool, + pub value: RprReal, +} +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprOptionalU32 { + pub enabled: RprBool, + pub value: u32, +} +/// Optional boolean override. When disabled, retain the recipe's native default. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprOptionalBool { + pub enabled: RprBool, + pub value: RprBool, +} +/// Plain configuration data; initialize defaults, edit, then apply. No destructor. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprSoftBodyMaterial { + pub edgeSoftness: RprSpringCoefficients, + pub bendSoftness: RprSpringCoefficients, + pub volumeSoftness: RprSpringCoefficients, + pub shapeMatchingSoftness: RprSpringCoefficients, + pub youngModulus: RprReal, + pub poissonRatio: RprReal, + pub elasticDampingRatio: RprReal, + pub plasticYield: RprReal, + pub plasticCreep: RprReal, + pub plasticMax: RprReal, + pub deformationDamping: RprReal, + pub edgePlasticYield: RprReal, + pub edgePlasticCreep: RprReal, + pub edgePlasticMax: RprReal, + pub edgePlasticFlow: u32, + pub tearStrain: RprOptionalReal, + pub tearForce: RprOptionalReal, + pub tearSmoothing: RprReal, + pub interiorStrength: RprReal, + pub maxTearsPerStep: u32, + pub minPiece: RprOptionalU32, +} +impl From for RprSoftBodyMaterial { + fn from(value: SoftBodyMaterial) -> Self { + Self { + edgeSoftness: value.edge_softness.into(), + bendSoftness: value.bend_softness.into(), + volumeSoftness: value.volume_softness.into(), + shapeMatchingSoftness: value.shape_matching_softness.into(), + youngModulus: value.young_modulus, + poissonRatio: value.poisson_ratio, + elasticDampingRatio: value.elastic_damping_ratio, + plasticYield: value.plastic_yield, + plasticCreep: value.plastic_creep, + plasticMax: value.plastic_max, + deformationDamping: value.deformation_damping, + edgePlasticYield: value.edge_plastic_yield, + edgePlasticCreep: value.edge_plastic_creep, + edgePlasticMax: value.edge_plastic_max, + edgePlasticFlow: match value.edge_plastic_flow { + SoftEdgePlasticFlow::Both => 0, + SoftEdgePlasticFlow::Compression => 1, + SoftEdgePlasticFlow::Tension => 2, + }, + tearStrain: RprOptionalReal { + enabled: value.tear_strain.is_some() as RprBool, + value: value.tear_strain.unwrap_or_default(), + }, + tearForce: RprOptionalReal { + enabled: value.tear_force.is_some() as RprBool, + value: value.tear_force.unwrap_or_default(), + }, + tearSmoothing: value.tear_smoothing, + interiorStrength: value.interior_strength, + maxTearsPerStep: value.max_tears_per_step, + minPiece: RprOptionalU32 { + enabled: value.min_piece.is_some() as RprBool, + value: value.min_piece.unwrap_or_default(), + }, + } + } +} +impl RprSoftBodyMaterial { + pub(crate) fn raw(&self) -> Result { + Ok(SoftBodyMaterial { + edge_softness: self.edgeSoftness.raw()?, + bend_softness: self.bendSoftness.raw()?, + volume_softness: self.volumeSoftness.raw()?, + shape_matching_softness: self.shapeMatchingSoftness.raw()?, + young_modulus: nonnegative(self.youngModulus)?, + poisson_ratio: { + ensure( + (0.0..0.5).contains(&self.poissonRatio), + "Poisson ratio must be in [0, 0.5)", + )?; + self.poissonRatio + }, + elastic_damping_ratio: nonnegative(self.elasticDampingRatio)?, + plastic_yield: nonnegative(self.plasticYield)?, + plastic_creep: nonnegative(self.plasticCreep)?, + plastic_max: nonnegative(self.plasticMax)?, + deformation_damping: nonnegative(self.deformationDamping)?, + edge_plastic_yield: nonnegative(self.edgePlasticYield)?, + edge_plastic_creep: nonnegative(self.edgePlasticCreep)?, + edge_plastic_max: nonnegative(self.edgePlasticMax)?, + edge_plastic_flow: match self.edgePlasticFlow { + 0 => SoftEdgePlasticFlow::Both, + 1 => SoftEdgePlasticFlow::Compression, + 2 => SoftEdgePlasticFlow::Tension, + _ => return Err(invalid("invalid edge_plastic_flow")), + }, + tear_strain: if boolean(self.tearStrain.enabled)? { + Some(nonnegative(self.tearStrain.value)?) + } else { + None + }, + tear_force: if boolean(self.tearForce.enabled)? { + Some(nonnegative(self.tearForce.value)?) + } else { + None + }, + tear_smoothing: nonnegative(self.tearSmoothing)?, + interior_strength: nonnegative(self.interiorStrength)?, + max_tears_per_step: self.maxTearsPerStep, + min_piece: if boolean(self.minPiece.enabled)? { + Some({ + ensure(self.minPiece.value > 0, "min piece must be positive")?; + self.minPiece.value + }) + } else { + None + }, + }) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_soft_body_material() -> RprSoftBodyMaterial { + SoftBodyMaterial::default().into() +} +/// Plain configuration data; initialize defaults, edit, then apply. No destructor. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprSoftRecoverySettings { + pub authoredVelocityMargin: RprBool, + pub edgeSpeculation: RprBool, + pub invertedCellDetection: RprBool, + pub selfCrossingDetection: RprBool, + pub detectionMotionGating: RprBool, + pub crossBodyDetection: RprBool, + pub selfStandDown: RprBool, + pub crossBodyExpelGate: RprBool, + pub edgeStandDown: RprBool, + pub crossingRepulsion: RprBool, + pub crossingRepulsionGuide: RprBool, + pub crossingRepulsionSelfGuide: RprBool, + pub recoveryPace: RprReal, + pub overlapConstraints: RprBool, + pub overlapRigid: RprBool, + pub overlapSkipSelfTangled: RprBool, + pub overlapEdgeStandDown: RprBool, + pub overlapConstraintPace: RprReal, + pub overlapPatchConstraints: u32, + pub overlapSkinVolume: RprBool, + pub overlapKeptDepth: RprReal, + pub overlapSelfRegions: RprBool, + pub overlapNormalPush: RprBool, + pub overlapMultiVolume: RprBool, + pub overlapSplit: u32, + pub overlapPatience: u32, + pub overlapProgressMargin: RprReal, +} +impl From for RprSoftRecoverySettings { + fn from(value: SoftRecoverySettings) -> Self { + Self { + authoredVelocityMargin: value.authored_velocity_margin as RprBool, + edgeSpeculation: value.edge_speculation as RprBool, + invertedCellDetection: value.inverted_cell_detection as RprBool, + selfCrossingDetection: value.self_crossing_detection as RprBool, + detectionMotionGating: value.detection_motion_gating as RprBool, + crossBodyDetection: value.cross_body_detection as RprBool, + selfStandDown: value.self_stand_down as RprBool, + crossBodyExpelGate: value.cross_body_expel_gate as RprBool, + edgeStandDown: value.edge_stand_down as RprBool, + crossingRepulsion: value.crossing_repulsion as RprBool, + crossingRepulsionGuide: value.crossing_repulsion_guide as RprBool, + crossingRepulsionSelfGuide: value.crossing_repulsion_self_guide as RprBool, + recoveryPace: value.recovery_pace, + overlapConstraints: value.overlap_constraints as RprBool, + overlapRigid: value.overlap_rigid as RprBool, + overlapSkipSelfTangled: value.overlap_skip_self_tangled as RprBool, + overlapEdgeStandDown: value.overlap_edge_stand_down as RprBool, + overlapConstraintPace: value.overlap_constraint_pace, + overlapPatchConstraints: match value.overlap_patch_constraints { + SoftPatchConstraints::Keep => 0, + SoftPatchConstraints::StandDown => 1, + SoftPatchConstraints::AlongNormal => 2, + }, + overlapSkinVolume: value.overlap_skin_volume as RprBool, + overlapKeptDepth: value.overlap_kept_depth, + overlapSelfRegions: value.overlap_self_regions as RprBool, + overlapNormalPush: value.overlap_normal_push as RprBool, + overlapMultiVolume: value.overlap_multi_volume as RprBool, + overlapSplit: value.overlap_split, + overlapPatience: value.overlap_patience, + overlapProgressMargin: value.overlap_progress_margin, + } + } +} +impl RprSoftRecoverySettings { + pub(crate) fn raw(&self) -> Result { + Ok(SoftRecoverySettings { + authored_velocity_margin: boolean(self.authoredVelocityMargin)?, + edge_speculation: boolean(self.edgeSpeculation)?, + inverted_cell_detection: boolean(self.invertedCellDetection)?, + self_crossing_detection: boolean(self.selfCrossingDetection)?, + detection_motion_gating: boolean(self.detectionMotionGating)?, + cross_body_detection: boolean(self.crossBodyDetection)?, + self_stand_down: boolean(self.selfStandDown)?, + cross_body_expel_gate: boolean(self.crossBodyExpelGate)?, + edge_stand_down: boolean(self.edgeStandDown)?, + crossing_repulsion: boolean(self.crossingRepulsion)?, + crossing_repulsion_guide: boolean(self.crossingRepulsionGuide)?, + crossing_repulsion_self_guide: boolean(self.crossingRepulsionSelfGuide)?, + recovery_pace: nonnegative(self.recoveryPace)?, + overlap_constraints: boolean(self.overlapConstraints)?, + overlap_rigid: boolean(self.overlapRigid)?, + overlap_skip_self_tangled: boolean(self.overlapSkipSelfTangled)?, + overlap_edge_stand_down: boolean(self.overlapEdgeStandDown)?, + overlap_constraint_pace: nonnegative(self.overlapConstraintPace)?, + overlap_patch_constraints: match self.overlapPatchConstraints { + 0 => SoftPatchConstraints::Keep, + 1 => SoftPatchConstraints::StandDown, + 2 => SoftPatchConstraints::AlongNormal, + _ => return Err(invalid("invalid overlap_patch_constraints")), + }, + overlap_skin_volume: boolean(self.overlapSkinVolume)?, + overlap_kept_depth: nonnegative(self.overlapKeptDepth)?, + overlap_self_regions: boolean(self.overlapSelfRegions)?, + overlap_normal_push: boolean(self.overlapNormalPush)?, + overlap_multi_volume: boolean(self.overlapMultiVolume)?, + overlap_split: self.overlapSplit, + overlap_patience: self.overlapPatience, + overlap_progress_margin: nonnegative(self.overlapProgressMargin)?, + }) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_soft_recovery_settings() -> RprSoftRecoverySettings { + SoftRecoverySettings::default().into() +} +#[cfg(feature = "fem")] +/// Plain configuration data; initialize defaults, edit, then apply. No destructor. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprSoftFemParameters { + pub linearTolerance: RprReal, + pub maxLinearIterations: usize, + pub maxDenseDofs: usize, +} +#[cfg(feature = "fem")] +impl From for RprSoftFemParameters { + fn from(value: SoftFemParameters) -> Self { + Self { + linearTolerance: value.linear_tolerance, + maxLinearIterations: value.max_linear_iterations, + maxDenseDofs: value.max_dense_dofs, + } + } +} +#[cfg(feature = "fem")] +impl RprSoftFemParameters { + pub(crate) fn raw(&self) -> Result { + Ok(SoftFemParameters { + linear_tolerance: positive(self.linearTolerance)?, + max_linear_iterations: self.maxLinearIterations, + max_dense_dofs: self.maxDenseDofs, + }) + } +} +#[cfg(feature = "fem")] +#[rapier_export] +pub extern "C" fn rpr_default_soft_fem_parameters() -> RprSoftFemParameters { + SoftFemParameters::default().into() +} +/// Plain configuration data; initialize defaults, edit, then apply. No destructor. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprSoftBodiesSettings { + pub recovery: RprSoftRecoverySettings, + pub resweepStrain: RprReal, + pub maxExtraSubsteps: usize, + pub contactStiffening: RprReal, + #[cfg(feature = "fem")] + pub fem: RprSoftFemParameters, +} +impl From for RprSoftBodiesSettings { + fn from(value: SoftBodiesSettings) -> Self { + Self { + recovery: value.recovery.into(), + resweepStrain: value.resweep_strain, + maxExtraSubsteps: value.max_extra_substeps, + contactStiffening: value.contact_stiffening, + #[cfg(feature = "fem")] + fem: value.fem.into(), + } + } +} +impl RprSoftBodiesSettings { + pub(crate) fn raw(&self) -> Result { + Ok(SoftBodiesSettings { + recovery: self.recovery.raw()?, + resweep_strain: nonnegative(self.resweepStrain)?, + max_extra_substeps: self.maxExtraSubsteps, + contact_stiffening: nonnegative(self.contactStiffening)?, + #[cfg(feature = "fem")] + fem: self.fem.raw()?, + }) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_soft_bodies_settings() -> RprSoftBodiesSettings { + SoftBodiesSettings::default().into() +} +/// Plain configuration data; initialize defaults, edit, then apply. No destructor. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprIntegrationParameters { + pub dt: RprReal, + pub minCcdDt: RprReal, + pub contactSoftness: RprSpringCoefficients, + pub staticContactSoftness: RprSpringCoefficients, + pub warmstartCoefficient: RprReal, + pub lengthUnit: RprReal, + pub softBodies: RprSoftBodiesSettings, + pub normalizedAllowedLinearError: RprReal, + pub normalizedMaxCorrectiveVelocity: RprReal, + pub normalizedPredictionDistance: RprReal, + pub normalizedMaxLinearVelocity: RprReal, + pub numSolverIterations: usize, + pub numInternalPgsIterations: usize, + pub numInternalStabilizationIterations: usize, + pub maxCcdSubsteps: usize, + pub contactClustering: RprBool, + pub contactRecycling: RprBool, + pub normalizedContactRecycleDistance: RprReal, + pub frictionInBiasPass: RprBool, + pub warmstartJoints: RprBool, + #[cfg(feature = "dim3")] + pub frictionModel: u32, +} +impl From for RprIntegrationParameters { + fn from(value: IntegrationParameters) -> Self { + Self { + dt: value.dt, + minCcdDt: value.min_ccd_dt, + contactSoftness: value.contact_softness.into(), + staticContactSoftness: value.static_contact_softness.into(), + warmstartCoefficient: value.warmstart_coefficient, + lengthUnit: value.length_unit, + softBodies: value.soft_bodies.into(), + normalizedAllowedLinearError: value.normalized_allowed_linear_error, + normalizedMaxCorrectiveVelocity: value.normalized_max_corrective_velocity, + normalizedPredictionDistance: value.normalized_prediction_distance, + normalizedMaxLinearVelocity: value.normalized_max_linear_velocity, + numSolverIterations: value.num_solver_iterations, + numInternalPgsIterations: value.num_internal_pgs_iterations, + numInternalStabilizationIterations: value.num_internal_stabilization_iterations, + maxCcdSubsteps: value.max_ccd_substeps, + contactClustering: value.contact_clustering as RprBool, + contactRecycling: value.contact_recycling as RprBool, + normalizedContactRecycleDistance: value.normalized_contact_recycle_distance, + frictionInBiasPass: value.friction_in_bias_pass as RprBool, + warmstartJoints: value.warmstart_joints as RprBool, + #[cfg(feature = "dim3")] + frictionModel: match value.friction_model { + FrictionModel::Simplified => 0, + FrictionModel::Coulomb => 1, + }, + } + } +} +impl RprIntegrationParameters { + pub(crate) fn raw(&self) -> Result { + Ok(IntegrationParameters { + dt: positive(self.dt)?, + min_ccd_dt: nonnegative(self.minCcdDt)?, + contact_softness: self.contactSoftness.raw()?, + static_contact_softness: self.staticContactSoftness.raw()?, + warmstart_coefficient: nonnegative(self.warmstartCoefficient)?, + length_unit: positive(self.lengthUnit)?, + soft_bodies: self.softBodies.raw()?, + normalized_allowed_linear_error: nonnegative(self.normalizedAllowedLinearError)?, + normalized_max_corrective_velocity: nonnegative(self.normalizedMaxCorrectiveVelocity)?, + normalized_prediction_distance: nonnegative(self.normalizedPredictionDistance)?, + normalized_max_linear_velocity: nonnegative(self.normalizedMaxLinearVelocity)?, + num_solver_iterations: self.numSolverIterations, + num_internal_pgs_iterations: self.numInternalPgsIterations, + num_internal_stabilization_iterations: self.numInternalStabilizationIterations, + max_ccd_substeps: self.maxCcdSubsteps, + contact_clustering: boolean(self.contactClustering)?, + contact_recycling: boolean(self.contactRecycling)?, + normalized_contact_recycle_distance: nonnegative( + self.normalizedContactRecycleDistance, + )?, + friction_in_bias_pass: boolean(self.frictionInBiasPass)?, + warmstart_joints: boolean(self.warmstartJoints)?, + #[cfg(feature = "dim3")] + friction_model: match self.frictionModel { + 0 => FrictionModel::Simplified, + 1 => FrictionModel::Coulomb, + _ => return Err(invalid("invalid friction_model")), + }, + }) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_integration_parameters() -> RprIntegrationParameters { + IntegrationParameters::default().into() +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_integration_parameters( + world: *const RprWorld, +) -> RprIntegrationParameters { + ffi_value(|out: *mut RprIntegrationParameters| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + output(out, get(object)?.0.into()) + }) + }) +} + +/// Copies validated values; does not expose a writable alias to Rust memory. +#[rapier_export] +pub unsafe extern "C" fn rpr_set_integration_parameters( + world: *mut RprWorld, + data: *const RprIntegrationParameters, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + crate::handle_access::forward(native_integration_parameters_set_data( + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(), + data, + )) + }) +} + +pub(crate) unsafe fn native_integration_parameters_set_data( + object: *mut NativeIntegrationParameters, + data: *const RprIntegrationParameters, +) -> RprStatus { + ffi(|| unsafe { + let value = get(data)?.raw()?; + get_mut(object)?.0 = value; + Ok(()) + }) +} + +/// Copies the live soft body's material into caller-owned data. +pub(crate) unsafe fn native_soft_body_read_material( + body: *const RprSoftBody, + out: *mut RprSoftBodyMaterial, +) -> RprStatus { + ffi(|| unsafe { output(out, (*get(body)?.0.material()).into()) }) +} +/// Validates all fields before applying the material to a live soft body. +pub(crate) unsafe fn native_soft_body_set_material_data( + body: *mut RprSoftBody, + data: *const RprSoftBodyMaterial, +) -> RprStatus { + ffi(|| unsafe { + let material = get(data)?.raw()?; + get_mut(body)?.0.set_material(material); + Ok(()) + }) +} diff --git a/c/src/control.rs b/c/src/control.rs new file mode 100644 index 000000000..ef733e531 --- /dev/null +++ b/c/src/control.rs @@ -0,0 +1,745 @@ +use crate::*; +use rapier::control::{ + CharacterAutostep, CharacterCollision, CharacterLength, KinematicCharacterController, +}; +/// Controller plus reusable collision output from the last move_shape call. +pub struct RprKinematicCharacterController { + world: *mut RprWorld, + inner: KinematicCharacterController, + collisions: Vec, +} +/// CharacterLength counterpart: relative=1 scales with character height, relative=0 uses world units. +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprCharacterLength { + pub value: RprReal, + pub relative: RprBool, +} +impl RprCharacterLength { + fn raw(self) -> Result { + nonnegative(self.value)?; + Ok(if boolean(self.relative)? { + CharacterLength::Relative(self.value) + } else { + CharacterLength::Absolute(self.value) + }) + } +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprCharacterMovement { + pub translation: RprVector, + pub grounded: RprBool, + pub is_sliding_down_slope: RprBool, +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprCharacterCollision { + pub collider: RprColliderHandle, + pub character_pos: RprPose, + pub translation_applied: RprVector, + pub translation_remaining: RprVector, + pub hit: RprShapeCastHit, +} +#[rapier_export] +pub unsafe extern "C" fn rpr_new_kinematic_character_controller() +-> *mut RprKinematicCharacterController { + ffi_value(|out: *mut *mut RprKinematicCharacterController| { + ffi(|| unsafe { + out_ptr(out)?; + output( + out, + Box::into_raw(Box::new(RprKinematicCharacterController { + world: std::ptr::null_mut(), + inner: Default::default(), + collisions: Vec::new(), + })), + ) + }) + }) +} +#[rapier_export] +pub unsafe extern "C" fn rpr_free_kinematic_character_controller( + controller: *mut RprKinematicCharacterController, +) -> RprStatus { + ffi(|| unsafe { + if !controller.is_null() { + get(controller)?; + drop(Box::from_raw(controller)); + } + Ok(()) + }) +} +#[rapier_export(kinematic_character_controller)] +pub unsafe extern "C" fn rpr_kinematic_character_controller_set_up( + controller: *mut RprKinematicCharacterController, + up: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let v = up.raw()?; + positive(v.length())?; + get_mut(controller)?.inner.up = v.normalize(); + Ok(()) + }) +} +#[rapier_export(kinematic_character_controller)] +pub unsafe extern "C" fn rpr_kinematic_character_controller_set_offset( + controller: *mut RprKinematicCharacterController, + offset: RprCharacterLength, +) -> RprStatus { + ffi(|| unsafe { + positive(offset.value)?; + let offset = offset.raw()?; + get_mut(controller)?.inner.offset = offset; + Ok(()) + }) +} +#[rapier_export(kinematic_character_controller)] +pub unsafe extern "C" fn rpr_kinematic_character_controller_set_slide( + controller: *mut RprKinematicCharacterController, + enabled: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let v = boolean(enabled)?; + get_mut(controller)?.inner.slide = v; + Ok(()) + }) +} +#[rapier_export(kinematic_character_controller)] +pub unsafe extern "C" fn rpr_kinematic_character_controller_set_slopes( + controller: *mut RprKinematicCharacterController, + max_climb_angle: RprReal, + min_slide_angle: RprReal, +) -> RprStatus { + ffi(|| unsafe { + for a in [max_climb_angle, min_slide_angle] { + nonnegative(a)?; + ensure(a <= 2.0 * real_pi(), "angle must be <= 2*pi")?; + } + let c = get_mut(controller)?; + c.inner.max_slope_climb_angle = max_climb_angle; + c.inner.min_slope_slide_angle = min_slide_angle; + Ok(()) + }) +} +#[rapier_export(kinematic_character_controller)] +pub unsafe extern "C" fn rpr_kinematic_character_controller_set_autostep( + controller: *mut RprKinematicCharacterController, + enabled: RprBool, + max_height: RprCharacterLength, + min_width: RprCharacterLength, + include_dynamic_bodies: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let step = CharacterAutostep { + max_height: max_height.raw()?, + min_width: min_width.raw()?, + include_dynamic_bodies: boolean(include_dynamic_bodies)?, + }; + let value = boolean(enabled)?.then_some(step); + get_mut(controller)?.inner.autostep = value; + Ok(()) + }) +} +#[rapier_export(kinematic_character_controller)] +pub unsafe extern "C" fn rpr_kinematic_character_controller_set_snap_to_ground( + controller: *mut RprKinematicCharacterController, + enabled: RprBool, + distance: RprCharacterLength, +) -> RprStatus { + ffi(|| unsafe { + let d = distance.raw()?; + let value = boolean(enabled)?.then_some(d); + get_mut(controller)?.inner.snap_to_ground = value; + Ok(()) + }) +} +/// Computes movement without moving any collider. Use the returned translation to set the character target. +#[rapier_export(kinematic_character_controller)] +pub unsafe extern "C" fn rpr_kinematic_character_controller_move_shape( + world: *const RprWorld, + options: *const RprQueryOptions, + controller: *mut RprKinematicCharacterController, + dt: RprReal, + shape: *const RprSharedShape, + pose: RprPose, + desired_translation: RprVector, +) -> RprCharacterMovement { + ffi_value(|out: *mut RprCharacterMovement| { + ffi(|| unsafe { + if !options.is_null() { + get(options)?.check_world(world)?; + } + let access = get(world)?.read()?; + let raw = access.raw(); + let query = QueryAccess::from_world(world as *mut RprWorld, raw, options)?; + let query: *const QueryAccess = &query; + + out_ptr(out)?; + positive(dt)?; + let pose = pose.raw()?; + let translation = desired_translation.raw()?; + let shape = &*get(shape)?.0; + let c = get_mut(controller)?; + c.world = world as *mut RprWorld; + c.collisions.clear(); + let result = get(query)?.with_raw(|q| { + Ok(c.inner + .move_shape(dt, &q, shape, &pose, translation, |hit| { + c.collisions.push(hit) + })) + })?; + output( + out, + RprCharacterMovement { + translation: result.translation.into(), + grounded: result.grounded as u32, + is_sliding_down_slope: result.is_sliding_down_slope as u32, + }, + ) + }) + }) +} + +#[rapier_export(kinematic_character_controller)] +pub unsafe extern "C" fn rpr_kinematic_character_controller_collisions( + controller: *const RprKinematicCharacterController, + buffer: *mut RprCharacterCollision, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array( + get(controller).map_or(std::ptr::null_mut(), |c| c.world), + buffer, + capacity, + |count: *mut usize| { + ffi(|| { + let values: Vec<_> = get(controller)? + .collisions + .iter() + .map(|c| RprCharacterCollision { + collider: c.handle.into(), + character_pos: c.character_pos.into(), + translation_applied: c.translation_applied.into(), + translation_remaining: c.translation_remaining.into(), + hit: RprShapeCastHit { + collider: c.handle.into(), + time_of_impact: c.hit.time_of_impact, + witness1: c.hit.witness1.into(), + witness2: c.hit.witness2.into(), + normal1: c.hit.normal1.into(), + normal2: c.hit.normal2.into(), + status: c.hit.status as u32, + }, + }) + .collect(); + copy_out(&values, buffer, capacity, count) + }) + }, + ) + } +} +/// Applies impulses for the most recent move_shape collisions. Use the same world, shape, dt and filter. +#[rapier_export(kinematic_character_controller)] +pub unsafe extern "C" fn rpr_kinematic_character_controller_solve_character_collision_impulses( + controller: *const RprKinematicCharacterController, + shape: *const RprSharedShape, + dt: RprReal, + mass: RprReal, + filter: *const RprQueryFilter, +) -> RprStatus { + ffi(|| unsafe { + let world = get(controller)?.world; + if !filter.is_null() { + get(filter)?.check_world(world)?; + } + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + positive(dt)?; + positive(mass)?; + let f = if filter.is_null() { + RprQueryFilter::default() + } else { + *get(filter)? + } + .raw()?; + let c = get(controller)?; + let shape = &*get(shape)?.0; + let w = &mut get_mut(world)?.0; + let mut q = w.broad_phase.as_query_pipeline_mut( + w.narrow_phase.query_dispatcher(), + &mut w.bodies, + &mut w.colliders, + f, + ); + c.inner + .solve_character_collision_impulses(dt, &mut q, shape, mass, &c.collisions); + Ok(()) + }) +} + +#[cfg(feature = "dim3")] +mod vehicle { + use super::*; + use rapier::control::{DynamicRayCastVehicleController, WheelTuning}; + pub struct RprDynamicRayCastVehicleController(DynamicRayCastVehicleController, *mut RprWorld); + #[repr(C)] + #[derive(Copy, Clone, Default)] + pub struct RprWheelTuning { + pub suspension_stiffness: RprReal, + pub suspension_compression: RprReal, + pub suspension_damping: RprReal, + pub max_suspension_travel: RprReal, + pub friction_slip: RprReal, + pub max_suspension_force: RprReal, + pub side_friction_stiffness: RprReal, + } + impl RprWheelTuning { + fn raw(self) -> Result { + Ok(WheelTuning { + suspension_stiffness: nonnegative(self.suspension_stiffness)?, + suspension_compression: nonnegative(self.suspension_compression)?, + suspension_damping: nonnegative(self.suspension_damping)?, + max_suspension_travel: nonnegative(self.max_suspension_travel)?, + friction_slip: nonnegative(self.friction_slip)?, + max_suspension_force: nonnegative(self.max_suspension_force)?, + side_friction_stiffness: nonnegative(self.side_friction_stiffness)?, + }) + } + } + #[repr(C)] + #[derive(Copy, Clone, Default)] + pub struct RprWheelState { + pub center: RprVector, + pub suspension: RprVector, + pub axle: RprVector, + pub rotation: RprReal, + pub suspension_force: RprReal, + pub suspension_length: RprReal, + pub is_in_contact: RprBool, + pub ground_object: RprColliderHandle, + pub contact_point: RprVector, + pub contact_normal: RprVector, + } + #[rapier_export] + pub extern "C" fn rpr_default_wheel_tuning() -> RprWheelTuning { + let t = WheelTuning::default(); + RprWheelTuning { + suspension_stiffness: t.suspension_stiffness, + suspension_compression: t.suspension_compression, + suspension_damping: t.suspension_damping, + max_suspension_travel: t.max_suspension_travel, + friction_slip: t.friction_slip, + max_suspension_force: t.max_suspension_force, + side_friction_stiffness: t.side_friction_stiffness, + } + } + #[rapier_export] + pub unsafe extern "C" fn rpr_new_dynamic_ray_cast_vehicle_controller( + chassis: RprRigidBodyHandle, + ) -> *mut RprDynamicRayCastVehicleController { + let world = chassis.world; + ffi_value(|out: *mut *mut RprDynamicRayCastVehicleController| { + ffi(|| unsafe { + chassis.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let bodies: *const RprRigidBodySet = std::ptr::addr_of!((*raw).0.bodies).cast(); + + out_ptr(out)?; + let b = get(bodies)?.0.get(chassis.raw()).ok_or_else(missing)?; + ensure( + b.is_dynamic() && b.soft_body().is_none(), + "vehicle chassis must be an ordinary dynamic body", + )?; + output( + out, + Box::into_raw(Box::new(RprDynamicRayCastVehicleController( + DynamicRayCastVehicleController::new(chassis.raw()), + world, + ))), + ) + }) + }) + } + + #[rapier_export] + pub unsafe extern "C" fn rpr_free_dynamic_ray_cast_vehicle_controller( + controller: *mut RprDynamicRayCastVehicleController, + ) -> RprStatus { + ffi(|| unsafe { + if !controller.is_null() { + get(controller)?; + drop(Box::from_raw(controller)); + } + Ok(()) + }) + } + #[rapier_export(dynamic_ray_cast_vehicle_controller)] + pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_add_wheel( + controller: *mut RprDynamicRayCastVehicleController, + connection: RprVector, + direction: RprVector, + axle: RprVector, + rest_length: RprReal, + radius: RprReal, + tuning: *const RprWheelTuning, + ) -> usize { + ffi_value(|out_index: *mut usize| { + ffi(|| unsafe { + out_ptr(out_index)?; + let p = connection.raw()?; + let d = direction.raw()?; + let a = axle.raw()?; + positive(d.length())?; + positive(a.length())?; + ensure( + d.cross(a).length_squared() > 1.0e-10, + "wheel direction and axle must not be parallel", + )?; + nonnegative(rest_length)?; + positive(radius)?; + let t = get(tuning)?.raw()?; + let c = &mut get_mut(controller)?.0; + let index = c.wheels().len(); + c.add_wheel(p, d.normalize(), a.normalize(), rest_length, radius, &t); + output(out_index, index) + }) + }) + } + #[rapier_export(dynamic_ray_cast_vehicle_controller)] + pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_set_axes( + controller: *mut RprDynamicRayCastVehicleController, + up: usize, + forward: usize, + ) -> RprStatus { + ffi(|| unsafe { + ensure( + up < 3 && forward < 3 && up != forward, + "invalid vehicle axes", + )?; + let c = &mut get_mut(controller)?.0; + c.index_up_axis = up; + c.index_forward_axis = forward; + Ok(()) + }) + } + #[rapier_export(dynamic_ray_cast_vehicle_controller)] + pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_set_wheel_controls( + controller: *mut RprDynamicRayCastVehicleController, + index: usize, + steering: RprReal, + engine_force: RprReal, + brake: RprReal, + ) -> RprStatus { + ffi(|| unsafe { + finite(steering)?; + finite(engine_force)?; + nonnegative(brake)?; + let w = get_mut(controller)? + .0 + .wheels_mut() + .get_mut(index) + .ok_or_else(|| invalid("wheel index out of range"))?; + w.steering = steering; + w.engine_force = engine_force; + w.brake = brake; + Ok(()) + }) + } + #[rapier_export(dynamic_ray_cast_vehicle_controller)] + pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_update_vehicle( + controller: *mut RprDynamicRayCastVehicleController, + dt: RprReal, + filter: *const RprQueryFilter, + ) -> RprStatus { + ffi(|| unsafe { + let world = get(controller)?.1; + if !filter.is_null() { + get(filter)?.check_world(world)?; + } + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + positive(dt)?; + let c = &mut get_mut(controller)?.0; + let mut f = if filter.is_null() { + RprQueryFilter::default() + } else { + *get(filter)? + } + .raw()?; + f.exclude_rigid_body = Some(c.chassis); + let w = &mut get_mut(world)?.0; + let chassis = w.bodies.get(c.chassis).ok_or_else(missing)?; + ensure( + chassis.is_dynamic() && chassis.soft_body().is_none(), + "vehicle chassis must be an ordinary dynamic body", + )?; + let q = w.broad_phase.as_query_pipeline_mut( + w.narrow_phase.query_dispatcher(), + &mut w.bodies, + &mut w.colliders, + f, + ); + c.update_vehicle(dt, q); + Ok(()) + }) + } + + #[rapier_export(dynamic_ray_cast_vehicle_controller)] + pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_current_vehicle_speed( + controller: *const RprDynamicRayCastVehicleController, + ) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { output(out, get(controller)?.0.current_vehicle_speed) }) + }) + } + #[rapier_export(dynamic_ray_cast_vehicle_controller)] + pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_wheels( + controller: *const RprDynamicRayCastVehicleController, + buffer: *mut RprWheelState, + capacity: usize, + ) -> usize { + unsafe { + ffi_world_array( + get(controller).map_or(std::ptr::null_mut(), |c| c.1), + buffer, + capacity, + |count: *mut usize| { + ffi(|| { + let v: Vec<_> = get(controller)? + .0 + .wheels() + .iter() + .map(|w| { + let r = w.raycast_info(); + RprWheelState { + center: w.center().into(), + suspension: w.suspension().into(), + axle: w.axle().into(), + rotation: w.rotation, + suspension_force: w.wheel_suspension_force, + suspension_length: r.suspension_length, + is_in_contact: r.is_in_contact as u32, + ground_object: r + .ground_object + .map(Into::into) + .unwrap_or_default(), + contact_point: r.contact_point_ws.into(), + contact_normal: r.contact_normal_ws.into(), + } + }) + .collect(); + copy_out(&v, buffer, capacity, count) + }) + }, + ) + } + } +} +#[cfg(feature = "dim3")] +pub use vehicle::*; + +/// PID controller with persistent integral state. +pub struct RprPidController(rapier::control::PidController); + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprPidGains { + pub lin_kp: RprVector, + pub lin_ki: RprVector, + pub lin_kd: RprVector, + pub ang_kp: RprAngVector, + pub ang_ki: RprAngVector, + pub ang_kd: RprAngVector, +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_new_pid_controller() -> *mut RprPidController { + ffi_value(|out: *mut *mut RprPidController| { + ffi(|| unsafe { + out_ptr(out)?; + output( + out, + Box::into_raw(Box::new(RprPidController(Default::default()))), + ) + }) + }) +} +#[rapier_export] +pub unsafe extern "C" fn rpr_free_pid_controller(controller: *mut RprPidController) -> RprStatus { + ffi(|| unsafe { + if !controller.is_null() { + drop(Box::from_raw(controller)); + } + Ok(()) + }) +} +#[rapier_export(pid_controller)] +pub unsafe extern "C" fn rpr_pid_controller_gains( + controller: *const RprPidController, +) -> RprPidGains { + ffi_value(|out: *mut RprPidGains| { + ffi(|| unsafe { + let c = &get(controller)?.0; + output( + out, + RprPidGains { + lin_kp: c.pd.lin_kp.into(), + lin_ki: c.lin_ki.into(), + lin_kd: c.pd.lin_kd.into(), + ang_kp: angular_out(c.pd.ang_kp), + ang_ki: angular_out(c.ang_ki), + ang_kd: angular_out(c.pd.ang_kd), + }, + ) + }) + }) +} +#[rapier_export(pid_controller)] +pub unsafe extern "C" fn rpr_pid_controller_set_gains( + controller: *mut RprPidController, + gains: RprPidGains, +) -> RprStatus { + ffi(|| unsafe { + let lin_kp = gains.lin_kp.raw()?; + let lin_ki = gains.lin_ki.raw()?; + let lin_kd = gains.lin_kd.raw()?; + let ang_kp = angular(gains.ang_kp)?; + let ang_ki = angular(gains.ang_ki)?; + let ang_kd = angular(gains.ang_kd)?; + let c = &mut get_mut(controller)?.0; + c.pd.lin_kp = lin_kp; + c.lin_ki = lin_ki; + c.pd.lin_kd = lin_kd; + c.pd.ang_kp = ang_kp; + c.ang_ki = ang_ki; + c.pd.ang_kd = ang_kd; + Ok(()) + }) +} +/// AxesMask bits match Rapier: linear X/Y/Z are 1/2/4, angular X/Y/Z are 8/16/32. +#[rapier_export(pid_controller)] +pub unsafe extern "C" fn rpr_pid_controller_set_axes( + controller: *mut RprPidController, + axes: u32, +) -> RprStatus { + ffi(|| unsafe { + let axes = u8::try_from(axes) + .ok() + .and_then(AxesMask::from_bits) + .ok_or_else(|| invalid("unknown PID axes"))?; + get_mut(controller)?.0.set_axes(axes); + Ok(()) + }) +} +/// Compute a velocity correction, preserving the body's state and updating PID integrals. +#[rapier_export(pid_controller)] +pub unsafe extern "C" fn rpr_pid_controller_rigid_body_correction( + controller: *mut RprPidController, + dt: RprReal, + body: RprRigidBodyHandle, + target_pose: RprPose, + target_linvel: RprVector, + target_angvel: RprAngVector, +) -> RprVelocityCorrection { + let world = body.world; + ffi_value(|result: *mut RprVelocityCorrection| { + let linear = unsafe { std::ptr::addr_of_mut!((*result).linear) }; + let angular_velocity = unsafe { std::ptr::addr_of_mut!((*result).angularVelocity) }; + + ffi(|| unsafe { + body.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_pid_controller_rigid_body_correction( + controller, + dt, + std::ptr::addr_of!((*raw).0.bodies).cast(), + body, + target_pose, + target_linvel, + target_angvel, + linear, + angular_velocity, + )) + }) + }) +} + +pub(crate) unsafe fn native_pid_controller_rigid_body_correction( + controller: *mut RprPidController, + dt: RprReal, + bodies: *const RprRigidBodySet, + body: RprRigidBodyHandle, + target_pose: RprPose, + target_linvel: RprVector, + target_angvel: RprAngVector, + linear: *mut RprVector, + angular_velocity: *mut RprAngVector, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(linear)?; + out_ptr(angular_velocity)?; + let dt = positive(dt)?; + let pose = target_pose.raw()?; + let velocity = RigidBodyVelocity { + linvel: target_linvel.raw()?, + angvel: angular(target_angvel)?, + }; + let correction = get_mut(controller)?.0.rigid_body_correction( + dt, + get(bodies)?.0.get(body.raw()).ok_or_else(missing)?, + pose, + velocity, + ); + output(linear, correction.linvel.into())?; + output(angular_velocity, angular_out(correction.angvel)) + }) +} + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprCharacterControllerSettings { + pub slide: RprBool, + pub max_slope_climb_angle: RprReal, + pub min_slope_slide_angle: RprReal, + pub snap_to_ground: RprBool, + pub snap_distance: RprCharacterLength, +} +#[rapier_export(kinematic_character_controller)] +pub unsafe extern "C" fn rpr_kinematic_character_controller_settings( + controller: *const RprKinematicCharacterController, +) -> RprCharacterControllerSettings { + ffi_value(|out: *mut RprCharacterControllerSettings| { + ffi(|| unsafe { + let c = &get(controller)?.inner; + let snap_distance = match c.snap_to_ground { + Some(CharacterLength::Relative(value)) => RprCharacterLength { value, relative: 1 }, + Some(CharacterLength::Absolute(value)) => RprCharacterLength { value, relative: 0 }, + None => RprCharacterLength { + value: 0.1, + relative: 1, + }, + }; + output( + out, + RprCharacterControllerSettings { + slide: c.slide as u32, + max_slope_climb_angle: c.max_slope_climb_angle, + min_slope_slide_angle: c.min_slope_slide_angle, + snap_to_ground: c.snap_to_ground.is_some() as u32, + snap_distance, + }, + ) + }) + }) +} diff --git a/c/src/descriptors.rs b/c/src/descriptors.rs new file mode 100644 index 000000000..a41842529 --- /dev/null +++ b/c/src/descriptors.rs @@ -0,0 +1,597 @@ +//! Caller-owned construction descriptions. Pointers borrow input only until build/insert returns. +#![allow(non_snake_case)] +use crate::*; + +/// Stack-allocated rigid-body construction data. Initialize with RigidBodyDescInit. +/// Copying this value is safe; it owns no resources and must never be freed by Rapier. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprRigidBodyDesc { + pub position: RprPose, + pub linvel: RprVector, + pub angvel: RprAngVector, + pub bodyType: u32, + pub gravityScale: RprReal, + pub linearDamping: RprReal, + pub angularDamping: RprReal, + pub additionalMass: RprReal, + pub useAdditionalMassProperties: RprBool, + pub additionalMassProperties: RprMassProperties, + pub lockedAxes: u8, + pub canSleep: RprBool, + pub sleeping: RprBool, + pub ccdEnabled: RprBool, + pub softCcdPrediction: RprReal, + pub allowFastRotation: RprBool, + pub enabled: RprBool, + pub dominanceGroup: i8, + pub additionalSolverIterations: usize, + pub additionalPgsIterations: usize, + pub gyroscopicForcesEnabled: RprBool, + pub userData: RprUserData, +} +impl RprRigidBodyDesc { + fn new(kind: u32) -> Result { + let b = RigidBodyBuilder::new(body_type(kind)?); + Ok(Self { + position: b.position.into(), + linvel: b.linvel.into(), + angvel: angular_out(b.angvel), + bodyType: kind, + gravityScale: b.gravity_scale, + linearDamping: b.linear_damping, + angularDamping: b.angular_damping, + additionalMass: 0.0, + useAdditionalMassProperties: 0, + additionalMassProperties: MassProperties::default().into(), + lockedAxes: 0, + canSleep: b.can_sleep as _, + sleeping: b.sleeping as _, + ccdEnabled: b.ccd_enabled as _, + softCcdPrediction: b.soft_ccd_prediction, + allowFastRotation: b.allow_fast_rotation as _, + enabled: b.enabled as _, + dominanceGroup: b.dominance_group, + additionalSolverIterations: b.additional_solver_iterations, + additionalPgsIterations: b.additional_pgs_iterations, + gyroscopicForcesEnabled: b.gyroscopic_forces_enabled as _, + userData: b.user_data.into(), + }) + } + pub(crate) fn raw(&self) -> Result { + let axes = + LockedAxes::from_bits(self.lockedAxes).ok_or_else(|| invalid("unknown locked axes"))?; + let mut b = RigidBodyBuilder::new(body_type(self.bodyType)?) + .pose(self.position.raw()?) + .linvel(self.linvel.raw()?) + .angvel(angular(self.angvel)?) + .gravity_scale(finite(self.gravityScale)?) + .linear_damping(nonnegative(self.linearDamping)?) + .angular_damping(nonnegative(self.angularDamping)?) + .locked_axes(axes) + .can_sleep(boolean(self.canSleep)?) + .sleeping(boolean(self.sleeping)?) + .ccd_enabled(boolean(self.ccdEnabled)?) + .soft_ccd_prediction(nonnegative(self.softCcdPrediction)?) + .allow_fast_rotation(boolean(self.allowFastRotation)?) + .enabled(boolean(self.enabled)?) + .dominance_group(self.dominanceGroup) + .additional_solver_iterations(self.additionalSolverIterations) + .additional_pgs_iterations(self.additionalPgsIterations) + .user_data(self.userData.raw()); + b = if boolean(self.useAdditionalMassProperties)? { + b.additional_mass_properties(self.additionalMassProperties.raw()?) + } else { + b.additional_mass(nonnegative(self.additionalMass)?) + }; + let gyro = boolean(self.gyroscopicForcesEnabled)?; + #[cfg(feature = "dim3")] + { + b = b.gyroscopic_forces_enabled(gyro); + } + #[cfg(feature = "dim2")] + let _ = gyro; + Ok(b) + } +} +#[rapier_export] +pub extern "C" fn rpr_dynamic_rigid_body_desc() -> RprRigidBodyDesc { + RprRigidBodyDesc::new(RPR_DYNAMIC).expect("valid body kind") +} + +#[rapier_export] +pub extern "C" fn rpr_fixed_rigid_body_desc() -> RprRigidBodyDesc { + RprRigidBodyDesc::new(RPR_FIXED).expect("valid body kind") +} + +#[rapier_export] +pub extern "C" fn rpr_kinematic_position_based_rigid_body_desc() -> RprRigidBodyDesc { + RprRigidBodyDesc::new(RPR_KINEMATIC_POSITION_BASED).expect("valid body kind") +} + +#[rapier_export] +pub extern "C" fn rpr_kinematic_velocity_based_rigid_body_desc() -> RprRigidBodyDesc { + RprRigidBodyDesc::new(RPR_KINEMATIC_VELOCITY_BASED).expect("valid body kind") +} + +#[cfg(feature = "dim2")] +pub const RPR_POLYLINE_ORIENTED: u32 = 1; +pub const RPR_POLYLINE_DEFORMABLE: u32 = 2; + +pub const RPR_SHAPE_DESC_BALL: u32 = 0; +pub const RPR_SHAPE_DESC_CUBOID: u32 = 1; +pub const RPR_SHAPE_DESC_ROUND_CUBOID: u32 = 2; +pub const RPR_SHAPE_DESC_CAPSULE: u32 = 3; +pub const RPR_SHAPE_DESC_SEGMENT: u32 = 4; +pub const RPR_SHAPE_DESC_TRIANGLE: u32 = 5; +pub const RPR_SHAPE_DESC_HALFSPACE: u32 = 6; +pub const RPR_SHAPE_DESC_CONVEX_HULL: u32 = 7; +pub const RPR_SHAPE_DESC_TRIMESH: u32 = 8; +pub const RPR_SHAPE_DESC_POLYLINE: u32 = 9; +pub const RPR_SHAPE_DESC_SHARED: u32 = 10; +pub const RPR_SHAPE_DESC_HEIGHTFIELD: u32 = 11; +pub const RPR_SHAPE_DESC_CYLINDER: u32 = 12; +pub const RPR_SHAPE_DESC_CONE: u32 = 13; +pub const RPR_SHAPE_DESC_COMPOUND: u32 = 14; +pub const RPR_SHAPE_DESC_ROUND_CYLINDER: u32 = 15; + +/// Non-owning shape description. Only fields selected by kind are read. +/// a = cuboid half extents, capsule/segment endpoint, triangle vertex, or halfspace normal. +/// b/c = remaining endpoints/vertices. radius is also the rounded-cuboid border radius. +/// Mesh views count edges or triangles; heightfields are column-major. +/// Arrays, compound children, and sharedShape remain borrowed until build/insert returns. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct RprShapeDesc { + pub kind: u32, + pub a: RprVector, + pub b: RprVector, + pub c: RprVector, + pub radius: RprReal, + pub halfHeight: RprReal, + pub borderRadius: RprReal, + pub vertices: RprVectorView, + pub triangles: RprTriangleView, + pub edges: RprEdgeView, + pub flags: u32, + pub heights: RprRealView, + pub rows: usize, + pub columns: usize, + pub scale: RprVector, + pub sharedShape: *const RprSharedShape, + pub children: RprCompoundShapeView, +} +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprCompoundShapeDesc { + pub pose: RprPose, + pub shape: RprShapeDesc, +} +impl Default for RprShapeDesc { + fn default() -> Self { + Self { + kind: RPR_SHAPE_DESC_BALL, + a: Vector::ZERO.into(), + b: Vector::ZERO.into(), + c: Vector::ZERO.into(), + radius: 0.5, + halfHeight: 0.5, + borderRadius: 0.0, + vertices: RprVectorView::default(), + triangles: RprTriangleView::default(), + edges: RprEdgeView::default(), + flags: 0, + heights: RprRealView::default(), + rows: 0, + columns: 0, + scale: Vector::ONE.into(), + sharedShape: std::ptr::null(), + children: RprCompoundShapeView::default(), + } + } +} +impl RprShapeDesc { + pub(crate) unsafe fn raw(&self) -> Result { + unsafe { self.raw_depth(0) } + } + unsafe fn raw_depth(&self, depth: usize) -> Result { + ensure(depth < 32, "compound shape nesting exceeds 32 levels")?; + Ok(match self.kind { + RPR_SHAPE_DESC_BALL => SharedShape::ball(positive(self.radius)?), + RPR_SHAPE_DESC_CUBOID | RPR_SHAPE_DESC_ROUND_CUBOID => { + let v = self.a.raw()?; + ensure(v.min_element() > 0.0, "half extents must be positive")?; + if self.kind == RPR_SHAPE_DESC_CUBOID { + #[cfg(feature = "dim2")] + { + SharedShape::cuboid(v.x, v.y) + } + #[cfg(feature = "dim3")] + { + SharedShape::cuboid(v.x, v.y, v.z) + } + } else { + let r = nonnegative(self.radius)?; + #[cfg(feature = "dim2")] + { + SharedShape::round_cuboid(v.x, v.y, r) + } + #[cfg(feature = "dim3")] + { + SharedShape::round_cuboid(v.x, v.y, v.z, r) + } + } + } + RPR_SHAPE_DESC_CAPSULE => { + SharedShape::capsule(self.a.raw()?, self.b.raw()?, positive(self.radius)?) + } + RPR_SHAPE_DESC_SEGMENT => SharedShape::segment(self.a.raw()?, self.b.raw()?), + RPR_SHAPE_DESC_TRIANGLE => { + SharedShape::triangle(self.a.raw()?, self.b.raw()?, self.c.raw()?) + } + RPR_SHAPE_DESC_HALFSPACE => { + let n = self.a.raw()?; + positive(n.length())?; + SharedShape::halfspace(n.normalize()) + } + #[cfg(feature = "dim3")] + RPR_SHAPE_DESC_ROUND_CYLINDER => SharedShape::round_cylinder( + positive(self.halfHeight)?, + positive(self.radius)?, + nonnegative(self.borderRadius)?, + ), + RPR_SHAPE_DESC_SHARED => unsafe { get(self.sharedShape)?.0.clone() }, + RPR_SHAPE_DESC_COMPOUND => { + let mut shapes = Vec::new(); + for child in unsafe { input(self.children.data, self.children.count)? } { + shapes.push((child.pose.raw()?, unsafe { + child.shape.raw_depth(depth + 1)? + })); + } + ensure(!shapes.is_empty(), "empty compound shape")?; + SharedShape::compound(shapes) + } + RPR_SHAPE_DESC_CONVEX_HULL | RPR_SHAPE_DESC_TRIMESH | RPR_SHAPE_DESC_POLYLINE => { + let vertices: Vec<_> = unsafe { input(self.vertices.data, self.vertices.count)? } + .iter() + .map(|v| v.raw()) + .collect::>()?; + ensure(!vertices.is_empty(), "empty vertices")?; + if self.kind == RPR_SHAPE_DESC_CONVEX_HULL { + SharedShape::convex_hull(&vertices) + .ok_or_else(|| invalid("degenerate convex hull"))? + } else { + let (data, count, arity) = if self.kind == RPR_SHAPE_DESC_TRIMESH { + (self.triangles.data.cast::(), self.triangles.count, 3) + } else { + (self.edges.data.cast::(), self.edges.count, 2) + }; + let count = count + .checked_mul(arity) + .ok_or_else(|| invalid("index count overflow"))?; + let indices = unsafe { input(data, count)? }; + ensure( + indices.iter().all(|i| (*i as usize) < vertices.len()), + "mesh index out of bounds", + )?; + if self.kind == RPR_SHAPE_DESC_TRIMESH { + let flags = rapier::parry::shape::TriMeshFlags::from_bits( + u16::try_from(self.flags) + .map_err(|_| invalid("unknown trimesh flags"))?, + ) + .ok_or_else(|| invalid("unknown trimesh flags"))?; + SharedShape::trimesh_with_flags( + vertices, + indices + .chunks_exact(3) + .map(|v| [v[0], v[1], v[2]]) + .collect(), + flags, + ) + .map_err(|e| invalid(e.to_string()))? + } else { + let flags = rapier::parry::shape::PolylineFlags::from_bits( + u8::try_from(self.flags) + .map_err(|_| invalid("unknown polyline flags"))?, + ) + .ok_or_else(|| invalid("unknown polyline flags"))?; + SharedShape::new(rapier::parry::shape::Polyline::with_flags( + vertices, + if indices.is_empty() { + None + } else { + Some(indices.chunks_exact(2).map(|v| [v[0], v[1]]).collect()) + }, + flags, + )) + } + } + } + #[cfg(feature = "dim3")] + RPR_SHAPE_DESC_CYLINDER => { + SharedShape::cylinder(positive(self.halfHeight)?, positive(self.radius)?) + } + #[cfg(feature = "dim3")] + RPR_SHAPE_DESC_CONE => { + SharedShape::cone(positive(self.halfHeight)?, positive(self.radius)?) + } + RPR_SHAPE_DESC_HEIGHTFIELD => { + let count = self + .rows + .checked_mul(self.columns) + .ok_or_else(|| invalid("heightfield size overflow"))?; + ensure( + self.heights.count == count, + "heightfield data length does not match dimensions", + )?; + let heights = unsafe { input(self.heights.data, self.heights.count)? }; + for &value in heights { + finite(value)?; + } + let scale = self.scale.raw()?; + ensure( + scale.min_element() > 0.0, + "heightfield scale must be positive", + )?; + #[cfg(feature = "dim2")] + { + ensure( + self.rows >= 2 && self.columns == 1, + "invalid 2D heightfield dimensions", + )?; + SharedShape::heightfield(heights.to_vec(), scale) + } + #[cfg(feature = "dim3")] + { + ensure( + self.rows >= 2 && self.columns >= 2, + "invalid 3D heightfield dimensions", + )?; + SharedShape::heightfield_with_flags( + rapier::parry::utils::Array2::new( + self.rows, + self.columns, + heights.to_vec(), + ), + scale, + rapier::parry::shape::HeightFieldFlags::from_bits( + u8::try_from(self.flags) + .map_err(|_| invalid("unknown heightfield flags"))?, + ) + .ok_or_else(|| invalid("unknown heightfield flags"))?, + ) + } + } + _ => return Err(invalid("unknown or unsupported shape description")), + }) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_shape_desc() -> RprShapeDesc { + RprShapeDesc::default() +} +#[rapier_export(shape_desc)] +pub unsafe extern "C" fn rpr_shape_desc_build(desc: *const RprShapeDesc) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let shape = get(desc)?.raw()?; + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) + }) +} + +pub const RPR_MASS_DENSITY: u32 = 0; +pub const RPR_MASS_TOTAL: u32 = 1; +pub const RPR_MASS_PROPERTIES: u32 = 2; +/// Copyable collider construction data. Shape inputs are borrowed, never owned. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct RprColliderDesc { + pub shape: RprShapeDesc, + pub position: RprPose, + pub massMode: u32, + pub density: RprReal, + pub mass: RprReal, + pub massProperties: RprMassProperties, + pub friction: RprReal, + pub restitution: RprReal, + pub frictionCombineRule: u32, + pub restitutionCombineRule: u32, + pub isSensor: RprBool, + pub enabled: RprBool, + pub collisionGroups: RprInteractionGroups, + pub solverGroups: RprInteractionGroups, + pub activeCollisionTypes: u16, + pub activeHooks: u32, + pub activeEvents: u32, + pub contactForceEventThreshold: RprReal, + pub contactSkin: RprReal, + pub userData: RprUserData, +} +impl Default for RprColliderDesc { + fn default() -> Self { + Self { + shape: RprShapeDesc::default(), + position: Pose::IDENTITY.into(), + massMode: RPR_MASS_DENSITY, + density: 1.0, + mass: 0.0, + massProperties: MassProperties::default().into(), + friction: ColliderBuilder::default_friction(), + restitution: 0.0, + frictionCombineRule: 0, + restitutionCombineRule: 0, + isSensor: 0, + enabled: 1, + collisionGroups: InteractionGroups::all().into(), + solverGroups: InteractionGroups::all().into(), + activeCollisionTypes: ActiveCollisionTypes::default().bits(), + activeHooks: 0, + activeEvents: 0, + contactForceEventThreshold: 0.0, + contactSkin: 0.0, + userData: 0u128.into(), + } + } +} +impl RprColliderDesc { + pub(crate) unsafe fn raw(&self) -> Result { + let mut b = ColliderBuilder::new(unsafe { self.shape.raw()? }) + .position(self.position.raw()?) + .friction(nonnegative(self.friction)?) + .restitution(nonnegative(self.restitution)?) + .sensor(boolean(self.isSensor)?) + .enabled(boolean(self.enabled)?) + .collision_groups(self.collisionGroups.raw()?) + .solver_groups(self.solverGroups.raw()?) + .user_data(self.userData.raw()) + .friction_combine_rule(combine(self.frictionCombineRule)?) + .restitution_combine_rule(combine(self.restitutionCombineRule)?) + .active_collision_types( + ActiveCollisionTypes::from_bits(self.activeCollisionTypes) + .ok_or_else(|| invalid("unknown collision types"))?, + ) + .active_hooks( + ActiveHooks::from_bits(self.activeHooks).ok_or_else(|| invalid("unknown hooks"))?, + ) + .active_events( + ActiveEvents::from_bits(self.activeEvents) + .ok_or_else(|| invalid("unknown events"))?, + ) + .contact_force_event_threshold(nonnegative(self.contactForceEventThreshold)?) + .contact_skin(nonnegative(self.contactSkin)?); + b = match self.massMode { + RPR_MASS_DENSITY => b.density(nonnegative(self.density)?), + RPR_MASS_TOTAL => b.mass(nonnegative(self.mass)?), + RPR_MASS_PROPERTIES => b.mass_properties(self.massProperties.raw()?), + _ => return Err(invalid("unknown mass mode")), + }; + Ok(b) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_collider_desc() -> RprColliderDesc { + RprColliderDesc::default() +} +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_ball_collider_desc(radius: RprReal) -> RprColliderDesc { + let mut d = RprColliderDesc::default(); + d.shape.radius = radius; + d +} +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_cuboid_collider_desc(half_extents: RprVector) -> RprColliderDesc { + let mut d = RprColliderDesc::default(); + d.shape.kind = RPR_SHAPE_DESC_CUBOID; + d.shape.a = half_extents; + d +} +#[rapier_export] +pub unsafe extern "C" fn rpr_insert_rigid_body( + world: *mut RprWorld, + desc: *const RprRigidBodyDesc, +) -> RprRigidBodyHandle { + ffi_world_value(world, |out: *mut RprRigidBodyHandle| { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + if !out.is_null() { + out_ptr(out)?; + } + let body = get(desc)?.raw()?.build(); + let h = get_mut(world)?.0.insert_body(body); + if !out.is_null() { + output(out, h.into())?; + } + Ok(()) + }) + }) +} + +/// Insert a collider attached to a rigid body, using the world stored in its handle. +/// The parent handle is copied by value. The description is borrowed through this call. +/// Invalid or removed parents fail without inserting a collider. +#[rapier_export] +pub unsafe extern "C" fn rpr_insert_collider( + parent: RprRigidBodyHandle, + desc: *const RprColliderDesc, +) -> RprColliderHandle { + let world = parent.world; + ffi_world_value(world, |out| { + ffi(|| unsafe { + if world.is_null() { + return Err(missing()); + } + let access = get(world)?.write()?; + let world = &mut (*access.raw()).0; + let parent = parent.raw(); + world.bodies.get(parent).ok_or_else(missing)?; + let collider = get(desc)?.raw()?.build(); + output(out, world.insert_collider(collider, Some(parent)).into()) + }) + }) +} + +/// Insert a collider without a rigid-body parent. The world owns the collider. +/// The description is borrowed through this call. +#[rapier_export] +pub unsafe extern "C" fn rpr_insert_collider_without_parent( + world: *mut RprWorld, + desc: *const RprColliderDesc, +) -> RprColliderHandle { + ffi_world_value(world, |out| { + ffi(|| unsafe { + let access = get(world)?.write()?; + let world = &mut (*access.raw()).0; + let collider = get(desc)?.raw()?.build(); + output(out, world.insert_collider(collider, None).into()) + }) + }) +} + +/// Sizes of the POD types in this library build, for foreign-language layout checks. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprPodLayout { + pub rigidBodyDesc: usize, + pub colliderDesc: usize, + pub shapeDesc: usize, + pub jointDesc: usize, + pub softBodyMaterial: usize, + pub integrationParameters: usize, + pub softBodyDesc: usize, + pub softMeshBindingDesc: usize, + pub queryOptions: usize, + /// Zero unless 3D f32 robotics is enabled. + pub urdfLoaderOptions: usize, + /// Zero unless 3D f32 robotics is enabled. + pub mjcfLoaderOptions: usize, +} +#[rapier_export] +pub extern "C" fn rpr_pod_layout() -> RprPodLayout { + RprPodLayout { + rigidBodyDesc: size_of::(), + colliderDesc: size_of::(), + shapeDesc: size_of::(), + jointDesc: size_of::(), + softBodyMaterial: size_of::(), + integrationParameters: size_of::(), + softBodyDesc: size_of::(), + softMeshBindingDesc: size_of::(), + queryOptions: size_of::(), + #[cfg(all(feature = "robotics", feature = "dim3", feature = "f32"))] + urdfLoaderOptions: size_of::(), + #[cfg(not(all(feature = "robotics", feature = "dim3", feature = "f32")))] + urdfLoaderOptions: 0, + #[cfg(all(feature = "robotics", feature = "dim3", feature = "f32"))] + mjcfLoaderOptions: size_of::(), + #[cfg(not(all(feature = "robotics", feature = "dim3", feature = "f32")))] + mjcfLoaderOptions: 0, + } +} diff --git a/c/src/dynamics.rs b/c/src/dynamics.rs new file mode 100644 index 000000000..14c522f3d --- /dev/null +++ b/c/src/dynamics.rs @@ -0,0 +1,792 @@ +use crate::*; + +pub(crate) unsafe fn native_rigid_body_position( + object: *const RprRigidBody, + out: *mut RprPose, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, (*object.0.position()).into()) + }) +} + +pub(crate) unsafe fn native_rigid_body_next_position( + object: *const RprRigidBody, + out: *mut RprPose, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, (*object.0.next_position()).into()) + }) +} + +pub(crate) unsafe fn native_rigid_body_translation( + object: *const RprRigidBody, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.translation().into()) + }) +} + +pub(crate) unsafe fn native_rigid_body_rotation( + object: *const RprRigidBody, + out: *mut RprRotation, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, (*object.0.rotation()).into()) + }) +} + +pub(crate) unsafe fn native_rigid_body_linvel( + object: *const RprRigidBody, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.linvel().into()) + }) +} + +pub(crate) unsafe fn native_rigid_body_angvel( + object: *const RprRigidBody, + out: *mut RprAngVector, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, angular_out(object.0.angvel())) + }) +} + +pub(crate) unsafe fn native_rigid_body_center_of_mass( + object: *const RprRigidBody, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.center_of_mass().into()) + }) +} + +pub(crate) unsafe fn native_rigid_body_local_center_of_mass( + object: *const RprRigidBody, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.local_center_of_mass().into()) + }) +} + +pub(crate) unsafe fn native_rigid_body_user_force( + object: *const RprRigidBody, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.user_force().into()) + }) +} + +pub(crate) unsafe fn native_rigid_body_user_torque( + object: *const RprRigidBody, + out: *mut RprAngVector, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, angular_out(object.0.user_torque())) + }) +} + +pub(crate) unsafe fn native_rigid_body_body_type( + object: *const RprRigidBody, + out: *mut u32, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.body_type() as u32) + }) +} + +pub(crate) unsafe fn native_rigid_body_user_data( + object: *const RprRigidBody, + out: *mut RprUserData, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.user_data.into()) + }) +} + +pub(crate) unsafe fn native_rigid_body_mass( + object: *const RprRigidBody, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.mass()) + }) +} + +pub(crate) unsafe fn native_rigid_body_gravity_scale( + object: *const RprRigidBody, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.gravity_scale()) + }) +} + +pub(crate) unsafe fn native_rigid_body_linear_damping( + object: *const RprRigidBody, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.linear_damping()) + }) +} + +pub(crate) unsafe fn native_rigid_body_angular_damping( + object: *const RprRigidBody, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.angular_damping()) + }) +} + +pub(crate) unsafe fn native_rigid_body_kinetic_energy( + object: *const RprRigidBody, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.kinetic_energy()) + }) +} + +pub(crate) unsafe fn native_rigid_body_soft_ccd_prediction( + object: *const RprRigidBody, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.soft_ccd_prediction()) + }) +} + +pub(crate) unsafe fn native_rigid_body_is_sleeping( + object: *const RprRigidBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.is_sleeping() as u32) + }) +} + +pub(crate) unsafe fn native_rigid_body_is_enabled( + object: *const RprRigidBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.is_enabled() as u32) + }) +} + +pub(crate) unsafe fn native_rigid_body_is_ccd_enabled( + object: *const RprRigidBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.is_ccd_enabled() as u32) + }) +} + +pub(crate) unsafe fn native_rigid_body_is_dynamic( + object: *const RprRigidBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.is_dynamic() as u32) + }) +} + +/// The soft body owning this proxy, or an invalid handle for an ordinary rigid body. +pub(crate) unsafe fn native_rigid_body_soft_body( + object: *const RprRigidBody, + out: *mut RprSoftBodyHandle, +) -> RprStatus { + ffi(|| unsafe { + output( + out, + get(object)? + .0 + .soft_body() + .map(Into::into) + .unwrap_or_default(), + ) + }) +} + +pub(crate) unsafe fn native_rigid_body_is_soft_frame( + object: *const RprRigidBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { output(out, get(object)?.0.is_soft_frame() as RprBool) }) +} + +pub(crate) unsafe fn native_rigid_body_is_fixed( + object: *const RprRigidBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.is_fixed() as u32) + }) +} + +pub(crate) unsafe fn native_rigid_body_is_kinematic( + object: *const RprRigidBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.is_kinematic() as u32) + }) +} + +pub(crate) unsafe fn native_rigid_body_is_moving( + object: *const RprRigidBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.is_moving() as u32) + }) +} + +pub(crate) unsafe fn native_rigid_body_is_ccd_active( + object: *const RprRigidBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.is_ccd_active() as u32) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_position( + object: *mut RprRigidBody, + value: RprPose, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.set_position(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_translation( + object: *mut RprRigidBody, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.set_translation(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_rotation( + object: *mut RprRigidBody, + value: RprRotation, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.set_rotation(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_linvel( + object: *mut RprRigidBody, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.set_linvel(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_angvel( + object: *mut RprRigidBody, + value: RprAngVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = angular(value)?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.set_angvel(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_body_type( + object: *mut RprRigidBody, + value: u32, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = body_type(value)?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.set_body_type(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_next_kinematic_position( + object: *mut RprRigidBody, + value: RprPose, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let object = get_mut(object)?; + object.0.set_next_kinematic_position(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_next_kinematic_translation( + object: *mut RprRigidBody, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let object = get_mut(object)?; + object.0.set_next_kinematic_translation(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_next_kinematic_rotation( + object: *mut RprRigidBody, + value: RprRotation, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let object = get_mut(object)?; + object.0.set_next_kinematic_rotation(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_gravity_scale( + object: *mut RprRigidBody, + value: RprReal, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = finite(value)?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.set_gravity_scale(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_additional_mass( + object: *mut RprRigidBody, + value: RprReal, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.set_additional_mass(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_linear_damping( + object: *mut RprRigidBody, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let object = get_mut(object)?; + object.0.set_linear_damping(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_angular_damping( + object: *mut RprRigidBody, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let object = get_mut(object)?; + object.0.set_angular_damping(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_soft_ccd_prediction( + object: *mut RprRigidBody, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let object = get_mut(object)?; + object.0.set_soft_ccd_prediction(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_enabled( + object: *mut RprRigidBody, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + let object = get_mut(object)?; + object.0.set_enabled(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_ccd_enabled( + object: *mut RprRigidBody, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + let object = get_mut(object)?; + object.0.enable_ccd(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_translations_locked( + object: *mut RprRigidBody, + value: RprBool, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.lock_translations(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_rotations_locked( + object: *mut RprRigidBody, + value: RprBool, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.lock_rotations(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_dominance_group( + object: *mut RprRigidBody, + value: i8, +) -> RprStatus { + ffi(|| unsafe { + let object = get_mut(object)?; + object.0.set_dominance_group(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_additional_solver_iterations( + object: *mut RprRigidBody, + value: usize, +) -> RprStatus { + ffi(|| unsafe { + let object = get_mut(object)?; + object.0.set_additional_solver_iterations(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_additional_pgs_iterations( + object: *mut RprRigidBody, + value: usize, +) -> RprStatus { + ffi(|| unsafe { + let object = get_mut(object)?; + object.0.set_additional_pgs_iterations(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_add_force( + object: *mut RprRigidBody, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.add_force(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_apply_impulse( + object: *mut RprRigidBody, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.apply_impulse(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_add_torque( + object: *mut RprRigidBody, + value: RprAngVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = angular(value)?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.add_torque(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_apply_torque_impulse( + object: *mut RprRigidBody, + value: RprAngVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = angular(value)?; + let wake_up = boolean(wake_up)?; + let object = get_mut(object)?; + object.0.apply_torque_impulse(value, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_user_data( + object: *mut RprRigidBody, + value: RprUserData, +) -> RprStatus { + ffi(|| unsafe { + get_mut(object)?.0.user_data = value.raw(); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_add_force_at_point( + object: *mut RprRigidBody, + value: RprVector, + point: RprVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let point = point.raw()?; + let wake_up = boolean(wake_up)?; + get_mut(object)?.0.add_force_at_point(value, point, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_apply_impulse_at_point( + object: *mut RprRigidBody, + value: RprVector, + point: RprVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let point = point.raw()?; + let wake_up = boolean(wake_up)?; + get_mut(object)? + .0 + .apply_impulse_at_point(value, point, wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_reset_forces( + object: *mut RprRigidBody, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let wake_up = boolean(wake_up)?; + get_mut(object)?.0.reset_forces(wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_reset_torques( + object: *mut RprRigidBody, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let wake_up = boolean(wake_up)?; + get_mut(object)?.0.reset_torques(wake_up); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_sleep(object: *mut RprRigidBody) -> RprStatus { + ffi(|| unsafe { + get_mut(object)?.0.sleep(); + Ok(()) + }) +} + +pub(crate) unsafe fn native_rigid_body_velocity_at_point( + object: *const RprRigidBody, + point: RprVector, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { output(out, get(object)?.0.velocity_at_point(point.raw()?).into()) }) +} + +pub(crate) unsafe fn native_rigid_body_colliders( + object: *const RprRigidBody, + buffer: *mut RprColliderHandle, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let values: Vec<_> = get(object)? + .0 + .colliders() + .iter() + .copied() + .map(Into::into) + .collect(); + copy_out(&values, buffer, capacity, count) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_rigid_body_propagate_modified_body_positions_to_colliders( + world: *mut RprWorld, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *const RprRigidBodySet = std::ptr::addr_of!((*raw).0.bodies).cast(); + let colliders: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + get(set)? + .0 + .propagate_modified_body_positions_to_colliders(&mut get_mut(colliders)?.0); + Ok(()) + }) +} + +#[cfg(feature = "dim3")] +pub(crate) unsafe fn native_rigid_body_gyroscopic_forces_enabled( + body: *const RprRigidBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { output(out, get(body)?.0.gyroscopic_forces_enabled() as RprBool) }) +} + +#[cfg(feature = "dim3")] +pub(crate) unsafe fn native_rigid_body_set_gyroscopic_forces_enabled( + body: *mut RprRigidBody, + enabled: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let enabled = boolean(enabled)?; + get_mut(body)?.0.enable_gyroscopic_forces(enabled); + Ok(()) + }) +} + +/// Copies the island manager's active body handles. +#[rapier_export] +pub unsafe extern "C" fn rpr_active_rigid_bodies( + world: *const RprWorld, + buffer: *mut RprRigidBodyHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + let access = get(world)?.read()?; + let raw = access.raw(); + + let islands: *const RprIslandManager = std::ptr::addr_of!((*raw).0.islands).cast(); + + let handles: Vec = + get(islands)?.0.active_bodies().map(Into::into).collect(); + copy_out(&handles, buffer, capacity, count) + }) + }) + } +} + +/// Wake a body by handle, including a soft-body cluster proxy. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_wake_up( + handle: RprRigidBodyHandle, + strong: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let strong = boolean(strong)?; + get_mut(set)? + .0 + .get_mut(handle.raw()) + .ok_or_else(missing)? + .wake_up(strong); + Ok(()) + }) +} diff --git a/c/src/error.rs b/c/src/error.rs new file mode 100644 index 000000000..6cae46d93 --- /dev/null +++ b/c/src/error.rs @@ -0,0 +1,235 @@ +use crate::*; +use std::{ + cell::{Cell, RefCell}, + ffi::{CString, c_char, c_void}, + panic::{AssertUnwindSafe, catch_unwind}, +}; + +/// Status-returning operations use these integer codes. +pub type RprStatus = u32; +pub const RPR_OK: RprStatus = 0; +pub const RPR_NULL_POINTER: RprStatus = 1; +pub const RPR_INVALID_ARGUMENT: RprStatus = 2; +pub const RPR_INVALID_HANDLE: RprStatus = 3; +pub const RPR_BUFFER_TOO_SMALL: RprStatus = 4; +pub const RPR_UNSUPPORTED: RprStatus = 5; +pub const RPR_PANIC: RprStatus = 6; +pub const RPR_NOT_FOUND: RprStatus = 7; +/// Conflicting or reentrant access to simulation state. No mutation was performed. +pub const RPR_WORLD_BUSY: RprStatus = 8; +pub(crate) type Result = std::result::Result; +thread_local! { static LAST_STATUS: Cell = const { Cell::new(RPR_OK) }; } +thread_local! { static LAST_ERROR: RefCell = RefCell::new(CString::default()); } +/// Called synchronously on the calling thread when an operation reports an error. +/// The diagnostic is borrowed for the duration of the callback. The callback +/// must return normally or terminate the process: never throw or longjmp across +/// the Rust/C boundary. Nested failing calls do not invoke the handler recursively. +pub type RprErrorCallback = Option; + +/// An optional thread-local error handler. A null callback disables reporting. +/// Keep the callback and user_data alive until the handler is replaced. +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprErrorHandler { + pub callback: RprErrorCallback, + pub user_data: *mut c_void, +} + +thread_local! { + static ERROR_HANDLER: Cell = const { Cell::new(RprErrorHandler { + callback: None, + user_data: std::ptr::null_mut(), + }) }; + static FFI_DEPTH: Cell = const { Cell::new(0) }; +} + +/// Replace this thread's error handler and return the previous handler so it can +/// be restored at the end of a scope. Status returns are unchanged. A handler +/// that returns lets the caller recover by checking the status; a fail-fast +/// handler may terminate the process. Includes RPR_NOT_FOUND query misses. +#[rapier_export] +pub unsafe extern "C" fn rpr_set_error_handler(handler: RprErrorHandler) -> RprErrorHandler { + ERROR_HANDLER.with(|current| current.replace(handler)) +} + +pub(crate) fn invalid(message: impl Into) -> (RprStatus, String) { + (RPR_INVALID_ARGUMENT, message.into()) +} +pub(crate) fn missing() -> (RprStatus, String) { + (RPR_INVALID_HANDLE, "invalid or stale handle".into()) +} +pub(crate) fn ensure(condition: bool, message: &str) -> Result { + if condition { + Ok(()) + } else { + Err(invalid(message)) + } +} +pub(crate) fn finite(value: Real) -> Result { + ensure(value.is_finite(), "expected a finite number")?; + Ok(value) +} +pub(crate) fn nonnegative(value: Real) -> Result { + finite(value)?; + ensure(value >= 0.0, "expected a nonnegative number")?; + Ok(value) +} +pub(crate) fn positive(value: Real) -> Result { + finite(value)?; + ensure(value > 0.0, "expected a positive number")?; + Ok(value) +} +pub(crate) fn boolean(value: u32) -> Result { + ensure(value <= 1, "boolean must be 0 or 1")?; + Ok(value != 0) +} +struct FfiCall; +impl Drop for FfiCall { + fn drop(&mut self) { + FFI_DEPTH.with(|depth| depth.set(depth.get() - 1)); + } +} + +pub(crate) fn ffi(f: impl FnOnce() -> Result) -> RprStatus { + let outermost = FFI_DEPTH.with(|depth| { + let previous = depth.get(); + depth.set(previous + 1); + previous == 0 + }); + let _call = FfiCall; + let result = catch_unwind(AssertUnwindSafe(f)).unwrap_or_else(|payload| { + let msg = payload + .downcast_ref::<&str>() + .copied() + .or_else(|| payload.downcast_ref::().map(String::as_str)) + .unwrap_or("Rust panic"); + Err(( + RPR_PANIC, + format!("Rapier panic: {msg}; discard objects mutated by this call"), + )) + }); + match result { + Ok(()) => { + LAST_STATUS.with(|s| s.set(RPR_OK)); + LAST_ERROR.with(|e| *e.borrow_mut() = CString::default()); + RPR_OK + } + Err((status, message)) => { + LAST_STATUS.with(|s| s.set(status)); + let message = CString::new(message.replace('\0', "?")).unwrap(); + LAST_ERROR.with(|e| *e.borrow_mut() = message.clone()); + let handler = ERROR_HANDLER.with(Cell::get); + if let Some(callback) = handler.callback { + if outermost { + // All operation-local borrows and the panic boundary have ended. + // Keep the diagnostic alive even if the handler calls Rapier again. + unsafe { callback(status, message.as_ptr(), handler.user_data) }; + LAST_ERROR.with(|e| *e.borrow_mut() = message); + LAST_STATUS.with(|s| s.set(status)); + } + } + status + } + } +} +/// Status of the most recent fallible operation on this thread. Reading this or +/// LastError does not clear it. Infallible value constructors do not change it. +/// Check immediately after a fallible value-returning operation when recovering +/// from errors instead of using a fail-fast error callback. +#[rapier_export] +pub extern "C" fn rpr_last_status() -> RprStatus { + LAST_STATUS.with(Cell::get) +} + +/// Capture a newly produced value without exposing an output pointer in the ABI. +/// Errors return the type's default value. A short-buffer error preserves the +/// required length so callers can resize and retry. +pub(crate) fn ffi_value(f: impl FnOnce(*mut T) -> RprStatus) -> T { + let mut value = T::default(); + let status = f(&mut value); + if status != RPR_OK && status != RPR_BUFFER_TOO_SMALL { + return T::default(); + } + value +} + +/// Thread-local UTF-8 diagnostic, valid until the next fallible call on this thread. +#[rapier_export] +pub extern "C" fn rpr_last_error() -> *const c_char { + LAST_ERROR.with(|e| e.borrow().as_ptr()) +} +/// These helpers check null and alignment, not allocation validity or ownership. +pub(crate) unsafe fn get<'a, T>(p: *const T) -> Result<&'a T> { + if p.is_null() { + return Err((RPR_NULL_POINTER, "null pointer".into())); + } + ensure(p.is_aligned(), "misaligned pointer")?; + Ok(unsafe { &*p }) +} +pub(crate) unsafe fn get_mut<'a, T>(p: *mut T) -> Result<&'a mut T> { + if p.is_null() { + return Err((RPR_NULL_POINTER, "null pointer".into())); + } + ensure(p.is_aligned(), "misaligned pointer")?; + Ok(unsafe { &mut *p }) +} +pub(crate) unsafe fn input<'a, T>(p: *const T, count: usize) -> Result<&'a [T]> { + if count == 0 { + return Ok(&[]); + } + unsafe { + get(p)?; + } + ensure( + count <= isize::MAX as usize / std::mem::size_of::().max(1), + "array is too large", + )?; + Ok(unsafe { std::slice::from_raw_parts(p, count) }) +} +pub(crate) unsafe fn output(p: *mut T, value: T) -> Result { + if p.is_null() { + return Err((RPR_NULL_POINTER, "null output pointer".into())); + } + ensure(p.is_aligned(), "misaligned output pointer")?; + unsafe { + p.write(value); + } + Ok(()) +} +pub(crate) unsafe fn out_ptr(p: *mut T) -> Result { + if p.is_null() { + return Err((RPR_NULL_POINTER, "null output pointer".into())); + } + ensure(p.is_aligned(), "misaligned output pointer") +} +/// A null buffer with capacity zero is a successful size query. Otherwise no partial writes. +pub(crate) unsafe fn copy_out( + values: &[T], + buffer: *mut T, + capacity: usize, + count: *mut usize, +) -> Result { + unsafe { + out_ptr(count)?; + } + if buffer.is_null() && capacity == 0 { + return unsafe { output(count, values.len()) }; + } + ensure( + capacity <= isize::MAX as usize / std::mem::size_of::().max(1), + "buffer is too large", + )?; + unsafe { + out_ptr(buffer)?; + } + unsafe { + output(count, values.len())?; + } + if capacity < values.len() { + return Err((RPR_BUFFER_TOO_SMALL, "output buffer is too small".into())); + } + unsafe { + std::ptr::copy_nonoverlapping(values.as_ptr(), buffer, values.len()); + } + Ok(()) +} diff --git a/c/src/extra.rs b/c/src/extra.rs new file mode 100644 index 000000000..4b4e5669c --- /dev/null +++ b/c/src/extra.rs @@ -0,0 +1,609 @@ +use crate::*; +use rapier::geometry::ContactPair; +/// Explicit mass and principal inertia, matching MassProperties constructors. Zero mass/inertia means infinite. +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprMassProperties { + pub local_com: RprVector, + pub mass: RprReal, + pub principal_inertia: RprAngVector, + #[cfg(feature = "dim3")] + pub principal_inertia_local_frame: RprRotation, +} +impl RprMassProperties { + pub(crate) fn raw(self) -> Result { + let com = self.local_com.raw()?; + nonnegative(self.mass)?; + #[cfg(feature = "dim2")] + { + Ok(MassProperties::new( + com, + self.mass, + nonnegative(self.principal_inertia)?, + )) + } + #[cfg(feature = "dim3")] + { + let i = self.principal_inertia.raw()?; + ensure(i.min_element() >= 0.0, "negative inertia")?; + Ok(MassProperties::with_principal_inertia_frame( + com, + self.mass, + i, + self.principal_inertia_local_frame.raw()?, + )) + } + } +} +impl From for RprMassProperties { + fn from(m: MassProperties) -> Self { + Self { + local_com: m.local_com.into(), + mass: m.mass(), + principal_inertia: angular_out(m.principal_inertia()), + #[cfg(feature = "dim3")] + principal_inertia_local_frame: m.principal_inertia_local_frame.into(), + } + } +} +pub(crate) unsafe fn native_rigid_body_set_additional_mass_properties( + body: *mut RprRigidBody, + properties: RprMassProperties, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let p = properties.raw()?; + let wake_up = boolean(wake_up)?; + get_mut(body)?.0.set_additional_mass_properties(p, wake_up); + Ok(()) + }) +} +pub(crate) unsafe fn native_rigid_body_recompute_mass_properties_from_colliders( + body: *mut RprRigidBody, + colliders: *const RprColliderSet, +) -> RprStatus { + ffi(|| unsafe { + get_mut(body)? + .0 + .recompute_mass_properties_from_colliders(&get(colliders)?.0); + Ok(()) + }) +} +pub(crate) unsafe fn native_collider_set_mass_properties( + collider: *mut RprCollider, + properties: RprMassProperties, +) -> RprStatus { + ffi(|| unsafe { + let p = properties.raw()?; + get_mut(collider)?.0.set_mass_properties(p); + Ok(()) + }) +} +pub(crate) unsafe fn native_collider_mass_properties( + collider: *const RprCollider, + out: *mut RprMassProperties, +) -> RprStatus { + ffi(|| unsafe { output(out, get(collider)?.0.mass_properties().into()) }) +} +pub(crate) unsafe fn native_rigid_body_set_locked_axes( + body: *mut RprRigidBody, + axes: u8, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let a = LockedAxes::from_bits(axes).ok_or_else(|| invalid("unknown locked axes"))?; + let w = boolean(wake_up)?; + get_mut(body)?.0.set_locked_axes(a, w); + Ok(()) + }) +} +pub(crate) unsafe fn native_rigid_body_locked_axes( + body: *const RprRigidBody, + out: *mut u8, +) -> RprStatus { + ffi(|| unsafe { output(out, get(body)?.0.locked_axes().bits()) }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_heightfield_shared_shape( + heights: RprRealView, + rows: usize, + columns: usize, + scale: RprVector, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let n = rows + .checked_mul(columns) + .ok_or_else(|| invalid("heightfield dimensions overflow"))?; + ensure( + heights.count == n, + "heightfield data length does not match dimensions", + )?; + let data = input(heights.data, heights.count)?; + for &h in data { + finite(h)?; + } + let scale = scale.raw()?; + ensure( + scale.min_element() > 0.0, + "heightfield scale must be positive", + )?; + #[cfg(feature = "dim2")] + let shape = { + ensure( + rows >= 2 && columns == 1, + "2D heightfield requires rows>=2 and columns=1", + )?; + SharedShape::heightfield(data.to_vec(), scale) + }; + #[cfg(feature = "dim3")] + let shape = { + ensure( + rows >= 2 && columns >= 2, + "heightfield dimensions must be >=2", + )?; + SharedShape::heightfield( + rapier::parry::utils::Array2::new(rows, columns, data.to_vec()), + scale, + ) + }; + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) + }) +} +/// Vertex indices have DIM entries per element. Uses Rapier's default decomposition parameters. +pub(crate) unsafe fn impl_rpr_shared_shape_convex_decomposition( + vertices: *const RprVector, + vertex_count: usize, + indices: *const u32, + element_count: usize, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + let p = input(vertices, vertex_count)? + .iter() + .copied() + .map(RprVector::raw) + .collect::>>()?; + let i = crate::geometry::indices_array::<{ rapier::math::DIM }>( + indices, + element_count, + vertex_count, + )?; + ensure(!i.is_empty(), "empty mesh")?; + let shape = SharedShape::convex_decomposition(&p, &i); + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) +} +pub(crate) unsafe fn impl_rpr_shared_shape_voxels_from_points( + voxel_size: RprVector, + points: *const RprVector, + count: usize, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + let size = voxel_size.raw()?; + ensure(size.min_element() > 0.0, "voxel size must be positive")?; + let p = input(points, count)? + .iter() + .copied() + .map(RprVector::raw) + .collect::>>()?; + ensure(!p.is_empty(), "no voxel points")?; + output( + out, + Box::into_raw(Box::new(RprSharedShape(SharedShape::voxels_from_points( + size, &p, + )))), + ) + }) +} +#[rapier_export(shared_shape)] +pub unsafe extern "C" fn rpr_shared_shape_compute_aabb( + shape: *const RprSharedShape, + pose: RprPose, +) -> RprAabb { + ffi_value(|out: *mut RprAabb| { + ffi(|| unsafe { + let a = get(shape)?.0.compute_aabb(&pose.raw()?); + output( + out, + RprAabb { + mins: a.mins.into(), + maxs: a.maxs.into(), + }, + ) + }) + }) +} +#[rapier_export(shared_shape)] +pub unsafe extern "C" fn rpr_shared_shape_mass_properties( + shape: *const RprSharedShape, + density: RprReal, +) -> RprMassProperties { + ffi_value(|out: *mut RprMassProperties| { + ffi(|| unsafe { + nonnegative(density)?; + output(out, get(shape)?.0.mass_properties(density).into()) + }) + }) +} +#[rapier_export(shared_shape)] +pub unsafe extern "C" fn rpr_shared_shape_contains_point( + shape: *const RprSharedShape, + pose: RprPose, + point: RprVector, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + let p = pose.raw()?; + let point = point.raw()?; + output(out, get(shape)?.0.contains_point(&p, point) as u32) + }) + }) +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprContactPair { + pub collider1: RprColliderHandle, + pub collider2: RprColliderHandle, + pub has_any_active_contact: RprBool, + pub total_impulse: RprVector, + pub total_impulse_magnitude: RprReal, + pub max_impulse: RprReal, + pub max_impulse_direction: RprVector, +} +impl From<&ContactPair> for RprContactPair { + fn from(p: &ContactPair) -> Self { + let (max, dir) = p.max_impulse(); + Self { + collider1: p.collider1.into(), + collider2: p.collider2.into(), + has_any_active_contact: p.has_any_active_contact() as u32, + total_impulse: p.total_impulse().into(), + total_impulse_magnitude: p.total_impulse_magnitude(), + max_impulse: max, + max_impulse_direction: dir.into(), + } + } +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprIntersectionPair { + pub collider1: RprColliderHandle, + pub collider2: RprColliderHandle, + pub intersecting: RprBool, +} +#[rapier_export] +pub unsafe extern "C" fn rpr_contact_pairs( + world: *const RprWorld, + buffer: *mut RprContactPair, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + let access = get(world)?.read()?; + let raw = access.raw(); + + let narrow: *const RprNarrowPhase = + std::ptr::addr_of!((*raw).0.narrow_phase).cast(); + + let v: Vec<_> = get(narrow)?.0.contact_pairs().map(Into::into).collect(); + copy_out(&v, buffer, capacity, count) + }) + }) + } +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_contact_pair( + collider1: RprColliderHandle, + collider2: RprColliderHandle, +) -> RprContactPair { + let world = collider1.world; + ffi_world_value(world, |out: *mut RprContactPair| { + ffi(|| unsafe { + collider1.check_world(world)?; + collider2.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let narrow: *const RprNarrowPhase = std::ptr::addr_of!((*raw).0.narrow_phase).cast(); + + let p = get(narrow)? + .0 + .contact_pair(collider1.raw(), collider2.raw()) + .ok_or((RPR_NOT_FOUND, "no contact pair".into()))?; + output(out, p.into()) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_intersection_pairs( + world: *const RprWorld, + buffer: *mut RprIntersectionPair, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + let access = get(world)?.read()?; + let raw = access.raw(); + + let narrow: *const RprNarrowPhase = + std::ptr::addr_of!((*raw).0.narrow_phase).cast(); + + let v: Vec<_> = get(narrow)? + .0 + .intersection_pairs() + .map(|(a, b, hit)| RprIntersectionPair { + collider1: a.into(), + collider2: b.into(), + intersecting: hit as u32, + }) + .collect(); + copy_out(&v, buffer, capacity, count) + }) + }) + } +} + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprContactPoint { + pub manifold_index: usize, + pub local_p1: RprVector, + pub local_p2: RprVector, + pub normal: RprVector, + pub distance: RprReal, + pub impulse: RprReal, +} +/// Contact points in collider-local space; normal in world space. Geometric manifolds may be recycled. +/// For clustered solver impulses use contact pair totals. Soft pairs have no rigid manifolds. +#[rapier_export] +pub unsafe extern "C" fn rpr_contact_points( + collider1: RprColliderHandle, + collider2: RprColliderHandle, + buffer: *mut RprContactPoint, + capacity: usize, +) -> usize { + let world = collider1.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + collider1.check_world(world)?; + collider2.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let narrow: *const RprNarrowPhase = std::ptr::addr_of!((*raw).0.narrow_phase).cast(); + + let p = get(narrow)? + .0 + .contact_pair(collider1.raw(), collider2.raw()) + .ok_or((RPR_NOT_FOUND, "no contact pair".into()))?; + let v: Vec<_> = p + .manifolds() + .iter() + .enumerate() + .flat_map(|(i, m)| { + m.points.iter().map(move |p| RprContactPoint { + manifold_index: i, + local_p1: p.local_p1.into(), + local_p2: p.local_p2.into(), + normal: m.data.normal.into(), + distance: p.dist, + impulse: p.data.impulse, + }) + }) + .collect(); + copy_out(&v, buffer, capacity, count) + }) + }) +} + +#[rapier_export(multibody_joint)] +pub unsafe extern "C" fn rpr_multibody_joint_generalized_velocity( + handle: RprMultibodyJointHandle, + buffer: *mut RprReal, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprMultibodyJointSet = + std::ptr::addr_of!((*raw).0.multibody_joints).cast(); + + let (m, _) = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + copy_out(m.generalized_velocity().as_slice(), buffer, capacity, count) + }) + }) +} + +#[rapier_export(multibody_joint)] +pub unsafe extern "C" fn rpr_multibody_joint_set_generalized_velocity( + handle: RprMultibodyJointHandle, + values: *const RprReal, + count: usize, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprMultibodyJointSet = + std::ptr::addr_of_mut!((*raw).0.multibody_joints).cast(); + + let v = input(values, count)?; + for &x in v { + finite(x)?; + } + let (m, _) = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + m.ndofs() == count, + "velocity count must equal articulation dofs", + )?; + m.generalized_velocity_mut() + .as_mut_slice() + .copy_from_slice(v); + Ok(()) + }) +} + +/// Check this before passing any dimension/precision-dependent structs across the ABI. +#[rapier_export] +pub unsafe extern "C" fn rpr_check_abi( + version: u32, + dimension: u32, + real_size: usize, + vector_size: usize, + pose_size: usize, +) -> RprStatus { + ffi(|| { + ensure( + version == RPR_ABI_VERSION + && dimension == rapier::math::DIM as u32 + && real_size == std::mem::size_of::() + && vector_size == std::mem::size_of::() + && pose_size == std::mem::size_of::(), + "header/library ABI mismatch", + ) + }) +} + +/// Voxelize a boundary mesh with the native default solid-fill mode. +/// Indices contain DIM entries per boundary element. +pub(crate) unsafe fn impl_rpr_shared_shape_voxelized_mesh( + vertices: *const RprVector, + vertex_count: usize, + indices: *const u32, + element_count: usize, + voxel_size: RprReal, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + positive(voxel_size)?; + let points = input(vertices, vertex_count)? + .iter() + .copied() + .map(RprVector::raw) + .collect::>>()?; + ensure(!points.is_empty(), "empty voxelization mesh")?; + let indices = crate::geometry::indices_array::<{ rapier::math::DIM }>( + indices, + element_count, + points.len(), + )?; + output( + out, + Box::into_raw(Box::new(RprSharedShape(SharedShape::voxelized_mesh( + &points, + &indices, + voxel_size, + Default::default(), + )))), + ) + }) +} + +pub(crate) unsafe fn native_collider_is_voxels( + collider: *const RprCollider, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + output( + out, + get(collider)?.0.shape().as_voxels().is_some() as RprBool, + ) + }) +} +/// Voxel coordinates have DIM signed integer components. +#[cfg(feature = "f32")] +pub type RprVoxelCoord = i32; +#[cfg(feature = "f64")] +pub type RprVoxelCoord = i64; +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprVoxelKey { + pub x: RprVoxelCoord, + pub y: RprVoxelCoord, + #[cfg(feature = "dim3")] + pub z: RprVoxelCoord, +} +impl RprVoxelKey { + fn raw(self) -> rapier::math::IVector { + #[cfg(feature = "dim2")] + { + rapier::math::IVector::new(self.x, self.y) + } + #[cfg(feature = "dim3")] + { + rapier::math::IVector::new(self.x, self.y, self.z) + } + } +} +pub(crate) unsafe fn native_collider_voxel_at_flat_id( + collider: *const RprCollider, + id: u32, + key: *mut RprVoxelKey, + center: *mut RprVector, + size: *mut RprVector, + found: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(key)?; + out_ptr(center)?; + out_ptr(size)?; + out_ptr(found)?; + let voxels = get(collider)? + .0 + .shape() + .as_voxels() + .ok_or_else(|| invalid("not a voxel collider"))?; + output(size, voxels.voxel_size().into())?; + if let Some(k) = voxels.voxel_at_flat_id(id) { + output( + key, + RprVoxelKey { + x: k.x, + y: k.y, + #[cfg(feature = "dim3")] + z: k.z, + }, + )?; + output(center, voxels.voxel_center(k).into())?; + output(found, 1) + } else { + output(key, Default::default())?; + output(center, Default::default())?; + output(found, 0) + } + }) +} +pub(crate) unsafe fn native_collider_set_voxel( + collider: *mut RprCollider, + key: RprVoxelKey, + filled: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let filled = boolean(filled)?; + get_mut(collider)? + .0 + .shape_mut() + .as_voxels_mut() + .ok_or_else(|| invalid("not a voxel collider"))? + .set_voxel(key.raw(), filled); + Ok(()) + }) +} diff --git a/c/src/geometry.rs b/c/src/geometry.rs new file mode 100644 index 000000000..a80301755 --- /dev/null +++ b/c/src/geometry.rs @@ -0,0 +1,799 @@ +use crate::*; +#[rapier_export] +pub unsafe extern "C" fn rpr_ball_shared_shape(radius: RprReal) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let shape = SharedShape::ball(positive(radius)?); + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_cuboid_shared_shape(half_extents: RprVector) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let v = half_extents.raw()?; + ensure(v.min_element() > 0.0, "half extents must be positive")?; + let shape = { + #[cfg(feature = "dim2")] + { + SharedShape::cuboid(v.x, v.y) + } + #[cfg(feature = "dim3")] + { + SharedShape::cuboid(v.x, v.y, v.z) + } + }; + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_round_cuboid_shared_shape( + half_extents: RprVector, + border_radius: RprReal, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let v = half_extents.raw()?; + ensure(v.min_element() > 0.0, "half extents must be positive")?; + let r = nonnegative(border_radius)?; + let shape = { + #[cfg(feature = "dim2")] + { + SharedShape::round_cuboid(v.x, v.y, r) + } + #[cfg(feature = "dim3")] + { + SharedShape::round_cuboid(v.x, v.y, v.z, r) + } + }; + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_capsule_shared_shape( + a: RprVector, + b: RprVector, + radius: RprReal, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let shape = SharedShape::capsule(a.raw()?, b.raw()?, positive(radius)?); + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_segment_shared_shape( + a: RprVector, + b: RprVector, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let shape = SharedShape::segment(a.raw()?, b.raw()?); + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_triangle_shared_shape( + a: RprVector, + b: RprVector, + c: RprVector, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let shape = SharedShape::triangle(a.raw()?, b.raw()?, c.raw()?); + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_halfspace_shared_shape(normal: RprVector) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let v = normal.raw()?; + positive(v.length())?; + let shape = SharedShape::halfspace(v / v.length()); + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) + }) +} + +#[cfg(feature = "dim3")] +#[rapier_export] +pub unsafe extern "C" fn rpr_cylinder_shared_shape( + half_height: RprReal, + radius: RprReal, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let shape = SharedShape::cylinder(positive(half_height)?, positive(radius)?); + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) + }) +} + +#[cfg(feature = "dim3")] +#[rapier_export] +pub unsafe extern "C" fn rpr_cone_shared_shape( + half_height: RprReal, + radius: RprReal, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let shape = SharedShape::cone(positive(half_height)?, positive(radius)?); + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) + }) +} + +pub(crate) unsafe fn impl_rpr_shared_shape_convex_hull( + vertices: *const RprVector, + count: usize, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + let points = input(vertices, count)? + .iter() + .copied() + .map(RprVector::raw) + .collect::>>()?; + ensure(points.len() > rapier::math::DIM, "not enough vertices")?; + let shape = + SharedShape::convex_hull(&points).ok_or_else(|| invalid("degenerate convex hull"))?; + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) +} + +pub(crate) unsafe fn impl_rpr_shared_shape_trimesh( + vertices: *const RprVector, + vertex_count: usize, + indices: *const u32, + element_count: usize, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + let points = input(vertices, vertex_count)? + .iter() + .copied() + .map(RprVector::raw) + .collect::>>()?; + let indices = indices_array::<3>(indices, element_count, vertex_count)?; + ensure(!indices.is_empty(), "empty mesh")?; + let shape = SharedShape::trimesh(points, indices).map_err(|e| invalid(e.to_string()))?; + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) +} + +pub(crate) unsafe fn impl_rpr_shared_shape_polyline( + vertices: *const RprVector, + vertex_count: usize, + indices: *const u32, + element_count: usize, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + let points = input(vertices, vertex_count)? + .iter() + .copied() + .map(RprVector::raw) + .collect::>>()?; + let indices = indices_array::<2>(indices, element_count, vertex_count)?; + ensure(!indices.is_empty(), "empty mesh")?; + let shape = SharedShape::polyline(points, Some(indices)); + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) +} + +pub(crate) unsafe fn indices_array( + indices: *const u32, + count: usize, + vertices: usize, +) -> Result> { + let len = count + .checked_mul(N) + .ok_or_else(|| invalid("index count overflow"))?; + let values = unsafe { input(indices, len)? }; + ensure( + values.iter().all(|&i| (i as usize) < vertices), + "vertex index out of range", + )?; + Ok(values + .chunks_exact(N) + .map(|c| c.try_into().unwrap()) + .collect()) +} +#[rapier_export] +pub unsafe extern "C" fn rpr_compound_shared_shape( + children: RprCompoundShapeView, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let desc = RprShapeDesc { + kind: RPR_SHAPE_DESC_COMPOUND, + children, + ..Default::default() + }; + output(out, Box::into_raw(Box::new(RprSharedShape(desc.raw()?)))) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_position( + object: *mut RprCollider, + value: RprPose, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let object = get_mut(object)?; + object.0.set_position(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_translation( + object: *mut RprCollider, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let object = get_mut(object)?; + object.0.set_translation(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_friction( + object: *mut RprCollider, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let object = get_mut(object)?; + object.0.set_friction(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_restitution( + object: *mut RprCollider, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let object = get_mut(object)?; + object.0.set_restitution(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_density( + object: *mut RprCollider, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let object = get_mut(object)?; + object.0.set_density(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_mass( + object: *mut RprCollider, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let object = get_mut(object)?; + object.0.set_mass(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_sensor( + object: *mut RprCollider, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + let object = get_mut(object)?; + object.0.set_sensor(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_enabled( + object: *mut RprCollider, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + let object = get_mut(object)?; + object.0.set_enabled(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_collision_groups( + object: *mut RprCollider, + value: RprInteractionGroups, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let object = get_mut(object)?; + object.0.set_collision_groups(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_solver_groups( + object: *mut RprCollider, + value: RprInteractionGroups, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let object = get_mut(object)?; + object.0.set_solver_groups(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_friction_combine_rule( + object: *mut RprCollider, + value: u32, +) -> RprStatus { + ffi(|| unsafe { + let value = combine(value)?; + let object = get_mut(object)?; + object.0.set_friction_combine_rule(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_restitution_combine_rule( + object: *mut RprCollider, + value: u32, +) -> RprStatus { + ffi(|| unsafe { + let value = combine(value)?; + let object = get_mut(object)?; + object.0.set_restitution_combine_rule(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_contact_skin( + object: *mut RprCollider, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let object = get_mut(object)?; + object.0.set_contact_skin(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_contact_force_event_threshold( + object: *mut RprCollider, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let object = get_mut(object)?; + object.0.set_contact_force_event_threshold(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_active_events( + object: *mut RprCollider, + value: u32, +) -> RprStatus { + ffi(|| unsafe { + let value = ActiveEvents::from_bits(value).ok_or_else(|| invalid("unknown event flags"))?; + let object = get_mut(object)?; + object.0.set_active_events(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_active_hooks( + object: *mut RprCollider, + value: u32, +) -> RprStatus { + ffi(|| unsafe { + let value = ActiveHooks::from_bits(value).ok_or_else(|| invalid("unknown hook flags"))?; + let object = get_mut(object)?; + object.0.set_active_hooks(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_active_collision_types( + object: *mut RprCollider, + value: u16, +) -> RprStatus { + ffi(|| unsafe { + let value = ActiveCollisionTypes::from_bits(value) + .ok_or_else(|| invalid("unknown collision type flags"))?; + let object = get_mut(object)?; + object.0.set_active_collision_types(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_user_data( + object: *mut RprCollider, + value: RprUserData, +) -> RprStatus { + ffi(|| unsafe { + get_mut(object)?.0.user_data = value.raw(); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_position( + object: *const RprCollider, + out: *mut RprPose, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, (*object.0.position()).into()) + }) +} + +pub(crate) unsafe fn native_collider_translation( + object: *const RprCollider, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.translation().into()) + }) +} + +pub(crate) unsafe fn native_collider_rotation( + object: *const RprCollider, + out: *mut RprRotation, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.rotation().into()) + }) +} + +pub(crate) unsafe fn native_collider_collision_groups( + object: *const RprCollider, + out: *mut RprInteractionGroups, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.collision_groups().into()) + }) +} + +pub(crate) unsafe fn native_collider_solver_groups( + object: *const RprCollider, + out: *mut RprInteractionGroups, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.solver_groups().into()) + }) +} + +pub(crate) unsafe fn native_collider_parent( + object: *const RprCollider, + out: *mut RprRigidBodyHandle, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.parent().map(Into::into).unwrap_or_default()) + }) +} + +pub(crate) unsafe fn native_collider_user_data( + object: *const RprCollider, + out: *mut RprUserData, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.user_data.into()) + }) +} + +pub(crate) unsafe fn native_collider_active_events( + object: *const RprCollider, + out: *mut u32, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.active_events().bits()) + }) +} + +pub(crate) unsafe fn native_collider_friction( + object: *const RprCollider, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.friction()) + }) +} + +pub(crate) unsafe fn native_collider_restitution( + object: *const RprCollider, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.restitution()) + }) +} + +pub(crate) unsafe fn native_collider_mass( + object: *const RprCollider, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.mass()) + }) +} + +pub(crate) unsafe fn native_collider_density( + object: *const RprCollider, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.density()) + }) +} + +pub(crate) unsafe fn native_collider_volume( + object: *const RprCollider, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.volume()) + }) +} + +pub(crate) unsafe fn native_collider_contact_skin( + object: *const RprCollider, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.contact_skin()) + }) +} + +pub(crate) unsafe fn native_collider_contact_force_event_threshold( + object: *const RprCollider, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.contact_force_event_threshold()) + }) +} + +pub(crate) unsafe fn native_collider_is_sensor( + object: *const RprCollider, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.is_sensor() as u32) + }) +} + +pub(crate) unsafe fn native_collider_is_enabled( + object: *const RprCollider, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.is_enabled() as u32) + }) +} + +pub(crate) unsafe fn native_collider_compute_aabb( + object: *const RprCollider, + out: *mut RprAabb, +) -> RprStatus { + ffi(|| unsafe { + let a = get(object)?.0.compute_aabb(); + output( + out, + RprAabb { + mins: a.mins.into(), + maxs: a.maxs.into(), + }, + ) + }) +} + +/// Returns an owned shared reference, independently freeable. +pub(crate) unsafe fn native_collider_shared_shape( + object: *const RprCollider, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + let shape = get(object)?.0.shared_shape().clone(); + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) +} + +pub(crate) unsafe fn native_collider_set_shape( + object: *mut RprCollider, + shape: *const RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + let shape = get(shape)?.0.clone(); + get_mut(object)?.0.set_shape(shape); + Ok(()) + }) +} + +pub(crate) unsafe fn native_collider_set_position_wrt_parent( + object: *mut RprCollider, + value: RprPose, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let object = get_mut(object)?; + object.0.set_position_wrt_parent(value); + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_remove_collider( + handle: RprColliderHandle, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + let islands: *mut RprIslandManager = std::ptr::addr_of_mut!((*raw).0.islands).cast(); + let bodies: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + let soft_bodies: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let wake_up = boolean(wake_up)?; + get_mut(set)? + .0 + .remove( + handle.raw(), + &mut get_mut(islands)?.0, + &mut get_mut(bodies)?.0, + &mut get_mut(soft_bodies)?.0, + wake_up, + ) + .ok_or_else(missing)?; + Ok(()) + }) +} + +/// Build a one-sided 2D polyline. Indices are flat pairs, as for impl_rpr_shared_shape_polyline. +#[cfg(feature = "dim2")] +pub(crate) unsafe fn impl_rpr_shared_shape_oriented_polyline( + vertices: *const RprVector, + vertex_count: usize, + indices: *const u32, + element_count: usize, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + let points = input(vertices, vertex_count)? + .iter() + .copied() + .map(RprVector::raw) + .collect::>>()?; + let indices = indices_array::<2>(indices, element_count, vertex_count)?; + ensure(!indices.is_empty(), "empty mesh")?; + let shape = ColliderBuilder::oriented_polyline(points, Some(indices)).shape; + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) +} +/// Convex polygon from an already ordered convex boundary. +#[cfg(feature = "dim2")] +pub(crate) unsafe fn impl_rpr_shared_shape_convex_polyline( + vertices: *const RprVector, + count: usize, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + let points = input(vertices, count)? + .iter() + .copied() + .map(RprVector::raw) + .collect::>>()?; + ensure(points.len() >= 3, "not enough vertices")?; + let shape = SharedShape::convex_polyline(points) + .ok_or_else(|| invalid("invalid convex polygon"))?; + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) +} +/// Rounded convex hull of the supplied points. +pub(crate) unsafe fn impl_rpr_shared_shape_round_convex_hull( + vertices: *const RprVector, + count: usize, + border_radius: RprReal, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + let points = input(vertices, count)? + .iter() + .copied() + .map(RprVector::raw) + .collect::>>()?; + ensure(points.len() > rapier::math::DIM, "not enough vertices")?; + let shape = SharedShape::round_convex_hull(&points, nonnegative(border_radius)?) + .ok_or_else(|| invalid("degenerate convex hull"))?; + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) +} + +/// Triangle mesh with Parry's TriMeshFlags bits. +pub(crate) unsafe fn impl_rpr_shared_shape_trimesh_with_flags( + vertices: *const RprVector, + vertex_count: usize, + indices: *const u32, + element_count: usize, + flags: u32, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + let points = input(vertices, vertex_count)? + .iter() + .copied() + .map(RprVector::raw) + .collect::>>()?; + let indices = indices_array::<3>(indices, element_count, vertex_count)?; + ensure(!indices.is_empty(), "empty mesh")?; + let flags = rapier::parry::shape::TriMeshFlags::from_bits(flags as u16) + .filter(|_| flags <= u16::MAX as u32) + .ok_or_else(|| invalid("unknown trimesh flags"))?; + let shape = SharedShape::trimesh_with_flags(points, indices, flags) + .map_err(|e| invalid(e.to_string()))?; + output(out, Box::into_raw(Box::new(RprSharedShape(shape)))) + }) +} diff --git a/c/src/geometry_views.rs b/c/src/geometry_views.rs new file mode 100644 index 000000000..64996e4cb --- /dev/null +++ b/c/src/geometry_views.rs @@ -0,0 +1,197 @@ +//! Typed geometry input boundaries; view counts always count elements. +use crate::handle_access::forward; +use crate::*; +/// Copies typed input geometry into an owned shared shape; arrays may be released on return. +#[rapier_export] +pub unsafe extern "C" fn rpr_convex_decomposition_shared_shape( + vertices: RprVectorView, + indices: RprSurfaceElementView, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + crate::array_views::validate_view(vertices.data, vertices.count)?; + crate::array_views::validate_view(indices.data, indices.count)?; + forward(impl_rpr_shared_shape_convex_decomposition( + vertices.data, + vertices.count, + indices.data.cast(), + indices.count, + out, + )) + }) + }) +} +/// Copies typed input geometry into an owned shared shape; arrays may be released on return. +#[rapier_export] +pub unsafe extern "C" fn rpr_voxels_shared_shape_from_points( + voxel_size: RprVector, + points: RprVectorView, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + crate::array_views::validate_view(points.data, points.count)?; + forward(impl_rpr_shared_shape_voxels_from_points( + voxel_size, + points.data, + points.count, + out, + )) + }) + }) +} +/// Copies typed input geometry into an owned shared shape; arrays may be released on return. +#[rapier_export] +pub unsafe extern "C" fn rpr_voxelized_mesh_shared_shape( + vertices: RprVectorView, + indices: RprSurfaceElementView, + voxel_size: RprReal, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + crate::array_views::validate_view(vertices.data, vertices.count)?; + crate::array_views::validate_view(indices.data, indices.count)?; + forward(impl_rpr_shared_shape_voxelized_mesh( + vertices.data, + vertices.count, + indices.data.cast(), + indices.count, + voxel_size, + out, + )) + }) + }) +} +/// Copies typed input geometry into an owned shared shape; arrays may be released on return. +#[rapier_export] +pub unsafe extern "C" fn rpr_convex_hull_shared_shape( + vertices: RprVectorView, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + crate::array_views::validate_view(vertices.data, vertices.count)?; + forward(impl_rpr_shared_shape_convex_hull( + vertices.data, + vertices.count, + out, + )) + }) + }) +} +/// Copies typed input geometry into an owned shared shape; arrays may be released on return. +#[rapier_export] +pub unsafe extern "C" fn rpr_trimesh_shared_shape( + vertices: RprVectorView, + indices: RprTriangleView, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + crate::array_views::validate_view(vertices.data, vertices.count)?; + crate::array_views::validate_view(indices.data, indices.count)?; + forward(impl_rpr_shared_shape_trimesh( + vertices.data, + vertices.count, + indices.data.cast(), + indices.count, + out, + )) + }) + }) +} +/// Copies typed input geometry into an owned shared shape; arrays may be released on return. +#[rapier_export] +pub unsafe extern "C" fn rpr_polyline_shared_shape( + vertices: RprVectorView, + indices: RprEdgeView, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + crate::array_views::validate_view(vertices.data, vertices.count)?; + crate::array_views::validate_view(indices.data, indices.count)?; + forward(impl_rpr_shared_shape_polyline( + vertices.data, + vertices.count, + indices.data.cast(), + indices.count, + out, + )) + }) + }) +} +#[cfg(feature = "dim2")] +/// Copies typed input geometry into an owned shared shape; arrays may be released on return. +#[rapier_export] +pub unsafe extern "C" fn rpr_oriented_polyline_shared_shape( + vertices: RprVectorView, + indices: RprEdgeView, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + crate::array_views::validate_view(vertices.data, vertices.count)?; + crate::array_views::validate_view(indices.data, indices.count)?; + forward(impl_rpr_shared_shape_oriented_polyline( + vertices.data, + vertices.count, + indices.data.cast(), + indices.count, + out, + )) + }) + }) +} +#[cfg(feature = "dim2")] +/// Copies typed input geometry into an owned shared shape; arrays may be released on return. +#[rapier_export] +pub unsafe extern "C" fn rpr_convex_polyline_shared_shape( + vertices: RprVectorView, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + crate::array_views::validate_view(vertices.data, vertices.count)?; + forward(impl_rpr_shared_shape_convex_polyline( + vertices.data, + vertices.count, + out, + )) + }) + }) +} +/// Copies typed input geometry into an owned shared shape; arrays may be released on return. +#[rapier_export] +pub unsafe extern "C" fn rpr_round_convex_hull_shared_shape( + vertices: RprVectorView, + border_radius: RprReal, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + crate::array_views::validate_view(vertices.data, vertices.count)?; + forward(impl_rpr_shared_shape_round_convex_hull( + vertices.data, + vertices.count, + border_radius, + out, + )) + }) + }) +} +/// Copies typed input geometry into an owned shared shape; arrays may be released on return. +#[rapier_export] +pub unsafe extern "C" fn rpr_trimesh_shared_shape_with_flags( + vertices: RprVectorView, + indices: RprTriangleView, + flags: u32, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + crate::array_views::validate_view(vertices.data, vertices.count)?; + crate::array_views::validate_view(indices.data, indices.count)?; + forward(impl_rpr_shared_shape_trimesh_with_flags( + vertices.data, + vertices.count, + indices.data.cast(), + indices.count, + flags, + out, + )) + }) + }) +} diff --git a/c/src/handle_access.rs b/c/src/handle_access.rs new file mode 100644 index 000000000..1763221ef --- /dev/null +++ b/c/src/handle_access.rs @@ -0,0 +1,1341 @@ +//! Short-lived element access through generational handles. No element pointer escapes. +//! Handles remain scoped to the original set; they do not keep the set/world alive. +use crate::*; + +// Delegate to the existing accessors so their validation and change semantics stay identical. +// ffi suppresses nested error callbacks; the outermost call reports each error once. +pub(crate) unsafe fn forward(status: RprStatus) -> Result { + if status == RPR_OK { + Ok(()) + } else { + Err(( + status, + unsafe { std::ffi::CStr::from_ptr(rpr_last_error()) } + .to_string_lossy() + .into_owned(), + )) + } +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_position(handle: RprRigidBodyHandle) -> RprPose { + let world = handle.world; + ffi_value(|out: *mut RprPose| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_position( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_position( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprPose, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_position( + (element as *const RigidBody).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_translation(handle: RprRigidBodyHandle) -> RprVector { + let world = handle.world; + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_translation( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_translation( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_translation( + (element as *const RigidBody).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_linvel(handle: RprRigidBodyHandle) -> RprVector { + let world = handle.world; + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_linvel( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_linvel( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_linvel( + (element as *const RigidBody).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_angvel(handle: RprRigidBodyHandle) -> RprAngVector { + let world = handle.world; + ffi_value(|out: *mut RprAngVector| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_angvel( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_angvel( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprAngVector, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_angvel( + (element as *const RigidBody).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_is_sleeping(handle: RprRigidBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_is_sleeping( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_is_sleeping( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_is_sleeping( + (element as *const RigidBody).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_is_enabled(handle: RprRigidBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_is_enabled( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_is_enabled( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_is_enabled( + (element as *const RigidBody).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_user_data(handle: RprRigidBodyHandle) -> RprUserData { + let world = handle.world; + ffi_value(|out: *mut RprUserData| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_user_data( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_user_data( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprUserData, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_user_data( + (element as *const RigidBody).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_position( + handle: RprRigidBodyHandle, + value: RprPose, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_position( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_translation( + handle: RprRigidBodyHandle, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_translation( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_linvel( + handle: RprRigidBodyHandle, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_linvel( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_angvel( + handle: RprRigidBodyHandle, + value: RprAngVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_angvel( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_next_kinematic_position( + handle: RprRigidBodyHandle, + value: RprPose, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_next_kinematic_position( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_next_kinematic_translation( + handle: RprRigidBodyHandle, + value: RprVector, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_next_kinematic_translation( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_gravity_scale( + handle: RprRigidBodyHandle, + value: RprReal, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_gravity_scale( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_linear_damping( + handle: RprRigidBodyHandle, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_set_linear_damping( + std::ptr::addr_of_mut!((*raw).0.bodies).cast(), + handle, + value, + )) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_set_linear_damping( + set: *mut RprRigidBodySet, + handle: RprRigidBodyHandle, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_linear_damping( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_angular_damping( + handle: RprRigidBodyHandle, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_angular_damping( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_enabled( + handle: RprRigidBodyHandle, + value: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_set_enabled( + std::ptr::addr_of_mut!((*raw).0.bodies).cast(), + handle, + value, + )) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_set_enabled( + set: *mut RprRigidBodySet, + handle: RprRigidBodyHandle, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_enabled( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_user_data( + handle: RprRigidBodyHandle, + value: RprUserData, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_user_data( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_apply_impulse( + handle: RprRigidBodyHandle, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_apply_impulse( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_apply_impulse_at_point( + handle: RprRigidBodyHandle, + value: RprVector, + point: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_apply_impulse_at_point( + (element as *mut RigidBody).cast(), + value, + point, + wake_up, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_add_force( + handle: RprRigidBodyHandle, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_add_force( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_reset_forces( + handle: RprRigidBodyHandle, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_reset_forces( + (element as *mut RigidBody).cast(), + wake_up, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_sleep(handle: RprRigidBodyHandle) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_sleep((element as *mut RigidBody).cast())) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_position(handle: RprColliderHandle) -> RprPose { + let world = handle.world; + ffi_value(|out: *mut RprPose| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_position( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_position( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprPose, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_position( + (element as *const Collider).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_translation(handle: RprColliderHandle) -> RprVector { + let world = handle.world; + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_translation( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_translation( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_translation( + (element as *const Collider).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_friction(handle: RprColliderHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_friction( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_friction( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_friction( + (element as *const Collider).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_restitution(handle: RprColliderHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_restitution( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_restitution( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_restitution( + (element as *const Collider).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_is_sensor(handle: RprColliderHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_is_sensor( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_is_sensor( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_is_sensor( + (element as *const Collider).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_parent(handle: RprColliderHandle) -> RprRigidBodyHandle { + let world = handle.world; + ffi_world_value(world, |out: *mut RprRigidBodyHandle| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_parent( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_parent( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprRigidBodyHandle, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_parent( + (element as *const Collider).cast(), + out, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_position( + handle: RprColliderHandle, + value: RprPose, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_position( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_translation( + handle: RprColliderHandle, + value: RprVector, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_translation( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_friction( + handle: RprColliderHandle, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_friction( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_restitution( + handle: RprColliderHandle, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_restitution( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_sensor( + handle: RprColliderHandle, + value: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_sensor( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_collision_groups( + handle: RprColliderHandle, + value: RprInteractionGroups, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_collision_groups( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_user_data( + handle: RprColliderHandle, + value: RprUserData, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_user_data( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_particle_position( + handle: RprSoftBodyHandle, + index: usize, +) -> RprVector { + let world = handle.world; + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_particle_position( + (element as *const SoftBody).cast(), + index, + out, + )) + }) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_particle_positions( + handle: RprSoftBodyHandle, + buffer: *mut RprVector, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_particle_positions( + (element as *const SoftBody).cast(), + buffer, + capacity, + count, + )) + }) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_material(handle: RprSoftBodyHandle) -> RprSoftBodyMaterial { + let world = handle.world; + ffi_value(|out: *mut RprSoftBodyMaterial| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_read_material( + (element as *const SoftBody).cast(), + out, + )) + }) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_particle_position( + handle: RprSoftBodyHandle, + index: usize, + value: RprVector, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_particle_position( + (element as *mut SoftBody).cast(), + index, + value, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_material( + handle: RprSoftBodyHandle, + data: *const RprSoftBodyMaterial, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_material_data( + (element as *mut SoftBody).cast(), + data, + )) + }) +} + +/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_add_particle_force( + handle: RprSoftBodyHandle, + index: usize, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_add_particle_force( + (element as *mut SoftBody).cast(), + index, + value, + wake_up, + )) + }) +} + +/// A copied state snapshot, with no pointers or ownership obligations. +#[repr(C)] +#[derive(Clone, Copy)] +#[allow(non_snake_case)] +pub struct RprRigidBodyState { + pub position: RprPose, + pub linvel: RprVector, + pub angvel: RprAngVector, + pub sleeping: RprBool, + pub enabled: RprBool, + pub userData: RprUserData, +} +impl From<&RigidBody> for RprRigidBodyState { + fn from(body: &RigidBody) -> Self { + Self { + position: (*body.position()).into(), + linvel: body.linvel().into(), + angvel: angular_out(body.angvel()), + sleeping: body.is_sleeping() as _, + enabled: body.is_enabled() as _, + userData: body.user_data.into(), + } + } +} + +/// Copies states in the same order as handles, without allocating temporary storage. +/// All handles are validated before writing. On INVALID_HANDLE outputs are unchanged. +/// NULL/0 is a size query. BUFFER_TOO_SMALL updates count but leaves states untouched. +#[rapier_export] +pub unsafe extern "C" fn rpr_rigid_body_read_states( + world: *const RprWorld, + handles: *const RprRigidBodyHandle, + handle_count: usize, + states: *mut RprRigidBodyState, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + for value in input(handles, handle_count)? { + value.check_world(world)?; + } + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_read_states( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handles, + handle_count, + states, + capacity, + count, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_read_states( + set: *const RprRigidBodySet, + handles: *const RprRigidBodyHandle, + handle_count: usize, + states: *mut RprRigidBodyState, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let bodies = &get(set)?.0; + let handles = input(handles, handle_count)?; + out_ptr(count)?; + for handle in handles { + bodies.get(handle.raw()).ok_or_else(missing)?; + } + if states.is_null() && capacity == 0 { + return output(count, handles.len()); + } + out_ptr(states)?; + ensure( + capacity <= isize::MAX as usize / size_of::(), + "buffer is too large", + )?; + if capacity < handles.len() { + output(count, handles.len())?; + return Err((RPR_BUFFER_TOO_SMALL, "output buffer is too small".into())); + } + for (i, handle) in handles.iter().enumerate() { + states + .add(i) + .write(RprRigidBodyState::from(&bodies[handle.raw()])); + } + output(count, handles.len()) + }) +} + +/// Copies joint configuration without returning a borrowed joint pointer. +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_desc(handle: RprImpulseJointHandle) -> RprJointDesc { + let world = handle.world; + ffi_value(|out: *mut RprJointDesc| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprImpulseJointSet = std::ptr::addr_of!((*raw).0.impulse_joints).cast(); + + output( + out, + get(set)? + .0 + .get(handle.raw()) + .ok_or_else(missing)? + .data + .into(), + ) + }) + }) +} + +/// Replaces configuration after validation, resetting cached limit/motor impulses. +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_desc( + handle: RprImpulseJointHandle, + desc: *const RprJointDesc, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let desc = get(desc)?.raw()?; + let wake_up = boolean(wake_up)?; + get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)? + .data = desc; + Ok(()) + }) +} diff --git a/c/src/handle_world.rs b/c/src/handle_world.rs new file mode 100644 index 000000000..732a987aa --- /dev/null +++ b/c/src/handle_world.rs @@ -0,0 +1,96 @@ +//! World ownership on foreign entity handles. Internal native adapters may create +//! unbound handles; attach the owner before returning them across the C boundary. +use crate::*; + +pub(crate) trait WorldHandles { + fn attach_world(&mut self, world: *mut RprWorld); + fn check_world(&self, world: *const RprWorld) -> Result; + fn with_world(mut self, world: *mut RprWorld) -> Self + where + Self: Sized, + { + self.attach_world(world); + self + } +} +macro_rules! entity_world { + ($($ty:ty),* $(,)?) => {$ ( + // Handles carry an address only; dereferencing it requires an unsafe API + // call with external lifetime synchronization and the world's borrow gate. + unsafe impl Send for $ty {} + unsafe impl Sync for $ty {} + impl WorldHandles for $ty { + fn attach_world(&mut self, world: *mut RprWorld) { + if self.index != u32::MAX { self.world = world; } + } + fn check_world(&self, world: *const RprWorld) -> Result { + if self.index != u32::MAX && (self.world.is_null() || !std::ptr::eq(self.world, world)) { + return Err((RPR_INVALID_HANDLE, "handle belongs to a different world".into())); + } + Ok(()) + } + } + )*}; +} +entity_world!( + RprRigidBodyHandle, + RprColliderHandle, + RprImpulseJointHandle, + RprMultibodyJointHandle, + RprSoftBodyHandle +); +macro_rules! fields_world { + ($ty:ty, $($field:ident),+ $(,)?) => { + impl WorldHandles for $ty { + fn attach_world(&mut self, world: *mut RprWorld) { $(self.$field.attach_world(world);)+ } + fn check_world(&self, world: *const RprWorld) -> Result { + $(self.$field.check_world(world)?;)+ Ok(()) + } + } + }; +} +fields_world!(RprCharacterCollision, collider, hit); +fields_world!(RprCollisionEvent, collider1, collider2); +fields_world!(RprContactForceEvent, collider1, collider2); +fields_world!(RprContactPair, collider1, collider2); +fields_world!(RprIntersectionPair, collider1, collider2); +fields_world!(RprJointBodies, body1, body2); +fields_world!(RprOptionalParticleDestination, body); +fields_world!(RprOptionalRayHit, hit); +fields_world!(RprParticleDestination, body); +fields_world!(RprPointProjection, collider); +fields_world!(RprQueryFilter, exclude_collider, exclude_rigid_body); +fields_world!(RprQueryOptions, filter); +fields_world!(RprRayHit, collider); +fields_world!(RprRayToi, collider); +fields_world!(RprShapeCastHit, collider); +fields_world!(RprSoftClusterSplit, soft_body, proxy); +fields_world!(RprSoftJointMove, joint, from, to); +fields_world!(RprSoftMeshInfo, collider); +#[cfg(feature = "dim3")] +fields_world!(RprWheelState, ground_object); + +pub(crate) fn ffi_world_value( + world: *const RprWorld, + call: impl FnOnce(*mut T) -> RprStatus, +) -> T { + ffi_value(call).with_world(world.cast_mut()) +} +/// copy_out writes no elements on failure, including BUFFER_TOO_SMALL. +pub(crate) unsafe fn ffi_world_array( + world: *const RprWorld, + buffer: *mut T, + capacity: usize, + call: impl FnOnce(*mut usize) -> RprStatus, +) -> usize { + let count = ffi_value(call); + if rpr_last_status() == RPR_OK && !buffer.is_null() && count <= capacity { + for value in unsafe { std::slice::from_raw_parts_mut(buffer, count) } { + value.attach_world(world.cast_mut()); + } + } + count +} +pub(crate) unsafe fn read_context_world(context: *const RprReadContext) -> *mut RprWorld { + unsafe { get(context).map_or(std::ptr::null_mut(), |c| c.world) } +} diff --git a/c/src/joint_access.rs b/c/src/joint_access.rs new file mode 100644 index 000000000..5214ec538 --- /dev/null +++ b/c/src/joint_access.rs @@ -0,0 +1,805 @@ +//! Joint configuration setters and handle-scoped live-joint edits. +use crate::handle_access::forward; +use crate::*; +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_local_frame1( + desc: *mut RprJointDesc, + value: RprPose, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_local_frame1(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_local_frame1( + handle: RprImpulseJointHandle, + value: RprPose, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_local_frame1( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_local_frame2( + desc: *mut RprJointDesc, + value: RprPose, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_local_frame2(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_local_frame2( + handle: RprImpulseJointHandle, + value: RprPose, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_local_frame2( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_local_anchor1( + desc: *mut RprJointDesc, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_local_anchor1(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_local_anchor1( + handle: RprImpulseJointHandle, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_local_anchor1( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_local_anchor2( + desc: *mut RprJointDesc, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_local_anchor2(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_local_anchor2( + handle: RprImpulseJointHandle, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_local_anchor2( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_contacts_enabled( + desc: *mut RprJointDesc, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_contacts_enabled(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_contacts_enabled( + handle: RprImpulseJointHandle, + value: RprBool, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_contacts_enabled( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_enabled( + desc: *mut RprJointDesc, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_enabled(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_enabled( + handle: RprImpulseJointHandle, + value: RprBool, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_enabled( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_softness( + desc: *mut RprJointDesc, + value: RprSpringCoefficients, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_softness(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_softness( + handle: RprImpulseJointHandle, + value: RprSpringCoefficients, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_softness( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_locked_axes( + desc: *mut RprJointDesc, + value: u8, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_locked_axes(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_locked_axes( + handle: RprImpulseJointHandle, + value: u8, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_locked_axes( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_limit_axes( + desc: *mut RprJointDesc, + value: u8, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_limit_axes(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_limit_axes( + handle: RprImpulseJointHandle, + value: u8, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_limit_axes( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_motor_axes( + desc: *mut RprJointDesc, + value: u8, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_motor_axes(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_motor_axes( + handle: RprImpulseJointHandle, + value: u8, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_motor_axes( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_coupled_axes( + desc: *mut RprJointDesc, + value: u8, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_coupled_axes(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_coupled_axes( + handle: RprImpulseJointHandle, + value: u8, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_coupled_axes( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_local_axis1( + desc: *mut RprJointDesc, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_local_axis1(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_local_axis1( + handle: RprImpulseJointHandle, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_local_axis1( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_local_axis2( + desc: *mut RprJointDesc, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_local_axis2(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_local_axis2( + handle: RprImpulseJointHandle, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_local_axis2( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_limits( + desc: *mut RprJointDesc, + joint_axis: u32, + min: RprReal, + max: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_limits( + &mut joint, joint_axis, min, max, + ))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_limits( + handle: RprImpulseJointHandle, + joint_axis: u32, + min: RprReal, + max: RprReal, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_limits( + (&mut joint.data as *mut GenericJoint).cast(), + joint_axis, + min, + max, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_motor( + desc: *mut RprJointDesc, + joint_axis: u32, + target_position: RprReal, + target_velocity: RprReal, + stiffness: RprReal, + damping: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_motor( + &mut joint, + joint_axis, + target_position, + target_velocity, + stiffness, + damping, + ))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_motor( + handle: RprImpulseJointHandle, + joint_axis: u32, + target_position: RprReal, + target_velocity: RprReal, + stiffness: RprReal, + damping: RprReal, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_motor( + (&mut joint.data as *mut GenericJoint).cast(), + joint_axis, + target_position, + target_velocity, + stiffness, + damping, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_motor_max_force( + desc: *mut RprJointDesc, + joint_axis: u32, + max_force: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_motor_max_force( + &mut joint, joint_axis, max_force, + ))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_motor_max_force( + handle: RprImpulseJointHandle, + joint_axis: u32, + max_force: RprReal, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_motor_max_force( + (&mut joint.data as *mut GenericJoint).cast(), + joint_axis, + max_force, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_motor_model( + desc: *mut RprJointDesc, + joint_axis: u32, + model: u32, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_motor_model( + &mut joint, joint_axis, model, + ))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_motor_model( + handle: RprImpulseJointHandle, + joint_axis: u32, + model: u32, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_motor_model( + (&mut joint.data as *mut GenericJoint).cast(), + joint_axis, + model, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_user_data( + desc: *mut RprJointDesc, + value: RprUserData, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_user_data(&mut joint, value))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_user_data( + handle: RprImpulseJointHandle, + value: RprUserData, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_user_data( + (&mut joint.data as *mut GenericJoint).cast(), + value, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_motor_position( + desc: *mut RprJointDesc, + joint_axis: u32, + target_position: RprReal, + stiffness: RprReal, + damping: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_motor_position( + &mut joint, + joint_axis, + target_position, + stiffness, + damping, + ))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_motor_position( + handle: RprImpulseJointHandle, + joint_axis: u32, + target_position: RprReal, + stiffness: RprReal, + damping: RprReal, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_motor_position( + (&mut joint.data as *mut GenericJoint).cast(), + joint_axis, + target_position, + stiffness, + damping, + )) + }) +} + +#[rapier_export(joint_desc)] +pub unsafe extern "C" fn rpr_joint_desc_set_motor_velocity( + desc: *mut RprJointDesc, + joint_axis: u32, + target_velocity: RprReal, + factor: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let mut joint = RprGenericJoint(get(desc)?.raw()?); + forward(rpr_generic_joint_set_motor_velocity( + &mut joint, + joint_axis, + target_velocity, + factor, + ))?; + output(desc, joint.0.into()) + }) +} +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_set_motor_velocity( + handle: RprImpulseJointHandle, + joint_axis: u32, + target_velocity: RprReal, + factor: RprReal, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let joint = get_mut(set)? + .0 + .get_mut(handle.raw(), wake_up) + .ok_or_else(missing)?; + forward(rpr_generic_joint_set_motor_velocity( + (&mut joint.data as *mut GenericJoint).cast(), + joint_axis, + target_velocity, + factor, + )) + }) +} diff --git a/c/src/joint_desc.rs b/c/src/joint_desc.rs new file mode 100644 index 000000000..9f35aa212 --- /dev/null +++ b/c/src/joint_desc.rs @@ -0,0 +1,251 @@ +//! Plain joint configuration, distinct from a borrowed live joint. +#![allow(non_snake_case)] +use crate::*; +#[cfg(feature = "dim2")] +pub const RPR_JOINT_DOF_COUNT: usize = 3; +#[cfg(feature = "dim3")] +pub const RPR_JOINT_DOF_COUNT: usize = 6; +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprJointLimits { + pub min: RprReal, + pub max: RprReal, +} +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprJointMotor { + pub targetVel: RprReal, + pub targetPos: RprReal, + pub stiffness: RprReal, + pub damping: RprReal, + pub maxForce: RprReal, + pub model: u32, +} +/// Copyable joint configuration. Limits/motors take effect when their axis mask is enabled. +/// Solver impulses are deliberately excluded. Applying data resets cached limit and motor impulses. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprJointDesc { + pub localFrame1: RprPose, + pub localFrame2: RprPose, + pub lockedAxes: u8, + pub limitAxes: u8, + pub motorAxes: u8, + pub coupledAxes: u8, + pub limits: [RprJointLimits; RPR_JOINT_DOF_COUNT], + pub motors: [RprJointMotor; RPR_JOINT_DOF_COUNT], + pub softness: RprSpringCoefficients, + pub contactsEnabled: RprBool, + pub enabled: RprBool, + pub userData: RprUserData, +} +impl From for RprJointDesc { + fn from(j: GenericJoint) -> Self { + Self { + localFrame1: j.local_frame1.into(), + localFrame2: j.local_frame2.into(), + lockedAxes: j.locked_axes.bits(), + limitAxes: j.limit_axes.bits(), + motorAxes: j.motor_axes.bits(), + coupledAxes: j.coupled_axes.bits(), + limits: j.limits.map(|v| RprJointLimits { + min: v.min, + max: v.max, + }), + motors: j.motors.map(|v| RprJointMotor { + targetVel: v.target_vel, + targetPos: v.target_pos, + stiffness: v.stiffness, + damping: v.damping, + maxForce: v.max_force, + model: match v.model { + MotorModel::AccelerationBased => 0, + MotorModel::ForceBased => 1, + }, + }), + softness: j.softness.into(), + contactsEnabled: j.contacts_enabled as _, + enabled: j.is_enabled() as _, + userData: j.user_data.into(), + } + } +} +impl RprJointDesc { + pub(crate) fn raw(&self) -> Result { + let mut j = GenericJoint::new(axes(self.lockedAxes)?); + j.local_frame1 = self.localFrame1.raw()?; + j.local_frame2 = self.localFrame2.raw()?; + j.limit_axes = axes(self.limitAxes)?; + j.motor_axes = axes(self.motorAxes)?; + j.coupled_axes = axes(self.coupledAxes)?; + j.softness = self.softness.raw()?; + j.contacts_enabled = boolean(self.contactsEnabled)?; + j.set_enabled(boolean(self.enabled)?); + j.user_data = self.userData.raw(); + for i in 0..RPR_JOINT_DOF_COUNT { + let l = self.limits[i]; + ensure( + !l.min.is_nan() && !l.max.is_nan(), + "joint limits must not be NaN", + )?; + ensure(l.min <= l.max, "reversed joint limits")?; + j.limits[i].min = l.min; + j.limits[i].max = l.max; + let m = self.motors[i]; + let dst = &mut j.motors[i]; + dst.target_vel = finite(m.targetVel)?; + dst.target_pos = finite(m.targetPos)?; + dst.stiffness = nonnegative(m.stiffness)?; + dst.damping = nonnegative(m.damping)?; + dst.max_force = nonnegative(m.maxForce)?; + dst.model = match m.model { + 0 => MotorModel::AccelerationBased, + 1 => MotorModel::ForceBased, + _ => return Err(invalid("invalid motor model")), + }; + } + Ok(j) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_joint_desc() -> RprJointDesc { + GenericJoint::new(JointAxesMask::empty()).into() +} + +// Axis normalization cannot fail across the C boundary. Invalid directions produce +// nonfinite frames, which the normal description validation rejects at insertion. +fn joint_desc_with_axis(locked_axes: JointAxesMask, axis: RprVector) -> RprJointDesc { + let mut desc = RprJointDesc::from(GenericJoint::new(locked_axes)); + let rotation = match axis.raw().ok().and_then(|v| v.try_normalize()) { + Some(axis) => GenericJoint::complete_ang_frame(axis).into(), + None => { + #[cfg(feature = "dim2")] + { + RprRotation { angle: Real::NAN } + } + #[cfg(feature = "dim3")] + { + RprRotation { + x: Real::NAN, + y: Real::NAN, + z: Real::NAN, + w: Real::NAN, + } + } + } + }; + desc.localFrame1.rotation = rotation; + desc.localFrame2.rotation = rotation; + desc +} + +#[rapier_export] +pub extern "C" fn rpr_fixed_joint_desc() -> RprJointDesc { + GenericJoint::from(FixedJointBuilder::new().build()).into() +} +#[cfg(feature = "dim2")] +#[rapier_export] +pub extern "C" fn rpr_revolute_joint_desc() -> RprJointDesc { + GenericJoint::from(RevoluteJointBuilder::new().build()).into() +} +/// Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. +#[cfg(feature = "dim3")] +#[rapier_export] +pub extern "C" fn rpr_revolute_joint_desc(axis_vector: RprVector) -> RprJointDesc { + joint_desc_with_axis(JointAxesMask::LOCKED_REVOLUTE_AXES, axis_vector) +} +/// Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. +#[rapier_export] +pub extern "C" fn rpr_prismatic_joint_desc(axis_vector: RprVector) -> RprJointDesc { + joint_desc_with_axis(JointAxesMask::LOCKED_PRISMATIC_AXES, axis_vector) +} +#[rapier_export] +pub extern "C" fn rpr_rope_joint_desc(length: RprReal) -> RprJointDesc { + GenericJoint::from(RopeJointBuilder::new(length).build()).into() +} +#[rapier_export] +pub extern "C" fn rpr_spring_joint_desc( + length: RprReal, + stiffness: RprReal, + damping: RprReal, +) -> RprJointDesc { + GenericJoint::from(SpringJointBuilder::new(length, stiffness, damping).build()).into() +} +#[cfg(feature = "dim3")] +#[rapier_export] +pub extern "C" fn rpr_spherical_joint_desc() -> RprJointDesc { + GenericJoint::from(SphericalJointBuilder::new().build()).into() +} +#[cfg(feature = "dim2")] +/// Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. +#[rapier_export] +pub extern "C" fn rpr_pin_slot_joint_desc(axis_vector: RprVector) -> RprJointDesc { + joint_desc_with_axis(JointAxesMask::LOCKED_PIN_SLOT_AXES, axis_vector) +} +#[rapier_export] +pub unsafe extern "C" fn rpr_insert_impulse_joint( + body1: RprRigidBodyHandle, + body2: RprRigidBodyHandle, + joint: *const RprJointDesc, +) -> RprImpulseJointHandle { + let world = body1.world; + ffi_world_value(world, |out: *mut RprImpulseJointHandle| { + ffi(|| unsafe { + body1.check_world(world)?; + body2.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + if !out.is_null() { + out_ptr(out)?; + } + let joint = get(joint)?.raw()?; + let world = &mut get_mut(world)?.0; + world.bodies.get(body1.raw()).ok_or_else(missing)?; + world.bodies.get(body2.raw()).ok_or_else(missing)?; + ensure(body1 != body2, "joint endpoints must differ")?; + let handle = world.insert_impulse_joint(body1.raw(), body2.raw(), joint); + if !out.is_null() { + output(out, handle.into())?; + } + Ok(()) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_insert_multibody_joint( + body1: RprRigidBodyHandle, + body2: RprRigidBodyHandle, + joint: *const RprJointDesc, +) -> RprMultibodyJointHandle { + let world = body1.world; + ffi_world_value(world, |out: *mut RprMultibodyJointHandle| { + ffi(|| unsafe { + body1.check_world(world)?; + body2.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + if !out.is_null() { + out_ptr(out)?; + } + let joint = get(joint)?.raw()?; + let world = &mut get_mut(world)?.0; + world.bodies.get(body1.raw()).ok_or_else(missing)?; + world.bodies.get(body2.raw()).ok_or_else(missing)?; + ensure(body1 != body2, "joint endpoints must differ")?; + let handle = world + .insert_multibody_joint(body1.raw(), body2.raw(), joint) + .ok_or_else(|| invalid("multibody loop or duplicate link"))?; + if !out.is_null() { + output(out, handle.into())?; + } + Ok(()) + }) + }) +} diff --git a/c/src/joints.rs b/c/src/joints.rs new file mode 100644 index 000000000..53bea5d73 --- /dev/null +++ b/c/src/joints.rs @@ -0,0 +1,512 @@ +use crate::*; +pub(crate) fn axis(value: u32) -> Result { + match value { + 0 => Ok(JointAxis::LinX), + 1 => Ok(JointAxis::LinY), + #[cfg(feature = "dim3")] + 2 => Ok(JointAxis::LinZ), + #[cfg(feature = "dim3")] + 3 => Ok(JointAxis::AngX), + #[cfg(feature = "dim3")] + 4 => Ok(JointAxis::AngY), + #[cfg(feature = "dim3")] + 5 => Ok(JointAxis::AngZ), + #[cfg(feature = "dim2")] + 2 => Ok(JointAxis::AngX), + _ => Err(invalid("joint axis out of range")), + } +} +pub(crate) fn axes(value: u8) -> Result { + JointAxesMask::from_bits(value).ok_or_else(|| invalid("invalid joint axes")) +} + +pub(crate) unsafe fn rpr_generic_joint_set_local_frame1( + joint: *mut RprGenericJoint, + value: RprPose, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + get_mut(joint)?.0.set_local_frame1(value); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_local_frame2( + joint: *mut RprGenericJoint, + value: RprPose, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + get_mut(joint)?.0.set_local_frame2(value); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_local_anchor1( + joint: *mut RprGenericJoint, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + get_mut(joint)?.0.set_local_anchor1(value); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_local_anchor2( + joint: *mut RprGenericJoint, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + get_mut(joint)?.0.set_local_anchor2(value); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_contacts_enabled( + joint: *mut RprGenericJoint, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + get_mut(joint)?.0.set_contacts_enabled(value); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_enabled( + joint: *mut RprGenericJoint, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + get_mut(joint)?.0.set_enabled(value); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_softness( + joint: *mut RprGenericJoint, + value: RprSpringCoefficients, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let _ = get_mut(joint)?.0.set_softness(value); + Ok(()) + }) +} + +pub(crate) unsafe fn rpr_generic_joint_set_locked_axes( + joint: *mut RprGenericJoint, + value: u8, +) -> RprStatus { + ffi(|| unsafe { + let value = axes(value)?; + get_mut(joint)?.0.locked_axes = value; + Ok(()) + }) +} + +pub(crate) unsafe fn rpr_generic_joint_set_limit_axes( + joint: *mut RprGenericJoint, + value: u8, +) -> RprStatus { + ffi(|| unsafe { + let value = axes(value)?; + get_mut(joint)?.0.limit_axes = value; + Ok(()) + }) +} + +pub(crate) unsafe fn rpr_generic_joint_set_motor_axes( + joint: *mut RprGenericJoint, + value: u8, +) -> RprStatus { + ffi(|| unsafe { + let value = axes(value)?; + get_mut(joint)?.0.motor_axes = value; + Ok(()) + }) +} + +pub(crate) unsafe fn rpr_generic_joint_set_coupled_axes( + joint: *mut RprGenericJoint, + value: u8, +) -> RprStatus { + ffi(|| unsafe { + let value = axes(value)?; + get_mut(joint)?.0.coupled_axes = value; + Ok(()) + }) +} + +pub(crate) unsafe fn rpr_generic_joint_set_local_axis1( + joint: *mut RprGenericJoint, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let v = value.raw()?; + positive(v.length())?; + get_mut(joint)?.0.set_local_axis1(v.normalize()); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_local_axis2( + joint: *mut RprGenericJoint, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let v = value.raw()?; + positive(v.length())?; + get_mut(joint)?.0.set_local_axis2(v.normalize()); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_limits( + joint: *mut RprGenericJoint, + joint_axis: u32, + min: RprReal, + max: RprReal, +) -> RprStatus { + ffi(|| unsafe { + ensure( + !min.is_nan() && !max.is_nan() && min <= max, + "limits must be ordered and not NaN", + )?; + let a = axis(joint_axis)?; + get_mut(joint)?.0.set_limits(a, [min, max]); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_motor( + joint: *mut RprGenericJoint, + joint_axis: u32, + target_position: RprReal, + target_velocity: RprReal, + stiffness: RprReal, + damping: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let a = axis(joint_axis)?; + finite(target_position)?; + finite(target_velocity)?; + nonnegative(stiffness)?; + nonnegative(damping)?; + get_mut(joint)? + .0 + .set_motor(a, target_position, target_velocity, stiffness, damping); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_motor_max_force( + joint: *mut RprGenericJoint, + joint_axis: u32, + max_force: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let a = axis(joint_axis)?; + nonnegative(max_force)?; + get_mut(joint)?.0.set_motor_max_force(a, max_force); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_motor_model( + joint: *mut RprGenericJoint, + joint_axis: u32, + model: u32, +) -> RprStatus { + ffi(|| unsafe { + let a = axis(joint_axis)?; + let model = match model { + 0 => MotorModel::AccelerationBased, + 1 => MotorModel::ForceBased, + _ => return Err(invalid("unknown motor model")), + }; + get_mut(joint)?.0.set_motor_model(a, model); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_user_data( + joint: *mut RprGenericJoint, + value: RprUserData, +) -> RprStatus { + ffi(|| unsafe { + get_mut(joint)?.0.user_data = value.raw(); + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_remove_impulse_joint( + handle: RprImpulseJointHandle, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprImpulseJointSet = std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + + let wake_up = boolean(wake_up)?; + let set = get_mut(set)?; + set.0.get(handle.raw()).ok_or_else(missing)?; + set.0.remove(handle.raw(), wake_up); + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_impulse_joint_handles( + world: *const RprWorld, + buffer: *mut RprImpulseJointHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprImpulseJointSet = + std::ptr::addr_of!((*raw).0.impulse_joints).cast(); + + let values: Vec<_> = get(set)?.0.iter().map(|(h, _)| h.into()).collect(); + copy_out(&values, buffer, capacity, count) + }) + }) + } +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_remove_multibody_joint( + handle: RprMultibodyJointHandle, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprMultibodyJointSet = + std::ptr::addr_of_mut!((*raw).0.multibody_joints).cast(); + + let wake_up = boolean(wake_up)?; + let set = get_mut(set)?; + set.0.get(handle.raw()).ok_or_else(missing)?; + set.0.remove(handle.raw(), wake_up); + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_multibody_joint_handles( + world: *const RprWorld, + buffer: *mut RprMultibodyJointHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprMultibodyJointSet = + std::ptr::addr_of!((*raw).0.multibody_joints).cast(); + + let values: Vec<_> = get(set)?.0.iter().map(|(h, _, _, _)| h.into()).collect(); + copy_out(&values, buffer, capacity, count) + }) + }) + } +} + +#[rapier_export(impulse_joint)] +pub unsafe extern "C" fn rpr_impulse_joint_bodies(handle: RprImpulseJointHandle) -> RprJointBodies { + let world = handle.world; + ffi_world_value(world, |result: *mut RprJointBodies| { + let body1 = unsafe { std::ptr::addr_of_mut!((*result).body1) }; + let body2 = unsafe { std::ptr::addr_of_mut!((*result).body2) }; + + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprImpulseJointSet = std::ptr::addr_of!((*raw).0.impulse_joints).cast(); + + out_ptr(body1)?; + out_ptr(body2)?; + let j = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + output(body1, j.body1().into())?; + output(body2, j.body2().into()) + }) + }) +} + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprInverseKinematicsOptions { + pub damping: RprReal, + pub max_iters: usize, + pub constrained_axes: u8, + pub epsilon_linear: RprReal, + pub epsilon_angular: RprReal, +} +#[rapier_export] +pub extern "C" fn rpr_default_inverse_kinematics_options() -> RprInverseKinematicsOptions { + let options = InverseKinematicsOption::default(); + RprInverseKinematicsOptions { + damping: options.damping, + max_iters: options.max_iters, + constrained_axes: options.constrained_axes.bits(), + epsilon_linear: options.epsilon_linear, + epsilon_angular: options.epsilon_angular, + } +} + +#[rapier_export(multibody_joint)] +pub unsafe extern "C" fn rpr_multibody_joint_ndofs(handle: RprMultibodyJointHandle) -> usize { + let world = handle.world; + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprMultibodyJointSet = + std::ptr::addr_of!((*raw).0.multibody_joints).cast(); + + output( + out, + get(set)?.0.get(handle.raw()).ok_or_else(missing)?.0.ndofs(), + ) + }) + }) +} + +/// Optional per-link filter, called synchronously. Must not reenter or retain physics objects. +pub type RprIkJointCanMove = + Option RprBool>; +/// Read/write displacement buffer must contain exactly ndofs entries; zero it for a fresh solve. +#[rapier_export(multibody_joint)] +pub unsafe extern "C" fn rpr_multibody_joint_inverse_kinematics( + handle: RprMultibodyJointHandle, + options: *const RprInverseKinematicsOptions, + target: RprPose, + can_move: RprIkJointCanMove, + user_data: *mut std::ffi::c_void, + displacements: *mut RprReal, + count: usize, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprMultibodyJointSet = std::ptr::addr_of!((*raw).0.multibody_joints).cast(); + let bodies: *const RprRigidBodySet = std::ptr::addr_of!((*raw).0.bodies).cast(); + + let (multibody, link_id) = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + ensure(multibody.link(link_id).is_some(), "invalid multibody link")?; + ensure( + count == multibody.ndofs(), + "displacement count must equal articulation dofs", + )?; + let values = input(displacements, count)?; + for &value in values { + finite(value)?; + } + let mut values = rapier::math::DVector::from_column_slice(values); + let options = get(options)?; + let options = InverseKinematicsOption { + damping: nonnegative(options.damping)?, + max_iters: options.max_iters, + constrained_axes: axes(options.constrained_axes)?, + epsilon_linear: nonnegative(options.epsilon_linear)?, + epsilon_angular: nonnegative(options.epsilon_angular)?, + }; + let target = target.raw()?; + multibody.inverse_kinematics( + &get(bodies)?.0, + link_id, + &options, + &target, + |link| { + can_move.is_none_or(|callback| { + callback( + user_data, + RprRigidBodyHandle::from(link.rigid_body_handle()).with_world(world), + ) != 0 + }) + }, + &mut values, + ); + if count != 0 { + std::ptr::copy_nonoverlapping(values.as_ptr(), displacements, count); + } + Ok(()) + }) +} + +#[rapier_export(multibody_joint)] +pub unsafe extern "C" fn rpr_multibody_joint_apply_displacements( + handle: RprMultibodyJointHandle, + displacements: *const RprReal, + count: usize, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprMultibodyJointSet = + std::ptr::addr_of_mut!((*raw).0.multibody_joints).cast(); + + let values = input(displacements, count)?; + for &value in values { + finite(value)?; + } + let (multibody, _) = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + count == multibody.ndofs(), + "displacement count must equal articulation dofs", + )?; + multibody.apply_displacements(values); + Ok(()) + }) +} + +pub(crate) unsafe fn rpr_generic_joint_set_motor_position( + joint: *mut RprGenericJoint, + joint_axis: u32, + target_position: RprReal, + stiffness: RprReal, + damping: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let a = axis(joint_axis)?; + finite(target_position)?; + nonnegative(stiffness)?; + nonnegative(damping)?; + get_mut(joint)? + .0 + .set_motor_position(a, target_position, stiffness, damping); + Ok(()) + }) +} +pub(crate) unsafe fn rpr_generic_joint_set_motor_velocity( + joint: *mut RprGenericJoint, + joint_axis: u32, + target_velocity: RprReal, + factor: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let a = axis(joint_axis)?; + finite(target_velocity)?; + nonnegative(factor)?; + get_mut(joint)? + .0 + .set_motor_velocity(a, target_velocity, factor); + Ok(()) + }) +} diff --git a/c/src/lib.rs b/c/src/lib.rs new file mode 100644 index 000000000..bf5a0d6c5 --- /dev/null +++ b/c/src/lib.rs @@ -0,0 +1,91 @@ +//! C ABI shared by the four Rapier dimensions/precisions. See c/README.md. +#![deny(unsafe_op_in_unsafe_fn)] +#![allow(clippy::missing_safety_doc, clippy::too_many_arguments)] +use rapier::math::{AngVector, Pose, Real, Rotation, Vector}; +use rapier::prelude::*; +use rapier_c_macros::rapier_export; +mod config_data; +pub use config_data::*; +mod joint_desc; +pub use joint_desc::*; +mod soft_desc; +pub use soft_desc::*; +mod world_queries; +pub use world_queries::*; +mod descriptors; +pub use descriptors::*; +mod control; +mod dynamics; +mod error; +mod geometry; +mod joints; +mod objects; +mod pipeline; +mod queries; +mod soft_body; +mod types; +pub use control::*; +pub use dynamics::*; +pub use error::*; +pub use geometry::*; +pub use joints::*; +pub use objects::*; +pub use pipeline::*; +pub use queries::*; +pub use soft_body::*; +pub use types::*; + +mod extra; +pub use extra::*; + +#[cfg(test)] +mod tests; + +mod render; +pub use render::*; + +#[cfg(all(feature = "robotics", feature = "dim3", feature = "f32"))] +mod robotics; +#[cfg(all(feature = "robotics", feature = "dim3", feature = "f32"))] +pub use robotics::*; + +#[cfg(test)] +mod pod_tests; + +mod handle_access; +pub use handle_access::*; + +mod array_views; +pub use array_views::*; + +mod shape_desc; +pub use shape_desc::*; + +mod soft_recipes; +pub use soft_recipes::*; + +mod scoped_access; +pub use scoped_access::*; + +mod joint_access; +pub use joint_access::*; + +mod geometry_views; +pub use geometry_views::*; + +mod world; +pub use world::*; +mod read_access; +pub use read_access::*; + +#[cfg(test)] +mod world_tests; + +mod return_values; +pub use return_values::*; + +mod handle_world; +use handle_world::*; + +#[cfg(test)] +mod owner_handle_tests; diff --git a/c/src/objects.rs b/c/src/objects.rs new file mode 100644 index 000000000..9f25cc9d1 --- /dev/null +++ b/c/src/objects.rs @@ -0,0 +1,346 @@ +use crate::*; +// Internal transparent adapter for native PhysicsWorld operations. +#[repr(transparent)] +pub(crate) struct RprPhysicsWorld(pub(crate) PhysicsWorld); + +// Internal transparent adapter for native PhysicsPipeline operations. +#[repr(transparent)] +pub(crate) struct RprPhysicsPipeline(pub(crate) PhysicsPipeline); + +// Internal transparent adapter for native IslandManager operations. +#[repr(transparent)] +pub(crate) struct RprIslandManager(pub(crate) IslandManager); + +// Internal transparent adapter for native BroadPhaseBvh operations. +#[repr(transparent)] +pub(crate) struct RprBroadPhaseBvh(pub(crate) BroadPhaseBvh); + +// Internal transparent adapter for native NarrowPhase operations. +#[repr(transparent)] +pub(crate) struct RprNarrowPhase(pub(crate) NarrowPhase); + +// Internal transparent adapter for native RigidBodySet operations. +#[repr(transparent)] +pub(crate) struct RprRigidBodySet(pub(crate) RigidBodySet); + +// Internal transparent adapter for native ColliderSet operations. +#[repr(transparent)] +pub(crate) struct RprColliderSet(pub(crate) ColliderSet); + +// Internal transparent adapter for native ImpulseJointSet operations. +#[repr(transparent)] +pub(crate) struct RprImpulseJointSet(pub(crate) ImpulseJointSet); + +// Internal transparent adapter for native MultibodyJointSet operations. +#[repr(transparent)] +pub(crate) struct RprMultibodyJointSet(pub(crate) MultibodyJointSet); + +// Internal transparent adapter for native SoftBodySet operations. +#[repr(transparent)] +pub(crate) struct RprSoftBodySet(pub(crate) SoftBodySet); + +// Internal transparent adapter for native IntegrationParameters operations. +#[repr(transparent)] +pub(crate) struct NativeIntegrationParameters(pub(crate) IntegrationParameters); + +// Internal transparent adapter for native RigidBody operations. +#[repr(transparent)] +pub(crate) struct RprRigidBody(pub(crate) RigidBody); + +// Internal transparent adapter for native Collider operations. +#[repr(transparent)] +pub(crate) struct RprCollider(pub(crate) Collider); + +// Internal transparent adapter for native GenericJoint operations. +#[repr(transparent)] +pub(crate) struct RprGenericJoint(pub(crate) GenericJoint); + +/// Opaque SharedShape. See the ownership and borrowing contract in README.md. +#[repr(transparent)] +pub struct RprSharedShape(pub(crate) SharedShape); +/// Frees an owned object; NULL is allowed. Never free a borrowed pointer. +#[rapier_export] +pub unsafe extern "C" fn rpr_free_shared_shape(object: *mut RprSharedShape) -> RprStatus { + ffi(|| unsafe { + if !object.is_null() { + get(object)?; + drop(Box::from_raw(object)); + } + Ok(()) + }) +} + +/// Creates an independent owned copy. +#[rapier_export(shared_shape)] +pub unsafe extern "C" fn rpr_shared_shape_clone( + object: *const RprSharedShape, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + let value = get(object)?.0.clone(); + output(out, Box::into_raw(Box::new(RprSharedShape(value)))) + }) + }) +} + +// Internal transparent adapter for native SoftBody operations. +#[repr(transparent)] +pub(crate) struct RprSoftBody(pub(crate) SoftBody); + +#[rapier_export] +pub unsafe extern "C" fn rpr_rigid_body_count(world: *const RprWorld) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_len( + std::ptr::addr_of!((*raw).0.bodies).cast(), + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_len( + object: *const RprRigidBodySet, + out: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.len()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_rigid_body_handles( + world: *const RprWorld, + buffer: *mut RprRigidBodyHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_handles( + std::ptr::addr_of!((*raw).0.bodies).cast(), + buffer, + capacity, + count, + )) + }) + }) + } +} + +pub(crate) unsafe fn native_rigid_body_set_handles( + set: *const RprRigidBodySet, + buffer: *mut RprRigidBodyHandle, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let values: Vec<_> = get(set)?.0.iter().map(|(h, _)| h.into()).collect(); + copy_out(&values, buffer, capacity, count) + }) +} + +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_contains(handle: RprRigidBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_contains( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_contains( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { output(out, get(set)?.0.get(handle.raw()).is_some() as u32) }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_collider_count(world: *const RprWorld) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_len( + std::ptr::addr_of!((*raw).0.colliders).cast(), + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_len( + object: *const RprColliderSet, + out: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let object = get(object)?; + output(out, object.0.len()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_collider_handles( + world: *const RprWorld, + buffer: *mut RprColliderHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_handles( + std::ptr::addr_of!((*raw).0.colliders).cast(), + buffer, + capacity, + count, + )) + }) + }) + } +} + +pub(crate) unsafe fn native_collider_set_handles( + set: *const RprColliderSet, + buffer: *mut RprColliderHandle, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let values: Vec<_> = get(set)?.0.iter().map(|(h, _)| h.into()).collect(); + copy_out(&values, buffer, capacity, count) + }) +} + +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_contains(handle: RprColliderHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_contains( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_contains( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { output(out, get(set)?.0.get(handle.raw()).is_some() as u32) }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_soft_body_count(world: *const RprWorld) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let object = get(object)?; + output(out, object.0.len()) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_soft_body_handles( + world: *const RprWorld, + buffer: *mut RprSoftBodyHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let values: Vec<_> = get(set)?.0.iter().map(|(h, _)| h.into()).collect(); + copy_out(&values, buffer, capacity, count) + }) + }) + } +} + +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_contains(handle: RprSoftBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + output(out, get(set)?.0.get(handle.raw()).is_some() as u32) + }) + }) +} + +/// Remove a body and its joints, optionally keeping colliders as standalone objects. +/// Returns whether a body was removed; a stale handle returns false without error. +#[rapier_export] +pub unsafe extern "C" fn rpr_remove_rigid_body( + handle: RprRigidBodyHandle, + remove_attached_colliders: RprBool, +) -> RprBool { + let world = handle.world; + ffi_value(|removed: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let remove = boolean(remove_attached_colliders)?; + if !removed.is_null() { + out_ptr(removed)?; + } + let access = get(world)?.write()?; + let world = &mut (*access.raw()).0; + if let Some(body) = world.bodies.get(handle.raw()) { + ensure( + body.soft_body().is_none() || body.is_soft_frame(), + "remove a soft-body root through RemoveSoftBody", + )?; + } + let did_remove = world + .remove_body_with_colliders(handle.raw(), remove) + .is_some(); + if !removed.is_null() { + output(removed, did_remove as RprBool)?; + } + Ok(()) + }) + }) +} diff --git a/c/src/owner_handle_tests.rs b/c/src/owner_handle_tests.rs new file mode 100644 index 000000000..db8cea10c --- /dev/null +++ b/c/src/owner_handle_tests.rs @@ -0,0 +1,263 @@ +use crate::*; +use std::ptr; + +fn ok() { + assert_eq!(rpr_last_status(), RPR_OK); +} + +#[test] +fn collider_insertion_checks_parent_and_preserves_world_on_failure() { + unsafe { + let world = rpr_new_world(); + ok(); + let parent = rpr_insert_rigid_body(world, &rpr_dynamic_rigid_body_desc()); + ok(); + let desc = rpr_ball_collider_desc(0.5); + let attached = rpr_insert_collider(parent, &desc); + ok(); + assert_eq!(rpr_collider_parent(attached), parent); + ok(); + let standalone = rpr_insert_collider_without_parent(world, &desc); + ok(); + assert_eq!(standalone.world, world); + assert_eq!( + rpr_collider_parent(standalone), + RprRigidBodyHandle::default() + ); + ok(); + assert_eq!(rpr_remove_rigid_body(parent, 1), 1); + ok(); + assert_eq!(rpr_collider_count(world), 1); + ok(); + let failed = rpr_insert_collider(parent, &desc); + assert_eq!(rpr_last_status(), RPR_INVALID_HANDLE); + assert_eq!(failed, RprColliderHandle::default()); + assert_eq!(rpr_collider_count(world), 1); + ok(); + let failed = rpr_insert_collider(RprRigidBodyHandle::default(), &desc); + assert_eq!(rpr_last_status(), RPR_INVALID_HANDLE); + assert_eq!(failed, RprColliderHandle::default()); + let failed = rpr_insert_collider_without_parent(ptr::null_mut(), &desc); + assert_eq!(rpr_last_status(), RPR_NULL_POINTER); + assert_eq!(failed, RprColliderHandle::default()); + assert_eq!(rpr_free_world(world), RPR_OK); + } +} + +#[test] +fn equal_native_indices_in_different_worlds_remain_distinct() { + unsafe { + let a = rpr_new_world(); + let b = rpr_new_world(); + let mut desc = rpr_dynamic_rigid_body_desc(); + desc.position.translation.x = 1.0; + let first_body = rpr_insert_rigid_body(a, &desc); + ok(); + let first_collider = rpr_insert_collider(first_body, &rpr_ball_collider_desc(0.5)); + ok(); + desc.position.translation.x = 7.0; + let other_body = rpr_insert_rigid_body(b, &desc); + ok(); + let other_collider = rpr_insert_collider(other_body, &rpr_ball_collider_desc(0.5)); + ok(); + assert_eq!(first_body.world, a); + assert_eq!(first_collider.world, a); + assert_eq!(other_body.world, b); + assert_eq!(first_body.index, other_body.index); + assert_eq!(first_body.generation, other_body.generation); + assert_ne!(first_body, other_body); + assert_eq!(rpr_rigid_body_translation(first_body).x, 1.0); + ok(); + assert_eq!(rpr_rigid_body_translation(other_body).x, 7.0); + ok(); + assert_eq!(rpr_collider_parent(first_collider), first_body); + ok(); + let failed = rpr_insert_impulse_joint(first_body, other_body, &rpr_fixed_joint_desc()); + assert_eq!(rpr_last_status(), RPR_INVALID_HANDLE); + assert_eq!(failed, RprImpulseJointHandle::default()); + assert_eq!(rpr_impulse_joint_handles(a, ptr::null_mut(), 0), 0); + ok(); + let attached = rpr_insert_collider(other_body, &rpr_ball_collider_desc(0.5)); + ok(); + assert_eq!(attached.world, b); + assert_eq!(rpr_collider_parent(attached), other_body); + ok(); + assert_eq!(rpr_collider_count(b), 2); + ok(); + assert_eq!(rpr_collider_count(a), 1); + ok(); + let mut options = rpr_default_query_options(); + options.filter.exclude_collider = other_collider; + rpr_cast_ray(a, &options, RprVector::default(), Vector::X.into(), 10.0, 1); + assert_eq!(rpr_last_status(), RPR_INVALID_HANDLE); + assert_eq!(rpr_free_world(a), RPR_OK); + assert_eq!(rpr_free_world(b), RPR_OK); + } +} + +#[test] +fn returned_arrays_joints_and_query_hits_keep_the_owner() { + unsafe { + let world = rpr_new_world(); + let fixed = rpr_insert_rigid_body(world, &rpr_fixed_rigid_body_desc()); + ok(); + let mut desc = rpr_dynamic_rigid_body_desc(); + desc.position.translation.x = 3.0; + let item_body = rpr_insert_rigid_body(world, &desc); + ok(); + let item_collider = rpr_insert_collider(item_body, &rpr_ball_collider_desc(0.5)); + ok(); + let joint = rpr_insert_impulse_joint(fixed, item_body, &rpr_fixed_joint_desc()); + ok(); + assert_eq!(joint.world, world); + let ends = rpr_impulse_joint_bodies(joint); + ok(); + assert_eq!(ends.body1, fixed); + assert_eq!(ends.body2, item_body); + let mut list = [RprRigidBodyHandle::default(); 2]; + assert_eq!( + rpr_rigid_body_handles(world, list.as_mut_ptr(), list.len()), + 2 + ); + ok(); + assert!(list.iter().all(|h| h.world == world)); + let sentinel = list[0]; + assert_eq!(rpr_rigid_body_handles(world, list.as_mut_ptr(), 1), 2); + assert_eq!(rpr_last_status(), RPR_BUFFER_TOO_SMALL); + assert_eq!(list[0], sentinel); + let mut colliders = [RprColliderHandle::default(); 1]; + assert_eq!( + rpr_rigid_body_colliders(item_body, colliders.as_mut_ptr(), 1), + 1 + ); + ok(); + assert_eq!(colliders[0], item_collider); + assert_eq!( + rpr_detect_collisions(world, ptr::null(), ptr::null()), + RPR_OK + ); + let hit = rpr_cast_ray( + world, + ptr::null(), + Vector::ZERO.into(), + Vector::X.into(), + 10.0, + 1, + ); + ok(); + assert_eq!(hit.collider, item_collider); + assert_eq!(rpr_remove_rigid_body(item_body, 1), 1); + ok(); + rpr_rigid_body_translation(item_body); + assert_eq!(rpr_last_status(), RPR_INVALID_HANDLE); + assert_eq!(rpr_free_world(world), RPR_OK); + } +} + +#[test] +fn restored_world_returns_fresh_owners_and_survives_source_destruction() { + unsafe { + let original = rpr_new_world(); + let mut desc = rpr_dynamic_rigid_body_desc(); + desc.position.translation.x = 5.0; + let old = rpr_insert_rigid_body(original, &desc); + ok(); + let snapshot = rpr_serialize_world(original); + ok(); + let bytes = rpr_bytes_data(snapshot); + ok(); + let restored = rpr_deserialize_world(bytes.data, bytes.count); + ok(); + let mut fresh = RprRigidBodyHandle::default(); + assert_eq!(rpr_rigid_body_handles(restored, &mut fresh, 1), 1); + ok(); + assert_eq!(fresh.world, restored); + assert_eq!(fresh.index, old.index); + assert_eq!(fresh.generation, old.generation); + assert_ne!(fresh, old); + assert_eq!(rpr_free_world(original), RPR_OK); + assert_eq!(rpr_rigid_body_translation(fresh).x, 5.0); + ok(); + assert_eq!(rpr_free_bytes(snapshot), RPR_OK); + assert_eq!(rpr_free_world(restored), RPR_OK); + } +} + +#[test] +fn callback_and_accumulated_event_handles_keep_their_original_worlds() { + unsafe extern "C" fn filter( + data: *mut std::ffi::c_void, + read: *const RprReadContext, + a: RprColliderHandle, + b: RprColliderHandle, + body_a: RprRigidBodyHandle, + body_b: RprRigidBodyHandle, + ) -> i32 { + let world = data.cast::(); + assert_eq!(a.world, world); + assert_eq!(b.world, world); + assert_eq!(body_a.world, world); + assert_eq!(body_b.world, world); + unsafe { + rpr_read_collider_translation(read, a); + } + ok(); + unsafe { + rpr_collider_translation(a); + } + assert_eq!(rpr_last_status(), RPR_WORLD_BUSY); + 1 + } + unsafe { + let events = rpr_new_event_collector(); + ok(); + let worlds = [rpr_new_world(), rpr_new_world()]; + for &world in &worlds { + let mut shape = rpr_ball_collider_desc(1.0); + shape.activeEvents = RPR_COLLISION_EVENTS; + shape.activeHooks = RPR_FILTER_CONTACT_PAIRS; + let fixed = rpr_insert_rigid_body(world, &rpr_fixed_rigid_body_desc()); + ok(); + rpr_insert_collider(fixed, &shape); + ok(); + let dynamic = rpr_insert_rigid_body(world, &rpr_dynamic_rigid_body_desc()); + ok(); + rpr_insert_collider(dynamic, &shape); + ok(); + let hooks = RprPhysicsHooks { + user_data: world.cast(), + filter_contact_pair: Some(filter), + ..Default::default() + }; + assert_eq!(rpr_step(world, &hooks, events), RPR_OK); + } + let mut copied = [RprCollisionEvent::default(); 2]; + assert_eq!( + rpr_event_collector_collision_events(events, copied.as_mut_ptr(), 2), + 2 + ); + ok(); + for (e, &world) in copied.iter().zip(&worlds) { + assert_eq!(e.collider1.world, world); + assert_eq!(e.collider2.world, world); + assert_eq!(rpr_collider_contains(e.collider1), 1); + ok(); + } + assert_eq!(rpr_free_event_collector(events), RPR_OK); + for world in worlds { + assert_eq!(rpr_free_world(world), RPR_OK); + } + } +} + +#[test] +fn invalid_event_alignment_is_reported_before_reading_owner() { + unsafe { + let event = ptr::without_provenance::(1); + assert_eq!( + rpr_soft_body_tear_event_soft_body(event), + RprSoftBodyHandle::default() + ); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + } +} diff --git a/c/src/pipeline.rs b/c/src/pipeline.rs new file mode 100644 index 000000000..a960defe6 --- /dev/null +++ b/c/src/pipeline.rs @@ -0,0 +1,2153 @@ +use crate::*; +#[rapier_export] +pub unsafe extern "C" fn rpr_time_step(world: *const RprWorld) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.dt) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_time_step(world: *mut RprWorld, value: RprReal) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + positive(value)?; + get_mut(object)?.0.dt = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_min_ccd_dt(world: *const RprWorld) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.min_ccd_dt) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_min_ccd_dt(world: *mut RprWorld, value: RprReal) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + nonnegative(value)?; + get_mut(object)?.0.min_ccd_dt = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_length_unit(world: *const RprWorld) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.length_unit) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_length_unit(world: *mut RprWorld, value: RprReal) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + positive(value)?; + get_mut(object)?.0.length_unit = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_warmstart_coefficient(world: *const RprWorld) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.warmstart_coefficient) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_warmstart_coefficient( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + nonnegative(value)?; + ensure(value <= 1.0, "warmstart coefficient must be <= 1")?; + get_mut(object)?.0.warmstart_coefficient = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_normalized_allowed_linear_error(world: *const RprWorld) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.normalized_allowed_linear_error) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_normalized_allowed_linear_error( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + nonnegative(value)?; + get_mut(object)?.0.normalized_allowed_linear_error = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_normalized_max_corrective_velocity(world: *const RprWorld) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.normalized_max_corrective_velocity) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_normalized_max_corrective_velocity( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + nonnegative(value)?; + get_mut(object)?.0.normalized_max_corrective_velocity = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_normalized_prediction_distance(world: *const RprWorld) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.normalized_prediction_distance) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_normalized_prediction_distance( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + nonnegative(value)?; + get_mut(object)?.0.normalized_prediction_distance = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_normalized_max_linear_velocity(world: *const RprWorld) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.normalized_max_linear_velocity) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_normalized_max_linear_velocity( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + nonnegative(value)?; + get_mut(object)?.0.normalized_max_linear_velocity = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_normalized_contact_recycle_distance( + world: *const RprWorld, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.normalized_contact_recycle_distance) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_normalized_contact_recycle_distance( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + nonnegative(value)?; + get_mut(object)?.0.normalized_contact_recycle_distance = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_num_solver_iterations(world: *const RprWorld) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.num_solver_iterations) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_num_solver_iterations( + world: *mut RprWorld, + value: usize, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + ensure(value > 0, "iteration count must be positive")?; + get_mut(object)?.0.num_solver_iterations = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_num_internal_pgs_iterations(world: *const RprWorld) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.num_internal_pgs_iterations) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_num_internal_pgs_iterations( + world: *mut RprWorld, + value: usize, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + ensure(value > 0, "iteration count must be positive")?; + get_mut(object)?.0.num_internal_pgs_iterations = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_num_internal_stabilization_iterations( + world: *const RprWorld, +) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.num_internal_stabilization_iterations) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_num_internal_stabilization_iterations( + world: *mut RprWorld, + value: usize, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + get_mut(object)?.0.num_internal_stabilization_iterations = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_max_ccd_substeps(world: *const RprWorld) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.max_ccd_substeps) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_max_ccd_substeps(world: *mut RprWorld, value: usize) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + get_mut(object)?.0.max_ccd_substeps = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_contact_clustering(world: *const RprWorld) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.contact_clustering as u32) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_contact_clustering( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(object)?.0.contact_clustering = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_contact_recycling(world: *const RprWorld) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.contact_recycling as u32) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_contact_recycling( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(object)?.0.contact_recycling = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_friction_in_bias_pass(world: *const RprWorld) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.friction_in_bias_pass as u32) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_friction_in_bias_pass( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(object)?.0.friction_in_bias_pass = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_warmstart_joints(world: *const RprWorld) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.warmstart_joints as u32) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_warmstart_joints( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(object)?.0.warmstart_joints = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_contact_softness(world: *const RprWorld) -> RprSpringCoefficients { + ffi_value(|out: *mut RprSpringCoefficients| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.contact_softness.into()) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_contact_softness( + world: *mut RprWorld, + value: RprSpringCoefficients, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = value.raw()?; + get_mut(object)?.0.contact_softness = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_static_contact_softness( + world: *const RprWorld, +) -> RprSpringCoefficients { + ffi_value(|out: *mut RprSpringCoefficients| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let object: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + + let object = get(object)?; + output(out, object.0.static_contact_softness.into()) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_static_contact_softness( + world: *mut RprWorld, + value: RprSpringCoefficients, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let object: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = value.raw()?; + get_mut(object)?.0.static_contact_softness = value; + Ok(()) + }) +} + +use bincode::Options; +use rapier::geometry::{ContactForceEvent, ContactPair, SolverFlags}; +use rapier::pipeline::{ContactModificationContext, PairFilterContext}; +use std::{ffi::c_void, sync::Mutex}; + +/// Collision start/stop flags match Rapier CollisionEventFlags. +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprCollisionEvent { + pub collider1: RprColliderHandle, + pub collider2: RprColliderHandle, + pub started: RprBool, + pub flags: u32, +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprContactForceEvent { + pub collider1: RprColliderHandle, + pub collider2: RprColliderHandle, + pub total_force: RprVector, + pub total_force_magnitude: RprReal, + pub max_force_direction: RprVector, + pub max_force_magnitude: RprReal, + pub started: RprBool, +} +/// Events accumulate until clear. Copying events never drains them, allowing two-call buffer sizing. +#[derive(Default)] +pub struct RprEventCollector { + collisions: Mutex>, + forces: Mutex>, + tears: Mutex>, +} +struct WorldEvents<'a> { + world: *mut RprWorld, + events: &'a RprEventCollector, +} +// The address is only copied into handles; the step holds the world write guard. +unsafe impl Sync for WorldEvents<'_> {} +impl EventHandler for WorldEvents<'_> { + fn handle_collision_event( + &self, + _: &RigidBodySet, + _: &ColliderSet, + event: CollisionEvent, + _: Option<&ContactPair>, + ) { + let (a, b, started, flags) = match event { + CollisionEvent::Started(a, b, f) => (a, b, 1, f.bits()), + CollisionEvent::Stopped(a, b, f) => (a, b, 0, f.bits()), + }; + self.events + .collisions + .lock() + .unwrap() + .push(RprCollisionEvent { + collider1: RprColliderHandle::from(a).with_world(self.world), + collider2: RprColliderHandle::from(b).with_world(self.world), + started, + flags, + }); + } + fn handle_contact_force_event( + &self, + dt: Real, + _: &RigidBodySet, + _: &ColliderSet, + pair: &ContactPair, + magnitude: Real, + ) { + let e = ContactForceEvent::from_contact_pair(dt, pair, magnitude); + self.events + .forces + .lock() + .unwrap() + .push(RprContactForceEvent { + collider1: RprColliderHandle::from(e.collider1).with_world(self.world), + collider2: RprColliderHandle::from(e.collider2).with_world(self.world), + total_force: e.total_force.into(), + total_force_magnitude: e.total_force_magnitude, + max_force_direction: e.max_force_direction.into(), + max_force_magnitude: e.max_force_magnitude, + started: e.started as u32, + }); + } + fn handle_soft_body_tear_event(&self, _: &SoftBodySet, event: &SoftBodyTearEvent) { + self.events + .tears + .lock() + .unwrap() + .push(RprSoftBodyTearEvent(event.clone(), self.world)); + } +} +/// Pair callback: -1 rejects a contact pair; 0 detects contacts without impulses; 1 computes impulses. +/// For sensor intersections only, zero rejects and any positive value accepts. +pub type RprPairFilter = Option< + unsafe extern "C" fn( + user_data: *mut c_void, + read: *const RprReadContext, + collider1: RprColliderHandle, + collider2: RprColliderHandle, + body1: RprRigidBodyHandle, + body2: RprRigidBodyHandle, + ) -> i32, +>; +/// Mutable per-manifold properties. Set enabled=0 to discard all its solver contacts. +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprContactModification { + pub normal: RprVector, + pub friction: RprReal, + pub restitution: RprReal, + pub user_data: u32, + pub enabled: RprBool, +} +pub type RprModifyContacts = Option< + unsafe extern "C" fn( + user_data: *mut c_void, + read: *const RprReadContext, + collider1: RprColliderHandle, + collider2: RprColliderHandle, + contact: *mut RprContactModification, + ), +>; +/// Borrowed native contact context. Valid only during its callback; never retain or free it. +pub struct RprContactModificationContext { + raw: *mut c_void, +} +pub type RprModifyContactContext = Option< + unsafe extern "C" fn( + user_data: *mut c_void, + read: *const RprReadContext, + collider1: RprColliderHandle, + collider2: RprColliderHandle, + context: *mut RprContactModificationContext, + ), +>; + +/// Callbacks must not unwind or retain arguments. Use their ReadContext to inspect bodies and +/// colliders; ordinary access to the stepping world returns WORLD_BUSY. Mutations must be +/// performed after stepping. With parallel builds +/// callbacks and their user_data must be safe for concurrent invocation. NULL callbacks use defaults. +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprPhysicsHooks { + pub user_data: *mut c_void, + pub filter_contact_pair: RprPairFilter, + pub filter_intersection_pair: RprPairFilter, + pub modify_solver_contacts: RprModifyContacts, + /// Runs after the legacy property callback. Context accessors may be called here. + pub modify_solver_contacts_context: RprModifyContactContext, +} +// SAFETY: The public callback contract requires thread-safe callbacks and user_data in parallel builds. +unsafe impl Sync for RprPhysicsHooks {} +struct WorldHooks { + world: *mut RprWorld, + hooks: RprPhysicsHooks, +} +// Callback data follows RprPhysicsHooks' thread-safety contract; the address is metadata. +unsafe impl Sync for WorldHooks {} +impl PhysicsHooks for WorldHooks { + fn filter_contact_pair(&self, c: &PairFilterContext) -> Option { + let Some(f) = self.hooks.filter_contact_pair else { + return Some(SolverFlags::COMPUTE_RIGID_IMPULSES); + }; + let result = unsafe { + f( + self.hooks.user_data, + &RprReadContext::new(self.world, c.bodies, c.colliders), + RprColliderHandle::from(c.collider1).with_world(self.world), + RprColliderHandle::from(c.collider2).with_world(self.world), + c.rigid_body1 + .map(|h| RprRigidBodyHandle::from(h).with_world(self.world)) + .unwrap_or_default(), + c.rigid_body2 + .map(|h| RprRigidBodyHandle::from(h).with_world(self.world)) + .unwrap_or_default(), + ) + }; + match result { + v if v < 0 => None, + 0 => Some(SolverFlags::empty()), + _ => Some(SolverFlags::COMPUTE_RIGID_IMPULSES), + } + } + fn filter_intersection_pair(&self, c: &PairFilterContext) -> bool { + self.hooks.filter_intersection_pair.is_none_or(|f| unsafe { + f( + self.hooks.user_data, + &RprReadContext::new(self.world, c.bodies, c.colliders), + RprColliderHandle::from(c.collider1).with_world(self.world), + RprColliderHandle::from(c.collider2).with_world(self.world), + c.rigid_body1 + .map(|h| RprRigidBodyHandle::from(h).with_world(self.world)) + .unwrap_or_default(), + c.rigid_body2 + .map(|h| RprRigidBodyHandle::from(h).with_world(self.world)) + .unwrap_or_default(), + ) > 0 + }) + } + fn modify_solver_contacts(&self, c: &mut ContactModificationContext) { + if let Some(f) = self.hooks.modify_solver_contacts { + let read = RprReadContext::new(self.world, c.bodies, c.colliders); + let a = RprColliderHandle::from(c.collider1).with_world(self.world); + let b = RprColliderHandle::from(c.collider2).with_world(self.world); + if let Some(m) = c.rigid_mut() { + let mut value = RprContactModification { + normal: (*m.normal).into(), + friction: *m.friction, + restitution: *m.restitution, + user_data: *m.user_data, + enabled: 1, + }; + unsafe { f(self.hooks.user_data, &read, a, b, &mut value) }; + // Invalid callback values leave the original manifold unchanged. + if let (Ok(n), Ok(fr), Ok(re), Ok(enabled)) = ( + value.normal.raw(), + nonnegative(value.friction), + nonnegative(value.restitution), + boolean(value.enabled), + ) { + if n.length_squared().is_finite() && n.length_squared() > 1.0e-20 { + *m.normal = n.normalize(); + *m.friction = fr; + *m.restitution = re; + *m.user_data = value.user_data; + if !enabled { + m.solver_contacts.clear(); + } + } + } + } + } + if let Some(callback) = self.hooks.modify_solver_contacts_context { + let mut context = RprContactModificationContext { + raw: (c as *mut ContactModificationContext<'_>).cast(), + }; + unsafe { + callback( + self.hooks.user_data, + &RprReadContext::new(self.world, c.bodies, c.colliders), + RprColliderHandle::from(c.collider1).with_world(self.world), + RprColliderHandle::from(c.collider2).with_world(self.world), + &mut context, + ) + }; + } + } +} + +/// Applies Rapier's persistent one-way platform logic to the borrowed manifold. +#[rapier_export(contact_modification_context)] +pub unsafe extern "C" fn rpr_contact_modification_context_update_as_oneway_platform( + context: *mut RprContactModificationContext, + allowed_local_n1: RprVector, + allowed_angle: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let normal = allowed_local_n1.raw()?; + let angle = nonnegative(allowed_angle)?; + let raw = get_mut(context)? + .raw + .cast::>(); + (*raw).update_as_oneway_platform(normal, angle); + Ok(()) + }) +} + +/// Sets the tangent velocity of every rigid solver contact in this manifold. +#[rapier_export(contact_modification_context)] +pub unsafe extern "C" fn rpr_contact_modification_context_set_tangent_velocity( + context: *mut RprContactModificationContext, + velocity: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let velocity = velocity.raw()?; + let raw = get_mut(context)? + .raw + .cast::>(); + if let Some(rigid) = (*raw).rigid_mut() { + for contact in rigid.solver_contacts.iter_mut() { + contact.tangent_velocity = velocity; + } + } + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_new_event_collector() -> *mut RprEventCollector { + ffi_value(|out: *mut *mut RprEventCollector| { + ffi(|| unsafe { + out_ptr(out)?; + output(out, Box::into_raw(Box::new(RprEventCollector::default()))) + }) + }) +} +#[rapier_export] +pub unsafe extern "C" fn rpr_free_event_collector(events: *mut RprEventCollector) -> RprStatus { + ffi(|| unsafe { + if !events.is_null() { + get(events)?; + drop(Box::from_raw(events)); + } + Ok(()) + }) +} +#[rapier_export(event_collector)] +pub unsafe extern "C" fn rpr_event_collector_clear(events: *mut RprEventCollector) -> RprStatus { + ffi(|| unsafe { + let e = get_mut(events)?; + e.collisions.get_mut().unwrap().clear(); + e.forces.get_mut().unwrap().clear(); + e.tears.get_mut().unwrap().clear(); + Ok(()) + }) +} +#[rapier_export(event_collector)] +pub unsafe extern "C" fn rpr_event_collector_collision_events( + events: *const RprEventCollector, + buffer: *mut RprCollisionEvent, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + copy_out( + &get(events)?.collisions.lock().unwrap(), + buffer, + capacity, + count, + ) + }) + }) +} +#[rapier_export(event_collector)] +pub unsafe extern "C" fn rpr_event_collector_contact_force_events( + events: *const RprEventCollector, + buffer: *mut RprContactForceEvent, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + copy_out( + &get(events)?.forces.lock().unwrap(), + buffer, + capacity, + count, + ) + }) + }) +} +#[rapier_export(event_collector)] +pub unsafe extern "C" fn rpr_event_collector_tear_event_count( + events: *const RprEventCollector, +) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { output(out, get(events)?.tears.lock().unwrap().len()) }) + }) +} +/// Owned copy of a tear event. Read particle remapping before rebuilding render meshes. +#[derive(Clone)] +pub struct RprSoftBodyTearEvent(pub(crate) SoftBodyTearEvent, pub(crate) *mut RprWorld); +// Owned event data plus a non-owning world address, never dereferenced by the event. +unsafe impl Send for RprSoftBodyTearEvent {} +unsafe impl Sync for RprSoftBodyTearEvent {} +#[rapier_export(event_collector)] +pub unsafe extern "C" fn rpr_event_collector_tear_event( + events: *const RprEventCollector, + index: usize, +) -> *mut RprSoftBodyTearEvent { + ffi_value(|out: *mut *mut RprSoftBodyTearEvent| { + ffi(|| unsafe { + out_ptr(out)?; + let e = get(events)? + .tears + .lock() + .unwrap() + .get(index) + .ok_or_else(|| invalid("event index out of range"))? + .clone(); + output(out, Box::into_raw(Box::new(e))) + }) + }) +} +#[rapier_export] +pub unsafe extern "C" fn rpr_gravity(world: *const RprWorld) -> RprVector { + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let world: *const RprPhysicsWorld = raw; + output(out, get(world)?.0.gravity.into()) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_set_gravity(world: *mut RprWorld, value: RprVector) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + let value = value.raw()?; + get_mut(world)?.0.gravity = value; + Ok(()) + }) +} + +/// Hooks and events may be NULL. This call invalidates all borrowed set-element pointers. +/// Advance simulation by one timestep. Hooks and events may be NULL. +#[rapier_export] +pub unsafe extern "C" fn rpr_step( + world: *mut RprWorld, + hooks: *const RprPhysicsHooks, + events: *const RprEventCollector, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let hooks = if hooks.is_null() { + RprPhysicsHooks::default() + } else { + *get(hooks)? + }; + let hooks = WorldHooks { world, hooks }; + let events = if events.is_null() { + None + } else { + Some(WorldEvents { + world, + events: get(events)?, + }) + }; + let events: &dyn EventHandler = events.as_ref().map_or(&() as &dyn EventHandler, |e| e); + (*access.raw()).0.step_with_events(&hooks, events); + Ok(()) + }) +} + +/// Refresh collision detection without advancing simulation. Hooks and events may be NULL. +#[rapier_export] +pub unsafe extern "C" fn rpr_detect_collisions( + world: *mut RprWorld, + hooks: *const RprPhysicsHooks, + events: *const RprEventCollector, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let hooks = if hooks.is_null() { + RprPhysicsHooks::default() + } else { + *get(hooks)? + }; + let hooks = WorldHooks { world, hooks }; + let events = if events.is_null() { + None + } else { + Some(WorldEvents { + world, + events: get(events)?, + }) + }; + let events: &dyn EventHandler = events.as_ref().map_or(&() as &dyn EventHandler, |e| e); + (*access.raw()).0.detect_collisions(&hooks, events); + Ok(()) + }) +} + +/// Immutable owned byte buffer. Release with the matching FreeBytes function. +pub struct RprBytes(Vec); +#[rapier_export(bytes)] +pub unsafe extern "C" fn rpr_bytes_data(bytes: *const RprBytes) -> RprByteView { + ffi_value(|result: *mut RprByteView| { + let data = unsafe { std::ptr::addr_of_mut!((*result).data) }; + let count = unsafe { std::ptr::addr_of_mut!((*result).count) }; + + ffi(|| unsafe { + out_ptr(data)?; + out_ptr(count)?; + let b = get(bytes)?; + output(data, b.0.as_ptr())?; + output(count, b.0.len()) + }) + }) +} +#[rapier_export] +pub unsafe extern "C" fn rpr_free_bytes(bytes: *mut RprBytes) -> RprStatus { + ffi(|| unsafe { + if !bytes.is_null() { + get(bytes)?; + drop(Box::from_raw(bytes)); + } + Ok(()) + }) +} +// Wire format: magic, then little-endian u32 ABI, dimension, and scalar size. +// The distinct magic prevents interpreting the old six-byte header as this format. +const SNAPSHOT_HEADER_LEN: usize = 16; + +fn snapshot_header(abi_version: u32) -> [u8; SNAPSHOT_HEADER_LEN] { + let build = rpr_build_info(); + let mut header = [0; SNAPSHOT_HEADER_LEN]; + header[..4].copy_from_slice(b"RPRS"); + header[4..8].copy_from_slice(&abi_version.to_le_bytes()); + header[8..12].copy_from_slice(&build.dimension.to_le_bytes()); + header[12..16].copy_from_slice(&build.real_size.to_le_bytes()); + header +} + +fn snapshot_options() -> impl Options { + bincode::DefaultOptions::new() + .with_fixint_encoding() + .with_limit(256 * 1024 * 1024) + .reject_trailing_bytes() +} +#[rapier_export] +pub unsafe extern "C" fn rpr_serialize_world(world: *const RprWorld) -> *mut RprBytes { + ffi_value(|out: *mut *mut RprBytes| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let world: *const RprPhysicsWorld = raw; + + out_ptr(out)?; + let mut bytes = snapshot_header(RPR_ABI_VERSION).to_vec(); + bytes.extend( + snapshot_options() + .serialize(&get(world)?.0) + .map_err(|e| invalid(e.to_string()))?, + ); + output(out, Box::into_raw(Box::new(RprBytes(bytes)))) + }) + }) +} + +/// Restore ONLY trusted snapshots produced by the identical Rapier build. Snapshots are not a stable file format. +#[rapier_export] +pub unsafe extern "C" fn rpr_deserialize_world(data: *const u8, count: usize) -> *mut RprWorld { + ffi_value(|out: *mut *mut RprWorld| { + ffi(|| unsafe { + out_ptr(out)?; + ensure(count <= 256 * 1024 * 1024, "snapshot exceeds 256 MiB")?; + let bytes = input(data, count)?; + ensure( + bytes.starts_with(&snapshot_header(RPR_ABI_VERSION)), + "incompatible snapshot", + )?; + let world = snapshot_options() + .deserialize(&bytes[SNAPSHOT_HEADER_LEN..]) + .map_err(|e| invalid(e.to_string()))?; + output( + out, + Box::into_raw(Box::new(RprWorld::new(RprPhysicsWorld(world)))), + ) + }) + }) +} + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprDebugLine { + pub a: RprVector, + pub b: RprVector, + pub color: [f32; 4], +} +struct Lines(Vec); +impl rapier::pipeline::DebugRenderBackend for Lines { + fn draw_line( + &mut self, + _: rapier::pipeline::DebugRenderObject, + a: Vector, + b: Vector, + color: [f32; 4], + ) { + self.0.push(RprDebugLine { + a: a.into(), + b: b.into(), + color, + }); + } +} +/// Color is HSLA (hue in degrees), matching Rapier DebugColor. mode uses DebugRenderMode bits. +#[rapier_export] +pub unsafe extern "C" fn rpr_debug_render( + world: *const RprWorld, + mode: u32, + buffer: *mut RprDebugLine, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let world: *const RprPhysicsWorld = raw; + + let mode = rapier::pipeline::DebugRenderMode::from_bits(mode) + .ok_or_else(|| invalid("unknown debug render flags"))?; + let mut pipeline = rapier::pipeline::DebugRenderPipeline::new(Default::default(), mode); + let mut lines = Lines(Vec::new()); + get(world)?.0.debug_render(&mut pipeline, &mut lines); + copy_out(&lines.0, buffer, capacity, count) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_soft_bodies_set_resweep_strain( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + nonnegative(value)?; + get_mut(parameters)?.0.soft_bodies.resweep_strain = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_soft_bodies_resweep_strain(world: *const RprWorld) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let parameters: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + output(out, get(parameters)?.0.soft_bodies.resweep_strain) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_soft_bodies_set_contact_stiffening( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + nonnegative(value)?; + get_mut(parameters)?.0.soft_bodies.contact_stiffening = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_soft_bodies_contact_stiffening(world: *const RprWorld) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let parameters: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + output(out, get(parameters)?.0.soft_bodies.contact_stiffening) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_soft_bodies_set_max_extra_substeps( + world: *mut RprWorld, + value: usize, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + get_mut(parameters)?.0.soft_bodies.max_extra_substeps = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_soft_bodies_max_extra_substeps(world: *const RprWorld) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let parameters: *const NativeIntegrationParameters = + std::ptr::addr_of!((*raw).0.integration_parameters).cast(); + output(out, get(parameters)?.0.soft_bodies.max_extra_substeps) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_authored_velocity_margin( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .authored_velocity_margin = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_edge_speculation( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)?.0.soft_bodies.recovery.edge_speculation = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_inverted_cell_detection( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .inverted_cell_detection = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_self_crossing_detection( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .self_crossing_detection = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_detection_motion_gating( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .detection_motion_gating = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_cross_body_detection( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .cross_body_detection = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_self_stand_down( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)?.0.soft_bodies.recovery.self_stand_down = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_cross_body_expel_gate( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .cross_body_expel_gate = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_edge_stand_down( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)?.0.soft_bodies.recovery.edge_stand_down = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_crossing_repulsion( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .crossing_repulsion = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_crossing_repulsion_guide( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .crossing_repulsion_guide = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_crossing_repulsion_self_guide( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .crossing_repulsion_self_guide = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_recovery_pace( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = nonnegative(value)?; + get_mut(parameters)?.0.soft_bodies.recovery.recovery_pace = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_overlap_constraints( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .overlap_constraints = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_overlap_rigid( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)?.0.soft_bodies.recovery.overlap_rigid = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_overlap_skip_self_tangled( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .overlap_skip_self_tangled = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_overlap_edge_stand_down( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .overlap_edge_stand_down = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_overlap_constraint_pace( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = nonnegative(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .overlap_constraint_pace = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_overlap_skin_volume( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .overlap_skin_volume = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_overlap_kept_depth( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = nonnegative(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .overlap_kept_depth = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_overlap_self_regions( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .overlap_self_regions = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_overlap_normal_push( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .overlap_normal_push = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_overlap_multi_volume( + world: *mut RprWorld, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = boolean(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .overlap_multi_volume = value; + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_recovery_set_overlap_progress_margin( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + let value = nonnegative(value)?; + get_mut(parameters)? + .0 + .soft_bodies + .recovery + .overlap_progress_margin = value; + Ok(()) + }) +} + +#[cfg(feature = "fem")] +#[rapier_export] +pub unsafe extern "C" fn rpr_fem_set_linear_tolerance( + world: *mut RprWorld, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + positive(value)?; + get_mut(parameters)?.0.soft_bodies.fem.linear_tolerance = value; + Ok(()) + }) +} + +#[cfg(feature = "fem")] +#[rapier_export] +pub unsafe extern "C" fn rpr_fem_set_max_linear_iterations( + world: *mut RprWorld, + value: usize, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + ensure(value > 0, "iterations must be positive")?; + get_mut(parameters)?.0.soft_bodies.fem.max_linear_iterations = value; + Ok(()) + }) +} + +#[cfg(feature = "fem")] +#[rapier_export] +pub unsafe extern "C" fn rpr_fem_set_max_dense_dofs( + world: *mut RprWorld, + value: usize, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let parameters: *mut NativeIntegrationParameters = + std::ptr::addr_of_mut!((*raw).0.integration_parameters).cast(); + + get_mut(parameters)?.0.soft_bodies.fem.max_dense_dofs = value; + Ok(()) + }) +} + +/// Configures a dedicated pool for this world's parallel work. Zero selects Rayon's default. +/// Takes effect on the next step. Reconfiguration must not race with a step or callback. +/// Returns RPR_UNSUPPORTED in builds without the parallel feature; keeps the previous +/// pool when constructing the new one fails. The pool is not included in snapshots. +#[rapier_export] +pub unsafe extern "C" fn rpr_set_num_threads( + world: *mut RprWorld, + num_threads: usize, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + let world = get_mut(world)?; + #[cfg(feature = "parallel")] + { + world + .0 + .configure_thread_pool(num_threads) + .map_err(|e| invalid(e.to_string())) + } + #[cfg(not(feature = "parallel"))] + { + let _ = (world, num_threads); + Err(( + RPR_UNSUPPORTED, + "thread pools require a Rapier library built with the parallel feature".into(), + )) + } + }) +} + +/// Removes the world's dedicated pool. A parallel build then uses the calling +/// context's Rayon pool (normally the global pool), not a single worker. +/// Returns RPR_UNSUPPORTED in a build without the parallel feature. +#[rapier_export] +pub unsafe extern "C" fn rpr_clear_thread_pool(world: *mut RprWorld) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + let world = get_mut(world)?; + #[cfg(feature = "parallel")] + { + world.0.clear_thread_pool(); + Ok(()) + } + #[cfg(not(feature = "parallel"))] + { + let _ = world; + Err(( + RPR_UNSUPPORTED, + "thread pools require a Rapier library built with the parallel feature".into(), + )) + } + }) +} + +/// Size of the world's dedicated pool, or zero if a parallel build has no dedicated +/// pool configured. Returns one for a build without the parallel feature. +#[rapier_export] +pub unsafe extern "C" fn rpr_num_threads(world: *const RprWorld) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let world: *const RprPhysicsWorld = raw; + + let world = get(world)?; + #[cfg(feature = "parallel")] + let count = world.0.num_threads().unwrap_or(0); + #[cfg(not(feature = "parallel"))] + let count = { + let _ = world; + 1 + }; + output(out, count) + }) + }) +} + +/// Enable or disable the native pipeline profiling counters. Enabling returns +/// RPR_UNSUPPORTED if the library was built without the profiler feature. +#[rapier_export] +pub unsafe extern "C" fn rpr_set_counters_enabled( + world: *mut RprWorld, + enabled: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let pipeline: *mut RprPhysicsPipeline = + std::ptr::addr_of_mut!((*raw).0.physics_pipeline).cast(); + + let enabled = boolean(enabled)?; + let counters = &mut get_mut(pipeline)?.0.counters; + if enabled && !cfg!(feature = "profiler") { + return Err(( + RPR_UNSUPPORTED, + "profiling requires the profiler feature".into(), + )); + } + if enabled { + counters.enable(); + } else { + counters.disable(); + } + Ok(()) + }) +} + +/// Native engine time of the most recent step, in milliseconds, as in the Rust testbed. +/// Enable counters before stepping. Excludes C callbacks outside the step, rendering, +/// and dispatch into a dedicated thread pool; remains unchanged while paused. +#[rapier_export] +pub unsafe extern "C" fn rpr_step_time_ms(world: *const RprWorld) -> f64 { + ffi_value(|out: *mut f64| { + ffi(|| unsafe { + let access = get(world)?.read()?; + let raw = access.raw(); + + let pipeline: *const RprPhysicsPipeline = + std::ptr::addr_of!((*raw).0.physics_pipeline).cast(); + output(out, get(pipeline)?.0.counters.step_time_ms()) + }) + }) +} + +/// Read ONLY trusted legacy rigid-world snapshots from debug_deserialize3.rs, +/// produced by the identical Rapier build. This is not a stable interchange format. + +#[rapier_export] +pub unsafe extern "C" fn rpr_deserialize_rigid_state( + data: *const u8, + count: usize, +) -> *mut RprWorld { + ffi_value(|out: *mut *mut RprWorld| { + #[derive(serde::Deserialize)] + struct PhysicsState { + gravity: Vector, + integration_parameters: IntegrationParameters, + islands: IslandManager, + broad_phase: BroadPhaseBvh, + narrow_phase: NarrowPhase, + bodies: RigidBodySet, + colliders: ColliderSet, + impulse_joints: ImpulseJointSet, + multibody_joints: MultibodyJointSet, + } + ffi(|| unsafe { + out_ptr(out)?; + ensure(count <= 256 * 1024 * 1024, "snapshot exceeds 256 MiB")?; + let bytes = input(data, count)?; + let state: PhysicsState = bincode::DefaultOptions::new() + .with_fixint_encoding() + .allow_trailing_bytes() + .with_limit(256 * 1024 * 1024) + .deserialize(bytes) + .map_err(|e| invalid(e.to_string()))?; + let mut world = PhysicsWorld::new(); + world.gravity = state.gravity; + world.integration_parameters = state.integration_parameters; + world.islands = state.islands; + world.broad_phase = state.broad_phase; + world.narrow_phase = state.narrow_phase; + world.bodies = state.bodies; + world.colliders = state.colliders; + world.impulse_joints = state.impulse_joints; + world.multibody_joints = state.multibody_joints; + output( + out, + Box::into_raw(Box::new(RprWorld::new(RprPhysicsWorld(world)))), + ) + }) + }) +} + +#[cfg(test)] +mod snapshot_header_tests { + use super::*; + + #[test] + fn snapshot_header_preserves_full_abi_version() { + for version in [1, 255, 256, 257, 0x0102_0304, u32::MAX] { + let header = snapshot_header(version); + assert_eq!(&header[..4], b"RPRS"); + assert_eq!( + u32::from_le_bytes(header[4..8].try_into().unwrap()), + version + ); + assert_eq!( + u32::from_le_bytes(header[8..12].try_into().unwrap()), + rpr_build_info().dimension + ); + assert_eq!( + u32::from_le_bytes(header[12..16].try_into().unwrap()), + rpr_build_info().real_size + ); + } + assert_eq!(&snapshot_header(0x0102_0304)[4..8], &[4, 3, 2, 1]); + assert_ne!(snapshot_header(1), snapshot_header(257)); + } + + #[test] + fn snapshots_reject_incompatible_and_truncated_headers() { + unsafe { + let world = rpr_new_world(); + let snapshot = rpr_serialize_world(world); + assert_eq!(rpr_last_status(), RPR_OK); + let data = &(*snapshot).0; + let header = snapshot_header(RPR_ABI_VERSION); + assert!(data.starts_with(&header)); + + let restored = rpr_deserialize_world(data.as_ptr(), data.len()); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!(rpr_free_world(restored), RPR_OK); + + // Every header byte is checked, including the high ABI bytes that + // previously disappeared in the u8 cast (e.g. ABI 1 versus 257). + for index in 0..SNAPSHOT_HEADER_LEN { + let mut invalid = data.clone(); + invalid[index] ^= 1; + assert!(rpr_deserialize_world(invalid.as_ptr(), invalid.len()).is_null()); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + } + for len in 0..=SNAPSHOT_HEADER_LEN { + assert!(rpr_deserialize_world(data.as_ptr(), len).is_null()); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + } + + let mut legacy = vec![b'R', b'P', b'R', 1, header[8], header[12]]; + legacy.extend_from_slice(&data[SNAPSHOT_HEADER_LEN..]); + assert!(rpr_deserialize_world(legacy.as_ptr(), legacy.len()).is_null()); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + assert_eq!(rpr_free_bytes(snapshot), RPR_OK); + assert_eq!(rpr_free_world(world), RPR_OK); + } + } +} diff --git a/c/src/pod_tests.rs b/c/src/pod_tests.rs new file mode 100644 index 000000000..5250f35fa --- /dev/null +++ b/c/src/pod_tests.rs @@ -0,0 +1,390 @@ +use crate::*; + +fn encoded(value: &T) -> Vec { + bincode::serialize(value).unwrap() +} + +#[test] +fn pod_defaults_match_native_configuration() { + unsafe { + for kind in [ + RPR_DYNAMIC, + RPR_FIXED, + RPR_KINEMATIC_POSITION_BASED, + RPR_KINEMATIC_VELOCITY_BASED, + ] { + let desc = match kind { + RPR_DYNAMIC => rpr_dynamic_rigid_body_desc(), + RPR_FIXED => rpr_fixed_rigid_body_desc(), + RPR_KINEMATIC_POSITION_BASED => rpr_kinematic_position_based_rigid_body_desc(), + _ => rpr_kinematic_velocity_based_rigid_body_desc(), + }; + let actual = desc.raw().unwrap().build(); + let expected = RigidBodyBuilder::new(body_type(kind).unwrap()).build(); + assert_eq!(encoded(&actual), encoded(&expected)); + } + let desc = RprColliderDesc::default(); + assert_eq!( + encoded(&desc.raw().unwrap().build()), + encoded(&ColliderBuilder::ball(0.5).build()) + ); + let native = IntegrationParameters::default(); + let desc = RprIntegrationParameters::from(native); + assert_eq!(encoded(&desc.raw().unwrap()), encoded(&native)); + let native = SoftBodyMaterial::default(); + let desc = RprSoftBodyMaterial::from(native); + assert_eq!(encoded(&desc.raw().unwrap()), encoded(&native)); + let native = GenericJoint::new(JointAxesMask::empty()); + assert_eq!( + encoded(&RprJointDesc::from(native).raw().unwrap()), + encoded(&native) + ); + } +} + +fn check_soft_recipe(desc: RprSoftBodyDesc, native: SoftBodyBuilder) { + let actual = unsafe { desc.raw().unwrap() }; + // Includes topology, generated radii, material and collider template defaults. + assert_eq!(format!("{actual:?}"), format!("{native:?}")); +} + +#[test] +fn pod_soft_recipes_preserve_generator_defaults() { + let positions: [RprVector; 2] = [Vector::ZERO.into(), Vector::X.into()]; + check_soft_recipe( + RprSoftBodyDesc { + positions: RprVectorView { + data: (positions.as_ptr()).cast(), + count: 2, + }, + ..Default::default() + }, + SoftBodyBuilder::new(vec![Vector::ZERO, Vector::X]), + ); + check_soft_recipe( + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_ROPE, + a: Vector::ZERO.into(), + b: Vector::X.into(), + nx: 5, + ..Default::default() + }, + SoftBodyBuilder::rope(Vector::ZERO, Vector::X, 5), + ); + #[cfg(feature = "dim2")] + check_soft_recipe( + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_GRID, + ..Default::default() + }, + SoftBodyBuilder::grid(Vector::ZERO, Vector::ONE, 2, 2), + ); + #[cfg(feature = "dim3")] + { + check_soft_recipe( + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_CLOTH, + ..Default::default() + }, + SoftBodyBuilder::cloth(Vector::ZERO, Vector::X, Vector::Y, 2, 2), + ); + check_soft_recipe( + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_CUBOID, + ..Default::default() + }, + SoftBodyBuilder::cuboid(Vector::ZERO, Vector::ONE, 2, 2, 2), + ); + } + let points: Vec = vec![Vector::ZERO.into(), Vector::X.into(), Vector::Y.into()]; + #[cfg(feature = "dim3")] + let indices = [0, 1, 2]; + #[cfg(feature = "dim2")] + let indices = [0, 1, 1, 2, 2, 0]; + let desc = RprSoftBodyDesc { + kind: RPR_SOFT_DESC_SURFACE, + positions: RprVectorView { + data: (points.as_ptr()).cast(), + count: 3, + }, + surface: RprSurfaceElementView { + data: (indices.as_ptr()).cast(), + count: indices.len() / rapier::math::DIM, + }, + ..Default::default() + }; + #[cfg(feature = "dim3")] + let native = + SoftBodyBuilder::trimesh(vec![Vector::ZERO, Vector::X, Vector::Y], vec![[0, 1, 2]]) + .unwrap(); + #[cfg(feature = "dim2")] + let native = SoftBodyBuilder::polyline( + vec![Vector::ZERO, Vector::X, Vector::Y], + Some(vec![[0, 1], [1, 2], [2, 0]]), + ) + .unwrap(); + check_soft_recipe(desc, native); +} + +#[test] +fn pod_insertion_copies_inputs_and_rejects_invalid_data_atomically() { + unsafe { + let mut world = RprWorld::new(RprPhysicsWorld(PhysicsWorld::default())); + let body = rpr_dynamic_rigid_body_desc(); + let mut collider = RprColliderDesc::default(); + collider.shape.radius = -1.0; + + let body_handle = rpr_insert_rigid_body(&mut world, &body); + assert_eq!(rpr_last_status(), RPR_OK); + let failed = rpr_insert_collider(body_handle, &collider); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + // Separate calls leave the successfully inserted body intact on collider failure. + assert_eq!((*world.read().unwrap().raw()).0.bodies.len(), 1); + assert_eq!((*world.read().unwrap().raw()).0.colliders.len(), 0); + assert_eq!(failed, RprColliderHandle::default()); + assert_eq!(rpr_rigid_body_validate_handle(body_handle), RPR_OK); + let mut points: [RprVector; 2] = [Vector::ZERO.into(), Vector::X.into()]; + collider.shape.kind = RPR_SHAPE_DESC_POLYLINE; + collider.shape.vertices = RprVectorView { + data: points.as_ptr(), + count: 2, + }; + let ch = rpr_insert_collider_without_parent(&mut world, &collider); + assert_eq!(rpr_last_status(), RPR_OK); + points[1] = (Vector::X * 100.0).into(); + assert_eq!( + (&(*world.read().unwrap().raw()).0.colliders)[ch.raw()] + .shape() + .as_polyline() + .unwrap() + .vertices()[1], + Vector::X + ); + let desc = RprSoftBodyDesc { + positions: RprVectorView { + data: (points.as_ptr()).cast(), + count: 2, + }, + ..Default::default() + }; + let copied = desc.raw().unwrap(); + points[1] = Vector::ZERO.into(); + assert_eq!(copied.positions[1], Vector::X * 100.0); + assert_eq!(points[1].x, 0.0); + let mut data = RprIntegrationParameters::from(IntegrationParameters::default()); + let mut params = NativeIntegrationParameters(IntegrationParameters::default()); + let before = encoded(¶ms.0); + data.dt = Real::NAN; + assert_eq!( + native_integration_parameters_set_data(&mut params, &data), + RPR_INVALID_ARGUMENT + ); + assert_eq!(encoded(¶ms.0), before); + } +} + +#[test] +fn pod_shape_recipes_match_native_shapes() { + unsafe { + let mut desc = RprShapeDesc::default(); + let check = |desc: &RprShapeDesc, expected: SharedShape| { + assert_eq!(encoded(&desc.raw().unwrap()), encoded(&expected)); + }; + check(&desc, SharedShape::ball(0.5)); + desc.kind = RPR_SHAPE_DESC_CUBOID; + desc.a = Vector::ONE.into(); + #[cfg(feature = "dim2")] + check(&desc, SharedShape::cuboid(1.0, 1.0)); + #[cfg(feature = "dim3")] + check(&desc, SharedShape::cuboid(1.0, 1.0, 1.0)); + desc.kind = RPR_SHAPE_DESC_ROUND_CUBOID; + #[cfg(feature = "dim2")] + check(&desc, SharedShape::round_cuboid(1.0, 1.0, 0.5)); + #[cfg(feature = "dim3")] + check(&desc, SharedShape::round_cuboid(1.0, 1.0, 1.0, 0.5)); + desc.a = Vector::ZERO.into(); + desc.b = Vector::X.into(); + desc.c = Vector::Y.into(); + desc.kind = RPR_SHAPE_DESC_CAPSULE; + check(&desc, SharedShape::capsule(Vector::ZERO, Vector::X, 0.5)); + desc.kind = RPR_SHAPE_DESC_SEGMENT; + check(&desc, SharedShape::segment(Vector::ZERO, Vector::X)); + desc.kind = RPR_SHAPE_DESC_TRIANGLE; + check( + &desc, + SharedShape::triangle(Vector::ZERO, Vector::X, Vector::Y), + ); + desc.kind = RPR_SHAPE_DESC_HALFSPACE; + desc.a = Vector::Y.into(); + check(&desc, SharedShape::halfspace(Vector::Y)); + #[cfg(feature = "dim3")] + { + desc.kind = RPR_SHAPE_DESC_CYLINDER; + check(&desc, SharedShape::cylinder(0.5, 0.5)); + desc.kind = RPR_SHAPE_DESC_CONE; + check(&desc, SharedShape::cone(0.5, 0.5)); + } + let child = RprCompoundShapeDesc { + pose: Pose::IDENTITY.into(), + shape: RprShapeDesc::default(), + }; + desc.kind = RPR_SHAPE_DESC_COMPOUND; + desc.children = RprCompoundShapeView { + data: &child, + count: 1, + }; + check( + &desc, + SharedShape::compound(vec![(Pose::IDENTITY, SharedShape::ball(0.5))]), + ); + // Cycles and unsupported tags are rejected before native construction. + let mut cycle = child; + cycle.shape.kind = RPR_SHAPE_DESC_COMPOUND; + cycle.shape.children = RprCompoundShapeView { + data: std::ptr::addr_of!(cycle), + count: 1, + }; + assert!(cycle.shape.raw().is_err()); + desc.kind = u32::MAX; + assert!(desc.raw().is_err()); + desc.kind = RPR_SHAPE_DESC_HEIGHTFIELD; + let heights = [0.0, 1.0, 0.5, 0.0]; + desc.heights = RprRealView { + data: heights.as_ptr(), + count: if cfg!(feature = "dim2") { 2 } else { 4 }, + }; + desc.rows = 2; + #[cfg(feature = "dim2")] + { + desc.columns = 1; + check( + &desc, + SharedShape::heightfield(heights[..2].to_vec(), Vector::ONE), + ); + } + #[cfg(feature = "dim3")] + { + desc.columns = 2; + check( + &desc, + SharedShape::heightfield( + rapier::parry::utils::Array2::new(2, 2, heights.to_vec()), + Vector::ONE, + ), + ); + } + } +} + +#[test] +fn procedural_description_constructors_match_rust() { + #[cfg(feature = "dim2")] + { + let desc = rpr_disk_soft_body_desc(Vector::Y.into(), 1.0, 12); + check_soft_recipe(desc, SoftBodyBuilder::disk(Vector::Y, 1.0, 12)); + } + #[cfg(feature = "dim3")] + { + let desc = rpr_sphere_soft_body_desc(Vector::Y.into(), 1.0, 1); + check_soft_recipe(desc, SoftBodyBuilder::sphere(Vector::Y, 1.0, 1)); + let desc = + rpr_cloth_tube_soft_body_desc(Vector::ZERO.into(), Vector::Y.into(), 1.0, 0.5, 8, 3); + check_soft_recipe( + desc, + SoftBodyBuilder::cloth_tube(Vector::ZERO, Vector::Y, 1.0, 0.5, 8, 3), + ); + } + let mut desc = rpr_rope_soft_body_desc(Vector::ZERO.into(), Vector::X.into(), 3); + desc.totalMass = RprOptionalReal { + enabled: 1, + value: 6.0, + }; + desc.translation = Vector::Y.into(); + check_soft_recipe( + desc, + SoftBodyBuilder::rope(Vector::ZERO, Vector::X, 3) + .mass(6.0) + .translated(Vector::Y), + ); +} +#[test] +fn joint_descriptions_allow_one_sided_limits() { + let mut desc = rpr_default_joint_desc(); + desc.limitAxes = 1; + desc.limits[0].min = -Real::INFINITY; + desc.limits[0].max = 2.0; + assert!(desc.raw().is_ok()); + desc.limits[0].min = Real::NAN; + assert!(desc.raw().is_err()); +} + +#[test] +fn value_constructors_match_native_values_and_defer_validation() { + unsafe { + let collider = rpr_cuboid_collider_desc(Vector::splat(2.0).into()); + #[cfg(feature = "dim2")] + let native = ColliderBuilder::cuboid(2.0, 2.0); + #[cfg(feature = "dim3")] + let native = ColliderBuilder::cuboid(2.0, 2.0, 2.0); + assert_eq!( + encoded(&collider.raw().unwrap().build()), + encoded(&native.build()) + ); + assert_eq!( + encoded(&rpr_fixed_joint_desc().raw().unwrap()), + encoded(&GenericJoint::from(FixedJointBuilder::new().build())) + ); + let axis = (Vector::X + Vector::Y).normalize(); + let mut actual = rpr_prismatic_joint_desc((axis * 3.0).into()).raw().unwrap(); + let native = GenericJoint::from(PrismaticJointBuilder::new(axis).build()); + // 2D frames round-trip through an angle; permit trigonometric rounding. + assert!((actual.local_axis1() - native.local_axis1()).length() < Real::EPSILON * 8.0); + assert!((actual.local_axis2() - native.local_axis2()).length() < Real::EPSILON * 8.0); + actual.local_frame1.rotation = native.local_frame1.rotation; + actual.local_frame2.rotation = native.local_frame2.rotation; + assert_eq!(encoded(&actual), encoded(&native)); + #[cfg(feature = "dim2")] + assert_eq!( + encoded(&rpr_revolute_joint_desc().raw().unwrap()), + encoded(&GenericJoint::from(RevoluteJointBuilder::new().build())) + ); + #[cfg(feature = "dim3")] + assert_eq!( + encoded(&rpr_revolute_joint_desc(Vector::X.into()).raw().unwrap()), + encoded(&GenericJoint::from( + RevoluteJointBuilder::new(Vector::X).build() + )) + ); + let softness = RprSpringCoefficients { + natural_frequency: 12.0, + damping_ratio: 0.3, + }; + assert_eq!( + encoded(&rpr_uniform_soft_body_material(softness).raw().unwrap()), + encoded(&SoftBodyMaterial::uniform(softness.raw().unwrap())) + ); + + // Invalid values can be edited before insertion; constructors do not report errors. + let mut invalid = rpr_ball_collider_desc(-1.0); + assert_eq!(invalid.shape.radius, -1.0); + assert!(invalid.raw().is_err()); + invalid.shape.radius = 1.0; + assert!(invalid.raw().is_ok()); + assert!( + rpr_cuboid_collider_desc(Vector::splat(-1.0).into()) + .raw() + .is_err() + ); + assert!(rpr_prismatic_joint_desc(Vector::ZERO.into()).raw().is_err()); + assert!( + rpr_prismatic_joint_desc(Vector::splat(Real::NAN).into()) + .raw() + .is_err() + ); + assert!( + rpr_rope_soft_body_desc(Vector::ZERO.into(), Vector::X.into(), 0) + .raw() + .is_err() + ); + } +} diff --git a/c/src/queries.rs b/c/src/queries.rs new file mode 100644 index 000000000..2af5756bd --- /dev/null +++ b/c/src/queries.rs @@ -0,0 +1,129 @@ +use crate::*; +use rapier::parry::query::ShapeCastOptions; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct RprQueryFilter { + pub flags: u32, + pub use_groups: RprBool, + pub groups: RprInteractionGroups, + pub exclude_collider: RprColliderHandle, + pub exclude_rigid_body: RprRigidBodyHandle, +} +impl Default for RprQueryFilter { + fn default() -> Self { + Self { + flags: 0, + use_groups: 0, + groups: RprInteractionGroups { + memberships: u32::MAX, + filter: u32::MAX, + test_mode: 0, + }, + exclude_collider: Default::default(), + exclude_rigid_body: Default::default(), + } + } +} +impl RprQueryFilter { + pub(crate) fn raw(self) -> Result> { + Ok(QueryFilter { + flags: QueryFilterFlags::from_bits(self.flags) + .ok_or_else(|| invalid("unknown query flags"))?, + groups: if boolean(self.use_groups)? { + Some(self.groups.raw()?) + } else { + None + }, + exclude_collider: (self.exclude_collider != RprColliderHandle::default()) + .then(|| self.exclude_collider.raw()), + exclude_rigid_body: (self.exclude_rigid_body != RprRigidBodyHandle::default()) + .then(|| self.exclude_rigid_body.raw()), + predicate: None, + }) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_query_filter() -> RprQueryFilter { + RprQueryFilter::default() +} + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprRayHit { + pub collider: RprColliderHandle, + pub time_of_impact: RprReal, + pub normal: RprVector, + pub feature_type: u32, + pub feature_id: u32, +} +pub(crate) fn feature(f: rapier::parry::shape::FeatureId) -> (u32, u32) { + use rapier::parry::shape::FeatureId; + match f { + FeatureId::Vertex(i) => (1, i), + #[cfg(feature = "dim3")] + FeatureId::Edge(i) => (2, i), + FeatureId::Face(i) => (3, i), + FeatureId::Unknown => (0, 0), + } +} + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprPointProjection { + pub collider: RprColliderHandle, + pub point: RprVector, + pub is_inside: RprBool, +} + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprShapeCastOptions { + pub max_time_of_impact: RprReal, + pub target_distance: RprReal, + pub stop_at_penetration: RprBool, + pub compute_impact_geometry_on_penetration: RprBool, +} +impl RprShapeCastOptions { + pub(crate) fn raw(self) -> Result { + Ok(ShapeCastOptions { + max_time_of_impact: nonnegative(self.max_time_of_impact)?, + target_distance: nonnegative(self.target_distance)?, + stop_at_penetration: boolean(self.stop_at_penetration)?, + compute_impact_geometry_on_penetration: boolean( + self.compute_impact_geometry_on_penetration, + )?, + }) + } +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprShapeCastHit { + pub collider: RprColliderHandle, + pub time_of_impact: RprReal, + pub witness1: RprVector, + pub witness2: RprVector, + pub normal1: RprVector, + pub normal2: RprVector, + pub status: u32, +} + +#[rapier_export] +pub extern "C" fn rpr_default_shape_cast_options() -> RprShapeCastOptions { + let o = ShapeCastOptions::default(); + RprShapeCastOptions { + max_time_of_impact: o.max_time_of_impact, + target_distance: o.target_distance, + stop_at_penetration: o.stop_at_penetration as _, + compute_impact_geometry_on_penetration: o.compute_impact_geometry_on_penetration as _, + } +} + +/// Called with scoped read access and a collider handle. Shared queries may nest; +/// world mutations are rejected until the outer query returns. Never retain the context. +pub type RprQueryPredicate = Option< + unsafe extern "C" fn( + user_data: *mut std::ffi::c_void, + read: *const RprReadContext, + handle: RprColliderHandle, + ) -> RprBool, +>; diff --git a/c/src/read_access.rs b/c/src/read_access.rs new file mode 100644 index 000000000..d60469058 --- /dev/null +++ b/c/src/read_access.rs @@ -0,0 +1,1154 @@ +//! Scoped read access derived from the borrows Rapier supplies to callbacks. +use crate::*; +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_pid_controller)] +pub unsafe extern "C" fn rpr_read_pid_controller_rigid_body_correction( + context: *const RprReadContext, + controller: *mut RprPidController, + dt: RprReal, + body: RprRigidBodyHandle, + target_pose: RprPose, + target_linvel: RprVector, + target_angvel: RprAngVector, +) -> RprVelocityCorrection { + ffi_value(|result: *mut RprVelocityCorrection| { + let linear = unsafe { std::ptr::addr_of_mut!((*result).linear) }; + let angular_velocity = unsafe { std::ptr::addr_of_mut!((*result).angularVelocity) }; + + ffi(|| unsafe { + body.check_world(read_context_world(context))?; + crate::handle_access::forward(native_pid_controller_rigid_body_correction( + controller, + dt, + get(context)?.bodies, + body, + target_pose, + target_linvel, + target_angvel, + linear, + angular_velocity, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export] +pub unsafe extern "C" fn rpr_read_rigid_body_count(context: *const RprReadContext) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + crate::handle_access::forward(native_rigid_body_set_len(get(context)?.bodies, out)) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export] +pub unsafe extern "C" fn rpr_read_rigid_body_handles( + context: *const RprReadContext, + buffer: *mut RprRigidBodyHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array( + read_context_world(context), + buffer, + capacity, + |count: *mut usize| { + ffi(|| { + crate::handle_access::forward(native_rigid_body_set_handles( + get(context)?.bodies, + buffer, + capacity, + count, + )) + }) + }, + ) + } +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_contains( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_contains( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export] +pub unsafe extern "C" fn rpr_read_collider_count(context: *const RprReadContext) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + crate::handle_access::forward(native_collider_set_len(get(context)?.colliders, out)) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export] +pub unsafe extern "C" fn rpr_read_collider_handles( + context: *const RprReadContext, + buffer: *mut RprColliderHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array( + read_context_world(context), + buffer, + capacity, + |count: *mut usize| { + ffi(|| { + crate::handle_access::forward(native_collider_set_handles( + get(context)?.colliders, + buffer, + capacity, + count, + )) + }) + }, + ) + } +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_contains( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_contains( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_shape_identity( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_shape_identity( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_mass_properties( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprMassProperties { + ffi_value(|out: *mut RprMassProperties| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_mass_properties( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_locked_axes( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> u8 { + ffi_value(|out: *mut u8| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_locked_axes( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_is_voxels( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_is_voxels( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_voxel_at_flat_id( + context: *const RprReadContext, + handle: RprColliderHandle, + id: u32, +) -> RprVoxelQuery { + ffi_value(|result: *mut RprVoxelQuery| { + let key = unsafe { std::ptr::addr_of_mut!((*result).key) }; + let center = unsafe { std::ptr::addr_of_mut!((*result).center) }; + let size = unsafe { std::ptr::addr_of_mut!((*result).size) }; + let found = unsafe { std::ptr::addr_of_mut!((*result).found) }; + + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_voxel_at_flat_id( + get(context)?.colliders, + handle, + id, + key, + center, + size, + found, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_next_position( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprPose { + ffi_value(|out: *mut RprPose| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_next_position( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_rotation( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprRotation { + ffi_value(|out: *mut RprRotation| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_rotation( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_center_of_mass( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprVector { + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_center_of_mass( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_local_center_of_mass( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprVector { + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_local_center_of_mass( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_user_force( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprVector { + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_user_force( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_user_torque( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprAngVector { + ffi_value(|out: *mut RprAngVector| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_user_torque( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_body_type( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> u32 { + ffi_value(|out: *mut u32| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_body_type( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_mass( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_mass( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_gravity_scale( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_gravity_scale( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_linear_damping( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_linear_damping( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_angular_damping( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_angular_damping( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_kinetic_energy( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_kinetic_energy( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_soft_ccd_prediction( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_soft_ccd_prediction( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_is_ccd_enabled( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_is_ccd_enabled( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_is_dynamic( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_is_dynamic( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_soft_body( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprSoftBodyHandle { + ffi_world_value( + unsafe { read_context_world(context) }, + |out: *mut RprSoftBodyHandle| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_soft_body( + get(context)?.bodies, + handle, + out, + )) + }) + }, + ) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_is_soft_frame( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_is_soft_frame( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_is_fixed( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_is_fixed( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_is_kinematic( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_is_kinematic( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_is_moving( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_is_moving( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_is_ccd_active( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_is_ccd_active( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_velocity_at_point( + context: *const RprReadContext, + handle: RprRigidBodyHandle, + point: RprVector, +) -> RprVector { + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_velocity_at_point( + get(context)?.bodies, + handle, + point, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_colliders( + context: *const RprReadContext, + handle: RprRigidBodyHandle, + buffer: *mut RprColliderHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array( + read_context_world(context), + buffer, + capacity, + |count: *mut usize| { + ffi(|| { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_colliders( + get(context)?.bodies, + handle, + buffer, + capacity, + count, + )) + }) + }, + ) + } +} +#[cfg(feature = "dim3")] +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_gyroscopic_forces_enabled( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_gyroscopic_forces_enabled( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_rotation( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprRotation { + ffi_value(|out: *mut RprRotation| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_rotation( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_collision_groups( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprInteractionGroups { + ffi_value(|out: *mut RprInteractionGroups| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_collision_groups( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_solver_groups( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprInteractionGroups { + ffi_value(|out: *mut RprInteractionGroups| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_solver_groups( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_user_data( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprUserData { + ffi_value(|out: *mut RprUserData| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_user_data( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_active_events( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> u32 { + ffi_value(|out: *mut u32| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_active_events( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_mass( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_mass( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_density( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_density( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_volume( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_volume( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_contact_skin( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_contact_skin( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_contact_force_event_threshold( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_contact_force_event_threshold( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_is_enabled( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_is_enabled( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_compute_aabb( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprAabb { + ffi_value(|out: *mut RprAabb| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_compute_aabb( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +/// Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_clone_shape( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_shared_shape( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_validate_handle( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprStatus { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_validate_handle( + get(context)?.bodies, + handle, + )) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_validate_handle( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprStatus { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_validate_handle( + get(context)?.colliders, + handle, + )) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_position( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprPose { + ffi_value(|out: *mut RprPose| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_position( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_translation( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprVector { + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_translation( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_linvel( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprVector { + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_linvel( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_angvel( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprAngVector { + ffi_value(|out: *mut RprAngVector| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_angvel( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_is_sleeping( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_is_sleeping( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_is_enabled( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_is_enabled( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_rigid_body)] +pub unsafe extern "C" fn rpr_read_rigid_body_user_data( + context: *const RprReadContext, + handle: RprRigidBodyHandle, +) -> RprUserData { + ffi_value(|out: *mut RprUserData| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_rigid_body_set_get_user_data( + get(context)?.bodies, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_position( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprPose { + ffi_value(|out: *mut RprPose| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_position( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_translation( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprVector { + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_translation( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_friction( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_friction( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_restitution( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprReal { + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_restitution( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_is_sensor( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprBool { + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_is_sensor( + get(context)?.colliders, + handle, + out, + )) + }) + }) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export(read_collider)] +pub unsafe extern "C" fn rpr_read_collider_parent( + context: *const RprReadContext, + handle: RprColliderHandle, +) -> RprRigidBodyHandle { + ffi_world_value( + unsafe { read_context_world(context) }, + |out: *mut RprRigidBodyHandle| { + ffi(|| unsafe { + handle.check_world(read_context_world(context))?; + crate::handle_access::forward(native_collider_set_get_parent( + get(context)?.colliders, + handle, + out, + )) + }) + }, + ) +} +/// Read callback-visible state. The context is valid only until its callback returns. +#[rapier_export] +pub unsafe extern "C" fn rpr_read_rigid_body_read_states( + context: *const RprReadContext, + handles: *const RprRigidBodyHandle, + handle_count: usize, + states: *mut RprRigidBodyState, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + for value in input(handles, handle_count)? { + value.check_world(read_context_world(context))?; + } + crate::handle_access::forward(native_rigid_body_set_read_states( + get(context)?.bodies, + handles, + handle_count, + states, + capacity, + count, + )) + }) + }) +} diff --git a/c/src/render.rs b/c/src/render.rs new file mode 100644 index 000000000..ab5a2d04f --- /dev/null +++ b/c/src/render.rs @@ -0,0 +1,321 @@ +//! Shape geometry for foreign renderers. Tessellation is independent of graphics libraries. +use crate::*; +use rapier::parry::shape::{Shape, TypedShape}; + +/// Owned tessellated shape: flat triangle vertices and independent line segments, in local space. +/// Rounded 3D shapes use their inner surface (as in the Rust testbed). Halfspaces use a finite patch. +pub struct RprShapeMesh { + triangles: Vec, + lines: Vec, +} + +impl RprShapeMesh { + fn triangle_mesh(&mut self, pose: Pose, vertices: Vec, indices: Vec<[u32; 3]>) { + for tri in indices { + for i in tri { + self.triangles.push((pose * vertices[i as usize]).into()); + } + } + } + fn line_mesh(&mut self, pose: Pose, vertices: Vec, indices: Vec<[u32; 2]>) { + for segment in indices { + for i in segment { + self.lines.push((pose * vertices[i as usize]).into()); + } + } + } + #[cfg(feature = "dim2")] + fn polygon(&mut self, pose: Pose, vertices: Vec) { + for i in 1..vertices.len().saturating_sub(1) { + for j in [0, i, i + 1] { + self.triangles.push((pose * vertices[j]).into()); + } + } + } + fn append(&mut self, shape: &dyn Shape, pose: Pose, n: u32) -> Result<()> { + match shape.as_typed_shape() { + TypedShape::Compound(c) => { + for (p, s) in c.shapes() { + self.append(s.as_ref(), pose * *p, n)?; + } + } + TypedShape::Segment(s) => self.line_mesh(pose, vec![s.a, s.b], vec![[0, 1]]), + TypedShape::Polyline(p) => { + self.line_mesh(pose, p.vertices().to_vec(), p.indices().to_vec()) + } + TypedShape::Triangle(t) => { + self.triangle_mesh(pose, vec![t.a, t.b, t.c], vec![[0, 1, 2]]) + } + TypedShape::RoundTriangle(t) => self.append(&t.inner_shape, pose, n)?, + TypedShape::TriMesh(m) => { + self.triangle_mesh(pose, m.vertices().to_vec(), m.indices().to_vec()) + } + TypedShape::HalfSpace(h) => { + #[cfg(feature = "dim2")] + { + let tangent = Vector::new(-h.normal.y, h.normal.x) * 1000.0; + self.line_mesh(pose, vec![-tangent, tangent], vec![[0, 1]]); + } + #[cfg(feature = "dim3")] + { + let normal = h.normal; + let tangent = normal.any_orthonormal_vector() * 1000.0; + let bitangent = normal.cross(tangent); + self.triangle_mesh( + pose, + vec![ + -tangent - bitangent, + tangent - bitangent, + tangent + bitangent, + -tangent + bitangent, + ], + vec![[0, 1, 2], [0, 2, 3]], + ); + } + } + #[cfg(feature = "dim2")] + TypedShape::Ball(s) => self.polygon(pose, s.to_polyline(n)), + #[cfg(feature = "dim2")] + TypedShape::Cuboid(s) => self.polygon(pose, s.to_polyline()), + #[cfg(feature = "dim2")] + TypedShape::RoundCuboid(s) => self.polygon(pose, s.to_polyline(n / 4)), + #[cfg(feature = "dim2")] + TypedShape::Capsule(s) => self.polygon(pose, s.to_polyline(n)), + #[cfg(feature = "dim2")] + TypedShape::ConvexPolygon(s) => self.polygon(pose, s.points().to_vec()), + #[cfg(feature = "dim2")] + TypedShape::RoundConvexPolygon(s) => self.polygon(pose, s.to_polyline(n / 4)), + #[cfg(feature = "dim2")] + TypedShape::HeightField(s) => { + let (v, i) = s.to_polyline(); + self.line_mesh(pose, v, i); + } + #[cfg(feature = "dim2")] + TypedShape::Voxels(s) => { + let (v, i) = s.to_polyline(); + self.line_mesh(pose, v, i); + } + #[cfg(feature = "dim3")] + TypedShape::Ball(s) => { + let (v, i) = s.to_trimesh(n, n / 2); + self.triangle_mesh(pose, v, i); + } + #[cfg(feature = "dim3")] + TypedShape::Cuboid(s) => { + let (v, i) = s.to_trimesh(); + self.triangle_mesh(pose, v, i); + } + #[cfg(feature = "dim3")] + TypedShape::Capsule(s) => { + let (v, i) = s.to_trimesh(n, n / 2); + self.triangle_mesh(pose, v, i); + } + #[cfg(feature = "dim3")] + TypedShape::Cylinder(s) => { + let (v, i) = s.to_trimesh(n); + self.triangle_mesh(pose, v, i); + } + #[cfg(feature = "dim3")] + TypedShape::Cone(s) => { + let (v, i) = s.to_trimesh(n); + self.triangle_mesh(pose, v, i); + } + #[cfg(feature = "dim3")] + TypedShape::RoundCuboid(s) => self.append(&s.inner_shape, pose, n)?, + #[cfg(feature = "dim3")] + TypedShape::RoundCylinder(s) => self.append(&s.inner_shape, pose, n)?, + #[cfg(feature = "dim3")] + TypedShape::RoundCone(s) => self.append(&s.inner_shape, pose, n)?, + #[cfg(feature = "dim3")] + TypedShape::ConvexPolyhedron(s) => { + let (v, i) = s.to_trimesh(); + self.triangle_mesh(pose, v, i); + } + #[cfg(feature = "dim3")] + TypedShape::RoundConvexPolyhedron(s) => self.append(&s.inner_shape, pose, n)?, + #[cfg(feature = "dim3")] + TypedShape::HeightField(s) => { + let (v, i) = s.to_trimesh(); + self.triangle_mesh(pose, v, i); + } + #[cfg(feature = "dim3")] + TypedShape::Voxels(s) => { + let (v, i) = s.to_trimesh(); + self.triangle_mesh(pose, v, i); + } + TypedShape::Custom(_) => { + return Err(( + RPR_UNSUPPORTED, + "custom shapes cannot be tessellated".into(), + )); + } + } + Ok(()) + } +} +/// Process-local identity of the immutable shape allocation, for render caches. Keep an owned +/// SharedShape clone alive while caching this value. Not serializable; does not identify equal geometry. +pub(crate) unsafe fn native_collider_shape_identity( + collider: *const RprCollider, + out: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + output( + out, + get(collider)?.0.shape() as *const dyn Shape as *const () as usize, + ) + }) +} +#[rapier_export(shared_shape)] +pub unsafe extern "C" fn rpr_shared_shape_tessellate( + shape: *const RprSharedShape, + subdivisions: u32, +) -> *mut RprShapeMesh { + ffi_value(|out: *mut *mut RprShapeMesh| { + ffi(|| unsafe { + out_ptr(out)?; + ensure( + (8..=128).contains(&subdivisions), + "subdivisions must be between 8 and 128", + )?; + let mut mesh = RprShapeMesh { + triangles: Vec::new(), + lines: Vec::new(), + }; + mesh.append(get(shape)?.0.as_ref(), Pose::IDENTITY, subdivisions)?; + output(out, Box::into_raw(Box::new(mesh))) + }) + }) +} +/// Flat groups of three vertices. Standard output-buffer convention. +#[rapier_export(shape_mesh)] +pub unsafe extern "C" fn rpr_shape_mesh_triangles( + mesh: *const RprShapeMesh, + buffer: *mut RprVector, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { copy_out(&get(mesh)?.triangles, buffer, capacity, count) }) + }) +} +/// Flat groups of two vertices. Standard output-buffer convention. +#[rapier_export(shape_mesh)] +pub unsafe extern "C" fn rpr_shape_mesh_lines( + mesh: *const RprShapeMesh, + buffer: *mut RprVector, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { copy_out(&get(mesh)?.lines, buffer, capacity, count) }) + }) +} +#[rapier_export] +pub unsafe extern "C" fn rpr_free_shape_mesh(mesh: *mut RprShapeMesh) -> RprStatus { + ffi(|| unsafe { + if !mesh.is_null() { + get(mesh)?; + drop(Box::from_raw(mesh)); + } + Ok(()) + }) +} +#[cfg(feature = "dim3")] +#[rapier_export] +pub unsafe extern "C" fn rpr_round_cylinder_shared_shape( + half_height: RprReal, + radius: RprReal, + border_radius: RprReal, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + output( + out, + Box::into_raw(Box::new(RprSharedShape(SharedShape::round_cylinder( + positive(half_height)?, + positive(radius)?, + nonnegative(border_radius)?, + )))), + ) + }) + }) +} + +/// Owned indexed geometry from Parry's shape tessellation, preserving its vertex order. +#[cfg(feature = "dim3")] +pub struct RprTriMeshData { + vertices: Vec, + indices: Vec, +} + +/// Tessellate a ball or capsule with independent longitude/latitude subdivision counts. +/// Cuboids, cones, cylinders, convex polyhedra, trimeshes, and heightfields are also supported. +#[cfg(feature = "dim3")] +#[rapier_export(shared_shape)] +pub unsafe extern "C" fn rpr_shared_shape_to_trimesh( + shape: *const RprSharedShape, + ntheta: u32, + nphi: u32, +) -> *mut RprTriMeshData { + ffi_value(|out: *mut *mut RprTriMeshData| { + ffi(|| unsafe { + out_ptr(out)?; + ensure( + (3..=4096).contains(&ntheta) && (2..=4096).contains(&nphi), + "invalid tessellation subdivisions", + )?; + let (vertices, indices) = match get(shape)?.0.as_typed_shape() { + TypedShape::Ball(s) => s.to_trimesh(ntheta, nphi), + TypedShape::Capsule(s) => s.to_trimesh(ntheta, nphi), + TypedShape::Cuboid(s) => s.to_trimesh(), + TypedShape::Cone(s) => s.to_trimesh(ntheta), + TypedShape::Cylinder(s) => s.to_trimesh(ntheta), + TypedShape::ConvexPolyhedron(s) => s.to_trimesh(), + TypedShape::TriMesh(s) => (s.vertices().to_vec(), s.indices().to_vec()), + TypedShape::HeightField(s) => s.to_trimesh(), + _ => return Err((RPR_UNSUPPORTED, "shape has no indexed tessellation".into())), + }; + let mesh = RprTriMeshData { + vertices: vertices.into_iter().map(Into::into).collect(), + indices: indices.into_iter().flatten().collect(), + }; + output(out, Box::into_raw(Box::new(mesh))) + }) + }) +} + +#[cfg(feature = "dim3")] +#[rapier_export(tri_mesh_data)] +pub unsafe extern "C" fn rpr_tri_mesh_data_vertices( + mesh: *const RprTriMeshData, + buffer: *mut RprVector, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { copy_out(&get(mesh)?.vertices, buffer, capacity, count) }) + }) +} + +/// Flat triangle indices; count and capacity are numbers of u32 entries. +#[cfg(feature = "dim3")] +#[rapier_export(tri_mesh_data)] +pub unsafe extern "C" fn rpr_tri_mesh_data_indices( + mesh: *const RprTriMeshData, + buffer: *mut u32, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { copy_out(&get(mesh)?.indices, buffer, capacity, count) }) + }) +} + +#[cfg(feature = "dim3")] +#[rapier_export] +pub unsafe extern "C" fn rpr_free_tri_mesh_data(mesh: *mut RprTriMeshData) -> RprStatus { + ffi(|| unsafe { + if !mesh.is_null() { + drop(Box::from_raw(mesh)); + } + Ok(()) + }) +} diff --git a/c/src/return_values.rs b/c/src/return_values.rs new file mode 100644 index 000000000..3734823aa --- /dev/null +++ b/c/src/return_values.rs @@ -0,0 +1,64 @@ +//! Values returned by operations that produce several related outputs. +#![allow(non_snake_case)] +use crate::*; + +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprRayToi { + pub collider: RprColliderHandle, + pub toi: RprReal, + pub found: RprBool, +} + +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprOptionalRayHit { + pub hit: RprRayHit, + pub found: RprBool, +} + +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprVelocityCorrection { + pub linear: RprVector, + pub angularVelocity: RprAngVector, +} + +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprParticleDestination { + pub body: RprSoftBodyHandle, + pub index: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprOptionalParticleDestination { + pub body: RprSoftBodyHandle, + pub index: u32, + pub found: RprBool, +} + +/// Borrowed bytes. Valid while the source Bytes object remains alive; never free data. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprByteView { + pub data: *const u8, + pub count: usize, +} + +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprVoxelQuery { + pub key: RprVoxelKey, + pub center: RprVector, + pub size: RprVector, + pub found: RprBool, +} + +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprJointBodies { + pub body1: RprRigidBodyHandle, + pub body2: RprRigidBodyHandle, +} diff --git a/c/src/robotics.rs b/c/src/robotics.rs new file mode 100644 index 000000000..b2c819b19 --- /dev/null +++ b/c/src/robotics.rs @@ -0,0 +1,1067 @@ +//! Optional native URDF and MJCF importers (3D f32). +#![allow(non_snake_case)] +use crate::*; +use rapier3d_mjcf::MjcfVisualMesh; +use rapier3d_mjcf::{MjcfLoaderOptions, MjcfMultibodyOptions, MjcfRobot, MjcfRobotHandles}; +use rapier3d_urdf::{UrdfLoaderOptions, UrdfMultibodyOptions, UrdfRobot, UrdfRobotHandles}; +use std::ffi::{CStr, c_char}; + +unsafe fn path_string<'a>(path: *const c_char) -> Result<&'a str> { + ensure(!path.is_null(), "null path")?; + unsafe { CStr::from_ptr(path) } + .to_str() + .map_err(|_| invalid("path must be UTF-8")) +} +/// Loader configuration. Initialize with DefaultUrdfLoaderOptions; no destructor. +/// Blueprint array views and shared shapes are borrowed through the load call. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct RprUrdfLoaderOptions { + pub createCollidersFromCollisionShapes: RprBool, + pub createCollidersFromVisualShapes: RprBool, + pub applyImportedMassProps: RprBool, + pub enableJointCollisions: RprBool, + pub makeRootsFixed: RprBool, + pub squeezeEmptyFixedLinks: RprBool, + pub shift: RprPose, + pub scale: RprReal, + pub colliderBlueprint: RprColliderDesc, + pub rigidBodyBlueprint: RprRigidBodyDesc, +} +impl Default for RprUrdfLoaderOptions { + fn default() -> Self { + let defaults = UrdfLoaderOptions::default(); + Self { + createCollidersFromCollisionShapes: defaults.create_colliders_from_collision_shapes + as RprBool, + createCollidersFromVisualShapes: defaults.create_colliders_from_visual_shapes + as RprBool, + applyImportedMassProps: defaults.apply_imported_mass_props as RprBool, + enableJointCollisions: defaults.enable_joint_collisions as RprBool, + makeRootsFixed: defaults.make_roots_fixed as RprBool, + squeezeEmptyFixedLinks: defaults.squeeze_empty_fixed_links as RprBool, + shift: defaults.shift.into(), + scale: defaults.scale, + colliderBlueprint: RprColliderDesc { + density: 0.0, + ..Default::default() + }, + rigidBodyBlueprint: rpr_dynamic_rigid_body_desc(), + } + } +} +impl RprUrdfLoaderOptions { + unsafe fn raw(&self) -> Result { + Ok(UrdfLoaderOptions { + create_colliders_from_collision_shapes: boolean( + self.createCollidersFromCollisionShapes, + )?, + create_colliders_from_visual_shapes: boolean(self.createCollidersFromVisualShapes)?, + apply_imported_mass_props: boolean(self.applyImportedMassProps)?, + enable_joint_collisions: boolean(self.enableJointCollisions)?, + make_roots_fixed: boolean(self.makeRootsFixed)?, + squeeze_empty_fixed_links: boolean(self.squeezeEmptyFixedLinks)?, + shift: self.shift.raw()?, + scale: positive(self.scale)?, + collider_blueprint: unsafe { self.colliderBlueprint.raw()? }, + rigid_body_blueprint: self.rigidBodyBlueprint.raw()?, + ..Default::default() + }) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_urdf_loader_options() -> RprUrdfLoaderOptions { + RprUrdfLoaderOptions::default() +} +pub struct RprUrdfRobot(pub(crate) UrdfRobot); +#[rapier_export] +pub unsafe extern "C" fn rpr_free_urdf_robot(object: *mut RprUrdfRobot) -> RprStatus { + ffi(|| unsafe { + if !object.is_null() { + get(object)?; + drop(Box::from_raw(object)); + } + Ok(()) + }) +} +/// Load from a UTF-8 path. Validates options before reading the file. +/// Options and their blueprint resources are borrowed through this call; the robot is owned. +#[rapier_export] +pub unsafe extern "C" fn rpr_urdf_robot_from_file( + path: *const c_char, + options: *const RprUrdfLoaderOptions, +) -> *mut RprUrdfRobot { + ffi_value(|out: *mut *mut RprUrdfRobot| { + ffi(|| unsafe { + out_ptr(out)?; + let path = path_string(path)?; + let (robot, _) = UrdfRobot::from_file(path, get(options)?.raw()?, None) + .map_err(|e| invalid(e.to_string()))?; + output(out, Box::into_raw(Box::new(RprUrdfRobot(robot)))) + }) + }) +} +#[rapier_export(urdf_robot)] +pub unsafe extern "C" fn rpr_urdf_robot_append_transform( + robot: *mut RprUrdfRobot, + transform: RprPose, +) -> RprStatus { + ffi(|| unsafe { + get_mut(robot)?.0.append_transform(&transform.raw()?); + Ok(()) + }) +} +pub struct RprUrdfRobotHandles { + world: *mut RprWorld, + handles: UrdfHandles, +} +enum UrdfHandles { + Impulse(UrdfRobotHandles), + Multibody(UrdfRobotHandles>), +} +#[rapier_export] +pub unsafe extern "C" fn rpr_free_urdf_robot_handles( + handles: *mut RprUrdfRobotHandles, +) -> RprStatus { + ffi(|| unsafe { + if !handles.is_null() { + get(handles)?; + drop(Box::from_raw(handles)); + } + Ok(()) + }) +} +/// Inserts a clone; the source robot remains owned by the caller. Returns owned handles. +#[rapier_export(urdf_robot)] +pub unsafe extern "C" fn rpr_urdf_robot_insert_using_impulse_joints( + world: *mut RprWorld, + robot: *const RprUrdfRobot, +) -> *mut RprUrdfRobotHandles { + ffi_value(|out: *mut *mut RprUrdfRobotHandles| { + ffi(|| unsafe { + let owner = world; + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + out_ptr(out)?; + + let world = &mut get_mut(world)?.0; + let handles = get(robot)?.0.clone().insert_using_impulse_joints( + &mut world.bodies, + &mut world.colliders, + &mut world.impulse_joints, + ); + output( + out, + Box::into_raw(Box::new(RprUrdfRobotHandles { + world: owner, + handles: UrdfHandles::Impulse(handles), + })), + ) + }) + }) +} + +/// Inserts a clone; the source robot remains owned by the caller. Returns owned handles. +#[rapier_export(urdf_robot)] +pub unsafe extern "C" fn rpr_urdf_robot_insert_using_multibody_joints( + world: *mut RprWorld, + robot: *const RprUrdfRobot, + options: u8, +) -> *mut RprUrdfRobotHandles { + ffi_value(|out: *mut *mut RprUrdfRobotHandles| { + ffi(|| unsafe { + let owner = world; + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + out_ptr(out)?; + let options = UrdfMultibodyOptions::from_bits(options) + .ok_or_else(|| invalid("unknown multibody options"))?; + let world = &mut get_mut(world)?.0; + let handles = get(robot)?.0.clone().insert_using_multibody_joints( + &mut world.bodies, + &mut world.colliders, + &mut world.multibody_joints, + options, + ); + output( + out, + Box::into_raw(Box::new(RprUrdfRobotHandles { + world: owner, + handles: UrdfHandles::Multibody(handles), + })), + ) + }) + }) +} + +/// Body handles in source order; absent MJCF bodies have invalid handles. +#[rapier_export(urdf_robot_handles)] +pub unsafe extern "C" fn rpr_urdf_robot_handles_bodies( + handles: *const RprUrdfRobotHandles, + buffer: *mut RprRigidBodyHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array( + get(handles).map_or(std::ptr::null_mut(), |h| h.world), + buffer, + capacity, + |count: *mut usize| { + ffi(|| { + let values: Vec = match &get(handles)?.handles { + UrdfHandles::Impulse(h) => h.links.iter().map(|b| b.body.into()).collect(), + UrdfHandles::Multibody(h) => { + h.links.iter().map(|b| b.body.into()).collect() + } + }; + copy_out(&values, buffer, capacity, count) + }) + }, + ) + } +} +/// Loader configuration. Initialize with DefaultMjcfLoaderOptions; no destructor. +/// Blueprint array views and shared shapes are borrowed through the load call. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct RprMjcfLoaderOptions { + pub createCollidersFromCollisionShapes: RprBool, + pub createCollidersFromVisualShapes: RprBool, + pub applyImportedMassProps: RprBool, + pub enableJointCollisions: RprBool, + pub makeRootsFixed: RprBool, + pub skipPlaneGeoms: RprBool, + pub disableJointMotors: RprBool, + pub shift: RprPose, + pub scale: RprReal, + pub colliderBlueprint: RprColliderDesc, + pub rigidBodyBlueprint: RprRigidBodyDesc, +} +impl Default for RprMjcfLoaderOptions { + fn default() -> Self { + let defaults = MjcfLoaderOptions::default(); + Self { + createCollidersFromCollisionShapes: defaults.create_colliders_from_collision_shapes + as RprBool, + createCollidersFromVisualShapes: defaults.create_colliders_from_visual_shapes + as RprBool, + applyImportedMassProps: defaults.apply_imported_mass_props as RprBool, + enableJointCollisions: defaults.enable_joint_collisions as RprBool, + makeRootsFixed: defaults.make_roots_fixed as RprBool, + skipPlaneGeoms: defaults.skip_plane_geoms as RprBool, + disableJointMotors: defaults.disable_joint_motors as RprBool, + shift: defaults.shift.into(), + scale: defaults.scale, + colliderBlueprint: RprColliderDesc { + density: 0.0, + ..Default::default() + }, + rigidBodyBlueprint: rpr_dynamic_rigid_body_desc(), + } + } +} +impl RprMjcfLoaderOptions { + unsafe fn raw(&self) -> Result { + Ok(MjcfLoaderOptions { + create_colliders_from_collision_shapes: boolean( + self.createCollidersFromCollisionShapes, + )?, + create_colliders_from_visual_shapes: boolean(self.createCollidersFromVisualShapes)?, + apply_imported_mass_props: boolean(self.applyImportedMassProps)?, + enable_joint_collisions: boolean(self.enableJointCollisions)?, + make_roots_fixed: boolean(self.makeRootsFixed)?, + skip_plane_geoms: boolean(self.skipPlaneGeoms)?, + disable_joint_motors: boolean(self.disableJointMotors)?, + shift: self.shift.raw()?, + scale: positive(self.scale)?, + collider_blueprint: unsafe { self.colliderBlueprint.raw()? }, + rigid_body_blueprint: self.rigidBodyBlueprint.raw()?, + ..Default::default() + }) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_mjcf_loader_options() -> RprMjcfLoaderOptions { + RprMjcfLoaderOptions::default() +} +pub struct RprMjcfRobot(pub(crate) MjcfRobot); +#[rapier_export] +pub unsafe extern "C" fn rpr_free_mjcf_robot(object: *mut RprMjcfRobot) -> RprStatus { + ffi(|| unsafe { + if !object.is_null() { + get(object)?; + drop(Box::from_raw(object)); + } + Ok(()) + }) +} +/// Load from a UTF-8 path. Validates options before reading the file. +/// Options and their blueprint resources are borrowed through this call; the robot is owned. +#[rapier_export] +pub unsafe extern "C" fn rpr_mjcf_robot_from_file( + path: *const c_char, + options: *const RprMjcfLoaderOptions, +) -> *mut RprMjcfRobot { + ffi_value(|out: *mut *mut RprMjcfRobot| { + ffi(|| unsafe { + out_ptr(out)?; + let path = path_string(path)?; + let (robot, _) = MjcfRobot::from_file(path, get(options)?.raw()?) + .map_err(|e| invalid(e.to_string()))?; + output(out, Box::into_raw(Box::new(RprMjcfRobot(robot)))) + }) + }) +} +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_append_transform( + robot: *mut RprMjcfRobot, + transform: RprPose, +) -> RprStatus { + ffi(|| unsafe { + get_mut(robot)?.0.append_transform(&transform.raw()?); + Ok(()) + }) +} +pub struct RprMjcfRobotHandles { + world: *mut RprWorld, + handles: MjcfHandles, +} +enum MjcfHandles { + Impulse(MjcfRobotHandles), + Multibody(MjcfRobotHandles>), +} +#[rapier_export] +pub unsafe extern "C" fn rpr_free_mjcf_robot_handles( + handles: *mut RprMjcfRobotHandles, +) -> RprStatus { + ffi(|| unsafe { + if !handles.is_null() { + get(handles)?; + drop(Box::from_raw(handles)); + } + Ok(()) + }) +} +/// Inserts a clone; the source robot remains owned by the caller. Returns owned handles. +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_insert_using_impulse_joints( + world: *mut RprWorld, + robot: *const RprMjcfRobot, +) -> *mut RprMjcfRobotHandles { + ffi_value(|out: *mut *mut RprMjcfRobotHandles| { + ffi(|| unsafe { + let owner = world; + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + out_ptr(out)?; + + let world = &mut get_mut(world)?.0; + let handles = get(robot)?.0.clone().insert_using_impulse_joints( + &mut world.bodies, + &mut world.colliders, + &mut world.impulse_joints, + ); + output( + out, + Box::into_raw(Box::new(RprMjcfRobotHandles { + world: owner, + handles: MjcfHandles::Impulse(handles), + })), + ) + }) + }) +} + +/// Inserts a clone; the source robot remains owned by the caller. Returns owned handles. +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_insert_using_multibody_joints( + world: *mut RprWorld, + robot: *const RprMjcfRobot, + options: u8, +) -> *mut RprMjcfRobotHandles { + ffi_value(|out: *mut *mut RprMjcfRobotHandles| { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + crate::handle_access::forward(native_mjcf_robot_insert_using_multibody_joints( + robot, raw, options, out, world, + )) + }) + }) +} + +pub(crate) unsafe fn native_mjcf_robot_insert_using_multibody_joints( + robot: *const RprMjcfRobot, + world: *mut RprPhysicsWorld, + options: u8, + out: *mut *mut RprMjcfRobotHandles, + owner: *mut RprWorld, +) -> RprStatus { + ffi(|| unsafe { + out_ptr(out)?; + let options = MjcfMultibodyOptions::from_bits(options) + .ok_or_else(|| invalid("unknown multibody options"))?; + let world = &mut get_mut(world)?.0; + let handles = get(robot)?.0.clone().insert_using_multibody_joints( + &mut world.bodies, + &mut world.colliders, + &mut world.multibody_joints, + &mut world.impulse_joints, + options, + ); + output( + out, + Box::into_raw(Box::new(RprMjcfRobotHandles { + world: owner, + handles: MjcfHandles::Multibody(handles), + })), + ) + }) +} +/// Body handles in source order; absent MJCF bodies have invalid handles. +#[rapier_export(mjcf_robot_handles)] +pub unsafe extern "C" fn rpr_mjcf_robot_handles_bodies( + handles: *const RprMjcfRobotHandles, + buffer: *mut RprRigidBodyHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array( + get(handles).map_or(std::ptr::null_mut(), |h| h.world), + buffer, + capacity, + |count: *mut usize| { + ffi(|| { + let values: Vec = match &get(handles)?.handles { + MjcfHandles::Impulse(h) => h + .bodies + .iter() + .map(|b| b.as_ref().map(|b| b.body.into()).unwrap_or_default()) + .collect(), + MjcfHandles::Multibody(h) => h + .bodies + .iter() + .map(|b| b.as_ref().map(|b| b.body.into()).unwrap_or_default()) + .collect(), + }; + copy_out(&values, buffer, capacity, count) + }) + }, + ) + } +} +/// Resolved model gravity before the caller chooses a world convention. +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_gravity(robot: *const RprMjcfRobot) -> RprVector { + ffi_value(|out: *mut RprVector| ffi(|| unsafe { output(out, get(robot)?.0.gravity.into()) })) +} +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_body_count(robot: *const RprMjcfRobot) -> usize { + ffi_value(|out: *mut usize| ffi(|| unsafe { output(out, get(robot)?.0.bodies.len()) })) +} +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_body_collider_count( + robot: *const RprMjcfRobot, + body: usize, +) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + output( + out, + get(robot)? + .0 + .bodies + .get(body) + .ok_or_else(|| invalid("body index out of range"))? + .colliders + .len(), + ) + }) + }) +} +/// Borrowed collider; invalidated by freeing or mutating the robot's storage. +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_set_body_collider_collision_groups( + robot: *mut RprMjcfRobot, + body: usize, + collider: usize, + groups: RprInteractionGroups, +) -> RprStatus { + ffi(|| unsafe { + let groups = groups.raw()?; + let collider = get_mut(robot)? + .0 + .bodies + .get_mut(body) + .and_then(|b| b.colliders.get_mut(collider)) + .ok_or_else(|| invalid("collider index out of range"))?; + collider.set_collision_groups(groups); + Ok(()) + }) +} +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_keyframe_count(robot: *const RprMjcfRobot) -> usize { + ffi_value(|out: *mut usize| ffi(|| unsafe { output(out, get(robot)?.0.keyframes.len()) })) +} +/// Copies a NUL-terminated UTF-8 name. Count includes NUL; unnamed keys return an empty string. +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_keyframe_name( + robot: *const RprMjcfRobot, + key: usize, + buffer: *mut c_char, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let key = get(robot)? + .0 + .keyframes + .get(key) + .ok_or_else(|| invalid("keyframe index out of range"))?; + let mut bytes = key.name.as_deref().unwrap_or("").as_bytes().to_vec(); + bytes.push(0); + copy_out(&bytes, buffer.cast(), capacity, count) + }) + }) +} +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_append_keyframe( + robot: *mut RprMjcfRobot, + source: *const RprMjcfRobot, + key: usize, +) -> RprStatus { + ffi(|| unsafe { + let key = get(source)? + .0 + .keyframes + .get(key) + .ok_or_else(|| invalid("keyframe index out of range"))? + .clone(); + get_mut(robot)?.0.keyframes.push(key); + Ok(()) + }) +} +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_keyframe_controls( + robot: *const RprMjcfRobot, + key: usize, + buffer: *mut RprReal, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let robot = &get(robot)?.0; + let key = robot + .keyframes + .get(key) + .ok_or_else(|| invalid("keyframe index out of range"))?; + copy_out(&robot.keyframe_controls(key), buffer, capacity, count) + }) + }) +} +#[rapier_export(mjcf_robot_handles)] +pub unsafe extern "C" fn rpr_mjcf_robot_handles_actuator_count( + handles: *const RprMjcfRobotHandles, +) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + output( + out, + match &get(handles)?.handles { + MjcfHandles::Impulse(h) => h.actuators.len(), + MjcfHandles::Multibody(h) => h.actuators.len(), + }, + ) + }) + }) +} +#[rapier_export(mjcf_robot_handles)] +pub unsafe extern "C" fn rpr_mjcf_robot_handles_apply_keyframe( + handles: *const RprMjcfRobotHandles, + robot: *const RprMjcfRobot, + key: usize, +) -> RprStatus { + ffi(|| unsafe { + let world = get(handles)?.world; + let access = get(world)?.write()?; + let raw = access.raw(); + + crate::handle_access::forward(native_mjcf_robot_handles_apply_keyframe( + handles, raw, robot, key, + )) + }) +} + +pub(crate) unsafe fn native_mjcf_robot_handles_apply_keyframe( + handles: *const RprMjcfRobotHandles, + world: *mut RprPhysicsWorld, + robot: *const RprMjcfRobot, + key: usize, +) -> RprStatus { + ffi(|| unsafe { + let robot = &get(robot)?.0; + let key = robot + .keyframes + .get(key) + .ok_or_else(|| invalid("keyframe index out of range"))?; + let world = &mut get_mut(world)?.0; + match &get(handles)?.handles { + MjcfHandles::Impulse(h) => h.apply_keyframe(&mut world.bodies, robot, key), + MjcfHandles::Multibody(h) => { + h.apply_keyframe(&mut world.bodies, &mut world.multibody_joints, robot, key) + } + }; + Ok(()) + }) +} +#[rapier_export(mjcf_robot_handles)] +pub unsafe extern "C" fn rpr_mjcf_robot_handles_apply_controls_scaled( + handles: *const RprMjcfRobotHandles, + controls: *const RprReal, + count: usize, + gain: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let world = get(handles)?.world; + let access = get(world)?.write()?; + let raw = access.raw(); + + crate::handle_access::forward(native_mjcf_robot_handles_apply_controls_scaled( + handles, raw, controls, count, gain, + )) + }) +} + +pub(crate) unsafe fn native_mjcf_robot_handles_apply_controls_scaled( + handles: *const RprMjcfRobotHandles, + world: *mut RprPhysicsWorld, + controls: *const RprReal, + count: usize, + gain: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let controls = input(controls, count)?; + for &value in controls { + finite(value)?; + } + let gain = nonnegative(gain)?; + let world = &mut get_mut(world)?.0; + match &get(handles)?.handles { + MjcfHandles::Impulse(h) => { + h.apply_controls_scaled(&mut world.impulse_joints, controls, gain) + } + MjcfHandles::Multibody(h) => h.apply_controls_multibody_scaled( + &mut world.bodies, + &mut world.multibody_joints, + controls, + gain, + ), + }; + Ok(()) + }) +} +/// A borrowed visual declaration, valid until its robot is freed or its body storage changes. +#[repr(transparent)] +pub struct RprMjcfVisualMesh(pub(crate) MjcfVisualMesh); +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprRenderMaterial { + pub metallic: f32, + pub roughness: f32, + pub reflectance: f32, + pub emissive: [f32; 3], +} +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprMjcfVisualMeshInfo { + pub local_pose: RprPose, + pub rgba: [f32; 4], + pub material: RprRenderMaterial, + pub has_color: RprBool, + pub has_material: RprBool, + pub is_trimesh: RprBool, +} +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_body_visual_count( + robot: *const RprMjcfRobot, + body: usize, +) -> usize { + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + output( + out, + get(robot)? + .0 + .bodies + .get(body) + .ok_or_else(|| invalid("body index out of range"))? + .visual_meshes + .len(), + ) + }) + }) +} +#[rapier_export(mjcf_robot)] +pub unsafe extern "C" fn rpr_mjcf_robot_body_visual( + robot: *const RprMjcfRobot, + body: usize, + visual: usize, +) -> *const RprMjcfVisualMesh { + ffi_value(|out: *mut *const RprMjcfVisualMesh| { + ffi(|| unsafe { + let visual = get(robot)? + .0 + .bodies + .get(body) + .and_then(|b| b.visual_meshes.get(visual)) + .ok_or_else(|| invalid("visual index out of range"))?; + output( + out, + visual as *const rapier3d_mjcf::MjcfVisualMesh as *const RprMjcfVisualMesh, + ) + }) + }) +} +#[rapier_export(mjcf_visual_mesh)] +pub unsafe extern "C" fn rpr_mjcf_visual_mesh_info( + visual: *const RprMjcfVisualMesh, +) -> RprMjcfVisualMeshInfo { + ffi_value(|out: *mut RprMjcfVisualMeshInfo| { + ffi(|| unsafe { + let v = &get(visual)?.0; + let m = v.material; + output( + out, + RprMjcfVisualMeshInfo { + local_pose: v.local_pose.into(), + rgba: v.rgba.unwrap_or([0.7, 0.7, 0.75, 1.0]), + has_color: v.rgba.is_some() as RprBool, + has_material: m.is_some() as RprBool, + is_trimesh: v.shape.as_trimesh().is_some() as RprBool, + material: RprRenderMaterial { + metallic: m.map_or(0.0, |m| m.metallic), + roughness: m.map_or(1.0, |m| m.roughness), + reflectance: m.map_or(0.5, |m| m.reflectance), + emissive: m.map_or([0.0; 3], |m| m.emissive), + }, + }, + ) + }) + }) +} +/// Returns an owned shared shape reference. +/// Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. +#[rapier_export(mjcf_visual_mesh)] +pub unsafe extern "C" fn rpr_mjcf_visual_mesh_clone_shape( + visual: *const RprMjcfVisualMesh, +) -> *mut RprSharedShape { + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + out_ptr(out)?; + output( + out, + Box::into_raw(Box::new(RprSharedShape(get(visual)?.0.shape.clone()))), + ) + }) + }) +} +/// Copies flattened pairs of per-vertex UV coordinates. +#[rapier_export(mjcf_visual_mesh)] +pub unsafe extern "C" fn rpr_mjcf_visual_mesh_uvs( + visual: *const RprMjcfVisualMesh, + buffer: *mut f32, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + copy_out( + get(visual)?.0.uvs.as_deref().unwrap_or(&[]).as_flattened(), + buffer, + capacity, + count, + ) + }) + }) +} +/// Copies flattened triples of per-vertex normals. +#[rapier_export(mjcf_visual_mesh)] +pub unsafe extern "C" fn rpr_mjcf_visual_mesh_normals( + visual: *const RprMjcfVisualMesh, + buffer: *mut f32, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + copy_out( + get(visual)? + .0 + .normals + .as_deref() + .unwrap_or(&[]) + .as_flattened(), + buffer, + capacity, + count, + ) + }) + }) +} +/// Copies a NUL-terminated texture path, or an empty string for untextured meshes. +#[rapier_export(mjcf_visual_mesh)] +pub unsafe extern "C" fn rpr_mjcf_visual_mesh_texture( + visual: *const RprMjcfVisualMesh, + buffer: *mut c_char, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let v = &get(visual)?.0; + let mut bytes = v + .texture + .as_ref() + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or_default() + .into_bytes(); + bytes.push(0); + copy_out(&bytes, buffer.cast(), capacity, count) + }) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn loader_values_preserve_defaults_and_validate_before_io() { + unsafe { + let urdf = rpr_default_urdf_loader_options(); + let mjcf = rpr_default_mjcf_loader_options(); + let native_urdf = urdf.raw().unwrap(); + let native_mjcf = mjcf.raw().unwrap(); + assert_eq!(native_urdf.scale, UrdfLoaderOptions::default().scale); + assert_eq!( + native_mjcf.skip_plane_geoms, + MjcfLoaderOptions::default().skip_plane_geoms + ); + assert!(native_urdf.squeeze_empty_fixed_links); + for body in [ + native_urdf.rigid_body_blueprint.build(), + native_mjcf.rigid_body_blueprint.build(), + ] { + assert!(body.is_dynamic() && body.is_enabled()); + assert_eq!(body.gravity_scale(), 1.0); + } + assert_eq!(native_urdf.collider_blueprint.build().density(), 0.0); + assert_eq!(native_mjcf.collider_blueprint.build().density(), 0.0); + + let mut copy = urdf; + copy.rigidBodyBlueprint.gravityScale = 2.0; + copy.colliderBlueprint.friction = 0.9; + let converted = copy.raw().unwrap(); + assert_eq!(converted.rigid_body_blueprint.build().gravity_scale(), 2.0); + assert_eq!(converted.collider_blueprint.build().friction(), 0.9); + assert_eq!(urdf.rigidBodyBlueprint.gravityScale, 1.0); + + let path = c""; // Invalid options must fail before attempting to open a file. + copy.scale = 0.0; + assert!(rpr_urdf_robot_from_file(path.as_ptr(), ©).is_null()); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + let mut copy = mjcf; + copy.skipPlaneGeoms = 2; + assert!(rpr_mjcf_robot_from_file(path.as_ptr(), ©).is_null()); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + copy = mjcf; + copy.rigidBodyBlueprint.bodyType = u32::MAX; + assert!(copy.raw().is_err()); + + let shape = rpr_ball_shared_shape(0.75); + let mut copy = urdf; + copy.colliderBlueprint.shape.kind = RPR_SHAPE_DESC_SHARED; + copy.colliderBlueprint.shape.sharedShape = shape; + let converted = copy.raw().unwrap(); + assert_eq!(rpr_free_shared_shape(shape), RPR_OK); + assert_eq!( + converted + .collider_blueprint + .build() + .shape() + .as_ball() + .unwrap() + .radius, + 0.75 + ); + } + } + + #[test] + fn urdf_load_uses_value_blueprints() { + let path = std::env::temp_dir().join(format!("rapier-c-pod-{}.urdf", std::process::id())); + std::fs::write( + &path, + r#" + + "#, + ) + .unwrap(); + let cpath = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); + unsafe { + let robot = { + let mut options = rpr_default_urdf_loader_options(); + options.makeRootsFixed = 1; + options.colliderBlueprint.friction = 0.8; + rpr_urdf_robot_from_file(cpath.as_ptr(), &options) + }; // No options allocation or destructor; the loaded robot is independent. + std::fs::remove_file(path).unwrap(); + assert_eq!(rpr_last_status(), RPR_OK); + assert!(!robot.is_null()); + let world = rpr_new_world(); + let handles = rpr_urdf_robot_insert_using_impulse_joints(world, robot); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!(rpr_free_urdf_robot(robot), RPR_OK); + let mut bodies = [RprRigidBodyHandle::default(); 1]; + assert_eq!( + rpr_urdf_robot_handles_bodies(handles, bodies.as_mut_ptr(), 1), + 1 + ); + assert_eq!(rpr_rigid_body_is_fixed(bodies[0]), 1); + let mut colliders = [RprColliderHandle::default(); 1]; + assert_eq!( + rpr_rigid_body_colliders(bodies[0], colliders.as_mut_ptr(), 1), + 1 + ); + assert_eq!(rpr_collider_friction(colliders[0]), 0.8); + assert_eq!(rpr_free_urdf_robot_handles(handles), RPR_OK); + assert_eq!(rpr_free_world(world), RPR_OK); + } + } + + #[test] + fn imported_keyframes_and_actuators_are_accessible_through_c() { + let path = std::env::temp_dir().join(format!("rapier-c-robot-{}.xml", std::process::id())); + std::fs::write( + &path, + r#" + + + + + + + "#, + ) + .unwrap(); + let path_string = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); + unsafe { + let options = rpr_default_mjcf_loader_options(); + let robot = rpr_mjcf_robot_from_file(path_string.as_ptr(), &options); + assert_eq!(rpr_last_status(), RPR_OK); + assert!(!robot.is_null()); + std::fs::remove_file(path).unwrap(); + let mut count = 0; + assert_eq!( + { + let value = rpr_mjcf_robot_keyframe_count(robot); + let status = rpr_last_status(); + let destination: *mut usize = &mut count; + if !destination.is_null() { + destination.write(value); + } + status + }, + RPR_OK + ); + assert_eq!(count, 1); + let mut name = [0i8; 5]; + assert_eq!( + { + let value = + rpr_mjcf_robot_keyframe_name(robot, 0, name.as_mut_ptr(), name.len()); + let status = rpr_last_status(); + let destination: *mut usize = &mut count; + if !destination.is_null() { + destination.write(value); + } + status + }, + RPR_OK + ); + assert_eq!(CStr::from_ptr(name.as_ptr()).to_bytes(), b"home"); + let mut ctrl = [0.0]; + assert_eq!( + { + let value = + rpr_mjcf_robot_keyframe_controls(robot, 0, ctrl.as_mut_ptr(), ctrl.len()); + let status = rpr_last_status(); + let destination: *mut usize = &mut count; + if !destination.is_null() { + destination.write(value); + } + status + }, + RPR_OK + ); + assert_eq!(ctrl, [0.5]); + let mut world = RprPhysicsWorld(PhysicsWorld::new()); + let mut handles = std::ptr::null_mut(); + assert_eq!( + native_mjcf_robot_insert_using_multibody_joints( + robot, + &mut world, + 0, + &mut handles, + std::ptr::null_mut() + ), + RPR_OK + ); + assert_eq!( + { + let value = rpr_mjcf_robot_handles_actuator_count(handles); + let status = rpr_last_status(); + let destination: *mut usize = &mut count; + if !destination.is_null() { + destination.write(value); + } + status + }, + RPR_OK + ); + assert_eq!(count, 1); + assert_eq!( + native_mjcf_robot_handles_apply_keyframe(handles, &mut world, robot, 0), + RPR_OK + ); + assert_eq!( + native_mjcf_robot_handles_apply_controls_scaled( + handles, + &mut world, + ctrl.as_ptr(), + 1, + 0.25 + ), + RPR_OK + ); + for _ in 0..30 { + world.0.step(); + } + for (_, body) in world.0.bodies.iter() { + assert!(body.translation().is_finite()); + } + assert_eq!( + native_mjcf_robot_handles_apply_keyframe(handles, &mut world, robot, 1), + RPR_INVALID_ARGUMENT + ); + assert_eq!(rpr_free_mjcf_robot_handles(handles), RPR_OK); + assert_eq!(rpr_free_mjcf_robot(robot), RPR_OK); + } + } +} diff --git a/c/src/scoped_access.rs b/c/src/scoped_access.rs new file mode 100644 index 000000000..459654293 --- /dev/null +++ b/c/src/scoped_access.rs @@ -0,0 +1,3446 @@ +//! Complete set-and-handle element access. No borrowed element pointer escapes. +use crate::handle_access::forward; +use crate::*; +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_shape_identity(handle: RprColliderHandle) -> usize { + let world = handle.world; + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_shape_identity( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_shape_identity( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_shape_identity( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_num_particles(handle: RprSoftBodyHandle) -> usize { + let world = handle.world; + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_num_particles( + (element as *const SoftBody).cast(), + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_topology_version(handle: RprSoftBodyHandle) -> u32 { + let world = handle.world; + ffi_value(|out: *mut u32| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_topology_version( + (element as *const SoftBody).cast(), + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_mass(handle: RprSoftBodyHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_mass( + (element as *const SoftBody).cast(), + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_volume(handle: RprSoftBodyHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_volume( + (element as *const SoftBody).cast(), + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_rest_volume(handle: RprSoftBodyHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_rest_volume( + (element as *const SoftBody).cast(), + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_volume_factor(handle: RprSoftBodyHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_volume_factor( + (element as *const SoftBody).cast(), + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_center_of_mass(handle: RprSoftBodyHandle) -> RprVector { + let world = handle.world; + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_center_of_mass( + (element as *const SoftBody).cast(), + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_root_body(handle: RprSoftBodyHandle) -> RprRigidBodyHandle { + let world = handle.world; + ffi_world_value(world, |out: *mut RprRigidBodyHandle| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_root_body( + (element as *const SoftBody).cast(), + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_is_enabled(handle: RprSoftBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_is_enabled( + (element as *const SoftBody).cast(), + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_is_sleeping(handle: RprSoftBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_is_sleeping( + (element as *const SoftBody).cast(), + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_particle_velocities( + handle: RprSoftBodyHandle, + buffer: *mut RprVector, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_particle_velocities( + (element as *const SoftBody).cast(), + buffer, + capacity, + count, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_edges( + handle: RprSoftBodyHandle, + buffer: *mut u32, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_edges( + (element as *const SoftBody).cast(), + buffer, + capacity, + count, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_cells( + handle: RprSoftBodyHandle, + buffer: *mut u32, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_cells( + (element as *const SoftBody).cast(), + buffer, + capacity, + count, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_boundary( + handle: RprSoftBodyHandle, + buffer: *mut u32, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_boundary( + (element as *const SoftBody).cast(), + buffer, + capacity, + count, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_pieces( + handle: RprSoftBodyHandle, + buffer: *mut RprSoftBodyHandle, + capacity: usize, +) -> usize { + let world = handle.world; + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_pieces( + (element as *const SoftBody).cast(), + buffer, + capacity, + count, + )) + }) + }) + } +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_particle_velocity( + handle: RprSoftBodyHandle, + index: usize, + value: RprVector, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_particle_velocity( + (element as *mut SoftBody).cast(), + index, + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_particle_kinematic_target( + handle: RprSoftBodyHandle, + index: usize, + value: RprVector, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_particle_kinematic_target( + (element as *mut SoftBody).cast(), + index, + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_particle_pinned( + handle: RprSoftBodyHandle, + index: usize, + value: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_particle_pinned( + (element as *mut SoftBody).cast(), + index, + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_apply_particle_impulse( + handle: RprSoftBodyHandle, + index: usize, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_apply_particle_impulse( + (element as *mut SoftBody).cast(), + index, + value, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_add_force( + handle: RprSoftBodyHandle, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_add_force( + (element as *mut SoftBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_apply_impulse( + handle: RprSoftBodyHandle, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_apply_impulse( + (element as *mut SoftBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_reset_forces( + handle: RprSoftBodyHandle, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_reset_forces( + (element as *mut SoftBody).cast(), + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_enabled( + handle: RprSoftBodyHandle, + value: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_enabled( + (element as *mut SoftBody).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_volume_factor( + handle: RprSoftBodyHandle, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_volume_factor( + (element as *mut SoftBody).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_attach_particle( + handle: RprSoftBodyHandle, + index: usize, + rigid_body: RprRigidBodyHandle, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + rigid_body.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + let bodies: *const RprRigidBodySet = std::ptr::addr_of!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_attach_particle( + (element as *mut SoftBody).cast(), + index, + rigid_body, + bodies, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_detach_particle( + handle: RprSoftBodyHandle, + index: usize, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_detach_particle( + (element as *mut SoftBody).cast(), + index, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_clusters( + handle: RprSoftBodyHandle, + buffer: *mut u32, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_clusters( + (element as *const SoftBody).cast(), + buffer, + capacity, + count, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_cluster_proxy( + handle: RprSoftBodyHandle, + cluster: u32, +) -> RprRigidBodyHandle { + let world = handle.world; + ffi_world_value(world, |out: *mut RprRigidBodyHandle| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_cluster_proxy( + (element as *const SoftBody).cast(), + cluster, + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_cluster_particles( + handle: RprSoftBodyHandle, + cluster: u32, + buffer: *mut u32, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_cluster_particles( + (element as *const SoftBody).cast(), + cluster, + buffer, + capacity, + count, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_cluster_pinned( + handle: RprSoftBodyHandle, + cluster: u32, + value: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_cluster_pinned( + (element as *mut SoftBody).cast(), + cluster, + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_cluster_kinematic_target( + handle: RprSoftBodyHandle, + cluster: u32, + value: RprPose, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_cluster_kinematic_target( + (element as *mut SoftBody).cast(), + cluster, + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_cluster_shape_matching_enabled( + handle: RprSoftBodyHandle, + cluster: u32, + value: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_cluster_shape_matching_enabled( + (element as *mut SoftBody).cast(), + cluster, + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_cluster_stiffness_scale( + handle: RprSoftBodyHandle, + cluster: u32, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_cluster_stiffness_scale( + (element as *mut SoftBody).cast(), + cluster, + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_cluster_tear_resistance( + handle: RprSoftBodyHandle, + cluster: u32, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_cluster_tear_resistance( + (element as *mut SoftBody).cast(), + cluster, + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_meshes( + handle: RprSoftBodyHandle, + buffer: *mut RprSoftMeshInfo, + capacity: usize, +) -> usize { + let world = handle.world; + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_meshes( + (element as *const SoftBody).cast(), + buffer, + capacity, + count, + )) + }) + }) + } +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_mesh_vertices_by_id( + handle: RprSoftBodyHandle, + id: RprSoftMeshId, + buffer: *mut RprVector, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_mesh_vertices_by_id( + (element as *const SoftBody).cast(), + id, + buffer, + capacity, + count, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_mesh_indices_by_id( + handle: RprSoftBodyHandle, + id: RprSoftMeshId, + buffer: *mut u32, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_mesh_indices_by_id( + (element as *const SoftBody).cast(), + id, + buffer, + capacity, + count, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_mesh_colliders( + handle: RprSoftBodyHandle, + buffer: *mut RprColliderHandle, + capacity: usize, +) -> usize { + let world = handle.world; + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_mesh_colliders( + (element as *const SoftBody).cast(), + buffer, + capacity, + count, + )) + }) + }) + } +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_mesh_vertices( + handle: RprSoftBodyHandle, + collider: RprColliderHandle, + buffer: *mut RprVector, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + collider.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_mesh_vertices( + (element as *const SoftBody).cast(), + collider, + buffer, + capacity, + count, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_mesh_indices( + handle: RprSoftBodyHandle, + collider: RprColliderHandle, + buffer: *mut u32, + capacity: usize, +) -> usize { + let world = handle.world; + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + collider.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_mesh_indices( + (element as *const SoftBody).cast(), + collider, + buffer, + capacity, + count, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_mesh_arity( + handle: RprSoftBodyHandle, + collider: RprColliderHandle, +) -> usize { + let world = handle.world; + ffi_value(|out: *mut usize| { + ffi(|| unsafe { + handle.check_world(world)?; + collider.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_mesh_arity( + (element as *const SoftBody).cast(), + collider, + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_mesh_topology_version( + handle: RprSoftBodyHandle, + collider: RprColliderHandle, +) -> u32 { + let world = handle.world; + ffi_value(|out: *mut u32| { + ffi(|| unsafe { + handle.check_world(world)?; + collider.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_mesh_topology_version( + (element as *const SoftBody).cast(), + collider, + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[cfg(feature = "fem")] +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_solver( + handle: RprSoftBodyHandle, + solver: u32, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_solver( + (element as *mut SoftBody).cast(), + solver, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_cluster_shape_matching_target( + handle: RprSoftBodyHandle, + cluster: u32, + target: *const RprPose, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_cluster_shape_matching_target( + (element as *mut SoftBody).cast(), + cluster, + target, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_set_edge_tear_resistance( + handle: RprSoftBodyHandle, + index: usize, + resistance: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_set_edge_tear_resistance( + (element as *mut SoftBody).cast(), + index, + resistance, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_mesh_is_closed( + handle: RprSoftBodyHandle, + collider: RprColliderHandle, +) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + collider.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_soft_body_mesh_is_closed( + (element as *const SoftBody).cast(), + collider, + out, + )) + }) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_additional_mass_properties( + handle: RprRigidBodyHandle, + properties: RprMassProperties, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_additional_mass_properties( + (element as *mut RigidBody).cast(), + properties, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_recompute_mass_properties_from_colliders( + handle: RprRigidBodyHandle, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + let colliders: *const RprColliderSet = std::ptr::addr_of!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_recompute_mass_properties_from_colliders( + (element as *mut RigidBody).cast(), + colliders, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_mass_properties( + handle: RprColliderHandle, + properties: RprMassProperties, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_mass_properties( + (element as *mut Collider).cast(), + properties, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_mass_properties( + handle: RprColliderHandle, +) -> RprMassProperties { + let world = handle.world; + ffi_value(|out: *mut RprMassProperties| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_mass_properties( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_mass_properties( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprMassProperties, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_mass_properties( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_locked_axes( + handle: RprRigidBodyHandle, + axes: u8, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_locked_axes( + (element as *mut RigidBody).cast(), + axes, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_locked_axes(handle: RprRigidBodyHandle) -> u8 { + let world = handle.world; + ffi_value(|out: *mut u8| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_locked_axes( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_locked_axes( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut u8, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_locked_axes( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_is_voxels(handle: RprColliderHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_is_voxels( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_is_voxels( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_is_voxels( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_voxel_at_flat_id( + handle: RprColliderHandle, + id: u32, +) -> RprVoxelQuery { + let world = handle.world; + ffi_value(|result: *mut RprVoxelQuery| { + let key = unsafe { std::ptr::addr_of_mut!((*result).key) }; + let center = unsafe { std::ptr::addr_of_mut!((*result).center) }; + let size = unsafe { std::ptr::addr_of_mut!((*result).size) }; + let found = unsafe { std::ptr::addr_of_mut!((*result).found) }; + + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_voxel_at_flat_id( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + id, + key, + center, + size, + found, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_voxel_at_flat_id( + set: *const RprColliderSet, + handle: RprColliderHandle, + id: u32, + key: *mut RprVoxelKey, + center: *mut RprVector, + size: *mut RprVector, + found: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_voxel_at_flat_id( + (element as *const Collider).cast(), + id, + key, + center, + size, + found, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_voxel( + handle: RprColliderHandle, + key: RprVoxelKey, + filled: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_voxel( + (element as *mut Collider).cast(), + key, + filled, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_next_position(handle: RprRigidBodyHandle) -> RprPose { + let world = handle.world; + ffi_value(|out: *mut RprPose| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_next_position( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_next_position( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprPose, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_next_position( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_rotation(handle: RprRigidBodyHandle) -> RprRotation { + let world = handle.world; + ffi_value(|out: *mut RprRotation| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_rotation( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_rotation( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprRotation, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_rotation( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_center_of_mass(handle: RprRigidBodyHandle) -> RprVector { + let world = handle.world; + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_center_of_mass( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_center_of_mass( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_center_of_mass( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_local_center_of_mass( + handle: RprRigidBodyHandle, +) -> RprVector { + let world = handle.world; + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_local_center_of_mass( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_local_center_of_mass( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_local_center_of_mass( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_user_force(handle: RprRigidBodyHandle) -> RprVector { + let world = handle.world; + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_user_force( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_user_force( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_user_force( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_user_torque(handle: RprRigidBodyHandle) -> RprAngVector { + let world = handle.world; + ffi_value(|out: *mut RprAngVector| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_user_torque( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_user_torque( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprAngVector, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_user_torque( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_body_type(handle: RprRigidBodyHandle) -> u32 { + let world = handle.world; + ffi_value(|out: *mut u32| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_body_type( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_body_type( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut u32, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_body_type( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_mass(handle: RprRigidBodyHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_mass( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_mass( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_mass( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_gravity_scale(handle: RprRigidBodyHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_gravity_scale( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_gravity_scale( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_gravity_scale( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_linear_damping(handle: RprRigidBodyHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_linear_damping( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_linear_damping( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_linear_damping( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_angular_damping(handle: RprRigidBodyHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_angular_damping( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_angular_damping( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_angular_damping( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_kinetic_energy(handle: RprRigidBodyHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_kinetic_energy( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_kinetic_energy( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_kinetic_energy( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_soft_ccd_prediction(handle: RprRigidBodyHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_soft_ccd_prediction( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_soft_ccd_prediction( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_soft_ccd_prediction( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_is_ccd_enabled(handle: RprRigidBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_is_ccd_enabled( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_is_ccd_enabled( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_is_ccd_enabled( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_is_dynamic(handle: RprRigidBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_is_dynamic( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_is_dynamic( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_is_dynamic( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_soft_body(handle: RprRigidBodyHandle) -> RprSoftBodyHandle { + let world = handle.world; + ffi_world_value(world, |out: *mut RprSoftBodyHandle| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_soft_body( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_soft_body( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprSoftBodyHandle, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_soft_body( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_is_soft_frame(handle: RprRigidBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_is_soft_frame( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_is_soft_frame( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_is_soft_frame( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_is_fixed(handle: RprRigidBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_is_fixed( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_is_fixed( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_is_fixed( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_is_kinematic(handle: RprRigidBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_is_kinematic( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_is_kinematic( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_is_kinematic( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_is_moving(handle: RprRigidBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_is_moving( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_is_moving( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_is_moving( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_is_ccd_active(handle: RprRigidBodyHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_is_ccd_active( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_is_ccd_active( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_is_ccd_active( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_rotation( + handle: RprRigidBodyHandle, + value: RprRotation, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_rotation( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_body_type( + handle: RprRigidBodyHandle, + value: u32, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_body_type( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_next_kinematic_rotation( + handle: RprRigidBodyHandle, + value: RprRotation, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_next_kinematic_rotation( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_additional_mass( + handle: RprRigidBodyHandle, + value: RprReal, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_additional_mass( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_soft_ccd_prediction( + handle: RprRigidBodyHandle, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_soft_ccd_prediction( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_ccd_enabled( + handle: RprRigidBodyHandle, + value: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_ccd_enabled( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_translations_locked( + handle: RprRigidBodyHandle, + value: RprBool, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_translations_locked( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_rotations_locked( + handle: RprRigidBodyHandle, + value: RprBool, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_rotations_locked( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_dominance_group( + handle: RprRigidBodyHandle, + value: i8, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_dominance_group( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_additional_solver_iterations( + handle: RprRigidBodyHandle, + value: usize, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_additional_solver_iterations( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_additional_pgs_iterations( + handle: RprRigidBodyHandle, + value: usize, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_additional_pgs_iterations( + (element as *mut RigidBody).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_add_torque( + handle: RprRigidBodyHandle, + value: RprAngVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_add_torque( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_apply_torque_impulse( + handle: RprRigidBodyHandle, + value: RprAngVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_apply_torque_impulse( + (element as *mut RigidBody).cast(), + value, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_add_force_at_point( + handle: RprRigidBodyHandle, + value: RprVector, + point: RprVector, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_add_force_at_point( + (element as *mut RigidBody).cast(), + value, + point, + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_reset_torques( + handle: RprRigidBodyHandle, + wake_up: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_reset_torques( + (element as *mut RigidBody).cast(), + wake_up, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_velocity_at_point( + handle: RprRigidBodyHandle, + point: RprVector, +) -> RprVector { + let world = handle.world; + ffi_value(|out: *mut RprVector| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_velocity_at_point( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + point, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_get_velocity_at_point( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + point: RprVector, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_velocity_at_point( + (element as *const RigidBody).cast(), + point, + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_colliders( + handle: RprRigidBodyHandle, + buffer: *mut RprColliderHandle, + capacity: usize, +) -> usize { + let world = handle.world; + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_colliders( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + buffer, + capacity, + count, + )) + }) + }) + } +} + +pub(crate) unsafe fn native_rigid_body_set_get_colliders( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + buffer: *mut RprColliderHandle, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_colliders( + (element as *const RigidBody).cast(), + buffer, + capacity, + count, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[cfg(feature = "dim3")] +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_gyroscopic_forces_enabled( + handle: RprRigidBodyHandle, +) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_get_gyroscopic_forces_enabled( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + out, + )) + }) + }) +} +#[cfg(feature = "dim3")] +pub(crate) unsafe fn native_rigid_body_set_get_gyroscopic_forces_enabled( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_rigid_body_gyroscopic_forces_enabled( + (element as *const RigidBody).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[cfg(feature = "dim3")] +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_set_gyroscopic_forces_enabled( + handle: RprRigidBodyHandle, + enabled: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + ensure( + element.soft_body().is_none(), + "mutate soft-body proxies through the soft-body API", + )?; + forward(native_rigid_body_set_gyroscopic_forces_enabled( + (element as *mut RigidBody).cast(), + enabled, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_density( + handle: RprColliderHandle, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_density( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_mass( + handle: RprColliderHandle, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_mass( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_enabled( + handle: RprColliderHandle, + value: RprBool, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_enabled( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_solver_groups( + handle: RprColliderHandle, + value: RprInteractionGroups, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_solver_groups( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_friction_combine_rule( + handle: RprColliderHandle, + value: u32, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_friction_combine_rule( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_restitution_combine_rule( + handle: RprColliderHandle, + value: u32, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_restitution_combine_rule( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_contact_skin( + handle: RprColliderHandle, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_contact_skin( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_contact_force_event_threshold( + handle: RprColliderHandle, + value: RprReal, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_contact_force_event_threshold( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_active_events( + handle: RprColliderHandle, + value: u32, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_active_events( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_active_hooks( + handle: RprColliderHandle, + value: u32, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_active_hooks( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_active_collision_types( + handle: RprColliderHandle, + value: u16, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_active_collision_types( + (element as *mut Collider).cast(), + value, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_rotation(handle: RprColliderHandle) -> RprRotation { + let world = handle.world; + ffi_value(|out: *mut RprRotation| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_rotation( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_rotation( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprRotation, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_rotation( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_collision_groups( + handle: RprColliderHandle, +) -> RprInteractionGroups { + let world = handle.world; + ffi_value(|out: *mut RprInteractionGroups| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_collision_groups( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_collision_groups( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprInteractionGroups, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_collision_groups( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_solver_groups( + handle: RprColliderHandle, +) -> RprInteractionGroups { + let world = handle.world; + ffi_value(|out: *mut RprInteractionGroups| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_solver_groups( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_solver_groups( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprInteractionGroups, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_solver_groups( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_user_data(handle: RprColliderHandle) -> RprUserData { + let world = handle.world; + ffi_value(|out: *mut RprUserData| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_user_data( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_user_data( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprUserData, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_user_data( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_active_events(handle: RprColliderHandle) -> u32 { + let world = handle.world; + ffi_value(|out: *mut u32| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_active_events( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_active_events( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut u32, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_active_events( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_mass(handle: RprColliderHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_mass( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_mass( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_mass( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_density(handle: RprColliderHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_density( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_density( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_density( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_volume(handle: RprColliderHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_volume( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_volume( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_volume( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_contact_skin(handle: RprColliderHandle) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_contact_skin( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_contact_skin( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_contact_skin( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_contact_force_event_threshold( + handle: RprColliderHandle, +) -> RprReal { + let world = handle.world; + ffi_value(|out: *mut RprReal| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_contact_force_event_threshold( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_contact_force_event_threshold( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_contact_force_event_threshold( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_is_enabled(handle: RprColliderHandle) -> RprBool { + let world = handle.world; + ffi_value(|out: *mut RprBool| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_is_enabled( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_is_enabled( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_is_enabled( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_compute_aabb(handle: RprColliderHandle) -> RprAabb { + let world = handle.world; + ffi_value(|out: *mut RprAabb| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_compute_aabb( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_compute_aabb( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut RprAabb, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_compute_aabb( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +/// Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_clone_shape( + handle: RprColliderHandle, +) -> *mut RprSharedShape { + let world = handle.world; + ffi_value(|out: *mut *mut RprSharedShape| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_get_shared_shape( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + out, + )) + }) + }) +} + +pub(crate) unsafe fn native_collider_set_get_shared_shape( + set: *const RprColliderSet, + handle: RprColliderHandle, + out: *mut *mut RprSharedShape, +) -> RprStatus { + ffi(|| unsafe { + let element = get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + forward(native_collider_shared_shape( + (element as *const Collider).cast(), + out, + )) + }) +} +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_shape( + handle: RprColliderHandle, + shape: *const RprSharedShape, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_shape( + (element as *mut Collider).cast(), + shape, + )) + }) +} + +/// Resolves the generational handle for this call; rejects stale handles. +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_set_position_wrt_parent( + handle: RprColliderHandle, + value: RprPose, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + let element = get_mut(set)?.0.get_mut(handle.raw()).ok_or_else(missing)?; + forward(native_collider_set_position_wrt_parent( + (element as *mut Collider).cast(), + value, + )) + }) +} + +#[rapier_export(rigid_body)] +pub unsafe extern "C" fn rpr_rigid_body_validate_handle(handle: RprRigidBodyHandle) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_rigid_body_set_validate_handle( + std::ptr::addr_of!((*raw).0.bodies).cast(), + handle, + )) + }) +} + +pub(crate) unsafe fn native_rigid_body_set_validate_handle( + set: *const RprRigidBodySet, + handle: RprRigidBodyHandle, +) -> RprStatus { + ffi(|| unsafe { + get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + Ok(()) + }) +} +#[rapier_export(collider)] +pub unsafe extern "C" fn rpr_collider_validate_handle(handle: RprColliderHandle) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + crate::handle_access::forward(native_collider_set_validate_handle( + std::ptr::addr_of!((*raw).0.colliders).cast(), + handle, + )) + }) +} + +pub(crate) unsafe fn native_collider_set_validate_handle( + set: *const RprColliderSet, + handle: RprColliderHandle, +) -> RprStatus { + ffi(|| unsafe { + get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + Ok(()) + }) +} +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_validate_handle(handle: RprSoftBodyHandle) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.read()?; + let raw = access.raw(); + + let set: *const RprSoftBodySet = std::ptr::addr_of!((*raw).0.soft_bodies).cast(); + + get(set)?.0.get(handle.raw()).ok_or_else(missing)?; + Ok(()) + }) +} diff --git a/c/src/shape_desc.rs b/c/src/shape_desc.rs new file mode 100644 index 000000000..3b2f7c73e --- /dev/null +++ b/c/src/shape_desc.rs @@ -0,0 +1,184 @@ +//! POD shape constructors; input geometry is borrowed until insertion. +use crate::*; +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_round_cuboid_collider_desc( + half_extents: RprVector, + border_radius: RprReal, +) -> RprColliderDesc { + RprColliderDesc { + shape: RprShapeDesc { + kind: RPR_SHAPE_DESC_ROUND_CUBOID, + a: half_extents, + radius: border_radius, + ..RprShapeDesc::default() + }, + ..RprColliderDesc::default() + } +} +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_capsule_collider_desc( + a: RprVector, + b: RprVector, + radius: RprReal, +) -> RprColliderDesc { + RprColliderDesc { + shape: RprShapeDesc { + kind: RPR_SHAPE_DESC_CAPSULE, + a, + b, + radius, + ..RprShapeDesc::default() + }, + ..RprColliderDesc::default() + } +} +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_segment_collider_desc(a: RprVector, b: RprVector) -> RprColliderDesc { + RprColliderDesc { + shape: RprShapeDesc { + kind: RPR_SHAPE_DESC_SEGMENT, + a, + b, + ..RprShapeDesc::default() + }, + ..RprColliderDesc::default() + } +} +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_triangle_collider_desc( + a: RprVector, + b: RprVector, + c: RprVector, +) -> RprColliderDesc { + RprColliderDesc { + shape: RprShapeDesc { + kind: RPR_SHAPE_DESC_TRIANGLE, + a, + b, + c, + ..RprShapeDesc::default() + }, + ..RprColliderDesc::default() + } +} +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_halfspace_collider_desc(normal: RprVector) -> RprColliderDesc { + RprColliderDesc { + shape: RprShapeDesc { + kind: RPR_SHAPE_DESC_HALFSPACE, + a: normal, + ..RprShapeDesc::default() + }, + ..RprColliderDesc::default() + } +} +#[cfg(feature = "dim3")] +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_cylinder_collider_desc( + half_height: RprReal, + radius: RprReal, +) -> RprColliderDesc { + RprColliderDesc { + shape: RprShapeDesc { + kind: RPR_SHAPE_DESC_CYLINDER, + halfHeight: half_height, + radius, + ..RprShapeDesc::default() + }, + ..RprColliderDesc::default() + } +} +#[cfg(feature = "dim3")] +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_cone_collider_desc(half_height: RprReal, radius: RprReal) -> RprColliderDesc { + RprColliderDesc { + shape: RprShapeDesc { + kind: RPR_SHAPE_DESC_CONE, + halfHeight: half_height, + radius, + ..RprShapeDesc::default() + }, + ..RprColliderDesc::default() + } +} +#[cfg(feature = "dim3")] +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_round_cylinder_collider_desc( + half_height: RprReal, + radius: RprReal, + border_radius: RprReal, +) -> RprColliderDesc { + RprColliderDesc { + shape: RprShapeDesc { + kind: RPR_SHAPE_DESC_ROUND_CYLINDER, + halfHeight: half_height, + radius, + borderRadius: border_radius, + ..RprShapeDesc::default() + }, + ..RprColliderDesc::default() + } +} +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_capsule_x_collider_desc( + half_height: RprReal, + radius: RprReal, +) -> RprColliderDesc { + let axis = Vector::X * half_height; + RprColliderDesc { + shape: RprShapeDesc { + kind: RPR_SHAPE_DESC_CAPSULE, + a: (-axis).into(), + b: axis.into(), + radius, + ..RprShapeDesc::default() + }, + ..RprColliderDesc::default() + } +} +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_capsule_y_collider_desc( + half_height: RprReal, + radius: RprReal, +) -> RprColliderDesc { + let axis = Vector::Y * half_height; + RprColliderDesc { + shape: RprShapeDesc { + kind: RPR_SHAPE_DESC_CAPSULE, + a: (-axis).into(), + b: axis.into(), + radius, + ..RprShapeDesc::default() + }, + ..RprColliderDesc::default() + } +} +#[cfg(feature = "dim3")] +/// Returns a description without allocating or validating. Build/insert validates its fields. +#[rapier_export] +pub extern "C" fn rpr_capsule_z_collider_desc( + half_height: RprReal, + radius: RprReal, +) -> RprColliderDesc { + let axis = Vector::Z * half_height; + RprColliderDesc { + shape: RprShapeDesc { + kind: RPR_SHAPE_DESC_CAPSULE, + a: (-axis).into(), + b: axis.into(), + radius, + ..RprShapeDesc::default() + }, + ..RprColliderDesc::default() + } +} diff --git a/c/src/soft_body.rs b/c/src/soft_body.rs new file mode 100644 index 000000000..916e4f384 --- /dev/null +++ b/c/src/soft_body.rs @@ -0,0 +1,1222 @@ +use crate::*; + +#[rapier_export] +pub unsafe extern "C" fn rpr_remove_soft_body(handle: RprSoftBodyHandle) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + let islands: *mut RprIslandManager = std::ptr::addr_of_mut!((*raw).0.islands).cast(); + let bodies: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + let colliders: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + let impulse_joints: *mut RprImpulseJointSet = + std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + let multibody_joints: *mut RprMultibodyJointSet = + std::ptr::addr_of_mut!((*raw).0.multibody_joints).cast(); + + get_mut(set)? + .0 + .remove( + handle.raw(), + &mut get_mut(islands)?.0, + &mut get_mut(bodies)?.0, + &mut get_mut(colliders)?.0, + &mut get_mut(impulse_joints)?.0, + &mut get_mut(multibody_joints)?.0, + ) + .ok_or_else(missing)?; + Ok(()) + }) +} + +pub(crate) unsafe fn native_soft_body_num_particles( + body: *const RprSoftBody, + out: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + output(out, b.num_particles()) + }) +} +pub(crate) unsafe fn native_soft_body_topology_version( + body: *const RprSoftBody, + out: *mut u32, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + output(out, b.topology_version()) + }) +} +pub(crate) unsafe fn native_soft_body_mass( + body: *const RprSoftBody, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + output(out, b.mass()) + }) +} +pub(crate) unsafe fn native_soft_body_volume( + body: *const RprSoftBody, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + output(out, b.volume()) + }) +} +pub(crate) unsafe fn native_soft_body_rest_volume( + body: *const RprSoftBody, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + output(out, b.rest_volume()) + }) +} +pub(crate) unsafe fn native_soft_body_volume_factor( + body: *const RprSoftBody, + out: *mut RprReal, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + output(out, b.volume_factor()) + }) +} +pub(crate) unsafe fn native_soft_body_center_of_mass( + body: *const RprSoftBody, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + output(out, b.center_of_mass().into()) + }) +} +pub(crate) unsafe fn native_soft_body_root_body( + body: *const RprSoftBody, + out: *mut RprRigidBodyHandle, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + output(out, b.root_body().into()) + }) +} +pub(crate) unsafe fn native_soft_body_is_enabled( + body: *const RprSoftBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + output(out, b.is_enabled() as u32) + }) +} +pub(crate) unsafe fn native_soft_body_is_sleeping( + body: *const RprSoftBody, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + output(out, b.is_sleeping() as u32) + }) +} +pub(crate) unsafe fn native_soft_body_particle_positions( + body: *const RprSoftBody, + buffer: *mut RprVector, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let values: Vec<_> = get(body)?.0.particle_positions().map(Into::into).collect(); + copy_out(&values, buffer, capacity, count) + }) +} +pub(crate) unsafe fn native_soft_body_particle_velocities( + body: *const RprSoftBody, + buffer: *mut RprVector, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let values: Vec<_> = get(body)?.0.particle_velocities().map(Into::into).collect(); + copy_out(&values, buffer, capacity, count) + }) +} +/// Flat particle indices; count and capacity are numbers of uint32_t elements. +pub(crate) unsafe fn native_soft_body_edges( + body: *const RprSoftBody, + buffer: *mut u32, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + let values: Vec<_> = b.edges().iter().flat_map(|v| v.vertices).collect(); + copy_out(&values, buffer, capacity, count) + }) +} +/// Flat particle indices; count and capacity are numbers of uint32_t elements. +pub(crate) unsafe fn native_soft_body_cells( + body: *const RprSoftBody, + buffer: *mut u32, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + let values: Vec<_> = b.cells().iter().flat_map(|v| v.vertices).collect(); + copy_out(&values, buffer, capacity, count) + }) +} +/// Flat particle indices; count and capacity are numbers of uint32_t elements. +pub(crate) unsafe fn native_soft_body_boundary( + body: *const RprSoftBody, + buffer: *mut u32, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let b = &get(body)?.0; + let values: Vec<_> = b.boundary().iter().flatten().copied().collect(); + copy_out(&values, buffer, capacity, count) + }) +} +pub(crate) unsafe fn native_soft_body_pieces( + body: *const RprSoftBody, + buffer: *mut RprSoftBodyHandle, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let values: Vec<_> = get(body)? + .0 + .pieces() + .iter() + .copied() + .map(Into::into) + .collect(); + copy_out(&values, buffer, capacity, count) + }) +} +pub(crate) unsafe fn native_soft_body_set_particle_position( + body: *mut RprSoftBody, + index: usize, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let b = &mut get_mut(body)?.0; + ensure(index < b.num_particles(), "particle index out of range")?; + b.set_particle_position(index, value); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_set_particle_velocity( + body: *mut RprSoftBody, + index: usize, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let b = &mut get_mut(body)?.0; + ensure(index < b.num_particles(), "particle index out of range")?; + b.set_particle_velocity(index, value); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_set_particle_kinematic_target( + body: *mut RprSoftBody, + index: usize, + value: RprVector, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let b = &mut get_mut(body)?.0; + ensure(index < b.num_particles(), "particle index out of range")?; + b.set_particle_kinematic_target(index, value); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_set_particle_pinned( + body: *mut RprSoftBody, + index: usize, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + let b = &mut get_mut(body)?.0; + ensure(index < b.num_particles(), "particle index out of range")?; + b.set_particle_pinned(index, value); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_add_particle_force( + body: *mut RprSoftBody, + index: usize, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let wake_up = boolean(wake_up)?; + let b = &mut get_mut(body)?.0; + ensure(index < b.num_particles(), "particle index out of range")?; + b.add_particle_force(index, value, wake_up); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_apply_particle_impulse( + body: *mut RprSoftBody, + index: usize, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let wake_up = boolean(wake_up)?; + let b = &mut get_mut(body)?.0; + ensure(index < b.num_particles(), "particle index out of range")?; + b.apply_particle_impulse(index, value, wake_up); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_add_force( + body: *mut RprSoftBody, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let wake_up = boolean(wake_up)?; + get_mut(body)?.0.add_force(value, wake_up); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_apply_impulse( + body: *mut RprSoftBody, + value: RprVector, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let wake_up = boolean(wake_up)?; + get_mut(body)?.0.apply_impulse(value, wake_up); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_reset_forces( + body: *mut RprSoftBody, + wake_up: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let wake_up = boolean(wake_up)?; + get_mut(body)?.0.reset_forces(wake_up); + Ok(()) + }) +} +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_wake_up(handle: RprSoftBodyHandle) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + + get_mut(set)? + .0 + .get_mut(handle.raw()) + .ok_or_else(missing)? + .wake_up(); + Ok(()) + }) +} + +pub(crate) unsafe fn native_soft_body_set_enabled( + body: *mut RprSoftBody, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + get_mut(body)?.0.set_enabled(value); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_set_volume_factor( + body: *mut RprSoftBody, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + positive(value)?; + get_mut(body)?.0.set_volume_factor(value); + Ok(()) + }) +} + +pub(crate) unsafe fn native_soft_body_attach_particle( + body: *mut RprSoftBody, + index: usize, + rigid_body: RprRigidBodyHandle, + bodies: *const RprRigidBodySet, +) -> RprStatus { + ffi(|| unsafe { + let bodies = get(bodies)?; + let rb = bodies.0.get(rigid_body.raw()).ok_or_else(missing)?; + ensure( + rb.soft_body().is_none(), + "attachment target must be a rigid body", + )?; + let b = &mut get_mut(body)?.0; + ensure(index < b.num_particles(), "particle index out of range")?; + b.attach_particle(index, rigid_body.raw(), &bodies.0); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_detach_particle( + body: *mut RprSoftBody, + index: usize, +) -> RprStatus { + ffi(|| unsafe { + let b = &mut get_mut(body)?.0; + ensure(index < b.num_particles(), "particle index out of range")?; + b.detach_particle(index); + Ok(()) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_free_soft_body_tear_event( + event: *mut RprSoftBodyTearEvent, +) -> RprStatus { + ffi(|| unsafe { + if !event.is_null() { + get(event)?; + drop(Box::from_raw(event)); + } + Ok(()) + }) +} +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_soft_body( + event: *const RprSoftBodyTearEvent, +) -> RprSoftBodyHandle { + ffi_world_value( + unsafe { get(event).map_or(std::ptr::null_mut(), |e| e.1) }, + |out: *mut RprSoftBodyHandle| { + ffi(|| unsafe { output(out, get(event)?.0.soft_body.into()) }) + }, + ) +} +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_bodies( + event: *const RprSoftBodyTearEvent, + buffer: *mut RprSoftBodyHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array( + get(event).map_or(std::ptr::null_mut(), |e| e.1), + buffer, + capacity, + |count: *mut usize| { + ffi(|| { + let v: Vec<_> = get(event)?.0.bodies().map(Into::into).collect(); + copy_out(&v, buffer, capacity, count) + }) + }, + ) + } +} +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_particle_destination( + event: *const RprSoftBodyTearEvent, + particle: u32, +) -> RprParticleDestination { + ffi_world_value( + unsafe { get(event).map_or(std::ptr::null_mut(), |e| e.1) }, + |result: *mut RprParticleDestination| { + let body = unsafe { std::ptr::addr_of_mut!((*result).body) }; + let index = unsafe { std::ptr::addr_of_mut!((*result).index) }; + + ffi(|| unsafe { + out_ptr(body)?; + out_ptr(index)?; + let (b, i) = get(event)? + .0 + .particle_destination(particle) + .ok_or((RPR_NOT_FOUND, "particle has no destination".into()))?; + output(body, b.into())?; + output(index, i) + }) + }, + ) +} +/// Flat indices; element arity follows the corresponding Rust event field. +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_torn_edges( + event: *const RprSoftBodyTearEvent, + buffer: *mut u32, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let e = &get(event)?.0; + let v: Vec<_> = e.torn_edges.iter().flatten().copied().collect(); + copy_out(&v, buffer, capacity, count) + }) + }) +} +/// Flat indices; element arity follows the corresponding Rust event field. +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_torn_cells( + event: *const RprSoftBodyTearEvent, + buffer: *mut u32, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let e = &get(event)?.0; + let v: Vec<_> = e.torn_cells.iter().flatten().copied().collect(); + copy_out(&v, buffer, capacity, count) + }) + }) +} +/// Flat indices; element arity follows the corresponding Rust event field. +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_removed_edges( + event: *const RprSoftBodyTearEvent, + buffer: *mut u32, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let e = &get(event)?.0; + let v: Vec<_> = e.removed_edges.iter().flatten().copied().collect(); + copy_out(&v, buffer, capacity, count) + }) + }) +} +/// Flat indices; element arity follows the corresponding Rust event field. +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_split_particles( + event: *const RprSoftBodyTearEvent, + buffer: *mut u32, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let e = &get(event)?.0; + let v: Vec<_> = e + .split_particles + .iter() + .flat_map(|&(a, b)| [a, b]) + .collect(); + copy_out(&v, buffer, capacity, count) + }) + }) +} +/// Flat indices; element arity follows the corresponding Rust event field. +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_inserted_particles( + event: *const RprSoftBodyTearEvent, + buffer: *mut u32, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let e = &get(event)?.0; + let v: Vec<_> = e.inserted_particles.clone(); + copy_out(&v, buffer, capacity, count) + }) + }) +} +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_piece_particles( + event: *const RprSoftBodyTearEvent, + piece_index: usize, + buffer: *mut u32, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let p = get(event)? + .0 + .pieces + .get(piece_index) + .ok_or_else(|| invalid("piece index out of range"))?; + copy_out(&p.particles, buffer, capacity, count) + }) + }) +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprSoftClusterSplit { + pub source_cluster: u32, + pub soft_body: RprSoftBodyHandle, + pub cluster: u32, + pub proxy: RprRigidBodyHandle, + pub keeps_proxy: RprBool, +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprSoftJointMove { + pub joint: RprImpulseJointHandle, + pub from: RprRigidBodyHandle, + pub to: RprRigidBodyHandle, +} +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_clusters( + event: *const RprSoftBodyTearEvent, + buffer: *mut RprSoftClusterSplit, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array( + get(event).map_or(std::ptr::null_mut(), |e| e.1), + buffer, + capacity, + |count: *mut usize| { + ffi(|| { + let v: Vec<_> = get(event)? + .0 + .clusters + .iter() + .map(|e| RprSoftClusterSplit { + source_cluster: e.source_cluster, + soft_body: e.soft_body.into(), + cluster: e.cluster, + proxy: e.proxy.into(), + keeps_proxy: e.keeps_proxy as u32, + }) + .collect(); + copy_out(&v, buffer, capacity, count) + }) + }, + ) + } +} +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_moved_joints( + event: *const RprSoftBodyTearEvent, + buffer: *mut RprSoftJointMove, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array( + get(event).map_or(std::ptr::null_mut(), |e| e.1), + buffer, + capacity, + |count: *mut usize| { + ffi(|| { + let v: Vec<_> = get(event)? + .0 + .moved_joints + .iter() + .map(|e| RprSoftJointMove { + joint: e.joint.into(), + from: e.from.into(), + to: e.to.into(), + }) + .collect(); + copy_out(&v, buffer, capacity, count) + }) + }, + ) + } +} + +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_tear( + handle: RprSoftBodyHandle, + edges: *const u32, + edge_count: usize, + cells: *const u32, + cell_count: usize, +) -> *mut RprSoftBodyTearEvent { + let world = handle.world; + ffi_value(|out: *mut *mut RprSoftBodyTearEvent| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + let islands: *mut RprIslandManager = std::ptr::addr_of_mut!((*raw).0.islands).cast(); + let bodies: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + let colliders: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + let impulse_joints: *mut RprImpulseJointSet = + std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + let multibody_joints: *mut RprMultibodyJointSet = + std::ptr::addr_of_mut!((*raw).0.multibody_joints).cast(); + + out_ptr(out)?; + let edges = input(edges, edge_count)?; + let cells = input(cells, cell_count)?; + let set = get_mut(set)?; + let b = set.0.get(handle.raw()).ok_or_else(missing)?; + ensure( + edges.iter().all(|&i| (i as usize) < b.edges().len()) + && cells.iter().all(|&i| (i as usize) < b.cells().len()), + "element index out of range", + )?; + let e = set.0.tear( + handle.raw(), + edges, + cells, + &mut get_mut(islands)?.0, + &mut get_mut(bodies)?.0, + &mut get_mut(colliders)?.0, + &mut get_mut(impulse_joints)?.0, + &mut get_mut(multibody_joints)?.0, + ); + output( + out, + e.map(|e| Box::into_raw(Box::new(RprSoftBodyTearEvent(e, handle.world)))) + .unwrap_or(std::ptr::null_mut()), + ) + }) + }) +} + +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_add_cluster( + handle: RprSoftBodyHandle, + particles: *const u32, + count: usize, +) -> u32 { + let world = handle.world; + ffi_value(|out: *mut u32| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + let bodies: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + let colliders: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + + out_ptr(out)?; + let p = input(particles, count)?; + let s = get_mut(set)?; + let b = s.0.get(handle.raw()).ok_or_else(missing)?; + ensure( + !p.is_empty() && p.iter().all(|&i| (i as usize) < b.num_particles()), + "invalid cluster particles", + )?; + let id = + s.0.add_cluster( + handle.raw(), + p, + &mut get_mut(bodies)?.0, + &mut get_mut(colliders)?.0, + ) + .ok_or_else(|| invalid("cluster could not be created"))?; + output(out, id) + }) + }) +} + +#[rapier_export(soft_body)] +pub unsafe extern "C" fn rpr_soft_body_remove_cluster( + handle: RprSoftBodyHandle, + cluster: u32, +) -> RprStatus { + let world = handle.world; + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let set: *mut RprSoftBodySet = std::ptr::addr_of_mut!((*raw).0.soft_bodies).cast(); + let islands: *mut RprIslandManager = std::ptr::addr_of_mut!((*raw).0.islands).cast(); + let bodies: *mut RprRigidBodySet = std::ptr::addr_of_mut!((*raw).0.bodies).cast(); + let colliders: *mut RprColliderSet = std::ptr::addr_of_mut!((*raw).0.colliders).cast(); + let impulse_joints: *mut RprImpulseJointSet = + std::ptr::addr_of_mut!((*raw).0.impulse_joints).cast(); + let multibody_joints: *mut RprMultibodyJointSet = + std::ptr::addr_of_mut!((*raw).0.multibody_joints).cast(); + + let s = get_mut(set)?; + s.0.get(handle.raw()).ok_or_else(missing)?; + s.0.remove_cluster( + handle.raw(), + cluster, + &mut get_mut(islands)?.0, + &mut get_mut(bodies)?.0, + &mut get_mut(colliders)?.0, + &mut get_mut(impulse_joints)?.0, + &mut get_mut(multibody_joints)?.0, + ) + .ok_or_else(|| invalid("invalid cluster"))?; + Ok(()) + }) +} + +pub(crate) unsafe fn native_soft_body_clusters( + body: *const RprSoftBody, + buffer: *mut u32, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let v: Vec<_> = get(body)?.0.live_clusters().map(|(i, _)| i).collect(); + copy_out(&v, buffer, capacity, count) + }) +} +pub(crate) unsafe fn native_soft_body_cluster_proxy( + body: *const RprSoftBody, + cluster: u32, + out: *mut RprRigidBodyHandle, +) -> RprStatus { + ffi(|| unsafe { + let h = get(body)? + .0 + .cluster_proxy(cluster) + .ok_or_else(|| invalid("invalid cluster"))?; + output(out, h.into()) + }) +} +pub(crate) unsafe fn native_soft_body_cluster_particles( + body: *const RprSoftBody, + cluster: u32, + buffer: *mut u32, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let c = get(body)? + .0 + .cluster(cluster) + .filter(|c| c.is_live()) + .ok_or_else(|| invalid("invalid cluster"))?; + copy_out(c.particles(), buffer, capacity, count) + }) +} +pub(crate) unsafe fn native_soft_body_set_cluster_pinned( + body: *mut RprSoftBody, + cluster: u32, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + let b = &mut get_mut(body)?.0; + b.cluster(cluster) + .filter(|c| c.is_live()) + .ok_or_else(|| invalid("invalid cluster"))?; + b.set_cluster_pinned(cluster, value); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_set_cluster_kinematic_target( + body: *mut RprSoftBody, + cluster: u32, + value: RprPose, +) -> RprStatus { + ffi(|| unsafe { + let value = value.raw()?; + let b = &mut get_mut(body)?.0; + b.cluster(cluster) + .filter(|c| c.is_live()) + .ok_or_else(|| invalid("invalid cluster"))?; + b.set_cluster_kinematic_target(cluster, value); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_set_cluster_shape_matching_enabled( + body: *mut RprSoftBody, + cluster: u32, + value: RprBool, +) -> RprStatus { + ffi(|| unsafe { + let value = boolean(value)?; + let b = &mut get_mut(body)?.0; + b.cluster(cluster) + .filter(|c| c.is_live()) + .ok_or_else(|| invalid("invalid cluster"))?; + b.enable_cluster_shape_matching(cluster, value); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_set_cluster_stiffness_scale( + body: *mut RprSoftBody, + cluster: u32, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let b = &mut get_mut(body)?.0; + b.cluster(cluster) + .filter(|c| c.is_live()) + .ok_or_else(|| invalid("invalid cluster"))?; + b.set_cluster_stiffness_scale(cluster, value); + Ok(()) + }) +} +pub(crate) unsafe fn native_soft_body_set_cluster_tear_resistance( + body: *mut RprSoftBody, + cluster: u32, + value: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let value = nonnegative(value)?; + let b = &mut get_mut(body)?.0; + b.cluster(cluster) + .filter(|c| c.is_live()) + .ok_or_else(|| invalid("invalid cluster"))?; + b.set_cluster_tear_resistance(cluster, value); + Ok(()) + }) +} + +/// Stable identity of a live mesh within one soft body; matches Rapier's SoftMeshId. +#[repr(C)] +#[derive(Clone, Copy, Default, PartialEq, Eq)] +pub struct RprSoftMeshId { + pub cluster: u32, + pub mesh: u32, +} +impl RprSoftMeshId { + fn raw(self) -> SoftMeshId { + SoftMeshId { + cluster: self.cluster, + mesh: self.mesh, + } + } +} +/// Mesh identity and rendering metadata. A render-only mesh has an invalid collider handle. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprSoftMeshInfo { + pub id: RprSoftMeshId, + pub collider: RprColliderHandle, + pub arity: usize, + pub is_skinned: RprBool, + pub collision_enabled: RprBool, +} +/// Enumerates all live meshes, including skins without a physics collider. +pub(crate) unsafe fn native_soft_body_meshes( + body: *const RprSoftBody, + buffer: *mut RprSoftMeshInfo, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let meshes: Vec<_> = get(body)? + .0 + .meshes() + .map(|m| RprSoftMeshInfo { + id: RprSoftMeshId { + cluster: m.id().cluster, + mesh: m.id().mesh, + }, + collider: m.collider().into(), + arity: m.arity(), + is_skinned: m.is_skinned() as RprBool, + collision_enabled: m.collision_enabled() as RprBool, + }) + .collect(); + copy_out(&meshes, buffer, capacity, count) + }) +} +/// Copies world-space vertices of a mesh, including render-only skins. +pub(crate) unsafe fn native_soft_body_mesh_vertices_by_id( + body: *const RprSoftBody, + id: RprSoftMeshId, + buffer: *mut RprVector, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let body = &get(body)?.0; + let mesh = body.mesh(id.raw()).ok_or_else(missing)?; + let vertices: Vec<_> = mesh.vertex_positions(body).map(Into::into).collect(); + copy_out(&vertices, buffer, capacity, count) + }) +} +/// Copies flattened vertex indices. Each element has RprSoftMeshInfo::arity entries. +pub(crate) unsafe fn native_soft_body_mesh_indices_by_id( + body: *const RprSoftBody, + id: RprSoftMeshId, + buffer: *mut u32, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let mesh = get(body)?.0.mesh(id.raw()).ok_or_else(missing)?; + let indices: Vec<_> = (0..mesh.indices().len()) + .flat_map(|i| mesh.element(i).iter().copied()) + .collect(); + copy_out(&indices, buffer, capacity, count) + }) +} +/// Enumerates actual collider handles only. Use SoftBodyMeshes for render-only meshes too. +pub(crate) unsafe fn native_soft_body_mesh_colliders( + body: *const RprSoftBody, + buffer: *mut RprColliderHandle, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + let v: Vec<_> = get(body)? + .0 + .meshes() + .map(|m| m.collider()) + .filter(|h| *h != ColliderHandle::invalid()) + .map(Into::into) + .collect(); + copy_out(&v, buffer, capacity, count) + }) +} +pub(crate) unsafe fn native_soft_body_mesh_vertices( + body: *const RprSoftBody, + collider: RprColliderHandle, + buffer: *mut RprVector, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + ensure( + collider != RprColliderHandle::default(), + "mesh has no collider; use its mesh ID", + )?; + let b = &get(body)?.0; + let m = b.mesh_of(collider.raw()).ok_or_else(missing)?; + let v: Vec<_> = m.vertex_positions(b).map(Into::into).collect(); + copy_out(&v, buffer, capacity, count) + }) +} +/// Indices use mesh vertices, not particles. Arity is two for wire meshes, DIM otherwise. +pub(crate) unsafe fn native_soft_body_mesh_indices( + body: *const RprSoftBody, + collider: RprColliderHandle, + buffer: *mut u32, + capacity: usize, + count: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + ensure( + collider != RprColliderHandle::default(), + "mesh has no collider; use its mesh ID", + )?; + let b = &get(body)?.0; + let m = b.mesh_of(collider.raw()).ok_or_else(missing)?; + let v: Vec<_> = (0..m.indices().len()) + .flat_map(|i| m.element(i).iter().copied()) + .collect(); + copy_out(&v, buffer, capacity, count) + }) +} +pub(crate) unsafe fn native_soft_body_mesh_arity( + body: *const RprSoftBody, + collider: RprColliderHandle, + out: *mut usize, +) -> RprStatus { + ffi(|| unsafe { + ensure( + collider != RprColliderHandle::default(), + "mesh has no collider; use its mesh ID", + )?; + let m = get(body)?.0.mesh_of(collider.raw()).ok_or_else(missing)?; + output(out, m.arity()) + }) +} +pub(crate) unsafe fn native_soft_body_mesh_topology_version( + body: *const RprSoftBody, + collider: RprColliderHandle, + out: *mut u32, +) -> RprStatus { + ffi(|| unsafe { + ensure( + collider != RprColliderHandle::default(), + "mesh has no collider; use its mesh ID", + )?; + let m = get(body)?.0.mesh_of(collider.raw()).ok_or_else(missing)?; + output(out, m.topology_version()) + }) +} + +#[cfg(feature = "fem")] +pub(crate) unsafe fn native_soft_body_set_solver(body: *mut RprSoftBody, solver: u32) -> RprStatus { + ffi(|| unsafe { + let v = match solver { + 0 => SoftBodySolver::Constraints, + 1 => SoftBodySolver::Fem, + _ => return Err(invalid("unknown soft body solver")), + }; + get_mut(body)?.0.set_solver(v); + Ok(()) + }) +} + +/// Set the optional shape-matching target of a live cluster. A null target clears it. +pub(crate) unsafe fn native_soft_body_set_cluster_shape_matching_target( + body: *mut RprSoftBody, + cluster: u32, + target: *const RprPose, +) -> RprStatus { + ffi(|| unsafe { + let target = if target.is_null() { + None + } else { + Some(get(target)?.raw()?) + }; + let b = &mut get_mut(body)?.0; + let c = b + .cluster_mut(cluster) + .filter(|c| c.is_live()) + .ok_or_else(|| invalid("invalid cluster"))?; + c.set_shape_matching_target(target); + Ok(()) + }) +} + +/// Corresponds to `SoftBody::particle_position`. Copies one particle position. +pub(crate) unsafe fn native_soft_body_particle_position( + body: *const RprSoftBody, + index: usize, + out: *mut RprVector, +) -> RprStatus { + ffi(|| unsafe { + let body = &get(body)?.0; + ensure(index < body.num_particles(), "particle index out of bounds")?; + output(out, body.particle_position(index).into()) + }) +} + +/// Optional particle destination after a tear. Missing destinations are normal and set +/// found to false; body/index are only written when a destination exists. +#[rapier_export(soft_body_tear_event)] +pub unsafe extern "C" fn rpr_soft_body_tear_event_try_particle_destination( + event: *const RprSoftBodyTearEvent, + particle: u32, +) -> RprOptionalParticleDestination { + ffi_world_value( + unsafe { get(event).map_or(std::ptr::null_mut(), |e| e.1) }, + |result: *mut RprOptionalParticleDestination| { + let body = unsafe { std::ptr::addr_of_mut!((*result).body) }; + let index = unsafe { std::ptr::addr_of_mut!((*result).index) }; + let found = unsafe { std::ptr::addr_of_mut!((*result).found) }; + + ffi(|| unsafe { + out_ptr(body)?; + out_ptr(index)?; + out_ptr(found)?; + if let Some((destination, new_index)) = get(event)?.0.particle_destination(particle) + { + output(body, destination.into())?; + output(index, new_index)?; + output(found, 1) + } else { + output(found, 0) + } + }) + }, + ) +} + +/// Changes the tear resistance multiplier for an individual edge. +pub(crate) unsafe fn native_soft_body_set_edge_tear_resistance( + body: *mut RprSoftBody, + index: usize, + resistance: RprReal, +) -> RprStatus { + ffi(|| unsafe { + let resistance = nonnegative(resistance)?; + let body = &mut get_mut(body)?.0; + ensure(index < body.edges().len(), "edge index out of range")?; + body.set_edge_tear_resistance(index, resistance); + Ok(()) + }) +} + +/// Cut using DIM points (a segment in 2D, triangle in 3D). A no-op returns a null event. +/// The optional owned event must be freed with FreeSoftBodyTearEvent. +#[rapier_export] +pub unsafe extern "C" fn rpr_cut_soft_body( + handle: RprSoftBodyHandle, + blade: *const RprVector, +) -> *mut RprSoftBodyTearEvent { + let world = handle.world; + ffi_value(|out: *mut *mut RprSoftBodyTearEvent| { + ffi(|| unsafe { + handle.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + out_ptr(out)?; + let blade = input(blade, rapier::math::DIM)? + .iter() + .copied() + .map(RprVector::raw) + .collect::>>()?; + let blade: [Vector; rapier::math::DIM] = blade.try_into().unwrap(); + let event = get_mut(world)?.0.cut_soft_body(handle.raw(), &blade); + output( + out, + event + .map(|e| Box::into_raw(Box::new(RprSoftBodyTearEvent(e, handle.world)))) + .unwrap_or(std::ptr::null_mut()), + ) + }) + }) +} + +/// Parameters for the native volumetric mesher. Enclosure: 0 cover, 1 crust (3D). +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprVolumeMeshParameters { + pub cell_size: RprReal, + #[cfg(feature = "dim2")] + pub min_angle: RprReal, + #[cfg(feature = "dim3")] + pub enclosure: u32, + #[cfg(feature = "dim3")] + pub cover_smoothing: u32, + #[cfg(feature = "dim3")] + pub cover_guard: RprReal, + #[cfg(feature = "dim3")] + pub cover_subdivisions: u32, +} +#[rapier_export] +pub extern "C" fn rpr_new_volume_mesh_parameters(cell_size: RprReal) -> RprVolumeMeshParameters { + let p = rapier::parry::transformation::VolumeMeshParameters::new(cell_size); + RprVolumeMeshParameters { + cell_size: p.cell_size, + #[cfg(feature = "dim2")] + min_angle: p.min_angle, + #[cfg(feature = "dim3")] + enclosure: 0, + #[cfg(feature = "dim3")] + cover_smoothing: p.cover_smoothing, + #[cfg(feature = "dim3")] + cover_guard: p.cover_guard, + #[cfg(feature = "dim3")] + cover_subdivisions: p.cover_subdivisions, + } +} + +/// Whether the collision mesh encloses an interior. Non-mesh colliders return false. +pub(crate) unsafe fn native_soft_body_mesh_is_closed( + body: *const RprSoftBody, + collider: RprColliderHandle, + out: *mut RprBool, +) -> RprStatus { + ffi(|| unsafe { + ensure( + collider != RprColliderHandle::default(), + "expected a valid collider handle", + )?; + output( + out, + get(body)? + .0 + .mesh_of(collider.raw()) + .is_some_and(|m| m.is_closed()) as RprBool, + ) + }) +} diff --git a/c/src/soft_desc.rs b/c/src/soft_desc.rs new file mode 100644 index 000000000..eb3c8cad9 --- /dev/null +++ b/c/src/soft_desc.rs @@ -0,0 +1,551 @@ +//! Soft-body recipes and borrowed topology. All arrays are copied during insertion. +#![allow(non_snake_case)] +use crate::*; +pub const RPR_SOFT_DESC_PARTICLES: u32 = 0; +pub const RPR_SOFT_DESC_ROPE: u32 = 1; +pub const RPR_SOFT_DESC_GRID: u32 = 2; +pub const RPR_SOFT_DESC_CLOTH: u32 = 3; +pub const RPR_SOFT_DESC_CUBOID: u32 = 4; +pub const RPR_SOFT_DESC_SURFACE: u32 = 5; +pub const RPR_SOFT_DESC_DISK: u32 = 6; +pub const RPR_SOFT_DESC_SPHERE: u32 = 7; +pub const RPR_SOFT_DESC_CLOTH_TUBE: u32 = 8; +pub const RPR_SOFT_DESC_VOLUMETRIC: u32 = 9; +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprSoftEdgeSoftness { + pub edge: u32, + pub softness: RprSpringCoefficients, +} +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprSoftEdgeTear { + pub edge: u32, + pub resistance: RprReal, +} +/// Copyable recipe, not an owned procedural builder. Initialize before editing. +/// All array views borrow caller data until build/insert returns; counts +/// for topology arrays are element counts (edges, triangles, or tetrahedra). +/// Nonempty topology overrides the generator's topology. Zero counts retain it. +/// Generator inputs: a/b are rope ends or center/half-extents; cloth uses a/du/dv. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct RprSoftBodyDesc { + pub kind: u32, + pub a: RprVector, + pub b: RprVector, + pub du: RprVector, + pub dv: RprVector, + pub nx: usize, + pub ny: usize, + pub nz: usize, + pub radius: RprReal, + pub radiusEnd: RprReal, + pub translation: RprVector, + pub totalMass: RprOptionalReal, + pub meshing: RprVolumeMeshParameters, + pub positions: RprVectorView, + pub masses: RprRealView, + pub pinned: RprIndexView, + pub edges: RprEdgeView, + pub bendEdges: RprEdgeView, + pub tensionOnlyEdges: RprIndexView, + pub edgeSoftness: RprSoftEdgeSoftnessView, + pub edgeTearResistance: RprSoftEdgeTearView, + pub cells: RprCellView, + pub surface: RprSurfaceElementView, + #[cfg(feature = "dim3")] + pub dihedrals: RprDihedralView, + #[cfg(feature = "dim3")] + pub wire: RprEdgeView, + pub skinVertices: RprVectorView, + pub skinIndices: RprSurfaceElementView, + pub material: RprSoftBodyMaterial, + pub cellModel: u32, + /// 0 = constraints, 1 = FEM (requires a library built with FEM). + pub solver: u32, + pub particleMass: RprReal, + /// Disabled by default: retain the radius computed by the generator. + pub particleRadius: RprOptionalReal, + pub volumePreservation: RprBool, + pub volumeFactor: RprReal, + pub shapeMatching: RprOptionalBool, + pub selfContacts: RprBool, + pub skinCollision: RprBool, + pub collisionEnabled: RprBool, + pub collider: RprColliderDesc, + pub linearDamping: RprReal, + pub gravityScale: RprReal, + pub additionalSolverIterations: usize, + pub additionalPgsIterations: usize, + pub canSleep: RprBool, + pub dominanceGroup: i8, + pub userData: RprUserData, +} +impl Default for RprSoftBodyDesc { + fn default() -> Self { + let settings = rapier::dynamics::SoftBodyParticleSettings::default(); + let mut collider = RprColliderDesc::default(); + collider.shape.radius = 0.05; + collider.density = 0.0; + let meshing = rapier::parry::transformation::VolumeMeshParameters::new(0.1); + Self { + kind: RPR_SOFT_DESC_PARTICLES, + radius: 0.5, + radiusEnd: 0.5, + translation: Vector::ZERO.into(), + totalMass: RprOptionalReal::default(), + meshing: RprVolumeMeshParameters { + cell_size: meshing.cell_size, + #[cfg(feature = "dim2")] + min_angle: meshing.min_angle, + #[cfg(feature = "dim3")] + enclosure: 0, + #[cfg(feature = "dim3")] + cover_smoothing: meshing.cover_smoothing, + #[cfg(feature = "dim3")] + cover_guard: meshing.cover_guard, + #[cfg(feature = "dim3")] + cover_subdivisions: meshing.cover_subdivisions, + }, + a: Vector::ZERO.into(), + b: Vector::ONE.into(), + du: Vector::X.into(), + dv: Vector::Y.into(), + nx: 2, + ny: 2, + nz: 2, + positions: RprVectorView::default(), + masses: RprRealView::default(), + pinned: RprIndexView::default(), + edges: RprEdgeView::default(), + bendEdges: RprEdgeView::default(), + tensionOnlyEdges: RprIndexView::default(), + edgeSoftness: RprSoftEdgeSoftnessView::default(), + edgeTearResistance: RprSoftEdgeTearView::default(), + cells: RprCellView::default(), + surface: RprSurfaceElementView::default(), + #[cfg(feature = "dim3")] + dihedrals: RprDihedralView::default(), + #[cfg(feature = "dim3")] + wire: RprEdgeView::default(), + skinVertices: RprVectorView::default(), + skinIndices: RprSurfaceElementView::default(), + material: SoftBodyMaterial::default().into(), + cellModel: match SoftBodyCellModel::default() { + SoftBodyCellModel::Volume => 0, + SoftBodyCellModel::Corotational => 1, + SoftBodyCellModel::NeoHookean => 2, + }, + solver: 0, + particleMass: 1.0, + particleRadius: RprOptionalReal::default(), + volumePreservation: 0, + volumeFactor: 1.0, + shapeMatching: RprOptionalBool::default(), + selfContacts: 0, + skinCollision: 0, + collisionEnabled: 1, + collider, + linearDamping: settings.linear_damping, + gravityScale: settings.gravity_scale, + additionalSolverIterations: settings.additional_solver_iterations, + additionalPgsIterations: settings.additional_pgs_iterations, + canSleep: settings.can_sleep as _, + dominanceGroup: settings.dominance_group, + userData: 0u128.into(), + } + } +} +impl RprSoftBodyDesc { + pub(crate) unsafe fn raw(&self) -> Result { + use crate::geometry::indices_array; + let points = || { + unsafe { input(self.positions.data, self.positions.count)? } + .iter() + .map(|v| v.raw()) + .collect::>>() + }; + let mut b = match self.kind { + RPR_SOFT_DESC_PARTICLES | RPR_SOFT_DESC_SURFACE => { + ensure( + self.positions.count > 0 && self.positions.count <= u32::MAX as usize, + "invalid particle count", + )?; + let vertices = points()?; + if self.kind == RPR_SOFT_DESC_PARTICLES { + SoftBodyBuilder::new(vertices) + } else { + let idx = unsafe { + indices_array::<{ rapier::math::DIM }>( + self.surface.data.cast(), + self.surface.count, + vertices.len(), + )? + }; + #[cfg(feature = "dim2")] + let b = SoftBodyBuilder::polyline(vertices, Some(idx)); + #[cfg(feature = "dim3")] + let b = SoftBodyBuilder::trimesh(vertices, idx); + b.ok_or_else(|| invalid("invalid soft surface"))? + } + } + #[cfg(feature = "dim2")] + RPR_SOFT_DESC_DISK => { + ensure( + (3..=1_000_000).contains(&self.nx), + "particle count out of range", + )?; + SoftBodyBuilder::disk(self.a.raw()?, positive(self.radius)?, self.nx) + } + #[cfg(feature = "dim3")] + RPR_SOFT_DESC_SPHERE => { + ensure(self.nx <= 6, "sphere subdivisions exceed 6")?; + SoftBodyBuilder::sphere(self.a.raw()?, positive(self.radius)?, self.nx) + } + #[cfg(feature = "dim3")] + RPR_SOFT_DESC_CLOTH_TUBE => { + ensure( + self.nx + .max(3) + .checked_mul(self.ny.max(2)) + .is_some_and(|n| n <= u32::MAX as usize), + "cloth tube too large", + )?; + SoftBodyBuilder::cloth_tube( + self.a.raw()?, + self.b.raw()?, + nonnegative(self.radius)?, + nonnegative(self.radiusEnd)?, + self.nx, + self.ny, + ) + } + RPR_SOFT_DESC_VOLUMETRIC => { + let points = points()?; + let indices = unsafe { + indices_array::<{ rapier::math::DIM }>( + self.surface.data.cast(), + self.surface.count, + points.len(), + )? + }; + let p = &self.meshing; + let mut params = rapier::parry::transformation::VolumeMeshParameters::new( + positive(p.cell_size)?, + ); + #[cfg(feature = "dim2")] + { + params.min_angle = nonnegative(p.min_angle)?; + } + #[cfg(feature = "dim3")] + { + use rapier::parry::transformation::MeshEnclosure; + params.enclosure = match p.enclosure { + 0 => MeshEnclosure::Cover, + 1 => MeshEnclosure::Crust, + _ => return Err(invalid("unknown mesh enclosure")), + }; + params.cover_smoothing = p.cover_smoothing; + params.cover_guard = nonnegative(p.cover_guard)?; + params.cover_subdivisions = p.cover_subdivisions; + } + SoftBodyBuilder::volumetric_with(&points, &indices, ¶ms) + .ok_or_else(|| invalid("volume meshing failed"))? + } + RPR_SOFT_DESC_ROPE => { + ensure( + (2..=u32::MAX as usize).contains(&self.nx), + "invalid rope count", + )?; + SoftBodyBuilder::rope(self.a.raw()?, self.b.raw()?, self.nx) + } + #[cfg(feature = "dim2")] + RPR_SOFT_DESC_GRID => { + self.check_grid(false)?; + SoftBodyBuilder::grid(self.a.raw()?, self.b.raw()?, self.nx, self.ny) + } + #[cfg(feature = "dim3")] + RPR_SOFT_DESC_CUBOID => { + self.check_grid(true)?; + SoftBodyBuilder::cuboid(self.a.raw()?, self.b.raw()?, self.nx, self.ny, self.nz) + } + #[cfg(feature = "dim3")] + RPR_SOFT_DESC_CLOTH => { + ensure( + self.nx >= 2 + && self.ny >= 2 + && self + .nx + .checked_mul(self.ny) + .is_some_and(|n| n <= u32::MAX as usize), + "invalid cloth size", + )?; + SoftBodyBuilder::cloth( + self.a.raw()?, + self.du.raw()?, + self.dv.raw()?, + self.nx, + self.ny, + ) + } + _ => return Err(invalid("unsupported soft-body recipe")), + }; + let n = b.positions.len(); + if self.masses.count != 0 { + ensure(self.masses.count == n, "particle masses length mismatch")?; + b.masses = unsafe { input(self.masses.data, n)? } + .iter() + .map(|v| positive(*v)) + .collect::>()?; + } + b.pinned = unsafe { input(self.pinned.data, self.pinned.count)? }.to_vec(); + ensure( + b.pinned.iter().all(|i| (*i as usize) < n), + "pinned particle out of bounds", + )?; + if self.edges.count != 0 { + b.edges = unsafe { indices_array::<2>(self.edges.data.cast(), self.edges.count, n)? }; + } + if self.bendEdges.count != 0 { + b.bend_edges = + unsafe { indices_array::<2>(self.bendEdges.data.cast(), self.bendEdges.count, n)? }; + } + if self.cells.count != 0 { + b.cells = unsafe { + indices_array::<{ rapier::math::DIM + 1 }>( + self.cells.data.cast(), + self.cells.count, + n, + )? + }; + } + if self.surface.count != 0 && self.kind != RPR_SOFT_DESC_VOLUMETRIC { + b.surface = unsafe { + indices_array::<{ rapier::math::DIM }>( + self.surface.data.cast(), + self.surface.count, + n, + )? + }; + } + #[cfg(feature = "dim3")] + { + if self.dihedrals.count != 0 { + b.dihedrals = unsafe { + indices_array::<4>(self.dihedrals.data.cast(), self.dihedrals.count, n)? + }; + } + if self.wire.count != 0 { + b.wire = unsafe { indices_array::<2>(self.wire.data.cast(), self.wire.count, n)? }; + } + } + let edge_count = b.edges.len() + b.bend_edges.len(); + b.tension_only_edges = + unsafe { input(self.tensionOnlyEdges.data, self.tensionOnlyEdges.count)? }.to_vec(); + ensure( + b.tension_only_edges + .iter() + .all(|i| (*i as usize) < edge_count), + "tension edge out of bounds", + )?; + b.edge_softness = unsafe { input(self.edgeSoftness.data, self.edgeSoftness.count)? } + .iter() + .map(|v| { + ensure( + (v.edge as usize) < edge_count, + "softness edge out of bounds", + )?; + Ok((v.edge, v.softness.raw()?)) + }) + .collect::>()?; + b.edge_tear_resistance = + unsafe { input(self.edgeTearResistance.data, self.edgeTearResistance.count)? } + .iter() + .map(|v| { + ensure((v.edge as usize) < edge_count, "tear edge out of bounds")?; + Ok((v.edge, nonnegative(v.resistance)?)) + }) + .collect::>()?; + if self.skinVertices.count != 0 { + let vertices = unsafe { input(self.skinVertices.data, self.skinVertices.count)? } + .iter() + .map(|v| v.raw()) + .collect::>>()?; + let indices = unsafe { + indices_array::<{ rapier::math::DIM }>( + self.skinIndices.data.cast(), + self.skinIndices.count, + vertices.len(), + )? + }; + b.skin = Some((vertices, indices)); + } else { + ensure(self.skinIndices.count == 0, "skin indices without vertices")?; + } + b.material = self.material.raw()?; + b.cell_model = match self.cellModel { + 0 => SoftBodyCellModel::Volume, + 1 => SoftBodyCellModel::Corotational, + 2 => SoftBodyCellModel::NeoHookean, + _ => return Err(invalid("invalid cell model")), + }; + #[cfg(feature = "fem")] + { + b.solver = match self.solver { + 0 => SoftBodySolver::Constraints, + 1 => SoftBodySolver::Fem, + _ => return Err(invalid("unknown soft solver")), + }; + } + #[cfg(not(feature = "fem"))] + ensure(self.solver == 0, "FEM support is not enabled")?; + b.particle_mass = positive(self.particleMass)?; + if boolean(self.particleRadius.enabled)? { + b.particle_radius = nonnegative(self.particleRadius.value)?; + } + b.volume_preservation = boolean(self.volumePreservation)?; + b.volume_factor = positive(self.volumeFactor)?; + if boolean(self.shapeMatching.enabled)? { + b.shape_matching = boolean(self.shapeMatching.value)?; + } + b.self_contacts = boolean(self.selfContacts)?; + b.skin_collision = boolean(self.skinCollision)?; + b.collider_template = if boolean(self.collisionEnabled)? { + Some(unsafe { self.collider.raw()? }) + } else { + None + }; + b.particle_settings.linear_damping = nonnegative(self.linearDamping)?; + b.particle_settings.gravity_scale = finite(self.gravityScale)?; + b.particle_settings.additional_solver_iterations = self.additionalSolverIterations; + b.particle_settings.additional_pgs_iterations = self.additionalPgsIterations; + b.particle_settings.can_sleep = boolean(self.canSleep)?; + b.particle_settings.dominance_group = self.dominanceGroup; + b.user_data = self.userData.raw(); + if boolean(self.totalMass.enabled)? { + b = b.mass(positive(self.totalMass.value)?); + } + b = b.translated(self.translation.raw()?); + Ok(b) + } + fn check_grid(&self, dim3: bool) -> Result<()> { + let count = self + .nx + .checked_add(1) + .and_then(|x| self.ny.checked_add(1).and_then(|y| x.checked_mul(y))) + .and_then(|n| { + if dim3 { + self.nz.checked_add(1).and_then(|z| n.checked_mul(z)) + } else { + Some(n) + } + }); + ensure( + self.nx > 0 + && self.ny > 0 + && (!dim3 || self.nz > 0) + && count.is_some_and(|n| n <= u32::MAX as usize) + && self.b.raw()?.min_element() > 0.0, + "invalid soft grid size", + ) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_soft_body_desc() -> RprSoftBodyDesc { + RprSoftBodyDesc::default() +} +/// Consumes no caller-owned resources. All borrowed arrays may be released on return. +#[rapier_export] +pub unsafe extern "C" fn rpr_insert_soft_body( + world: *mut RprWorld, + desc: *const RprSoftBodyDesc, +) -> RprSoftBodyHandle { + ffi_world_value(world, |out: *mut RprSoftBodyHandle| { + ffi(|| unsafe { + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + if !out.is_null() { + out_ptr(out)?; + } + let builder = get(desc)?.raw()?; + let h = get_mut(world)?.0.insert_soft_body(builder); + if !out.is_null() { + output(out, h.into())?; + } + Ok(()) + }) + }) +} + +pub const RPR_SOFT_BINDING_SKINNED: u32 = 0; +pub const RPR_SOFT_BINDING_DIRECT: u32 = 1; +pub const RPR_SOFT_BINDING_DIRECT_BY_POSITION: u32 = 2; +/// Non-owning deformable binding description. Direct particle indices are borrowed. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct RprSoftMeshBindingDesc { + pub kind: u32, + pub particles: RprIndexView, + pub epsilon: RprReal, + pub selfContacts: RprBool, +} +impl RprSoftMeshBindingDesc { + unsafe fn raw(&self) -> Result { + let b = match self.kind { + RPR_SOFT_BINDING_SKINNED => SoftMeshBinding::skinned(), + RPR_SOFT_BINDING_DIRECT => SoftMeshBinding::direct( + unsafe { input(self.particles.data, self.particles.count)? }.to_vec(), + ), + RPR_SOFT_BINDING_DIRECT_BY_POSITION => { + SoftMeshBinding::direct_by_position(nonnegative(self.epsilon)?) + } + _ => return Err(invalid("unknown soft binding")), + }; + Ok(b.self_contacts(boolean(self.selfContacts)?)) + } +} +#[rapier_export] +pub extern "C" fn rpr_default_soft_mesh_binding_desc() -> RprSoftMeshBindingDesc { + RprSoftMeshBindingDesc { + kind: 0, + particles: RprIndexView::default(), + epsilon: 0.0, + selfContacts: 0, + } +} +#[rapier_export] +pub unsafe extern "C" fn rpr_insert_deformable_collider( + collider: *const RprColliderDesc, + binding: *const RprSoftMeshBindingDesc, + parent: RprRigidBodyHandle, +) -> RprColliderHandle { + let world = parent.world; + ffi_world_value(world, |out: *mut RprColliderHandle| { + ffi(|| unsafe { + parent.check_world(world)?; + let access = get(world)?.write()?; + let raw = access.raw(); + + let world: *mut RprPhysicsWorld = raw; + + if !out.is_null() { + out_ptr(out)?; + } + let collider = get(collider)?.raw()?; + let binding = get(binding)?.raw()?; + let w = &mut get_mut(world)?.0; + w.bodies.get(parent.raw()).ok_or_else(missing)?; + let handle = w + .insert_deformable(collider, binding, parent.raw()) + .map_err(|e| invalid(e.to_string()))?; + if !out.is_null() { + output(out, handle.into())?; + } + Ok(()) + }) + }) +} diff --git a/c/src/soft_recipes.rs b/c/src/soft_recipes.rs new file mode 100644 index 000000000..584bf95e1 --- /dev/null +++ b/c/src/soft_recipes.rs @@ -0,0 +1,188 @@ +//! POD soft-body recipes and copied previews of procedural geometry. +use crate::*; +/// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +#[rapier_export] +pub extern "C" fn rpr_rope_soft_body_desc( + a: RprVector, + b: RprVector, + particles: usize, +) -> RprSoftBodyDesc { + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_ROPE, + a, + b, + nx: particles, + ..RprSoftBodyDesc::default() + } +} +#[cfg(feature = "dim2")] +/// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +#[rapier_export] +pub extern "C" fn rpr_grid_soft_body_desc( + center: RprVector, + half_extents: RprVector, + nx: usize, + ny: usize, +) -> RprSoftBodyDesc { + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_GRID, + a: center, + b: half_extents, + nx, + ny, + ..RprSoftBodyDesc::default() + } +} +#[cfg(feature = "dim3")] +/// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +#[rapier_export] +pub extern "C" fn rpr_cuboid_soft_body_desc( + center: RprVector, + half_extents: RprVector, + nx: usize, + ny: usize, + nz: usize, +) -> RprSoftBodyDesc { + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_CUBOID, + a: center, + b: half_extents, + nx, + ny, + nz, + ..RprSoftBodyDesc::default() + } +} +#[cfg(feature = "dim3")] +/// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +#[rapier_export] +pub extern "C" fn rpr_cloth_soft_body_desc( + origin: RprVector, + du: RprVector, + dv: RprVector, + nu: usize, + nv: usize, +) -> RprSoftBodyDesc { + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_CLOTH, + a: origin, + du, + dv, + nx: nu, + ny: nv, + ..RprSoftBodyDesc::default() + } +} +#[cfg(feature = "dim2")] +/// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +#[rapier_export] +pub extern "C" fn rpr_disk_soft_body_desc( + center: RprVector, + radius: RprReal, + particles: usize, +) -> RprSoftBodyDesc { + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_DISK, + volumePreservation: 1, + a: center, + radius, + nx: particles, + ..RprSoftBodyDesc::default() + } +} +#[cfg(feature = "dim3")] +/// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +#[rapier_export] +pub extern "C" fn rpr_sphere_soft_body_desc( + center: RprVector, + radius: RprReal, + subdivisions: u32, +) -> RprSoftBodyDesc { + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_SPHERE, + a: center, + radius, + nx: subdivisions as usize, + volumePreservation: 1, + ..RprSoftBodyDesc::default() + } +} +#[cfg(feature = "dim3")] +/// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +#[rapier_export] +pub extern "C" fn rpr_cloth_tube_soft_body_desc( + origin: RprVector, + axis: RprVector, + radius_start: RprReal, + radius_end: RprReal, + num_around: usize, + num_along: usize, +) -> RprSoftBodyDesc { + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_CLOTH_TUBE, + a: origin, + b: axis, + radius: radius_start, + radiusEnd: radius_end, + nx: num_around, + ny: num_along, + ..RprSoftBodyDesc::default() + } +} +/// Initializes a borrowed meshing recipe. Mesh generation happens on preview/insertion. +#[rapier_export] +pub extern "C" fn rpr_volumetric_soft_body_desc( + vertices: RprVectorView, + surface: RprSurfaceElementView, + parameters: RprVolumeMeshParameters, +) -> RprSoftBodyDesc { + RprSoftBodyDesc { + kind: RPR_SOFT_DESC_VOLUMETRIC, + positions: vertices, + surface, + meshing: parameters, + ..RprSoftBodyDesc::default() + } +} +/// Returns a material with the same softness for each constraint family. +#[rapier_export] +pub extern "C" fn rpr_uniform_soft_body_material( + value: RprSpringCoefficients, +) -> RprSoftBodyMaterial { + let mut material = RprSoftBodyMaterial::from(SoftBodyMaterial::default()); + material.edgeSoftness = value; + material.bendSoftness = value; + material.volumeSoftness = value; + material.shapeMatchingSoftness = value; + material +} +/// Copies generated particle positions into caller-owned storage; no persistent builder. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_particle_positions( + desc: *const RprSoftBodyDesc, + buffer: *mut RprVector, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let b = get(desc)?.raw()?; + let v: Vec = b.positions.iter().copied().map(Into::into).collect(); + copy_out(&v, buffer, capacity, count) + }) + }) +} +/// Copies generated cell indices into caller-owned storage. Counts scalar indices. +#[rapier_export(soft_body_desc)] +pub unsafe extern "C" fn rpr_soft_body_desc_cell_indices( + desc: *const RprSoftBodyDesc, + buffer: *mut u32, + capacity: usize, +) -> usize { + ffi_value(|count: *mut usize| { + ffi(|| unsafe { + let b = get(desc)?.raw()?; + let v: Vec = b.cells.iter().flatten().copied().collect(); + copy_out(&v, buffer, capacity, count) + }) + }) +} diff --git a/c/src/tests.rs b/c/src/tests.rs new file mode 100644 index 000000000..b52903776 --- /dev/null +++ b/c/src/tests.rs @@ -0,0 +1,614 @@ +use crate::*; +#[test] +fn panic_boundary_and_thread_local_errors() { + assert_eq!( + crate::error::ffi(|| panic!("intentional boundary test")), + RPR_PANIC + ); + let message = unsafe { std::ffi::CStr::from_ptr(rpr_last_error()) } + .to_str() + .unwrap() + .to_owned(); + assert!(message.contains("intentional boundary test")); + std::thread::spawn(|| { + assert_eq!(rpr_last_status(), RPR_OK); + assert!(unsafe { rpr_ball_shared_shape(-1.0) }.is_null()); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + assert!( + unsafe { std::ffi::CStr::from_ptr(rpr_last_error()) } + .to_str() + .unwrap() + .contains("positive") + ); + }) + .join() + .unwrap(); + assert_eq!( + unsafe { std::ffi::CStr::from_ptr(rpr_last_error()) } + .to_str() + .unwrap(), + message + ); + assert_eq!(unsafe { rpr_free_world(std::ptr::null_mut()) }, RPR_OK); + assert!( + unsafe { std::ffi::CStr::from_ptr(rpr_last_error()) } + .to_bytes() + .is_empty() + ); +} +#[test] +fn buffers_and_null_arrays() { + let mut count = 99; + let mut sentinel = [123u32, 456]; + let code = crate::error::ffi(|| unsafe { + crate::error::copy_out(&[1, 2, 3], sentinel.as_mut_ptr(), 2, &mut count) + }); + assert_eq!(code, RPR_BUFFER_TOO_SMALL); + assert_eq!(count, 3); + assert_eq!(sentinel, [123, 456]); + assert_eq!( + crate::error::ffi(|| unsafe { + crate::error::input::(std::ptr::null(), 0)?; + Ok(()) + }), + RPR_OK + ); + assert_eq!( + crate::error::ffi(|| unsafe { + crate::error::input::(std::ptr::null(), 1)?; + Ok(()) + }), + RPR_NULL_POINTER + ); + assert_eq!( + crate::error::ffi(|| unsafe { + crate::error::input::(sentinel.as_ptr(), usize::MAX)?; + Ok(()) + }), + RPR_INVALID_ARGUMENT + ); +} +#[test] +fn invalid_inputs_leave_objects_unchanged() { + unsafe { + let mut bodies = RprRigidBodySet(RigidBodySet::new()); + let handle = bodies.0.insert(RigidBodyBuilder::dynamic()).into(); + assert_eq!( + native_rigid_body_set_set_enabled(&mut bodies, handle, 2), + RPR_INVALID_ARGUMENT + ); + assert_eq!( + native_rigid_body_set_set_linear_damping(&mut bodies, handle, Real::NAN), + RPR_INVALID_ARGUMENT + ); + let mut enabled = 0; + assert_eq!( + native_rigid_body_set_get_is_enabled(&bodies, handle, &mut enabled), + RPR_OK + ); + assert_eq!(enabled, 1); + } +} + +#[test] +fn build_profile_is_reported_by_the_library() { + unsafe { + let profile = rpr_build_profile(); + assert_eq!( + std::ffi::CStr::from_ptr(profile).to_str().unwrap(), + env!("RAPIER_CARGO_PROFILE") + ); + // A later call must not invalidate the borrowed profile string. + assert_eq!(rpr_free_world(std::ptr::null_mut()), RPR_OK); + assert_eq!( + std::ffi::CStr::from_ptr(profile).to_str().unwrap(), + env!("RAPIER_CARGO_PROFILE") + ); + } +} + +#[test] +fn description_insertion_copies_values_and_preserves_ownership() { + unsafe { + let mut body = rpr_dynamic_rigid_body_desc(); + let world = rpr_new_world(); + assert_eq!(rpr_last_status(), RPR_OK); + let collider = rpr_ball_collider_desc(0.5); + + let body_handle = rpr_insert_rigid_body(world, &body); + assert_eq!(rpr_last_status(), RPR_OK); + let mut collider_handle = rpr_insert_collider(body_handle, &collider); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!( + (&(*(*world).read().unwrap().raw()).0.colliders)[collider_handle.raw()].parent(), + Some(body_handle.raw()) + ); + + // Reusing and changing a description does not change inserted objects. + body.position.translation = Vector::Y.into(); + let other = rpr_insert_rigid_body(world, &body); + assert_eq!(rpr_last_status(), RPR_OK); + rpr_insert_collider(other, &collider); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!((*(*world).read().unwrap().raw()).0.bodies.len(), 2); + assert_eq!( + (&(*(*world).read().unwrap().raw()).0.bodies)[body_handle.raw()].translation(), + Vector::ZERO + ); + + let count = (*(*world).read().unwrap().raw()).0.colliders.len(); + rpr_insert_collider(RprRigidBodyHandle::default(), &collider); + assert_eq!(rpr_last_status(), RPR_INVALID_HANDLE); + assert_eq!((*(*world).read().unwrap().raw()).0.colliders.len(), count); + collider_handle = rpr_insert_collider_without_parent(world, &collider); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!( + (&(*(*world).read().unwrap().raw()).0.colliders)[collider_handle.raw()].parent(), + None + ); + rpr_insert_collider(body_handle, std::ptr::null()); + assert_eq!(rpr_last_status(), RPR_NULL_POINTER); + assert_eq!((*(*world).read().unwrap().raw()).0.bodies.len(), 2); + assert_eq!( + rpr_step(world, std::ptr::null(), std::ptr::null_mut()), + RPR_OK + ); + assert_eq!(rpr_free_world(world), RPR_OK); + + // Constructors return descriptions even for invalid geometry. Building validates it. + let collider = rpr_ball_collider_desc(-1.0); + assert_eq!(collider.shape.radius, -1.0); + let shape = rpr_shape_desc_build(&collider.shape); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + assert!(shape.is_null()); + } +} + +#[test] +fn world_soft_body_insertion_and_particle_access() { + unsafe { + let world = rpr_new_world(); + assert_eq!(rpr_last_status(), RPR_OK); + let desc = rpr_rope_soft_body_desc(Vector::ZERO.into(), Vector::X.into(), 3); + let handle = rpr_insert_soft_body(world, &desc); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!((*(*world).read().unwrap().raw()).0.soft_bodies.len(), 1); + let mut position: RprVector = rpr_soft_body_particle_position(handle, 2); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!(position.raw().unwrap(), Vector::X); + position = rpr_soft_body_particle_position(handle, 3); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + assert_eq!(position.raw().unwrap(), Vector::ZERO); + assert_eq!(rpr_free_world(world), RPR_OK); + } +} + +#[test] +fn error_handlers_are_scoped_thread_local_and_reentrant_safe() { + struct Report { + calls: usize, + status: RprStatus, + message: String, + nested_status: RprStatus, + } + unsafe extern "C" fn report( + status: RprStatus, + message: *const std::ffi::c_char, + user_data: *mut std::ffi::c_void, + ) { + let report = unsafe { &mut *user_data.cast::() }; + report.calls += 1; + report.status = status; + // Calling Rapier here must not recursively call the handler or invalidate message. + report.nested_status = + unsafe { rpr_set_gravity(std::ptr::null_mut(), RprVector::default()) }; + report.message = unsafe { std::ffi::CStr::from_ptr(message) } + .to_string_lossy() + .into_owned(); + } + unsafe { + let mut observed = Report { + calls: 0, + status: RPR_OK, + message: String::new(), + nested_status: RPR_OK, + }; + let previous = rpr_set_error_handler(RprErrorHandler { + callback: Some(report), + user_data: (&mut observed as *mut Report).cast(), + }); + assert!(previous.callback.is_none()); + let mut shape = rpr_ball_shared_shape(-1.0); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + assert_eq!(observed.calls, 1); + assert_eq!(observed.status, RPR_INVALID_ARGUMENT); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + assert!(shape.is_null()); + assert_eq!(observed.nested_status, RPR_NULL_POINTER); + assert!(observed.message.contains("positive")); + assert_eq!( + std::ffi::CStr::from_ptr(rpr_last_error()).to_string_lossy(), + observed.message + ); + assert_eq!(rpr_free_world(std::ptr::null_mut()), RPR_OK); + assert_eq!(observed.calls, 1); + std::thread::spawn(|| { + assert_eq!( + rpr_set_gravity(std::ptr::null_mut(), RprVector::default()), + RPR_NULL_POINTER + ); + }) + .join() + .unwrap(); + assert_eq!(observed.calls, 1); + // Description validation reports exactly one error at the build boundary. + shape = rpr_shape_desc_build(&rpr_ball_collider_desc(-1.0).shape); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + assert_eq!(observed.calls, 2); + assert!(shape.is_null()); + let installed = rpr_set_error_handler(previous); + assert_eq!(installed.user_data, (&mut observed as *mut Report).cast()); + assert_eq!( + rpr_set_gravity(std::ptr::null_mut(), RprVector::default()), + RPR_NULL_POINTER + ); + assert_eq!(observed.calls, 2); + } +} + +#[test] +fn query_predicates_filter_hits_and_misses_are_successful() { + unsafe extern "C" fn only_selected( + data: *mut std::ffi::c_void, + _read: *const RprReadContext, + handle: RprColliderHandle, + ) -> RprBool { + (handle == unsafe { *(data.cast::()) }) as RprBool + } + unsafe { + let mut world = RprPhysicsWorld(PhysicsWorld::new()); + world.0.insert_collider( + ColliderBuilder::ball(0.5).translation(Vector::X * 2.0), + None, + ); + let far = world.0.insert_collider( + ColliderBuilder::ball(0.5).translation(Vector::X * 5.0), + None, + ); + world.0.step(); + let world = RprWorld::new(world); + let mut query = rpr_default_query_options(); + let mut selected = + RprColliderHandle::from(far).with_world((&world as *const RprWorld).cast_mut()); + query.predicate = Some(only_selected); + query.userData = (&mut selected as *mut RprColliderHandle).cast(); + + let value = rpr_cast_ray_toi( + &world, + &query, + Vector::ZERO.into(), + Vector::X.into(), + 10.0, + 1, + ); + + let mut collider = value.collider; + let mut toi = value.toi; + let mut found = value.found; + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!(found, 1); + assert!(collider == selected); + assert!((toi - 4.5).abs() < 1.0e-5); + let value = rpr_cast_ray_toi( + &world, + &query, + Vector::ZERO.into(), + (-Vector::X).into(), + 10.0, + 1, + ); + + collider = value.collider; + toi = value.toi; + found = value.found; + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!(found, 0); + assert_eq!(toi, 0.0); + assert!(collider == RprColliderHandle::default()); + query.predicate = None; + let value = rpr_cast_ray_toi( + &world, + &query, + Vector::ZERO.into(), + Vector::X.into(), + 10.0, + 1, + ); + + collider = value.collider; + toi = value.toi; + found = value.found; + assert_eq!(rpr_last_status(), RPR_OK); + assert!((toi - 1.5).abs() < 1.0e-5); + assert_eq!(found, 1); + assert_ne!(collider, RprColliderHandle::default()); + } +} + +#[test] +fn pid_axes_preserve_uncontrolled_motion_and_body_state() { + unsafe { + let pid = rpr_new_pid_controller(); + assert_eq!(rpr_last_status(), RPR_OK); + let mut bodies = RprRigidBodySet(RigidBodySet::new()); + let handle = bodies + .0 + .insert(RigidBodyBuilder::dynamic().linvel(Vector::Y)); + let zero = angular_out(AngVector::default()); + let gains = RprPidGains { + lin_kp: Vector::splat(10.0).into(), + lin_ki: Vector::ZERO.into(), + lin_kd: Vector::ZERO.into(), + ang_kp: zero, + ang_ki: zero, + ang_kd: zero, + }; + assert_eq!(rpr_pid_controller_set_gains(pid, gains), RPR_OK); + assert_eq!(rpr_pid_controller_set_axes(pid, 1), RPR_OK); + let mut linear = RprVector::default(); + let mut angular = zero; + assert_eq!( + native_pid_controller_rigid_body_correction( + pid, + 0.01, + &bodies, + handle.into(), + Pose::from_translation(Vector::X + Vector::Y).into(), + Vector::ZERO.into(), + zero, + &mut linear, + &mut angular + ), + RPR_OK + ); + assert!(linear.x > 0.0); + assert_eq!(linear.y, 0.0); + assert_eq!(bodies.0[handle].translation(), Vector::ZERO); + assert_eq!(bodies.0[handle].linvel(), Vector::Y); + assert_eq!(rpr_free_pid_controller(pid), RPR_OK); + } +} + +#[test] +fn soft_proxy_can_be_woken_without_exposing_mutable_rigid_body() { + unsafe { + let world = rpr_new_world(); + assert_eq!(rpr_last_status(), RPR_OK); + let desc = rpr_rope_soft_body_desc(Vector::ZERO.into(), Vector::X.into(), 3); + let handle = rpr_insert_soft_body(world, &desc); + assert_eq!(rpr_last_status(), RPR_OK); + let proxy = rpr_soft_body_root_body(handle); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!( + rpr_rigid_body_set_translation(proxy, Vector::ZERO.into(), 1), + RPR_INVALID_ARGUMENT + ); + assert_eq!(rpr_rigid_body_wake_up(proxy, 1), RPR_OK); + assert_eq!(rpr_free_world(world), RPR_OK); + } +} + +#[test] +fn legacy_rigid_snapshot_preserves_state() { + #[derive(serde::Serialize)] + struct State<'a> { + gravity: Vector, + integration_parameters: &'a IntegrationParameters, + islands: &'a IslandManager, + broad_phase: &'a BroadPhaseBvh, + narrow_phase: &'a NarrowPhase, + bodies: &'a RigidBodySet, + colliders: &'a ColliderSet, + impulse_joints: &'a ImpulseJointSet, + multibody_joints: &'a MultibodyJointSet, + } + let mut original = PhysicsWorld::new(); + original.insert( + RigidBodyBuilder::dynamic().translation(Vector::Y * 3.0), + ColliderBuilder::ball(0.5), + ); + original.step(); + let bytes = bincode::serialize(&State { + gravity: original.gravity, + integration_parameters: &original.integration_parameters, + islands: &original.islands, + broad_phase: &original.broad_phase, + narrow_phase: &original.narrow_phase, + bodies: &original.bodies, + colliders: &original.colliders, + impulse_joints: &original.impulse_joints, + multibody_joints: &original.multibody_joints, + }) + .unwrap(); + unsafe { + let restored = rpr_deserialize_rigid_state(bytes.as_ptr(), bytes.len()); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!((*(*restored).read().unwrap().raw()).0.bodies.len(), 1); + original.step(); + assert_eq!( + rpr_step(restored, std::ptr::null(), std::ptr::null()), + RPR_OK + ); + let a = original.bodies.iter().next().unwrap().1; + let access = (*restored).read().unwrap(); + let b = (*access.raw()).0.bodies.iter().next().unwrap().1; + assert_eq!(a.position(), b.position()); + assert_eq!(a.linvel(), b.linvel()); + drop(access); + assert_eq!(rpr_free_world(restored), RPR_OK); + } +} + +#[cfg(feature = "dim3")] +#[test] +fn render_only_meshes_have_distinct_ids_and_no_collider_cache_key() { + let mut world = PhysicsWorld::new(); + let (vertices, indices) = rapier::parry::shape::Ball::new(0.75).to_trimesh(8, 8); + let handle = world.insert_soft_body( + SoftBodyBuilder::cuboid(Vector::ZERO, Vector::ONE, 3, 3, 3) + .skin(vertices, indices) + .skin_collision(false), + ); + let body = world.soft_bodies.get(handle).unwrap(); + let native: Vec<_> = body.meshes().collect(); + assert!( + native + .iter() + .any(|m| m.is_skinned() && m.collider() == ColliderHandle::invalid()) + ); + let body = (body as *const SoftBody).cast::(); + unsafe { + let mut count = 0; + assert_eq!( + native_soft_body_meshes(body, std::ptr::null_mut(), 0, &mut count), + RPR_OK + ); + assert_eq!(count, native.len()); + let mut meshes = Vec::::with_capacity(count); + assert_eq!( + native_soft_body_meshes(body, meshes.as_mut_ptr(), count, &mut count), + RPR_OK + ); + meshes.set_len(count); + for (i, mesh) in meshes.iter().enumerate() { + assert!(!meshes[..i].iter().any(|other| other.id == mesh.id)); + let mut nv = 0; + assert_eq!( + native_soft_body_mesh_vertices_by_id( + body, + mesh.id, + std::ptr::null_mut(), + 0, + &mut nv + ), + RPR_OK + ); + let mut vertices = vec![RprVector::default(); nv]; + assert_eq!( + native_soft_body_mesh_vertices_by_id( + body, + mesh.id, + vertices.as_mut_ptr(), + nv, + &mut nv + ), + RPR_OK + ); + assert_eq!( + vertices + .iter() + .map(|v| v.raw().unwrap()) + .collect::>(), + native[i] + .vertex_positions(&world.soft_bodies[handle]) + .collect::>() + ); + let mut ni = 0; + assert_eq!( + native_soft_body_mesh_indices_by_id( + body, + mesh.id, + std::ptr::null_mut(), + 0, + &mut ni + ), + RPR_OK + ); + let mut indices = vec![0; ni]; + assert_eq!( + native_soft_body_mesh_indices_by_id( + body, + mesh.id, + indices.as_mut_ptr(), + ni, + &mut ni + ), + RPR_OK + ); + assert_eq!(indices, native[i].indices().as_flattened()); + assert!(indices.iter().all(|&v| (v as usize) < nv)); + } + assert_eq!( + native_soft_body_mesh_vertices( + body, + RprColliderHandle::default(), + std::ptr::null_mut(), + 0, + &mut count + ), + RPR_INVALID_ARGUMENT + ); + assert_eq!( + native_soft_body_mesh_colliders(body, std::ptr::null_mut(), 0, &mut count), + RPR_OK + ); + let mut handles = vec![RprColliderHandle::default(); count]; + assert_eq!( + native_soft_body_mesh_colliders(body, handles.as_mut_ptr(), count, &mut count), + RPR_OK + ); + assert!(handles.iter().all(|&h| h != RprColliderHandle::default())); + assert_eq!( + native_soft_body_mesh_vertices_by_id( + body, + RprSoftMeshId { + cluster: u32::MAX, + mesh: 0 + }, + std::ptr::null_mut(), + 0, + &mut count + ), + RPR_INVALID_HANDLE + ); + } +} + +#[test] +fn value_returns_preserve_status_until_the_next_fallible_call() { + unsafe { + assert!(rpr_ball_shared_shape(-1.0).is_null()); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + let message = std::ffi::CStr::from_ptr(rpr_last_error()).to_owned(); + let _ = rpr_build_info(); + let _ = rpr_dynamic_rigid_body_desc(); + assert_eq!(rpr_last_status(), RPR_INVALID_ARGUMENT); + assert_eq!( + std::ffi::CStr::from_ptr(rpr_last_error()), + message.as_c_str() + ); + let world = rpr_new_world(); + assert!(!world.is_null()); + assert_eq!(rpr_last_status(), RPR_OK); + assert!( + std::ffi::CStr::from_ptr(rpr_last_error()) + .to_bytes() + .is_empty() + ); + assert_eq!(rpr_free_world(world), RPR_OK); + } +} + +#[test] +fn value_returns_discard_partial_values_after_a_panic() { + let value: RprVector = ffi_value(|out| { + ffi(|| { + unsafe { + output(out, Vector::X.into())?; + } + panic!("value return boundary test"); + }) + }); + assert_eq!(value.raw().unwrap(), Vector::ZERO); + assert_eq!(rpr_last_status(), RPR_PANIC); +} diff --git a/c/src/types.rs b/c/src/types.rs new file mode 100644 index 000000000..876da0409 --- /dev/null +++ b/c/src/types.rs @@ -0,0 +1,586 @@ +use crate::*; + +#[cfg(feature = "f32")] +pub type RprReal = f32; +#[cfg(feature = "f64")] +pub type RprReal = f64; +/// ABI booleans are uint32_t: zero is false, one is true. +pub type RprBool = u32; +pub const RPR_ABI_VERSION: u32 = 1; +pub const RPR_DYNAMIC: u32 = 0; +pub const RPR_FIXED: u32 = 1; +pub const RPR_KINEMATIC_POSITION_BASED: u32 = 2; +pub const RPR_KINEMATIC_VELOCITY_BASED: u32 = 3; +pub const RPR_COLLISION_EVENTS: u32 = 1; +pub const RPR_CONTACT_FORCE_EVENTS: u32 = 2; +pub const RPR_COMBINE_AVERAGE: u32 = 0; +pub const RPR_COMBINE_MIN: u32 = 1; +pub const RPR_COMBINE_MULTIPLY: u32 = 2; +pub const RPR_COMBINE_MAX: u32 = 3; + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprVector { + pub x: RprReal, + pub y: RprReal, + #[cfg(feature = "dim3")] + pub z: RprReal, +} +impl RprVector { + pub(crate) fn raw(self) -> Result { + finite(self.x)?; + finite(self.y)?; + #[cfg(feature = "dim2")] + { + Ok(Vector::new(self.x, self.y)) + } + #[cfg(feature = "dim3")] + { + finite(self.z)?; + Ok(Vector::new(self.x, self.y, self.z)) + } + } +} +impl From for RprVector { + fn from(v: Vector) -> Self { + Self { + x: v.x, + y: v.y, + #[cfg(feature = "dim3")] + z: v.z, + } + } +} +#[cfg(feature = "dim2")] +pub type RprAngVector = RprReal; +#[cfg(feature = "dim3")] +pub type RprAngVector = RprVector; +pub(crate) fn angular(v: RprAngVector) -> Result { + #[cfg(feature = "dim2")] + { + finite(v) + } + #[cfg(feature = "dim3")] + { + v.raw() + } +} +/// 2D: angle in radians. 3D: unit quaternion in x,y,z,w order (normalized on input). +#[repr(C)] +#[derive(Copy, Clone)] +pub struct RprRotation { + #[cfg(feature = "dim2")] + pub angle: RprReal, + #[cfg(feature = "dim3")] + pub x: RprReal, + #[cfg(feature = "dim3")] + pub y: RprReal, + #[cfg(feature = "dim3")] + pub z: RprReal, + #[cfg(feature = "dim3")] + pub w: RprReal, +} +impl Default for RprRotation { + fn default() -> Self { + Rotation::IDENTITY.into() + } +} +impl RprRotation { + pub(crate) fn raw(self) -> Result { + #[cfg(feature = "dim2")] + { + Ok(Rotation::from_angle(finite(self.angle)?)) + } + #[cfg(feature = "dim3")] + { + finite(self.x)?; + finite(self.y)?; + finite(self.z)?; + finite(self.w)?; + let q = Rotation::from_xyzw(self.x, self.y, self.z, self.w); + ensure( + q.length_squared().is_finite() && q.length_squared() > 1.0e-20, + "quaternion must be nonzero and finite", + )?; + Ok(q.normalize()) + } + } +} +impl From for RprRotation { + fn from(q: Rotation) -> Self { + #[cfg(feature = "dim2")] + { + Self { angle: q.angle() } + } + #[cfg(feature = "dim3")] + { + Self { + x: q.x, + y: q.y, + z: q.z, + w: q.w, + } + } + } +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprPose { + pub translation: RprVector, + pub rotation: RprRotation, +} +impl RprPose { + pub(crate) fn raw(self) -> Result { + Ok(Pose::from_parts( + self.translation.raw()?, + self.rotation.raw()?, + )) + } +} +impl From for RprPose { + fn from(p: Pose) -> Self { + Self { + translation: p.translation.into(), + rotation: p.rotation.into(), + } + } +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprInteractionGroups { + pub memberships: u32, + pub filter: u32, + pub test_mode: u32, +} +impl RprInteractionGroups { + pub(crate) fn raw(self) -> Result { + ensure(self.test_mode <= 1, "invalid group test mode")?; + Ok(InteractionGroups::new( + Group::from_bits_retain(self.memberships), + Group::from_bits_retain(self.filter), + if self.test_mode == 0 { + InteractionTestMode::And + } else { + InteractionTestMode::Or + }, + )) + } +} +impl From for RprInteractionGroups { + fn from(g: InteractionGroups) -> Self { + Self { + memberships: g.memberships.bits(), + filter: g.filter.bits(), + test_mode: g.test_mode as u32, + } + } +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprUserData { + pub low: u64, + pub high: u64, +} +impl RprUserData { + pub(crate) fn raw(self) -> u128 { + self.low as u128 | ((self.high as u128) << 64) + } +} +impl From for RprUserData { + fn from(v: u128) -> Self { + Self { + low: v as u64, + high: (v >> 64) as u64, + } + } +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprAabb { + pub mins: RprVector, + pub maxs: RprVector, +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprSpringCoefficients { + pub natural_frequency: RprReal, + pub damping_ratio: RprReal, +} +impl RprSpringCoefficients { + pub(crate) fn raw(self) -> Result> { + Ok(SpringCoefficients::new( + nonnegative(self.natural_frequency)?, + nonnegative(self.damping_ratio)?, + )) + } +} +impl From> for RprSpringCoefficients { + fn from(v: SpringCoefficients) -> Self { + Self { + natural_frequency: v.natural_frequency, + damping_ratio: v.damping_ratio, + } + } +} +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprBuildInfo { + pub abi_version: u32, + pub dimension: u32, + pub real_size: u32, + pub pointer_size: u32, +} +#[rapier_export] +pub extern "C" fn rpr_build_info() -> RprBuildInfo { + RprBuildInfo { + abi_version: RPR_ABI_VERSION, + dimension: rapier::math::DIM as u32, + real_size: std::mem::size_of::() as u32, + pointer_size: std::mem::size_of::() as u32, + } +} +/// Release version of the loaded C bindings, e.g. "0.35.3+c.2". +/// The suffix identifies the C bindings revision for the Rust crate version. +/// The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. +/// This release identifier is independent of the ABI compatibility version. +#[rapier_export] +pub extern "C" fn rpr_version() -> *const std::ffi::c_char { + static VERSION: &[u8] = concat!(env!("RAPIER_C_VERSION"), "\0").as_bytes(); + VERSION.as_ptr().cast() +} + +/// Cargo profile of the loaded physics library: "debug" or "release". +/// Custom profiles report the corresponding inherited Cargo profile category. +/// The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. +/// This is independent of the consumer's build mode and of per-package optimization overrides. +#[rapier_export] +pub extern "C" fn rpr_build_profile() -> *const std::ffi::c_char { + static PROFILE: &[u8] = concat!(env!("RAPIER_CARGO_PROFILE"), "\0").as_bytes(); + PROFILE.as_ptr().cast() +} + +/// Features available through the loaded C library, independent of consumer defines. +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct RprBuildFeatures { + /// Whether native profiling timers were compiled in. + pub profiling: RprBool, + /// Solver SIMD lane count. Hardware instruction width depends on the target CPU. + pub simd_lanes: u32, + /// Whether this library exposes Rapier's parallel execution and thread-pool APIs. + pub parallel: RprBool, +} +#[rapier_export] +pub extern "C" fn rpr_build_features() -> RprBuildFeatures { + RprBuildFeatures { + profiling: cfg!(feature = "profiler") as RprBool, + simd_lanes: rapier::math::SIMD_WIDTH as u32, + parallel: cfg!(feature = "parallel") as RprBool, + } +} + +pub(crate) fn body_type(value: u32) -> Result { + match value { + 0 => Ok(RigidBodyType::Dynamic), + 1 => Ok(RigidBodyType::Fixed), + 2 => Ok(RigidBodyType::KinematicPositionBased), + 3 => Ok(RigidBodyType::KinematicVelocityBased), + _ => Err(invalid("unknown rigid body type")), + } +} +pub(crate) fn combine(value: u32) -> Result { + match value { + 0 => Ok(CoefficientCombineRule::Average), + 1 => Ok(CoefficientCombineRule::Min), + 2 => Ok(CoefficientCombineRule::Multiply), + 3 => Ok(CoefficientCombineRule::Max), + _ => Err(invalid("unknown combine rule")), + } +} + +/// Copyable non-owning handle: world pointer plus entity index and generation. +/// The world must remain alive throughout every use. Copying does not retain it. +/// UINT32_MAX/UINT32_MAX with a NULL world is invalid. +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct RprRigidBodyHandle { + /// Borrowed owning world. Never use this handle after freeing that world. + pub world: *mut RprWorld, + pub index: u32, + pub generation: u32, +} +impl Default for RprRigidBodyHandle { + fn default() -> Self { + Self { + world: std::ptr::null_mut(), + index: u32::MAX, + generation: u32::MAX, + } + } +} +impl RprRigidBodyHandle { + pub(crate) fn raw(self) -> RigidBodyHandle { + RigidBodyHandle::from_raw_parts(self.index, self.generation) + } +} +impl From for RprRigidBodyHandle { + fn from(h: RigidBodyHandle) -> Self { + let (index, generation) = h.into_raw_parts(); + Self { + world: std::ptr::null_mut(), + index, + generation, + } + } +} +/// Copyable non-owning handle: world pointer plus entity index and generation. +/// The world must remain alive throughout every use. Copying does not retain it. +/// UINT32_MAX/UINT32_MAX with a NULL world is invalid. +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct RprColliderHandle { + /// Borrowed owning world. Never use this handle after freeing that world. + pub world: *mut RprWorld, + pub index: u32, + pub generation: u32, +} +impl Default for RprColliderHandle { + fn default() -> Self { + Self { + world: std::ptr::null_mut(), + index: u32::MAX, + generation: u32::MAX, + } + } +} +impl RprColliderHandle { + pub(crate) fn raw(self) -> ColliderHandle { + ColliderHandle::from_raw_parts(self.index, self.generation) + } +} +impl From for RprColliderHandle { + fn from(h: ColliderHandle) -> Self { + let (index, generation) = h.into_raw_parts(); + Self { + world: std::ptr::null_mut(), + index, + generation, + } + } +} +/// Copyable non-owning handle: world pointer plus entity index and generation. +/// The world must remain alive throughout every use. Copying does not retain it. +/// UINT32_MAX/UINT32_MAX with a NULL world is invalid. +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct RprImpulseJointHandle { + /// Borrowed owning world. Never use this handle after freeing that world. + pub world: *mut RprWorld, + pub index: u32, + pub generation: u32, +} +impl Default for RprImpulseJointHandle { + fn default() -> Self { + Self { + world: std::ptr::null_mut(), + index: u32::MAX, + generation: u32::MAX, + } + } +} +impl RprImpulseJointHandle { + pub(crate) fn raw(self) -> ImpulseJointHandle { + ImpulseJointHandle::from_raw_parts(self.index, self.generation) + } +} +impl From for RprImpulseJointHandle { + fn from(h: ImpulseJointHandle) -> Self { + let (index, generation) = h.into_raw_parts(); + Self { + world: std::ptr::null_mut(), + index, + generation, + } + } +} +/// Copyable non-owning handle: world pointer plus entity index and generation. +/// The world must remain alive throughout every use. Copying does not retain it. +/// UINT32_MAX/UINT32_MAX with a NULL world is invalid. +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct RprMultibodyJointHandle { + /// Borrowed owning world. Never use this handle after freeing that world. + pub world: *mut RprWorld, + pub index: u32, + pub generation: u32, +} +impl Default for RprMultibodyJointHandle { + fn default() -> Self { + Self { + world: std::ptr::null_mut(), + index: u32::MAX, + generation: u32::MAX, + } + } +} +impl RprMultibodyJointHandle { + pub(crate) fn raw(self) -> MultibodyJointHandle { + MultibodyJointHandle::from_raw_parts(self.index, self.generation) + } +} +impl From for RprMultibodyJointHandle { + fn from(h: MultibodyJointHandle) -> Self { + let (index, generation) = h.into_raw_parts(); + Self { + world: std::ptr::null_mut(), + index, + generation, + } + } +} +/// Copyable non-owning handle: world pointer plus entity index and generation. +/// The world must remain alive throughout every use. Copying does not retain it. +/// UINT32_MAX/UINT32_MAX with a NULL world is invalid. +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct RprSoftBodyHandle { + /// Borrowed owning world. Never use this handle after freeing that world. + pub world: *mut RprWorld, + pub index: u32, + pub generation: u32, +} +impl Default for RprSoftBodyHandle { + fn default() -> Self { + Self { + world: std::ptr::null_mut(), + index: u32::MAX, + generation: u32::MAX, + } + } +} +impl RprSoftBodyHandle { + pub(crate) fn raw(self) -> SoftBodyHandle { + SoftBodyHandle::from_raw_parts(self.index, self.generation) + } +} +impl From for RprSoftBodyHandle { + fn from(h: SoftBodyHandle) -> Self { + let (index, generation) = h.into_raw_parts(); + Self { + world: std::ptr::null_mut(), + index, + generation, + } + } +} + +pub const RPR_FILTER_CONTACT_PAIRS: u32 = 1; +pub const RPR_FILTER_INTERSECTION_PAIR: u32 = 2; +pub const RPR_MODIFY_SOLVER_CONTACTS: u32 = 4; +pub const RPR_QUERY_EXCLUDE_FIXED: u32 = 1; +pub const RPR_QUERY_EXCLUDE_KINEMATIC: u32 = 2; +pub const RPR_QUERY_EXCLUDE_DYNAMIC: u32 = 4; +pub const RPR_QUERY_EXCLUDE_SENSORS: u32 = 8; +pub const RPR_QUERY_EXCLUDE_SOLIDS: u32 = 16; +pub const RPR_QUERY_ONLY_DYNAMIC: u32 = 3; +pub const RPR_QUERY_ONLY_KINEMATIC: u32 = 5; +pub const RPR_QUERY_ONLY_FIXED: u32 = 6; +pub const RPR_DEBUG_COLLIDER_SHAPES: u32 = 1; +pub const RPR_DEBUG_RIGID_BODY_AXES: u32 = 2; +pub const RPR_DEBUG_MULTIBODY_JOINTS: u32 = 4; +pub const RPR_DEBUG_IMPULSE_JOINTS: u32 = 8; +pub const RPR_DEBUG_SOLVER_CONTACTS: u32 = 16; +pub const RPR_DEBUG_CONTACTS: u32 = 32; +pub const RPR_DEBUG_COLLIDER_AABBS: u32 = 64; +pub const RPR_DEBUG_SOFT_BODIES: u32 = 128; +pub const RPR_DEBUG_PSEUDO_NORMALS: u32 = 256; +pub const RPR_DEBUG_SOFT_VOLUME_CONTACTS: u32 = 512; +pub const RPR_DEBUG_SOFT_BODY_STRESS: u32 = 1024; +pub const RPR_GROUPS_AND: u32 = 0; +pub const RPR_GROUPS_OR: u32 = 1; +pub const RPR_MOTOR_ACCELERATION_BASED: u32 = 0; +pub const RPR_MOTOR_FORCE_BASED: u32 = 1; +pub const RPR_SOFT_CELL_VOLUME: u32 = 0; +pub const RPR_SOFT_CELL_COROTATIONAL: u32 = 1; +pub const RPR_SOFT_CELL_NEO_HOOKEAN: u32 = 2; +pub const RPR_SOFT_SOLVER_CONSTRAINTS: u32 = 0; +pub const RPR_SOFT_SOLVER_FEM: u32 = 1; +pub const RPR_AXIS_LIN_X: u32 = 0; +pub const RPR_AXIS_LIN_Y: u32 = 1; +pub const RPR_LOCK_TRANSLATION_X: u32 = 1; +pub const RPR_LOCK_TRANSLATION_Y: u32 = 2; +pub const RPR_LOCK_TRANSLATION_Z: u32 = 4; +pub const RPR_LOCK_ROTATION_X: u32 = 8; +pub const RPR_LOCK_ROTATION_Y: u32 = 16; +pub const RPR_LOCK_ROTATION_Z: u32 = 32; +#[cfg(feature = "dim2")] +pub const RPR_AXIS_ANG_X: u32 = 2; +#[cfg(feature = "dim2")] +pub const RPR_JOINT_FIXED_AXES: u32 = 7; +#[cfg(feature = "dim2")] +pub const RPR_JOINT_REVOLUTE_AXES: u32 = 3; +#[cfg(feature = "dim2")] +pub const RPR_JOINT_PRISMATIC_AXES: u32 = 6; +#[cfg(feature = "dim3")] +pub const RPR_AXIS_LIN_Z: u32 = 2; +#[cfg(feature = "dim3")] +pub const RPR_AXIS_ANG_X: u32 = 3; +#[cfg(feature = "dim3")] +pub const RPR_AXIS_ANG_Y: u32 = 4; +#[cfg(feature = "dim3")] +pub const RPR_AXIS_ANG_Z: u32 = 5; +#[cfg(feature = "dim3")] +pub const RPR_JOINT_FIXED_AXES: u32 = 63; +#[cfg(feature = "dim3")] +pub const RPR_JOINT_REVOLUTE_AXES: u32 = 55; +#[cfg(feature = "dim3")] +pub const RPR_JOINT_PRISMATIC_AXES: u32 = 62; +#[cfg(feature = "dim3")] +pub const RPR_JOINT_SPHERICAL_AXES: u32 = 7; + +pub(crate) fn angular_out(v: AngVector) -> RprAngVector { + #[cfg(feature = "dim2")] + { + v + } + #[cfg(feature = "dim3")] + { + v.into() + } +} +pub(crate) fn real_pi() -> Real { + #[cfg(feature = "f32")] + { + std::f32::consts::PI + } + #[cfg(feature = "f64")] + { + std::f64::consts::PI + } +} + +/// Read-only body type used by soft-body cluster proxies; not valid for builder_new. +pub const RPR_SOFT_FRAME: u32 = 4; +pub const RPR_SHAPE_CAST_OUT_OF_ITERATIONS: u32 = 0; +pub const RPR_SHAPE_CAST_CONVERGED: u32 = 1; +pub const RPR_SHAPE_CAST_FAILED: u32 = 2; +pub const RPR_SHAPE_CAST_PENETRATING: u32 = 3; +pub const RPR_FEATURE_UNKNOWN: u32 = 0; +pub const RPR_FEATURE_VERTEX: u32 = 1; +pub const RPR_FEATURE_EDGE: u32 = 2; +pub const RPR_FEATURE_FACE: u32 = 3; + +/// Native URDF/MJCF multibody insertion flags. +pub const RPR_MULTIBODY_JOINTS_ARE_KINEMATIC: u8 = 1; +pub const RPR_MULTIBODY_DISABLE_SELF_CONTACTS: u8 = 2; +pub const RPR_MULTIBODY_SKIP_LOOP_CLOSURES: u8 = 4; +pub const RPR_MULTIBODY_SKIP_JOINT_MOTORS: u8 = 8; +pub const RPR_MULTIBODY_SKIP_JOINT_LIMITS: u8 = 16; +pub const RPR_MULTIBODY_SKIP_JOINT_SPRINGS: u8 = 32; + +/// Parry triangle-mesh and heightfield flags used by the public shape constructors. +pub const RPR_TRIMESH_MERGE_DUPLICATE_VERTICES: u32 = 16; +pub const RPR_TRIMESH_FIX_INTERNAL_EDGES: u32 = 144; +pub const RPR_TRIMESH_DEFORMABLE: u32 = 256; +pub const RPR_TRIMESH_FIX_INTERNAL_EDGES_TWO_SIDED: u32 = 656; +pub const RPR_HEIGHTFIELD_FIX_INTERNAL_EDGES: u32 = 1; diff --git a/c/src/world.rs b/c/src/world.rs new file mode 100644 index 000000000..a427face2 --- /dev/null +++ b/c/src/world.rs @@ -0,0 +1,116 @@ +//! Owning C world and dynamic borrow checks at the foreign-language boundary. +use crate::*; +use std::{ + cell::UnsafeCell, + sync::atomic::{AtomicUsize, Ordering}, +}; + +const WRITER: usize = usize::MAX; +/// Sole owner of simulation state. Handles belong to the world that created them. +/// Ordinary reads may overlap. A mutation or step requires exclusive access. +/// Destruction must be externally synchronized with all users of this pointer. +pub struct RprWorld { + access: AtomicUsize, + data: UnsafeCell, +} +// Access to simulation data is protected by the atomic shared/exclusive gate. +unsafe impl Send for RprWorld {} +unsafe impl Sync for RprWorld {} +impl RprWorld { + pub(crate) fn new(data: RprPhysicsWorld) -> Self { + Self { + access: AtomicUsize::new(0), + data: UnsafeCell::new(data), + } + } + pub(crate) fn read(&self) -> Result> { + self.access + .fetch_update(Ordering::Acquire, Ordering::Relaxed, |n| { + (n < WRITER - 1).then(|| n + 1) + }) + .map_err(|_| busy())?; + Ok(WorldRead(self)) + } + pub(crate) fn write(&self) -> Result> { + self.access + .compare_exchange(0, WRITER, Ordering::Acquire, Ordering::Relaxed) + .map_err(|_| busy())?; + Ok(WorldWrite(self)) + } +} +fn busy() -> (RprStatus, String) { + (RPR_WORLD_BUSY, "world is already borrowed; use callback read access or defer the mutation until the operation returns".into()) +} +pub(crate) struct WorldRead<'a>(&'a RprWorld); +impl WorldRead<'_> { + pub(crate) fn raw(&self) -> *const RprPhysicsWorld { + self.0.data.get() + } +} +impl Drop for WorldRead<'_> { + fn drop(&mut self) { + self.0.access.fetch_sub(1, Ordering::Release); + } +} +pub(crate) struct WorldWrite<'a>(&'a RprWorld); +impl WorldWrite<'_> { + pub(crate) fn raw(&self) -> *mut RprPhysicsWorld { + self.0.data.get() + } +} +impl Drop for WorldWrite<'_> { + fn drop(&mut self) { + self.0.access.store(0, Ordering::Release); + } +} +/// Create an owned world. Release it with FreeWorld. +#[rapier_export] +pub unsafe extern "C" fn rpr_new_world() -> *mut RprWorld { + ffi_value(|out: *mut *mut RprWorld| { + ffi(|| unsafe { + out_ptr(out)?; + output( + out, + Box::into_raw(Box::new(RprWorld::new( + RprPhysicsWorld(PhysicsWorld::new()), + ))), + ) + }) + }) +} +/// Free a world. NULL is allowed. Rejects destruction from an active callback. +/// The caller must prevent other threads from starting calls during destruction. +#[rapier_export] +pub unsafe extern "C" fn rpr_free_world(world: *mut RprWorld) -> RprStatus { + ffi(|| unsafe { + if world.is_null() { + return Ok(()); + } + let access = get(world)?.write()?; + // End the shared reference to the wrapper, keeping the gate closed until destruction. + std::mem::forget(access); + drop(Box::from_raw(world)); + Ok(()) + }) +} + +/// Callback-scoped read access to bodies and colliders. Never retain or free it. +/// Only the Read* functions accept this context; it cannot mutate the world. +pub struct RprReadContext { + pub(crate) world: *mut RprWorld, + pub(crate) bodies: *const RprRigidBodySet, + pub(crate) colliders: *const RprColliderSet, +} +impl RprReadContext { + pub(crate) fn new( + world: *mut RprWorld, + bodies: &RigidBodySet, + colliders: &ColliderSet, + ) -> Self { + Self { + world, + bodies: (bodies as *const RigidBodySet).cast(), + colliders: (colliders as *const ColliderSet).cast(), + } + } +} diff --git a/c/src/world_queries.rs b/c/src/world_queries.rs new file mode 100644 index 000000000..d8412ce61 --- /dev/null +++ b/c/src/world_queries.rs @@ -0,0 +1,417 @@ +//! World queries with operation-scoped native access. +#![allow(non_snake_case)] +use crate::*; +use rapier::parry::bounding_volume::Aabb; +#[derive(Clone, Copy)] +pub(crate) struct QueryAccess { + pub world: *mut RprWorld, + pub broadPhase: *const RprBroadPhaseBvh, + pub narrowPhase: *const RprNarrowPhase, + pub bodies: *const RprRigidBodySet, + pub colliders: *const RprColliderSet, + pub filter: RprQueryFilter, + pub predicate: RprQueryPredicate, + pub userData: *mut std::ffi::c_void, +} +/// Copyable query settings. They borrow callback data, never world components. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct RprQueryOptions { + pub filter: RprQueryFilter, + pub predicate: RprQueryPredicate, + pub userData: *mut std::ffi::c_void, +} +impl Default for RprQueryOptions { + fn default() -> Self { + Self { + filter: RprQueryFilter::default(), + predicate: None, + userData: std::ptr::null_mut(), + } + } +} +#[rapier_export] +pub extern "C" fn rpr_default_query_options() -> RprQueryOptions { + RprQueryOptions::default() +} +impl QueryAccess { + pub(crate) unsafe fn from_world( + owner: *mut RprWorld, + world: *const RprPhysicsWorld, + query_options: *const RprQueryOptions, + ) -> Result { + unsafe { + let options = if query_options.is_null() { + RprQueryOptions::default() + } else { + *get(query_options)? + }; + let w = &get(world)?.0; + options.filter.check_world(owner)?; + options.filter.raw()?; + Ok(Self { + world: owner, + broadPhase: std::ptr::addr_of!(w.broad_phase).cast(), + narrowPhase: std::ptr::addr_of!(w.narrow_phase).cast(), + bodies: std::ptr::addr_of!(w.bodies).cast(), + colliders: std::ptr::addr_of!(w.colliders).cast(), + filter: options.filter, + predicate: options.predicate, + userData: options.userData, + }) + } + } + + pub(crate) unsafe fn with_raw( + &self, + f: impl FnOnce(QueryPipeline<'_>) -> Result, + ) -> Result { + unsafe { + let read = + RprReadContext::new(self.world, &get(self.bodies)?.0, &get(self.colliders)?.0); + let callback = |handle: ColliderHandle, _collider: &Collider| { + self.predicate.is_none_or(|p| { + p( + self.userData, + &read, + RprColliderHandle::from(handle).with_world(self.world), + ) != 0 + }) + }; + let mut filter = self.filter.raw()?; + if self.predicate.is_some() { + filter.predicate = Some(&callback); + } + let query = get(self.broadPhase)?.0.as_query_pipeline( + get(self.narrowPhase)?.0.query_dispatcher(), + &get(self.bodies)?.0, + &get(self.colliders)?.0, + filter, + ); + f(query) + } + } +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_cast_ray( + world: *const RprWorld, + query_options: *const RprQueryOptions, + origin: RprVector, + direction: RprVector, + max_toi: RprReal, + solid: RprBool, +) -> RprRayHit { + ffi_world_value(world, |out: *mut RprRayHit| { + ffi(|| unsafe { + if !query_options.is_null() { + get(query_options)?.check_world(world)?; + } + let access = get(world)?.read()?; + let raw = access.raw(); + let query = QueryAccess::from_world(world as *mut RprWorld, raw, query_options)?; + let query: *const QueryAccess = &query; + + let origin = origin.raw()?; + let direction = direction.raw()?; + positive(direction.length())?; + nonnegative(max_toi)?; + let solid = boolean(solid)?; + get(query)?.with_raw(|q| { + let (h, hit) = q + .cast_ray_and_get_normal(&Ray::new(origin, direction), max_toi, solid) + .ok_or((RPR_NOT_FOUND, "ray missed".into()))?; + let (feature_type, feature_id) = feature(hit.feature); + output( + out, + RprRayHit { + collider: h.into(), + time_of_impact: hit.time_of_impact, + normal: hit.normal.into(), + feature_type, + feature_id, + }, + ) + }) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_project_point( + world: *const RprWorld, + query_options: *const RprQueryOptions, + point: RprVector, + max_distance: RprReal, + solid: RprBool, +) -> RprPointProjection { + ffi_world_value(world, |out: *mut RprPointProjection| { + ffi(|| unsafe { + if !query_options.is_null() { + get(query_options)?.check_world(world)?; + } + let access = get(world)?.read()?; + let raw = access.raw(); + let query = QueryAccess::from_world(world as *mut RprWorld, raw, query_options)?; + let query: *const QueryAccess = &query; + + let p = point.raw()?; + nonnegative(max_distance)?; + let solid = boolean(solid)?; + get(query)?.with_raw(|q| { + let (h, p) = q + .project_point(p, max_distance, solid) + .ok_or((RPR_NOT_FOUND, "no projection".into()))?; + output( + out, + RprPointProjection { + collider: h.into(), + point: p.point.into(), + is_inside: p.is_inside as u32, + }, + ) + }) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_cast_shape( + world: *const RprWorld, + query_options: *const RprQueryOptions, + pose: RprPose, + velocity: RprVector, + shape: *const RprSharedShape, + options: RprShapeCastOptions, +) -> RprShapeCastHit { + ffi_world_value(world, |out: *mut RprShapeCastHit| { + ffi(|| unsafe { + if !query_options.is_null() { + get(query_options)?.check_world(world)?; + } + let access = get(world)?.read()?; + let raw = access.raw(); + let query = QueryAccess::from_world(world as *mut RprWorld, raw, query_options)?; + let query: *const QueryAccess = &query; + + let p = pose.raw()?; + let v = velocity.raw()?; + let o = options.raw()?; + get(query)?.with_raw(|q| { + let (h, r) = q + .cast_shape(&p, v, &*get(shape)?.0, o) + .ok_or((RPR_NOT_FOUND, "shape missed".into()))?; + output( + out, + RprShapeCastHit { + collider: h.into(), + time_of_impact: r.time_of_impact, + witness1: r.witness1.into(), + witness2: r.witness2.into(), + normal1: r.normal1.into(), + normal2: r.normal2.into(), + status: r.status as u32, + }, + ) + }) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_intersect_point( + world: *const RprWorld, + query_options: *const RprQueryOptions, + point: RprVector, + buffer: *mut RprColliderHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + if !query_options.is_null() { + get(query_options)?.check_world(world)?; + } + let access = get(world)?.read()?; + let raw = access.raw(); + let query = QueryAccess::from_world(world as *mut RprWorld, raw, query_options)?; + let query: *const QueryAccess = &query; + + let point = point.raw()?; + get(query)?.with_raw(|q| { + let values: Vec<_> = q.intersect_point(point).map(|(h, _)| h.into()).collect(); + copy_out(&values, buffer, capacity, count) + }) + }) + }) + } +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_intersect_shape( + world: *const RprWorld, + query_options: *const RprQueryOptions, + pose: RprPose, + shape: *const RprSharedShape, + buffer: *mut RprColliderHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + if !query_options.is_null() { + get(query_options)?.check_world(world)?; + } + let access = get(world)?.read()?; + let raw = access.raw(); + let query = QueryAccess::from_world(world as *mut RprWorld, raw, query_options)?; + let query: *const QueryAccess = &query; + + let pose = pose.raw()?; + get(query)?.with_raw(|q| { + let values: Vec<_> = q + .intersect_shape(pose, &*get(shape)?.0) + .map(|(h, _)| h.into()) + .collect(); + copy_out(&values, buffer, capacity, count) + }) + }) + }) + } +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_intersect_aabb_conservative( + world: *const RprWorld, + query_options: *const RprQueryOptions, + aabb: RprAabb, + buffer: *mut RprColliderHandle, + capacity: usize, +) -> usize { + unsafe { + ffi_world_array(world, buffer, capacity, |count: *mut usize| { + ffi(|| { + if !query_options.is_null() { + get(query_options)?.check_world(world)?; + } + let access = get(world)?.read()?; + let raw = access.raw(); + let query = QueryAccess::from_world(world as *mut RprWorld, raw, query_options)?; + let query: *const QueryAccess = &query; + + let mins = aabb.mins.raw()?; + let maxs = aabb.maxs.raw()?; + ensure(mins.cmple(maxs).all(), "invalid AABB")?; + get(query)?.with_raw(|q| { + let values: Vec<_> = q + .intersect_aabb_conservative(Aabb::new(mins, maxs)) + .map(|(h, _)| h.into()) + .collect(); + copy_out(&values, buffer, capacity, count) + }) + }) + }) + } +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_cast_ray_toi( + world: *const RprWorld, + query_options: *const RprQueryOptions, + origin: RprVector, + direction: RprVector, + max_toi: RprReal, + solid: RprBool, +) -> RprRayToi { + ffi_world_value(world, |result: *mut RprRayToi| { + let collider = unsafe { std::ptr::addr_of_mut!((*result).collider) }; + let toi = unsafe { std::ptr::addr_of_mut!((*result).toi) }; + let found = unsafe { std::ptr::addr_of_mut!((*result).found) }; + + ffi(|| unsafe { + if !query_options.is_null() { + get(query_options)?.check_world(world)?; + } + let access = get(world)?.read()?; + let raw = access.raw(); + let query = QueryAccess::from_world(world as *mut RprWorld, raw, query_options)?; + let query: *const QueryAccess = &query; + + out_ptr(collider)?; + out_ptr(toi)?; + out_ptr(found)?; + let origin = origin.raw()?; + let direction = direction.raw()?; + positive(direction.length())?; + nonnegative(max_toi)?; + get(query)?.with_raw(|q| { + if let Some((handle, time)) = + q.cast_ray(&Ray::new(origin, direction), max_toi, boolean(solid)?) + { + output(collider, handle.into())?; + output(toi, time)?; + output(found, 1) + } else { + output(collider, Default::default())?; + output(toi, 0.0)?; + output(found, 0) + } + }) + }) + }) +} + +#[rapier_export] +pub unsafe extern "C" fn rpr_try_cast_ray( + world: *const RprWorld, + query_options: *const RprQueryOptions, + origin: RprVector, + direction: RprVector, + max_toi: RprReal, + solid: RprBool, +) -> RprOptionalRayHit { + ffi_world_value(world, |result: *mut RprOptionalRayHit| { + let out = unsafe { std::ptr::addr_of_mut!((*result).hit) }; + let found = unsafe { std::ptr::addr_of_mut!((*result).found) }; + + ffi(|| unsafe { + if !query_options.is_null() { + get(query_options)?.check_world(world)?; + } + let access = get(world)?.read()?; + let raw = access.raw(); + let query = QueryAccess::from_world(world as *mut RprWorld, raw, query_options)?; + let query: *const QueryAccess = &query; + + out_ptr(out)?; + out_ptr(found)?; + let origin = origin.raw()?; + let direction = direction.raw()?; + positive(direction.length())?; + nonnegative(max_toi)?; + get(query)?.with_raw(|q| { + if let Some((h, hit)) = q.cast_ray_and_get_normal( + &Ray::new(origin, direction), + max_toi, + boolean(solid)?, + ) { + let (feature_type, feature_id) = feature(hit.feature); + output( + out, + RprRayHit { + collider: h.into(), + time_of_impact: hit.time_of_impact, + normal: hit.normal.into(), + feature_type, + feature_id, + }, + )?; + output(found, 1) + } else { + output(out, RprRayHit::default())?; + output(found, 0) + } + }) + }) + }) +} diff --git a/c/src/world_tests.rs b/c/src/world_tests.rs new file mode 100644 index 000000000..e47851474 --- /dev/null +++ b/c/src/world_tests.rs @@ -0,0 +1,223 @@ +use crate::*; +use std::{ + ffi::c_void, + ptr, + sync::atomic::{AtomicUsize, Ordering}, +}; + +struct CallbackState { + world: *mut RprWorld, + calls: AtomicUsize, +} +unsafe fn check_hook_access( + data: *mut c_void, + read: *const RprReadContext, + collider: RprColliderHandle, +) { + unsafe { + let state = &*data.cast::(); + let _position = rpr_read_collider_translation(read, collider); + assert_eq!(rpr_last_status(), RPR_OK); + let _count = rpr_collider_count(state.world); + assert_eq!(rpr_last_status(), RPR_WORLD_BUSY); + assert_eq!( + rpr_set_gravity(state.world, Vector::ZERO.into()), + RPR_WORLD_BUSY + ); + assert_eq!( + rpr_step(state.world, ptr::null(), ptr::null()), + RPR_WORLD_BUSY + ); + assert_eq!(rpr_free_world(state.world), RPR_WORLD_BUSY); + state.calls.fetch_add(1, Ordering::Relaxed); + } +} +unsafe extern "C" fn filter_pair( + data: *mut c_void, + read: *const RprReadContext, + a: RprColliderHandle, + _b: RprColliderHandle, + _ba: RprRigidBodyHandle, + _bb: RprRigidBodyHandle, +) -> i32 { + unsafe { check_hook_access(data, read, a) }; + 1 +} +unsafe extern "C" fn modify_contacts( + data: *mut c_void, + read: *const RprReadContext, + a: RprColliderHandle, + _b: RprColliderHandle, + context: *mut RprContactModificationContext, +) { + unsafe { + check_hook_access(data, read, a); + assert_eq!( + rpr_contact_modification_context_set_tangent_velocity(context, Vector::ZERO.into()), + RPR_OK + ); + } +} + +#[test] +fn hooks_read_scoped_state_and_reject_world_reentry() { + unsafe { + let world = rpr_new_world(); + assert_eq!(rpr_last_status(), RPR_OK); + let mut collider = rpr_ball_collider_desc(1.0); + collider.activeHooks = 1 | 4; + let fixed = rpr_fixed_rigid_body_desc(); + let mut dynamic = rpr_dynamic_rigid_body_desc(); + dynamic.position.translation = Vector::Y.into(); + let fixed_handle = rpr_insert_rigid_body(world, &fixed); + assert_eq!(rpr_last_status(), RPR_OK); + rpr_insert_collider(fixed_handle, &collider); + assert_eq!(rpr_last_status(), RPR_OK); + let dynamic_handle = rpr_insert_rigid_body(world, &dynamic); + assert_eq!(rpr_last_status(), RPR_OK); + rpr_insert_collider(dynamic_handle, &collider); + assert_eq!(rpr_last_status(), RPR_OK); + let mut state = CallbackState { + world, + calls: AtomicUsize::new(0), + }; + let hooks = RprPhysicsHooks { + user_data: (&mut state as *mut CallbackState).cast(), + filter_contact_pair: Some(filter_pair), + modify_solver_contacts_context: Some(modify_contacts), + ..Default::default() + }; + assert_eq!(rpr_step(world, &hooks, ptr::null()), RPR_OK); + assert!(state.calls.load(Ordering::Relaxed) >= 2); + // The step released its exclusive access, so the deferred change now succeeds. + assert_eq!(rpr_set_gravity(world, Vector::ZERO.into()), RPR_OK); + assert_eq!(rpr_free_world(world), RPR_OK); + } +} + +unsafe extern "C" fn query_predicate( + data: *mut c_void, + read: *const RprReadContext, + collider: RprColliderHandle, +) -> RprBool { + unsafe { + let state = &*data.cast::(); + let _position = rpr_read_collider_translation(read, collider); + assert_eq!(rpr_last_status(), RPR_OK); + let count = rpr_collider_count(state.world); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!(count, 1); + assert_eq!( + rpr_set_gravity(state.world, Vector::ZERO.into()), + RPR_WORLD_BUSY + ); + assert_eq!(rpr_free_world(state.world), RPR_WORLD_BUSY); + + // Read-only queries can reenter, provided the nested query doesn't recurse indefinitely. + let hit = rpr_cast_ray( + state.world, + ptr::null(), + Vector::ZERO.into(), + Vector::X.into(), + 10.0, + 1, + ); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!(hit.collider, collider); + state.calls.fetch_add(1, Ordering::Relaxed); + 1 + } +} + +#[test] +fn query_options_are_owner_independent_and_nested_reads_are_allowed() { + unsafe { + let world = rpr_new_world(); + assert_eq!(rpr_last_status(), RPR_OK); + let mut desc = rpr_ball_collider_desc(0.5); + desc.position.translation = (Vector::X * 2.0).into(); + rpr_insert_collider_without_parent(world, &desc); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!( + rpr_detect_collisions(world, ptr::null(), ptr::null()), + RPR_OK + ); + let mut state = CallbackState { + world, + calls: AtomicUsize::new(0), + }; + let mut options = rpr_default_query_options(); + options.predicate = Some(query_predicate); + options.userData = (&mut state as *mut CallbackState).cast(); + let _hit = rpr_cast_ray( + world, + &options, + Vector::ZERO.into(), + Vector::X.into(), + 10.0, + 1, + ); + assert_eq!(rpr_last_status(), RPR_OK); + assert!(state.calls.load(Ordering::Relaxed) > 0); + assert_eq!(rpr_set_gravity(world, Vector::ZERO.into()), RPR_OK); + assert_eq!(rpr_free_world(world), RPR_OK); + } +} + +#[test] +fn world_gate_checks_conflicts_across_threads_and_releases_after_errors() { + fn thread_safe() {} + thread_safe::(); + let world = RprWorld::new(RprPhysicsWorld(PhysicsWorld::new())); + let read = world.read().unwrap(); + std::thread::scope(|scope| { + scope + .spawn(|| { + let count = unsafe { rpr_rigid_body_count(&world) }; + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!(count, 0); + assert!(world.write().is_err()); + }) + .join() + .unwrap(); + }); + drop(read); + let write = world.write().unwrap(); + assert!(world.read().is_err()); + assert!(world.write().is_err()); + drop(write); + assert_eq!( + ffi(|| { + let _write = world.write()?; + panic!("release the guard"); + }), + RPR_PANIC + ); + assert!(world.write().is_ok()); +} + +#[test] +fn removing_a_body_can_preserve_its_colliders() { + unsafe { + let world = rpr_new_world(); + assert_eq!(rpr_last_status(), RPR_OK); + + let desc = rpr_dynamic_rigid_body_desc(); + let shape = rpr_ball_collider_desc(0.5); + let body = rpr_insert_rigid_body(world, &desc); + assert_eq!(rpr_last_status(), RPR_OK); + let collider = rpr_insert_collider(body, &shape); + assert_eq!(rpr_last_status(), RPR_OK); + let mut removed = rpr_remove_rigid_body(body, 0); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!(removed, 1); + let parent = rpr_collider_parent(collider); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!(parent, RprRigidBodyHandle::default()); + assert_eq!(rpr_rigid_body_validate_handle(body), RPR_INVALID_HANDLE); + removed = rpr_remove_rigid_body(body, 1); + assert_eq!(rpr_last_status(), RPR_OK); + assert_eq!(removed, 0); + assert_eq!(rpr_free_world(world), RPR_OK); + } +} diff --git a/c/tests/array_views.c b/c/tests/array_views.c new file mode 100644 index 000000000..3fbb4a803 --- /dev/null +++ b/c/tests/array_views.c @@ -0,0 +1,170 @@ +#include "rapier_helpers.h" +#include +#include +#include +#include +#include + +#define OK(call) \ + do { \ + RAPIER_TYPE(Status) status = (call); \ + if (status != RAPIER_CONST(OK)) { \ + fprintf(stderr, "%s: %s\n", #call, RAPIER_FN(LastError)()); \ + abort(); \ + } \ + } while (0) + +/* Aggregate initialization is shared by C11 and C++17. */ +static void test_meshes(RAPIER_TYPE(World) *world) { + RAPIER_TYPE(Vector) points[4]; + memset(points, 0, sizeof(points)); + points[1].x = 1; + points[2].y = 1; + points[3].x = points[3].y = 1; + RAPIER_TYPE(Triangle) triangles[] = {{0, 1, 2}, {1, 3, 2}}; + RAPIER_TYPE(Edge) edges[] = {{0, 1}, {1, 2}, {2, 3}}; + RAPIER_TYPE(VectorView) vertices = {points, 4}; + RAPIER_TYPE(TriangleView) faces = {triangles, 2}; + RAPIER_TYPE(EdgeView) segments = {edges, 3}; + RAPIER_TYPE(RigidBodyDesc) body = RAPIER_FN(FixedRigidBodyDesc)(); + RAPIER_TYPE(ColliderDesc) collider = RAPIER_FN(DefaultColliderDesc)(); + OK(RAPIER_FN(ShapeDesc_SetTrimesh)(&collider.shape, vertices, faces, 0)); + assert(collider.shape.triangles.count == 2 && collider.shape.vertices.count == 4); + assert(collider.shape.vertices.data == points); /* The setter borrows; insertion copies. */ + RAPIER_TYPE(RigidBodyHandle) bodyHandle = RAPIER_FN(InsertRigidBody)(world, &body); + OK(RAPIER_FN(LastStatus)()); + RAPIER_FN(InsertCollider)(bodyHandle, &collider); + OK(RAPIER_FN(LastStatus)()); + OK(RAPIER_FN(ShapeDesc_SetPolyline)(&collider.shape, vertices, segments, 0)); + assert(collider.shape.edges.count == 3 && + collider.shape.kind == RAPIER_CONST(SHAPE_DESC_POLYLINE)); + bodyHandle = RAPIER_FN(InsertRigidBody)(world, &body); + OK(RAPIER_FN(LastStatus)()); + RAPIER_FN(InsertCollider)(bodyHandle, &collider); + OK(RAPIER_FN(LastStatus)()); +#ifdef RAPIER_DIM3 + points[3].z = 1; +#endif + OK(RAPIER_FN(ShapeDesc_SetConvexHull)(&collider.shape, vertices)); + assert(collider.shape.triangles.count == 0 && collider.shape.triangles.data == NULL); + bodyHandle = RAPIER_FN(InsertRigidBody)(world, &body); + OK(RAPIER_FN(LastStatus)()); + RAPIER_FN(InsertCollider)(bodyHandle, &collider); + OK(RAPIER_FN(LastStatus)()); + + RAPIER_TYPE(ShapeDesc) original; + memcpy(&original, &collider.shape, sizeof(original)); + RAPIER_TYPE(TriangleView) null_faces = {NULL, 2}; + assert(RAPIER_FN(ShapeDesc_SetTrimesh)(&collider.shape, vertices, null_faces, 0) == + RAPIER_CONST(NULL_POINTER)); + assert(memcmp(&original, &collider.shape, sizeof(original)) == 0); + RAPIER_TYPE(TriangleView) huge_faces = {triangles, SIZE_MAX}; + assert(RAPIER_FN(ShapeDesc_SetTrimesh)(&collider.shape, vertices, huge_faces, 0) == + RAPIER_CONST(INVALID_ARGUMENT)); + assert(memcmp(&original, &collider.shape, sizeof(original)) == 0); + RAPIER_TYPE(TriangleView) + misaligned = {(const RAPIER_TYPE(Triangle) *)((const char *)triangles + 1), 1}; + assert(RAPIER_FN(ShapeDesc_SetTrimesh)(&collider.shape, vertices, misaligned, 0) == + RAPIER_CONST(INVALID_ARGUMENT)); + assert(memcmp(&original, &collider.shape, sizeof(original)) == 0); + assert(RAPIER_FN(ShapeDesc_SetTrimesh)(NULL, vertices, faces, 0) == RAPIER_CONST(NULL_POINTER)); + + /* Values and index bounds are checked when building, not when assigning views. */ + triangles[1].c = 100; + OK(RAPIER_FN(ShapeDesc_SetTrimesh)(&collider.shape, vertices, faces, 0)); + bodyHandle = RAPIER_FN(InsertRigidBody)(world, &body); + OK(RAPIER_FN(LastStatus)()); + RAPIER_FN(InsertCollider)(bodyHandle, &collider); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(INVALID_ARGUMENT)); + RAPIER_TYPE(TriangleView) empty = {NULL, 0}; + OK(RAPIER_FN(ShapeDesc_SetTrimesh)(&collider.shape, vertices, empty, 0)); + assert(collider.shape.triangles.count == 0); +} + +static RAPIER_TYPE(SoftBodyHandle) test_soft(RAPIER_TYPE(World) *world) { + RAPIER_TYPE(Vector) points[4]; + memset(points, 0, sizeof(points)); + points[0].y = 4; + points[1].x = 1; + points[1].y = 4; + points[2].y = 5; + points[3].x = 1; + points[3].y = 5; + RAPIER_TYPE(VectorView) vertices = {points, 4}; + RAPIER_TYPE(Edge) edges[] = {{0, 1}, {1, 2}, {2, 3}}; + RAPIER_TYPE(EdgeView) segments = {edges, 3}; + RAPIER_TYPE(Real) masses[] = {1, 2, 3, 4}; + RAPIER_TYPE(RealView) mass_view = {masses, 4}; + uint32_t pins[] = {0}; + RAPIER_TYPE(IndexView) pinned = {pins, 1}; + RAPIER_TYPE(SoftBodyDesc) soft = RAPIER_FN(DefaultSoftBodyDesc)(); + soft.gravityScale = 0.5; + OK(RAPIER_FN(SoftBodyDesc_SetParticles)(&soft, vertices)); + OK(RAPIER_FN(SoftBodyDesc_SetEdges)(&soft, segments)); + OK(RAPIER_FN(SoftBodyDesc_SetMasses)(&soft, mass_view)); + OK(RAPIER_FN(SoftBodyDesc_SetPinnedParticles)(&soft, pinned)); + assert(soft.gravityScale == 0.5 && soft.edges.count == 3); + RAPIER_TYPE(EdgeView) empty_edges = {NULL, 0}; + OK(RAPIER_FN(SoftBodyDesc_SetBendEdges)(&soft, empty_edges)); + RAPIER_TYPE(IndexView) empty_indices = {NULL, 0}; + OK(RAPIER_FN(SoftBodyDesc_SetTensionOnlyEdges)(&soft, empty_indices)); +#ifdef RAPIER_DIM3 + points[3].x = 0; + points[3].y = 4; + points[3].z = 1; + RAPIER_TYPE(Tetrahedron) cells[] = {{0, 1, 2, 3}}; + RAPIER_TYPE(Triangle) surface[] = {{0, 2, 1}, {0, 1, 3}, {0, 3, 2}, {1, 2, 3}}; + RAPIER_TYPE(SurfaceElementView) boundary = {surface, 4}; + RAPIER_TYPE(DihedralView) empty_dihedrals = {NULL, 0}; + OK(RAPIER_FN(SoftBodyDesc_SetDihedrals)(&soft, empty_dihedrals)); + OK(RAPIER_FN(SoftBodyDesc_SetWire)(&soft, empty_edges)); +#else + RAPIER_TYPE(Triangle) cells[] = {{0, 1, 2}, {1, 3, 2}}; + RAPIER_TYPE(Edge) surface[] = {{0, 1}, {1, 3}, {3, 2}, {2, 0}}; + RAPIER_TYPE(SurfaceElementView) boundary = {surface, 4}; +#endif + RAPIER_TYPE(CellView) volume = {cells, sizeof(cells) / sizeof(cells[0])}; + OK(RAPIER_FN(SoftBodyDesc_SetCells)(&soft, volume)); + OK(RAPIER_FN(SoftBodyDesc_SetSurface)(&soft, boundary)); + OK(RAPIER_FN(SoftBodyDesc_SetSkin)(&soft, vertices, boundary)); + assert(soft.cells.count == volume.count && soft.surface.count == 4 && + soft.skinIndices.count == 4); + RAPIER_TYPE(SoftBodyHandle) handle = RAPIER_FN(InsertSoftBody)(world, &soft); + OK(RAPIER_FN(LastStatus)()); + + RAPIER_TYPE(SoftBodyDesc) surface_body = RAPIER_FN(DefaultSoftBodyDesc)(); + OK(RAPIER_FN(SoftBodyDesc_SetSurfaceMesh)(&surface_body, vertices, boundary)); + assert(surface_body.kind == RAPIER_CONST(SOFT_DESC_SURFACE) && surface_body.surface.count == 4); + RAPIER_FN(InsertSoftBody)(world, &surface_body); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(SoftBodyDesc) original; + memcpy(&original, &soft, sizeof(original)); + RAPIER_TYPE(RealView) bad_masses = {NULL, 4}; + assert(RAPIER_FN(SoftBodyDesc_SetMasses)(&soft, bad_masses) == RAPIER_CONST(NULL_POINTER)); + assert(memcmp(&soft, &original, sizeof(soft)) == 0); + RAPIER_TYPE(VectorView) bad_vertices = {NULL, 4}; + assert(RAPIER_FN(SoftBodyDesc_SetSurfaceMesh)(&soft, bad_vertices, boundary) == + RAPIER_CONST(NULL_POINTER)); + assert(memcmp(&soft, &original, sizeof(soft)) == 0); + + /* Neither descriptor copies nor views own memory. Native storage is independent. */ + points[0].y = 999; + RAPIER_TYPE(Vector) position = RAPIER_FN(SoftBody_ParticlePosition)(handle, 0); + OK(RAPIER_FN(LastStatus)()); + assert(position.y == 4); + return handle; +} + +int main(void) { + RAPIER_TYPE(World) *world = RAPIER_FN(NewWorld)(); + OK(RAPIER_FN(LastStatus)()); + test_meshes(world); + RAPIER_TYPE(SoftBodyHandle) soft = test_soft(world); + /* All input arrays have left scope by the time physics reads its copies. */ + OK(RAPIER_FN(Step)(world, NULL, NULL)); + RAPIER_TYPE(Vector) position = RAPIER_FN(SoftBody_ParticlePosition)(soft, 0); + OK(RAPIER_FN(LastStatus)()); + assert(isfinite(position.y)); + OK(RAPIER_FN(FreeWorld)(world)); + return 0; +} diff --git a/c/tests/array_views.cpp b/c/tests/array_views.cpp new file mode 100644 index 000000000..c1e4a58c3 --- /dev/null +++ b/c/tests/array_views.cpp @@ -0,0 +1 @@ +#include "array_views.c" diff --git a/c/tests/consumer/CMakeLists.txt b/c/tests/consumer/CMakeLists.txt new file mode 100644 index 000000000..0f590f5d1 --- /dev/null +++ b/c/tests/consumer/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.20) +project(RapierInstalledConsumer LANGUAGES CXX) +find_package(Rapier CONFIG REQUIRED) +add_executable(consumer main.cpp) +target_link_libraries(consumer PRIVATE Rapier::rapier) +target_compile_features(consumer PRIVATE cxx_std_17) +get_target_property(rapier_kind Rapier::rapier TYPE) +if(WIN32 AND rapier_kind STREQUAL "SHARED_LIBRARY") + add_custom_command(TARGET consumer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "$" VERBATIM) +endif() +enable_testing() +add_test(NAME installed_consumer COMMAND consumer) diff --git a/c/tests/consumer/main.cpp b/c/tests/consumer/main.cpp new file mode 100644 index 000000000..620e0a534 --- /dev/null +++ b/c/tests/consumer/main.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + auto world = rapier::make_world(); + rapier::check(RAPIER_FN(Step)(world.get(), nullptr, nullptr)); +} diff --git a/c/tests/cpp.cpp b/c/tests/cpp.cpp new file mode 100644 index 000000000..44f0af413 --- /dev/null +++ b/c/tests/cpp.cpp @@ -0,0 +1,73 @@ +#include "rapier.hpp" +#include +#include + +int main() { + static_assert(std::is_standard_layout::value, "POD pose"); + static_assert(sizeof(RAPIER_TYPE(RigidBodyHandle)) == sizeof(void *) + 2 * sizeof(uint32_t), "handle ABI"); + static_assert(std::is_trivially_copyable::value, "POD body"); + static_assert(std::is_standard_layout::value, "POD collider"); + static_assert(std::is_trivially_copyable::value, "POD soft recipe"); + static_assert(std::is_trivially_copyable::value, "POD joint"); + auto world = rapier::make_world(); + auto body = rapier::rigid_body(); + auto collider = rapier::ball(0.5); + body.position.translation.y = 5; + auto bodyHandle = RAPIER_FN(InsertRigidBody)(world.get(), &body); + rapier::check(RAPIER_FN(LastStatus)()); + auto colliderHandle = RAPIER_FN(InsertCollider)(bodyHandle, &collider); + rapier::check(RAPIER_FN(LastStatus)()); + auto query = rapier::queryOptions(); + assert(!query.predicate && !query.userData); + auto moved = std::move(world); + assert(!world && moved); + rapier::check(RAPIER_FN(Step)(moved.get(), nullptr, nullptr)); + { + rapier::Shape shape(RAPIER_FN(Collider_CloneShape)(colliderHandle)); + rapier::check(RAPIER_FN(LastStatus)()); + // The owned clone must remain usable after its source collider is removed. + rapier::check(RAPIER_FN(RemoveCollider)(colliderHandle, 1)); + rapier::ShapeMesh mesh(RAPIER_FN(SharedShape_Tessellate)(shape.get(), 8)); + rapier::EventCollector events(RAPIER_FN(NewEventCollector)()); + rapier::KinematicCharacterController character( + RAPIER_FN(NewKinematicCharacterController)()); + rapier::PidController pid(RAPIER_FN(NewPidController)()); + rapier::Snapshot snapshot(RAPIER_FN(SerializeWorld)(moved.get())); + rapier::SoftBodyTearEvent tear; + assert(shape && mesh && events && character && pid && snapshot); + auto transferred = std::move(mesh); + assert(transferred && !mesh); +#if defined(RAPIER_DIM3) + rapier::TriMeshData triangles(RAPIER_FN(SharedShape_ToTrimesh)(shape.get(), 8, 8)); + assert(triangles); + auto chassisDesc = RAPIER_FN(DynamicRigidBodyDesc)(); + auto chassis = RAPIER_FN(InsertRigidBody)(moved.get(), &chassisDesc); + rapier::DynamicRayCastVehicleController vehicle( + RAPIER_FN(NewDynamicRayCastVehicleController)(chassis)); + assert(vehicle); +#endif +#if defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32) + static_assert(std::is_trivially_copyable::value, "POD URDF options"); + static_assert(std::is_trivially_copyable::value, "POD MJCF options"); + const auto layout = r3PodLayout(); + assert(layout.urdfLoaderOptions == sizeof(R3UrdfLoaderOptions)); + assert(layout.mjcfLoaderOptions == sizeof(R3MjcfLoaderOptions)); + const auto urdf = r3DefaultUrdfLoaderOptions(); + const auto mjcf = r3DefaultMjcfLoaderOptions(); + assert(urdf.rigidBodyBlueprint.enabled && mjcf.rigidBodyBlueprint.enabled); + assert(urdf.colliderBlueprint.density == 0 && mjcf.colliderBlueprint.density == 0); + rapier::UrdfRobot urdfRobot; + rapier::UrdfRobotHandles urdfHandles; + rapier::MjcfRobot mjcfRobot; + rapier::MjcfRobotHandles mjcfHandles; +#endif + } + bool caught = false; + try { + RAPIER_FN(BallSharedShape)(-1); + rapier::check(RAPIER_FN(LastStatus)()); + } catch (const std::runtime_error &) { + caught = true; + } + assert(caught); +} diff --git a/c/tests/handles.c b/c/tests/handles.c new file mode 100644 index 000000000..73cc0ae00 --- /dev/null +++ b/c/tests/handles.c @@ -0,0 +1,125 @@ +#include "rapier.h" +#include +#include +#include +#include + +#define OK(call) \ + do { \ + RAPIER_TYPE(Status) status = (call); \ + if (status != RAPIER_CONST(OK)) { \ + fprintf(stderr, "%s: %s\n", #call, RAPIER_FN(LastError)()); \ + abort(); \ + } \ + } while (0) + +static void RAPIER_CALL on_error(RAPIER_TYPE(Status) status, const char *message, void *data) { + assert(status != RAPIER_CONST(OK) && message); + assert(RAPIER_FN(LastStatus)() == status); + ++*(unsigned *)data; +} + +int main(void) { + RAPIER_TYPE(World) *world = RAPIER_FN(NewWorld)(); + OK(RAPIER_FN(LastStatus)()); + + RAPIER_TYPE(RigidBodyDesc) desc = RAPIER_FN(DynamicRigidBodyDesc)(); + RAPIER_TYPE(ColliderDesc) collider = RAPIER_FN(BallColliderDesc)(0.5); + desc.canSleep = 0; + RAPIER_TYPE(RigidBodyHandle) body; + RAPIER_TYPE(ColliderHandle) shape; + body = RAPIER_FN(InsertRigidBody)(world, &desc); + OK(RAPIER_FN(LastStatus)()); + shape = RAPIER_FN(InsertCollider)(body, &collider); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(Vector) position = {0}; + position.y = 10; + OK(RAPIER_FN(RigidBody_SetTranslation)(body, position, 1)); + OK(RAPIER_FN(Collider_SetSensor)(shape, 1)); + RAPIER_TYPE(Bool) sensor = RAPIER_FN(Collider_IsSensor)(shape); + OK(RAPIER_FN(LastStatus)()); + assert(sensor); + OK(RAPIER_FN(Step)(world, NULL, NULL)); + position = RAPIER_FN(RigidBody_Translation)(body); + OK(RAPIER_FN(LastStatus)()); + assert(position.y < 10); + + RAPIER_TYPE(Vector) throughSet = RAPIER_FN(RigidBody_Translation)(body); + OK(RAPIER_FN(LastStatus)()); + assert(throughSet.y == position.y); + /* Retained handles survive storage growth and stepping; no element pointer is kept. */ + for (int i = 0; i < 512; ++i) { + desc.position.translation.x = 10 + i; + RAPIER_FN(InsertRigidBody)(world, &desc); + OK(RAPIER_FN(LastStatus)()); + } + RAPIER_TYPE(RigidBodyHandle) order[] = {body, body}; + RAPIER_TYPE(RigidBodyState) states[2]; + size_t count = 99; + count = RAPIER_FN(RigidBodyReadStates)(world, order, 2, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count == 2); + states[0].position.translation.x = 123; + count = RAPIER_FN(RigidBodyReadStates)(world, order, 2, states, 1); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(BUFFER_TOO_SMALL)); + assert(count == 2 && states[0].position.translation.x == 123); + count = RAPIER_FN(RigidBodyReadStates)(world, order, 2, states, 2); + OK(RAPIER_FN(LastStatus)()); + assert(states[0].position.translation.y == position.y && + states[1].position.translation.y == position.y); + RAPIER_TYPE(Vector) invalid = position; + invalid.y = (RAPIER_TYPE(Real))NAN; + assert(RAPIER_FN(RigidBody_SetTranslation)(body, invalid, 1) == + RAPIER_CONST(INVALID_ARGUMENT)); + throughSet = RAPIER_FN(RigidBody_Translation)(body); + OK(RAPIER_FN(LastStatus)()); + assert(throughSet.y == position.y); + + RAPIER_TYPE(SoftBodyDesc) soft = RAPIER_FN(DefaultSoftBodyDesc)(); + soft.kind = RAPIER_CONST(SOFT_DESC_ROPE); + soft.nx = 3; + RAPIER_TYPE(SoftBodyHandle) softHandle = RAPIER_FN(InsertSoftBody)(world, &soft); + OK(RAPIER_FN(LastStatus)()); + position = RAPIER_FN(SoftBody_ParticlePosition)(softHandle, 0); + OK(RAPIER_FN(LastStatus)()); + position.y = 20; + OK(RAPIER_FN(SoftBody_SetParticlePosition)(softHandle, 0, position)); + throughSet = RAPIER_FN(SoftBody_ParticlePosition)(softHandle, 0); + OK(RAPIER_FN(LastStatus)()); + assert(throughSet.y == 20); + + RAPIER_TYPE(RigidBodyHandle) root; + OK(RAPIER_FN(SoftBody_ValidateHandle)(softHandle)); + root = RAPIER_FN(SoftBody_RootBody)(softHandle); + OK(RAPIER_FN(LastStatus)()); + assert(RAPIER_FN(RigidBody_SetTranslation)(root, position, 1) == + RAPIER_CONST(INVALID_ARGUMENT)); + + RAPIER_TYPE(Bool) removed = RAPIER_FN(RemoveRigidBody)(body, 1); + OK(RAPIER_FN(LastStatus)()); + assert(removed); + RAPIER_TYPE(RigidBodyHandle) replacement = RAPIER_FN(InsertRigidBody)(world, &desc); + OK(RAPIER_FN(LastStatus)()); + assert(replacement.index == body.index && replacement.generation != body.generation); + unsigned errors = 0; + RAPIER_TYPE(ErrorHandler) + previous = RAPIER_FN(SetErrorHandler)((RAPIER_TYPE(ErrorHandler)){on_error, &errors}); + position.y = 123; + position = RAPIER_FN(RigidBody_Translation)(body); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(INVALID_HANDLE)); + assert(errors == 1 && position.y == 0); + RAPIER_FN(SetErrorHandler)(previous); + sensor = RAPIER_FN(Collider_IsSensor)(shape); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(INVALID_HANDLE)); + order[0] = replacement; + count = 999; + states[0].position.translation.x = 123; + count = RAPIER_FN(RigidBodyReadStates)(world, order, 2, states, 2); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(INVALID_HANDLE)); + assert(count == 0 && states[0].position.translation.x == 123); + count = RAPIER_FN(RigidBodyReadStates)(world, NULL, 0, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count == 0); + OK(RAPIER_FN(FreeWorld)(world)); + return 0; +} diff --git a/c/tests/initializers.c b/c/tests/initializers.c new file mode 100644 index 000000000..c11e2afdf --- /dev/null +++ b/c/tests/initializers.c @@ -0,0 +1,56 @@ +#include "rapier_helpers.h" +#include + +int main(void) { + RAPIER_TYPE(RigidBodyDesc) + bodies[] = {RAPIER_FN(DynamicRigidBodyDesc)(), RAPIER_FN(FixedRigidBodyDesc)(), + RAPIER_FN(KinematicPositionBasedRigidBodyDesc)(), + RAPIER_FN(KinematicVelocityBasedRigidBodyDesc)()}; + RAPIER_TYPE(World) *world = RAPIER_FN(NewWorld)(); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + RAPIER_TYPE(ColliderDesc) collider = RAPIER_FN(DefaultColliderDesc)(); + RAPIER_TYPE(RigidBodyHandle) handle = RAPIER_CONST(INVALID_RIGID_BODY_HANDLE); + assert(handle.index == UINT32_MAX && handle.generation == UINT32_MAX); + for (unsigned i = 0; i < 4; ++i) { + assert(bodies[i].bodyType == i); + bodies[i].position.translation.x = 2 * i; + bodies[i].position.translation.y = 5; + handle = RAPIER_FN(InsertRigidBody)(world, &bodies[i]); + RAPIER_FN(InsertCollider)(handle, &collider); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + } + RAPIER_TYPE(ShapeDesc) shape = RAPIER_FN(DefaultShapeDesc)(); + assert(shape.kind == RAPIER_CONST(SHAPE_DESC_BALL) && !shape.vertices.data); + RAPIER_TYPE(SoftBodyDesc) soft = RAPIER_FN(DefaultSoftBodyDesc)(); + RAPIER_TYPE(SoftBodyMaterial) material = RAPIER_FN(DefaultSoftBodyMaterial)(); + soft.material = material; + soft.kind = RAPIER_CONST(SOFT_DESC_ROPE); + RAPIER_FN(InsertSoftBody)(world, &soft); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + RAPIER_TYPE(JointDesc) joint = RAPIER_FN(DefaultJointDesc)(); + assert(joint.enabled && !joint.lockedAxes); + RAPIER_TYPE(SoftMeshBindingDesc) binding = RAPIER_FN(DefaultSoftMeshBindingDesc)(); + assert(binding.kind == RAPIER_CONST(SOFT_BINDING_SKINNED) && !binding.particles.data); + RAPIER_TYPE(IntegrationParameters) settings = RAPIER_FN(DefaultIntegrationParameters)(); + settings.softBodies = RAPIER_FN(DefaultSoftBodiesSettings)(); + settings.softBodies.recovery = RAPIER_FN(DefaultSoftRecoverySettings)(); +#ifdef RAPIER_FEM + settings.softBodies.fem = RAPIER_FN(DefaultSoftFemParameters)(); +#endif + + assert(RAPIER_FN(SetIntegrationParameters)(world, &settings) == RAPIER_CONST(OK)); + assert(RAPIER_FN(Step)(world, NULL, NULL) == RAPIER_CONST(OK)); + RAPIER_TYPE(QueryFilter) filter = RAPIER_FN(DefaultQueryFilter)(); + assert(filter.exclude_collider.index == UINT32_MAX && + filter.exclude_rigid_body.index == UINT32_MAX); + RAPIER_TYPE(QueryOptions) query = RAPIER_FN(DefaultQueryOptions)(); + query.filter = *(&filter); + RAPIER_TYPE(ShapeCastOptions) options = RAPIER_FN(DefaultShapeCastOptions)(); + assert(options.max_time_of_impact > 0); + assert(RAPIER_CONST(INVALID_COLLIDER_HANDLE).index == UINT32_MAX); + assert(RAPIER_CONST(INVALID_IMPULSE_JOINT_HANDLE).index == UINT32_MAX); + assert(RAPIER_CONST(INVALID_MULTIBODY_JOINT_HANDLE).index == UINT32_MAX); + assert(RAPIER_CONST(INVALID_SOFT_BODY_HANDLE).index == UINT32_MAX); + assert(RAPIER_FN(FreeWorld)(world) == RAPIER_CONST(OK)); + return 0; +} diff --git a/c/tests/initializers.cpp b/c/tests/initializers.cpp new file mode 100644 index 000000000..d83a4a0cb --- /dev/null +++ b/c/tests/initializers.cpp @@ -0,0 +1,2 @@ +/* The same initializer surface must work as strict C++17 as well as C11. */ +#include "initializers.c" diff --git a/c/tests/integration.c b/c/tests/integration.c new file mode 100644 index 000000000..1c0553549 --- /dev/null +++ b/c/tests/integration.c @@ -0,0 +1,456 @@ +#include "rapier.h" +#include +#include +#include +#include +#include +#define OK(expr) \ + do { \ + RAPIER_TYPE(Status) status_ = (expr); \ + if (status_ != RAPIER_CONST(OK)) { \ + fprintf(stderr, "%s:%d: %s: %s (%u)\n", __FILE__, __LINE__, #expr, \ + RAPIER_FN(LastError)(), status_); \ + abort(); \ + } \ + } while (0) +#define EXPECT(expr, code) \ + do { \ + RAPIER_TYPE(Status) status_ = (expr); \ + if (status_ != (code)) { \ + fprintf(stderr, "%s:%d: expected %u, got %u: %s\n", __FILE__, __LINE__, \ + (unsigned)(code), status_, RAPIER_FN(LastError)()); \ + abort(); \ + } \ + } while (0) + +static RAPIER_TYPE(Vector) vector(RAPIER_TYPE(Real) x, RAPIER_TYPE(Real) y, RAPIER_TYPE(Real) z) { + RAPIER_TYPE(Vector) v; + v.x = x; + v.y = y; +#ifdef RAPIER_DIM3 + v.z = z; +#else + (void)z; +#endif + return v; +} + +static RAPIER_TYPE(Pose) pose(RAPIER_TYPE(Real) x, RAPIER_TYPE(Real) y, RAPIER_TYPE(Real) z) { + RAPIER_TYPE(Pose) p = {0}; + p.translation = vector(x, y, z); +#ifdef RAPIER_DIM3 + p.rotation.w = 1; +#endif + return p; +} + +static RAPIER_TYPE(RigidBodyHandle) add_body(RAPIER_TYPE(World) *world, uint32_t kind, + RAPIER_TYPE(Real) y) { + RAPIER_TYPE(RigidBodyDesc) body = RAPIER_FN(DynamicRigidBodyDesc)(); + body.bodyType = kind; + body.position = pose(0, y, 0); + body.canSleep = 0; + RAPIER_TYPE(RigidBodyHandle) handle = RAPIER_FN(InsertRigidBody)(world, &body); + OK(RAPIER_FN(LastStatus)()); + return handle; +} + +static RAPIER_TYPE(ColliderHandle) add_ball(RAPIER_TYPE(RigidBodyHandle) body) { + RAPIER_TYPE(ColliderDesc) collider = RAPIER_FN(DefaultColliderDesc)(); + collider.activeEvents = RAPIER_CONST(COLLISION_EVENTS) | RAPIER_CONST(CONTACT_FORCE_EVENTS); + collider.activeHooks = 1 | 4; + RAPIER_TYPE(ColliderHandle) handle = RAPIER_FN(InsertCollider)(body, &collider); + OK(RAPIER_FN(LastStatus)()); + return handle; +} + +static int filter_calls = 0, modify_calls = 0; + +static int32_t RAPIER_CALL contact_filter(void *user, const RAPIER_TYPE(ReadContext) *read, + RAPIER_TYPE(ColliderHandle) a, + RAPIER_TYPE(ColliderHandle) b, + RAPIER_TYPE(RigidBodyHandle) x, + RAPIER_TYPE(RigidBodyHandle) y) { + RAPIER_TYPE(Vector) position = RAPIER_FN(ReadCollider_Translation)(read, a); + OK(RAPIER_FN(LastStatus)()); + assert(isfinite(position.y)); + (void)a; + (void)b; + (void)x; + (void)y; + assert(user == &filter_calls); + ++filter_calls; + return 1; +} + +static void RAPIER_CALL modify_contact(void *user, const RAPIER_TYPE(ReadContext) *read, + RAPIER_TYPE(ColliderHandle) a, RAPIER_TYPE(ColliderHandle) b, + RAPIER_TYPE(ContactModification) *contact) { + (void)read; + (void)user; + (void)a; + (void)b; + ++modify_calls; + contact->friction = (RAPIER_TYPE(Real))0.7; +} + +static void test_world_pipeline(void) { + RAPIER_TYPE(World) *world = RAPIER_FN(NewWorld)(); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(BuildFeatures) profiling = RAPIER_FN(BuildFeatures)(); + EXPECT(RAPIER_FN(SetCountersEnabled)(world, 1), + profiling.profiling ? RAPIER_CONST(OK) : RAPIER_CONST(UNSUPPORTED)); + EXPECT(RAPIER_FN(SetCountersEnabled)(world, 2), RAPIER_CONST(INVALID_ARGUMENT)); + RAPIER_TYPE(RigidBodyHandle) body = add_body(world, RAPIER_CONST(DYNAMIC), 10); + (void)add_ball(body); + OK(RAPIER_FN(Step)(world, NULL, NULL)); + double step_ms = RAPIER_FN(StepTimeMs)(world); + OK(RAPIER_FN(LastStatus)()); + assert(isfinite(step_ms) && (profiling.profiling ? step_ms > 0 : step_ms == 0)); + OK(RAPIER_FN(SetCountersEnabled)(world, 0)); + RAPIER_FN(StepTimeMs)(NULL); + EXPECT(RAPIER_FN(LastStatus)(), RAPIER_CONST(NULL_POINTER)); + RAPIER_TYPE(Vector) position = RAPIER_FN(RigidBody_Translation)(body); + OK(RAPIER_FN(LastStatus)()); + assert(position.y < 10); + OK(RAPIER_FN(DetectCollisions)(world, NULL, NULL)); + RAPIER_TYPE(Vector) after = RAPIER_FN(RigidBody_Translation)(body); + OK(RAPIER_FN(LastStatus)()); + assert(after.y == position.y); + OK(RAPIER_FN(FreeWorld)(world)); +} + +int main(void) { + OK(RAPIER_FN(CheckAbi)(RAPIER_CONST(ABI_VERSION), RAPIER_CONST(DIMENSION), + sizeof(RAPIER_TYPE(Real)), sizeof(RAPIER_TYPE(Vector)), + sizeof(RAPIER_TYPE(Pose)))); + EXPECT(RAPIER_FN(CheckAbi)(RAPIER_CONST(ABI_VERSION), 99, sizeof(RAPIER_TYPE(Real)), + sizeof(RAPIER_TYPE(Vector)), sizeof(RAPIER_TYPE(Pose))), + RAPIER_CONST(INVALID_ARGUMENT)); + const char *version = RAPIER_FN(Version)(); + assert(version && strstr(version, "+c.")); + assert(version == RAPIER_FN(Version)()); +#ifdef RAPIER_EXPECTED_VERSION + assert(!strcmp(version, RAPIER_EXPECTED_VERSION)); +#endif + const char *profile = RAPIER_FN(BuildProfile)(); + assert(profile && (!strcmp(profile, "release") || !strcmp(profile, "debug"))); +#ifdef RAPIER_EXPECTED_PROFILE + assert(!strcmp(profile, RAPIER_EXPECTED_PROFILE)); +#endif + RAPIER_TYPE(BuildFeatures) features = RAPIER_FN(BuildFeatures)(); + assert(features.simd_lanes == 4 || features.simd_lanes == 8); +#ifdef RAPIER_EXPECTED_SIMD_LANES + assert(features.simd_lanes == RAPIER_EXPECTED_SIMD_LANES); +#endif +#ifdef RAPIER_PARALLEL + assert(features.parallel == 1); +#else + assert(features.parallel == 0); +#endif + RAPIER_TYPE(World) *pool_world = NULL, *other_pool_world = NULL; + pool_world = RAPIER_FN(NewWorld)(); + OK(RAPIER_FN(LastStatus)()); + other_pool_world = RAPIER_FN(NewWorld)(); + OK(RAPIER_FN(LastStatus)()); + size_t workers = 99; + if (features.parallel) { + workers = RAPIER_FN(NumThreads)(pool_world); + OK(RAPIER_FN(LastStatus)()); + assert(workers == 0); + OK(RAPIER_FN(SetNumThreads)(pool_world, 1)); + OK(RAPIER_FN(SetNumThreads)(other_pool_world, 2)); + workers = RAPIER_FN(NumThreads)(pool_world); + OK(RAPIER_FN(LastStatus)()); + assert(workers == 1); + OK(RAPIER_FN(SetNumThreads)(pool_world, 4)); + workers = RAPIER_FN(NumThreads)(pool_world); + OK(RAPIER_FN(LastStatus)()); + assert(workers == 4); + workers = RAPIER_FN(NumThreads)(other_pool_world); + OK(RAPIER_FN(LastStatus)()); + assert(workers == 2); + OK(RAPIER_FN(Step)(pool_world, NULL, NULL)); + OK(RAPIER_FN(ClearThreadPool)(pool_world)); + workers = RAPIER_FN(NumThreads)(pool_world); + OK(RAPIER_FN(LastStatus)()); + assert(workers == 0); + } else { + EXPECT(RAPIER_FN(SetNumThreads)(pool_world, 2), RAPIER_CONST(UNSUPPORTED)); + EXPECT(RAPIER_FN(ClearThreadPool)(pool_world), RAPIER_CONST(UNSUPPORTED)); + workers = RAPIER_FN(NumThreads)(pool_world); + OK(RAPIER_FN(LastStatus)()); + assert(workers == 1); + } + OK(RAPIER_FN(FreeWorld)(pool_world)); + OK(RAPIER_FN(FreeWorld)(other_pool_world)); + RAPIER_TYPE(BuildInfo) info = RAPIER_FN(BuildInfo)(); + assert(info.dimension == RAPIER_CONST(DIMENSION) && + info.real_size == sizeof(RAPIER_TYPE(Real))); + EXPECT(RAPIER_FN(SetGravity)(NULL, vector(0, 0, 0)), RAPIER_CONST(NULL_POINTER)); + assert(strstr(RAPIER_FN(LastError)(), "null")); + OK(RAPIER_FN(FreeWorld)(NULL)); + RAPIER_TYPE(SharedShape) *bad = (RAPIER_TYPE(SharedShape) *)(uintptr_t)1; + bad = RAPIER_FN(BallSharedShape)(-1); + EXPECT(RAPIER_FN(LastStatus)(), RAPIER_CONST(INVALID_ARGUMENT)); + assert(bad == NULL); + bad = RAPIER_FN(BallSharedShape)((RAPIER_TYPE(Real))NAN); + EXPECT(RAPIER_FN(LastStatus)(), RAPIER_CONST(INVALID_ARGUMENT)); + RAPIER_TYPE(Vector) triangle[3] = {vector(0, 0, 0), vector(1, 0, 0), vector(0, 1, 0)}; + uint32_t bad_indices[3] = {0, 1, 3}; + bad = RAPIER_FN(TrimeshSharedShape)( + (RAPIER_TYPE(VectorView)){triangle, 3}, + (RAPIER_TYPE(TriangleView)){(const RAPIER_TYPE(Triangle) *)bad_indices, 1}); + EXPECT(RAPIER_FN(LastStatus)(), RAPIER_CONST(INVALID_ARGUMENT)); + test_world_pipeline(); + RAPIER_TYPE(World) *world = RAPIER_FN(NewWorld)(); + OK(RAPIER_FN(LastStatus)()); + + RAPIER_TYPE(ColliderHandle) floor_h; + RAPIER_TYPE(ColliderDesc) + floor = RAPIER_FN(CuboidColliderDesc)(vector(10, (RAPIER_TYPE(Real))0.5, 10)); + floor.position = pose(0, (RAPIER_TYPE(Real))-0.5, 0); + floor_h = RAPIER_FN(InsertColliderWithoutParent)(world, &floor); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(RigidBodyHandle) ball = add_body(world, RAPIER_CONST(DYNAMIC), 5); + RAPIER_TYPE(ColliderHandle) ball_collider = add_ball(ball); + RAPIER_TYPE(EventCollector) *events = RAPIER_FN(NewEventCollector)(); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(PhysicsHooks) hooks = {0}; + hooks.user_data = &filter_calls; + hooks.filter_contact_pair = contact_filter; + hooks.modify_solver_contacts = modify_contact; + for (int i = 0; i < 240; ++i) { + OK(RAPIER_FN(Step)(world, &hooks, events)); + } + RAPIER_TYPE(Vector) position; + OK(RAPIER_FN(RigidBody_ValidateHandle)(ball)); + position = RAPIER_FN(RigidBody_Translation)(ball); + OK(RAPIER_FN(LastStatus)()); + assert(position.y > 0.45 && position.y < 0.6); + assert(filter_calls > 0 && modify_calls > 0); + size_t count = RAPIER_FN(EventCollector_CollisionEvents)(events, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count > 0); + RAPIER_TYPE(CollisionEvent) *collisions = malloc(count * sizeof(*collisions)); + assert(collisions); + count = RAPIER_FN(EventCollector_CollisionEvents)(events, collisions, count); + OK(RAPIER_FN(LastStatus)()); + assert(collisions[0].started == 1); + free(collisions); + count = RAPIER_FN(EventCollector_ContactForceEvents)(events, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count > 0); + count = RAPIER_FN(ContactPairs)(world, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count > 0); + RAPIER_TYPE(ContactPair) pair = RAPIER_FN(ContactPair)(floor_h, ball_collider); + OK(RAPIER_FN(LastStatus)()); + assert(pair.has_any_active_contact); + count = RAPIER_FN(ContactPoints)(floor_h, ball_collider, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count > 0); + RAPIER_TYPE(QueryOptions) query = RAPIER_FN(DefaultQueryOptions)(); + RAPIER_TYPE(RayHit) + hit = RAPIER_FN(CastRay)(world, &query, vector(0, 10, 0), vector(0, -1, 0), 20, 1); + OK(RAPIER_FN(LastStatus)()); + assert(hit.collider.index == ball_collider.index && hit.normal.y > 0.9); + hit = RAPIER_FN(CastRay)(world, &query, vector(100, 10, 0), vector(0, -1, 0), 20, 1); + EXPECT(RAPIER_FN(LastStatus)(), RAPIER_CONST(NOT_FOUND)); + count = RAPIER_FN(IntersectPoint)(world, &query, vector(0, (RAPIER_TYPE(Real))0.5, 0), NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count == 1); + RAPIER_TYPE(ColliderHandle) sentinel = {NULL, 123, 456}; + count = RAPIER_FN(IntersectPoint)(world, &query, vector(0, (RAPIER_TYPE(Real))0.5, 0), + &sentinel, 0); + EXPECT(RAPIER_FN(LastStatus)(), RAPIER_CONST(BUFFER_TOO_SMALL)); + assert(sentinel.index == 123 && count == 1); + RAPIER_TYPE(SharedShape) *cast_shape = RAPIER_FN(BallSharedShape)((RAPIER_TYPE(Real))0.25); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(ShapeCastOptions) cast_options = RAPIER_FN(DefaultShapeCastOptions)(); + cast_options.max_time_of_impact = 10; + RAPIER_TYPE(ShapeCastHit) + shape_hit = RAPIER_FN(CastShape)(world, &query, pose(0, 4, 0), vector(0, -1, 0), cast_shape, + cast_options); + OK(RAPIER_FN(LastStatus)()); + assert(shape_hit.collider.index == ball_collider.index && shape_hit.time_of_impact > 2 && + shape_hit.time_of_impact < 4); + count = RAPIER_FN(IntersectShape)(world, &query, pose(0, (RAPIER_TYPE(Real))0.5, 0), cast_shape, + NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count == 1); + RAPIER_TYPE(Aabb) aabb = {vector(-1, -1, -1), vector(1, 1, 1)}; + count = RAPIER_FN(IntersectAabbConservative)(world, &query, aabb, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count >= 2); + RAPIER_TYPE(MassProperties) + mass_properties = RAPIER_FN(SharedShape_MassProperties)(cast_shape, 1); + OK(RAPIER_FN(LastStatus)()); + assert(mass_properties.mass > 0); + OK(RAPIER_FN(FreeSharedShape)(cast_shape)); + RAPIER_TYPE(Real) heights[4] = {0, 0, 0, 0}; +#ifdef RAPIER_DIM3 + cast_shape = RAPIER_FN(HeightfieldSharedShape)((RAPIER_TYPE(RealView)){heights, 4}, 2, 2, + vector(2, 1, 2)); + OK(RAPIER_FN(LastStatus)()); +#else + cast_shape = RAPIER_FN(HeightfieldSharedShape)((RAPIER_TYPE(RealView)){heights, 2}, 2, 1, + vector(2, 1, 0)); + OK(RAPIER_FN(LastStatus)()); +#endif + aabb = RAPIER_FN(SharedShape_ComputeAabb)(cast_shape, pose(0, 0, 0)); + OK(RAPIER_FN(LastStatus)()); + assert(aabb.maxs.x > 0); + OK(RAPIER_FN(FreeSharedShape)(cast_shape)); + RAPIER_TYPE(PointProjection) + projection = RAPIER_FN(ProjectPoint)(world, &query, vector(0, 3, 0), 10, 1); + OK(RAPIER_FN(LastStatus)()); + assert(projection.point.y < 1.1); + RAPIER_TYPE(QueryFilter) filter = RAPIER_FN(DefaultQueryFilter)(); + filter.exclude_collider = ball_collider; + query.filter = filter; + hit = RAPIER_FN(CastRay)(world, &query, vector(0, 10, 0), vector(0, -1, 0), 20, 1); + OK(RAPIER_FN(LastStatus)()); + assert(hit.collider.index == floor_h.index); + RAPIER_TYPE(Bytes) *snapshot; + const uint8_t *data; + size_t length; + snapshot = RAPIER_FN(SerializeWorld)(world); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(ByteView) bytesDataResult = RAPIER_FN(Bytes_Data)(snapshot); + data = bytesDataResult.data; + length = bytesDataResult.count; + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(World) *restored = RAPIER_FN(DeserializeWorld)(data, length); + OK(RAPIER_FN(LastStatus)()); + + OK(RAPIER_FN(RigidBody_ValidateHandle)(ball)); + RAPIER_TYPE(RigidBodyHandle) restored_ball = ball; + restored_ball.world = restored; /* Same snapshot indices, new owning allocation. */ + RAPIER_TYPE(Vector) restored_position = RAPIER_FN(RigidBody_Translation)(restored_ball); + OK(RAPIER_FN(LastStatus)()); + assert(fabs((double)(restored_position.y - position.y)) < 1e-6); + OK(RAPIER_FN(FreeBytes)(snapshot)); + OK(RAPIER_FN(Step)(restored, NULL, NULL)); + OK(RAPIER_FN(FreeWorld)(restored)); + uint8_t malformed[6] = {'R', + 'P', + 'R', + RAPIER_CONST(ABI_VERSION), + RAPIER_CONST(DIMENSION), + sizeof(RAPIER_TYPE(Real))}; + restored = RAPIER_FN(DeserializeWorld)(malformed, sizeof(malformed)); + EXPECT(RAPIER_FN(LastStatus)(), RAPIER_CONST(INVALID_ARGUMENT)); + + RAPIER_TYPE(ImpulseJointHandle) jh; + RAPIER_TYPE(RigidBodyHandle) anchor = add_body(world, RAPIER_CONST(FIXED), 5); + RAPIER_TYPE(JointDesc) joint = RAPIER_FN(RopeJointDesc)(5); + jh = RAPIER_FN(InsertImpulseJoint)(anchor, ball, &joint); + OK(RAPIER_FN(LastStatus)()); + OK(RAPIER_FN(ImpulseJoint_SetContactsEnabled)(jh, 0, 1)); + OK(RAPIER_FN(Step)(world, NULL, NULL)); + OK(RAPIER_FN(RemoveImpulseJoint)(jh, 1)); + EXPECT(RAPIER_FN(RemoveImpulseJoint)(jh, 1), RAPIER_CONST(INVALID_HANDLE)); + RAPIER_TYPE(MultibodyJointHandle) mh; + joint = RAPIER_FN(FixedJointDesc)(); + mh = RAPIER_FN(InsertMultibodyJoint)(anchor, ball, &joint); + OK(RAPIER_FN(LastStatus)()); + OK(RAPIER_FN(Step)(world, NULL, NULL)); + OK(RAPIER_FN(RemoveMultibodyJoint)(mh, 1)); + RAPIER_TYPE(SoftBodyDesc) + rope = RAPIER_FN(RopeSoftBodyDesc)(vector(3, 4, 0), vector(3, 2, 0), 8); + uint32_t pin = 0; + OK(RAPIER_FN(SoftBodyDesc_SetPinnedParticles)(&rope, (RAPIER_TYPE(IndexView)){&pin, 1})); + RAPIER_TYPE(SoftBodyHandle) sh = RAPIER_FN(InsertSoftBody)(world, &rope); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(Vector) particles[8]; + count = RAPIER_FN(SoftBody_ParticlePositions)(sh, particles, 8); + OK(RAPIER_FN(LastStatus)()); + assert(count == 8 && particles[0].y == 4); + for (int i = 0; i < 10; ++i) { + OK(RAPIER_FN(Step)(world, NULL, events)); + } + count = RAPIER_FN(SoftBody_ParticlePositions)(sh, particles, 8); + OK(RAPIER_FN(LastStatus)()); + assert(fabs((double)(particles[0].y - 4)) < 1e-5); + EXPECT(RAPIER_FN(SoftBody_SetParticlePosition)(sh, 99, vector(0, 0, 0)), + RAPIER_CONST(INVALID_ARGUMENT)); + RAPIER_TYPE(RigidBodyHandle) root = RAPIER_FN(SoftBody_RootBody)(sh); + OK(RAPIER_FN(LastStatus)()); + EXPECT(RAPIER_FN(RigidBody_SetTranslation)(root, vector(0, 0, 0), 1), + RAPIER_CONST(INVALID_ARGUMENT)); + position = RAPIER_FN(RigidBody_Translation)(root); + OK(RAPIER_FN(LastStatus)()); + count = RAPIER_FN(SoftBody_MeshColliders)(sh, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count > 0); + RAPIER_TYPE(ColliderHandle) mesh; + count = RAPIER_FN(SoftBody_MeshColliders)(sh, &mesh, 1); + OK(RAPIER_FN(LastStatus)()); + count = RAPIER_FN(SoftBody_MeshVertices)(sh, mesh, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count > 0); + RAPIER_TYPE(Vector) blade[RAPIER_CONST(DIMENSION)]; + blade[0] = vector(2, 3, 0); + blade[1] = vector(4, 3, 0); +#ifdef RAPIER_DIM3 + blade[0] = vector(2, 3, -1); + blade[1] = vector(4, 3, -1); + blade[2] = vector(3, 3, 1); +#endif + RAPIER_TYPE(SoftBodyTearEvent) *tear = RAPIER_FN(CutSoftBody)(sh, blade); + OK(RAPIER_FN(LastStatus)()); + assert(tear); + count = RAPIER_FN(SoftBodyTearEvent_Bodies)(tear, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count >= 1); + OK(RAPIER_FN(FreeSoftBodyTearEvent)(tear)); + OK(RAPIER_FN(RemoveSoftBody)(sh)); + EXPECT(RAPIER_FN(SoftBody_ValidateHandle)(sh), RAPIER_CONST(INVALID_HANDLE)); + RAPIER_TYPE(SharedShape) *character_shape = RAPIER_FN(BallSharedShape)((RAPIER_TYPE(Real))0.3); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(KinematicCharacterController) *character = + RAPIER_FN(NewKinematicCharacterController)(); + OK(RAPIER_FN(LastStatus)()); + query = RAPIER_FN(DefaultQueryOptions)(); + RAPIER_TYPE(CharacterMovement) + movement = RAPIER_FN(KinematicCharacterController_MoveShape)( + world, &query, character, (RAPIER_TYPE(Real))(1.0 / 60.0), character_shape, pose(5, 2, 0), + vector(0, -5, 0)); + OK(RAPIER_FN(LastStatus)()); + assert(movement.translation.y > -2 && movement.grounded); + OK(RAPIER_FN(FreeKinematicCharacterController)(character)); + OK(RAPIER_FN(FreeSharedShape)(character_shape)); +#ifdef RAPIER_DIM3 + RAPIER_TYPE(DynamicRayCastVehicleController) *vehicle = + RAPIER_FN(NewDynamicRayCastVehicleController)(ball); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(WheelTuning) tuning = RAPIER_FN(DefaultWheelTuning)(); + size_t wheel = RAPIER_FN(DynamicRayCastVehicleController_AddWheel)( + vehicle, vector(0, 0, 0), vector(0, -1, 0), vector(1, 0, 0), (RAPIER_TYPE(Real))0.4, + (RAPIER_TYPE(Real))0.3, &tuning); + OK(RAPIER_FN(LastStatus)()); + assert(wheel == 0); + OK(RAPIER_FN(DynamicRayCastVehicleController_UpdateVehicle)( + vehicle, (RAPIER_TYPE(Real))(1.0 / 60.0), NULL)); + OK(RAPIER_FN(FreeDynamicRayCastVehicleController)(vehicle)); +#endif + count = RAPIER_FN(DebugRender)(world, 1, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count > 0); + RAPIER_FN(RemoveRigidBody)(ball, 1); + OK(RAPIER_FN(LastStatus)()); + EXPECT(RAPIER_FN(RigidBody_ValidateHandle)(ball), RAPIER_CONST(INVALID_HANDLE)); + EXPECT(RAPIER_FN(Collider_ValidateHandle)(ball_collider), RAPIER_CONST(INVALID_HANDLE)); + RAPIER_TYPE(RigidBodyHandle) replacement = add_body(world, RAPIER_CONST(DYNAMIC), 3); + assert(replacement.index != ball.index || replacement.generation != ball.generation); + OK(RAPIER_FN(EventCollector_Clear)(events)); + count = RAPIER_FN(EventCollector_CollisionEvents)(events, NULL, 0); + OK(RAPIER_FN(LastStatus)()); + assert(count == 0); + OK(RAPIER_FN(FreeEventCollector)(events)); + OK(RAPIER_FN(FreeWorld)(world)); + printf("Rapier C integration passed: %uD, %zu-bit real\n", info.dimension, + 8 * sizeof(RAPIER_TYPE(Real))); + return 0; +} diff --git a/c/tests/pod.c b/c/tests/pod.c new file mode 100644 index 000000000..a955d15ef --- /dev/null +++ b/c/tests/pod.c @@ -0,0 +1,252 @@ +#include "rapier.h" +#include +#include +#include +#include + +#define OK(call) \ + do { \ + RAPIER_TYPE(Status) status = (call); \ + if (status != RAPIER_CONST(OK)) { \ + fprintf(stderr, "%s: %s\n", #call, RAPIER_FN(LastError)()); \ + abort(); \ + } \ + } while (0) + +static RAPIER_TYPE(Bool) RAPIER_CALL reject(void *data, const RAPIER_TYPE(ReadContext) *read, + RAPIER_TYPE(ColliderHandle) handle) { + (void)read; + (void)handle; + ++*(unsigned *)data; + return 0; +} + +static void test_world_and_deformable_bindings(void) { + RAPIER_TYPE(World) *world = RAPIER_FN(NewWorld)(); + OK(RAPIER_FN(LastStatus)()); + + RAPIER_TYPE(RigidBodyHandle) a, b; + RAPIER_TYPE(ColliderHandle) colliderHandle; + RAPIER_TYPE(RigidBodyDesc) body = RAPIER_FN(DynamicRigidBodyDesc)(); + body.additionalMass = 1; + a = RAPIER_FN(InsertRigidBody)(world, &body); + OK(RAPIER_FN(LastStatus)()); + body.position.translation.x = 2; + b = RAPIER_FN(InsertRigidBody)(world, &body); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(ColliderDesc) collider = RAPIER_FN(BallColliderDesc)(0.5); + colliderHandle = RAPIER_FN(InsertCollider)(a, &collider); + OK(RAPIER_FN(LastStatus)()); + collider.position.translation.y = -10; + colliderHandle = RAPIER_FN(InsertColliderWithoutParent)(world, &collider); + OK(RAPIER_FN(LastStatus)()); + + RAPIER_TYPE(ImpulseJointHandle) impulse; + RAPIER_TYPE(MultibodyJointHandle) multi; + RAPIER_TYPE(JointDesc) joint = RAPIER_FN(FixedJointDesc)(); + impulse = RAPIER_FN(InsertImpulseJoint)(a, b, &joint); + OK(RAPIER_FN(LastStatus)()); + assert(impulse.index != UINT32_MAX); + multi = RAPIER_FN(InsertMultibodyJoint)(a, b, &joint); + OK(RAPIER_FN(LastStatus)()); + assert(multi.index != UINT32_MAX); + + RAPIER_TYPE(SoftBodyDesc) soft; + RAPIER_TYPE(SoftBodyHandle) softHandle; + RAPIER_TYPE(Vector) vertices[3] = {{0}, {0}, {0}}; + vertices[1].x = 1; + vertices[2].y = 1; + soft = RAPIER_FN(DefaultSoftBodyDesc)(); + soft.positions = (RAPIER_TYPE(VectorView)){vertices, 3}; + soft.collisionEnabled = 0; + softHandle = RAPIER_FN(InsertSoftBody)(world, &soft); + OK(RAPIER_FN(LastStatus)()); + + RAPIER_TYPE(RigidBodyHandle) root; + OK(RAPIER_FN(SoftBody_ValidateHandle)(softHandle)); + root = RAPIER_FN(SoftBody_RootBody)(softHandle); + OK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(SoftBodyMaterial) material = RAPIER_FN(SoftBody_Material)(softHandle); + OK(RAPIER_FN(LastStatus)()); + material.edgeSoftness.natural_frequency = 45; + material.tearStrain = (RAPIER_TYPE(OptionalReal)){1, 0.5}; + OK(RAPIER_FN(SoftBody_SetMaterial)(softHandle, &material)); + material = RAPIER_FN(SoftBody_Material)(softHandle); + OK(RAPIER_FN(LastStatus)()); + assert(material.edgeSoftness.natural_frequency == 45 && material.tearStrain.enabled); + + collider = RAPIER_FN(DefaultColliderDesc)(); + collider.isSensor = 1; + collider.shape.vertices = (RAPIER_TYPE(VectorView)){vertices, 3}; +#ifdef RAPIER_DIM3 + uint32_t indices[] = {0, 1, 2}; + collider.shape.kind = RAPIER_CONST(SHAPE_DESC_TRIMESH); + collider.shape.flags = RAPIER_CONST(TRIMESH_DEFORMABLE); +#else + uint32_t indices[] = {0, 1, 1, 2}; + collider.shape.kind = RAPIER_CONST(SHAPE_DESC_POLYLINE); + collider.shape.flags = RAPIER_CONST(POLYLINE_DEFORMABLE); +#endif +#ifdef RAPIER_DIM3 + collider.shape.triangles = + (RAPIER_TYPE(TriangleView)){(const RAPIER_TYPE(Triangle) *)indices, 1}; +#else + collider.shape.edges = (RAPIER_TYPE(EdgeView)){(const RAPIER_TYPE(Edge) *)indices, 2}; +#endif + RAPIER_TYPE(SoftMeshBindingDesc) binding = RAPIER_FN(DefaultSoftMeshBindingDesc)(); + uint32_t particleIndices[] = {0, 1, 2}; + binding.kind = RAPIER_CONST(SOFT_BINDING_DIRECT); + binding.particles = (RAPIER_TYPE(IndexView)){particleIndices, 3}; + colliderHandle = RAPIER_FN(InsertDeformableCollider)(&collider, &binding, root); + OK(RAPIER_FN(LastStatus)()); + particleIndices[2] = 999; + vertices[1].x = 100; + OK(RAPIER_FN(Step)(world, NULL, NULL)); + OK(RAPIER_FN(SoftBody_ValidateHandle)(softHandle)); + size_t count; + RAPIER_TYPE(Vector) copied[3]; + count = RAPIER_FN(SoftBody_MeshVertices)(softHandle, colliderHandle, copied, 3); + OK(RAPIER_FN(LastStatus)()); + assert(count == 3 && copied[1].x < 2); + OK(RAPIER_FN(FreeWorld)(world)); +} + +int main(void) { + test_world_and_deformable_bindings(); + RAPIER_TYPE(PodLayout) layout = RAPIER_FN(PodLayout)(); + assert(layout.rigidBodyDesc == sizeof(RAPIER_TYPE(RigidBodyDesc))); + assert(layout.colliderDesc == sizeof(RAPIER_TYPE(ColliderDesc))); + assert(layout.shapeDesc == sizeof(RAPIER_TYPE(ShapeDesc))); + assert(layout.jointDesc == sizeof(RAPIER_TYPE(JointDesc))); + assert(layout.softBodyMaterial == sizeof(RAPIER_TYPE(SoftBodyMaterial))); + assert(layout.integrationParameters == sizeof(RAPIER_TYPE(IntegrationParameters))); + assert(layout.softBodyDesc == sizeof(RAPIER_TYPE(SoftBodyDesc))); + assert(layout.softMeshBindingDesc == sizeof(RAPIER_TYPE(SoftMeshBindingDesc))); + assert(layout.queryOptions == sizeof(RAPIER_TYPE(QueryOptions))); + + RAPIER_TYPE(World) *world = RAPIER_FN(NewWorld)(); + OK(RAPIER_FN(LastStatus)()); + + RAPIER_TYPE(RigidBodyDesc) body = RAPIER_FN(DynamicRigidBodyDesc)(); + RAPIER_TYPE(ColliderDesc) collider = RAPIER_FN(BallColliderDesc)(0.5); + body.canSleep = 0; + body.position.translation.y = 5; + body.userData.low = 42; + RAPIER_TYPE(RigidBodyHandle) first, second; + RAPIER_TYPE(ColliderHandle) firstCollider; + first = RAPIER_FN(InsertRigidBody)(world, &body); + OK(RAPIER_FN(LastStatus)()); + firstCollider = RAPIER_FN(InsertCollider)(first, &collider); + OK(RAPIER_FN(LastStatus)()); + body.position.translation.x = 2; + second = RAPIER_FN(InsertRigidBody)(world, &body); + OK(RAPIER_FN(LastStatus)()); + RAPIER_FN(InsertCollider)(second, &collider); + OK(RAPIER_FN(LastStatus)()); + + RAPIER_TYPE(Vector) position; + OK(RAPIER_FN(RigidBody_ValidateHandle)(first)); + position = RAPIER_FN(RigidBody_Translation)(first); + OK(RAPIER_FN(LastStatus)()); + assert(position.x == 0 && position.y == 5); + RAPIER_TYPE(UserData) userData = RAPIER_FN(RigidBody_UserData)(first); + OK(RAPIER_FN(LastStatus)()); + assert(userData.low == 42); + + RAPIER_TYPE(JointDesc) joint = RAPIER_FN(SpringJointDesc)(2, 100, 1); + RAPIER_TYPE(ImpulseJointHandle) + jointHandle = RAPIER_FN(InsertImpulseJoint)(first, second, &joint); + OK(RAPIER_FN(LastStatus)()); + assert(jointHandle.index != UINT32_MAX); + joint.motors[0].stiffness = -1; + RAPIER_FN(InsertImpulseJoint)(first, second, &joint); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(INVALID_ARGUMENT)); + + RAPIER_TYPE(IntegrationParameters) settings = RAPIER_FN(IntegrationParameters)(world); + OK(RAPIER_FN(LastStatus)()); + settings.dt = (RAPIER_TYPE(Real))(1.0 / 120.0); + OK(RAPIER_FN(SetIntegrationParameters)(world, &settings)); + settings.dt = -1; + assert(RAPIER_FN(SetIntegrationParameters)(world, &settings) == RAPIER_CONST(INVALID_ARGUMENT)); + settings = RAPIER_FN(IntegrationParameters)(world); + OK(RAPIER_FN(LastStatus)()); + assert(settings.dt == (RAPIER_TYPE(Real))(1.0 / 120.0)); + + OK(RAPIER_FN(Step)(world, NULL, NULL)); + RAPIER_TYPE(QueryOptions) query = RAPIER_FN(DefaultQueryOptions)(); + RAPIER_TYPE(Vector) origin = {0}, direction = {0}; + origin.y = 10; + direction.y = -1; + RAPIER_TYPE(Bool) found = 0; + RAPIER_TYPE(RayHit) hit; + RAPIER_TYPE(OptionalRayHit) + tryCastRayResult2 = RAPIER_FN(TryCastRay)(world, &query, origin, direction, 20, 1); + hit = tryCastRayResult2.hit; + found = tryCastRayResult2.found; + OK(RAPIER_FN(LastStatus)()); + assert(found && hit.collider.index == firstCollider.index); + unsigned calls = 0; + query.predicate = reject; + query.userData = &calls; + RAPIER_TYPE(OptionalRayHit) + tryCastRayResult3 = RAPIER_FN(TryCastRay)(world, &query, origin, direction, 20, 1); + hit = tryCastRayResult3.hit; + found = tryCastRayResult3.found; + OK(RAPIER_FN(LastStatus)()); + assert(!found && calls); + query.predicate = NULL; + OK(RAPIER_FN(Step)(world, NULL, NULL)); + RAPIER_TYPE(OptionalRayHit) + tryCastRayResult4 = RAPIER_FN(TryCastRay)(world, &query, origin, direction, 20, 1); + hit = tryCastRayResult4.hit; + found = tryCastRayResult4.found; + OK(RAPIER_FN(LastStatus)()); + assert(found); + + /* A copied description borrows arrays until insertion. The world then owns copies. */ + RAPIER_TYPE(SoftBodyDesc) soft = RAPIER_FN(DefaultSoftBodyDesc)(); + RAPIER_TYPE(Vector) particles[2] = {{0}, {0}}; + particles[0].y = particles[1].y = 10; + particles[1].x = 1; + RAPIER_TYPE(Edge) edges[] = {{0, 1}}; + soft.positions = (RAPIER_TYPE(VectorView)){particles, 2}; + soft.edges = (RAPIER_TYPE(EdgeView)){edges, 1}; + soft.collisionEnabled = 0; + soft.canSleep = 0; + soft.material.edgeSoftness.natural_frequency = 40; + RAPIER_TYPE(SoftBodyHandle) softHandle = RAPIER_FN(InsertSoftBody)(world, &soft); + OK(RAPIER_FN(LastStatus)()); + particles[1].x = 100; + edges[0].b = 999; + + OK(RAPIER_FN(SoftBody_ValidateHandle)(softHandle)); + position = RAPIER_FN(SoftBody_ParticlePosition)(softHandle, 1); + OK(RAPIER_FN(LastStatus)()); + assert(position.x == 1); + RAPIER_TYPE(SoftBodyHandle) sentinel = {NULL, 123, 456}; + sentinel = RAPIER_FN(InsertSoftBody)(world, &soft); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(INVALID_ARGUMENT)); + assert(sentinel.index == UINT32_MAX && sentinel.generation == UINT32_MAX); + + /* Retained shared shapes remain usable after the caller releases its reference. */ + RAPIER_TYPE(SharedShape) *shape = RAPIER_FN(BallSharedShape)(0.75); + OK(RAPIER_FN(LastStatus)()); + collider = RAPIER_FN(DefaultColliderDesc)(); + collider.shape.kind = RAPIER_CONST(SHAPE_DESC_SHARED); + collider.shape.sharedShape = shape; + collider.position.translation.x = 10; + RAPIER_FN(InsertColliderWithoutParent)(world, &collider); + OK(RAPIER_FN(LastStatus)()); + OK(RAPIER_FN(FreeSharedShape)(shape)); + OK(RAPIER_FN(Step)(world, NULL, NULL)); + origin.x = 10; + RAPIER_TYPE(OptionalRayHit) + tryCastRayResult5 = RAPIER_FN(TryCastRay)(world, &query, origin, direction, 20, 1); + hit = tryCastRayResult5.hit; + found = tryCastRayResult5.found; + OK(RAPIER_FN(LastStatus)()); + assert(found && isfinite(hit.time_of_impact)); + /* The only remaining owner is world. POD values and borrowed views need no cleanup. */ + OK(RAPIER_FN(FreeWorld)(world)); + return 0; +} diff --git a/c/tools/export_names.py b/c/tools/export_names.py new file mode 100644 index 000000000..986e222db --- /dev/null +++ b/c/tools/export_names.py @@ -0,0 +1,52 @@ +"""Read C export names from the same receiver annotations used by rapier_export.""" +from pathlib import Path +import re + +ROOT = Path(__file__).resolve().parents[2] +EXPORT_ATTRIBUTE = r"#\[rapier_export(?:\(([a-z][a-z0-9_]*)\))?\]" +DECLARATION = r'pub (?:unsafe )?extern "C" fn (rpr_[a-z0-9_]+)' + + +def pascal_case(name): + if not re.fullmatch(r"[a-z0-9]+(?:_[a-z0-9]+)*", name): + raise ValueError(f"Invalid snake_case export name: {name}") + return "".join(word[0].upper() + word[1:] for word in name.split("_")) + + +def export_suffix(name, receiver=None): + if not name.startswith("rpr_"): + raise ValueError(f"Missing rpr_ prefix: {name}") + suffix = name[4:] + if receiver is None: + return pascal_case(suffix) + if not suffix.startswith(receiver + "_"): + raise ValueError(f"{name} does not start with receiver {receiver}") + return pascal_case(receiver) + "_" + pascal_case(suffix[len(receiver) + 1:]) + + +def read_exports(): + exports = {} + # Attributes may be followed by cfg attributes and documentation comments. + pattern = EXPORT_ATTRIBUTE + r"(?:(?!#\[rapier_export)[\s\S])*?" + DECLARATION + for source in sorted((ROOT / "c/src").glob("*.rs")): + text = source.read_text() + declarations = set(re.findall(DECLARATION, text)) + annotated = set() + for receiver, name in re.findall(pattern, text): + suffix = export_suffix(name, receiver or None) + if name in exports and exports[name] != suffix: + raise ValueError(f"Conflicting export annotations: {name}") + exports[name] = suffix + annotated.add(name) + if declarations != annotated: + raise ValueError(f"Missing export annotation in {source}: {declarations - annotated}") + if len(set(exports.values())) != len(exports): + raise ValueError("Duplicate C export names") + return exports + + +EXPORTS = read_exports() + + +def c_name(name, dimension): + return f"r{dimension}{EXPORTS[name]}" diff --git a/c/tools/generate-header.py b/c/tools/generate-header.py new file mode 100644 index 000000000..1fd35d5e0 --- /dev/null +++ b/c/tools/generate-header.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Generate dimension-specific C names from the shared Rust binding source.""" +from pathlib import Path +import re +import subprocess +import sys +import tempfile + +from export_names import EXPORT_ATTRIBUTE, c_name + +ROOT = Path(__file__).resolve().parents[2] +DESTINATION = Path(sys.argv[1]) if len(sys.argv) > 1 else ROOT / "c/include/rapier.h" + + +with tempfile.TemporaryDirectory(prefix="rapier-header-") as tmp: + project = Path(tmp) + source_dir = project / "src" + source_dir.mkdir() + # cbindgen parses source without expanding custom attributes. Give it a + # temporary declaration view where our export marker is the standard one. + # Signatures, docs, types, and feature gates all come from the real source. + sources = sorted((ROOT / "c/src").glob("*.rs")) + for source in sources: + text = re.sub(EXPORT_ATTRIBUTE, "#[unsafe(no_mangle)]", source.read_text()) + (source_dir / source.name).write_text(text) + (project / "Cargo.toml").write_text('''[package] +name = "rapier-c-header" +version = "0.0.0" +edition = "2024" +[workspace] +[features] +default = ["dim3", "f32"] +dim2 = [] +dim3 = [] +f32 = [] +f64 = [] +fem = [] +parallel = [] +robotics = [] +''') + raw = project / "rapier.h" + subprocess.run([ + "cbindgen", "--config", str(ROOT / "c/cbindgen.toml"), + "--crate", "rapier-c-header", "--output", str(raw), + ], cwd=project, check=True) + text = raw.read_text() + # Transparent native wrappers remain opaque to C. + for source in sources: + for name, native in re.findall(r"pub struct (Rpr\w+)\(pub\(crate\) (\w+)\)", source.read_text()): + text = text.replace(f"typedef {native} {name};", f"typedef struct {name} {name};") + for name in ["RprPairFilter", "RprModifyContacts", "RprErrorCallback", "RprModifyContactContext", "RprIkJointCanMove", "RprQueryPredicate"]: + text = text.replace(f"(*{name})", f"(RAPIER_CALL *{name})") + text = text.replace("\n#endif\n ;", ";\n#endif") + + # cbindgen emits this mutually recursive pair in pointer-dependency order. + # C requires the by-value ShapeDesc field to be complete first. + compound = re.search(r"typedef struct RprCompoundShapeDesc \{.*?\} RprCompoundShapeDesc;\n", text, re.S) + if compound: + declaration = compound[0] + text = text[:compound.start()] + text[compound.end():] + shape_end = text.index("} RprShapeDesc;") + len("} RprShapeDesc;") + text = text[:shape_end] + "\n\n" + declaration + text[shape_end:] + + # Rust stays dimension-neutral. Emit concrete C type, constant, and function + # names for each selected dimension, without compatibility aliases. + start = list(re.finditer(r"^#include <[^>]+>\n", text, re.M))[-1].end() + end = text.rindex("#endif /* RAPIER_H */") + declarations = text[start:end] + variants = [] + for dim in (2, 3): + variant = re.sub(r"\brpr_[a-z0-9_]+\b", lambda m: c_name(m[0], dim), declarations) + variant = re.sub(r"\bRpr(\w+)\b", lambda m: f"R{dim}{m[1]}", variant) + variant = re.sub(r"\bRPR_(\w+)\b", lambda m: f"R{dim}_{m[1]}", variant) + variants.append(variant) + text = (text[:start] + "\n#if defined(RAPIER_DIM2)\n" + variants[0] + + "#else /* RAPIER_DIM3 */\n" + variants[1] + "#endif\n\n" + text[end:]) + DESTINATION.parent.mkdir(parents=True, exist_ok=True) + DESTINATION.write_text(text) diff --git a/c/tools/generate-header.sh b/c/tools/generate-header.sh new file mode 100644 index 000000000..c9c408842 --- /dev/null +++ b/c/tools/generate-header.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +set -eu +exec python3 "$(dirname -- "$0")/generate-header.py" "$@" diff --git a/c/tools/test-native.py b/c/tools/test-native.py new file mode 100644 index 000000000..d0150cc80 --- /dev/null +++ b/c/tools/test-native.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +"""Test all native ABIs, exported names, linkage modes, and dimension coexistence.""" +import ctypes +import json +import os +from pathlib import Path +import re +import shlex +import subprocess +import sys +import tempfile + +from export_names import EXPORTS, c_name + +ROOT = Path(__file__).resolve().parents[2] +VERSION = (ROOT / "c/VERSION").read_text().strip() +CC = shlex.split(os.environ.get("CC", "cc")) +CXX = shlex.split(os.environ.get("CXX", "c++")) +VARIANTS = [(d, p) for d in (2, 3) for p in (32, 64)] +PACKAGES = [f"rapier{d}d" + ("-f64" if p == 64 else "") + "-ffi" for d, p in VARIANTS] +subprocess.run(["cargo", "build", *[arg for package in PACKAGES for arg in ("-p", package)]], + cwd=ROOT, check=True) +metadata = subprocess.check_output(["cargo", "metadata", "--no-deps", "--format-version", "1"], + cwd=ROOT, text=True) +LIBDIR = Path(json.loads(metadata)["target_directory"]) / "debug" +SYSTEM_LIBS = ["-lpthread", "-lm"] + (["-framework", "Security", "-framework", "CoreFoundation"] + if sys.platform == "darwin" else ["-ldl", "-lrt", "-lutil"]) + + +def link_flags(libraries, linkage): + if linkage == "shared": + return [f"-L{LIBDIR}", *[f"-l{lib}" for lib in libraries], f"-Wl,-rpath,{LIBDIR}"] + return [*[str(LIBDIR / f"lib{lib}.a") for lib in libraries], *SYSTEM_LIBS] + + +rust_names = set(EXPORTS) + +with tempfile.TemporaryDirectory(prefix="rapier-c-tests-") as directory: + temp = Path(directory) + for (dim, precision), package in zip(VARIANTS, PACKAGES): + library = package.replace("-", "_") + flags = ["-Wall", "-Wextra", "-Werror", "-UNDEBUG", "-I", str(ROOT / "c/include"), + f"-DRAPIER_DIM{dim}", f"-DRAPIER_F{precision}", + f'-DRAPIER_EXPECTED_VERSION="{VERSION}"'] + header = subprocess.check_output([*CC, "-E", "-P", "-x", "c", *flags, + str(ROOT / "c/include/rapier.h")], text=True) + symbols = set(re.findall(r"\b(r[23][A-Z]\w*)\s*\(", header)) + assert symbols and all(name.startswith(f"r{dim}") for name in symbols) + assert f"r{dim}InsertRigidBody" in symbols + # Reject the replaced abbreviated free-function names. + for old, new in { + "RemoveBody": "RemoveRigidBody", + "InsertDeformable": "InsertDeformableCollider", + "ActiveBodies": "ActiveRigidBodies", + "Parameters": "IntegrationParameters", + "SetParameters": "SetIntegrationParameters", + "Serialize": "SerializeWorld", + "Deserialize": "DeserializeWorld", + "RigidBodyLen": "RigidBodyCount", + "ColliderLen": "ColliderCount", + "SoftBodyLen": "SoftBodyCount", + "ReadRigidBodyLen": "ReadRigidBodyCount", + "ReadColliderLen": "ReadColliderCount", + "Dt": "TimeStep", + "SetDt": "SetTimeStep", + }.items(): + assert f"r{dim}{old}" not in symbols + assert f"r{dim}{new}" in symbols + assert f"r{dim}InsertColliderWithoutParent" in symbols + assert f"r{dim}Insert" not in symbols and f"r{dim}InsertBody" not in symbols + assert not re.search(rf"\bR{dim}BodyCollider\b", header) + assert not re.search(r"\brpr_\w+\s*\(", header) + assert not re.search(r"\b(?:Rpr\w*|RPR_\w+|R" + str(5 - dim) + r"[A-Z_]\w*)\b", header) + macros = subprocess.check_output([*CC, "-E", "-dM", "-x", "c", *flags, + str(ROOT / "c/include/rapier.h")], text=True) + assert re.search(rf"^#define R{dim}_OK 0$", macros, re.M) + assert re.search(rf"^#define R{dim}_ABI_VERSION 1$", macros, re.M) + assert not re.search(r"^#define (?:RPR_|R" + str(5 - dim) + r"_)", macros, re.M) + # Produced values must use direct returns, not the former scalar output pointers. + declarations = re.findall(r"\br[23][A-Z]\w*\([^;]*?\);", header, re.S) + assert not any(re.search(r"\*\s*(?:out(?:_\w+)?|count|found)\b", d) + for d in declarations) + # ABI 7 has one owner: no legacy component objects, query views, or aliases. + for legacy_type in ("PhysicsWorld", "PhysicsPipeline", "CollisionPipeline", + "RigidBodySet", "ColliderSet", "SoftBodySet", + "ImpulseJointSet", "MultibodyJointSet", "QueryView"): + assert not re.search(rf"\bR{dim}" + legacy_type + r"\b", header), legacy_type + assert not any(re.match(r"r[23](PhysicsWorld|PhysicsPipeline|CollisionPipeline|QueryView)", + symbol) for symbol in symbols) + # Compare the entire dynamic export set, not just the names used in examples. + extension = "dylib" if sys.platform == "darwin" else "so" + binary = LIBDIR / f"lib{library}.{extension}" + nm_args = ["-gU"] if sys.platform == "darwin" else ["-D", "--defined-only"] + exports = subprocess.check_output(["nm", *nm_args, str(binary)], text=True) + actual = set(re.findall(r"\b(r[23][A-Z]\w*)$", exports, re.MULTILINE)) + # macOS nm prints a leading underscore before C symbols. + if sys.platform == "darwin": + actual = set(re.findall(r"\b_(r[23][A-Z]\w*)$", exports, re.MULTILINE)) + assert actual == symbols, (package, "missing", symbols - actual, "unexpected", actual - symbols) + assert not re.search(r"\b_?rpr_\w+$", exports, re.MULTILINE) + loaded = ctypes.CDLL(str(binary)) + for name in rust_names: + assert not hasattr(loaded, name), (package, "legacy export", name) + assert not hasattr(loaded, c_name(name, 5 - dim)), (package, "wrong dimension", name) + if "_" in EXPORTS[name]: + old_name = c_name(name, dim).replace("_", "") + assert not hasattr(loaded, old_name), (package, "obsolete method export", old_name) + + symbol_source = temp / "symbols.c" + symbol_source.write_text( + '#include "rapier.h"\nstatic void (*const symbols[])(void) = {\n' + + "".join(f" (void (*)(void)){name},\n" for name in sorted(symbols)) + + "};\nint main(void) { for (unsigned i = 0; i < sizeof(symbols)/sizeof(symbols[0]); ++i) " + "if (!symbols[i]) return 1; return 0; }\n") + for linkage in ("shared", "static"): + for source, compiler, standard in [(ROOT / "c/tests/integration.c", CC, "c11"), + (ROOT / "c/tests/cpp.cpp", CXX, "c++17"), + (ROOT / "c/tests/pod.c", CC, "c11"), + (ROOT / "c/tests/handles.c", CC, "c11"), + (ROOT / "c/tests/initializers.c", CC, "c11"), + (ROOT / "c/tests/initializers.cpp", CXX, "c++17"), + (ROOT / "c/tests/array_views.c", CC, "c11"), + (ROOT / "c/tests/array_views.cpp", CXX, "c++17"), + (symbol_source, CC, "c11")]: + output = temp / f"{source.stem}-{library}-{linkage}" + subprocess.run([*compiler, f"-std={standard}", *flags, str(source), + *link_flags([library], linkage), "-o", str(output)], check=True) + subprocess.run([str(output)], check=True) + print(f"{package}: C, C++, and {len(symbols)} exports passed shared + static linking", flush=True) + + # Each dimension keeps its own POD layouts in a separate translation unit. + # Both libraries must coexist in one executable without symbol collisions. + main = temp / "both.c" + main.write_text("int run2(void); int run3(void); int main(void) { return run2() || run3(); }\n") + for precision in (32, 64): + objects, libraries = [], [] + for dim in (2, 3): + source, obj = temp / f"dim{dim}.c", temp / f"dim{dim}.o" + source.write_text(f'''#include "rapier.h" +int run{dim}(void) {{ + R{dim}World *world = 0; + if (r{dim}CheckAbi(R{dim}_ABI_VERSION, {dim}, sizeof(R{dim}Real), sizeof(R{dim}Vector), sizeof(R{dim}Pose))) return 1; + world = r{dim}NewWorld(); + if (r{dim}LastStatus()) return 2; + R{dim}Status status = r{dim}Step(world, 0, 0); + return r{dim}FreeWorld(world) || status; +}} +''') + subprocess.run([*CC, "-std=c11", "-Wall", "-Wextra", "-Werror", + f"-DRAPIER_DIM{dim}", f"-DRAPIER_F{precision}", "-I", str(ROOT / "c/include"), + "-c", str(source), "-o", str(obj)], check=True) + objects.append(str(obj)) + libraries.append(f"rapier{dim}d" + ("_f64" if precision == 64 else "") + "_ffi") + for linkage in ("shared", "static"): + output = temp / f"both-f{precision}-{linkage}" + subprocess.run([*CC, str(main), *objects, *link_flags(libraries, linkage), + "-o", str(output)], check=True) + subprocess.run([str(output)], check=True) + print(f"2D + 3D / f{precision}: coexistence passed shared + static linking", flush=True) diff --git a/src/pipeline/physics_world.rs b/src/pipeline/physics_world.rs index fef03e24d..5bc0b4655 100644 --- a/src/pipeline/physics_world.rs +++ b/src/pipeline/physics_world.rs @@ -12,7 +12,8 @@ use crate::geometry::{ }; use crate::math::{DIM, Real, Vector}; use crate::pipeline::{ - EventHandler, PhysicsHooks, PhysicsPipeline, Quarantine, QueryFilter, QueryPipeline, + CollisionPipeline, EventHandler, PhysicsHooks, PhysicsPipeline, Quarantine, QueryFilter, + QueryPipeline, }; use parry::bounding_volume::{Aabb, BoundingVolume}; use parry::partitioning::BvhNode; @@ -68,6 +69,9 @@ pub struct PhysicsWorld { /// The main simulation pipeline that orchestrates each physics step. #[cfg_attr(feature = "serde-serialize", serde(skip))] pub physics_pipeline: PhysicsPipeline, + /// Workspace for collision-only updates that do not integrate positions or solve constraints. + #[cfg_attr(feature = "serde-serialize", serde(skip))] + pub collision_pipeline: CollisionPipeline, /// Manages active/sleeping body groups (islands) for efficient simulation. pub islands: IslandManager, /// The broad-phase acceleration structure for fast spatial queries. @@ -97,6 +101,7 @@ impl Default for PhysicsWorld { gravity: Vector::Y * -9.81, integration_parameters: IntegrationParameters::default(), physics_pipeline: PhysicsPipeline::new(), + collision_pipeline: CollisionPipeline::new(), islands: IslandManager::new(), broad_phase: DefaultBroadPhase::default(), narrow_phase: NarrowPhase::new(), @@ -163,6 +168,23 @@ impl PhysicsWorld { ); } + /// Update broad-phase and narrow-phase collision detection without advancing simulation. + /// + /// Uses the prediction distance from this world's integration parameters. This is useful + /// after editing transforms when contacts and scene queries must be refreshed immediately. + pub fn detect_collisions(&mut self, hooks: &dyn PhysicsHooks, events: &dyn EventHandler) { + self.collision_pipeline.step( + self.integration_parameters.prediction_distance(), + &mut self.islands, + &mut self.broad_phase, + &mut self.narrow_phase, + &mut self.bodies, + &mut self.colliders, + hooks, + events, + ); + } + /// The bodies and colliders automatically disabled during the last step because their /// state became non-finite; see [`Quarantine`]. pub fn quarantine(&self) -> &Quarantine { @@ -222,6 +244,17 @@ impl PhysicsWorld { /// /// Returns the removed body, or `None` if the handle was invalid. pub fn remove_body(&mut self, handle: RigidBodyHandle) -> Option { + self.remove_body_with_colliders(handle, true) + } + + /// Remove a rigid body and its joints, optionally preserving attached colliders. + /// + /// Preserved colliders become standalone colliders. Returns `None` for an invalid handle. + pub fn remove_body_with_colliders( + &mut self, + handle: RigidBodyHandle, + remove_attached_colliders: bool, + ) -> Option { self.bodies.remove( handle, &mut self.islands, @@ -229,7 +262,7 @@ impl PhysicsWorld { &mut self.impulse_joints, &mut self.multibody_joints, &mut self.soft_bodies, - true, + remove_attached_colliders, ) } From 52a13007929d377ea26d5093a1cc646f851aac24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 24 Sep 2026 15:32:51 +0200 Subject: [PATCH 2/8] feat: add C testbed and demos --- .github/workflows/c-bindings.yml | 63 + c/CMakeLists.txt | 7 +- c/testbed/.clang-format | 16 + c/testbed/CMakeLists.txt | 126 ++ c/testbed/PORTING.md | 72 + c/testbed/README.md | 151 ++ c/testbed/coverage.json | 1820 +++++++++++++++++ c/testbed/example_math.h | 15 + c/testbed/examples2d/add_remove2.c | 73 + c/testbed/examples2d/b2d_compounds.c | 68 + c/testbed/examples2d/b2d_joint_grid.c | 60 + c/testbed/examples2d/b2d_junkyard.c | 73 + c/testbed/examples2d/b2d_large_pyramid.c | 45 + c/testbed/examples2d/b2d_many_pyramids.c | 58 + c/testbed/examples2d/b2d_rain.c | 292 +++ c/testbed/examples2d/b2d_smash.c | 45 + c/testbed/examples2d/b2d_spinner.c | 94 + c/testbed/examples2d/b2d_tumbler.c | 62 + c/testbed/examples2d/b2d_washer.c | 71 + c/testbed/examples2d/ccd2.c | 102 + c/testbed/examples2d/character_controller2.c | 164 ++ c/testbed/examples2d/collision_groups2.c | 50 + c/testbed/examples2d/convex_polygons2.c | 55 + c/testbed/examples2d/damping2.c | 39 + c/testbed/examples2d/debug_angular_limits2.c | 60 + c/testbed/examples2d/debug_box_ball2.c | 39 + c/testbed/examples2d/debug_compression2.c | 56 + c/testbed/examples2d/debug_intersection2.c | 58 + c/testbed/examples2d/debug_many_colliders2.c | 148 ++ c/testbed/examples2d/debug_self_intersect2.c | 228 +++ c/testbed/examples2d/debug_total_overlap2.c | 30 + c/testbed/examples2d/debug_vertical_column2.c | 38 + c/testbed/examples2d/drum2.c | 55 + c/testbed/examples2d/heightfield2.c | 54 + c/testbed/examples2d/inv_pyramid2.c | 40 + c/testbed/examples2d/inverse_kinematics2.c | 75 + c/testbed/examples2d/joint_motor_position2.c | 59 + c/testbed/examples2d/joints2.c | 55 + c/testbed/examples2d/locked_rotations2.c | 46 + c/testbed/examples2d/multi_pendulum2.c | 69 + c/testbed/examples2d/one_way_platforms2.c | 96 + c/testbed/examples2d/pin_slot_joint2.c | 76 + c/testbed/examples2d/platform2.c | 64 + c/testbed/examples2d/polyline2.c | 54 + c/testbed/examples2d/pyramid2.c | 46 + c/testbed/examples2d/restitution2.c | 41 + c/testbed/examples2d/rope_joints2.c | 77 + c/testbed/examples2d/s2d_arch.c | 92 + c/testbed/examples2d/s2d_ball_and_chain.c | 69 + c/testbed/examples2d/s2d_bridge.c | 51 + c/testbed/examples2d/s2d_card_house.c | 56 + c/testbed/examples2d/s2d_confined.c | 49 + c/testbed/examples2d/s2d_far_pyramid.c | 45 + c/testbed/examples2d/s2d_high_mass_ratio_1.c | 51 + c/testbed/examples2d/s2d_high_mass_ratio_2.c | 45 + c/testbed/examples2d/s2d_high_mass_ratio_3.c | 39 + c/testbed/examples2d/s2d_joint_grid.c | 51 + c/testbed/examples2d/s2d_pyramid.c | 45 + c/testbed/examples2d/sensor2.c | 82 + c/testbed/examples2d/soft_blobs2.c | 58 + c/testbed/examples2d/soft_bodies2.c | 136 ++ c/testbed/examples2d/soft_cutting2.c | 178 ++ c/testbed/examples2d/soft_fem2.c | 129 ++ c/testbed/examples2d/soft_force_tearing2.c | 228 +++ c/testbed/examples2d/soft_jelly2.c | 92 + c/testbed/examples2d/soft_joints2.c | 365 ++++ c/testbed/examples2d/soft_letters2.c | 112 + c/testbed/examples2d/soft_pile2.c | 122 ++ c/testbed/examples2d/soft_plasticity2.c | 175 ++ c/testbed/examples2d/soft_stress2.c | 173 ++ c/testbed/examples2d/soft_surface2.c | 141 ++ c/testbed/examples2d/soft_tearing2.c | 228 +++ c/testbed/examples2d/soft_thin_features2.c | 187 ++ c/testbed/examples2d/stress_tests/balls2.c | 32 + c/testbed/examples2d/stress_tests/boxes2.c | 47 + c/testbed/examples2d/stress_tests/capsules2.c | 48 + .../stress_tests/convex_polygons2.c | 63 + .../examples2d/stress_tests/heightfield2.c | 54 + .../examples2d/stress_tests/joint_ball2.c | 50 + .../examples2d/stress_tests/joint_fixed2.c | 54 + .../stress_tests/joint_prismatic2.c | 50 + .../examples2d/stress_tests/large_pyramids2.c | 45 + .../examples2d/stress_tests/many_pyramids2.c | 57 + c/testbed/examples2d/stress_tests/pyramid2.c | 38 + c/testbed/examples2d/stress_tests/ragdolls2.c | 92 + c/testbed/examples2d/stress_tests/ropes2.c | 48 + .../examples2d/stress_tests/soft_blobs2.c | 58 + .../stress_tests/soft_cloth_keva2.c | 68 + .../examples2d/stress_tests/soft_fem_beams2.c | 83 + .../examples2d/stress_tests/soft_jellies2.c | 61 + .../examples2d/stress_tests/soft_ropes2.c | 67 + .../examples2d/stress_tests/soft_slab2.c | 81 + .../examples2d/stress_tests/soft_strips2.c | 71 + .../stress_tests/vertical_stacks2.c | 41 + c/testbed/examples2d/trimesh2.c | 61 + c/testbed/examples2d/utils/character.h | 111 + c/testbed/examples2d/utils/logo_mesh.h | 236 +++ c/testbed/examples2d/voxels2.c | 82 + c/testbed/examples3d/b3d_joint_grid.c | 60 + c/testbed/examples3d/b3d_junkyard.c | 114 ++ c/testbed/examples3d/b3d_large_pyramid.c | 45 + c/testbed/examples3d/b3d_large_world.c | 55 + c/testbed/examples3d/b3d_many_pyramids.c | 50 + c/testbed/examples3d/b3d_rain.c | 451 ++++ c/testbed/examples3d/b3d_trees.c | 121 ++ c/testbed/examples3d/b3d_trees_run100.c | 7 + c/testbed/examples3d/b3d_trees_run25.c | 7 + c/testbed/examples3d/b3d_trees_run50.c | 7 + c/testbed/examples3d/b3d_washer.c | 79 + c/testbed/examples3d/ccd3.c | 100 + c/testbed/examples3d/character_controller3.c | 169 ++ c/testbed/examples3d/collision_groups3.c | 53 + c/testbed/examples3d/compound3.c | 97 + c/testbed/examples3d/convex_decomposition3.c | 7 + c/testbed/examples3d/convex_polyhedron3.c | 58 + c/testbed/examples3d/damping3.c | 39 + .../examples3d/debug_add_remove_collider3.c | 41 + c/testbed/examples3d/debug_angular_limits3.c | 60 + c/testbed/examples3d/debug_articulations3.c | 70 + c/testbed/examples3d/debug_balls3.c | 40 + c/testbed/examples3d/debug_big_colliders3.c | 47 + c/testbed/examples3d/debug_boxes3.c | 42 + .../examples3d/debug_chain_high_mass_ratio3.c | 46 + .../examples3d/debug_cube_high_mass_ratio3.c | 51 + c/testbed/examples3d/debug_cylinder3.c | 36 + c/testbed/examples3d/debug_deserialize3.c | 50 + c/testbed/examples3d/debug_disabled3.c | 54 + .../examples3d/debug_dynamic_collider_add3.c | 72 + c/testbed/examples3d/debug_friction3.c | 39 + c/testbed/examples3d/debug_infinite_fall3.c | 40 + c/testbed/examples3d/debug_internal_edges3.c | 61 + c/testbed/examples3d/debug_long_chain3.c | 42 + .../examples3d/debug_multi_collider_body3.c | 41 + .../debug_multibody_ang_motor_pos3.c | 44 + c/testbed/examples3d/debug_pop3.c | 38 + c/testbed/examples3d/debug_prismatic3.c | 57 + c/testbed/examples3d/debug_rollback3.c | 59 + c/testbed/examples3d/debug_self_intersect3.c | 247 +++ .../examples3d/debug_shape_modification3.c | 116 ++ .../examples3d/debug_sleeping_kinematic3.c | 61 + .../examples3d/debug_thin_cube_on_mesh3.c | 43 + c/testbed/examples3d/debug_triangle3.c | 46 + c/testbed/examples3d/debug_trimesh3.c | 48 + c/testbed/examples3d/debug_two_cubes3.c | 34 + c/testbed/examples3d/domino3.c | 60 + c/testbed/examples3d/dynamic_trimesh3.c | 110 + c/testbed/examples3d/fountain3.c | 73 + c/testbed/examples3d/gyroscopic3.c | 50 + c/testbed/examples3d/heightfield3.c | 102 + c/testbed/examples3d/inverse_kinematics3.c | 75 + c/testbed/examples3d/joint_motor_position3.c | 60 + c/testbed/examples3d/joints3.c | 486 +++++ .../examples3d/joints3_run_impulse_joints.c | 7 + .../examples3d/joints3_run_multibody_joints.c | 7 + c/testbed/examples3d/keva3.c | 97 + c/testbed/examples3d/locked_rotations3.c | 55 + c/testbed/examples3d/mjcf3.c | 38 + c/testbed/examples3d/mujoco_menagerie3.c | 372 ++++ c/testbed/examples3d/newton_cradle3.c | 43 + c/testbed/examples3d/one_way_platforms3.c | 96 + c/testbed/examples3d/platform3.c | 72 + c/testbed/examples3d/primitives3.c | 81 + c/testbed/examples3d/restitution3.c | 41 + c/testbed/examples3d/rope_joints3.c | 94 + c/testbed/examples3d/sensor3.c | 84 + c/testbed/examples3d/soft_bodies3.c | 132 ++ c/testbed/examples3d/soft_cloth3.c | 88 + c/testbed/examples3d/soft_cloth_stress3.c | 144 ++ c/testbed/examples3d/soft_dress3.c | 206 ++ c/testbed/examples3d/soft_fem3.c | 132 ++ c/testbed/examples3d/soft_jelly3.c | 183 ++ c/testbed/examples3d/soft_joints3.c | 396 ++++ c/testbed/examples3d/soft_meshes3.c | 275 +++ c/testbed/examples3d/soft_pile3.c | 94 + c/testbed/examples3d/soft_plasticity3.c | 186 ++ c/testbed/examples3d/soft_surface3.c | 154 ++ c/testbed/examples3d/soft_tearing3.c | 217 ++ c/testbed/examples3d/soft_thin_features3.c | 240 +++ c/testbed/examples3d/soft_trimesh3.c | 165 ++ c/testbed/examples3d/spring_joints3.c | 52 + c/testbed/examples3d/stress_tests/balls3.c | 35 + c/testbed/examples3d/stress_tests/boxes3.c | 42 + c/testbed/examples3d/stress_tests/capsules3.c | 43 + c/testbed/examples3d/stress_tests/ccd3.c | 62 + c/testbed/examples3d/stress_tests/compound3.c | 48 + .../stress_tests/convex_polyhedron3.c | 65 + .../examples3d/stress_tests/heightfield3.c | 59 + .../examples3d/stress_tests/joint_ball3.c | 55 + .../examples3d/stress_tests/joint_fixed3.c | 57 + .../stress_tests/joint_prismatic3.c | 53 + .../examples3d/stress_tests/joint_revolute3.c | 60 + c/testbed/examples3d/stress_tests/keva3.c | 48 + .../stress_tests/many_kinematics3.c | 62 + .../examples3d/stress_tests/many_pyramids3.c | 40 + .../examples3d/stress_tests/many_sleep3.c | 39 + .../examples3d/stress_tests/many_static3.c | 35 + c/testbed/examples3d/stress_tests/pyramid3.c | 43 + c/testbed/examples3d/stress_tests/ragdolls3.c | 104 + c/testbed/examples3d/stress_tests/ray_cast3.c | 95 + c/testbed/examples3d/stress_tests/ropes3.c | 49 + .../examples3d/stress_tests/soft_blobs3.c | 87 + .../stress_tests/soft_cloth_drape3.c | 81 + .../stress_tests/soft_cloth_keva3.c | 70 + .../examples3d/stress_tests/soft_fem_beams3.c | 84 + .../examples3d/stress_tests/soft_jellies3.c | 72 + .../examples3d/stress_tests/soft_ropes3.c | 84 + .../examples3d/stress_tests/soft_slab3.c | 96 + c/testbed/examples3d/stress_tests/stacks3.c | 71 + c/testbed/examples3d/stress_tests/trimesh3.c | 71 + c/testbed/examples3d/trimesh3.c | 115 ++ c/testbed/examples3d/urdf3.c | 39 + c/testbed/examples3d/utils/character.h | 114 ++ c/testbed/examples3d/utils/files.h | 69 + c/testbed/examples3d/utils/obj.h | 128 ++ c/testbed/examples3d/vehicle_controller3.c | 105 + c/testbed/examples3d/vehicle_joints3.c | 130 ++ c/testbed/examples3d/voxels3.c | 177 ++ c/testbed/font_data.h.in | 5 + c/testbed/grab.c | 309 +++ c/testbed/grab.h | 18 + c/testbed/graphics.c | 816 ++++++++ c/testbed/graphics.h | 10 + c/testbed/gui.c | 928 +++++++++ c/testbed/headless.c | 222 ++ c/testbed/headless_main.c | 5 + c/testbed/registry2.c | 189 ++ c/testbed/registry3.c | 253 +++ c/testbed/testbed.c | 361 ++++ c/testbed/testbed.h | 130 ++ c/testbed/testbed_internal.h | 7 + c/testbed/tests/errors.c | 46 + c/testbed/tests/errors.cmake | 8 + c/testbed/tests/grab.c | 185 ++ c/testbed/tests/interactive.c | 165 ++ c/testbed/tests/soft_render.c | 188 ++ c/testbed/tests/threading.c | 122 ++ c/testbed/tests/ui.c | 105 + c/testbed/tools/README.md | 38 + c/testbed/tools/logo-mesh/Cargo.toml | 10 + c/testbed/tools/logo-mesh/src/main.rs | 37 + c/testbed/tools/step_benchmark.c | 112 + c/testbed/tools/timing-results.json | 194 ++ c/testbed/tools/timing-results.md | 43 + c/testbed/update_catalog.py | 29 + 244 files changed, 25014 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/c-bindings.yml create mode 100644 c/testbed/.clang-format create mode 100644 c/testbed/CMakeLists.txt create mode 100644 c/testbed/PORTING.md create mode 100644 c/testbed/README.md create mode 100644 c/testbed/coverage.json create mode 100644 c/testbed/example_math.h create mode 100644 c/testbed/examples2d/add_remove2.c create mode 100644 c/testbed/examples2d/b2d_compounds.c create mode 100644 c/testbed/examples2d/b2d_joint_grid.c create mode 100644 c/testbed/examples2d/b2d_junkyard.c create mode 100644 c/testbed/examples2d/b2d_large_pyramid.c create mode 100644 c/testbed/examples2d/b2d_many_pyramids.c create mode 100644 c/testbed/examples2d/b2d_rain.c create mode 100644 c/testbed/examples2d/b2d_smash.c create mode 100644 c/testbed/examples2d/b2d_spinner.c create mode 100644 c/testbed/examples2d/b2d_tumbler.c create mode 100644 c/testbed/examples2d/b2d_washer.c create mode 100644 c/testbed/examples2d/ccd2.c create mode 100644 c/testbed/examples2d/character_controller2.c create mode 100644 c/testbed/examples2d/collision_groups2.c create mode 100644 c/testbed/examples2d/convex_polygons2.c create mode 100644 c/testbed/examples2d/damping2.c create mode 100644 c/testbed/examples2d/debug_angular_limits2.c create mode 100644 c/testbed/examples2d/debug_box_ball2.c create mode 100644 c/testbed/examples2d/debug_compression2.c create mode 100644 c/testbed/examples2d/debug_intersection2.c create mode 100644 c/testbed/examples2d/debug_many_colliders2.c create mode 100644 c/testbed/examples2d/debug_self_intersect2.c create mode 100644 c/testbed/examples2d/debug_total_overlap2.c create mode 100644 c/testbed/examples2d/debug_vertical_column2.c create mode 100644 c/testbed/examples2d/drum2.c create mode 100644 c/testbed/examples2d/heightfield2.c create mode 100644 c/testbed/examples2d/inv_pyramid2.c create mode 100644 c/testbed/examples2d/inverse_kinematics2.c create mode 100644 c/testbed/examples2d/joint_motor_position2.c create mode 100644 c/testbed/examples2d/joints2.c create mode 100644 c/testbed/examples2d/locked_rotations2.c create mode 100644 c/testbed/examples2d/multi_pendulum2.c create mode 100644 c/testbed/examples2d/one_way_platforms2.c create mode 100644 c/testbed/examples2d/pin_slot_joint2.c create mode 100644 c/testbed/examples2d/platform2.c create mode 100644 c/testbed/examples2d/polyline2.c create mode 100644 c/testbed/examples2d/pyramid2.c create mode 100644 c/testbed/examples2d/restitution2.c create mode 100644 c/testbed/examples2d/rope_joints2.c create mode 100644 c/testbed/examples2d/s2d_arch.c create mode 100644 c/testbed/examples2d/s2d_ball_and_chain.c create mode 100644 c/testbed/examples2d/s2d_bridge.c create mode 100644 c/testbed/examples2d/s2d_card_house.c create mode 100644 c/testbed/examples2d/s2d_confined.c create mode 100644 c/testbed/examples2d/s2d_far_pyramid.c create mode 100644 c/testbed/examples2d/s2d_high_mass_ratio_1.c create mode 100644 c/testbed/examples2d/s2d_high_mass_ratio_2.c create mode 100644 c/testbed/examples2d/s2d_high_mass_ratio_3.c create mode 100644 c/testbed/examples2d/s2d_joint_grid.c create mode 100644 c/testbed/examples2d/s2d_pyramid.c create mode 100644 c/testbed/examples2d/sensor2.c create mode 100644 c/testbed/examples2d/soft_blobs2.c create mode 100644 c/testbed/examples2d/soft_bodies2.c create mode 100644 c/testbed/examples2d/soft_cutting2.c create mode 100644 c/testbed/examples2d/soft_fem2.c create mode 100644 c/testbed/examples2d/soft_force_tearing2.c create mode 100644 c/testbed/examples2d/soft_jelly2.c create mode 100644 c/testbed/examples2d/soft_joints2.c create mode 100644 c/testbed/examples2d/soft_letters2.c create mode 100644 c/testbed/examples2d/soft_pile2.c create mode 100644 c/testbed/examples2d/soft_plasticity2.c create mode 100644 c/testbed/examples2d/soft_stress2.c create mode 100644 c/testbed/examples2d/soft_surface2.c create mode 100644 c/testbed/examples2d/soft_tearing2.c create mode 100644 c/testbed/examples2d/soft_thin_features2.c create mode 100644 c/testbed/examples2d/stress_tests/balls2.c create mode 100644 c/testbed/examples2d/stress_tests/boxes2.c create mode 100644 c/testbed/examples2d/stress_tests/capsules2.c create mode 100644 c/testbed/examples2d/stress_tests/convex_polygons2.c create mode 100644 c/testbed/examples2d/stress_tests/heightfield2.c create mode 100644 c/testbed/examples2d/stress_tests/joint_ball2.c create mode 100644 c/testbed/examples2d/stress_tests/joint_fixed2.c create mode 100644 c/testbed/examples2d/stress_tests/joint_prismatic2.c create mode 100644 c/testbed/examples2d/stress_tests/large_pyramids2.c create mode 100644 c/testbed/examples2d/stress_tests/many_pyramids2.c create mode 100644 c/testbed/examples2d/stress_tests/pyramid2.c create mode 100644 c/testbed/examples2d/stress_tests/ragdolls2.c create mode 100644 c/testbed/examples2d/stress_tests/ropes2.c create mode 100644 c/testbed/examples2d/stress_tests/soft_blobs2.c create mode 100644 c/testbed/examples2d/stress_tests/soft_cloth_keva2.c create mode 100644 c/testbed/examples2d/stress_tests/soft_fem_beams2.c create mode 100644 c/testbed/examples2d/stress_tests/soft_jellies2.c create mode 100644 c/testbed/examples2d/stress_tests/soft_ropes2.c create mode 100644 c/testbed/examples2d/stress_tests/soft_slab2.c create mode 100644 c/testbed/examples2d/stress_tests/soft_strips2.c create mode 100644 c/testbed/examples2d/stress_tests/vertical_stacks2.c create mode 100644 c/testbed/examples2d/trimesh2.c create mode 100644 c/testbed/examples2d/utils/character.h create mode 100644 c/testbed/examples2d/utils/logo_mesh.h create mode 100644 c/testbed/examples2d/voxels2.c create mode 100644 c/testbed/examples3d/b3d_joint_grid.c create mode 100644 c/testbed/examples3d/b3d_junkyard.c create mode 100644 c/testbed/examples3d/b3d_large_pyramid.c create mode 100644 c/testbed/examples3d/b3d_large_world.c create mode 100644 c/testbed/examples3d/b3d_many_pyramids.c create mode 100644 c/testbed/examples3d/b3d_rain.c create mode 100644 c/testbed/examples3d/b3d_trees.c create mode 100644 c/testbed/examples3d/b3d_trees_run100.c create mode 100644 c/testbed/examples3d/b3d_trees_run25.c create mode 100644 c/testbed/examples3d/b3d_trees_run50.c create mode 100644 c/testbed/examples3d/b3d_washer.c create mode 100644 c/testbed/examples3d/ccd3.c create mode 100644 c/testbed/examples3d/character_controller3.c create mode 100644 c/testbed/examples3d/collision_groups3.c create mode 100644 c/testbed/examples3d/compound3.c create mode 100644 c/testbed/examples3d/convex_decomposition3.c create mode 100644 c/testbed/examples3d/convex_polyhedron3.c create mode 100644 c/testbed/examples3d/damping3.c create mode 100644 c/testbed/examples3d/debug_add_remove_collider3.c create mode 100644 c/testbed/examples3d/debug_angular_limits3.c create mode 100644 c/testbed/examples3d/debug_articulations3.c create mode 100644 c/testbed/examples3d/debug_balls3.c create mode 100644 c/testbed/examples3d/debug_big_colliders3.c create mode 100644 c/testbed/examples3d/debug_boxes3.c create mode 100644 c/testbed/examples3d/debug_chain_high_mass_ratio3.c create mode 100644 c/testbed/examples3d/debug_cube_high_mass_ratio3.c create mode 100644 c/testbed/examples3d/debug_cylinder3.c create mode 100644 c/testbed/examples3d/debug_deserialize3.c create mode 100644 c/testbed/examples3d/debug_disabled3.c create mode 100644 c/testbed/examples3d/debug_dynamic_collider_add3.c create mode 100644 c/testbed/examples3d/debug_friction3.c create mode 100644 c/testbed/examples3d/debug_infinite_fall3.c create mode 100644 c/testbed/examples3d/debug_internal_edges3.c create mode 100644 c/testbed/examples3d/debug_long_chain3.c create mode 100644 c/testbed/examples3d/debug_multi_collider_body3.c create mode 100644 c/testbed/examples3d/debug_multibody_ang_motor_pos3.c create mode 100644 c/testbed/examples3d/debug_pop3.c create mode 100644 c/testbed/examples3d/debug_prismatic3.c create mode 100644 c/testbed/examples3d/debug_rollback3.c create mode 100644 c/testbed/examples3d/debug_self_intersect3.c create mode 100644 c/testbed/examples3d/debug_shape_modification3.c create mode 100644 c/testbed/examples3d/debug_sleeping_kinematic3.c create mode 100644 c/testbed/examples3d/debug_thin_cube_on_mesh3.c create mode 100644 c/testbed/examples3d/debug_triangle3.c create mode 100644 c/testbed/examples3d/debug_trimesh3.c create mode 100644 c/testbed/examples3d/debug_two_cubes3.c create mode 100644 c/testbed/examples3d/domino3.c create mode 100644 c/testbed/examples3d/dynamic_trimesh3.c create mode 100644 c/testbed/examples3d/fountain3.c create mode 100644 c/testbed/examples3d/gyroscopic3.c create mode 100644 c/testbed/examples3d/heightfield3.c create mode 100644 c/testbed/examples3d/inverse_kinematics3.c create mode 100644 c/testbed/examples3d/joint_motor_position3.c create mode 100644 c/testbed/examples3d/joints3.c create mode 100644 c/testbed/examples3d/joints3_run_impulse_joints.c create mode 100644 c/testbed/examples3d/joints3_run_multibody_joints.c create mode 100644 c/testbed/examples3d/keva3.c create mode 100644 c/testbed/examples3d/locked_rotations3.c create mode 100644 c/testbed/examples3d/mjcf3.c create mode 100644 c/testbed/examples3d/mujoco_menagerie3.c create mode 100644 c/testbed/examples3d/newton_cradle3.c create mode 100644 c/testbed/examples3d/one_way_platforms3.c create mode 100644 c/testbed/examples3d/platform3.c create mode 100644 c/testbed/examples3d/primitives3.c create mode 100644 c/testbed/examples3d/restitution3.c create mode 100644 c/testbed/examples3d/rope_joints3.c create mode 100644 c/testbed/examples3d/sensor3.c create mode 100644 c/testbed/examples3d/soft_bodies3.c create mode 100644 c/testbed/examples3d/soft_cloth3.c create mode 100644 c/testbed/examples3d/soft_cloth_stress3.c create mode 100644 c/testbed/examples3d/soft_dress3.c create mode 100644 c/testbed/examples3d/soft_fem3.c create mode 100644 c/testbed/examples3d/soft_jelly3.c create mode 100644 c/testbed/examples3d/soft_joints3.c create mode 100644 c/testbed/examples3d/soft_meshes3.c create mode 100644 c/testbed/examples3d/soft_pile3.c create mode 100644 c/testbed/examples3d/soft_plasticity3.c create mode 100644 c/testbed/examples3d/soft_surface3.c create mode 100644 c/testbed/examples3d/soft_tearing3.c create mode 100644 c/testbed/examples3d/soft_thin_features3.c create mode 100644 c/testbed/examples3d/soft_trimesh3.c create mode 100644 c/testbed/examples3d/spring_joints3.c create mode 100644 c/testbed/examples3d/stress_tests/balls3.c create mode 100644 c/testbed/examples3d/stress_tests/boxes3.c create mode 100644 c/testbed/examples3d/stress_tests/capsules3.c create mode 100644 c/testbed/examples3d/stress_tests/ccd3.c create mode 100644 c/testbed/examples3d/stress_tests/compound3.c create mode 100644 c/testbed/examples3d/stress_tests/convex_polyhedron3.c create mode 100644 c/testbed/examples3d/stress_tests/heightfield3.c create mode 100644 c/testbed/examples3d/stress_tests/joint_ball3.c create mode 100644 c/testbed/examples3d/stress_tests/joint_fixed3.c create mode 100644 c/testbed/examples3d/stress_tests/joint_prismatic3.c create mode 100644 c/testbed/examples3d/stress_tests/joint_revolute3.c create mode 100644 c/testbed/examples3d/stress_tests/keva3.c create mode 100644 c/testbed/examples3d/stress_tests/many_kinematics3.c create mode 100644 c/testbed/examples3d/stress_tests/many_pyramids3.c create mode 100644 c/testbed/examples3d/stress_tests/many_sleep3.c create mode 100644 c/testbed/examples3d/stress_tests/many_static3.c create mode 100644 c/testbed/examples3d/stress_tests/pyramid3.c create mode 100644 c/testbed/examples3d/stress_tests/ragdolls3.c create mode 100644 c/testbed/examples3d/stress_tests/ray_cast3.c create mode 100644 c/testbed/examples3d/stress_tests/ropes3.c create mode 100644 c/testbed/examples3d/stress_tests/soft_blobs3.c create mode 100644 c/testbed/examples3d/stress_tests/soft_cloth_drape3.c create mode 100644 c/testbed/examples3d/stress_tests/soft_cloth_keva3.c create mode 100644 c/testbed/examples3d/stress_tests/soft_fem_beams3.c create mode 100644 c/testbed/examples3d/stress_tests/soft_jellies3.c create mode 100644 c/testbed/examples3d/stress_tests/soft_ropes3.c create mode 100644 c/testbed/examples3d/stress_tests/soft_slab3.c create mode 100644 c/testbed/examples3d/stress_tests/stacks3.c create mode 100644 c/testbed/examples3d/stress_tests/trimesh3.c create mode 100644 c/testbed/examples3d/trimesh3.c create mode 100644 c/testbed/examples3d/urdf3.c create mode 100644 c/testbed/examples3d/utils/character.h create mode 100644 c/testbed/examples3d/utils/files.h create mode 100644 c/testbed/examples3d/utils/obj.h create mode 100644 c/testbed/examples3d/vehicle_controller3.c create mode 100644 c/testbed/examples3d/vehicle_joints3.c create mode 100644 c/testbed/examples3d/voxels3.c create mode 100644 c/testbed/font_data.h.in create mode 100644 c/testbed/grab.c create mode 100644 c/testbed/grab.h create mode 100644 c/testbed/graphics.c create mode 100644 c/testbed/graphics.h create mode 100644 c/testbed/gui.c create mode 100644 c/testbed/headless.c create mode 100644 c/testbed/headless_main.c create mode 100644 c/testbed/registry2.c create mode 100644 c/testbed/registry3.c create mode 100644 c/testbed/testbed.c create mode 100644 c/testbed/testbed.h create mode 100644 c/testbed/testbed_internal.h create mode 100644 c/testbed/tests/errors.c create mode 100644 c/testbed/tests/errors.cmake create mode 100644 c/testbed/tests/grab.c create mode 100644 c/testbed/tests/interactive.c create mode 100644 c/testbed/tests/soft_render.c create mode 100644 c/testbed/tests/threading.c create mode 100644 c/testbed/tests/ui.c create mode 100644 c/testbed/tools/README.md create mode 100644 c/testbed/tools/logo-mesh/Cargo.toml create mode 100644 c/testbed/tools/logo-mesh/src/main.rs create mode 100644 c/testbed/tools/step_benchmark.c create mode 100644 c/testbed/tools/timing-results.json create mode 100644 c/testbed/tools/timing-results.md create mode 100644 c/testbed/update_catalog.py diff --git a/.github/workflows/c-bindings.yml b/.github/workflows/c-bindings.yml new file mode 100644 index 000000000..b138e3647 --- /dev/null +++ b/.github/workflows/c-bindings.yml @@ -0,0 +1,63 @@ +name: C bindings +on: + push: + paths: ['c/**', 'src/**', 'crates/rapier*/**', 'Cargo.toml', '.github/workflows/c-bindings.yml'] + pull_request: + paths: ['c/**', 'src/**', 'crates/rapier*/**', 'Cargo.toml', '.github/workflows/c-bindings.yml'] + workflow_dispatch: +jobs: + native: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + variant: + - {dimension: 2, parallel: "OFF"} + - {dimension: 3, parallel: "ON"} + precision: [32, 64] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Linux graphics build prerequisites + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libgl1-mesa-dev + - name: Configure + run: cmake -S c -B build/c -DRAPIER_DIMENSION=${{ matrix.variant.dimension }} -DRAPIER_PRECISION=${{ matrix.precision }} -DRAPIER_ENABLE_PARALLEL=${{ matrix.variant.parallel }} -DRAPIER_BUILD_TESTBED=ON -DRAPIER_PROFILE=debug -DRAPIER_CARGO_TARGET_DIR=${{ github.workspace }}/target + - name: Build + run: cmake --build build/c --config Release --parallel 2 + - name: Test + run: ctest --test-dir build/c -C Release --output-on-failure + - name: Install + run: cmake --install build/c --config Release --prefix build/install + - name: Consume installed package + run: | + cmake -S c/tests/consumer -B build/consumer -DCMAKE_PREFIX_PATH=${{ github.workspace }}/build/install + cmake --build build/consumer --config Release + ctest --test-dir build/consumer -C Release --output-on-failure + abi-and-features: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + - run: cargo install cbindgen --version 0.29.4 --locked + - name: Header matches source + run: | + sh c/tools/generate-header.sh + git diff --exit-code -- c/include/rapier.h + - run: cargo fmt -p rapier-c-macros -p rapier3d-ffi -- --check + - run: cargo test -p rapier-c-macros -p rapier2d-ffi -p rapier3d-ffi -p rapier2d-f64-ffi -p rapier3d-f64-ffi + - run: cargo clippy -p rapier-c-macros -p rapier2d-ffi -p rapier3d-ffi -p rapier2d-f64-ffi -p rapier3d-f64-ffi --no-deps -- -D warnings + - name: Every ABI symbol and both linkage modes + run: python3 c/tools/test-native.py + - name: Optional feature combinations + run: cargo check -p rapier2d-ffi -p rapier3d-ffi -p rapier2d-f64-ffi -p rapier3d-f64-ffi --features parallel,fem,enhanced-determinism + - name: Eight-lane SIMD with parallel execution + run: | + cmake -S c -B build/simd8 -DRAPIER_BUILD_TESTBED=ON -DRAPIER_TESTBED_GRAPHICS=OFF -DRAPIER_PROFILE=debug -DRAPIER_SIMD_LANES=8 -DRAPIER_ENABLE_PARALLEL=ON -DRAPIER_CARGO_TARGET_DIR=${{ github.workspace }}/target + cmake --build build/simd8 --parallel 2 + ctest --test-dir build/simd8 --output-on-failure diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt index de917b986..2be028c11 100644 --- a/c/CMakeLists.txt +++ b/c/CMakeLists.txt @@ -16,12 +16,14 @@ set_property(CACHE RAPIER_PRECISION PROPERTY STRINGS 32 64) option(RAPIER_SHARED "Link the shared library (otherwise static)" ON) option(RAPIER_BUILD_TESTS "Build C/C++ integration tests" ON) option(RAPIER_BUILD_EXAMPLES "Build the falling-ball example" ON) +option(RAPIER_BUILD_TESTBED "Build the C example testbed" OFF) +option(RAPIER_TESTBED_GRAPHICS "Include the vendored raylib viewer" ON) set(RAPIER_PROFILE "release" CACHE STRING "Cargo release or debug profile") set_property(CACHE RAPIER_PROFILE PROPERTY STRINGS release debug) set(RAPIER_FEATURES "" CACHE STRING "Additional Cargo features: enhanced-determinism,fem,profiler,robotics") # Accept the earlier parallel/simd8 feature spelling as defaults, while the # explicit options below determine the final effective feature set. -set(RAPIER_DEFAULT_PARALLEL OFF) +set(RAPIER_DEFAULT_PARALLEL "${RAPIER_BUILD_TESTBED}") set(RAPIER_DEFAULT_SIMD_LANES "4") if(RAPIER_FEATURES MATCHES "(^|,)parallel(,|$)") set(RAPIER_DEFAULT_PARALLEL ON) @@ -195,3 +197,6 @@ configure_package_config_file(cmake/RapierConfig.cmake.in "${CMAKE_CURRENT_BINAR write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/RapierConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY ExactVersion) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/RapierConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/RapierConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Rapier") +if(RAPIER_BUILD_TESTBED) + add_subdirectory(testbed) +endif() diff --git a/c/testbed/.clang-format b/c/testbed/.clang-format new file mode 100644 index 000000000..2ba613a80 --- /dev/null +++ b/c/testbed/.clang-format @@ -0,0 +1,16 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +ColumnLimit: 100 +PointerAlignment: Right +AllowShortBlocksOnASingleLine: Never +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +InsertBraces: true +BreakBeforeBraces: Attach +KeepEmptyLinesAtTheStartOfBlocks: false +SeparateDefinitionBlocks: Always +SortIncludes: Never + +TypenameMacros: [RAPIER_TYPE] diff --git a/c/testbed/CMakeLists.txt b/c/testbed/CMakeLists.txt new file mode 100644 index 000000000..a9c84a9df --- /dev/null +++ b/c/testbed/CMakeLists.txt @@ -0,0 +1,126 @@ +# The headless runner shares exactly the same C scene sources and callbacks. +file(GLOB_RECURSE TB_SCENES CONFIGURE_DEPENDS "examples${RAPIER_DIMENSION}d/*.c") +add_library(rapier_testbed_core STATIC testbed.c headless.c grab.c "registry${RAPIER_DIMENSION}.c" ${TB_SCENES}) +target_link_libraries(rapier_testbed_core PUBLIC Rapier::rapier) +target_include_directories(rapier_testbed_core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") +target_compile_features(rapier_testbed_core PUBLIC c_std_11) +target_compile_definitions(rapier_testbed_core PUBLIC TB_ASSET_ROOT="${RAPIER_ROOT}/assets") +if(NOT MSVC) + target_link_libraries(rapier_testbed_core PUBLIC m) + target_compile_options(rapier_testbed_core PRIVATE -Wall -Wextra) +endif() +rapier_executable(rapier_testbed_headless headless_main.c) +target_link_libraries(rapier_testbed_headless PRIVATE rapier_testbed_core) +# Explicit opt-in target; performance checks are not pass/fail timing tests. +rapier_executable(rapier_testbed_step_benchmark tools/step_benchmark.c) +set_target_properties(rapier_testbed_step_benchmark PROPERTIES EXCLUDE_FROM_ALL TRUE) +target_link_libraries(rapier_testbed_step_benchmark PRIVATE rapier_testbed_core) +if(RAPIER_TESTBED_GRAPHICS) + # Normal variables stay in this directory: don't alter the parent project's options. + set(BUILD_EXAMPLES OFF) + set(BUILD_SHARED_LIBS OFF) + # raylib 6.0's custom CMake flag parser treats even `#define FLAG 0` as ON. + # Use config.h defaults and explicit numeric overrides to retain automatic + # buffer swaps, input polling, and frame timing in EndDrawing(). + set(CUSTOMIZE_BUILD OFF) + set(SUPPORT_MODULE_RAUDIO OFF CACHE BOOL "" FORCE) + set(USE_AUDIO OFF CACHE BOOL "" FORCE) + set(USE_EXTERNAL_GLFW OFF CACHE STRING "" FORCE) + set(GLFW_BUILD_WAYLAND OFF CACHE BOOL "" FORCE) + set(GLFW_BUILD_X11 ON CACHE BOOL "" FORCE) + set(OPENGL_VERSION "3.3" CACHE STRING "" FORCE) + add_subdirectory(vendor/raylib EXCLUDE_FROM_ALL) + target_compile_definitions(raylib PRIVATE + SUPPORT_MODULE_RAUDIO=0 SUPPORT_CUSTOM_FRAME_CONTROL=0 SUPPORT_BUSY_WAIT_LOOP=0) + # Version-matched C wrappers and renderer; the testbed itself stays C11. + set(TB_IMGUI_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/cimgui/imgui") + add_library(rapier_testbed_imgui STATIC + vendor/cimgui/cimgui.cpp vendor/rlImGui/rlImGui.cpp + "${TB_IMGUI_DIR}/imgui.cpp" "${TB_IMGUI_DIR}/imgui_draw.cpp" + "${TB_IMGUI_DIR}/imgui_widgets.cpp" "${TB_IMGUI_DIR}/imgui_tables.cpp" + "${TB_IMGUI_DIR}/imgui_demo.cpp") + target_compile_features(rapier_testbed_imgui PRIVATE cxx_std_17) + target_compile_definitions(rapier_testbed_imgui PUBLIC NO_FONT_AWESOME CIMGUI_NO_EXPORT) + target_include_directories(rapier_testbed_imgui SYSTEM PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/cimgui" + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/rlImGui" "${TB_IMGUI_DIR}") + target_link_libraries(rapier_testbed_imgui PUBLIC raylib) + # Embed the font with CMake alone: no runtime asset path, font installation, + # generator dependency, or network access is required by consumers. + set(TB_FONT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vendor/fira/FiraSans-Regular.ttf") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${TB_FONT_FILE}") + file(READ "${TB_FONT_FILE}" TB_FONT_HEX HEX) + string(REGEX REPLACE "(..)" "0x\\1," TB_FONT_BYTES "${TB_FONT_HEX}") + # Bound source line length for compilers such as MSVC. + string(REPEAT "0x[0-9a-f][0-9a-f]," 16 TB_FONT_ROW) + string(REGEX REPLACE "(${TB_FONT_ROW})" "\\1\n" TB_FONT_BYTES "${TB_FONT_BYTES}") + configure_file(font_data.h.in "${CMAKE_CURRENT_BINARY_DIR}/font_data.h" @ONLY) + unset(TB_FONT_HEX) + unset(TB_FONT_BYTES) + unset(TB_FONT_ROW) + rapier_executable(rapier_testbed gui.c) + target_include_directories(rapier_testbed PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + add_custom_command(TARGET rapier_testbed POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/NOTICES.txt" + "$/THIRD_PARTY_NOTICES.txt" + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/licenses" + "$/licenses" + COMMAND "${CMAKE_COMMAND}" -E make_directory + "$/licenses/glfw-mingw" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/raylib/src/external/glfw/deps/mingw/dinput.h" + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/raylib/src/external/glfw/deps/mingw/xinput.h" + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/raylib/src/external/glfw/deps/mingw/_mingw_dxhelper.h" + "$/licenses/glfw-mingw" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/raylib/LICENSE" + "$/raylib-LICENSE.txt" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/raylib/src/external/glfw/LICENSE.md" + "$/GLFW-LICENSE.txt" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/fira/LICENSE" + "$/FiraSans-LICENSE.txt" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/cimgui/LICENSE" + "$/cimgui-LICENSE.txt" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/cimgui/imgui/LICENSE.txt" + "$/DearImGui-LICENSE.txt" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/vendor/rlImGui/LICENSE" + "$/rlImGui-LICENSE.txt" + VERBATIM) + target_sources(rapier_testbed PRIVATE graphics.c) + target_link_libraries(rapier_testbed PRIVATE rapier_testbed_core rapier_testbed_imgui) + if(RAPIER_BUILD_TESTS) + rapier_executable(rapier_testbed_ui_test tests/ui.c) + target_sources(rapier_testbed_ui_test PRIVATE graphics.c) + target_include_directories(rapier_testbed_ui_test PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + target_link_libraries(rapier_testbed_ui_test PRIVATE rapier_testbed_core rapier_testbed_imgui) + add_test(NAME testbed_ui_input COMMAND rapier_testbed_ui_test) + rapier_executable(rapier_testbed_soft_render_test tests/soft_render.c) + target_link_libraries(rapier_testbed_soft_render_test PRIVATE rapier_testbed_core raylib) + add_test(NAME testbed_soft_render COMMAND rapier_testbed_soft_render_test) + set_tests_properties(testbed_soft_render PROPERTIES TIMEOUT 30) + endif() +endif() +if(RAPIER_BUILD_TESTS) + rapier_executable(rapier_testbed_grab_test tests/grab.c) + target_link_libraries(rapier_testbed_grab_test PRIVATE rapier_testbed_core) + add_test(NAME testbed_mouse_grab COMMAND rapier_testbed_grab_test) + rapier_executable(rapier_testbed_interactive_test tests/interactive.c) + target_link_libraries(rapier_testbed_interactive_test PRIVATE rapier_testbed_core) + add_test(NAME testbed_interactions COMMAND rapier_testbed_interactive_test) + rapier_executable(rapier_testbed_threading_test tests/threading.c) + target_link_libraries(rapier_testbed_threading_test PRIVATE rapier_testbed_core) + add_test(NAME testbed_threading COMMAND rapier_testbed_threading_test) + rapier_executable(rapier_testbed_error_test tests/errors.c) + target_link_libraries(rapier_testbed_error_test PRIVATE rapier_testbed_core) + add_test(NAME testbed_error_reporting COMMAND "${CMAKE_COMMAND}" + "-DEXECUTABLE=$" -P "${CMAKE_CURRENT_SOURCE_DIR}/tests/errors.cmake") + add_test(NAME testbed_rigid COMMAND rapier_testbed_headless --example "restitution${RAPIER_DIMENSION}" --steps 120 --no-sleep) + add_test(NAME testbed_dynamic COMMAND rapier_testbed_headless --example $,add_remove2,fountain3> --steps 120 --no-sleep) +endif() diff --git a/c/testbed/PORTING.md b/c/testbed/PORTING.md new file mode 100644 index 000000000..4a3649c7b --- /dev/null +++ b/c/testbed/PORTING.md @@ -0,0 +1,72 @@ +# Writing C examples + +Treat the matching Rust file as the structure of the C example. Preserve its +construction order, variable meanings, parameters, comments, and distinct examples. +Rapier instance methods separate the receiver and method with an underscore, +for example `r3RigidBody_SetTranslation`. Constructors such as +`r3DynamicRigidBodyDesc` put the qualifier first. Creation and destruction use +`r3NewWorld` and `r3FreeWorld`; simple math values use `r3Vector` and `r3Pose`. + +Use camelCase for C functions, fields, parameters, and local variables, including +the testbed helpers. Keep types in PascalCase and macros in UPPER_SNAKE_CASE. +Keep the corresponding file and scene names. Do not combine separate Rust blocks +into loops or conditional expressions merely to shorten the C code. + +Use the public Rapier C API for world creation, descriptions, insertion, queries, +joints, soft bodies, and simulation callbacks. If a Rust operation is missing +from the bindings, add its native counterpart to the bindings instead of hiding +it in a testbed helper. Translate Rust's `PhysicsWorld::insert(body, collider)` +into `r3InsertRigidBody(world, &body)`, then +`r3InsertCollider(bodyHandle, &collider)`. Keep these calls directly in +the example. The `r3*ColliderDesc` functions correspond to `ColliderBuilder` +constructors. Description constructors return POD values directly. + +The testbed provides one-frame rendering, camera, colors, input, UI settings, +and run/pause controls. Each example owns its world and its simulation loop: + +```c +tbSetWorld(testbed, world); +while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } +} +r3FreeWorld(world); +``` + +`tbSetWorld` borrows the world. `tbRenderFrame` renders and processes input; +it never advances physics. It returns false on close, restart, or scene switch. +The world pointer is passed by address so restoring a snapshot updates the +example's local pointer. Code outside `tbSimulating` runs on paused frames too. +Keep per-frame logic in the same place relative to stepping as in the Rust source. +Sensor examples own their event collectors and process events immediately after +stepping. Animation state stays in ordinary local variables; there are no +before/after-step callbacks or heap-allocated callback state. Examples with local +state that snapshots cannot restore set `snapshotSupported` to zero. + +Show ownership in the example. POD descriptions need no explicit cleanup; their +array views borrow data that must remain valid through the build or insertion call. +Inserted objects belong to their world and are accessed through handles. +Pass the world directly; do not cache component aliases or add physics wrappers. Shared +shapes retained by the example must be freed when no longer needed. The optional +`--no-sleep` override is explicit in the example's description setup. + +Call the dimension-specific `r2*` or `r3*` functions directly without wrapping them +in testbed checking macros. +The example dispatcher installs a thread-local error handler for the duration of +the example. Unexpected API errors print the scene and diagnostic and terminate +the process before invalid outputs can be used. Viewer operations handle their +recoverable errors separately. The C API reports failures through status returns +or `LastStatus()` by default; applications may choose their own error policy. Never longjmp or throw +from an error handler through Rust frames. + +Use named locals for positions, handles, and material parameters. Separate +construction, configuration, insertion, and release. Use `rapier_math.h` for the +public math types' value constructors and arithmetic. Keep dimension-specific +source files dimension-specific. Helpers implementing a particular example's +algorithm are appropriate when the Rust example has the same helper; general +physics convenience wrappers do not belong in the viewer. + +Format authored C and headers with the adjacent `.clang-format`; do not reformat +vendored code. `examples3d/primitives3.c`, `examples3d/compound3.c`, +`examples3d/soft_bodies3.c`, and `examples2d/add_remove2.c` show the intended layout. diff --git a/c/testbed/README.md b/c/testbed/README.md new file mode 100644 index 000000000..08a14aac1 --- /dev/null +++ b/c/testbed/README.md @@ -0,0 +1,151 @@ +# Rapier C testbed + +The viewer uses raylib for graphics and Dear ImGui through cimgui for its C UI. +Sources and Fira Sans Regular are vendored; configuring and building never downloads +graphics dependencies. A C/C++ compiler, Cargo, and CMake 3.22+ are required. +macOS uses system frameworks. Linux requires the usual X11/OpenGL development +packages needed to compile raylib's bundled GLFW. See [vendor/README.md](vendor/README.md) +for exact versions and licenses. + +From the repository root: + +```sh +cmake -S c -B build/c3 -DRAPIER_BUILD_TESTBED=ON -DRAPIER_DIMENSION=3 \ + -DRAPIER_PROFILE=release -DCMAKE_BUILD_TYPE=Release +cmake --build build/c3 --config Release --parallel +build/c3/testbed/rapier_testbed --example restitution3 +``` + +With a multi-configuration generator, the executable is under `testbed/Release/`. +Use a separate build directory and `-DRAPIER_DIMENSION=2` for 2D; use +`-DRAPIER_PRECISION=64` for double precision. The visible **Physics: Release/Debug** +label is read from the loaded Rapier library's `r3BuildProfile` function. +`RAPIER_PROFILE` selects Cargo's profile; `CMAKE_BUILD_TYPE` / `--config` selects +the C/C++ build mode. They are independent. The workspace development profile +currently has `opt-level=1`; the label still correctly identifies it as Debug. + +The header also reports **SIMD lanes**, **parallel support**, and the actual worker +count from the loaded library. Build options are `-DRAPIER_ENABLE_PARALLEL=ON|OFF` +(default ON for the testbed) and `-DRAPIER_SIMD_LANES=4|8` (default 4). This branch +always uses SIMD; eight lanes require f32 and cannot be combined with +`enhanced-determinism`. See [binding build options](../README.md#cmake-and-installation). + +Choose workers under **Settings > Execution**, then Apply, or pass `--threads N` +to either executable. `0` selects automatic sizing, `1` uses one worker, and the +viewer accepts explicit counts through 256. The UI shows the resolved worker +count. Changes preserve the running scene and persist through restart, scene +switches, and snapshot restore. Builds without parallel support run serially and +explain how to enable the control. + +## Controls + +- Examples: grouped selection and search. Prev/Next cycle through matching available + demos, wrapping at the ends. Scenes requiring disabled build features are hidden. +- Settings: worker count, timestep, gravity, solver iterations, CCD substeps, and scene settings. + Scene-specific settings and the sleeping option apply on Restart. +- Performance: native physics time per step (the same counter as the Rust UI), + simulation time per rendered frame with its step count, and draw CPU time. + The viewer advances one step per rendered frame, like the Rust viewer. + Simulation includes the example's per-frame logic; compare the **ms/step** value with Rust. Draw CPU excludes the UI and GPU execution. +- Debug: surfaces, wireframes, body axes, contacts, joints, soft constraints/stress. +- T: play/pause. S: one step. R: restart. F: frame the simulation. +- Left drag: pull a dynamic object with a spring joint, including articulated links + and individual soft-body particles. Release removes the temporary joint and anchor. + Dragging needs the simulation running (or single-stepping) to move the object. +- Right drag: arc-ball orbit about the camera target in 3D, or pan in 2D. + Shift + right drag or middle drag: pan in 3D. Mouse wheel: zoom. +- Deformable polylines use three-pixel ribbons. Sensor colliders are translucent, + sorted behind-to-front with depth writes disabled during their render pass. +- Arrows and Enter: example controls. Space/Right Ctrl: character up/down; Shift: slow movement. + Hold C: cut in the cutting demo. Space adds a voxel in the 3D voxel demo; Left Shift + Space removes it. Text entry and focused UI widgets capture + keyboard input, and UI interactions do not control the scene camera. +- Save/Restore: in-memory physics snapshots. Disabled for scenes with local animation state + because physics snapshots do not serialize those C variables. + +The UI uses automatic layout and scrollable panels, keyboard navigation, and a +TrueType font rasterized at the display's framebuffer density. `--ui-tab Settings` +(or `Examples`, `Performance`, `Debug`) chooses the initial tab. + +## Headless and smoke runs + +```sh +build/c3/testbed/rapier_testbed_headless --list +build/c3/testbed/rapier_testbed_headless --example restitution3 --steps 120 --no-sleep --threads 1 +build/c3/testbed/rapier_testbed --example restitution3 --frames 90 --screenshot preview.png +ctest --test-dir build/c3 -C Release --output-on-failure +``` + +The `testbed_ui_input` CTest case exercises real ImGui text-input capture and +simulation shortcut routing, example navigation, and orbit-camera geometry using a +null renderer; it needs no display or GPU. `testbed_mouse_grab` checks rigid and soft +spring dragging, fixed/sensor exclusion, temporary-cluster cleanup, and deletion of +a grabbed object. +The C ABI test checks the library-reported profile and execution features against +CMake's options. `testbed_threading` verifies live worker changes and persistence +across restart, scene switches, and snapshot restore, including serial builds. +[Manual C-versus-Rust timing checks](tools/README.md) replay identical no-sleep +snapshots with both APIs and compare the final poses and velocities exactly. +See the [Keva timing investigation](tools/timing-results.md) for measured results. + +Configure `-DRAPIER_TESTBED_GRAPHICS=OFF` to build only the headless runner without +raylib, ImGui, or display requirements. Viewer and headless runner use the same C +scene sources. `--assets PATH` overrides the repository asset directory. + +## Example source + +Each scene uses the Rapier C API directly, following its matching Rust example. +Each example owns its render loop, physics stepping, events, and local animation +state. The viewer renders one frame and processes input when the example calls +`tbRenderFrame`; it does not wrap physics construction or stepping. See [Writing C examples](PORTING.md) for the correspondence +and ownership conventions. + +## Port coverage + +All **202 Rust catalog entries** have matching C ports: **88 in 2D and 114 in 3D**. +`coverage.json` records the correspondence; `update_catalog.py` regenerates the +registries from the Rust catalog and C sources. Compile-time feature requirements +remain explicit in the example picker. +`--all` reports passed, failed, and unavailable counts separately; unavailable +scenes are not validation passes. The UI covers the controls described above; +advanced Rust profiler and internal physics-counter panels are not yet ported. + + +## Optional examples + +Enable the native FEM solver with `-DRAPIER_FEATURES=fem`. For 3D/f32, use +`-DRAPIER_FEATURES=fem,robotics` to include the URDF, MJCF, and MuJoCo Menagerie +examples too. Robotics is limited to 3D/f32 because the Rust loader crates have +that same restriction. These features add Rust dependencies through Cargo, not +extra C dependency installation steps. + +The URDF and Cassie MJCF examples use the repository's `assets/3d` files. +Menagerie discovers `/scene*.xml` in `../mujoco_menagerie`; set +`RAPIER_MENAGERIE_DIR` to use another checkout. It exposes model and keyframe +pickers, both joint representations, collision and spring switches, and live +actuator strength. Render meshes retain their colors, UVs, smooth normals, +textures, and material parameters; the lightweight raylib lighting differs +from the Rust viewer's rendering. + +The deserialization debug example requires `snapshotN.bincode` files in +`RAPIER_SNAPSHOT_DIR` (the Rust example's original directory is the fallback). +These are the legacy rigid-state files from the identical Rust build, not the +C binding's tagged whole-world snapshots. Missing or incompatible files report +an error; they do not count as a validated scene. + +OBJ models are read by a small example utility. The 2D logo's checked-in mesh +uses the Rust example's SVG tessellator, so there is no SVG dependency at runtime. +To regenerate it after editing the source logo: + +```sh +cargo run -p rapier-c-logo-mesh > c/testbed/examples2d/utils/logo_mesh.h +``` + +`testbed_interactions` drives the actual C example loops with synthetic input: +IK target convergence, switching from kinematic to PID control, and 3D voxel +addition/removal. It runs without a graphics context. + +The `testbed_soft_render` regression test traverses and draws the soft-mesh +geometry with recorded draw calls, without a window or GPU. It covers the +cluster/skin demos, bounds cache allocations, and checks that back-face culling +stays disabled until raylib flushes queued mesh triangles. All mesh surfaces are +rendered two-sided. diff --git a/c/testbed/coverage.json b/c/testbed/coverage.json new file mode 100644 index 000000000..da33efcf5 --- /dev/null +++ b/c/testbed/coverage.json @@ -0,0 +1,1820 @@ +[ + { + "id": "add_remove2", + "dimension": 2, + "group": "Collisions", + "name": "Add remove", + "rust": "examples2d/add_remove2.rs", + "c": "examples2d/add_remove2.c", + "feature": null + }, + { + "id": "drum2", + "dimension": 2, + "group": "Collisions", + "name": "Drum", + "rust": "examples2d/drum2.rs", + "c": "examples2d/drum2.c", + "feature": null + }, + { + "id": "inv_pyramid2", + "dimension": 2, + "group": "Collisions", + "name": "Inv pyramid", + "rust": "examples2d/inv_pyramid2.rs", + "c": "examples2d/inv_pyramid2.c", + "feature": null + }, + { + "id": "platform2", + "dimension": 2, + "group": "Collisions", + "name": "Platform", + "rust": "examples2d/platform2.rs", + "c": "examples2d/platform2.c", + "feature": null + }, + { + "id": "pyramid2", + "dimension": 2, + "group": "Collisions", + "name": "Pyramid", + "rust": "examples2d/pyramid2.rs", + "c": "examples2d/pyramid2.c", + "feature": null + }, + { + "id": "sensor2", + "dimension": 2, + "group": "Collisions", + "name": "Sensor", + "rust": "examples2d/sensor2.rs", + "c": "examples2d/sensor2.c", + "feature": null + }, + { + "id": "convex_polygons2", + "dimension": 2, + "group": "Collisions", + "name": "Convex polygons", + "rust": "examples2d/convex_polygons2.rs", + "c": "examples2d/convex_polygons2.c", + "feature": null + }, + { + "id": "heightfield2", + "dimension": 2, + "group": "Collisions", + "name": "Heightfield", + "rust": "examples2d/heightfield2.rs", + "c": "examples2d/heightfield2.c", + "feature": null + }, + { + "id": "polyline2", + "dimension": 2, + "group": "Collisions", + "name": "Polyline", + "rust": "examples2d/polyline2.rs", + "c": "examples2d/polyline2.c", + "feature": null + }, + { + "id": "trimesh2", + "dimension": 2, + "group": "Collisions", + "name": "Trimesh", + "rust": "examples2d/trimesh2.rs", + "c": "examples2d/trimesh2.c", + "feature": null + }, + { + "id": "voxels2", + "dimension": 2, + "group": "Collisions", + "name": "Voxels", + "rust": "examples2d/voxels2.rs", + "c": "examples2d/voxels2.c", + "feature": null + }, + { + "id": "collision_groups2", + "dimension": 2, + "group": "Collisions", + "name": "Collision groups", + "rust": "examples2d/collision_groups2.rs", + "c": "examples2d/collision_groups2.c", + "feature": null + }, + { + "id": "one_way_platforms2", + "dimension": 2, + "group": "Collisions", + "name": "One-way platforms", + "rust": "examples2d/one_way_platforms2.rs", + "c": "examples2d/one_way_platforms2.c", + "feature": null + }, + { + "id": "locked_rotations2", + "dimension": 2, + "group": "Dynamics", + "name": "Locked rotations", + "rust": "examples2d/locked_rotations2.rs", + "c": "examples2d/locked_rotations2.c", + "feature": null + }, + { + "id": "restitution2", + "dimension": 2, + "group": "Dynamics", + "name": "Restitution", + "rust": "examples2d/restitution2.rs", + "c": "examples2d/restitution2.c", + "feature": null + }, + { + "id": "damping2", + "dimension": 2, + "group": "Dynamics", + "name": "Damping", + "rust": "examples2d/damping2.rs", + "c": "examples2d/damping2.c", + "feature": null + }, + { + "id": "ccd2", + "dimension": 2, + "group": "Dynamics", + "name": "CCD", + "rust": "examples2d/ccd2.rs", + "c": "examples2d/ccd2.c", + "feature": null + }, + { + "id": "joints2", + "dimension": 2, + "group": "Joints", + "name": "Joints", + "rust": "examples2d/joints2.rs", + "c": "examples2d/joints2.c", + "feature": null + }, + { + "id": "rope_joints2", + "dimension": 2, + "group": "Joints", + "name": "Rope Joints", + "rust": "examples2d/rope_joints2.rs", + "c": "examples2d/rope_joints2.c", + "feature": null + }, + { + "id": "pin_slot_joint2", + "dimension": 2, + "group": "Joints", + "name": "Pin Slot Joint", + "rust": "examples2d/pin_slot_joint2.rs", + "c": "examples2d/pin_slot_joint2.c", + "feature": null + }, + { + "id": "joint_motor_position2", + "dimension": 2, + "group": "Joints", + "name": "Joint motor position", + "rust": "examples2d/joint_motor_position2.rs", + "c": "examples2d/joint_motor_position2.c", + "feature": null + }, + { + "id": "inverse_kinematics2", + "dimension": 2, + "group": "Joints", + "name": "Inverse kinematics", + "rust": "examples2d/inverse_kinematics2.rs", + "c": "examples2d/inverse_kinematics2.c", + "feature": null + }, + { + "id": "multi_pendulum2", + "dimension": 2, + "group": "Joints", + "name": "Multi Pendulum", + "rust": "examples2d/multi_pendulum2.rs", + "c": "examples2d/multi_pendulum2.c", + "feature": null + }, + { + "id": "soft_bodies2", + "dimension": 2, + "group": "Soft bodies", + "name": "Soft bodies", + "rust": "examples2d/soft_bodies2.rs", + "c": "examples2d/soft_bodies2.c", + "feature": null + }, + { + "id": "soft_blobs2", + "dimension": 2, + "group": "Soft bodies", + "name": "Blobs", + "rust": "examples2d/soft_blobs2.rs", + "c": "examples2d/soft_blobs2.c", + "feature": null + }, + { + "id": "soft_jelly2", + "dimension": 2, + "group": "Soft bodies", + "name": "Jelly", + "rust": "examples2d/soft_jelly2.rs", + "c": "examples2d/soft_jelly2.c", + "feature": null + }, + { + "id": "soft_surface2", + "dimension": 2, + "group": "Soft bodies", + "name": "Deformable polylines", + "rust": "examples2d/soft_surface2.rs", + "c": "examples2d/soft_surface2.c", + "feature": null + }, + { + "id": "soft_pile2", + "dimension": 2, + "group": "Soft bodies", + "name": "Soft pile", + "rust": "examples2d/soft_pile2.rs", + "c": "examples2d/soft_pile2.c", + "feature": null + }, + { + "id": "soft_thin_features2", + "dimension": 2, + "group": "Soft bodies", + "name": "Thin features", + "rust": "examples2d/soft_thin_features2.rs", + "c": "examples2d/soft_thin_features2.c", + "feature": null + }, + { + "id": "soft_letters2", + "dimension": 2, + "group": "Soft bodies", + "name": "Soft letters", + "rust": "examples2d/soft_letters2.rs", + "c": "examples2d/soft_letters2.c", + "feature": null + }, + { + "id": "soft_plasticity2", + "dimension": 2, + "group": "Soft bodies", + "name": "Plasticity", + "rust": "examples2d/soft_plasticity2.rs", + "c": "examples2d/soft_plasticity2.c", + "feature": null + }, + { + "id": "soft_tearing2", + "dimension": 2, + "group": "Soft bodies", + "name": "Tearing", + "rust": "examples2d/soft_tearing2.rs", + "c": "examples2d/soft_tearing2.c", + "feature": null + }, + { + "id": "soft_force_tearing2", + "dimension": 2, + "group": "Soft bodies", + "name": "Force tearing", + "rust": "examples2d/soft_force_tearing2.rs", + "c": "examples2d/soft_force_tearing2.c", + "feature": null + }, + { + "id": "soft_cutting2", + "dimension": 2, + "group": "Soft bodies", + "name": "Cutting", + "rust": "examples2d/soft_cutting2.rs", + "c": "examples2d/soft_cutting2.c", + "feature": null + }, + { + "id": "soft_stress2", + "dimension": 2, + "group": "Soft bodies", + "name": "Stress coloring", + "rust": "examples2d/soft_stress2.rs", + "c": "examples2d/soft_stress2.c", + "feature": null + }, + { + "id": "soft_fem2", + "dimension": 2, + "group": "Soft bodies", + "name": "Soft FEM", + "rust": "examples2d/soft_fem2.rs", + "c": "examples2d/soft_fem2.c", + "feature": "fem" + }, + { + "id": "soft_joints2", + "dimension": 2, + "group": "Soft bodies", + "name": "Soft joints", + "rust": "examples2d/soft_joints2.rs", + "c": "examples2d/soft_joints2.c", + "feature": null + }, + { + "id": "character_controller2", + "dimension": 2, + "group": "Controls", + "name": "Character controller", + "rust": "examples2d/character_controller2.rs", + "c": "examples2d/character_controller2.c", + "feature": null + }, + { + "id": "debug_angular_limits2", + "dimension": 2, + "group": "Debug", + "name": "Angular limits", + "rust": "examples2d/debug_angular_limits2.rs", + "c": "examples2d/debug_angular_limits2.c", + "feature": null + }, + { + "id": "debug_box_ball2", + "dimension": 2, + "group": "Debug", + "name": "Box ball", + "rust": "examples2d/debug_box_ball2.rs", + "c": "examples2d/debug_box_ball2.c", + "feature": null + }, + { + "id": "debug_compression2", + "dimension": 2, + "group": "Debug", + "name": "Compression", + "rust": "examples2d/debug_compression2.rs", + "c": "examples2d/debug_compression2.c", + "feature": null + }, + { + "id": "debug_intersection2", + "dimension": 2, + "group": "Debug", + "name": "Intersection", + "rust": "examples2d/debug_intersection2.rs", + "c": "examples2d/debug_intersection2.c", + "feature": null + }, + { + "id": "debug_many_colliders2", + "dimension": 2, + "group": "Debug", + "name": "Many colliders", + "rust": "examples2d/debug_many_colliders2.rs", + "c": "examples2d/debug_many_colliders2.c", + "feature": null + }, + { + "id": "debug_total_overlap2", + "dimension": 2, + "group": "Debug", + "name": "Total overlap", + "rust": "examples2d/debug_total_overlap2.rs", + "c": "examples2d/debug_total_overlap2.c", + "feature": null + }, + { + "id": "debug_vertical_column2", + "dimension": 2, + "group": "Debug", + "name": "Vertical column", + "rust": "examples2d/debug_vertical_column2.rs", + "c": "examples2d/debug_vertical_column2.c", + "feature": null + }, + { + "id": "debug_self_intersect2", + "dimension": 2, + "group": "Debug", + "name": "Self intersect", + "rust": "examples2d/debug_self_intersect2.rs", + "c": "examples2d/debug_self_intersect2.c", + "feature": null + }, + { + "id": "s2d_high_mass_ratio_1", + "dimension": 2, + "group": "Inspired by Solver 2D", + "name": "High mass ratio 1", + "rust": "examples2d/s2d_high_mass_ratio_1.rs", + "c": "examples2d/s2d_high_mass_ratio_1.c", + "feature": null + }, + { + "id": "s2d_high_mass_ratio_2", + "dimension": 2, + "group": "Inspired by Solver 2D", + "name": "High mass ratio 2", + "rust": "examples2d/s2d_high_mass_ratio_2.rs", + "c": "examples2d/s2d_high_mass_ratio_2.c", + "feature": null + }, + { + "id": "s2d_high_mass_ratio_3", + "dimension": 2, + "group": "Inspired by Solver 2D", + "name": "High mass ratio 3", + "rust": "examples2d/s2d_high_mass_ratio_3.rs", + "c": "examples2d/s2d_high_mass_ratio_3.c", + "feature": null + }, + { + "id": "s2d_confined", + "dimension": 2, + "group": "Inspired by Solver 2D", + "name": "Confined", + "rust": "examples2d/s2d_confined.rs", + "c": "examples2d/s2d_confined.c", + "feature": null + }, + { + "id": "s2d_pyramid", + "dimension": 2, + "group": "Inspired by Solver 2D", + "name": "Pyramid", + "rust": "examples2d/s2d_pyramid.rs", + "c": "examples2d/s2d_pyramid.c", + "feature": null + }, + { + "id": "s2d_card_house", + "dimension": 2, + "group": "Inspired by Solver 2D", + "name": "Card house", + "rust": "examples2d/s2d_card_house.rs", + "c": "examples2d/s2d_card_house.c", + "feature": null + }, + { + "id": "s2d_arch", + "dimension": 2, + "group": "Inspired by Solver 2D", + "name": "Arch", + "rust": "examples2d/s2d_arch.rs", + "c": "examples2d/s2d_arch.c", + "feature": null + }, + { + "id": "s2d_bridge", + "dimension": 2, + "group": "Inspired by Solver 2D", + "name": "Bridge", + "rust": "examples2d/s2d_bridge.rs", + "c": "examples2d/s2d_bridge.c", + "feature": null + }, + { + "id": "s2d_ball_and_chain", + "dimension": 2, + "group": "Inspired by Solver 2D", + "name": "Ball and chain", + "rust": "examples2d/s2d_ball_and_chain.rs", + "c": "examples2d/s2d_ball_and_chain.c", + "feature": null + }, + { + "id": "s2d_joint_grid", + "dimension": 2, + "group": "Inspired by Solver 2D", + "name": "Joint grid", + "rust": "examples2d/s2d_joint_grid.rs", + "c": "examples2d/s2d_joint_grid.c", + "feature": null + }, + { + "id": "s2d_far_pyramid", + "dimension": 2, + "group": "Inspired by Solver 2D", + "name": "Far pyramid", + "rust": "examples2d/s2d_far_pyramid.rs", + "c": "examples2d/s2d_far_pyramid.c", + "feature": null + }, + { + "id": "b2d_compounds", + "dimension": 2, + "group": "Third-party benchmarks", + "name": "Compounds", + "rust": "examples2d/b2d_compounds.rs", + "c": "examples2d/b2d_compounds.c", + "feature": null + }, + { + "id": "b2d_joint_grid", + "dimension": 2, + "group": "Third-party benchmarks", + "name": "Joint grid", + "rust": "examples2d/b2d_joint_grid.rs", + "c": "examples2d/b2d_joint_grid.c", + "feature": null + }, + { + "id": "b2d_junkyard", + "dimension": 2, + "group": "Third-party benchmarks", + "name": "Junkyard", + "rust": "examples2d/b2d_junkyard.rs", + "c": "examples2d/b2d_junkyard.c", + "feature": null + }, + { + "id": "b2d_large_pyramid", + "dimension": 2, + "group": "Third-party benchmarks", + "name": "Large pyramid", + "rust": "examples2d/b2d_large_pyramid.rs", + "c": "examples2d/b2d_large_pyramid.c", + "feature": null + }, + { + "id": "b2d_many_pyramids", + "dimension": 2, + "group": "Third-party benchmarks", + "name": "Many pyramids", + "rust": "examples2d/b2d_many_pyramids.rs", + "c": "examples2d/b2d_many_pyramids.c", + "feature": null + }, + { + "id": "b2d_rain", + "dimension": 2, + "group": "Third-party benchmarks", + "name": "Rain", + "rust": "examples2d/b2d_rain.rs", + "c": "examples2d/b2d_rain.c", + "feature": null + }, + { + "id": "b2d_smash", + "dimension": 2, + "group": "Third-party benchmarks", + "name": "Smash", + "rust": "examples2d/b2d_smash.rs", + "c": "examples2d/b2d_smash.c", + "feature": null + }, + { + "id": "b2d_spinner", + "dimension": 2, + "group": "Third-party benchmarks", + "name": "Spinner", + "rust": "examples2d/b2d_spinner.rs", + "c": "examples2d/b2d_spinner.c", + "feature": null + }, + { + "id": "b2d_tumbler", + "dimension": 2, + "group": "Third-party benchmarks", + "name": "Tumbler", + "rust": "examples2d/b2d_tumbler.rs", + "c": "examples2d/b2d_tumbler.c", + "feature": null + }, + { + "id": "b2d_washer", + "dimension": 2, + "group": "Third-party benchmarks", + "name": "Washer", + "rust": "examples2d/b2d_washer.rs", + "c": "examples2d/b2d_washer.c", + "feature": null + }, + { + "id": "stress_tests_balls2", + "dimension": 2, + "group": "Stress tests", + "name": "Balls", + "rust": "examples2d/stress_tests/balls2.rs", + "c": "examples2d/stress_tests/balls2.c", + "feature": null + }, + { + "id": "stress_tests_boxes2", + "dimension": 2, + "group": "Stress tests", + "name": "Boxes", + "rust": "examples2d/stress_tests/boxes2.rs", + "c": "examples2d/stress_tests/boxes2.c", + "feature": null + }, + { + "id": "stress_tests_capsules2", + "dimension": 2, + "group": "Stress tests", + "name": "Capsules", + "rust": "examples2d/stress_tests/capsules2.rs", + "c": "examples2d/stress_tests/capsules2.c", + "feature": null + }, + { + "id": "stress_tests_convex_polygons2", + "dimension": 2, + "group": "Stress tests", + "name": "Convex polygons", + "rust": "examples2d/stress_tests/convex_polygons2.rs", + "c": "examples2d/stress_tests/convex_polygons2.c", + "feature": null + }, + { + "id": "stress_tests_heightfield2", + "dimension": 2, + "group": "Stress tests", + "name": "Heightfield", + "rust": "examples2d/stress_tests/heightfield2.rs", + "c": "examples2d/stress_tests/heightfield2.c", + "feature": null + }, + { + "id": "stress_tests_large_pyramids2", + "dimension": 2, + "group": "Stress tests", + "name": "Large pyramids", + "rust": "examples2d/stress_tests/large_pyramids2.rs", + "c": "examples2d/stress_tests/large_pyramids2.c", + "feature": null + }, + { + "id": "stress_tests_many_pyramids2", + "dimension": 2, + "group": "Stress tests", + "name": "Many pyramids", + "rust": "examples2d/stress_tests/many_pyramids2.rs", + "c": "examples2d/stress_tests/many_pyramids2.c", + "feature": null + }, + { + "id": "stress_tests_pyramid2", + "dimension": 2, + "group": "Stress tests", + "name": "Pyramid", + "rust": "examples2d/stress_tests/pyramid2.rs", + "c": "examples2d/stress_tests/pyramid2.c", + "feature": null + }, + { + "id": "stress_tests_ragdolls2", + "dimension": 2, + "group": "Stress tests", + "name": "Ragdoll piles", + "rust": "examples2d/stress_tests/ragdolls2.rs", + "c": "examples2d/stress_tests/ragdolls2.c", + "feature": null + }, + { + "id": "stress_tests_ropes2", + "dimension": 2, + "group": "Stress tests", + "name": "Ropes", + "rust": "examples2d/stress_tests/ropes2.rs", + "c": "examples2d/stress_tests/ropes2.c", + "feature": null + }, + { + "id": "stress_tests_vertical_stacks2", + "dimension": 2, + "group": "Stress tests", + "name": "Verticals stacks", + "rust": "examples2d/stress_tests/vertical_stacks2.rs", + "c": "examples2d/stress_tests/vertical_stacks2.c", + "feature": null + }, + { + "id": "stress_tests_joint_ball2", + "dimension": 2, + "group": "Stress tests", + "name": "(Stress test) joint ball", + "rust": "examples2d/stress_tests/joint_ball2.rs", + "c": "examples2d/stress_tests/joint_ball2.c", + "feature": null + }, + { + "id": "stress_tests_joint_fixed2", + "dimension": 2, + "group": "Stress tests", + "name": "(Stress test) joint fixed", + "rust": "examples2d/stress_tests/joint_fixed2.rs", + "c": "examples2d/stress_tests/joint_fixed2.c", + "feature": null + }, + { + "id": "stress_tests_joint_prismatic2", + "dimension": 2, + "group": "Stress tests", + "name": "(Stress test) joint prismatic", + "rust": "examples2d/stress_tests/joint_prismatic2.rs", + "c": "examples2d/stress_tests/joint_prismatic2.c", + "feature": null + }, + { + "id": "stress_tests_soft_blobs2", + "dimension": 2, + "group": "Stress tests", + "name": "Soft blobs", + "rust": "examples2d/stress_tests/soft_blobs2.rs", + "c": "examples2d/stress_tests/soft_blobs2.c", + "feature": null + }, + { + "id": "stress_tests_soft_jellies2", + "dimension": 2, + "group": "Stress tests", + "name": "Soft jellies", + "rust": "examples2d/stress_tests/soft_jellies2.rs", + "c": "examples2d/stress_tests/soft_jellies2.c", + "feature": null + }, + { + "id": "stress_tests_soft_ropes2", + "dimension": 2, + "group": "Stress tests", + "name": "Soft ropes", + "rust": "examples2d/stress_tests/soft_ropes2.rs", + "c": "examples2d/stress_tests/soft_ropes2.c", + "feature": null + }, + { + "id": "stress_tests_soft_strips2", + "dimension": 2, + "group": "Stress tests", + "name": "Soft strips", + "rust": "examples2d/stress_tests/soft_strips2.rs", + "c": "examples2d/stress_tests/soft_strips2.c", + "feature": null + }, + { + "id": "stress_tests_soft_cloth_keva2", + "dimension": 2, + "group": "Stress tests", + "name": "Soft cloth on Keva tower", + "rust": "examples2d/stress_tests/soft_cloth_keva2.rs", + "c": "examples2d/stress_tests/soft_cloth_keva2.c", + "feature": null + }, + { + "id": "stress_tests_soft_slab2", + "dimension": 2, + "group": "Stress tests", + "name": "Soft slab shower", + "rust": "examples2d/stress_tests/soft_slab2.rs", + "c": "examples2d/stress_tests/soft_slab2.c", + "feature": null + }, + { + "id": "stress_tests_soft_fem_beams2", + "dimension": 2, + "group": "Stress tests", + "name": "Soft FEM beams", + "rust": "examples2d/stress_tests/soft_fem_beams2.rs", + "c": "examples2d/stress_tests/soft_fem_beams2.c", + "feature": "fem" + }, + { + "id": "fountain3", + "dimension": 3, + "group": "Collisions", + "name": "Fountain", + "rust": "examples3d/fountain3.rs", + "c": "examples3d/fountain3.c", + "feature": null + }, + { + "id": "primitives3", + "dimension": 3, + "group": "Collisions", + "name": "Primitives", + "rust": "examples3d/primitives3.rs", + "c": "examples3d/primitives3.c", + "feature": null + }, + { + "id": "keva3", + "dimension": 3, + "group": "Collisions", + "name": "Keva tower", + "rust": "examples3d/keva3.rs", + "c": "examples3d/keva3.c", + "feature": null + }, + { + "id": "newton_cradle3", + "dimension": 3, + "group": "Collisions", + "name": "Newton cradle", + "rust": "examples3d/newton_cradle3.rs", + "c": "examples3d/newton_cradle3.c", + "feature": null + }, + { + "id": "domino3", + "dimension": 3, + "group": "Collisions", + "name": "Domino", + "rust": "examples3d/domino3.rs", + "c": "examples3d/domino3.c", + "feature": null + }, + { + "id": "platform3", + "dimension": 3, + "group": "Collisions", + "name": "Platform", + "rust": "examples3d/platform3.rs", + "c": "examples3d/platform3.c", + "feature": null + }, + { + "id": "sensor3", + "dimension": 3, + "group": "Collisions", + "name": "Sensor", + "rust": "examples3d/sensor3.rs", + "c": "examples3d/sensor3.c", + "feature": null + }, + { + "id": "compound3", + "dimension": 3, + "group": "Collisions", + "name": "Compound", + "rust": "examples3d/compound3.rs", + "c": "examples3d/compound3.c", + "feature": null + }, + { + "id": "convex_decomposition3", + "dimension": 3, + "group": "Collisions", + "name": "Convex decomposition", + "rust": "examples3d/convex_decomposition3.rs", + "c": "examples3d/convex_decomposition3.c", + "feature": null + }, + { + "id": "convex_polyhedron3", + "dimension": 3, + "group": "Collisions", + "name": "Convex polyhedron", + "rust": "examples3d/convex_polyhedron3.rs", + "c": "examples3d/convex_polyhedron3.c", + "feature": null + }, + { + "id": "trimesh3", + "dimension": 3, + "group": "Collisions", + "name": "TriMesh", + "rust": "examples3d/trimesh3.rs", + "c": "examples3d/trimesh3.c", + "feature": null + }, + { + "id": "dynamic_trimesh3", + "dimension": 3, + "group": "Collisions", + "name": "Dynamic trimeshes", + "rust": "examples3d/dynamic_trimesh3.rs", + "c": "examples3d/dynamic_trimesh3.c", + "feature": null + }, + { + "id": "heightfield3", + "dimension": 3, + "group": "Collisions", + "name": "Heightfield", + "rust": "examples3d/heightfield3.rs", + "c": "examples3d/heightfield3.c", + "feature": null + }, + { + "id": "voxels3", + "dimension": 3, + "group": "Collisions", + "name": "Voxels", + "rust": "examples3d/voxels3.rs", + "c": "examples3d/voxels3.c", + "feature": null + }, + { + "id": "collision_groups3", + "dimension": 3, + "group": "Collisions", + "name": "Collision groups", + "rust": "examples3d/collision_groups3.rs", + "c": "examples3d/collision_groups3.c", + "feature": null + }, + { + "id": "one_way_platforms3", + "dimension": 3, + "group": "Collisions", + "name": "One-way platforms", + "rust": "examples3d/one_way_platforms3.rs", + "c": "examples3d/one_way_platforms3.c", + "feature": null + }, + { + "id": "locked_rotations3", + "dimension": 3, + "group": "Dynamics", + "name": "Locked rotations", + "rust": "examples3d/locked_rotations3.rs", + "c": "examples3d/locked_rotations3.c", + "feature": null + }, + { + "id": "restitution3", + "dimension": 3, + "group": "Dynamics", + "name": "Restitution", + "rust": "examples3d/restitution3.rs", + "c": "examples3d/restitution3.c", + "feature": null + }, + { + "id": "damping3", + "dimension": 3, + "group": "Dynamics", + "name": "Damping", + "rust": "examples3d/damping3.rs", + "c": "examples3d/damping3.c", + "feature": null + }, + { + "id": "gyroscopic3", + "dimension": 3, + "group": "Dynamics", + "name": "Gyroscopic", + "rust": "examples3d/gyroscopic3.rs", + "c": "examples3d/gyroscopic3.c", + "feature": null + }, + { + "id": "ccd3", + "dimension": 3, + "group": "Dynamics", + "name": "CCD", + "rust": "examples3d/ccd3.rs", + "c": "examples3d/ccd3.c", + "feature": null + }, + { + "id": "joints3_run_impulse_joints", + "dimension": 3, + "group": "Joints", + "name": "Impulse Joints", + "rust": "examples3d/joints3.rs", + "c": "examples3d/joints3_run_impulse_joints.c", + "feature": null + }, + { + "id": "joints3_run_multibody_joints", + "dimension": 3, + "group": "Joints", + "name": "Multibody Joints", + "rust": "examples3d/joints3.rs", + "c": "examples3d/joints3_run_multibody_joints.c", + "feature": null + }, + { + "id": "rope_joints3", + "dimension": 3, + "group": "Joints", + "name": "Rope Joints", + "rust": "examples3d/rope_joints3.rs", + "c": "examples3d/rope_joints3.c", + "feature": null + }, + { + "id": "spring_joints3", + "dimension": 3, + "group": "Joints", + "name": "Spring Joints", + "rust": "examples3d/spring_joints3.rs", + "c": "examples3d/spring_joints3.c", + "feature": null + }, + { + "id": "joint_motor_position3", + "dimension": 3, + "group": "Joints", + "name": "Joint Motor Position", + "rust": "examples3d/joint_motor_position3.rs", + "c": "examples3d/joint_motor_position3.c", + "feature": null + }, + { + "id": "inverse_kinematics3", + "dimension": 3, + "group": "Joints", + "name": "Inverse kinematics", + "rust": "examples3d/inverse_kinematics3.rs", + "c": "examples3d/inverse_kinematics3.c", + "feature": null + }, + { + "id": "soft_bodies3", + "dimension": 3, + "group": "Soft bodies", + "name": "Soft bodies", + "rust": "examples3d/soft_bodies3.rs", + "c": "examples3d/soft_bodies3.c", + "feature": null + }, + { + "id": "soft_cloth3", + "dimension": 3, + "group": "Soft bodies", + "name": "Cloth", + "rust": "examples3d/soft_cloth3.rs", + "c": "examples3d/soft_cloth3.c", + "feature": null + }, + { + "id": "soft_jelly3", + "dimension": 3, + "group": "Soft bodies", + "name": "Jelly", + "rust": "examples3d/soft_jelly3.rs", + "c": "examples3d/soft_jelly3.c", + "feature": null + }, + { + "id": "soft_joints3", + "dimension": 3, + "group": "Soft bodies", + "name": "Soft joints", + "rust": "examples3d/soft_joints3.rs", + "c": "examples3d/soft_joints3.c", + "feature": null + }, + { + "id": "soft_meshes3", + "dimension": 3, + "group": "Soft bodies", + "name": "Cluster meshes", + "rust": "examples3d/soft_meshes3.rs", + "c": "examples3d/soft_meshes3.c", + "feature": null + }, + { + "id": "soft_surface3", + "dimension": 3, + "group": "Soft bodies", + "name": "Deformable trimeshes", + "rust": "examples3d/soft_surface3.rs", + "c": "examples3d/soft_surface3.c", + "feature": null + }, + { + "id": "soft_pile3", + "dimension": 3, + "group": "Soft bodies", + "name": "Soft pile", + "rust": "examples3d/soft_pile3.rs", + "c": "examples3d/soft_pile3.c", + "feature": null + }, + { + "id": "soft_thin_features3", + "dimension": 3, + "group": "Soft bodies", + "name": "Thin features", + "rust": "examples3d/soft_thin_features3.rs", + "c": "examples3d/soft_thin_features3.c", + "feature": null + }, + { + "id": "soft_cloth_stress3", + "dimension": 3, + "group": "Soft bodies", + "name": "Cloth stress", + "rust": "examples3d/soft_cloth_stress3.rs", + "c": "examples3d/soft_cloth_stress3.c", + "feature": null + }, + { + "id": "soft_plasticity3", + "dimension": 3, + "group": "Soft bodies", + "name": "Plasticity", + "rust": "examples3d/soft_plasticity3.rs", + "c": "examples3d/soft_plasticity3.c", + "feature": null + }, + { + "id": "soft_tearing3", + "dimension": 3, + "group": "Soft bodies", + "name": "Tearing", + "rust": "examples3d/soft_tearing3.rs", + "c": "examples3d/soft_tearing3.c", + "feature": null + }, + { + "id": "soft_dress3", + "dimension": 3, + "group": "Soft bodies", + "name": "Dancing dress", + "rust": "examples3d/soft_dress3.rs", + "c": "examples3d/soft_dress3.c", + "feature": null + }, + { + "id": "soft_fem3", + "dimension": 3, + "group": "Soft bodies", + "name": "Soft FEM", + "rust": "examples3d/soft_fem3.rs", + "c": "examples3d/soft_fem3.c", + "feature": "fem" + }, + { + "id": "soft_trimesh3", + "dimension": 3, + "group": "Soft bodies", + "name": "Soft trimeshes", + "rust": "examples3d/soft_trimesh3.rs", + "c": "examples3d/soft_trimesh3.c", + "feature": null + }, + { + "id": "character_controller3", + "dimension": 3, + "group": "Controls", + "name": "Character controller", + "rust": "examples3d/character_controller3.rs", + "c": "examples3d/character_controller3.c", + "feature": null + }, + { + "id": "vehicle_controller3", + "dimension": 3, + "group": "Controls", + "name": "Vehicle controller", + "rust": "examples3d/vehicle_controller3.rs", + "c": "examples3d/vehicle_controller3.c", + "feature": null + }, + { + "id": "vehicle_joints3", + "dimension": 3, + "group": "Controls", + "name": "Vehicle joints", + "rust": "examples3d/vehicle_joints3.rs", + "c": "examples3d/vehicle_joints3.c", + "feature": null + }, + { + "id": "urdf3", + "dimension": 3, + "group": "Robotics", + "name": "URDF", + "rust": "examples3d/urdf3.rs", + "c": "examples3d/urdf3.c", + "feature": "robotics" + }, + { + "id": "mjcf3", + "dimension": 3, + "group": "Robotics", + "name": "MJCF", + "rust": "examples3d/mjcf3.rs", + "c": "examples3d/mjcf3.c", + "feature": "robotics" + }, + { + "id": "mujoco_menagerie3", + "dimension": 3, + "group": "Robotics", + "name": "Mujoco Menagerie", + "rust": "examples3d/mujoco_menagerie3.rs", + "c": "examples3d/mujoco_menagerie3.c", + "feature": "robotics" + }, + { + "id": "debug_angular_limits3", + "dimension": 3, + "group": "Debug", + "name": "Angular limits", + "rust": "examples3d/debug_angular_limits3.rs", + "c": "examples3d/debug_angular_limits3.c", + "feature": null + }, + { + "id": "debug_articulations3", + "dimension": 3, + "group": "Debug", + "name": "Multibody joints", + "rust": "examples3d/debug_articulations3.rs", + "c": "examples3d/debug_articulations3.c", + "feature": null + }, + { + "id": "debug_add_remove_collider3", + "dimension": 3, + "group": "Debug", + "name": "Add/rm collider", + "rust": "examples3d/debug_add_remove_collider3.rs", + "c": "examples3d/debug_add_remove_collider3.c", + "feature": null + }, + { + "id": "debug_multi_collider_body3", + "dimension": 3, + "group": "Debug", + "name": "Multi-collider body", + "rust": "examples3d/debug_multi_collider_body3.rs", + "c": "examples3d/debug_multi_collider_body3.c", + "feature": null + }, + { + "id": "debug_big_colliders3", + "dimension": 3, + "group": "Debug", + "name": "Big colliders", + "rust": "examples3d/debug_big_colliders3.rs", + "c": "examples3d/debug_big_colliders3.c", + "feature": null + }, + { + "id": "debug_boxes3", + "dimension": 3, + "group": "Debug", + "name": "Boxes", + "rust": "examples3d/debug_boxes3.rs", + "c": "examples3d/debug_boxes3.c", + "feature": null + }, + { + "id": "debug_balls3", + "dimension": 3, + "group": "Debug", + "name": "Balls", + "rust": "examples3d/debug_balls3.rs", + "c": "examples3d/debug_balls3.c", + "feature": null + }, + { + "id": "debug_disabled3", + "dimension": 3, + "group": "Debug", + "name": "Disabled", + "rust": "examples3d/debug_disabled3.rs", + "c": "examples3d/debug_disabled3.c", + "feature": null + }, + { + "id": "debug_two_cubes3", + "dimension": 3, + "group": "Debug", + "name": "Two cubes", + "rust": "examples3d/debug_two_cubes3.rs", + "c": "examples3d/debug_two_cubes3.c", + "feature": null + }, + { + "id": "debug_pop3", + "dimension": 3, + "group": "Debug", + "name": "Pop", + "rust": "examples3d/debug_pop3.rs", + "c": "examples3d/debug_pop3.c", + "feature": null + }, + { + "id": "debug_dynamic_collider_add3", + "dimension": 3, + "group": "Debug", + "name": "Dyn. collider add", + "rust": "examples3d/debug_dynamic_collider_add3.rs", + "c": "examples3d/debug_dynamic_collider_add3.c", + "feature": null + }, + { + "id": "debug_friction3", + "dimension": 3, + "group": "Debug", + "name": "Friction", + "rust": "examples3d/debug_friction3.rs", + "c": "examples3d/debug_friction3.c", + "feature": null + }, + { + "id": "debug_internal_edges3", + "dimension": 3, + "group": "Debug", + "name": "Internal edges", + "rust": "examples3d/debug_internal_edges3.rs", + "c": "examples3d/debug_internal_edges3.c", + "feature": null + }, + { + "id": "debug_self_intersect3", + "dimension": 3, + "group": "Debug", + "name": "Self intersect", + "rust": "examples3d/debug_self_intersect3.rs", + "c": "examples3d/debug_self_intersect3.c", + "feature": null + }, + { + "id": "debug_long_chain3", + "dimension": 3, + "group": "Debug", + "name": "Long chain", + "rust": "examples3d/debug_long_chain3.rs", + "c": "examples3d/debug_long_chain3.c", + "feature": null + }, + { + "id": "debug_chain_high_mass_ratio3", + "dimension": 3, + "group": "Debug", + "name": "High mass ratio: chain", + "rust": "examples3d/debug_chain_high_mass_ratio3.rs", + "c": "examples3d/debug_chain_high_mass_ratio3.c", + "feature": null + }, + { + "id": "debug_cube_high_mass_ratio3", + "dimension": 3, + "group": "Debug", + "name": "High mass ratio: cube", + "rust": "examples3d/debug_cube_high_mass_ratio3.rs", + "c": "examples3d/debug_cube_high_mass_ratio3.c", + "feature": null + }, + { + "id": "debug_triangle3", + "dimension": 3, + "group": "Debug", + "name": "Triangle", + "rust": "examples3d/debug_triangle3.rs", + "c": "examples3d/debug_triangle3.c", + "feature": null + }, + { + "id": "debug_trimesh3", + "dimension": 3, + "group": "Debug", + "name": "Trimesh", + "rust": "examples3d/debug_trimesh3.rs", + "c": "examples3d/debug_trimesh3.c", + "feature": null + }, + { + "id": "debug_thin_cube_on_mesh3", + "dimension": 3, + "group": "Debug", + "name": "Thin cube", + "rust": "examples3d/debug_thin_cube_on_mesh3.rs", + "c": "examples3d/debug_thin_cube_on_mesh3.c", + "feature": null + }, + { + "id": "debug_cylinder3", + "dimension": 3, + "group": "Debug", + "name": "Cylinder", + "rust": "examples3d/debug_cylinder3.rs", + "c": "examples3d/debug_cylinder3.c", + "feature": null + }, + { + "id": "debug_infinite_fall3", + "dimension": 3, + "group": "Debug", + "name": "Infinite fall", + "rust": "examples3d/debug_infinite_fall3.rs", + "c": "examples3d/debug_infinite_fall3.c", + "feature": null + }, + { + "id": "debug_prismatic3", + "dimension": 3, + "group": "Debug", + "name": "Prismatic", + "rust": "examples3d/debug_prismatic3.rs", + "c": "examples3d/debug_prismatic3.c", + "feature": null + }, + { + "id": "debug_rollback3", + "dimension": 3, + "group": "Debug", + "name": "Rollback", + "rust": "examples3d/debug_rollback3.rs", + "c": "examples3d/debug_rollback3.c", + "feature": null + }, + { + "id": "debug_shape_modification3", + "dimension": 3, + "group": "Debug", + "name": "Shape modification", + "rust": "examples3d/debug_shape_modification3.rs", + "c": "examples3d/debug_shape_modification3.c", + "feature": null + }, + { + "id": "debug_sleeping_kinematic3", + "dimension": 3, + "group": "Debug", + "name": "Sleeping kinematics", + "rust": "examples3d/debug_sleeping_kinematic3.rs", + "c": "examples3d/debug_sleeping_kinematic3.c", + "feature": null + }, + { + "id": "debug_deserialize3", + "dimension": 3, + "group": "Debug", + "name": "Deserialize", + "rust": "examples3d/debug_deserialize3.rs", + "c": "examples3d/debug_deserialize3.c", + "feature": null + }, + { + "id": "debug_multibody_ang_motor_pos3", + "dimension": 3, + "group": "Debug", + "name": "Multibody ang. motor pos.", + "rust": "examples3d/debug_multibody_ang_motor_pos3.rs", + "c": "examples3d/debug_multibody_ang_motor_pos3.c", + "feature": null + }, + { + "id": "stress_tests_balls3", + "dimension": 3, + "group": "Stress tests", + "name": "Balls", + "rust": "examples3d/stress_tests/balls3.rs", + "c": "examples3d/stress_tests/balls3.c", + "feature": null + }, + { + "id": "stress_tests_boxes3", + "dimension": 3, + "group": "Stress tests", + "name": "Boxes", + "rust": "examples3d/stress_tests/boxes3.rs", + "c": "examples3d/stress_tests/boxes3.c", + "feature": null + }, + { + "id": "stress_tests_capsules3", + "dimension": 3, + "group": "Stress tests", + "name": "Capsules", + "rust": "examples3d/stress_tests/capsules3.rs", + "c": "examples3d/stress_tests/capsules3.c", + "feature": null + }, + { + "id": "stress_tests_ccd3", + "dimension": 3, + "group": "Stress tests", + "name": "CCD", + "rust": "examples3d/stress_tests/ccd3.rs", + "c": "examples3d/stress_tests/ccd3.c", + "feature": null + }, + { + "id": "stress_tests_compound3", + "dimension": 3, + "group": "Stress tests", + "name": "Compound", + "rust": "examples3d/stress_tests/compound3.rs", + "c": "examples3d/stress_tests/compound3.c", + "feature": null + }, + { + "id": "stress_tests_convex_polyhedron3", + "dimension": 3, + "group": "Stress tests", + "name": "Convex polyhedron", + "rust": "examples3d/stress_tests/convex_polyhedron3.rs", + "c": "examples3d/stress_tests/convex_polyhedron3.c", + "feature": null + }, + { + "id": "stress_tests_many_kinematics3", + "dimension": 3, + "group": "Stress tests", + "name": "Many kinematics", + "rust": "examples3d/stress_tests/many_kinematics3.rs", + "c": "examples3d/stress_tests/many_kinematics3.c", + "feature": null + }, + { + "id": "stress_tests_many_static3", + "dimension": 3, + "group": "Stress tests", + "name": "Many static", + "rust": "examples3d/stress_tests/many_static3.rs", + "c": "examples3d/stress_tests/many_static3.c", + "feature": null + }, + { + "id": "stress_tests_many_sleep3", + "dimension": 3, + "group": "Stress tests", + "name": "Many sleep", + "rust": "examples3d/stress_tests/many_sleep3.rs", + "c": "examples3d/stress_tests/many_sleep3.c", + "feature": null + }, + { + "id": "stress_tests_heightfield3", + "dimension": 3, + "group": "Stress tests", + "name": "Heightfield", + "rust": "examples3d/stress_tests/heightfield3.rs", + "c": "examples3d/stress_tests/heightfield3.c", + "feature": null + }, + { + "id": "stress_tests_stacks3", + "dimension": 3, + "group": "Stress tests", + "name": "Stacks", + "rust": "examples3d/stress_tests/stacks3.rs", + "c": "examples3d/stress_tests/stacks3.c", + "feature": null + }, + { + "id": "stress_tests_pyramid3", + "dimension": 3, + "group": "Stress tests", + "name": "Pyramid", + "rust": "examples3d/stress_tests/pyramid3.rs", + "c": "examples3d/stress_tests/pyramid3.c", + "feature": null + }, + { + "id": "stress_tests_trimesh3", + "dimension": 3, + "group": "Stress tests", + "name": "Trimesh", + "rust": "examples3d/stress_tests/trimesh3.rs", + "c": "examples3d/stress_tests/trimesh3.c", + "feature": null + }, + { + "id": "stress_tests_joint_ball3", + "dimension": 3, + "group": "Stress tests", + "name": "ImpulseJoint ball", + "rust": "examples3d/stress_tests/joint_ball3.rs", + "c": "examples3d/stress_tests/joint_ball3.c", + "feature": null + }, + { + "id": "stress_tests_joint_fixed3", + "dimension": 3, + "group": "Stress tests", + "name": "ImpulseJoint fixed", + "rust": "examples3d/stress_tests/joint_fixed3.rs", + "c": "examples3d/stress_tests/joint_fixed3.c", + "feature": null + }, + { + "id": "stress_tests_joint_revolute3", + "dimension": 3, + "group": "Stress tests", + "name": "ImpulseJoint revolute", + "rust": "examples3d/stress_tests/joint_revolute3.rs", + "c": "examples3d/stress_tests/joint_revolute3.c", + "feature": null + }, + { + "id": "stress_tests_joint_prismatic3", + "dimension": 3, + "group": "Stress tests", + "name": "ImpulseJoint prismatic", + "rust": "examples3d/stress_tests/joint_prismatic3.rs", + "c": "examples3d/stress_tests/joint_prismatic3.c", + "feature": null + }, + { + "id": "stress_tests_ragdolls3", + "dimension": 3, + "group": "Stress tests", + "name": "Ragdoll piles", + "rust": "examples3d/stress_tests/ragdolls3.rs", + "c": "examples3d/stress_tests/ragdolls3.c", + "feature": null + }, + { + "id": "stress_tests_ropes3", + "dimension": 3, + "group": "Stress tests", + "name": "Ropes", + "rust": "examples3d/stress_tests/ropes3.rs", + "c": "examples3d/stress_tests/ropes3.c", + "feature": null + }, + { + "id": "stress_tests_many_pyramids3", + "dimension": 3, + "group": "Stress tests", + "name": "Many pyramids", + "rust": "examples3d/stress_tests/many_pyramids3.rs", + "c": "examples3d/stress_tests/many_pyramids3.c", + "feature": null + }, + { + "id": "stress_tests_keva3", + "dimension": 3, + "group": "Stress tests", + "name": "Keva tower", + "rust": "examples3d/stress_tests/keva3.rs", + "c": "examples3d/stress_tests/keva3.c", + "feature": null + }, + { + "id": "stress_tests_ray_cast3", + "dimension": 3, + "group": "Stress tests", + "name": "Ray cast", + "rust": "examples3d/stress_tests/ray_cast3.rs", + "c": "examples3d/stress_tests/ray_cast3.c", + "feature": null + }, + { + "id": "stress_tests_soft_blobs3", + "dimension": 3, + "group": "Stress tests", + "name": "Soft blobs", + "rust": "examples3d/stress_tests/soft_blobs3.rs", + "c": "examples3d/stress_tests/soft_blobs3.c", + "feature": null + }, + { + "id": "stress_tests_soft_jellies3", + "dimension": 3, + "group": "Stress tests", + "name": "Soft jellies", + "rust": "examples3d/stress_tests/soft_jellies3.rs", + "c": "examples3d/stress_tests/soft_jellies3.c", + "feature": null + }, + { + "id": "stress_tests_soft_ropes3", + "dimension": 3, + "group": "Stress tests", + "name": "Soft ropes", + "rust": "examples3d/stress_tests/soft_ropes3.rs", + "c": "examples3d/stress_tests/soft_ropes3.c", + "feature": null + }, + { + "id": "stress_tests_soft_cloth_drape3", + "dimension": 3, + "group": "Stress tests", + "name": "Soft cloth drape", + "rust": "examples3d/stress_tests/soft_cloth_drape3.rs", + "c": "examples3d/stress_tests/soft_cloth_drape3.c", + "feature": null + }, + { + "id": "stress_tests_soft_cloth_keva3", + "dimension": 3, + "group": "Stress tests", + "name": "Soft cloth on Keva tower", + "rust": "examples3d/stress_tests/soft_cloth_keva3.rs", + "c": "examples3d/stress_tests/soft_cloth_keva3.c", + "feature": null + }, + { + "id": "stress_tests_soft_slab3", + "dimension": 3, + "group": "Stress tests", + "name": "Soft slab shower", + "rust": "examples3d/stress_tests/soft_slab3.rs", + "c": "examples3d/stress_tests/soft_slab3.c", + "feature": null + }, + { + "id": "stress_tests_soft_fem_beams3", + "dimension": 3, + "group": "Stress tests", + "name": "Soft FEM beams", + "rust": "examples3d/stress_tests/soft_fem_beams3.rs", + "c": "examples3d/stress_tests/soft_fem_beams3.c", + "feature": "fem" + }, + { + "id": "b3d_large_pyramid", + "dimension": 3, + "group": "Third-party benchmarks", + "name": "Large pyramid", + "rust": "examples3d/b3d_large_pyramid.rs", + "c": "examples3d/b3d_large_pyramid.c", + "feature": null + }, + { + "id": "b3d_many_pyramids", + "dimension": 3, + "group": "Third-party benchmarks", + "name": "Many pyramids", + "rust": "examples3d/b3d_many_pyramids.rs", + "c": "examples3d/b3d_many_pyramids.c", + "feature": null + }, + { + "id": "b3d_joint_grid", + "dimension": 3, + "group": "Third-party benchmarks", + "name": "Joint grid", + "rust": "examples3d/b3d_joint_grid.rs", + "c": "examples3d/b3d_joint_grid.c", + "feature": null + }, + { + "id": "b3d_junkyard", + "dimension": 3, + "group": "Third-party benchmarks", + "name": "Junkyard", + "rust": "examples3d/b3d_junkyard.rs", + "c": "examples3d/b3d_junkyard.c", + "feature": null + }, + { + "id": "b3d_washer", + "dimension": 3, + "group": "Third-party benchmarks", + "name": "Washer", + "rust": "examples3d/b3d_washer.rs", + "c": "examples3d/b3d_washer.c", + "feature": null + }, + { + "id": "b3d_trees_run100", + "dimension": 3, + "group": "Third-party benchmarks", + "name": "Trees 100", + "rust": "examples3d/b3d_trees.rs", + "c": "examples3d/b3d_trees_run100.c", + "feature": null + }, + { + "id": "b3d_trees_run50", + "dimension": 3, + "group": "Third-party benchmarks", + "name": "Trees 50", + "rust": "examples3d/b3d_trees.rs", + "c": "examples3d/b3d_trees_run50.c", + "feature": null + }, + { + "id": "b3d_trees_run25", + "dimension": 3, + "group": "Third-party benchmarks", + "name": "Trees 25", + "rust": "examples3d/b3d_trees.rs", + "c": "examples3d/b3d_trees_run25.c", + "feature": null + }, + { + "id": "b3d_rain", + "dimension": 3, + "group": "Third-party benchmarks", + "name": "Rain", + "rust": "examples3d/b3d_rain.rs", + "c": "examples3d/b3d_rain.c", + "feature": null + }, + { + "id": "b3d_large_world", + "dimension": 3, + "group": "Third-party benchmarks", + "name": "Large world", + "rust": "examples3d/b3d_large_world.rs", + "c": "examples3d/b3d_large_world.c", + "feature": null + } +] diff --git a/c/testbed/example_math.h b/c/testbed/example_math.h new file mode 100644 index 000000000..48fbe243f --- /dev/null +++ b/c/testbed/example_math.h @@ -0,0 +1,15 @@ +#ifndef RAPIER_EXAMPLE_MATH_H +#define RAPIER_EXAMPLE_MATH_H +#include "rapier_math.h" +#include + +/* A fixed PCG stream makes the C examples reproducible across libc versions. */ +static inline RAPIER_TYPE(Real) exampleRandom(uint64_t *state) { + uint64_t old = *state; + *state = old * UINT64_C(6364136223846793005) + UINT64_C(1442695040888963407); + uint32_t value = (uint32_t)(((old >> 18) ^ old) >> 27); + uint32_t rotation = (uint32_t)(old >> 59); + return (RAPIER_TYPE(Real))(((value >> rotation) | (value << ((-rotation) & 31))) >> 8) / + (RAPIER_TYPE(Real))16777216; +} +#endif diff --git a/c/testbed/examples2d/add_remove2.c b/c/testbed/examples2d/add_remove2.c new file mode 100644 index 000000000..5c787d23e --- /dev/null +++ b/c/testbed/examples2d/add_remove2.c @@ -0,0 +1,73 @@ +/* Port of examples2d/add_remove2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "example_math.h" + +void tbAddRemove2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + const R2Real rad = 0.5; + const R2Vector positions[] = {r2Vector(5.0, -1.0), r2Vector(-5.0, -1.0)}; + + R2RigidBodyHandle platformHandles[2] = {0}; + + for (size_t i = 0; i < TB_COUNT(positions); i++) { + R2RigidBodyDesc rigidBody = r2KinematicPositionBasedRigidBodyDesc(); + rigidBody.position.translation = positions[i]; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(rad * 10.0, rad)); + platformHandles[i] = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(platformHandles[i], &collider); + } + + /* Set up the viewer. */ + tbCamera2(testbed, 0.0, 0.0, 20.0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + + const uint64_t stepId = testbed->step + 1; + R2Real dt = r2TimeStep(world); + + const R2Real rot = -(R2Real)stepId * dt; + for (size_t i = 0; i < TB_COUNT(platformHandles); i++) { + r2RigidBody_SetNextKinematicRotation(platformHandles[i], r2Rotation(rot)); + } + + if (stepId % 10 == 0) { + const R2Real rad = 0.5; + const R2Real x = exampleRandom(&testbed->randomState) * 10.0 - 5.0; + const R2Real y = exampleRandom(&testbed->randomState) * 10.0 + 10.0; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x, y); + rigidBody.canSleep = !testbed->noSleep; + + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(rad, rad)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + + /* Copy the handles before removing bodies from their set. */ + size_t numBodies = r2RigidBodyHandles(world, NULL, 0); + R2RigidBodyHandle *handles = malloc(numBodies * sizeof(*handles)); + if (!handles && numBodies != 0) { + abort(); + } + numBodies = r2RigidBodyHandles(world, handles, numBodies); + + for (size_t i = 0; i < numBodies; i++) { + R2Vector position = r2RigidBody_Translation(handles[i]); + if (position.y < -10.0) { + r2RemoveRigidBody(handles[i], 1); + } + } + free(handles); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/b2d_compounds.c b/c/testbed/examples2d/b2d_compounds.c new file mode 100644 index 000000000..ace65262f --- /dev/null +++ b/c/testbed/examples2d/b2d_compounds.c @@ -0,0 +1,68 @@ +/* Port of examples2d/b2d_compounds.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB2dCompounds(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, -10)); + R2RigidBodyHandle ground; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + rigidBody.canSleep = !testbed->noSleep; + ground = r2InsertRigidBody(world, &rigidBody); + + for (int i = 0; i <= 80; i++) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.55, 0.5)); + collider.position.translation = r2Vector(-40 + i, 0); + r2InsertCollider(ground, &collider); + } + for (int side = -1; side <= 1; side += 2) { + for (int i = 0; i < 100; i++) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.55)); + collider.position.translation = r2Vector(side * 40, i + 1); + r2InsertCollider(ground, &collider); + } + } + R2SharedShape *segment = r2SegmentSharedShape(r2Vector(-800, -80), r2Vector(800, -80)); + R2ColliderDesc shapeCollider = r2DefaultColliderDesc(); + shapeCollider.shape.kind = R2_SHAPE_DESC_SHARED; + shapeCollider.shape.sharedShape = segment; + r2InsertCollider(ground, &shapeCollider); + + const R2Vector left[] = {r2Vector(-1, 0), r2Vector(0.5, 1), r2Vector(0, 2)}; + const R2Vector right[] = {r2Vector(1, 0), r2Vector(-0.5, 1), r2Vector(0, 2)}; + R2Real side = 0.25; + for (int i = 0; i < 20; i++) { + for (int j = 0; j < 150; j++) { + R2RigidBodyHandle handle; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i * 2 - 19 + side, j * 2.25 + 5.575); + rigidBody.canSleep = !testbed->noSleep; + handle = r2InsertRigidBody(world, &rigidBody); + + side = -side; + for (int p = 0; p < 2; p++) { + R2ColliderDesc collider = r2DefaultColliderDesc(); + r2ShapeDesc_SetConvexHull(&collider.shape, (R2VectorView){p ? right : left, 3}); + collider.friction = 0.5; + r2InsertCollider(handle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 120, 2); + r2FreeSharedShape(segment); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/b2d_joint_grid.c b/c/testbed/examples2d/b2d_joint_grid.c new file mode 100644 index 000000000..e4303f4a0 --- /dev/null +++ b/c/testbed/examples2d/b2d_joint_grid.c @@ -0,0 +1,60 @@ +/* Port of examples2d/b2d_joint_grid.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB2dJointGrid(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, -10)); + R2RigidBodyHandle *handles = calloc(1, 10000 * sizeof(*handles)); + if (!handles) { + abort(); + } + for (int k = 0; k < 100; k++) { + for (int i = 0; i < 100; i++) { + int fixed = k >= 47 && k <= 53 && i == 0; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.bodyType = fixed ? R2_FIXED : R2_DYNAMIC; + rigidBody.position.translation = r2Vector(k, -i); + if (!fixed) { + rigidBody.canSleep = 0; + } + R2ColliderDesc collider = r2BallColliderDesc(0.4); + collider.collisionGroups = (R2InteractionGroups){2, UINT32_MAX ^ 2, R2_GROUPS_AND}; + R2RigidBodyHandle handle; + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + for (int dir = 0; dir < 2; dir++) { + if (dir ? k > 0 : i > 0) { + R2JointDesc joint = r2DefaultJointDesc(); + joint.lockedAxes = 3; + joint.localFrame1.translation = dir ? r2Vector(0.5, 0) : r2Vector(0, -0.5); + joint.localFrame2.translation = dir ? r2Vector(-0.5, 0) : r2Vector(0, 0.5); + joint.contactsEnabled = 0; + r2InsertImpulseJoint(handles[k * 100 + i - (dir ? 100 : 1)], handle, + &joint); + } + } + handles[k * 100 + i] = handle; + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 50, -50, 4); + free(handles); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/b2d_junkyard.c b/c/testbed/examples2d/b2d_junkyard.c new file mode 100644 index 000000000..510592699 --- /dev/null +++ b/c/testbed/examples2d/b2d_junkyard.c @@ -0,0 +1,73 @@ +/* Port of examples2d/b2d_junkyard.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB2dJunkyard(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, -10)); + R2RigidBodyHandle ground; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + rigidBody.canSleep = !testbed->noSleep; + ground = r2InsertRigidBody(world, &rigidBody); + + for (int i = 0; i <= 160; i++) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.55, 0.5)); + collider.position.translation = r2Vector(-80 + i, 0); + r2InsertCollider(ground, &collider); + } + for (int side = -1; side <= 1; side += 2) { + for (int i = 0; i < 50; i++) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.55)); + collider.position.translation = r2Vector(side * 80, i + 1); + r2InsertCollider(ground, &collider); + } + } + R2Vector points[5]; + R2Real phi = R2_PI * ((R2Real)sqrt(5) - 1); + for (int i = 0; i < 5; i++) { + points[i] = r2Vector(0.25 * cos(phi * i), 0.25 * sin(phi * i)); + } + R2Real side = -0.1; + for (int i = 0; i < 200; i++) { + for (int j = 0; j < 40; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(1.5 * (2 * i - 200) * 0.25 + side, j + 15); + R2ColliderDesc collider = r2DefaultColliderDesc(); + r2ShapeDesc_SetConvexHull(&collider.shape, (R2VectorView){points, 5}); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + + side = -side; + } + } + R2RigidBodyHandle pusher = {0}; + R2RigidBodyDesc platformBody = r2KinematicPositionBasedRigidBodyDesc(); + platformBody.position.translation = r2Vector(0, 0); + platformBody.canSleep = !testbed->noSleep; + pusher = r2InsertRigidBody(world, &platformBody); + + R2ColliderDesc boxCollider = r2CuboidColliderDesc(r2Vector(2, 4)); + boxCollider.position.translation = r2Vector(0, 4); + r2InsertCollider(pusher, &boxCollider); + /* Set up the viewer. */ + tbCamera2(testbed, 0, 20, 4); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2RigidBody_SetNextKinematicTranslation( + pusher, r2Vector(60 * sin(0.2 * testbed->step / 60.0), 0)); + + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/b2d_large_pyramid.c b/c/testbed/examples2d/b2d_large_pyramid.c new file mode 100644 index 000000000..e3617e7c4 --- /dev/null +++ b/c/testbed/examples2d/b2d_large_pyramid.c @@ -0,0 +1,45 @@ +/* Port of examples2d/b2d_large_pyramid.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB2dLargePyramid(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, -10)); + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, -1); + R2ColliderDesc boxCollider = r2CuboidColliderDesc(r2Vector(120, 1)); + groundBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle groundBodyHandle = r2InsertRigidBody(world, &groundBody); + r2InsertCollider(groundBodyHandle, &boxCollider); + for (int i = 0; i < 200; i++) { + for (int j = i; j < 200; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = + r2Vector((i + 1) * 0.5 + j - i - 100, (2 * i + 1) * 0.5); + rigidBody.canSleep = 0; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + collider.density = 1; + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 50, 2.5); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/b2d_many_pyramids.c b/c/testbed/examples2d/b2d_many_pyramids.c new file mode 100644 index 000000000..114a84a63 --- /dev/null +++ b/c/testbed/examples2d/b2d_many_pyramids.c @@ -0,0 +1,58 @@ +/* Port of examples2d/b2d_many_pyramids.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB2dManyPyramids(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, -10)); + R2RigidBodyHandle floor; + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + groundBody.canSleep = !testbed->noSleep; + floor = r2InsertRigidBody(world, &groundBody); + + for (int i = 0; i < 20; i++) { + R2SharedShape *shape = r2SegmentSharedShape(r2Vector(-110, i * 11), r2Vector(110, i * 11)); + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + r2InsertCollider(floor, &collider); + + r2FreeSharedShape(shape); + } + for (int row = 0; row < 20; row++) { + for (int col = 0; col < 20; col++) { + for (int i = 0; i < 10; i++) { + for (int j = i; j < 10; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = + r2Vector((i + 1) * 0.5 + (j - i) - 110 + col * 11 + 1 - 0.5, + (2 * i + 1) * 0.5 + row * 11); + rigidBody.canSleep = 0; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 60, 2); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/b2d_rain.c b/c/testbed/examples2d/b2d_rain.c new file mode 100644 index 000000000..b2363a5ec --- /dev/null +++ b/c/testbed/examples2d/b2d_rain.c @@ -0,0 +1,292 @@ +/* Port of examples2d/b2d_rain.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +enum { + ROW_COUNT = 5, + COLUMN_COUNT = 40, + GROUP_SIZE = 5, + GRID_COUNT = 500, + BONE_COUNT = 11, + HIP = 0, + TORSO = 1, + HEAD = 2, + UPPER_LEFT_LEG = 3, + LOWER_LEFT_LEG = 4, + UPPER_RIGHT_LEG = 5, + LOWER_RIGHT_LEG = 6, + UPPER_LEFT_ARM = 7, + LOWER_LEFT_ARM = 8, + UPPER_RIGHT_ARM = 9, + LOWER_RIGHT_ARM = 10 +}; + +const R2Real GRID_SIZE = .5; + +typedef struct BoneDef { + int parent; + R2Real posY; + R2Vector capA, capB; + R2Real capR; + int hasFoot; + R2Real pivotY, limits[2], frameAAngle; +} BoneDef; + +static const BoneDef boneDefs[BONE_COUNT] = { + // hip (root, no joint) + {.parent = -1, + .posY = 0.95, + .capA = {0.0, -0.02}, + .capB = {0.0, 0.02}, + .capR = 0.095, + .hasFoot = 0, + .pivotY = 0.0, + .limits = {0.0, 0.0}, + .frameAAngle = 0.0}, + // torso + {.parent = HIP, + .posY = 1.2, + .capA = {0.0, -0.135}, + .capB = {0.0, 0.135}, + .capR = 0.09, + .hasFoot = 0, + .pivotY = 1.0, + .limits = {-0.25 * R2_PI, 0.0}, + .frameAAngle = 0.0}, + // head + {.parent = TORSO, + .posY = 1.475, + .capA = {0.0, -0.038}, + .capB = {0.0, 0.039}, + .capR = 0.075, + .hasFoot = 0, + .pivotY = 1.4, + .limits = {-0.3 * R2_PI, 0.1 * R2_PI}, + .frameAAngle = 0.0}, + // upper left leg + {.parent = HIP, + .posY = 0.775, + .capA = {0.0, -0.125}, + .capB = {0.0, 0.125}, + .capR = 0.06, + .hasFoot = 0, + .pivotY = 0.9, + .limits = {-0.05 * R2_PI, 0.4 * R2_PI}, + .frameAAngle = 0.0}, + // lower left leg + {.parent = UPPER_LEFT_LEG, + .posY = 0.475, + .capA = {0.0, -0.155}, + .capB = {0.0, 0.125}, + .capR = 0.045, + .hasFoot = 1, + .pivotY = 0.625, + .limits = {-0.5 * R2_PI, -0.02 * R2_PI}, + .frameAAngle = 0.0}, + // upper right leg + {.parent = HIP, + .posY = 0.775, + .capA = {0.0, -0.125}, + .capB = {0.0, 0.125}, + .capR = 0.06, + .hasFoot = 0, + .pivotY = 0.9, + .limits = {-0.05 * R2_PI, 0.4 * R2_PI}, + .frameAAngle = 0.0}, + // lower right leg + {.parent = UPPER_RIGHT_LEG, + .posY = 0.475, + .capA = {0.0, -0.155}, + .capB = {0.0, 0.125}, + .capR = 0.045, + .hasFoot = 1, + .pivotY = 0.625, + .limits = {-0.5 * R2_PI, -0.02 * R2_PI}, + .frameAAngle = 0.0}, + // upper left arm + {.parent = TORSO, + .posY = 1.225, + .capA = {0.0, -0.125}, + .capB = {0.0, 0.125}, + .capR = 0.035, + .hasFoot = 0, + .pivotY = 1.35, + .limits = {-0.1 * R2_PI, 0.8 * R2_PI}, + .frameAAngle = 0.0}, + // lower left arm + {.parent = UPPER_LEFT_ARM, + .posY = 0.975, + .capA = {0.0, -0.125}, + .capB = {0.0, 0.125}, + .capR = 0.03, + .hasFoot = 0, + .pivotY = 1.1, + .limits = {-0.2 * R2_PI, 0.3 * R2_PI}, + .frameAAngle = 0.25 * R2_PI}, + // upper right arm + {.parent = TORSO, + .posY = 1.225, + .capA = {0.0, -0.125}, + .capB = {0.0, 0.125}, + .capR = 0.035, + .hasFoot = 0, + .pivotY = 1.35, + .limits = {-0.1 * R2_PI, 0.8 * R2_PI}, + .frameAAngle = 0.0}, + // lower right arm + {.parent = UPPER_RIGHT_ARM, + .posY = 0.975, + .capA = {0.0, -0.125}, + .capB = {0.0, 0.125}, + .capR = 0.03, + .hasFoot = 0, + .pivotY = 1.1, + .limits = {-0.2 * R2_PI, 0.3 * R2_PI}, + .frameAAngle = 0.25 * R2_PI}, +}; + +typedef struct HumanHandles { + R2RigidBodyHandle bones[BONE_COUNT]; +} HumanHandles; + +static HumanHandles createHuman(Testbed *testbed, R2World *world, R2Vector position, R2Real scale, + R2Real hertz, R2Real damping, uint32_t groupBit) { + HumanHandles human; + const uint32_t bit = UINT32_C(1) << (groupBit % 24); + const R2InteractionGroups groups = {bit, ~bit, 0}; + const R2Vector footPoints[] = {{-.03 * scale, -.185 * scale}, + {.11 * scale, -.185 * scale}, + {.11 * scale, -.16 * scale}, + {-.03 * scale, -.14 * scale}}; + for (size_t i = 0; i < BONE_COUNT; ++i) { + const BoneDef *def = &boneDefs[i]; + R2RigidBodyDesc body = r2DynamicRigidBodyDesc(); + body.canSleep = !testbed->noSleep; + body.position.translation = r2VectorAdd(position, r2Vector(0, def->posY * scale)); + human.bones[i] = r2InsertRigidBody(world, &body); + + R2ColliderDesc collider = r2CapsuleColliderDesc( + r2VectorScale(def->capA, scale), r2VectorScale(def->capB, scale), def->capR * scale); + collider.friction = .2; + collider.collisionGroups = groups; + r2InsertCollider(human.bones[i], &collider); + + if (def->hasFoot) { + R2SharedShape *shape = + r2RoundConvexHullSharedShape((R2VectorView){footPoints, 4}, .015 * scale); + collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + + collider.friction = .05; + collider.collisionGroups = groups; + r2InsertCollider(human.bones[i], &collider); + r2FreeSharedShape(shape); + } + } + const R2Real omega = 2 * R2_PI * hertz, stiffness = omega * omega, + motorDamping = 2 * damping * omega; + for (size_t i = 0; i < BONE_COUNT; ++i) { + const BoneDef *def = &boneDefs[i]; + if (def->parent < 0) { + continue; + } + R2JointDesc joint = r2DefaultJointDesc(); + joint.lockedAxes = 3; + const R2Vector anchorA = r2Vector(0, (def->pivotY - boneDefs[def->parent].posY) * scale); + const R2Vector anchorB = r2Vector(0, (def->pivotY - def->posY) * scale); + joint.localFrame1 = r2Pose(anchorA, r2Rotation(def->frameAAngle)); + joint.localFrame2 = r2TranslationPose(anchorB); + r2JointDesc_SetLimits(&joint, R2_AXIS_ANG_X, def->limits[0], def->limits[1]); + joint.contactsEnabled = 0; + r2JointDesc_SetMotorModel(&joint, R2_AXIS_ANG_X, 0); + r2JointDesc_SetMotorPosition(&joint, R2_AXIS_ANG_X, 0, stiffness, motorDamping); + r2InsertImpulseJoint(human.bones[def->parent], human.bones[i], &joint); + } + return human; +} + +typedef struct RainState { + HumanHandles groups[ROW_COUNT * COLUMN_COUNT][GROUP_SIZE]; + size_t columnCount, columnIndex; +} RainState; + +static void createGroup(Testbed *testbed, R2World *world, RainState *state, size_t row, + size_t col) { + const size_t groupIndex = row * COLUMN_COUNT + col; + const R2Real span = GRID_COUNT * GRID_SIZE, groupDistance = span / COLUMN_COUNT; + R2Real x = -.5 * span + groupDistance * (col + .5); + const R2Real y = 40 + 45 * row; + for (size_t i = 0; i < GROUP_SIZE; ++i) { + state->groups[groupIndex][i] = + createHuman(testbed, world, r2Vector(x, y), 1, 5, .5, (uint32_t)(i + 1)); + x += .5; + } +} + +static void destroyGroup(R2World *world, RainState *state, size_t row, size_t col) { + for (size_t i = 0; i < GROUP_SIZE; ++i) { + for (size_t j = 0; j < BONE_COUNT; ++j) { + r2RemoveRigidBody(state->groups[row * COLUMN_COUNT + col][i].bones[j], 1); + } + } +} + +static void stepRain(Testbed *testbed, R2World *world, RainState *state, size_t stepCount) { + if (stepCount & 0x7) { + return; + } + if (state->columnCount < COLUMN_COUNT) { + const size_t col = state->columnCount; + for (size_t row = 0; row < ROW_COUNT; ++row) { + createGroup(testbed, world, state, row, col); + } + ++state->columnCount; + } else { + const size_t col = state->columnIndex; + for (size_t row = 0; row < ROW_COUNT; ++row) { + destroyGroup(world, state, row, col); + createGroup(testbed, world, state, row, col); + } + state->columnIndex = (state->columnIndex + 1) % COLUMN_COUNT; + } +} + +void tbB2dRain(Testbed *testbed) { + R2World *world = r2NewWorld(); + r2SetGravity(world, r2Vector(0, -10)); + R2RigidBodyDesc builder = r2FixedRigidBodyDesc(); + R2RigidBodyHandle ground = r2InsertRigidBody(world, &builder); + + R2Real y = 0; + for (size_t row = 0; row < ROW_COUNT; ++row) { + R2Real x = -.5 * GRID_COUNT * GRID_SIZE; + for (size_t i = 0; i <= GRID_COUNT; ++i) { + R2ColliderDesc collider = + r2CuboidColliderDesc(r2Vector(.5 * GRID_SIZE, .5 * GRID_SIZE)); + collider.position.translation = r2Vector(x, y); + r2InsertCollider(ground, &collider); + + x += GRID_SIZE; + } + y += 45; + } + RainState *state = calloc(1, sizeof(*state)); + if (!state) { + abort(); + } + tbCamera2(testbed, 0, 110, 2); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + size_t stepCount = 0; + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + stepRain(testbed, world, state, stepCount); + ++stepCount; + r2Step(world, NULL, NULL); + } + } + free(state); + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/b2d_smash.c b/c/testbed/examples2d/b2d_smash.c new file mode 100644 index 000000000..ac146693c --- /dev/null +++ b/c/testbed/examples2d/b2d_smash.c @@ -0,0 +1,45 @@ +/* Port of examples2d/b2d_smash.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB2dSmash(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, 0)); + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-20, 0); + rigidBody.linvel = r2Vector(40, 0); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(4, 4)); + collider.density = 8; + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + for (int i = 0; i < 120; i++) { + for (int j = 0; j < 80; j++) { + rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i * 0.4 + 30, (j - 40) * 0.4); + rigidBody.sleeping = 1; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.2, 0.2)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 20, 0, 8); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/b2d_spinner.c b/c/testbed/examples2d/b2d_spinner.c new file mode 100644 index 000000000..6ce27f936 --- /dev/null +++ b/c/testbed/examples2d/b2d_spinner.c @@ -0,0 +1,94 @@ +/* Port of examples2d/b2d_spinner.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB2dSpinner(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, -10)); + R2RigidBodyHandle ground; + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + groundBody.canSleep = !testbed->noSleep; + ground = r2InsertRigidBody(world, &groundBody); + + R2Vector points[360]; + R2Vector position = r2Vector(40, 0); + uint32_t indices[720]; + R2Real angle = -2 * R2_PI / 360; + for (uint32_t i = 0; i < 360; i++) { + points[i] = r2VectorAdd(position, r2Vector(0, 32)); + position = r2Vector(cos(angle) * position.x - sin(angle) * position.y, + sin(angle) * position.x + cos(angle) * position.y); + indices[2 * i] = i; + indices[2 * i + 1] = (i + 1) % 360; + } + R2SharedShape *shape = r2OrientedPolylineSharedShape( + (R2VectorView){points, 360}, (R2EdgeView){(const R2Edge *)indices, 360}); + R2ColliderDesc wall = r2DefaultColliderDesc(); + wall.shape.kind = R2_SHAPE_DESC_SHARED; + wall.shape.sharedShape = shape; + wall.friction = 0.1; + r2InsertCollider(ground, &wall); + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 12); + rigidBody.canSleep = 0; + R2ColliderDesc collider = r2RoundCuboidColliderDesc(r2Vector(0.4, 20), 0.2); + collider.friction = 0; + R2RigidBodyHandle spinner; + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + spinner = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(spinner, &collider); + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2Vector(0, 12); + joint.localFrame2.translation = r2Vector(0, 0); + r2JointDesc_SetMotor(&joint, R2_AXIS_ANG_X, 0, 5, 0, 1.0e5); + r2JointDesc_SetMotorMaxForce(&joint, R2_AXIS_ANG_X, 1.0e9); + R2ImpulseJointHandle jointHandle = r2InsertImpulseJoint(ground, spinner, &joint); + R2Real x = -23; + R2Real y = 2; + for (int i = 0; i < 6076; i++) { + R2ColliderDesc particleCollider; + if (i % 3 == 0) { + particleCollider = + r2CapsuleColliderDesc(r2Vector(-0.25, 0.0), r2Vector(0.25, 0.0), 0.25); + } else if (i % 3 == 1) { + particleCollider = r2BallColliderDesc(0.35); + } else { + particleCollider = r2CuboidColliderDesc(r2Vector(0.35, 0.35)); + } + particleCollider.density = 0.25; + particleCollider.friction = 0.1; + particleCollider.restitution = 0.1; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x, y); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &particleCollider); + + x += 0.5; + if (x >= 23) { + x = -23; + y += 0.5; + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 32, 6); + + r2FreeSharedShape(shape); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/b2d_tumbler.c b/c/testbed/examples2d/b2d_tumbler.c new file mode 100644 index 000000000..e165c4412 --- /dev/null +++ b/c/testbed/examples2d/b2d_tumbler.c @@ -0,0 +1,62 @@ +/* Port of examples2d/b2d_tumbler.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB2dTumbler(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, -10)); + R2RigidBodyHandle ground; + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + groundBody.canSleep = !testbed->noSleep; + ground = r2InsertRigidBody(world, &groundBody); + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 10); + rigidBody.canSleep = 0; + R2RigidBodyHandle drum; + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + drum = r2InsertRigidBody(world, &rigidBody); + const R2Vector half[] = {r2Vector(0.5, 10), r2Vector(0.5, 10), r2Vector(10, 0.5), + r2Vector(10, 0.5)}; + const R2Vector off[] = {r2Vector(10, 0), r2Vector(-10, 0), r2Vector(0, 10), r2Vector(0, -10)}; + for (int i = 0; i < 4; i++) { + R2ColliderDesc collider = r2CuboidColliderDesc(half[i]); + collider.position.translation = off[i]; + collider.density = 50; + r2InsertCollider(drum, &collider); + } + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2Vector(0, 10); + joint.localFrame2.translation = r2Vector(0, 0); + r2JointDesc_SetMotor(&joint, R2_AXIS_ANG_X, 0, R2_PI / 180 * 25, 0, 1.0e5); + r2JointDesc_SetMotorMaxForce(&joint, R2_AXIS_ANG_X, 1.0e8); + r2InsertImpulseJoint(ground, drum, &joint); + for (int i = 0; i < 45; i++) { + for (int j = 0; j < 45; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-9 + j * 0.4, 1 + i * 0.4); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.125, 0.125)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 10, 12); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/b2d_washer.c b/c/testbed/examples2d/b2d_washer.c new file mode 100644 index 000000000..5297ad456 --- /dev/null +++ b/c/testbed/examples2d/b2d_washer.c @@ -0,0 +1,71 @@ +/* Port of examples2d/b2d_washer.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB2dWasher(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, -10)); + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + groundBody.canSleep = !testbed->noSleep; + r2InsertRigidBody(world, &groundBody); + + R2RigidBodyDesc rigidBody = r2KinematicVelocityBasedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 10); + rigidBody.angvel = R2_PI / 180 * 25; + rigidBody.linvel = r2Vector(0.001, -0.002); + R2RigidBodyHandle washer; + rigidBody.canSleep = !testbed->noSleep; + washer = r2InsertRigidBody(world, &rigidBody); + R2Real angle = R2_PI / 18; + R2Vector u1 = r2Vector(1, 0); + for (int i = 0; i < 36; i++) { + R2Vector u2 = i == 35 ? r2Vector(1, 0) + : r2Vector(cos(angle) * u1.x - sin(angle) * u1.y, + sin(angle) * u1.x + cos(angle) * u1.y); + R2Vector a1 = r2Vector(cos(angle * 0.1) * u1.x + sin(angle * 0.1) * u1.y, + -sin(angle * 0.1) * u1.x + cos(angle * 0.1) * u1.y); + R2Vector a2 = r2Vector(cos(angle * 0.1) * u2.x - sin(angle * 0.1) * u2.y, + sin(angle * 0.1) * u2.x + cos(angle * 0.1) * u2.y); + for (int part = 0; part < (i % 9 == 0 ? 2 : 1); part++) { + R2Vector vertices[4]; + R2Vector left = part ? u1 : a1; + R2Vector right = part ? u2 : a2; + R2Real rmin = part ? 14 : 16; + R2Real rmax = part ? 16 : 18; + vertices[0] = r2VectorScale(left, rmin); + vertices[1] = r2VectorScale(left, rmax); + vertices[2] = r2VectorScale(right, rmin); + vertices[3] = r2VectorScale(right, rmax); + R2ColliderDesc collider = r2DefaultColliderDesc(); + r2ShapeDesc_SetConvexHull(&collider.shape, (R2VectorView){vertices, TB_COUNT(vertices)}); + r2InsertCollider(washer, &collider); + } + u1 = u2; + } + for (int i = 0; i < 90; i++) { + for (int j = 0; j < 90; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-9.9 + j * 0.21, 0.1 + i * 0.21); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.1, 0.1)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 10, 12); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/ccd2.c b/c/testbed/examples2d/ccd2.c new file mode 100644 index 000000000..7e5617029 --- /dev/null +++ b/c/testbed/examples2d/ccd2.c @@ -0,0 +1,102 @@ +/* Port of examples2d/ccd2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static int same(R2RigidBodyHandle handle, R2RigidBodyHandle handleB) { + return handle.world == handleB.world && handle.index == handleB.index && + handle.generation == handleB.generation; +} + +void tbCcd2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyHandle ground = {0}; + R2RigidBodyHandle sensor = {0}; + R2RigidBodyDesc floor = r2FixedRigidBodyDesc(); + floor.position.translation = r2Vector(0, 0); + floor.ccdEnabled = 1; + R2ColliderDesc boxCollider = r2CuboidColliderDesc(r2Vector(25, 0.1)); + floor.canSleep = !testbed->noSleep; + ground = r2InsertRigidBody(world, &floor); + r2InsertCollider(ground, &boxCollider); + + sensor = ground; + const R2Real x[] = {-3, 6, 2.5}; + for (int i = 0; i < 3; i++) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.1, 25)); + collider.position.translation = r2Vector(x[i], 0); + if (i == 2) { + collider.isSensor = 1; + collider.activeEvents = R2_COLLISION_EVENTS; + } + r2InsertCollider(ground, &collider); + } + R2Pose poses[] = {r2TranslationPose(r2Vector(0, 0.35)), r2TranslationPose(r2Vector(-0.35, 0)), + r2TranslationPose(r2Vector(0.35, 0))}; + R2SharedShape *shapes[3] = {NULL}; + shapes[0] = r2CuboidSharedShape(r2Vector(0.4, 0.05)); + shapes[1] = r2CuboidSharedShape(r2Vector(0.05, 0.4)); + shapes[2] = r2CuboidSharedShape(r2Vector(0.05, 0.4)); + R2ColliderDesc shape; + R2CompoundShapeDesc shapeParts[3]; + for (size_t part = 0; part < 3; ++part) { + shapeParts[part].pose = poses[part]; + shapeParts[part].shape = r2DefaultShapeDesc(); + shapeParts[part].shape.kind = R2_SHAPE_DESC_SHARED; + shapeParts[part].shape.sharedShape = ((const R2SharedShape *const *)shapes)[part]; + } + shape = r2DefaultColliderDesc(); + shape.shape.kind = R2_SHAPE_DESC_COMPOUND; + shape.shape.children = (R2CompoundShapeView){shapeParts, 3}; + + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 6; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i * 0.82 - 1.96, j * 0.82 + 4.41); + rigidBody.linvel = r2Vector(100, -10); + rigidBody.ccdEnabled = 1; + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &shape); + } + } + for (size_t part = 0; part < 3; part++) { + r2FreeSharedShape(shapes[part]); + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + R2EventCollector *eventHandler = r2NewEventCollector(); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2EventCollector_Clear(eventHandler); + r2Step(world, NULL, eventHandler); + + size_t n = r2EventCollector_CollisionEvents(eventHandler, NULL, 0); + R2CollisionEvent *events = calloc(n ? n : 1, sizeof(*events)); + if (!events) { + abort(); + } + n = r2EventCollector_CollisionEvents(eventHandler, events, n); + for (size_t i = 0; i < n; i++) { + R2ColliderHandle colliderHandles[] = {events[i].collider1, events[i].collider2}; + for (size_t j = 0; j < 2; j++) { + R2RigidBodyHandle handle = r2Collider_Parent(colliderHandles[j]); + if (!same(handle, ground) && !same(handle, sensor)) { + tbBodyColor(testbed, handle, events[i].started ? 1 : 0.5f, + events[i].started ? 1 : 0.5f, events[i].started ? 0 : 1, 1); + } + } + } + free(events); + } + } + r2FreeEventCollector(eventHandler); + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/character_controller2.c b/c/testbed/examples2d/character_controller2.c new file mode 100644 index 000000000..6789640ae --- /dev/null +++ b/c/testbed/examples2d/character_controller2.c @@ -0,0 +1,164 @@ +/* Port of examples2d/character_controller2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#include "utils/character.h" + +void tbCharacterController2(Testbed *testbed) { + R2World *world = r2NewWorld(); + + const R2Real groundSize = 5, groundHeight = .1; + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -groundHeight); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(groundSize, groundHeight)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Character with stronger gravity and predictive CCD for PID control. */ + R2RigidBodyHandle characterHandle; + { + R2RigidBodyDesc rigidBody = r2KinematicPositionBasedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-3, 5); + rigidBody.gravityScale = 10; + rigidBody.softCcdPrediction = 10; + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CapsuleYColliderDesc(.3, .15); + characterHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(characterHandle, &collider); + } + tbBodyColor(testbed, characterHandle, .8f, .1f, .1f, 1); + /* Cubes. */ + const int num = 8; + const R2Real rad = .1, shift = rad * 2, centerx = shift * (num / 2), centery = rad; + for (int j = 0; j < 4; ++j) { + for (int i = 0; i < num; ++i) { + const R2Real x = i * shift - centerx, y = j * shift + centery; + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x, y); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(rad, rad)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Stairs. */ + const R2Real stairWidth = 1, stairHeight = .1; + for (int i = 0; i < 10; ++i) { + const R2Real x = i * stairWidth / 2, y = i * stairHeight * 1.5 + 3; + { + R2ColliderDesc collider = + r2CuboidColliderDesc(r2Vector(stairWidth / 2, stairHeight / 2)); + collider.position.translation = r2Vector(x, y); + r2InsertColliderWithoutParent(world, &collider); + } + } + /* Climbable and unclimbable slopes. */ + const R2Real slopeAngle = .2, slopeSize = 2, impossibleSlopeSize = 2; + const R2Real impossibleSlopeAngle = .9; + { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(slopeSize, groundHeight)); + collider.position.translation = r2Vector(groundSize + slopeSize, -groundHeight + .4); + collider.position.rotation = r2Rotation(slopeAngle); + r2InsertColliderWithoutParent(world, &collider); + } + { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(slopeSize, groundHeight)); + collider.position.translation = + r2Vector(groundSize + slopeSize * 2 + impossibleSlopeSize - .9, -groundHeight + 2.3); + collider.position.rotation = r2Rotation(impossibleSlopeAngle); + r2InsertColliderWithoutParent(world, &collider); + } + /* Wall and horizontal ledge. */ + const R2Vector wallPos = + r2Vector(groundSize + slopeSize * 2 + impossibleSlopeSize + .35, -groundHeight + 2.5 * 2.3); + { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(2, groundHeight)); + collider.position.translation = wallPos; + collider.position.rotation = r2Rotation(R2_PI / 2); + r2InsertColliderWithoutParent(world, &collider); + } + { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(2, groundHeight)); + collider.position.translation = wallPos; + r2InsertColliderWithoutParent(world, &collider); + } + /* Moving platform. */ + R2RigidBodyHandle platformHandle; + { + R2RigidBodyDesc rigidBody = r2KinematicVelocityBasedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-8, 0); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(2, groundHeight)); + platformHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(platformHandle, &collider); + } + /* Wavy heightfield. */ + const int nsubdivs = 20; + R2Real heights[21]; + for (int i = 0; i <= nsubdivs; ++i) { + heights[i] = cos(i * 10.0 / nsubdivs / 2) * 1.5; + } + { + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_HEIGHTFIELD; + collider.shape.heights = (R2RealView){heights, (21) * (1)}; + collider.shape.rows = 21; + collider.shape.columns = 1; + collider.shape.scale = r2Vector(10, 1); + collider.shape.flags = 0; + collider.position.translation = r2Vector(-8, 5); + r2InsertColliderWithoutParent(world, &collider); + } + /* Tilting dynamic body with a limited joint. */ + R2RigidBodyDesc ground = r2FixedRigidBodyDesc(); + ground.position.translation = r2Vector(0, 5); + R2RigidBodyHandle groundHandle, handle; + groundHandle = r2InsertRigidBody(world, &ground); + + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(1, 0.1)); + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + } + { + R2JointDesc joint = r2RevoluteJointDesc(); + r2JointDesc_SetLimits(&joint, R2_AXIS_ANG_X, -.3, .3); + r2InsertImpulseJoint(groundHandle, handle, &joint); + } + CharacterControlMode controlMode = CHARACTER_KINEMATIC; + R2KinematicCharacterController *controller = NULL; + R2PidController *pid = NULL; + controller = r2NewKinematicCharacterController(); + pid = r2NewPidController(); + r2KinematicCharacterController_SetSlopes(controller, impossibleSlopeAngle - .02, + impossibleSlopeAngle - .02); + tbCamera2(testbed, 0, 1, 100); + + tbSetWorld(testbed, world); + size_t stepId = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + ++stepId; + + R2Real dt = r2TimeStep(world); + const R2Vector linvel = r2Vector(sin(stepId * dt * 2) * 2, sin(stepId * dt * 5) * 1.5); + + r2RigidBody_SetLinvel(platformHandle, linvel, 1); + updateCharacter(testbed, world, &controlMode, controller, pid, characterHandle); + } + } + r2FreePidController(pid); + r2FreeKinematicCharacterController(controller); + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/collision_groups2.c b/c/testbed/examples2d/collision_groups2.c new file mode 100644 index 000000000..2c7099bda --- /dev/null +++ b/c/testbed/examples2d/collision_groups2.c @@ -0,0 +1,50 @@ +/* Port of examples2d/collision_groups2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbCollisionGroups2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyHandle floor; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.1); + R2ColliderDesc boxCollider = r2CuboidColliderDesc(r2Vector(5, 0.1)); + rigidBody.canSleep = !testbed->noSleep; + floor = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(floor, &boxCollider); + for (int i = 1; i <= 2; i++) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(1, 0.1)); + collider.position.translation = r2Vector(0, i); + collider.collisionGroups = (R2InteractionGroups){(uint32_t)i, (uint32_t)i, R2_GROUPS_AND}; + R2ColliderHandle colliderHandle = r2InsertCollider(floor, &collider); + tbColliderColor(testbed, colliderHandle, 0, i == 1, i == 2, 1); + } + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 8; i++) { + uint32_t group = i % 2 ? 2 : 1; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.1, 0.1)); + collider.collisionGroups = (R2InteractionGroups){group, group, R2_GROUPS_AND}; + R2RigidBodyHandle handle; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i * 0.2 - 0.8, j * 0.2 + 2.5); + rigidBody.canSleep = !testbed->noSleep; + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + tbBodyColor(testbed, handle, 0, group == 1, group == 2, 1); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 1, 100); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/convex_polygons2.c b/c/testbed/examples2d/convex_polygons2.c new file mode 100644 index 000000000..faa8d3b66 --- /dev/null +++ b/c/testbed/examples2d/convex_polygons2.c @@ -0,0 +1,55 @@ +/* Port of examples2d/convex_polygons2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "example_math.h" + +void tbConvexPolygons2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(30, 1.2)); + groundBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle groundBodyHandle = r2InsertRigidBody(world, &groundBody); + r2InsertCollider(groundBodyHandle, &collider); + + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 30, 60); + rigidBody.position = r2Pose(r2Vector(side * 30, 60), r2Rotation(R2_PI / 2)); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(60, 1.2)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int i = 0; i < 14; i++) { + for (int j = 0; j < 56; j++) { + R2Vector points[10]; + for (size_t k = 0; k < 10; k++) { + R2Real x = exampleRandom(&testbed->randomState) * 4; + R2Real y = exampleRandom(&testbed->randomState) * 4; + points[k] = r2Vector(x, y); + } + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i * 4 - 28, j * 8 + 4); + R2ColliderDesc collider = r2DefaultColliderDesc(); + r2ShapeDesc_SetConvexHull(&collider.shape, (R2VectorView){points, 10}); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 50, 10); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/damping2.c b/c/testbed/examples2d/damping2.c new file mode 100644 index 000000000..cd6042c4a --- /dev/null +++ b/c/testbed/examples2d/damping2.c @@ -0,0 +1,39 @@ +/* Port of examples2d/damping2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDamping2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, 0)); + const int num = 10; + const R2Real subdiv = (R2Real)1 / num; + for (int i = 0; i < num; i++) { + R2Real x = (R2Real)sin(i * subdiv * R2_PI * 2); + R2Real y = (R2Real)cos(i * subdiv * R2_PI * 2); + R2RigidBodyDesc body = r2DynamicRigidBodyDesc(); + body.position.translation = r2Vector(x, y); + body.linvel = r2Vector(x * 10, y * 10); + body.angvel = 100; + body.linearDamping = (i + 1) * subdiv * 10; + body.angularDamping = (num - i) * subdiv * 10; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.2, 0.2)); + body.canSleep = !testbed->noSleep; + R2RigidBodyHandle bodyHandle = r2InsertRigidBody(world, &body); + r2InsertCollider(bodyHandle, &collider); + } + /* Set up the viewer. */ + tbCamera2(testbed, 3, 2, 50); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/debug_angular_limits2.c b/c/testbed/examples2d/debug_angular_limits2.c new file mode 100644 index 000000000..bc0b958f5 --- /dev/null +++ b/c/testbed/examples2d/debug_angular_limits2.c @@ -0,0 +1,60 @@ +/* Port of examples2d/debug_angular_limits2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugAngularLimits2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, 0)); + int multi = (int)tbSetting(testbed, "Multibody joints", 0, 0, 1, 1); + const R2Real limits[][2] = {{-45, 45}, {0, 270}, {135, 225}, {-350, 0}, {-200, 200}}; + for (int i = 0; i < 5; i++) { + for (int dir = 1; dir >= -1; dir -= 2) { + R2Vector position = r2Vector(i * 4, dir > 0 ? 0 : -4); + R2RigidBodyHandle anchor; + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = position; + R2ColliderDesc collider = r2BallColliderDesc(0.2); + groundBody.canSleep = !testbed->noSleep; + anchor = r2InsertRigidBody(world, &groundBody); + r2InsertCollider(anchor, &collider); + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2VectorAdd(position, r2Vector(1, 0)); + rigidBody.angularDamping = 3; + rigidBody.canSleep = 0; + R2RigidBodyHandle handle; + R2ColliderDesc boxCollider = r2CuboidColliderDesc(r2Vector(0.5, 0.1)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &boxCollider); + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2Vector(0, 0); + joint.localFrame2.translation = r2Vector(-1, 0); + r2JointDesc_SetLimits(&joint, R2_AXIS_ANG_X, limits[i][0] * R2_PI / 180, + limits[i][1] * R2_PI / 180); + r2JointDesc_SetMotor(&joint, R2_AXIS_ANG_X, 0, dir * 5, 0, 20); + if (multi) { + r2InsertMultibodyJoint(anchor, handle, &joint); + } else { + r2InsertImpulseJoint(anchor, handle, &joint); + } + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 8, -2, 40); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/debug_box_ball2.c b/c/testbed/examples2d/debug_box_ball2.c new file mode 100644 index 000000000..dfe7ca5b7 --- /dev/null +++ b/c/testbed/examples2d/debug_box_ball2.c @@ -0,0 +1,39 @@ +/* Port of examples2d/debug_box_ball2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugBoxBall2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc floor = r2FixedRigidBodyDesc(); + floor.position.translation = r2Vector(0, -1); + floor.position = r2Pose(r2Vector(0, -1), r2Rotation(R2_PI / 4)); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(1, 1)); + floor.canSleep = !testbed->noSleep; + R2RigidBodyHandle floorHandle = r2InsertRigidBody(world, &floor); + r2InsertCollider(floorHandle, &collider); + R2RigidBodyDesc ball = r2DynamicRigidBodyDesc(); + ball.position.translation = r2Vector(0, 3); + ball.canSleep = 0; + R2ColliderDesc ballCollider = r2BallColliderDesc(1); + if (testbed->noSleep) { + ball.canSleep = 0; + ball.sleeping = 0; + } + floorHandle = r2InsertRigidBody(world, &ball); + r2InsertCollider(floorHandle, &ballCollider); + /* Set up the viewer. */ + tbCamera2(testbed, 0, 0, 50); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/debug_compression2.c b/c/testbed/examples2d/debug_compression2.c new file mode 100644 index 000000000..72eb9aab8 --- /dev/null +++ b/c/testbed/examples2d/debug_compression2.c @@ -0,0 +1,56 @@ +/* Port of examples2d/debug_compression2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugCompression2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, side * 32); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(75, 2)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + R2RigidBodyHandle handle[2] = {0}; + for (int i = 0; i < 2; i++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i ? 73 : -73, 0); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(2, 30)); + rigidBody.canSleep = !testbed->noSleep; + handle[i] = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle[i], &collider); + } + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i * 7.5 - 30, j * 7.5 - 26.25); + R2ColliderDesc collider = r2BallColliderDesc(3.75); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 0, 50); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + + for (int i = 0; i < 2; i++) { + r2RigidBody_ResetForces(handle[i], 1); + r2RigidBody_AddForce(handle[i], + r2Vector((i ? -1 : 1) * (R2Real)testbed->step * 10000, 0), 1); + } + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/debug_intersection2.c b/c/testbed/examples2d/debug_intersection2.c new file mode 100644 index 000000000..cd5b724e4 --- /dev/null +++ b/c/testbed/examples2d/debug_intersection2.c @@ -0,0 +1,58 @@ +/* Port of examples2d/debug_intersection2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugIntersection2(Testbed *testbed) { + R2World *world = r2NewWorld(); + const R2Real rad = 1; + R2ColliderDesc collider = r2BallColliderDesc(rad); + const int count = 100; + R2RigidBodyHandle handles[100 * 100]; + for (int x = 0; x < count; ++x) { + for (int y = 0; y < count; ++y) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = + r2Vector((x - count / 2.0) * rad * 3, (y - count / 2.0) * rad * 3); + R2RigidBodyHandle handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + + handles[x * count + y] = handle; + tbBodyColor(testbed, handle, x / (float)count, (count - y) / (float)count, .5, 1); + } + } + + tbCamera2(testbed, 0, 0, 50); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + size_t stepId = 0; + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + ++stepId; + const R2Real slowTime = stepId / 3.0; + + R2SharedShape *ball = r2BallSharedShape(rad / 2); + const R2Pose pose = r2TranslationPose(r2Vector(cos(slowTime) * 10, sin(slowTime) * 10)); + size_t intersectionCount = r2IntersectShape(world, NULL, pose, ball, NULL, 0); + R2ColliderHandle *intersections = malloc(intersectionCount * sizeof(*intersections)); + if (intersectionCount && !intersections) { + abort(); + } + intersectionCount = + r2IntersectShape(world, NULL, pose, ball, intersections, intersectionCount); + r2FreeSharedShape(ball); + + for (size_t i = 0; i < intersectionCount; ++i) { + for (size_t j = 0; j < TB_COUNT(handles); ++j) { + tbBodyColor(testbed, handles[j], .5, .5, .5, 1); + } + + R2RigidBodyHandle bodyHandle = r2Collider_Parent(intersections[i]); + tbBodyColor(testbed, bodyHandle, 1, 0, 0, 1); + } + free(intersections); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/debug_many_colliders2.c b/c/testbed/examples2d/debug_many_colliders2.c new file mode 100644 index 000000000..7b50f23bb --- /dev/null +++ b/c/testbed/examples2d/debug_many_colliders2.c @@ -0,0 +1,148 @@ +/* Port of examples2d/debug_many_colliders2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static const R2Vector part0[] = {V(525.0 * 0.01, 104.0 * 0.01, 0), V(540.0 * 0.01, 104.0 * 0.01, 0), + V(419.0 * 0.01, 119.0 * 0.01, 0)}; + +static const R2Vector part1[] = {V(419.0 * 0.01, 119.0 * 0.01, 0), V(449.0 * 0.01, 74.0 * 0.01, 0), + V(510.0 * 0.01, 59.0 * 0.01, 0), V(525.0 * 0.01, 104.0 * 0.01, 0)}; + +static const R2Vector part2[] = {V(299.0 * 0.01, 134.0 * 0.01, 0), V(419.0 * 0.01, 119.0 * 0.01, 0), + V(540.0 * 0.01, 104.0 * 0.01, 0), + V(540.0 * 0.01, 134.0 * 0.01, 0)}; + +static const R2Vector part3[] = {V(315.0 * 0.01, 450.0 * 0.01, 0), V(179.0 * 0.01, 284.0 * 0.01, 0), + V(179.0 * 0.01, 254.0 * 0.01, 0), + V(224.0 * 0.01, 224.0 * 0.01, 0)}; + +static const R2Vector part4[] = { + V(224.0 * 0.01, 224.0 * 0.01, 0), V(224.0 * 0.01, 223.0 * 0.01, 0), + V(299.0 * 0.01, 134.0 * 0.01, 0), V(540.0 * 0.01, 134.0 * 0.01, 0), + V(555.0 * 0.01, 134.0 * 0.01, 0), V(555.0 * 0.01, 209.0 * 0.01, 0), + V(359.0 * 0.01, 465.0 * 0.01, 0), V(315.0 * 0.01, 450.0 * 0.01, 0)}; + +static const R2Vector part5[] = { + V(119.0 * 0.01, 359.0 * 0.01, 0), V(134.0 * 0.01, 314.0 * 0.01, 0), + V(179.0 * 0.01, 284.0 * 0.01, 0), V(315.0 * 0.01, 450.0 * 0.01, 0), + V(315.0 * 0.01, 465.0 * 0.01, 0), V(300.0 * 0.01, 465.0 * 0.01, 0)}; + +static const R2Vector part6[] = {V(164.0 * 0.01, 510.0 * 0.01, 0), V(134.0 * 0.01, 495.0 * 0.01, 0), + V(240.0 * 0.01, 510.0 * 0.01, 0)}; + +static const R2Vector part7[] = {V(240.0 * 0.01, 510.0 * 0.01, 0), V(240.0 * 0.01, 525.0 * 0.01, 0), + V(164.0 * 0.01, 525.0 * 0.01, 0), + V(164.0 * 0.01, 510.0 * 0.01, 0)}; + +static const R2Vector part8[] = { + V(134.0 * 0.01, 495.0 * 0.01, 0), V(104.0 * 0.01, 359.0 * 0.01, 0), + V(119.0 * 0.01, 359.0 * 0.01, 0), V(300.0 * 0.01, 465.0 * 0.01, 0), + V(270.0 * 0.01, 510.0 * 0.01, 0), V(240.0 * 0.01, 510.0 * 0.01, 0)}; + +static const R2Vector part9[] = {V(615.0 * 0.01, 269.0 * 0.01, 0), V(660.0 * 0.01, 284.0 * 0.01, 0), + V(660.0 * 0.01, 359.0 * 0.01, 0)}; + +static const R2Vector part10[] = { + V(673.6813186813187 * 0.01, 390.010989010989 * 0.01, 0), V(660.0 * 0.01, 359.0 * 0.01, 0), + V(675.0 * 0.01, 359.0 * 0.01, 0), V(675.0 * 0.01, 389.0 * 0.01, 0)}; + +static const R2Vector part11[] = { + V(675.0 * 0.01, 389.0 * 0.01, 0), V(735.0 * 0.01, 434.0 * 0.01, 0), + V(735.0 * 0.01, 495.0 * 0.01, 0), V(720.0 * 0.01, 495.0 * 0.01, 0), + V(673.6813186813187 * 0.01, 390.010989010989 * 0.01, 0)}; + +static const R2Vector part12[] = { + V(645.0 * 0.01, 540.0 * 0.01, 0), V(645.0 * 0.01, 555.0 * 0.01, 0), + V(494.0 * 0.01, 555.0 * 0.01, 0), V(494.0 * 0.01, 540.0 * 0.01, 0)}; + +static const R2Vector part13[] = { + V(705.0 * 0.01, 525.0 * 0.01, 0), V(645.0 * 0.01, 540.0 * 0.01, 0), + V(494.0 * 0.01, 540.0 * 0.01, 0), V(464.0 * 0.01, 540.0 * 0.01, 0), + V(464.0 * 0.01, 525.0 * 0.01, 0)}; + +static const R2Vector part14[] = { + V(660.0 * 0.01, 359.0 * 0.01, 0), V(720.0 * 0.01, 495.0 * 0.01, 0), + V(705.0 * 0.01, 525.0 * 0.01, 0), V(464.0 * 0.01, 525.0 * 0.01, 0), + V(434.0 * 0.01, 525.0 * 0.01, 0), V(434.0 * 0.01, 510.0 * 0.01, 0)}; + +static const R2Vector part15[] = { + V(660.0 * 0.01, 359.0 * 0.01, 0), V(434.0 * 0.01, 510.0 * 0.01, 0), + V(404.0 * 0.01, 510.0 * 0.01, 0), V(404.0 * 0.01, 495.0 * 0.01, 0)}; + +static const R2Vector part16[] = { + V(404.0 * 0.01, 495.0 * 0.01, 0), V(359.0 * 0.01, 495.0 * 0.01, 0), + V(359.0 * 0.01, 465.0 * 0.01, 0), V(555.0 * 0.01, 209.0 * 0.01, 0), + V(570.0 * 0.01, 209.0 * 0.01, 0), V(615.0 * 0.01, 269.0 * 0.01, 0), + V(660.0 * 0.01, 359.0 * 0.01, 0)}; + +static R2ColliderDesc polygon(const R2Vector *position, size_t n) { + R2ColliderDesc collider = r2DefaultColliderDesc(); + r2ShapeDesc_SetConvexHull(&collider.shape, (R2VectorView){position, n}); + return collider; +} + +void tbDebugManyColliders2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, 0)); + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + rigidBody.canSleep = 0; + rigidBody.angvel = 1; + R2RigidBodyHandle handle; + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + handle = r2InsertRigidBody(world, &rigidBody); + for (int i = 0; i < 130; i++) { + R2ColliderDesc collider = polygon(part0, TB_COUNT(part0)); + r2InsertCollider(handle, &collider); + collider = polygon(part1, TB_COUNT(part1)); + r2InsertCollider(handle, &collider); + collider = polygon(part2, TB_COUNT(part2)); + r2InsertCollider(handle, &collider); + collider = polygon(part3, TB_COUNT(part3)); + r2InsertCollider(handle, &collider); + collider = polygon(part4, TB_COUNT(part4)); + r2InsertCollider(handle, &collider); + collider = polygon(part5, TB_COUNT(part5)); + r2InsertCollider(handle, &collider); + collider = polygon(part6, TB_COUNT(part6)); + r2InsertCollider(handle, &collider); + collider = polygon(part7, TB_COUNT(part7)); + r2InsertCollider(handle, &collider); + collider = polygon(part8, TB_COUNT(part8)); + r2InsertCollider(handle, &collider); + collider = polygon(part9, TB_COUNT(part9)); + r2InsertCollider(handle, &collider); + collider = polygon(part10, TB_COUNT(part10)); + r2InsertCollider(handle, &collider); + collider = polygon(part11, TB_COUNT(part11)); + r2InsertCollider(handle, &collider); + collider = polygon(part12, TB_COUNT(part12)); + r2InsertCollider(handle, &collider); + collider = polygon(part13, TB_COUNT(part13)); + r2InsertCollider(handle, &collider); + collider = polygon(part14, TB_COUNT(part14)); + r2InsertCollider(handle, &collider); + collider = polygon(part15, TB_COUNT(part15)); + r2InsertCollider(handle, &collider); + collider = polygon(part16, TB_COUNT(part16)); + r2InsertCollider(handle, &collider); + } + /* Set up the viewer. */ + tbCamera2(testbed, 5, 3, 40); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/debug_self_intersect2.c b/c/testbed/examples2d/debug_self_intersect2.c new file mode 100644 index 000000000..bd78767c5 --- /dev/null +++ b/c/testbed/examples2d/debug_self_intersect2.c @@ -0,0 +1,228 @@ +/* Port of examples2d/debug_self_intersect2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static R2SoftBodyDesc strip(R2Vector center) { + R2SoftBodyDesc builder = r2GridSoftBodyDesc(center, r2Vector(3, .15), 3, 2); + static const uint32_t pinned[] = {0, 1, 4, 5}; + r2SoftBodyDesc_SetPinnedParticles(&builder, (R2IndexView){(const uint32_t *)pinned, 4}); + builder.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){3, 1}); + builder.selfContacts = 1; + return builder; +} + +static void reload(R2SoftBodyHandle bullet, R2Vector cannonOrigin) { + size_t n = r2SoftBody_NumParticles(bullet); + for (size_t i = 0; i < n; ++i) { + const R2Real angle = 2 * R2_PI * i / n; + const R2Vector p = + r2VectorAdd(cannonOrigin, r2Vector(.3 * cos(angle), 2.5 + .3 * sin(angle))); + r2SoftBody_SetParticlePosition(bullet, i, p); + r2SoftBody_SetParticleVelocity(bullet, i, r2Vector(0, -150)); + } +} + +static R2SoftBodyHandle eightBlob(Testbed *testbed, R2World *world, R2Vector center, R2Real radius, + size_t n) { + R2SoftBodyDesc builder = r2DiskSoftBodyDesc(center, radius, n); + builder.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){4, 1}); + builder.selfContacts = 1; + builder.particleMass = .05; + builder.canSleep = !testbed->noSleep; + R2SoftBodyHandle handle = r2InsertSoftBody(world, &builder); + + return handle; +} + +static void geronoEight(R2SoftBodyHandle h, R2Vector center, R2Real r) { + size_t n = r2SoftBody_NumParticles(h); + for (size_t i = 0; i < n; ++i) { + const R2Real t = 2 * R2_PI * i / n; + r2SoftBody_SetParticlePosition( + h, i, r2VectorAdd(center, r2Vector(r * cos(t), r * sin(t) * cos(t)))); + } +} + +static void asymEight(R2SoftBodyHandle h, R2Vector center, R2Real rb, R2Real rs) { + size_t n = r2SoftBody_NumParticles(h); + const size_t nBig = (size_t)(n * rb / (rb + rs)); + for (size_t i = 0; i < n; ++i) { + R2Vector p; + if (i < nBig) { + const R2Real t = 2 * R2_PI * i / nBig; + p = r2VectorAdd(center, r2Vector(-rb + rb * cos(t), rb * sin(t))); + } else { + const R2Real t = 2 * R2_PI * (i - nBig) / (n - nBig); + p = r2VectorAdd(center, r2Vector(rs - rs * cos(t), -rs * sin(t))); + } + r2SoftBody_SetParticlePosition(h, i, p); + } +} + +static void eightFloor(Testbed *testbed, R2World *world, R2Real x) { + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x, -6.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(3, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } +} + +void tbDebugSelfIntersect2(Testbed *testbed) { + R2World *world = r2NewWorld(); + + R2SoftBodyHandle captured; + { + R2SoftBodyDesc builder = strip(r2Vector(-16, 0)); + builder.canSleep = !testbed->noSleep; + captured = r2InsertSoftBody(world, &builder); + } + + r2SoftBody_SetParticlePosition(captured, 3, r2Vector(-15, -.4)); + R2SoftBodyHandle loaded; + { + R2SoftBodyDesc builder = strip(r2Vector(-6, 0)); + builder.canSleep = !testbed->noSleep; + loaded = r2InsertSoftBody(world, &builder); + } + + r2SoftBody_SetParticlePosition(loaded, 3, r2Vector(-5, -.4)); + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-5.7, 0.8); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2BallColliderDesc(.45); + collider.density = 20; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(2, -1); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(2.5, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + R2SoftBodyHandle blob; + R2SoftBodyDesc blobBuilder = r2DiskSoftBodyDesc(r2Vector(2, 0), .5, 16); + blobBuilder.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){4, 1}); + blobBuilder.selfContacts = 1; + blobBuilder.particleMass = .05; + blobBuilder.canSleep = !testbed->noSleep; + blob = r2InsertSoftBody(world, &blobBuilder); + + r2SoftBody_SetParticlePosition(blob, 4, r2Vector(2, -.8)); + const R2Vector grabOrigin = {12, 0}; + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(12, -0.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(2.5, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + uint32_t bottomRow[7]; + for (uint32_t i = 0; i < 7; ++i) { + bottomRow[i] = i * 3; + } + R2SoftBodyHandle groundStrip; + R2SoftBodyDesc groundBuilder = + r2GridSoftBodyDesc(r2Vector(12, 0.15), r2Vector(1.5, 0.15), 7, 3); + r2SoftBodyDesc_SetPinnedParticles(&groundBuilder, (R2IndexView){(const uint32_t *)bottomRow, 7}); + groundBuilder.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){5, 1}); + groundBuilder.selfContacts = 1; + groundBuilder.canSleep = !testbed->noSleep; + groundStrip = r2InsertSoftBody(world, &groundBuilder); + + const uint32_t grabbed = 11; + + R2Vector anchor = r2SoftBody_ParticlePosition(groundStrip, grabbed); + uint32_t cluster = r2SoftBody_AddCluster(groundStrip, &grabbed, 1); + + R2RigidBodyHandle proxy = r2SoftBody_ClusterProxy(groundStrip, cluster); + R2RigidBodyDesc mouseBuilder = r2KinematicPositionBasedRigidBodyDesc(); + mouseBuilder.position.translation = anchor; + R2RigidBodyHandle mouse = r2InsertRigidBody(world, &mouseBuilder); + R2JointDesc joint = r2DefaultJointDesc(); + joint.lockedAxes = 0; + r2JointDesc_SetMotorPosition(&joint, R2_AXIS_LIN_X, 0, 1000, 50); + r2JointDesc_SetMotorPosition(&joint, R2_AXIS_LIN_Y, 0, 1000, 50); + r2InsertImpulseJoint(mouse, proxy, &joint); + + const R2Vector cannonOrigin = {20.5, 0}; + const uint32_t cannonPins[] = {0, 1, 16, 17}; + R2SoftBodyDesc cannonStrip = r2GridSoftBodyDesc(cannonOrigin, r2Vector(2, 0.1), 9, 2); + r2SoftBodyDesc_SetPinnedParticles(&cannonStrip, (R2IndexView){(const uint32_t *)cannonPins, 4}); + cannonStrip.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){10, 1}); + cannonStrip.selfContacts = 1; + cannonStrip.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &cannonStrip); + + R2SoftBodyHandle bullet; + R2SoftBodyDesc bulletBuilder = r2DiskSoftBodyDesc(r2Vector(20.5, 2.5), .3, 12); + bulletBuilder.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){20, 1}); + bulletBuilder.particleMass = .2; + bulletBuilder.selfContacts = 1; + bulletBuilder.canSleep = !testbed->noSleep; + bullet = r2InsertSoftBody(world, &bulletBuilder); + + reload(bullet, cannonOrigin); + eightFloor(testbed, world, -14); + R2SoftBodyHandle asym = eightBlob(testbed, world, r2Vector(-14, -5.35), .6, 24); + asymEight(asym, r2Vector(-13.9, -5.35), .55, .3); + eightFloor(testbed, world, -7); + R2SoftBodyHandle sym = eightBlob(testbed, world, r2Vector(-7, -5.35), .6, 24); + geronoEight(sym, r2Vector(-7, -5.35), .6); + eightFloor(testbed, world, 1); + R2SoftBodyHandle e1 = eightBlob(testbed, world, r2Vector(0.65, -5.35), .6, 24); + geronoEight(e1, r2Vector(0.65, -5.35), .6); + R2SoftBodyHandle e2 = eightBlob(testbed, world, r2Vector(1.35, -5.05), .6, 24); + geronoEight(e2, r2Vector(1.35, -5.05), .6); + eightFloor(testbed, world, 8); + R2SoftBodyHandle swallowed = eightBlob(testbed, world, r2Vector(8, -5.35), .6, 24); + asymEight(swallowed, r2Vector(7.8, -5.35), .55, .3); + eightBlob(testbed, world, r2Vector(8.65, -5.35), 0.55, 20); + eightFloor(testbed, world, 15); + R2SoftBodyHandle overlapped = eightBlob(testbed, world, r2Vector(15, -5.35), .6, 24); + asymEight(overlapped, r2Vector(15.1, -5.35), .55, .3); + eightBlob(testbed, world, r2Vector(14.15, -5.35), 0.5, 20); + eightFloor(testbed, world, 22); + R2SoftBodyHandle threaded = eightBlob(testbed, world, r2Vector(22, -5.35), .6, 24); + asymEight(threaded, r2Vector(21.8, -5.35), .55, .3); + eightBlob(testbed, world, r2Vector(22.1, -5.35), 0.22, 14); + tbCamera2(testbed, 0, -2.5, 38); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + R2Real t = 0; + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + const R2Real cycle = fmod(t, 6); + R2Vector target; + if (cycle < 1) { + target = r2Vector(0, .5 - .48 * cycle); + } else if (cycle < 4) { + const R2Real w = 2 * R2_PI * (cycle - 1); + target = r2Vector(.3 * sin(w), .02 + .03 * (1 - cos(2 * w))); + } else if (cycle < 5) { + target = r2Vector(0, .02 + .48 * (cycle - 4)); + } else { + target = r2Vector(0, .5); + } + + r2RigidBody_SetNextKinematicTranslation(mouse, r2VectorAdd(grabOrigin, target)); + r2RigidBody_WakeUp(proxy, 1); + + R2Real dt = r2TimeStep(world); + if (fmod(t, 4) < dt && t > 0) { + reload(bullet, cannonOrigin); + } + r2Step(world, NULL, NULL); + t += dt; + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/debug_total_overlap2.c b/c/testbed/examples2d/debug_total_overlap2.c new file mode 100644 index 000000000..dcfce6994 --- /dev/null +++ b/c/testbed/examples2d/debug_total_overlap2.c @@ -0,0 +1,30 @@ +/* Port of examples2d/debug_total_overlap2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugTotalOverlap2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + for (int i = 0; i < 100; i++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 0, 50); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/debug_vertical_column2.c b/c/testbed/examples2d/debug_vertical_column2.c new file mode 100644 index 000000000..fb075b9e6 --- /dev/null +++ b/c/testbed/examples2d/debug_vertical_column2.c @@ -0,0 +1,38 @@ +/* Port of examples2d/debug_vertical_column2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugVerticalColumn2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2ColliderDesc floor = r2CuboidColliderDesc(r2Vector(1, 1)); + floor.friction = 0.3; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &floor); + for (int i = 0; i < 80; i++) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + collider.friction = 0.3; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, i + 1.5); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 5); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/drum2.c b/c/testbed/examples2d/drum2.c new file mode 100644 index 000000000..d953d6246 --- /dev/null +++ b/c/testbed/examples2d/drum2.c @@ -0,0 +1,55 @@ +/* Port of examples2d/drum2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDrum2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + for (int i = 0; i < 30; i++) { + for (int j = 0; j < 30; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i * 0.4 - 6, j * 0.4 - 6); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.2, 0.2)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + R2RigidBodyHandle drum = {0}; + R2RigidBodyDesc platformBody = r2KinematicVelocityBasedRigidBodyDesc(); + platformBody.position.translation = r2Vector(0, 0); + platformBody.canSleep = !testbed->noSleep; + drum = r2InsertRigidBody(world, &platformBody); + + for (int side = -1; side <= 1; side += 2) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(10, 0.25)); + collider.position.translation = r2Vector(0, side * 10); + r2InsertCollider(drum, &collider); + + collider = r2CuboidColliderDesc(r2Vector(0.25, 10)); + collider.position.translation = r2Vector(side * 10, 0); + r2InsertCollider(drum, &collider); + for (int x = -1; x <= 1; x += 2) { + collider = r2BallColliderDesc(1.25); + collider.position.translation = r2Vector(x * 6, side * 6); + r2InsertCollider(drum, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 1, 40); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + + r2RigidBody_SetAngvel(drum, -0.15, 1); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/heightfield2.c b/c/testbed/examples2d/heightfield2.c new file mode 100644 index 000000000..2cb6cfe08 --- /dev/null +++ b/c/testbed/examples2d/heightfield2.c @@ -0,0 +1,54 @@ +/* Port of examples2d/heightfield2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbHeightfield2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2Real heights[2001]; + for (int i = 0; i <= 2000; i++) { + heights[i] = i == 0 || i == 2000 ? 8 : (R2Real)cos(i * 50.0 / 2000) * 2; + } + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_HEIGHTFIELD; + collider.shape.heights = (R2RealView){heights, (2001) * (1)}; + collider.shape.rows = 2001; + collider.shape.columns = 1; + collider.shape.scale = r2Vector(50, 1); + collider.shape.flags = 0; + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + + for (int i = 0; i < 20; i++) { + for (int j = 0; j < 20; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i - 10, j + 3.5); + R2ColliderDesc collider; + if (j % 2) { + collider = r2BallColliderDesc(0.5); + } else { + collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + } + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 0, 10); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/inv_pyramid2.c b/c/testbed/examples2d/inv_pyramid2.c new file mode 100644 index 000000000..aba3d65d2 --- /dev/null +++ b/c/testbed/examples2d/inv_pyramid2.c @@ -0,0 +1,40 @@ +/* Port of examples2d/inv_pyramid2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbInvPyramid2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(10, 1)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + R2Real rad = 0.5; + R2Real y = rad; + for (int i = 0; i < 6; i++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, y + 1); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(rad, rad)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + y += rad + rad * 2; + rad *= 2; + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/inverse_kinematics2.c b/c/testbed/examples2d/inverse_kinematics2.c new file mode 100644 index 000000000..69c4b81cc --- /dev/null +++ b/c/testbed/examples2d/inverse_kinematics2.c @@ -0,0 +1,75 @@ +/* Port of examples2d/inverse_kinematics2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbInverseKinematics2(Testbed *testbed) { + R2World *world = r2NewWorld(); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.01); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(1, 0.01)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + + const int numSegments = 10; + R2RigidBodyDesc body = r2FixedRigidBodyDesc(); + R2RigidBodyHandle lastBody = r2InsertRigidBody(world, &body); + R2MultibodyJointHandle lastLink = {NULL, UINT32_MAX, UINT32_MAX}; + for (int i = 0; i < numSegments; ++i) { + const R2Real size = 1.0 / numSegments; + R2RigidBodyHandle newBody; + /* Sensors draw the links; IK does not require colliders. */ + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + rigidBody.canSleep = 0; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(size / 8, size / 2)); + collider.density = 0; + collider.isSensor = 1; + newBody = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(newBody, &collider); + } + R2JointDesc linkAb = r2RevoluteJointDesc(); + linkAb.localFrame1.translation = r2Vector(0, size / 2 * (i != 0)); + linkAb.localFrame2.translation = r2Vector(0, -size / 2); + lastLink = r2InsertMultibodyJoint(lastBody, newBody, &linkAb); + + lastBody = newBody; + } + tbCamera2(testbed, 0, 0, 300); + + tbSetWorld(testbed, world); + R2Real *displacements = NULL; + size_t capacity = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + if (!testbed->cursorValid) { + continue; + } + size_t ndofs = r2MultibodyJoint_Ndofs(lastLink); + if (capacity < ndofs) { + R2Real *resized = realloc(displacements, ndofs * sizeof(*resized)); + if (!resized) { + abort(); + } + displacements = resized; + capacity = ndofs; + } + memset(displacements, 0, ndofs * sizeof(*displacements)); + R2InverseKinematicsOptions options = r2DefaultInverseKinematicsOptions(); + options.constrained_axes = 3; /* Linear axes only. */ + const R2Pose target = r2TranslationPose(testbed->cursor); + r2MultibodyJoint_InverseKinematics(lastLink, &options, target, NULL, NULL, + displacements, ndofs); + r2MultibodyJoint_ApplyDisplacements(lastLink, displacements, ndofs); + } + } + free(displacements); + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/joint_motor_position2.c b/c/testbed/examples2d/joint_motor_position2.c new file mode 100644 index 000000000..195d1d305 --- /dev/null +++ b/c/testbed/examples2d/joint_motor_position2.c @@ -0,0 +1,59 @@ +/* Port of examples2d/joint_motor_position2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbJointMotorPosition2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyHandle ground; + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + groundBody.canSleep = !testbed->noSleep; + ground = r2InsertRigidBody(world, &groundBody); + for (int row = 0; row < 2; row++) { + for (int num = 0; num < (row ? 8 : 9); num++) { + R2Real x = -6 + 1.5 * num; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x, row ? 4.5 : 2); + rigidBody.canSleep = 0; + if (row) { + rigidBody.position = r2Pose(r2Vector(x, 4.5), r2Rotation(R2_PI)); + } + R2RigidBodyHandle handle; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.1, 0.5)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2Vector(x, row ? 5 : 1.5); + joint.localFrame2.translation = r2Vector(0, -0.5); + R2Real angle = -R2_PI + R2_PI / 4 * num; + if (row) { + r2JointDesc_SetMotor(&joint, R2_AXIS_ANG_X, 0, 1.5, 0, 30); + r2JointDesc_SetMotorMaxForce(&joint, R2_AXIS_ANG_X, 100); + r2JointDesc_SetLimits(&joint, R2_AXIS_ANG_X, -R2_PI, angle); + } else { + r2JointDesc_SetMotor(&joint, R2_AXIS_ANG_X, angle, 0, 1000, 150); + } + r2InsertImpulseJoint(ground, handle, &joint); + } + } + r2SetGravity(world, r2Vector(0, 0)); + /* Set up the viewer. */ + tbCamera2(testbed, 0, 0, 40); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/joints2.c b/c/testbed/examples2d/joints2.c new file mode 100644 index 000000000..9bb0e33e9 --- /dev/null +++ b/c/testbed/examples2d/joints2.c @@ -0,0 +1,55 @@ +/* Port of examples2d/joints2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbJoints2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + int variable = (int)tbSetting(testbed, "Variable softness", 0, 0, 1, 1); + R2RigidBodyHandle *handles = calloc(1, 100 * sizeof(*handles)); + if (!handles) { + abort(); + } + for (int k = 0; k < 10; k++) { + for (int i = 0; i < 10; i++) { + int fixed = i == 0 && k == 0; + R2RigidBodyHandle handle; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.bodyType = fixed ? R2_FIXED : R2_DYNAMIC; + rigidBody.position.translation = r2Vector(k, -i); + R2ColliderDesc collider = r2BallColliderDesc(0.4); + rigidBody.canSleep = !testbed->noSleep; + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + for (int dir = 0; dir < 2; dir++) { + if (dir ? k > 0 : i > 0) { + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2Vector(0, 0); + joint.localFrame2.translation = dir ? r2Vector(-1, 0) : r2Vector(0, 1); + if (variable) { + R2Real scale = (i > k ? i : k) + 1; + joint.softness = (R2SpringCoefficients){5 * scale, 0.1 * scale}; + } + r2InsertImpulseJoint(handles[k * 10 + i - (dir ? 10 : 1)], handle, + &joint); + } + } + handles[k * 10 + i] = handle; + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 4.0, -4.0, 20); + free(handles); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/locked_rotations2.c b/c/testbed/examples2d/locked_rotations2.c new file mode 100644 index 000000000..d313db88c --- /dev/null +++ b/c/testbed/examples2d/locked_rotations2.c @@ -0,0 +1,46 @@ +/* Port of examples2d/locked_rotations2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbLockedRotations2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.1); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(5, 0.1)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + + R2RigidBodyDesc rectangle = r2DynamicRigidBodyDesc(); + rectangle.position.translation = r2Vector(0, 3); + rectangle.lockedAxes = R2_LOCK_TRANSLATION_X | R2_LOCK_TRANSLATION_Y; + R2ColliderDesc boxCollider = r2CuboidColliderDesc(r2Vector(2, 0.6)); + rectangle.canSleep = !testbed->noSleep; + rigidBodyHandle = r2InsertRigidBody(world, &rectangle); + r2InsertCollider(rigidBodyHandle, &boxCollider); + + R2RigidBodyDesc capsule = r2DynamicRigidBodyDesc(); + capsule.position.translation = r2Vector(0, 5); + capsule.position = r2Pose(r2Vector(0, 5), r2Rotation(1)); + capsule.lockedAxes = R2_LOCK_ROTATION_Z; + R2ColliderDesc capsuleCollider = r2CapsuleYColliderDesc(0.6, 0.4); + capsule.canSleep = !testbed->noSleep; + rigidBodyHandle = r2InsertRigidBody(world, &capsule); + r2InsertCollider(rigidBodyHandle, &capsuleCollider); + + /* Set up the viewer. */ + tbCamera2(testbed, 0, 0, 40); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/multi_pendulum2.c b/c/testbed/examples2d/multi_pendulum2.c new file mode 100644 index 000000000..82f22cf0e --- /dev/null +++ b/c/testbed/examples2d/multi_pendulum2.c @@ -0,0 +1,69 @@ +/* Port of examples2d/multi_pendulum2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "example_math.h" + +void tbMultiPendulum2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + r2SetGravity(world, r2Vector(0, -9.81)); + int randomize = (int)tbSetting(testbed, "Randomize", 0, 0, 1, 1); + int count = (int)tbSetting(testbed, "Pendulum Count", 32, 1, 64, 1); + int segments = (int)tbSetting(testbed, "Pendulum Segments", 4, 1, 40, 1); + R2Real spacing = 2 * segments + 4; + int cols = (int)ceil(sqrt(count)); + int rows = (count + cols - 1) / cols; + for (int i = 0; i < count; i++) { + R2Vector end = r2Vector((i % cols - (cols - 1) * 0.5) * spacing, + ((rows - 1) * 0.5 - i / cols) * spacing); + R2Vector local = r2Vector(0, 0); + R2RigidBodyHandle parent; + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = end; + groundBody.canSleep = !testbed->noSleep; + parent = r2InsertRigidBody(world, &groundBody); + + for (int n = 0; n < segments; n++) { + R2Real angle = randomize ? (exampleRandom(&testbed->randomState) - 0.5) * R2_PI : 0; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = end; + rigidBody.position = r2Pose(end, r2Rotation(angle)); + rigidBody.canSleep = 0; + R2SharedShape *shape = r2CapsuleSharedShape(r2Vector(-1, 0), r2Vector(1, 0), 0.2); + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + collider.position.translation = r2Vector(1, 0); + R2RigidBodyHandle handle; + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = local; + joint.localFrame2.translation = r2Vector(0, 0); + joint.contactsEnabled = 0; + r2InsertImpulseJoint(parent, handle, &joint); + parent = handle; + local = r2Vector(2, 0); + end = r2VectorAdd(end, r2Vector(2 * cos(angle), 2 * sin(angle))); + + r2FreeSharedShape(shape); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 0, (float)fmin(1000 / ((cols > rows ? cols : rows) * spacing), 20)); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/one_way_platforms2.c b/c/testbed/examples2d/one_way_platforms2.c new file mode 100644 index 000000000..ce3b97a64 --- /dev/null +++ b/c/testbed/examples2d/one_way_platforms2.c @@ -0,0 +1,96 @@ +/* Port of examples2d/one_way_platforms2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +typedef struct OneWayPlatformHook { + R2ColliderHandle platform1, platform2; +} OneWayPlatformHook; + +static int sameCollider(R2ColliderHandle a, R2ColliderHandle b) { + return a.world == b.world && a.index == b.index && a.generation == b.generation; +} + +static void RAPIER_CALL modifySolverContacts(void *userData, const R2ReadContext *read, + R2ColliderHandle collider1, R2ColliderHandle collider2, + R2ContactModificationContext *context) { + (void)read; + const OneWayPlatformHook *hook = userData; + R2Vector allowedLocalN1 = r2Vector(0, 0); + /* Flip the allowed normal when the platform is collider2. */ + if (sameCollider(collider1, hook->platform1)) { + allowedLocalN1 = r2Vector(0, 1); + } else if (sameCollider(collider2, hook->platform1)) { + allowedLocalN1 = r2Vector(0, -1); + } + if (sameCollider(collider1, hook->platform2)) { + allowedLocalN1 = r2Vector(0, -1); + } else if (sameCollider(collider2, hook->platform2)) { + allowedLocalN1 = r2Vector(0, 1); + } + r2ContactModificationContext_UpdateAsOnewayPlatform(context, allowedLocalN1, .1); + const R2Real tangentVelocity = + sameCollider(collider1, hook->platform1) || sameCollider(collider2, hook->platform2) ? -12 + : 12; + r2ContactModificationContext_SetTangentVelocity(context, r2Vector(tangentVelocity, 0)); +} + +void tbOneWayPlatforms2(Testbed *testbed) { + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(25, 0.5)); + collider.position.translation = r2Vector(30, 2); + collider.activeHooks = R2_MODIFY_SOLVER_CONTACTS; + R2RigidBodyHandle handle; + OneWayPlatformHook platformHook; + handle = r2InsertRigidBody(world, &rigidBody); + platformHook.platform1 = r2InsertCollider(handle, &collider); + collider = r2CuboidColliderDesc(r2Vector(25, 0.5)); + collider.position.translation = r2Vector(-30, -2); + collider.activeHooks = R2_MODIFY_SOLVER_CONTACTS; + platformHook.platform2 = r2InsertCollider(handle, &collider); + R2PhysicsHooks physicsHooks = {0}; + physicsHooks.user_data = &platformHook; + physicsHooks.modify_solver_contacts_context = modifySolverContacts; + tbCamera2(testbed, 0, 0, 20); + + tbSetWorld(testbed, world); + size_t stepId = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, &physicsHooks, NULL); + ++stepId; + size_t bodyCount = r2RigidBodyCount(world); + /* Spawn cubes periodically and reverse gravity below the lower platform. */ + if (stepId % 200 == 0 && bodyCount <= 7) { + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(20, 10); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(1.5, 2)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + size_t activeCount = r2ActiveRigidBodies(world, NULL, 0); + R2RigidBodyHandle *active = malloc(activeCount * sizeof(*active)); + if (activeCount && !active) { + abort(); + } + activeCount = r2ActiveRigidBodies(world, active, activeCount); + for (size_t i = 0; i < activeCount; ++i) { + R2Vector position = r2RigidBody_Translation(active[i]); + if (position.y > 1) { + r2RigidBody_SetGravityScale(active[i], 1, 0); + } else if (position.y < -1) { + r2RigidBody_SetGravityScale(active[i], -1, 0); + } + } + free(active); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/pin_slot_joint2.c b/c/testbed/examples2d/pin_slot_joint2.c new file mode 100644 index 000000000..b8efd4def --- /dev/null +++ b/c/testbed/examples2d/pin_slot_joint2.c @@ -0,0 +1,76 @@ +/* Port of examples2d/pin_slot_joint2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#include "utils/character.h" + +void tbPinSlotJoint2(Testbed *testbed) { + R2World *world = r2NewWorld(); + + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.1); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(3, 0.1)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + R2RigidBodyHandle characterHandle, cubeHandle, ballHandle; + { + R2RigidBodyDesc rigidBody = r2KinematicPositionBasedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0.3); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.15, 0.3)); + characterHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(characterHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(1, 1); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.4, 0.4)); + cubeHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(cubeHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(1, 1); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2BallColliderDesc(.1); + ballHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(ballHandle, &collider); + } + { + R2JointDesc fixedJoint = r2FixedJointDesc(); + fixedJoint.localFrame1.translation = r2Vector(0, 0); + fixedJoint.localFrame2.translation = r2Vector(0, -0.4); + r2InsertImpulseJoint(cubeHandle, ballHandle, &fixedJoint); + } + { + R2JointDesc pinSlotJoint = r2PinSlotJointDesc(r2Vector(1 / sqrt(2), 1 / sqrt(2))); + pinSlotJoint.localFrame1.translation = r2Vector(2, 2); + pinSlotJoint.localFrame2.translation = r2Vector(0, 0.4); + r2JointDesc_SetLimits(&pinSlotJoint, R2_AXIS_LIN_X, -1, INFINITY); + r2InsertImpulseJoint(characterHandle, cubeHandle, &pinSlotJoint); + } + CharacterControlMode controlMode = CHARACTER_KINEMATIC; + R2KinematicCharacterController *controller = NULL; + R2PidController *pid = NULL; + controller = r2NewKinematicCharacterController(); + pid = r2NewPidController(); + tbCamera2(testbed, 0, 1, 100); + + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + updateCharacter(testbed, world, &controlMode, controller, pid, characterHandle); + } + } + r2FreePidController(pid); + r2FreeKinematicCharacterController(controller); + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/platform2.c b/c/testbed/examples2d/platform2.c new file mode 100644 index 000000000..66320ee50 --- /dev/null +++ b/c/testbed/examples2d/platform2.c @@ -0,0 +1,64 @@ +/* Port of examples2d/platform2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbPlatform2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.1); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(10, 0.1)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 6; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i * 0.4 - 1.2, j * 0.4 + 3.24); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.2, 0.2)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + R2RigidBodyHandle velocityBasedPlatformHandle = {0}; + R2RigidBodyHandle positionBasedPlatformHandle = {0}; + R2RigidBodyDesc platformBody = r2KinematicVelocityBasedRigidBodyDesc(); + platformBody.position.translation = r2Vector(-2, 2.3); + R2ColliderDesc boxCollider = r2CuboidColliderDesc(r2Vector(2, 0.2)); + platformBody.canSleep = !testbed->noSleep; + velocityBasedPlatformHandle = r2InsertRigidBody(world, &platformBody); + r2InsertCollider(velocityBasedPlatformHandle, &boxCollider); + R2RigidBodyDesc positionBasedPlatform = r2KinematicPositionBasedRigidBodyDesc(); + positionBasedPlatform.position.translation = r2Vector(-2, 4.3); + R2ColliderDesc positionBasedCollider = r2CuboidColliderDesc(r2Vector(2, 0.2)); + positionBasedPlatform.canSleep = !testbed->noSleep; + positionBasedPlatformHandle = r2InsertRigidBody(world, &positionBasedPlatform); + r2InsertCollider(positionBasedPlatformHandle, &positionBasedCollider); + /* Set up the viewer. */ + tbCamera2(testbed, 0, 1, 40); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + + R2Real dt = r2TimeStep(world); + R2Real time = (R2Real)(testbed->step + 1) * dt; + R2Vector velocity = r2Vector(sin(time) * 5, sin(time * 5)); + + r2RigidBody_SetLinvel(velocityBasedPlatformHandle, velocity, 1); + + R2Vector position = r2RigidBody_Translation(positionBasedPlatformHandle); + r2RigidBody_SetNextKinematicTranslation( + positionBasedPlatformHandle, + r2VectorAdd(position, r2VectorScale(velocity, dt))); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/polyline2.c b/c/testbed/examples2d/polyline2.c new file mode 100644 index 000000000..6afcfe55a --- /dev/null +++ b/c/testbed/examples2d/polyline2.c @@ -0,0 +1,54 @@ +/* Port of examples2d/polyline2.rs. */ +#include "testbed.h" +#include "rapier_math.h" +#include "rapier_helpers.h" + +void tbPolyline2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2Vector points[2000]; + R2Edge edges[1999]; + points[0] = r2Vector(-25, 40); + points[1999] = r2Vector(25, 40); + for (int i = 1; i < 1999; i++) { + points[i] = r2Vector(-25 + i * 0.025, cos(i * 0.025) * 2); + } + for (uint32_t i = 0; i < 1999; i++) { + edges[i] = (R2Edge){i, i + 1}; + } + R2RigidBodyDesc ground = r2FixedRigidBodyDesc(); + ground.canSleep = !testbed->noSleep; + R2ColliderDesc groundCollider = r2DefaultColliderDesc(); + r2ShapeDesc_SetPolyline(&groundCollider.shape, (R2VectorView){points, TB_COUNT(points)}, + (R2EdgeView){edges, TB_COUNT(edges)}, 0); + R2RigidBodyHandle groundHandle = r2InsertRigidBody(world, &ground); + r2InsertCollider(groundHandle, &groundCollider); + for (int i = 0; i < 20; i++) { + for (int j = 0; j < 20; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i - 10, j + 3.5); + R2ColliderDesc collider; + if (j % 2) { + collider = r2BallColliderDesc(0.5); + } else { + collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + } + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 0, 10); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/pyramid2.c b/c/testbed/examples2d/pyramid2.c new file mode 100644 index 000000000..96c952611 --- /dev/null +++ b/c/testbed/examples2d/pyramid2.c @@ -0,0 +1,46 @@ +/* Port of examples2d/pyramid2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbPyramid2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + const R2Real groundThickness = 1; + const R2Real rad = 0.5; + const R2Real shift = rad * 2; + const int num = 10; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(10, groundThickness)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + const R2Real centerx = shift * num / 2; + const R2Real centery = shift / 2 + groundThickness + rad * 1.5; + for (int i = 0; i < num; i++) { + for (int j = i; j < num; j++) { + R2Real x = i * shift / 2 + (j - i) * shift - centerx; + R2Real y = i * shift + centery; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x, y); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(rad, rad)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/restitution2.c b/c/testbed/examples2d/restitution2.c new file mode 100644 index 000000000..3fb6559fe --- /dev/null +++ b/c/testbed/examples2d/restitution2.c @@ -0,0 +1,41 @@ +/* Port of examples2d/restitution2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbRestitution2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2ColliderDesc floor = r2CuboidColliderDesc(r2Vector(20, 1)); + floor.restitution = 1; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -1); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &floor); + const int num = 10; + for (int j = 0; j < 2; j++) { + for (int i = 0; i <= num; i++) { + R2ColliderDesc collider = r2BallColliderDesc(0.5); + collider.restitution = (R2Real)i / num; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector((i - num / 2.0) * 2, 10 * (j + 1)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 1, 25); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/rope_joints2.c b/c/testbed/examples2d/rope_joints2.c new file mode 100644 index 000000000..0af20a320 --- /dev/null +++ b/c/testbed/examples2d/rope_joints2.c @@ -0,0 +1,77 @@ +/* Port of examples2d/rope_joints2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#include "utils/character.h" + +void tbRopeJoints2(Testbed *testbed) { + R2World *world = r2NewWorld(); + + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.1); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.75, 0.1)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-0.85, 0.75); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.1, 0.75)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0.85, 0.75); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.1, 0.75)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Manually controlled character, tethered to a ball. */ + R2RigidBodyHandle characterHandle; + { + R2RigidBodyDesc rigidBody = r2KinematicPositionBasedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0.3); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.15, 0.3)); + characterHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(characterHandle, &collider); + } + R2RigidBodyHandle childHandle; + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(1, 1); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2BallColliderDesc(.04); + childHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(childHandle, &collider); + } + { + R2JointDesc joint = r2RopeJointDesc(2); + r2InsertImpulseJoint(characterHandle, childHandle, &joint); + } + CharacterControlMode controlMode = CHARACTER_KINEMATIC; + R2KinematicCharacterController *controller = NULL; + R2PidController *pid = NULL; + controller = r2NewKinematicCharacterController(); + pid = r2NewPidController(); + tbCamera2(testbed, 0, 1, 100); + + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + updateCharacter(testbed, world, &controlMode, controller, pid, characterHandle); + } + } + r2FreePidController(pid); + r2FreeKinematicCharacterController(controller); + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/s2d_arch.c b/c/testbed/examples2d/s2d_arch.c new file mode 100644 index 000000000..d6f8fa850 --- /dev/null +++ b/c/testbed/examples2d/s2d_arch.c @@ -0,0 +1,92 @@ +/* Port of examples2d/s2d_arch.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbS2dArch(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + const R2Vector ps1[] = {r2Vector(16.0 * 0.25, 0.0 * 0.25), + r2Vector(14.93803712795643 * 0.25, 5.133601056842984 * 0.25), + r2Vector(13.79871746027416 * 0.25, 10.24928069555078 * 0.25), + r2Vector(12.56252963284711 * 0.25, 15.34107019122473 * 0.25), + r2Vector(11.20040987372525 * 0.25, 20.39856541571217 * 0.25), + r2Vector(9.66521217819836 * 0.25, 25.40369899225096 * 0.25), + r2Vector(7.87179930638133 * 0.25, 30.3179337000085 * 0.25), + r2Vector(5.635199558196225 * 0.25, 35.03820717801641 * 0.25), + r2Vector(2.405937953536585 * 0.25, 39.09554102558315 * 0.25)}; + const R2Vector ps2[] = {r2Vector(24.0 * 0.25, 0.0 * 0.25), + r2Vector(22.33619528222415 * 0.25, 6.02299846205841 * 0.25), + r2Vector(20.54936888969905 * 0.25, 12.00964361211476 * 0.25), + r2Vector(18.60854610798073 * 0.25, 17.9470321677465 * 0.25), + r2Vector(16.46769273811807 * 0.25, 23.81367936585418 * 0.25), + r2Vector(14.05325025774858 * 0.25, 29.57079353071012 * 0.25), + r2Vector(11.23551045834022 * 0.25, 35.13775818285372 * 0.25), + r2Vector(7.752568160730571 * 0.25, 40.30450679009583 * 0.25), + r2Vector(3.016931552701656 * 0.25, 44.28891593799322 * 0.25)}; + R2SharedShape *shape = r2SegmentSharedShape(r2Vector(-100, 0), r2Vector(100, 0)); + R2ColliderDesc floor = r2DefaultColliderDesc(); + floor.shape.kind = R2_SHAPE_DESC_SHARED; + floor.shape.sharedShape = shape; + floor.friction = 0.6; + r2InsertColliderWithoutParent(world, &floor); + for (int side = 0; side < 2; side++) { + for (int i = 0; i < 8; i++) { + R2Vector vertices[4]; + if (!side) { + vertices[0] = ps1[i]; + vertices[1] = ps2[i]; + vertices[2] = ps2[i + 1]; + vertices[3] = ps1[i + 1]; + } else { + vertices[0] = r2Vector(-ps2[i].x, ps2[i].y); + vertices[1] = r2Vector(-ps1[i].x, ps1[i].y); + vertices[2] = r2Vector(-ps1[i + 1].x, ps1[i + 1].y); + vertices[3] = r2Vector(-ps2[i + 1].x, ps2[i + 1].y); + } + R2ColliderDesc collider = r2DefaultColliderDesc(); + r2ShapeDesc_SetConvexHull(&collider.shape, (R2VectorView){vertices, 4}); + collider.friction = 0.6; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + R2Vector position[] = {ps1[8], ps2[8], r2Vector(-ps1[8].x, ps1[8].y), + r2Vector(-ps2[8].x, ps2[8].y)}; + R2ColliderDesc objectCollider = r2DefaultColliderDesc(); + r2ShapeDesc_SetConvexHull(&objectCollider.shape, (R2VectorView){position, 4}); + objectCollider.friction = 0.6; + R2RigidBodyDesc dynamicBody = r2DynamicRigidBodyDesc(); + dynamicBody.position.translation = r2Vector(0, 0); + dynamicBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle dynamicBodyHandle = r2InsertRigidBody(world, &dynamicBody); + r2InsertCollider(dynamicBodyHandle, &objectCollider); + + for (int i = 0; i < 4; i++) { + objectCollider = r2CuboidColliderDesc(r2Vector(2, 0.5)); + objectCollider.friction = 0.6; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0.5 + ps2[8].y + i); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &objectCollider); + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + + r2FreeSharedShape(shape); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/s2d_ball_and_chain.c b/c/testbed/examples2d/s2d_ball_and_chain.c new file mode 100644 index 000000000..b507e2310 --- /dev/null +++ b/c/testbed/examples2d/s2d_ball_and_chain.c @@ -0,0 +1,69 @@ +/* Port of examples2d/s2d_ball_and_chain.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbS2dBallAndChain(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyHandle ground; + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + groundBody.canSleep = !testbed->noSleep; + ground = r2InsertRigidBody(world, &groundBody); + + R2RigidBodyHandle prev = ground; + for (int i = 0; i < 40; i++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0 + 0.5 + i, 20); + rigidBody.linearDamping = 0.1; + rigidBody.angularDamping = 0.1; + R2SharedShape *shape = r2CapsuleSharedShape(r2Vector(-0.5, 0), r2Vector(0.5, 0), 0.125); + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + collider.friction = 0.6; + collider.density = 20; + R2RigidBodyHandle handle; + rigidBody.canSleep = !testbed->noSleep; + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = i ? r2Vector(0.5, 0) : r2Vector(0, 20); + joint.localFrame2.translation = r2Vector(-0.5, 0); + joint.contactsEnabled = 0; + r2InsertImpulseJoint(prev, handle, &joint); + prev = handle; + + r2FreeSharedShape(shape); + } + R2RigidBodyDesc dynamicBody = r2DynamicRigidBodyDesc(); + dynamicBody.position.translation = r2Vector(48, 20); + dynamicBody.linearDamping = 0.1; + dynamicBody.angularDamping = 0.1; + R2ColliderDesc ballCollider = r2BallColliderDesc(8); + ballCollider.density = 20; + ballCollider.friction = 0.6; + R2RigidBodyHandle handleH; + dynamicBody.canSleep = !testbed->noSleep; + handleH = r2InsertRigidBody(world, &dynamicBody); + r2InsertCollider(handleH, &ballCollider); + R2JointDesc jointValue = r2RevoluteJointDesc(); + jointValue.localFrame1.translation = r2Vector(0.5, 0); + jointValue.localFrame2.translation = r2Vector(-8, 0); + jointValue.contactsEnabled = 0; + r2InsertImpulseJoint(prev, handleH, &jointValue); + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/s2d_bridge.c b/c/testbed/examples2d/s2d_bridge.c new file mode 100644 index 000000000..a040cdb57 --- /dev/null +++ b/c/testbed/examples2d/s2d_bridge.c @@ -0,0 +1,51 @@ +/* Port of examples2d/s2d_bridge.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbS2dBridge(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyHandle ground; + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + groundBody.canSleep = !testbed->noSleep; + ground = r2InsertRigidBody(world, &groundBody); + R2RigidBodyHandle prev = ground; + for (int i = 0; i < 160; i++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-80 + 0.5 + i, 20); + rigidBody.linearDamping = 0.1; + rigidBody.angularDamping = 0.1; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.125)); + collider.density = 20; + R2RigidBodyHandle handle; + rigidBody.canSleep = !testbed->noSleep; + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = i ? r2Vector(0.5, 0) : r2Vector(-80, 20); + joint.localFrame2.translation = r2Vector(-0.5, 0); + joint.contactsEnabled = 0; + r2InsertImpulseJoint(prev, handle, &joint); + prev = handle; + } + R2JointDesc jointValue = r2RevoluteJointDesc(); + jointValue.localFrame1.translation = r2Vector(0.5, 0); + jointValue.localFrame2.translation = r2Vector(80, 20); + jointValue.contactsEnabled = 0; + r2InsertImpulseJoint(prev, ground, &jointValue); + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/s2d_card_house.c b/c/testbed/examples2d/s2d_card_house.c new file mode 100644 index 000000000..8342ce80b --- /dev/null +++ b/c/testbed/examples2d/s2d_card_house.c @@ -0,0 +1,56 @@ +/* Port of examples2d/s2d_card_house.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static void card(Testbed *testbed, R2World *world, R2Vector position, R2Real angle) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = position; + rigidBody.position = r2Pose(position, r2Rotation(angle)); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.01, 2)); + collider.friction = 0.7; + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); +} + +void tbS2dCardHouse(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2ColliderDesc floor = r2CuboidColliderDesc(r2Vector(40, 2)); + floor.friction = 0.7; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -2); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &floor); + R2Real z0 = 0; + R2Real y = 1.8; + for (int nb = 5; nb; nb--) { + R2Real z = z0; + for (int i = 0; i < nb; i++) { + if (i != nb - 1) { + card(testbed, world, r2Vector(z + 2.5, y + 1.85), R2_PI / 2); + } + card(testbed, world, r2Vector(z, y), -25 * R2_PI / 180); + z += 1.75; + card(testbed, world, r2Vector(z, y), 25 * R2_PI / 180); + z += 1.75; + } + y += 3.7; + z0 += 1.75; + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/s2d_confined.c b/c/testbed/examples2d/s2d_confined.c new file mode 100644 index 000000000..00b54453c --- /dev/null +++ b/c/testbed/examples2d/s2d_confined.c @@ -0,0 +1,49 @@ +/* Port of examples2d/s2d_confined.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbS2dConfined(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + const R2Vector ends[][2] = {{r2Vector(-10.5, 0), r2Vector(10.5, 0)}, + {r2Vector(-10.5, 0), r2Vector(-10.5, 20.5)}, + {r2Vector(10.5, 0), r2Vector(10.5, 20.5)}, + {r2Vector(-10.5, 20.5), r2Vector(10.5, 20.5)}}; + for (int i = 0; i < 4; i++) { + R2SharedShape *shape = r2CapsuleSharedShape(ends[i][0], ends[i][1], 0.5); + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + collider.friction = 0.6; + r2InsertColliderWithoutParent(world, &collider); + + r2FreeSharedShape(shape); + } + for (int col = 0; col < 25; col++) { + for (int row = 0; row < 25; row++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = + r2Vector(-8.75 + col * 18.0 / 25, 1.5 + row * 18.0 / 25); + rigidBody.gravityScale = 0; + R2ColliderDesc collider = r2BallColliderDesc(0.5); + collider.friction = 0.6; + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/s2d_far_pyramid.c b/c/testbed/examples2d/s2d_far_pyramid.c new file mode 100644 index 000000000..8a7cd9f2a --- /dev/null +++ b/c/testbed/examples2d/s2d_far_pyramid.c @@ -0,0 +1,45 @@ +/* Port of examples2d/s2d_far_pyramid.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbS2dFarPyramid(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2Vector origin = r2Vector(100000, -80000); + R2ColliderDesc floor = r2CuboidColliderDesc(r2Vector(100, 1)); + floor.friction = 0.6; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2VectorAdd(origin, r2Vector(0, -1)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &floor); + int base = 10; + R2Real shift = 0.625; + for (int i = 0; i < base; i++) { + for (int j = i; j < base; j++) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + collider.friction = 0.6; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = + r2VectorAdd(origin, r2Vector((i + 1) * shift + 2 * (j - i) * shift - 0.5 * base, + (2 * i + 1) * shift + 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, (float)origin.x, (float)origin.y + 2.5f, 20); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/s2d_high_mass_ratio_1.c b/c/testbed/examples2d/s2d_high_mass_ratio_1.c new file mode 100644 index 000000000..499021aef --- /dev/null +++ b/c/testbed/examples2d/s2d_high_mass_ratio_1.c @@ -0,0 +1,51 @@ +/* Port of examples2d/s2d_high_mass_ratio_1.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbS2dHighMassRatio1(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2SharedShape *shape = r2SegmentSharedShape(r2Vector(-66, 0), r2Vector(66, 0)); + R2ColliderDesc floor = r2DefaultColliderDesc(); + floor.shape.kind = R2_SHAPE_DESC_SHARED; + floor.shape.sharedShape = shape; + floor.friction = 0.5; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &floor); + + for (int j = 0; j < 3; j++) { + for (int count = 10; count > 0; count--) { + for (int i = 0; i < count; i++) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(1, 1)); + collider.density = count == 1 ? (j + 1) * 100 : 1; + collider.friction = 0.5; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = + r2Vector(2 * (i - count * 0.5) - 20 + 22 * j, + 1 + (10 - count) * 2 + (count == 1 ? 2 : 0)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + + r2FreeSharedShape(shape); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/s2d_high_mass_ratio_2.c b/c/testbed/examples2d/s2d_high_mass_ratio_2.c new file mode 100644 index 000000000..9853d96a2 --- /dev/null +++ b/c/testbed/examples2d/s2d_high_mass_ratio_2.c @@ -0,0 +1,45 @@ +/* Port of examples2d/s2d_high_mass_ratio_2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbS2dHighMassRatio2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2SharedShape *shape = r2SegmentSharedShape(r2Vector(-66, 0), r2Vector(66, 0)); + R2ColliderDesc floor = r2DefaultColliderDesc(); + floor.shape.kind = R2_SHAPE_DESC_SHARED; + floor.shape.sharedShape = shape; + floor.friction = 0.6; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &floor); + + for (int i = 0; i < 3; i++) { + R2ColliderDesc collider = + r2CuboidColliderDesc(i == 2 ? r2Vector(10, 10) : r2Vector(0.5, 0.5)); + collider.friction = 0.6; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i == 2 ? 0 : i ? 9 : -9, i == 2 ? 26 : 0.5); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + + r2FreeSharedShape(shape); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/s2d_high_mass_ratio_3.c b/c/testbed/examples2d/s2d_high_mass_ratio_3.c new file mode 100644 index 000000000..6e49cb524 --- /dev/null +++ b/c/testbed/examples2d/s2d_high_mass_ratio_3.c @@ -0,0 +1,39 @@ +/* Port of examples2d/s2d_high_mass_ratio_3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbS2dHighMassRatio3(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2ColliderDesc floor = r2CuboidColliderDesc(r2Vector(40, 2)); + floor.friction = 0.6; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -2); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &floor); + for (int i = 0; i < 3; i++) { + R2ColliderDesc collider = + r2CuboidColliderDesc(i == 2 ? r2Vector(10, 10) : r2Vector(0.5, 0.5)); + collider.friction = 0.6; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i == 2 ? 0 : i ? 9 : -9, i == 2 ? 26 : 0.5); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/s2d_joint_grid.c b/c/testbed/examples2d/s2d_joint_grid.c new file mode 100644 index 000000000..c3fbb8175 --- /dev/null +++ b/c/testbed/examples2d/s2d_joint_grid.c @@ -0,0 +1,51 @@ +/* Port of examples2d/s2d_joint_grid.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbS2dJointGrid(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyHandle *handles = calloc(1, 10000 * sizeof(*handles)); + if (!handles) { + abort(); + } + for (int k = 0; k < 100; k++) { + for (int i = 0; i < 100; i++) { + int fixed = k >= 47 && k <= 53 && i == 0; + R2RigidBodyHandle handle; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.bodyType = fixed ? R2_FIXED : R2_DYNAMIC; + rigidBody.position.translation = r2Vector(k, -i); + R2ColliderDesc collider = r2BallColliderDesc(0.4); + rigidBody.canSleep = !testbed->noSleep; + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + for (int dir = 0; dir < 2; dir++) { + if (dir ? k > 0 : i > 0) { + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = dir ? r2Vector(0.5, 0) : r2Vector(0, -0.5); + joint.localFrame2.translation = dir ? r2Vector(-0.5, 0) : r2Vector(0, 0.5); + joint.contactsEnabled = 0; + r2InsertImpulseJoint(handles[k * 100 + i - (dir ? 100 : 1)], handle, + &joint); + } + } + handles[k * 100 + i] = handle; + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 20); + free(handles); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/s2d_pyramid.c b/c/testbed/examples2d/s2d_pyramid.c new file mode 100644 index 000000000..0dfa6b820 --- /dev/null +++ b/c/testbed/examples2d/s2d_pyramid.c @@ -0,0 +1,45 @@ +/* Port of examples2d/s2d_pyramid.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbS2dPyramid(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2Vector origin = r2Vector(0, 0); + R2ColliderDesc floor = r2CuboidColliderDesc(r2Vector(100, 1)); + floor.friction = 0.6; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2VectorAdd(origin, r2Vector(0, -1)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &floor); + int base = (int)tbSetting(testbed, "# of basis cubes", 100, 2, 200, 1); + R2Real shift = 0.5; + for (int i = 0; i < base; i++) { + for (int j = i; j < base; j++) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + collider.friction = 0.6; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = + r2VectorAdd(origin, r2Vector((i + 1) * shift + 2 * (j - i) * shift - 0.5 * base, + (2 * i + 1) * shift + 0)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, (float)origin.x, (float)origin.y + 2.5f, 20); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/sensor2.c b/c/testbed/examples2d/sensor2.c new file mode 100644 index 000000000..d3d5eb47b --- /dev/null +++ b/c/testbed/examples2d/sensor2.c @@ -0,0 +1,82 @@ +/* Port of examples2d/sensor2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static int same(R2RigidBodyHandle handle, R2RigidBodyHandle handleB) { + return handle.world == handleB.world && handle.index == handleB.index && + handle.generation == handleB.generation; +} + +void tbSensor2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyHandle ground = {0}; + R2RigidBodyHandle sensor = {0}; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.1); + R2ColliderDesc boxCollider = r2CuboidColliderDesc(r2Vector(200.1, 0.1)); + rigidBody.canSleep = !testbed->noSleep; + ground = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(ground, &boxCollider); + + for (int i = 0; i < 10; i++) { + R2RigidBodyHandle handle; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i * 0.4 - 2, 3); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.2, 0.2)); + rigidBody.canSleep = !testbed->noSleep; + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + + tbBodyColor(testbed, handle, 0.5, 0.5, 1, 1); + } + R2RigidBodyDesc dynamicBody = r2DynamicRigidBodyDesc(); + dynamicBody.position.translation = r2Vector(0, 10); + R2ColliderDesc sensorBodyCollider = r2CuboidColliderDesc(r2Vector(0.2, 0.2)); + dynamicBody.canSleep = !testbed->noSleep; + sensor = r2InsertRigidBody(world, &dynamicBody); + r2InsertCollider(sensor, &sensorBodyCollider); + + R2ColliderDesc collider = r2BallColliderDesc(1); + collider.density = 0; + collider.isSensor = 1; + collider.activeEvents = R2_COLLISION_EVENTS; + r2InsertCollider(sensor, &collider); + tbBodyColor(testbed, sensor, 0.5, 1, 1, 1); + /* Set up the viewer. */ + tbCamera2(testbed, 0, 1, 100); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + R2EventCollector *eventHandler = r2NewEventCollector(); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2EventCollector_Clear(eventHandler); + r2Step(world, NULL, eventHandler); + + size_t n = r2EventCollector_CollisionEvents(eventHandler, NULL, 0); + R2CollisionEvent *events = calloc(n ? n : 1, sizeof(*events)); + if (!events) { + abort(); + } + n = r2EventCollector_CollisionEvents(eventHandler, events, n); + for (size_t i = 0; i < n; i++) { + R2ColliderHandle colliderHandles[] = {events[i].collider1, events[i].collider2}; + for (size_t j = 0; j < 2; j++) { + R2RigidBodyHandle handle = r2Collider_Parent(colliderHandles[j]); + if (!same(handle, ground) && !same(handle, sensor)) { + tbBodyColor(testbed, handle, events[i].started ? 1 : 0.5f, + events[i].started ? 1 : 0.5f, events[i].started ? 0 : 1, 1); + } + } + } + free(events); + } + } + r2FreeEventCollector(eventHandler); + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_blobs2.c b/c/testbed/examples2d/soft_blobs2.c new file mode 100644 index 000000000..55c34f32d --- /dev/null +++ b/c/testbed/examples2d/soft_blobs2.c @@ -0,0 +1,58 @@ +/* Port of examples2d/soft_blobs2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSoftBlobs2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(6, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 6, 6); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 6)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int j = 0; j < 15; j++) { + for (int i = 0; i < 5; i++) { + R2SoftBodyDesc softBody = r2DiskSoftBodyDesc( + r2Vector(-4 + i * 2 + j % 2 * 0.5, 3 + j * 2), 0.45 + 0.1 * ((i + j) % 3), 20); + softBody.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){20, 1}); + softBody.volumeFactor = 1.05; + softBody.selfContacts = 1; + softBody.particleMass = 0.05; + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r2InsertSoftBody(world, &softBody); + } + } + R2SoftBodyDesc strip = r2GridSoftBodyDesc(r2Vector(0, 16), r2Vector(3, 0.15), 40, 3); + strip.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){30, 1}); + strip.selfContacts = 1; + strip.particleMass = 0.02; + if (testbed->noSleep) { + strip.canSleep = 0; + } + r2InsertSoftBody(world, &strip); + /* Set up the viewer. */ + tbCamera2(testbed, 0, 6, 30); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_bodies2.c b/c/testbed/examples2d/soft_bodies2.c new file mode 100644 index 000000000..5271ad398 --- /dev/null +++ b/c/testbed/examples2d/soft_bodies2.c @@ -0,0 +1,136 @@ +/* Port of examples2d/soft_bodies2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSoftBodies2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + /* Ground and walls. */ + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(15, 0.5)); + + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-15, 5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 5)); + + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(15, 5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 5)); + + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + + /* Pressurized blobs of various sizes. */ + for (int i = 0; i < 5; i++) { + const R2Real radius = 0.6 + 0.15 * i; + R2SoftBodyDesc blob = r2DiskSoftBodyDesc(r2Vector(-10.0 + i * 2.5, 2.0 + i), radius, 24); + blob.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){20.0, 1.0}); + blob.volumeFactor = 1.1; + blob.selfContacts = 1; + blob.particleMass = 0.05; + blob.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &blob); + } + + /* Jelly bodies: corotational, Neo-Hookean, and per-cell volume constraints. */ + { + R2SoftBodyDesc jelly = r2GridSoftBodyDesc(r2Vector(2, 1.2), r2Vector(1, 1), 6, 6); + jelly.cellModel = R2_SOFT_CELL_COROTATIONAL; + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 3.0e3; + material.poissonRatio = 0.35; + material.elasticDampingRatio = 0.5; + jelly.material = material; + jelly.particleMass = 0.2; + jelly.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &jelly); + } + { + R2SoftBodyDesc jelly = r2GridSoftBodyDesc(r2Vector(8, 1.2), r2Vector(1, 1), 6, 6); + jelly.cellModel = R2_SOFT_CELL_NEO_HOOKEAN; + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 3.0e3; + material.poissonRatio = 0.35; + material.elasticDampingRatio = 0.5; + jelly.material = material; + jelly.particleMass = 0.2; + jelly.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &jelly); + } + { + R2SoftBodyDesc jelly = r2GridSoftBodyDesc(r2Vector(5, 1.2), r2Vector(1, 1), 6, 6); + jelly.cellModel = R2_SOFT_CELL_VOLUME; + jelly.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){20.0, 1.0}); + jelly.particleMass = 0.2; + jelly.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &jelly); + } + + /* A rope hanging from a fixed anchor, holding a rigid weight. */ + const uint32_t pinnedParticle = 0; + R2SoftBodyDesc rope = r2DefaultSoftBodyDesc(); + rope.kind = R2_SOFT_DESC_ROPE; + rope.a = r2Vector(8, 9); + rope.b = r2Vector(12, 9); + rope.nx = 25; + rope.pinned = (R2IndexView){&pinnedParticle, 1}; + rope.material.edgeSoftness = rope.material.bendSoftness = rope.material.volumeSoftness = + rope.material.shapeMatchingSoftness = (R2SpringCoefficients){40.0, 1.0}; + rope.particleMass = 0.05; + rope.canSleep = !testbed->noSleep; + R2SoftBodyHandle ropeHandle = r2InsertSoftBody(world, &rope); + + R2Vector lastPos = r2SoftBody_ParticlePosition(ropeHandle, 24); + R2RigidBodyHandle weight; + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2VectorAdd(lastPos, r2Vector(0, -0.4)); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.3, 0.3)); + collider.density = 2.0; + weight = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(weight, &collider); + } + + r2SoftBody_AttachParticle(ropeHandle, 24, weight); + + /* A stack of rigid boxes for the blobs to knock down. */ + for (int i = 0; i < 6; i++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-4.0, 0.3 + 0.6 * i); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.3, 0.3)); + + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + + /* Set up the viewer. */ + tbCamera2(testbed, 0.0, 4.0, 30.0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_cutting2.c b/c/testbed/examples2d/soft_cutting2.c new file mode 100644 index 000000000..fb2342fcb --- /dev/null +++ b/c/testbed/examples2d/soft_cutting2.c @@ -0,0 +1,178 @@ +/* Port of examples2d/soft_cutting2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSoftCutting2(Testbed *testbed) { + R2World *world = r2NewWorld(); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(30, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Jelly block to slice by hand. */ + R2SoftBodyDesc block = r2GridSoftBodyDesc(r2Vector(-8, 2), r2Vector(2, 2), 13, 13); + block.cellModel = R2_SOFT_CELL_COROTATIONAL; + block.particleMass = .05; + block.particleRadius = (R2OptionalReal){1, .15}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 2.0e4; + material.poissonRatio = .35; + material.elasticDampingRatio = 1; + block.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.15); + surface.friction = .8; + block.collider = surface; + } + block.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &block); + + /* Pressurized ring: a cut opens it and it falls limp. */ + R2SoftBodyDesc blob = r2DiskSoftBodyDesc(r2Vector(-2, 2), 1.6, 40); + blob.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){30, 1}); + blob.particleMass = .05; + { + R2ColliderDesc surface = r2BallColliderDesc(.1); + surface.friction = .8; + blob.collider = surface; + } + blob.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &blob); + + /* Curtain. */ + { + uint32_t pinned[279]; + size_t pinnedCount = 0; + for (uint32_t i = 0; i < 9; ++i) { + for (uint32_t j = 0; j < 31; ++j) { + if (j == 30) { + pinned[pinnedCount++] = i * 31 + j; + } + } + } + R2SoftBodyDesc curtain = r2GridSoftBodyDesc(r2Vector(3, 4.5), r2Vector(0.6, 3), 9, 31); + curtain.cellModel = R2_SOFT_CELL_COROTATIONAL; + r2SoftBodyDesc_SetPinnedParticles(&curtain, + (R2IndexView){(const uint32_t *)pinned, pinnedCount}); + curtain.particleMass = .05; + curtain.particleRadius = (R2OptionalReal){1, .1}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 3.0e4; + material.poissonRatio = .35; + material.elasticDampingRatio = 1; + curtain.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.1); + surface.friction = .8; + curtain.collider = surface; + } + curtain.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &curtain); + } + /* Slab. */ + { + uint32_t pinned[125]; + size_t pinnedCount = 0; + for (uint32_t i = 0; i < 25; ++i) { + for (uint32_t j = 0; j < 5; ++j) { + if (i == 0 || i == 24) { + pinned[pinnedCount++] = i * 5 + j; + } + } + } + R2SoftBodyDesc slab = r2GridSoftBodyDesc(r2Vector(10, 4), r2Vector(3, 0.5), 25, 5); + slab.cellModel = R2_SOFT_CELL_COROTATIONAL; + r2SoftBodyDesc_SetPinnedParticles(&slab, + (R2IndexView){(const uint32_t *)pinned, pinnedCount}); + slab.particleMass = .05; + slab.particleRadius = (R2OptionalReal){1, .1}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 5.0e4; + material.poissonRatio = .35; + material.elasticDampingRatio = 1; + slab.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.1); + surface.friction = .8; + slab.collider = surface; + } + slab.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &slab); + } + /* Sensor blade: the cut does the work, without pushing the slab. */ + const R2Vector sawStart = r2Vector(10, 1.5); + R2RigidBodyHandle saw; + { + R2RigidBodyDesc rigidBody = r2KinematicPositionBasedRigidBodyDesc(); + rigidBody.position.translation = sawStart; + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.05, 1)); + collider.isSensor = 1; + saw = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(saw, &collider); + } + + size_t handleCount = r2SoftBodyCount(world); + R2SoftBodyHandle *handles = malloc(handleCount * sizeof(*handles)); + if (!handles) { + abort(); + } + handleCount = r2SoftBodyHandles(world, handles, handleCount); + tbCamera2(testbed, 1, 3.5, 40); + + tbSetWorld(testbed, world); + R2Real t = 0; + R2Vector bladeStart = {0}; + int bladeActive = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + /* Hold C to position a hand blade, then release C to cut, even while paused. */ + if (testbed->cutting) { + if (!bladeActive && testbed->cursorValid) { + bladeStart = testbed->cursor; + bladeActive = 1; + } + if (bladeActive && testbed->cursorValid) { + tbLine(testbed, bladeStart, testbed->cursor, 1, .35f, .25f, 1); + } + } else if (bladeActive) { + bladeActive = 0; + if (testbed->cursorValid) { + const R2Vector edge[] = {bladeStart, testbed->cursor}; + for (size_t i = 0; i < handleCount; ++i) { + R2SoftBodyTearEvent *event = r2CutSoftBody(handles[i], edge); + r2FreeSoftBodyTearEvent(event); + } + } + } + + if (tbSimulating(testbed)) { + R2Real dt = r2TimeStep(world); + t += dt; + const R2Real rise = fmin(fmax(t - 1, 0) * .2, 4.5); + const R2Vector position = r2VectorAdd(sawStart, r2Vector(0, rise)); + + r2RigidBody_SetNextKinematicTranslation(saw, position); + const R2Vector edge[] = {r2VectorSub(position, r2Vector(0, 1)), + r2VectorAdd(position, r2Vector(0, 1))}; + for (size_t i = 0; i < handleCount; ++i) { + R2SoftBodyTearEvent *event = r2CutSoftBody(handles[i], edge); + r2FreeSoftBodyTearEvent(event); + } + r2Step(world, NULL, NULL); + } + } + free(handles); + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_fem2.c b/c/testbed/examples2d/soft_fem2.c new file mode 100644 index 000000000..d39252b0e --- /dev/null +++ b/c/testbed/examples2d/soft_fem2.c @@ -0,0 +1,129 @@ +/* Port of examples2d/soft_fem2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#ifdef RAPIER_FEM + +void tbSoftFem2(Testbed *testbed) { + R2World *world = r2NewWorld(); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(30, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Compare FEM (1) with the constraint solver (0). */ + const uint32_t solvers[] = {R2_SOFT_SOLVER_FEM, R2_SOFT_SOLVER_CONSTRAINTS}; + for (size_t row = 0; row < TB_COUNT(solvers); ++row) { + const R2Real y = 2.0 + row * 6.0; + /* Cantilever bolted to a wall. */ + R2SoftBodyDesc beam = r2GridSoftBodyDesc(r2Vector(-6, y), r2Vector(2, 0.2), 17, 3); + beam.cellModel = R2_SOFT_CELL_COROTATIONAL; + beam.totalMass = (R2OptionalReal){1, 8.0}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 2.0e6; + material.poissonRatio = .3; + material.elasticDampingRatio = 1; + beam.material = material; + } + beam.canSleep = !testbed->noSleep; + uint32_t *beamPins = NULL; + { + size_t count = r2SoftBodyDesc_ParticlePositions(&beam, NULL, 0); + R2Vector *positions = malloc(count * sizeof(*positions)); + beamPins = malloc(count * sizeof(*beamPins)); + if (!positions || !beamPins) { + abort(); + } + count = r2SoftBodyDesc_ParticlePositions(&beam, positions, count); + size_t beamPinsCount = 0; + for (size_t i = 0; i < count; ++i) { + if (positions[i].x < -8.0 + 1.0e-4) { + beamPins[beamPinsCount++] = (uint32_t)i; + } + } + r2SoftBodyDesc_SetPinnedParticles( + &beam, (R2IndexView){(const uint32_t *)beamPins, beamPinsCount}); + + free(positions); + } + beam.solver = solvers[row]; + r2InsertSoftBody(world, &beam); + free(beamPins); + + /* Plank pinned at both ends. */ + R2SoftBodyDesc plank = r2GridSoftBodyDesc(r2Vector(2, y), r2Vector(3, 0.25), 21, 3); + plank.cellModel = R2_SOFT_CELL_COROTATIONAL; + plank.totalMass = (R2OptionalReal){1, 20.0}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 4.0e6; + material.poissonRatio = .3; + material.elasticDampingRatio = 1; + plank.material = material; + } + plank.canSleep = !testbed->noSleep; + uint32_t *plankPins = NULL; + { + size_t count = r2SoftBodyDesc_ParticlePositions(&plank, NULL, 0); + R2Vector *positions = malloc(count * sizeof(*positions)); + plankPins = malloc(count * sizeof(*plankPins)); + if (!positions || !plankPins) { + abort(); + } + count = r2SoftBodyDesc_ParticlePositions(&plank, positions, count); + size_t plankPinsCount = 0; + for (size_t i = 0; i < count; ++i) { + if (fabs(positions[i].x - 2.0) > 2.9) { + plankPins[plankPinsCount++] = (uint32_t)i; + } + } + r2SoftBodyDesc_SetPinnedParticles( + &plank, (R2IndexView){(const uint32_t *)plankPins, plankPinsCount}); + + free(positions); + } + plank.solver = solvers[row]; + r2InsertSoftBody(world, &plank); + free(plankPins); + + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(2, y + 2.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + collider.density = 20; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Neo-Hookean jelly. */ + R2SoftBodyDesc jelly = r2GridSoftBodyDesc(r2Vector(9, y + 1), r2Vector(0.8, 0.8), 5, 5); + jelly.cellModel = R2_SOFT_CELL_NEO_HOOKEAN; + jelly.particleMass = .1; + jelly.solver = solvers[row]; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 2.0e4; + material.poissonRatio = .4; + material.elasticDampingRatio = .5; + jelly.material = material; + } + jelly.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &jelly); + } + tbCamera2(testbed, 1, 5, 25); + + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} +#endif diff --git a/c/testbed/examples2d/soft_force_tearing2.c b/c/testbed/examples2d/soft_force_tearing2.c new file mode 100644 index 000000000..573566b14 --- /dev/null +++ b/c/testbed/examples2d/soft_force_tearing2.c @@ -0,0 +1,228 @@ +/* Port of examples2d/soft_force_tearing2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +typedef struct DrivenParticle { + R2SoftBodyHandle body; + uint32_t index; + R2Vector rest; + int right; +} DrivenParticle; + +/* Follow driven particle indices through compaction and newly split bodies. */ +static void followTears(R2EventCollector *events, DrivenParticle *ends, size_t endCount) { + size_t count = r2EventCollector_TearEventCount(events); + for (size_t i = 0; i < count; ++i) { + R2SoftBodyTearEvent *event = r2EventCollector_TearEvent(events, i); + R2SoftBodyHandle origin = r2SoftBodyTearEvent_SoftBody(event); + for (size_t j = 0; j < endCount; ++j) { + if (ends[j].body.index == origin.index && + ends[j].body.generation == origin.generation) { + R2Bool found; + R2SoftBodyHandle destination; + uint32_t index; + R2OptionalParticleDestination softBodyTearEventTryParticleDestinationResult = + r2SoftBodyTearEvent_TryParticleDestination(event, ends[j].index); + destination = softBodyTearEventTryParticleDestinationResult.body; + index = softBodyTearEventTryParticleDestinationResult.index; + found = softBodyTearEventTryParticleDestinationResult.found; + if (found) { + ends[j].body = destination; + ends[j].index = index; + } + } + } + r2FreeSoftBodyTearEvent(event); + } +} + +static R2SoftBodyDesc hangingBar(R2Real x, R2SoftBodyMaterial *material) { + R2SoftBodyDesc bar = r2GridSoftBodyDesc(r2Vector(x, 7.5), r2Vector(.3, 1.5), 3, 13); + static const uint32_t pinned[] = {12, 25, 38}; + r2SoftBodyDesc_SetPinnedParticles(&bar, + (R2IndexView){(const uint32_t *)pinned, TB_COUNT(pinned)}); + bar.material = *material; + bar.particleMass = .05; + bar.particleRadius = (R2OptionalReal){1, .1}; + R2ColliderDesc surface = r2BallColliderDesc(.1); + surface.friction = .8; + bar.collider = surface; + + return bar; +} + +static void stiff(R2SoftBodyMaterial *material) { + material->edgeSoftness = (R2SpringCoefficients){150, 1}; + material->volumeSoftness = (R2SpringCoefficients){150, 1}; +} + +void tbSoftForceTearing2(Testbed *testbed) { + R2World *world = r2NewWorld(); + const R2Real tearForce = tbSetting(testbed, "Tear force (crate bar)", 8, 2, 30, 0); + const R2Real interiorStrength = tbSetting(testbed, "Interior strength (slab)", 4, 1, 8, 0); + const R2Real smoothing = tbSetting(testbed, "Tear smoothing (right bar, s)", 1, 0, 3, 0); + + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(30, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 9.3); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(30, 0.3)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int column = 0; column < 2; ++column) { + const R2Real x = column == 0 ? -13 : -10; + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + stiff(&material); + if (column == 0) { + material.tearStrain = (R2OptionalReal){1, .5}; + } else { + material.tearForce = (R2OptionalReal){1, tearForce}; + material.tearSmoothing = .5; + } + R2SoftBodyDesc builder = hangingBar(x, &material); + builder.canSleep = !testbed->noSleep; + R2SoftBodyHandle bar = r2InsertSoftBody(world, &builder); + + R2RigidBodyHandle crateBody; + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x, 5.4); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.6, 0.5)); + collider.density = 1.2; + crateBody = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(crateBody, &collider); + } + + for (size_t i = 0; i < 3; ++i) { + r2SoftBody_AttachParticle(bar, i * 13, crateBody); + } + } + const size_t sx = 25, sy = 7; + uint32_t pinned[28]; + for (size_t j = 0; j < sy; ++j) { + pinned[4 * j] = j; + pinned[4 * j + 1] = sy + j; + pinned[4 * j + 2] = (sx - 2) * sy + j; + pinned[4 * j + 3] = (sx - 1) * sy + j; + } + R2SoftBodyDesc builder = r2GridSoftBodyDesc(r2Vector(-1, 2), r2Vector(3, 0.75), sx, sy); + r2SoftBodyDesc_SetPinnedParticles(&builder, + (R2IndexView){(const uint32_t *)pinned, TB_COUNT(pinned)}); + builder.particleMass = .05; + builder.particleRadius = (R2OptionalReal){1, .1}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.edgeSoftness = (R2SpringCoefficients){60, 1}; + material.volumeSoftness = (R2SpringCoefficients){60, 1}; + material.tearForce = (R2OptionalReal){1, 60}; + material.tearSmoothing = .05; + material.interiorStrength = interiorStrength; + builder.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.1); + surface.friction = .8; + builder.collider = surface; + } + builder.canSleep = !testbed->noSleep; + R2SoftBodyHandle slab = r2InsertSoftBody(world, &builder); + + size_t indexCount = r2SoftBody_Edges(slab, NULL, 0); + uint32_t *edges = malloc(indexCount * sizeof(*edges)); + if (!edges) { + abort(); + } + indexCount = r2SoftBody_Edges(slab, edges, indexCount); + for (size_t i = 0; i < indexCount / 2; ++i) { + const uint32_t a = edges[2 * i], b = edges[2 * i + 1]; + if (a % sy == sy - 1 && b % sy == sy - 1 && a / sy >= 11 && a / sy <= 13 && b / sy >= 11 && + b / sy <= 13) { + r2SoftBody_SetEdgeTearResistance(slab, i, .4); + } + } + free(edges); + DrivenParticle rightEnd[14]; + for (size_t j = 0; j < sy; ++j) { + for (size_t side = 0; side < 2; ++side) { + const uint32_t index = (sx - 2 + side) * sy + j; + R2Vector rest = r2SoftBody_ParticlePosition(slab, index); + rightEnd[j * 2 + side] = (DrivenParticle){slab, index, rest, 1}; + } + } + R2RigidBodyHandle disks[2]; + for (int column = 0; column < 2; ++column) { + const R2Real x = column == 0 ? 8 : 13; + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.tearForce = (R2OptionalReal){1, 25}; + material.tearSmoothing = column == 0 ? 0 : smoothing; + stiff(&material); + R2SoftBodyDesc builder = hangingBar(x, &material); + builder.canSleep = !testbed->noSleep; + R2SoftBodyHandle bar = r2InsertSoftBody(world, &builder); + + R2RigidBodyHandle crateBody; + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x, 5.4); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(1.2, 0.4)); + collider.density = .75; + crateBody = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(crateBody, &collider); + } + + for (size_t i = 0; i < 3; ++i) { + r2SoftBody_AttachParticle(bar, i * 13, crateBody); + } + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x + .8, 8.6); + rigidBody.enabled = 0; + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2BallColliderDesc(.2); + collider.density = 3; + disks[column] = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(disks[column], &collider); + } + } + R2EventCollector *events = r2NewEventCollector(); + tbCamera2(testbed, 0, 4.5, 40); + testbed->snapshotSupported = 0; + testbed->initialDebug = R2_DEBUG_SOFT_BODIES | R2_DEBUG_SOFT_BODY_STRESS; + tbSetWorld(testbed, world); + R2Real t = 0; + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + R2Real dt = r2TimeStep(world); + if (t < 1 && t + dt >= 1) { + for (size_t i = 0; i < TB_COUNT(disks); ++i) { + r2RigidBody_SetEnabled(disks[i], 1); + r2RigidBody_SetLinvel(disks[i], r2Vector(0, -15), 1); + } + } + t += dt; + const R2Real shift = fmin(fmax(t - 2, 0) * .25, 3); + for (size_t i = 0; i < TB_COUNT(rightEnd); ++i) { + r2SoftBody_SetParticleKinematicTarget( + rightEnd[i].body, rightEnd[i].index, + r2VectorAdd(rightEnd[i].rest, r2Vector(shift, 0))); + } + r2EventCollector_Clear(events); + r2Step(world, NULL, events); + followTears(events, rightEnd, TB_COUNT(rightEnd)); + } + } + r2FreeEventCollector(events); + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_jelly2.c b/c/testbed/examples2d/soft_jelly2.c new file mode 100644 index 000000000..03f69af5e --- /dev/null +++ b/c/testbed/examples2d/soft_jelly2.c @@ -0,0 +1,92 @@ +/* Port of examples2d/soft_jelly2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSoftJelly2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(20, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + for (int level = 0; level < 4; level++) { + for (int i = 0; i < 4 - level; i++) { + R2SoftBodyDesc softBody = r2GridSoftBodyDesc( + r2Vector(-8 + (i - (4 - level) * 0.5 + 0.5) * 1.6, 0.75 + level * 1.5), + r2Vector(0.75, 0.75), 5, 5); + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + softBody.cellModel = R2_SOFT_CELL_COROTATIONAL; + material.youngModulus = 2.0e4 / (1 + level * 1.5); + material.poissonRatio = 0.4; + material.elasticDampingRatio = 0.5; + softBody.material = material; + softBody.particleMass = 0.1; + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r2InsertSoftBody(world, &softBody); + } + } + R2SoftBodyDesc bridge = r2GridSoftBodyDesc(r2Vector(3, 3), r2Vector(4, 0.2), 41, 3); + bridge.cellModel = R2_SOFT_CELL_VOLUME; + bridge.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){30, 1}); + uint32_t pins[] = {0, 1, 2, 120, 121, 122}; + r2SoftBodyDesc_SetPinnedParticles(&bridge, (R2IndexView){(const uint32_t *)pins, 6}); + bridge.particleMass = 0.05; + if (testbed->noSleep) { + bridge.canSleep = 0; + } + r2InsertSoftBody(world, &bridge); + for (int i = 0; i < 6; i++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0.5 + i, 5 + i); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.25, 0.25)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + R2SoftBodyDesc driven = r2DiskSoftBodyDesc(r2Vector(8, 4), 0.8, 24); + driven.shapeMatching = (R2OptionalBool){1, 1}; + driven.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){15, 1}); + driven.volumePreservation = 0; + driven.gravityScale = 0; + driven.particleMass = 0.2; + driven.canSleep = 0; + R2SoftBodyHandle softBodyHandle = {0}; + if (testbed->noSleep) { + driven.canSleep = 0; + } + softBodyHandle = r2InsertSoftBody(world, &driven); + for (int i = 0; i < 8; i++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(6.5 + 0.5 * i, 0.25); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.2, 0.2)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 3, 30); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + R2Real dt = r2TimeStep(world); + R2Real time = (R2Real)testbed->time + dt; + R2Pose target = r2Pose(r2Vector(8 + 2.5 * cos(time), 1 + 1.5 * fabs(sin(2 * time))), + r2Rotation(time)); + + r2SoftBody_SetClusterShapeMatchingTarget(softBodyHandle, 0, &target); + + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_joints2.c b/c/testbed/examples2d/soft_joints2.c new file mode 100644 index 000000000..25261dfec --- /dev/null +++ b/c/testbed/examples2d/soft_joints2.c @@ -0,0 +1,365 @@ +/* Port of examples2d/soft_joints2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static R2SoftBodyDesc jelly(R2Vector center, R2Vector half, R2Real young) { + R2SoftBodyDesc builder = r2GridSoftBodyDesc(center, half, 5, 5); + builder.cellModel = R2_SOFT_CELL_COROTATIONAL; + builder.particleMass = .08; + builder.particleRadius = (R2OptionalReal){1, .06}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = young; + material.poissonRatio = .35; + material.elasticDampingRatio = .8; + builder.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.06); + surface.friction = .6; + builder.collider = surface; + } + return builder; +} + +void tbSoftJoints2(Testbed *testbed) { + R2World *world = r2NewWorld(); + + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(30, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Revolute joint and velocity motor. */ + R2SoftBodyHandle spinner; + { + R2SoftBodyDesc builder = jelly(r2Vector(-12, 2), r2Vector(0.6, 0.6), 8e3); + builder.canSleep = !testbed->noSleep; + spinner = r2InsertSoftBody(world, &builder); + } + R2RigidBodyHandle spinnerRoot = r2SoftBody_RootBody(spinner); + R2Vector com = r2SoftBody_CenterOfMass(spinner); + R2RigidBodyHandle pivot; + { + R2RigidBodyDesc builder = r2FixedRigidBodyDesc(); + builder.position.translation = com; + pivot = r2InsertRigidBody(world, &builder); + } + { + R2JointDesc joint = r2RevoluteJointDesc(); + r2JointDesc_SetMotorVelocity(&joint, R2_AXIS_ANG_X, 1.5, 60); + r2InsertImpulseJoint(pivot, spinnerRoot, &joint); + } + /* Weld a rigid plate to the jelly's top cluster. */ + R2SoftBodyHandle wobbler; + { + R2SoftBodyDesc builder = jelly(r2Vector(-8, 0.61), r2Vector(0.6, 0.6), 2.5e3); + builder.canSleep = !testbed->noSleep; + wobbler = r2InsertSoftBody(world, &builder); + } + + R2Vector positions[64]; + size_t particleCount = + r2SoftBody_ParticlePositions(wobbler, positions, TB_COUNT(positions)); + R2Real maxY = 0; + for (size_t i = 0; i < particleCount; ++i) { + maxY = fmax(maxY, positions[i].y); + } + uint32_t top[5]; + size_t topCount = 0; + for (size_t i = 0; i < particleCount; ++i) { + if (fabs(positions[i].y - maxY) < 1e-3) { + top[topCount++] = (uint32_t)i; + } + } + uint32_t topCluster = r2SoftBody_AddCluster(wobbler, top, topCount); + R2RigidBodyHandle topProxy = r2SoftBody_ClusterProxy(wobbler, topCluster); + R2Vector topPos; + { topPos = r2RigidBody_Translation(topProxy); } + R2RigidBodyHandle plate; + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2VectorAdd(topPos, r2Vector(0, 0.12)); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.7, 0.06)); + collider.density = .4; + plate = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(plate, &collider); + } + { + R2JointDesc joint = r2FixedJointDesc(); + joint.localFrame1.translation = r2Vector(0, -0.12); + r2InsertImpulseJoint(plate, topProxy, &joint); + } + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2VectorAdd(topPos, r2Vector(0.3, 1.4)); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.15, 0.15)); + collider.density = 1.5; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Prismatic motor with two stops. */ + R2SoftBodyHandle shuttle; + { + R2SoftBodyDesc builder = jelly(r2Vector(-3, 0.85), r2Vector(0.4, 0.4), 6e3); + builder.canSleep = !testbed->noSleep; + shuttle = r2InsertSoftBody(world, &builder); + } + R2RigidBodyHandle shuttleRoot = r2SoftBody_RootBody(shuttle); + R2Vector shuttleCom = r2SoftBody_CenterOfMass(shuttle); + R2RigidBodyHandle rail; + { + R2RigidBodyDesc builder = r2FixedRigidBodyDesc(); + builder.position.translation = shuttleCom; + rail = r2InsertRigidBody(world, &builder); + } + R2ImpulseJointHandle railJoint; + { + R2JointDesc joint = r2PrismaticJointDesc(r2Vector(1, 0)); + r2JointDesc_SetLimits(&joint, R2_AXIS_LIN_X, -1.8, 1.8); + r2JointDesc_SetMotorPosition(&joint, R2_AXIS_LIN_X, 0, 40, 8); + railJoint = r2InsertImpulseJoint(rail, shuttleRoot, &joint); + } + /* Rope over a ledge. */ + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(1.5, 1); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(1.2, 1)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + R2SoftBodyHandle anchorJelly; + { + R2SoftBodyDesc builder = jelly(r2Vector(1.5, 2.6), r2Vector(0.5, 0.5), 1.2e4); + builder.canSleep = !testbed->noSleep; + anchorJelly = r2InsertSoftBody(world, &builder); + } + R2SoftBodyHandle hangingJelly; + { + R2SoftBodyDesc builder = jelly(r2Vector(3.8, 2.6), r2Vector(0.5, 0.5), 1.2e4); + builder.canSleep = !testbed->noSleep; + hangingJelly = r2InsertSoftBody(world, &builder); + } + R2RigidBodyHandle anchorRoot = r2SoftBody_RootBody(anchorJelly); + R2RigidBodyHandle hangingRoot = r2SoftBody_RootBody(hangingJelly); + { + R2JointDesc joint = r2RopeJointDesc(2.2); + r2InsertImpulseJoint(anchorRoot, hangingRoot, &joint); + } + /* Spring bungee under a gantry. */ + R2SoftBodyHandle bungee; + { + R2SoftBodyDesc builder = jelly(r2Vector(6.5, 3.2), r2Vector(0.45, 0.45), 6e3); + builder.canSleep = !testbed->noSleep; + bungee = r2InsertSoftBody(world, &builder); + } + R2RigidBodyHandle bungeeRoot = r2SoftBody_RootBody(bungee); + R2RigidBodyHandle gantry; + { + R2RigidBodyDesc builder = r2FixedRigidBodyDesc(); + builder.position.translation = r2Vector(6.5, 5.5); + gantry = r2InsertRigidBody(world, &builder); + } + { + R2JointDesc joint = r2SpringJointDesc(1.2, 25, 1.5); + r2InsertImpulseJoint(gantry, bungeeRoot, &joint); + } + /* Bead on a visual-only pole. */ + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(9.5, 2.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.05, 2.5)); + collider.collisionGroups = (R2InteractionGroups){0, 0, 0}; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + R2SoftBodyHandle bead; + { + R2SoftBodyDesc builder = jelly(r2Vector(9.5, 4.2), r2Vector(0.35, 0.35), 8e3); + builder.canSleep = !testbed->noSleep; + bead = r2InsertSoftBody(world, &builder); + } + R2RigidBodyHandle beadRoot = r2SoftBody_RootBody(bead); + R2RigidBodyHandle pole; + { + R2RigidBodyDesc builder = r2FixedRigidBodyDesc(); + builder.position.translation = r2Vector(9.5, 2.5); + pole = r2InsertRigidBody(world, &builder); + } + { + R2JointDesc joint = r2PinSlotJointDesc(r2Vector(0, 1)); + r2JointDesc_SetLimits(&joint, R2_AXIS_LIN_X, -1.8, 1.8); + r2InsertImpulseJoint(pole, beadRoot, &joint); + } + /* Hinge two disjoint clusters of one soft bar. */ + R2SoftBodyHandle bar; + R2SoftBodyDesc barBuilder = r2GridSoftBodyDesc(r2Vector(13.5, 3), r2Vector(1, 0.22), 9, 3); + barBuilder.cellModel = R2_SOFT_CELL_COROTATIONAL; + barBuilder.particleMass = .08; + barBuilder.particleRadius = (R2OptionalReal){1, .06}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 2e4; + material.poissonRatio = .35; + material.elasticDampingRatio = 1; + barBuilder.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.06); + surface.friction = .5; + barBuilder.collider = surface; + } + barBuilder.canSleep = !testbed->noSleep; + bar = r2InsertSoftBody(world, &barBuilder); + + R2Vector barCom = r2SoftBody_CenterOfMass(bar); + uint32_t leftHalf[64], rightHalf[64]; + size_t leftCount = 0, rightCount = 0; + particleCount = r2SoftBody_ParticlePositions(bar, positions, TB_COUNT(positions)); + for (size_t i = 0; i < particleCount; ++i) { + if (positions[i].x < barCom.x - 1e-3) { + leftHalf[leftCount++] = i; + } else if (positions[i].x > barCom.x + 1e-3) { + rightHalf[rightCount++] = i; + } + } + uint32_t leftCluster = r2SoftBody_AddCluster(bar, leftHalf, leftCount); + uint32_t rightCluster = r2SoftBody_AddCluster(bar, rightHalf, rightCount); + R2RigidBodyHandle leftProxy = r2SoftBody_ClusterProxy(bar, leftCluster); + R2RigidBodyHandle rightProxy = r2SoftBody_ClusterProxy(bar, rightCluster); + R2Vector leftPos; + { leftPos = r2RigidBody_Translation(leftProxy); } + R2Vector rightPos; + { rightPos = r2RigidBody_Translation(rightProxy); } + R2RigidBodyHandle barAnchor; + { + R2RigidBodyDesc builder = r2FixedRigidBodyDesc(); + builder.position.translation = leftPos; + barAnchor = r2InsertRigidBody(world, &builder); + } + { + R2JointDesc joint = r2FixedJointDesc(); + r2InsertImpulseJoint(barAnchor, leftProxy, &joint); + } + R2ImpulseJointHandle flapJoint; + { + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2VectorSub(barCom, leftPos); + joint.localFrame2.translation = r2VectorSub(barCom, rightPos); + r2JointDesc_SetMotorPosition(&joint, R2_AXIS_ANG_X, 0, 80, 10); + flapJoint = r2InsertImpulseJoint(leftProxy, rightProxy, &joint); + } + /* Multibody arm and rope attached to one jelly. */ + R2RigidBodyHandle armRoot; + { + R2RigidBodyDesc builder = r2FixedRigidBodyDesc(); + builder.position.translation = r2Vector(17, 6); + armRoot = r2InsertRigidBody(world, &builder); + } + R2RigidBodyHandle link1; + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(18.2, 6); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CapsuleXColliderDesc(.5, .08); + collider.density = 2; + link1 = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(link1, &collider); + } + { + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2Vector(0, 0); + joint.localFrame2.translation = r2Vector(-1.2, 0); + r2InsertMultibodyJoint(armRoot, link1, &joint); + } + R2RigidBodyHandle link2; + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(19.4, 6); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CapsuleXColliderDesc(.5, .08); + collider.density = 2; + link2 = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(link2, &collider); + } + { + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2Vector(0.6, 0); + joint.localFrame2.translation = r2Vector(-0.6, 0); + r2InsertMultibodyJoint(link1, link2, &joint); + } + R2SoftBodyHandle pendulum; + { + R2SoftBodyDesc builder = jelly(r2Vector(20.2, 5.2), r2Vector(0.5, 0.5), 5e3); + builder.canSleep = !testbed->noSleep; + pendulum = r2InsertSoftBody(world, &builder); + } + R2RigidBodyHandle pendulumRoot = r2SoftBody_RootBody(pendulum); + { + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2Vector(0.7, 0); + joint.localFrame2.translation = r2Vector(0, 0.6); + r2InsertImpulseJoint(link2, pendulumRoot, &joint); + } + R2RigidBodyHandle crateBody; + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(20.2, 0.3); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.3, 0.3)); + collider.density = .5; + crateBody = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(crateBody, &collider); + } + { + R2JointDesc joint = r2RopeJointDesc(4.2); + r2InsertImpulseJoint(pendulumRoot, crateBody, &joint); + } + /* Wave a pinned cluster without a joint. */ + R2SoftBodyHandle strand; + R2SoftBodyDesc gripBuilder = r2RopeSoftBodyDesc(r2Vector(-16, 5.5), r2Vector(-16, 1.5), 20); + gripBuilder.particleMass = .05; + gripBuilder.canSleep = !testbed->noSleep; + strand = r2InsertSoftBody(world, &gripBuilder); + + uint32_t gripParticles[2]; + for (uint32_t i = 0; i < 2; ++i) { + gripParticles[i] = i; + } + uint32_t grip = r2SoftBody_AddCluster(strand, gripParticles, 2); + R2RigidBodyHandle gripProxy = r2SoftBody_ClusterProxy(strand, grip); + r2SoftBody_SetClusterPinned(strand, grip, 1); + R2Vector gripHome; + { gripHome = r2RigidBody_Translation(gripProxy); } + tbCamera2(testbed, 2, 3, 30); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + R2Real t = 0; + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + R2Real dt = r2TimeStep(world); + t += dt; + + r2ImpulseJoint_SetMotorPosition(railJoint, R2_AXIS_LIN_X, 1.5 * sin(.6 * t), 40, + 8, 1); + + r2ImpulseJoint_SetMotorPosition(flapJoint, R2_AXIS_ANG_X, .8 * sin(1.4 * t), 80, + 10, 1); + + r2SoftBody_SetClusterKinematicTarget( + strand, grip, + r2Pose(r2VectorAdd(gripHome, r2Vector(1.2 * sin(.7 * t), .15 * sin(1.9 * t))), + r2Rotation(.5 * sin(1.1 * t)))); + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_letters2.c b/c/testbed/examples2d/soft_letters2.c new file mode 100644 index 000000000..bbd876e79 --- /dev/null +++ b/c/testbed/examples2d/soft_letters2.c @@ -0,0 +1,112 @@ +/* Port of examples2d/soft_letters2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#include "utils/logo_mesh.h" + +void tbSoftLetters2(Testbed *testbed) { + R2World *world = r2NewWorld(); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(25, 1.2)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(25, 25); + rigidBody.position.rotation = r2Rotation(R2_PI / 2); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(25, 1.2)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-25, 25); + rigidBody.position.rotation = r2Rotation(R2_PI / 2); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(25, 1.2)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + const R2Real cellSize = 2.4; + + struct Letter { + R2Vector *positions; + uint32_t *cells; + size_t particleCount, indexCount; + } letters[TB_COUNT(logoMeshes)]; + + size_t letterCount = 0; + for (size_t i = 0; i < TB_COUNT(logoMeshes); ++i) { + const LogoMesh *mesh = &logoMeshes[i]; + + R2VolumeMeshParameters letterMeshing = r2NewVolumeMeshParameters(cellSize); + R2SoftBodyDesc letter = r2VolumetricSoftBodyDesc( + (R2VectorView){mesh->vertices, mesh->vertexCount}, + (R2SurfaceElementView){(const R2Edge *)mesh->outline, mesh->edgeCount}, letterMeshing); + if (letter.positions.count == 0) { + continue; + } + struct Letter *data = &letters[letterCount++]; + data->particleCount = r2SoftBodyDesc_ParticlePositions(&letter, NULL, 0); + data->indexCount = r2SoftBodyDesc_CellIndices(&letter, NULL, 0); + data->positions = malloc(data->particleCount * sizeof(*data->positions)); + data->cells = malloc(data->indexCount * sizeof(*data->cells)); + if (!data->positions || !data->cells) { + abort(); + } + data->particleCount = + r2SoftBodyDesc_ParticlePositions(&letter, data->positions, data->particleCount); + data->indexCount = r2SoftBodyDesc_CellIndices(&letter, data->cells, data->indexCount); + } + const R2Real stiffnesses[] = {1e3, 5e3, 1e4, 5e4, 1e5, 5e5, 1e6, 5e6}; + for (size_t row = 0; row < TB_COUNT(stiffnesses); ++row) { + const R2Real young = stiffnesses[TB_COUNT(stiffnesses) - 1 - row]; + for (size_t ith = 0; ith < letterCount; ++ith) { + const struct Letter *data = &letters[ith]; + const R2Vector offset = r2Vector(ith * 8.0 - 22, 12 + row * 11); + R2Vector *positions = malloc(data->particleCount * sizeof(*positions)); + if (!positions) { + abort(); + } + for (size_t i = 0; i < data->particleCount; ++i) { + positions[i] = r2VectorAdd(data->positions[i], offset); + } + R2SoftBodyDesc letter = r2DefaultSoftBodyDesc(); + r2SoftBodyDesc_SetParticles(&letter, (R2VectorView){positions, data->particleCount}); + r2SoftBodyDesc_SetCells( + &letter, (R2CellView){(const R2Triangle *)data->cells, data->indexCount / 3}); + letter.cellModel = R2_SOFT_CELL_COROTATIONAL; + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = young; + material.poissonRatio = .4; + material.elasticDampingRatio = .5; + material.deformationDamping = fmin(young / 4e4, 50); + letter.material = material; + letter.particleMass = .1; + letter.particleRadius = (R2OptionalReal){1, .15}; + letter.selfContacts = 1; + letter.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &letter); + free(positions); + } + } + for (size_t i = 0; i < letterCount; ++i) { + free(letters[i].positions); + free(letters[i].cells); + } + tbCamera2(testbed, 0, 20, 17); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_pile2.c b/c/testbed/examples2d/soft_pile2.c new file mode 100644 index 000000000..fc1515c3d --- /dev/null +++ b/c/testbed/examples2d/soft_pile2.c @@ -0,0 +1,122 @@ +/* Port of examples2d/soft_pile2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSoftPile2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + R2ColliderDesc boxCollider = r2CuboidColliderDesc(r2Vector(9, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &boxCollider); + + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 9, 12); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 12)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int i = 0; i < 5; i++) { + R2SharedShape *shape = r2CapsuleSharedShape(r2Vector(-0.6, 0), r2Vector(0.6, 0), 0.1); + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-6 + i * 3, 6); + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + + r2FreeSharedShape(shape); + } + int k = 0; + for (int layer = 0; layer < 40; layer++) { + for (int i = 0; i < 6; i++, k++) { + R2Vector position = r2Vector(-7 + i * 2.8 + layer % 2, 9 + layer * 2.6); + R2SoftBodyDesc softBody; + if (k % 5 == 0 || k % 5 == 3) { + softBody = r2GridSoftBodyDesc(position, r2Vector(0.6, 0.6), 4, 4); + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + softBody.cellModel = R2_SOFT_CELL_COROTATIONAL; + material.youngModulus = 3.0e3 * (1 + k % 7 * 4); + material.poissonRatio = 0.4; + material.elasticDampingRatio = 0.5; + softBody.material = material; + softBody.particleMass = 0.1; + softBody.particleRadius = (R2OptionalReal){1, 0.08}; + R2ColliderDesc surfaceCollider = r2BallColliderDesc(0.08); + surfaceCollider.friction = 0.7; + softBody.collider = surfaceCollider; + } else if (k % 5 == 1) { + softBody = r2DiskSoftBodyDesc(position, 0.6, 20); + softBody.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){20, 1}); + softBody.volumeFactor = 1.1; + softBody.selfContacts = 1; + softBody.particleMass = 0.05; + softBody.particleRadius = (R2OptionalReal){1, 0.06}; + R2ColliderDesc surfaceCollider2 = r2BallColliderDesc(0.06); + surfaceCollider2.friction = 0.6; + softBody.collider = surfaceCollider2; + } else if (k % 5 == 2) { + softBody = r2GridSoftBodyDesc(position, r2Vector(1.2, 0.12), 13, 2); + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + softBody.cellModel = R2_SOFT_CELL_COROTATIONAL; + material.youngModulus = 2.0e4; + material.poissonRatio = 0.4; + material.elasticDampingRatio = 0.5; + softBody.material = material; + softBody.particleMass = 0.1; + softBody.particleRadius = (R2OptionalReal){1, 0.06}; + R2ColliderDesc surfaceCollider3 = r2BallColliderDesc(0.06); + surfaceCollider3.friction = 0.6; + softBody.collider = surfaceCollider3; + } else { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.4, 0.4)); + collider.density = 0.5; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = position; + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r2InsertSoftBody(world, &softBody); + } + } + } + for (int i = 0; i < 3; i++) { + R2SoftBodyDesc softBody = + r2RopeSoftBodyDesc(r2Vector(-6 + i, 36 + i), r2Vector(4 + i, 37 + i), 30); + softBody.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){30, 1}); + softBody.particleMass = 0.03; + R2ColliderDesc surfaceCollider4 = r2BallColliderDesc(0.08); + surfaceCollider4.friction = 0.6; + softBody.collider = surfaceCollider4; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r2InsertSoftBody(world, &softBody); + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 10, 20); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_plasticity2.c b/c/testbed/examples2d/soft_plasticity2.c new file mode 100644 index 000000000..1f7814099 --- /dev/null +++ b/c/testbed/examples2d/soft_plasticity2.c @@ -0,0 +1,175 @@ +/* Port of examples2d/soft_plasticity2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static R2SoftBodyMaterial clay(R2Real young, R2Real plasticYield, R2Real plasticCreep) { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = young; + material.poissonRatio = .35; + material.elasticDampingRatio = 1; + material.plasticYield = plasticYield; + material.plasticCreep = plasticCreep; + material.deformationDamping = 4; + return material; +} + +void tbSoftPlasticity2(Testbed *testbed) { + R2World *world = r2NewWorld(); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(16, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(13, 2); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.2, 2)); + collider.friction = .8; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Yield ladder: elastic to increasingly plastic, hit by identical balls. */ + const R2Real yields[] = {0, .2, .08, .02}; + for (size_t i = 0; i < TB_COUNT(yields); ++i) { + const R2Real x = -13 + i * 2.4; + R2SoftBodyDesc square = r2GridSoftBodyDesc(r2Vector(x, 0.75), r2Vector(0.75, 0.75), 6, 6); + square.cellModel = R2_SOFT_CELL_COROTATIONAL; + square.particleMass = .1; + square.canSleep = !testbed->noSleep; + { + R2SoftBodyMaterial material = clay(1.0e4, yields[i], 20); + square.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.1); + surface.friction = .8; + square.collider = surface; + } + r2InsertSoftBody(world, &square); + + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x, 6); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2BallColliderDesc(.4); + collider.density = 5; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Clay slab stamped by a kinematic press. */ + R2SoftBodyDesc slab = r2GridSoftBodyDesc(r2Vector(0, 0.5), r2Vector(3, 0.5), 25, 5); + slab.cellModel = R2_SOFT_CELL_COROTATIONAL; + slab.particleMass = .1; + slab.canSleep = !testbed->noSleep; + { + R2SoftBodyMaterial material = clay(3.0e4, .02, 50); + slab.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.12); + surface.friction = .8; + slab.collider = surface; + } + r2InsertSoftBody(world, &slab); + + const R2Vector pressRest = r2Vector(-2, 2.4); + R2RigidBodyHandle press; + { + R2RigidBodyDesc rigidBody = r2KinematicPositionBasedRigidBodyDesc(); + rigidBody.position.translation = pressRest; + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.4, 0.4)); + collider.position.rotation = r2Rotation(R2_PI / 4); + collider.friction = .5; + press = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(press, &collider); + } + /* Elastic and creeping columns under their own weight. */ + for (int i = 0; i < 2; ++i) { + const R2Real x = 5.5 + i * 1.5; + R2SoftBodyDesc column = r2GridSoftBodyDesc(r2Vector(x, 1.2), r2Vector(0.3, 1.2), 3, 12); + column.cellModel = R2_SOFT_CELL_COROTATIONAL; + column.particleMass = .2; + column.canSleep = !testbed->noSleep; + { + R2SoftBodyMaterial material = clay(2.0e3, i == 0 ? 0 : .04, .5); + column.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.1); + surface.friction = 1; + column.collider = surface; + } + r2InsertSoftBody(world, &column); + } + /* Volumetric clay disks thrown at the wall. */ + const size_t n = 24; + uint32_t indices[24][2]; + for (uint32_t i = 0; i < n; ++i) { + indices[i][0] = i; + indices[i][1] = (i + 1) % n; + } + for (int i = 0; i < 3; ++i) { + const R2Vector center = r2Vector(11.5 - i * 1.5, 1 + i * .5); + R2Vector vertices[24]; + for (size_t k = 0; k < n; ++k) { + const R2Real a = (R2Real)k / n * 2 * R2_PI; + vertices[k] = r2VectorAdd(center, r2Vector(.5 * cos(a), .5 * sin(a))); + } + + R2VolumeMeshParameters ballMeshing = r2NewVolumeMeshParameters(.15); + R2SoftBodyDesc ball = r2VolumetricSoftBodyDesc( + (R2VectorView){vertices, n}, (R2SurfaceElementView){(const R2Edge *)&indices[0][0], n}, + ballMeshing); + { + ball.cellModel = R2_SOFT_CELL_COROTATIONAL; + R2SoftBodyMaterial material = clay(1.0e4, .03, 60); + ball.material = material; + ball.particleMass = .05; + ball.canSleep = !testbed->noSleep; + { + R2ColliderDesc surface = r2BallColliderDesc(.075); + surface.friction = .8; + ball.collider = surface; + } + R2SoftBodyHandle handle = r2InsertSoftBody(world, &ball); + + size_t count = r2SoftBody_NumParticles(handle); + for (size_t k = 0; k < count; ++k) { + r2SoftBody_SetParticleVelocity(handle, k, r2Vector(10, 1)); + } + } + } + + tbCamera2(testbed, 0, 3, 30); + + tbSetWorld(testbed, world); + R2Real t = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + R2Real dt = r2TimeStep(world); + t += dt; + /* One second down, one second up, then move to the next stamp. */ + const R2Real period = 3; + const R2Real cycle = floor(t / period); + const R2Real phase = t - cycle * period; + const R2Real x = pressRest.x + fmod(cycle, 5); + const R2Real depth = 1; + const R2Real y = pressRest.y - (phase < 1 ? depth * phase + : phase < 2 ? depth * (2 - phase) + : 0); + + r2RigidBody_SetNextKinematicTranslation(press, r2Vector(x, y)); + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_stress2.c b/c/testbed/examples2d/soft_stress2.c new file mode 100644 index 000000000..7fe6447b0 --- /dev/null +++ b/c/testbed/examples2d/soft_stress2.c @@ -0,0 +1,173 @@ +/* Port of examples2d/soft_stress2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSoftStress2(Testbed *testbed) { + R2World *world = r2NewWorld(); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(30, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(8, 9.3); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(3, 0.3)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Bridge under a crate. */ + { + uint32_t pinned[155]; + size_t pinnedCount = 0; + for (uint32_t i = 0; i < 31; ++i) { + for (uint32_t j = 0; j < 5; ++j) { + if (i == 0 || i == 30) { + pinned[pinnedCount++] = i * 5 + j; + } + } + } + R2SoftBodyDesc bridge = r2GridSoftBodyDesc(r2Vector(-7, 3), r2Vector(4.5, 0.6), 31, 5); + r2SoftBodyDesc_SetPinnedParticles(&bridge, + (R2IndexView){(const uint32_t *)pinned, pinnedCount}); + bridge.particleMass = .05; + bridge.particleRadius = (R2OptionalReal){1, .15}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.edgeSoftness = (R2SpringCoefficients){100, 1}; + material.volumeSoftness = (R2SpringCoefficients){100, 1}; + material.tearForce = (R2OptionalReal){1, 65}; + material.tearSmoothing = .5; + bridge.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.15); + surface.friction = .8; + bridge.collider = surface; + } + bridge.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &bridge); + } + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-7, 4.4); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.6, 0.6)); + collider.density = 1.5; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Bar hanging a crate. */ + R2SoftBodyHandle bar; + { + uint32_t pinned[51]; + size_t pinnedCount = 0; + for (uint32_t i = 0; i < 3; ++i) { + for (uint32_t j = 0; j < 17; ++j) { + if (j == 16) { + pinned[pinnedCount++] = i * 17 + j; + } + } + } + R2SoftBodyDesc builder = r2GridSoftBodyDesc(r2Vector(8, 6.8), r2Vector(0.3, 2.2), 3, 17); + r2SoftBodyDesc_SetPinnedParticles(&builder, + (R2IndexView){(const uint32_t *)pinned, pinnedCount}); + builder.particleMass = .05; + builder.particleRadius = (R2OptionalReal){1, .1}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.edgeSoftness = (R2SpringCoefficients){100, 1}; + material.volumeSoftness = (R2SpringCoefficients){100, 1}; + material.tearForce = (R2OptionalReal){1, 16}; + material.tearSmoothing = .5; + builder.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.1); + surface.friction = .8; + builder.collider = surface; + } + builder.canSleep = !testbed->noSleep; + bar = r2InsertSoftBody(world, &builder); + } + R2RigidBodyHandle crateBody; + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(8, 4); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.6, 0.5)); + collider.density = 1; + crateBody = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(crateBody, &collider); + } + + for (size_t i = 0; i < 3; ++i) { + r2SoftBody_AttachParticle(bar, i * 17, crateBody); + } + /* Block under a crate. */ + R2SoftBodyDesc block = r2GridSoftBodyDesc(r2Vector(2, 1.2), r2Vector(1.2, 1.2), 9, 9); + block.particleMass = .05; + block.particleRadius = (R2OptionalReal){1, .15}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.edgeSoftness = (R2SpringCoefficients){100, 1}; + material.volumeSoftness = (R2SpringCoefficients){100, 1}; + material.tearForce = (R2OptionalReal){1, 10}; + material.tearSmoothing = .5; + block.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.15); + surface.friction = .8; + block.collider = surface; + } + block.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &block); + + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(2, 3.2); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.8, 0.6)); + collider.density = 2; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Blob colored by stretch instead of a tear threshold. */ + R2SoftBodyDesc blob = r2DiskSoftBodyDesc(r2Vector(13, 1.6), 1.5, 36); + blob.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){8, 1}); + blob.particleMass = .05; + { + R2ColliderDesc surface = r2BallColliderDesc(.1); + surface.friction = .8; + blob.collider = surface; + } + blob.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &blob); + + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(13, 4); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.6, 0.4)); + collider.density = 1; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + tbCamera2(testbed, 2, 4, 40); + testbed->initialDebug = R2_DEBUG_SOFT_BODIES | R2_DEBUG_SOFT_BODY_STRESS; + + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_surface2.c b/c/testbed/examples2d/soft_surface2.c new file mode 100644 index 000000000..7da687bbc --- /dev/null +++ b/c/testbed/examples2d/soft_surface2.c @@ -0,0 +1,141 @@ +/* Port of examples2d/soft_surface2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSoftSurface2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + R2ColliderDesc boxCollider = r2CuboidColliderDesc(r2Vector(22, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &boxCollider); + + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 22, 2); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 3)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + R2SoftBodyDesc hammock = r2GridSoftBodyDesc(r2Vector(-9, 5), r2Vector(3, 0.15), 21, 2); + hammock.cellModel = R2_SOFT_CELL_VOLUME; + uint32_t hp[] = {0, 1, 40, 41}; + r2SoftBodyDesc_SetPinnedParticles(&hammock, (R2IndexView){(const uint32_t *)hp, 4}); + hammock.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){30, 1}); + hammock.particleMass = 0.05; + hammock.particleRadius = (R2OptionalReal){1, 0.06}; + R2ColliderDesc surfaceCollider = r2BallColliderDesc(0.06); + surfaceCollider.friction = 0.6; + hammock.collider = surfaceCollider; + + if (testbed->noSleep) { + hammock.canSleep = 0; + } + r2InsertSoftBody(world, &hammock); + for (int i = 0; i < 12; i++) { + for (int h = 0; h < 4; h++) { + R2ColliderDesc collider = r2BallColliderDesc(0.08); + collider.density = 2; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-11.2 + i * 0.4 + h % 2 * 0.15, 7 + h * 0.5); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + R2SoftBodyDesc bridge = r2GridSoftBodyDesc(r2Vector(2, 3), r2Vector(3, 0.2), 31, 3); + bridge.cellModel = R2_SOFT_CELL_VOLUME; + uint32_t bp[] = {0, 1, 2, 90, 91, 92}; + r2SoftBodyDesc_SetPinnedParticles(&bridge, (R2IndexView){(const uint32_t *)bp, 6}); + bridge.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){30, 1}); + bridge.particleMass = 0.05; + bridge.particleRadius = (R2OptionalReal){1, 0.05}; + R2ColliderDesc clothSurfaceCollider = r2BallColliderDesc(0.05); + clothSurfaceCollider.friction = 0.8; + bridge.collider = clothSurfaceCollider; + + if (testbed->noSleep) { + bridge.canSleep = 0; + } + r2InsertSoftBody(world, &bridge); + for (int i = 0; i < 8; i++) { + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.03, 0.4)); + collider.density = 3; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-0.4 + i * 0.65, 4.5); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int i = 0; i < 3; i++) { + R2SoftBodyDesc softBody = + r2GridSoftBodyDesc(r2Vector(8, 0.75 + i * 1.6), r2Vector(0.7, 0.7), 5, 5); + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + softBody.cellModel = R2_SOFT_CELL_COROTATIONAL; + material.youngModulus = 1.0e4; + material.poissonRatio = 0.4; + material.elasticDampingRatio = 0.5; + softBody.material = material; + softBody.particleMass = 0.1; + softBody.particleRadius = (R2OptionalReal){1, 0.05}; + R2ColliderDesc jellySurfaceCollider = r2BallColliderDesc(0.05); + jellySurfaceCollider.friction = 0.8; + softBody.collider = jellySurfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r2InsertSoftBody(world, &softBody); + } + R2SoftBodyDesc blob = r2DiskSoftBodyDesc(r2Vector(8, 5), 0.8, 24); + blob.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){20, 1}); + blob.volumeFactor = 1.1; + blob.particleMass = 0.05; + blob.particleRadius = (R2OptionalReal){1, 0.05}; + R2ColliderDesc blobSurfaceCollider = r2BallColliderDesc(0.05); + blobSurfaceCollider.friction = 0.6; + blob.collider = blobSurfaceCollider; + + if (testbed->noSleep) { + blob.canSleep = 0; + } + r2InsertSoftBody(world, &blob); + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(14, 1); + R2ColliderDesc supportCollider = r2CuboidColliderDesc(r2Vector(0.15, 1)); + groundBody.canSleep = !testbed->noSleep; + rigidBodyHandle = r2InsertRigidBody(world, &groundBody); + r2InsertCollider(rigidBodyHandle, &supportCollider); + + R2SoftBodyDesc strip = r2GridSoftBodyDesc(r2Vector(14, 9), r2Vector(4, 0.1), 60, 2); + strip.cellModel = R2_SOFT_CELL_VOLUME; + strip.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){30, 1}); + strip.selfContacts = 1; + strip.particleMass = 0.02; + strip.particleRadius = (R2OptionalReal){1, 0.05}; + R2ColliderDesc stripSurfaceCollider = r2BallColliderDesc(0.05); + stripSurfaceCollider.friction = 0.6; + strip.collider = stripSurfaceCollider; + + if (testbed->noSleep) { + strip.canSleep = 0; + } + r2InsertSoftBody(world, &strip); + /* Set up the viewer. */ + tbCamera2(testbed, 0, 4, 18); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_tearing2.c b/c/testbed/examples2d/soft_tearing2.c new file mode 100644 index 000000000..1cd1cd67e --- /dev/null +++ b/c/testbed/examples2d/soft_tearing2.c @@ -0,0 +1,228 @@ +/* Port of examples2d/soft_tearing2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +typedef struct DrivenParticle { + R2SoftBodyHandle body; + uint32_t index; + R2Vector rest; + int right; +} DrivenParticle; + +/* Follow driven particle indices through compaction and newly split bodies. */ +static void followTears(R2EventCollector *events, DrivenParticle *ends, size_t endCount) { + size_t count = r2EventCollector_TearEventCount(events); + for (size_t i = 0; i < count; ++i) { + R2SoftBodyTearEvent *event = r2EventCollector_TearEvent(events, i); + R2SoftBodyHandle origin = r2SoftBodyTearEvent_SoftBody(event); + for (size_t j = 0; j < endCount; ++j) { + if (ends[j].body.index == origin.index && + ends[j].body.generation == origin.generation) { + R2Bool found; + R2SoftBodyHandle destination; + uint32_t index; + R2OptionalParticleDestination softBodyTearEventTryParticleDestinationResult = + r2SoftBodyTearEvent_TryParticleDestination(event, ends[j].index); + destination = softBodyTearEventTryParticleDestinationResult.body; + index = softBodyTearEventTryParticleDestinationResult.index; + found = softBodyTearEventTryParticleDestinationResult.found; + if (found) { + ends[j].body = destination; + ends[j].index = index; + } + } + } + r2FreeSoftBodyTearEvent(event); + } +} + +void tbSoftTearing2(Testbed *testbed) { + R2World *world = r2NewWorld(); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(30, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(16, 3); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.3, 3)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Bridge. */ + { + const size_t nx = 31, ny = 6; + uint32_t pinned[186]; + size_t pinnedCount = 0; + for (size_t i = 0; i < nx; ++i) { + for (size_t j = 0; j < ny; ++j) { + if (i == 0 || i == nx - 1) { + pinned[pinnedCount++] = (uint32_t)(i * ny + j); + } + } + } + R2SoftBodyDesc bridge = r2GridSoftBodyDesc(r2Vector(-8, 4), r2Vector(3, 0.5), nx, ny); + bridge.cellModel = R2_SOFT_CELL_COROTATIONAL; + r2SoftBodyDesc_SetPinnedParticles(&bridge, + (R2IndexView){(const uint32_t *)pinned, pinnedCount}); + bridge.particleMass = .05; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 1.0e6; + material.poissonRatio = .3; + material.elasticDampingRatio = 1; + material.tearStrain = (R2OptionalReal){1, .35}; + bridge.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.1); + surface.friction = .8; + bridge.collider = surface; + } + bridge.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &bridge); + } + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-8, 9); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2BallColliderDesc(.6); + collider.density = 20; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Curtain. */ + { + const size_t nx = 7, ny = 41; + uint32_t pinned[287]; + size_t pinnedCount = 0; + for (size_t i = 0; i < nx; ++i) { + for (size_t j = 0; j < ny; ++j) { + if (j == ny - 1) { + pinned[pinnedCount++] = (uint32_t)(i * ny + j); + } + } + } + R2SoftBodyDesc curtain = r2GridSoftBodyDesc(r2Vector(2, 5), r2Vector(0.45, 3), nx, ny); + curtain.cellModel = R2_SOFT_CELL_COROTATIONAL; + r2SoftBodyDesc_SetPinnedParticles(&curtain, + (R2IndexView){(const uint32_t *)pinned, pinnedCount}); + curtain.particleMass = .05; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 3.0e4; + material.poissonRatio = .3; + material.elasticDampingRatio = 1; + material.tearStrain = (R2OptionalReal){1, .35}; + curtain.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.08); + surface.friction = .8; + curtain.collider = surface; + } + curtain.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &curtain); + } + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-4, 4.5); + rigidBody.linvel = r2Vector(20, 0); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2BallColliderDesc(.4); + collider.density = 10; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Jelly bar pulled apart by its two pinned ends. */ + R2SoftBodyHandle bar; + R2SoftBodyDesc builder = r2GridSoftBodyDesc(r2Vector(-3, 1), r2Vector(2, 0.4), 21, 5); + builder.cellModel = R2_SOFT_CELL_COROTATIONAL; + builder.particleMass = .05; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 5.0e4; + material.poissonRatio = .3; + material.elasticDampingRatio = 1; + material.tearStrain = (R2OptionalReal){1, .4}; + builder.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.1); + surface.friction = .8; + builder.collider = surface; + } + builder.canSleep = !testbed->noSleep; + bar = r2InsertSoftBody(world, &builder); + + size_t count = r2SoftBody_NumParticles(bar); + DrivenParticle *ends = malloc(count * sizeof(*ends)); + if (!ends) { + abort(); + } + size_t endCount = 0; + for (size_t i = 0; i < count; ++i) { + R2Vector position = r2SoftBody_ParticlePosition(bar, i); + int left = position.x < -4.99; + int right = position.x > -1.01; + if (left || right) { + ends[endCount++] = (DrivenParticle){bar, (uint32_t)i, position, right}; + r2SoftBody_SetParticlePinned(bar, i, 1); + } + } + R2EventCollector *events = r2NewEventCollector(); + + tbCamera2(testbed, 0, 4, 40); + + tbSetWorld(testbed, world); + R2Real t = 0; + uint32_t previousMinPiece = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + const int useDefault = (int)tbLiveSetting(testbed, "Default minimum piece", 1, 0, 1, 1); + const uint32_t selected = + (uint32_t)tbLiveSetting(testbed, "Minimum piece (elements)", 3, 1, 40, 1); + const uint32_t minPiece = useDefault ? 0 : selected; + if (minPiece != previousMinPiece) { + size_t bodyCount = r2SoftBodyCount(world); + R2SoftBodyHandle *handles = malloc(bodyCount * sizeof(*handles)); + if (bodyCount && !handles) { + abort(); + } + bodyCount = r2SoftBodyHandles(world, handles, bodyCount); + for (size_t i = 0; i < bodyCount; ++i) { + R2SoftBodyMaterial material = r2SoftBody_Material(handles[i]); + material.minPiece = (R2OptionalU32){minPiece != 0, minPiece}; + r2SoftBody_SetMaterial(handles[i], &material); + } + free(handles); + previousMinPiece = minPiece; + } + if (tbSimulating(testbed)) { + R2Real dt = r2TimeStep(world); + t += dt; + /* Move the right clamp after one second, stopping at twice the bar's length. */ + const R2Real shift = fmin(fmax(t - 1, 0) * .5, 4); + for (size_t i = 0; i < endCount; ++i) { + if (ends[i].right) { + r2SoftBody_SetParticleKinematicTarget( + ends[i].body, ends[i].index, + r2VectorAdd(ends[i].rest, r2Vector(shift, 0))); + } + } + r2EventCollector_Clear(events); + r2Step(world, NULL, events); + followTears(events, ends, endCount); + } + } + free(ends); + r2FreeEventCollector(events); + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/soft_thin_features2.c b/c/testbed/examples2d/soft_thin_features2.c new file mode 100644 index 000000000..5d173bfae --- /dev/null +++ b/c/testbed/examples2d/soft_thin_features2.c @@ -0,0 +1,187 @@ +/* Port of examples2d/soft_thin_features2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static R2SoftBodyDesc jelly(R2Vector center, R2Real half, size_t n, R2Real young) { + R2SoftBodyDesc builder = r2GridSoftBodyDesc(center, r2Vector(half, half), n, n); + builder.cellModel = R2_SOFT_CELL_COROTATIONAL; + builder.particleMass = .1; + builder.particleRadius = (R2OptionalReal){1, .06}; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = young; + material.poissonRatio = .4; + material.elasticDampingRatio = .5; + builder.material = material; + } + { + R2ColliderDesc surface = r2BallColliderDesc(.06); + surface.friction = .7; + builder.collider = surface; + } + return builder; +} + +static R2SoftBodyDesc strip(R2Vector center, R2Vector half, size_t nx) { + R2SoftBodyDesc builder = r2GridSoftBodyDesc(center, half, nx, 2); + builder.cellModel = R2_SOFT_CELL_VOLUME; + builder.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){30, 1}); + builder.particleMass = .03; + builder.particleRadius = (R2OptionalReal){1, .05}; + { + R2ColliderDesc surface = r2BallColliderDesc(.05); + surface.friction = .6; + builder.collider = surface; + } + return builder; +} + +static R2SoftBodyDesc blob(R2Vector center, R2Real radius) { + R2SoftBodyDesc builder = r2DiskSoftBodyDesc(center, radius, 24); + builder.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){20, 1}); + builder.volumeFactor = 1.2; + builder.particleMass = .05; + builder.particleRadius = (R2OptionalReal){1, .06}; + { + R2ColliderDesc surface = r2BallColliderDesc(.06); + surface.friction = .6; + builder.collider = surface; + } + return builder; +} + +void tbSoftThinFeatures2(Testbed *testbed) { + R2World *world = r2NewWorld(); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(40, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Bed of nails. */ + for (int i = 0; i < 24; ++i) { + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-16 + i * .5, 0.6); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CapsuleYColliderDesc(.6, .03); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + { + R2SoftBodyDesc body = jelly(r2Vector(-14.5, 3.5), .75, 5, 3.0e3); + body.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &body); + } + { + R2SoftBodyDesc body = jelly(r2Vector(-11.5, 3.5), .75, 5, 5.0e4); + body.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &body); + } + { + R2SoftBodyDesc body = blob(r2Vector(-8.5, 3.5), .8); + body.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &body); + } + { + R2SoftBodyDesc body = strip(r2Vector(-6, 6), r2Vector(2.5, .1), 31); + body.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &body); + } + /* Needle rain on a hammock and a jelly block. */ + const uint32_t pinned[] = {0, 1, 60, 61}; + { + R2SoftBodyDesc hammock = strip(r2Vector(0, 4), r2Vector(3, .1), 31); + r2SoftBodyDesc_SetPinnedParticles(&hammock, + (R2IndexView){(const uint32_t *)pinned, TB_COUNT(pinned)}); + hammock.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &hammock); + } + { + R2SoftBodyDesc body = jelly(r2Vector(6.5, .9), .9, 6, 2.0e4); + body.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &body); + } + const R2Real centers[] = {0, 6.5}; + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 3; ++j) { + for (size_t k = 0; k < TB_COUNT(centers); ++k) { + const R2Real cx = centers[k]; + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(cx - 2 + i * .45, 8 + j * 1.2); + rigidBody.position.rotation = r2Rotation(.4 * (i + j)); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CapsuleYColliderDesc(.5, .02); + collider.density = 3; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + } + } + /* Thin plates falling on a blob. */ + { + R2SoftBodyDesc body = blob(r2Vector(11, .9), .9); + body.canSleep = !testbed->noSleep; + r2InsertSoftBody(world, &body); + } + for (int i = 0; i < 4; ++i) { + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(11, 3.5 + i * .5); + rigidBody.position.rotation = r2Rotation(.15 * i); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.9, 0.015)); + collider.density = 1; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* A strip stretched at its right end with a rod resting on it. */ + R2SoftBodyHandle stretched; + { + R2SoftBodyDesc body = strip(r2Vector(16, 3), r2Vector(2.5, .1), 31); + r2SoftBodyDesc_SetPinnedParticles(&body, + (R2IndexView){(const uint32_t *)pinned, TB_COUNT(pinned)}); + body.canSleep = !testbed->noSleep; + stretched = r2InsertSoftBody(world, &body); + } + + R2Vector rightRest[2]; + rightRest[0] = r2SoftBody_ParticlePosition(stretched, 60); + rightRest[1] = r2SoftBody_ParticlePosition(stretched, 61); + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(16, 4); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CapsuleXColliderDesc(.8, .03); + collider.density = 2; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + tbCamera2(testbed, 0, 4, 22); + + tbSetWorld(testbed, world); + R2Real t = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + R2Real dt = r2TimeStep(world); + t += dt; + const R2Real stretch = 2.5 * (1 - cos(.5 * t)); + + for (size_t i = 0; i < 2; ++i) { + r2SoftBody_SetParticleKinematicTarget( + stretched, 60 + i, r2VectorAdd(rightRest[i], r2Vector(stretch, 0))); + } + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/balls2.c b/c/testbed/examples2d/stress_tests/balls2.c new file mode 100644 index 000000000..379fd2b42 --- /dev/null +++ b/c/testbed/examples2d/stress_tests/balls2.c @@ -0,0 +1,32 @@ +/* Port of examples2d/stress_tests/balls2.rs. */ +#include "testbed.h" +#include "rapier_math.h" + +void tbStressTestsBalls2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + for (int i = 0; i < 50; i++) { + for (int j = 0; j < 250; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.bodyType = j ? R2_DYNAMIC : R2_FIXED; + rigidBody.position.translation = r2Vector(i * 2.5 - 62.5, j * 2 + 1); + R2ColliderDesc collider = r2BallColliderDesc(1); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 5); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/boxes2.c b/c/testbed/examples2d/stress_tests/boxes2.c new file mode 100644 index 000000000..62294a79d --- /dev/null +++ b/c/testbed/examples2d/stress_tests/boxes2.c @@ -0,0 +1,47 @@ +/* Port of examples2d/stress_tests/boxes2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsBoxes2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(25, 1.2)); + groundBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle groundBodyHandle = r2InsertRigidBody(world, &groundBody); + r2InsertCollider(groundBodyHandle, &collider); + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 25, 50); + rigidBody.position = r2Pose(r2Vector(side * 25, 50), r2Rotation(R2_PI / 2)); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(50, 1.2)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int i = 0; i < 26; i++) { + for (int j = 0; j < 130; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i - 13, j * 1 + 2.5); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 50, 10); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/capsules2.c b/c/testbed/examples2d/stress_tests/capsules2.c new file mode 100644 index 000000000..e349667fb --- /dev/null +++ b/c/testbed/examples2d/stress_tests/capsules2.c @@ -0,0 +1,48 @@ +/* Port of examples2d/stress_tests/capsules2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsCapsules2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(25, 1.2)); + groundBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle groundBodyHandle = r2InsertRigidBody(world, &groundBody); + r2InsertCollider(groundBodyHandle, &collider); + + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 25, 100); + rigidBody.position = r2Pose(r2Vector(side * 25, 100), r2Rotation(R2_PI / 2)); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(100, 1.2)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int i = 0; i < 26; i++) { + for (int j = 0; j < 130; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i - 13, j * 2.5 + 3.5); + R2ColliderDesc collider = r2CapsuleYColliderDesc(0.75, 0.5); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 50, 10); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/convex_polygons2.c b/c/testbed/examples2d/stress_tests/convex_polygons2.c new file mode 100644 index 000000000..75f114186 --- /dev/null +++ b/c/testbed/examples2d/stress_tests/convex_polygons2.c @@ -0,0 +1,63 @@ +/* Port of examples2d/stress_tests/convex_polygons2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "example_math.h" + +void tbStressTestsConvexPolygons2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(30, 1.2)); + groundBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle groundBodyHandle = r2InsertRigidBody(world, &groundBody); + r2InsertCollider(groundBodyHandle, &collider); + + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 30, 60); + rigidBody.position = r2Pose(r2Vector(side * 30, 60), r2Rotation(R2_PI / 2)); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(60, 1.2)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + R2SharedShape *shapes[5]; + for (size_t i = 0; i < 5; i++) { + R2Vector points[10]; + for (size_t k = 0; k < 10; k++) { + R2Real x = exampleRandom(&testbed->randomState) * 2; + R2Real y = exampleRandom(&testbed->randomState) * 2; + points[k] = r2Vector(x, y); + } + shapes[i] = r2ConvexHullSharedShape((R2VectorView){points, 10}); + } + for (int i = 0; i < 26; i++) { + for (int j = 0; j < 130; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i * 2 - 26, j * 4 + 3); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shapes[i % 5]; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + for (size_t i = 0; i < TB_COUNT(shapes); ++i) { + r2FreeSharedShape(shapes[i]); + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 50, 10); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/heightfield2.c b/c/testbed/examples2d/stress_tests/heightfield2.c new file mode 100644 index 000000000..ab001c8aa --- /dev/null +++ b/c/testbed/examples2d/stress_tests/heightfield2.c @@ -0,0 +1,54 @@ +/* Port of examples2d/stress_tests/heightfield2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsHeightfield2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2Real heights[2001]; + for (int i = 0; i <= 2000; i++) { + heights[i] = i == 0 || i == 2000 ? 80 : (R2Real)cos(i * 50.0 / 2000) * 2; + } + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_HEIGHTFIELD; + collider.shape.heights = (R2RealView){heights, (2001) * (1)}; + collider.shape.rows = 2001; + collider.shape.columns = 1; + collider.shape.scale = r2Vector(50, 1); + collider.shape.flags = 0; + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + + for (int i = 0; i < 26; i++) { + for (int j = 0; j < 130; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i - 13, j + 3.5); + R2ColliderDesc collider; + if (j % 2) { + collider = r2BallColliderDesc(0.5); + } else { + collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + } + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 50, 10); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/joint_ball2.c b/c/testbed/examples2d/stress_tests/joint_ball2.c new file mode 100644 index 000000000..51116dd3b --- /dev/null +++ b/c/testbed/examples2d/stress_tests/joint_ball2.c @@ -0,0 +1,50 @@ +/* Port of examples2d/stress_tests/joint_ball2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsJointBall2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyHandle *handles = calloc(1, 10000 * sizeof(*handles)); + if (!handles) { + abort(); + } + for (int k = 0; k < 100; k++) { + for (int i = 0; i < 100; i++) { + int fixed = k >= 47 && k <= 53 && i == 0; + R2RigidBodyHandle handle; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.bodyType = fixed ? R2_FIXED : R2_DYNAMIC; + rigidBody.position.translation = r2Vector(k, -i); + R2ColliderDesc collider = r2BallColliderDesc(0.4); + rigidBody.canSleep = !testbed->noSleep; + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + for (int dir = 0; dir < 2; dir++) { + if (dir ? k > 0 : i > 0) { + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2Vector(0, 0); + joint.localFrame2.translation = dir ? r2Vector(-1, 0) : r2Vector(0, 1); + r2InsertImpulseJoint(handles[k * 100 + i - (dir ? 100 : 1)], handle, + &joint); + } + } + handles[k * 100 + i] = handle; + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 40.0, -40.0, 5); + free(handles); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/joint_fixed2.c b/c/testbed/examples2d/stress_tests/joint_fixed2.c new file mode 100644 index 000000000..1b3dd43b7 --- /dev/null +++ b/c/testbed/examples2d/stress_tests/joint_fixed2.c @@ -0,0 +1,54 @@ +/* Port of examples2d/stress_tests/joint_fixed2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsJointFixed2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyHandle handles[900]; + for (int xx = 0; xx < 4; xx++) { + for (int yy = 0; yy < 4; yy++) { + for (int k = 0; k < 30; k++) { + for (int i = 0; i < 30; i++) { + R2RigidBodyHandle handle; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.bodyType = k ? R2_DYNAMIC : R2_FIXED; + rigidBody.position.translation = r2Vector(xx * 32 + k, yy * 34 - i); + R2ColliderDesc collider = r2BallColliderDesc(0.4); + rigidBody.canSleep = !testbed->noSleep; + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + if (i) { + R2JointDesc joint = r2DefaultJointDesc(); + joint.lockedAxes = R2_JOINT_FIXED_AXES; + joint.localFrame1.translation = r2Vector(0, 0); + joint.localFrame2.translation = r2Vector(0, 1); + r2InsertImpulseJoint(handles[k * 30 + i - 1], handle, &joint); + } + if (k) { + R2JointDesc joint = r2DefaultJointDesc(); + joint.lockedAxes = R2_JOINT_FIXED_AXES; + joint.localFrame1.translation = r2Vector(0, 0); + joint.localFrame2.translation = r2Vector(-1, 0); + r2InsertImpulseJoint(handles[k * 30 + i - 30], handle, &joint); + } + handles[k * 30 + i] = handle; + } + } + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 50, 50, 5); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/joint_prismatic2.c b/c/testbed/examples2d/stress_tests/joint_prismatic2.c new file mode 100644 index 000000000..c3aed3443 --- /dev/null +++ b/c/testbed/examples2d/stress_tests/joint_prismatic2.c @@ -0,0 +1,50 @@ +/* Port of examples2d/stress_tests/joint_prismatic2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsJointPrismatic2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + for (int l = 0; l < 25; l++) { + for (int j = 0; j < 50; j++) { + R2Real x = j * 4; + R2Real y = l * 24; + R2RigidBodyHandle parent; + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x, y); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.4, 0.4)); + rigidBody.canSleep = !testbed->noSleep; + parent = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(parent, &collider); + for (int i = 0; i < 10; i++) { + R2RigidBodyHandle handle; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x, y - i - 1); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.4, 0.4)); + rigidBody.canSleep = !testbed->noSleep; + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + R2JointDesc joint = r2PrismaticJointDesc(r2Vector(i % 2 ? -1 : 1, 1)); + joint.localFrame1.translation = r2Vector(0, 0); + joint.localFrame2.translation = r2Vector(0, 1); + r2JointDesc_SetLimits(&joint, R2_AXIS_LIN_X, -1.5, 1.5); + r2InsertImpulseJoint(parent, handle, &joint); + parent = handle; + } + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 80, 80, 15); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/large_pyramids2.c b/c/testbed/examples2d/stress_tests/large_pyramids2.c new file mode 100644 index 000000000..158fb8e86 --- /dev/null +++ b/c/testbed/examples2d/stress_tests/large_pyramids2.c @@ -0,0 +1,45 @@ +/* Port of examples2d/stress_tests/large_pyramids2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsLargePyramids2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, -1); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(520, 1)); + groundBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle groundBodyHandle = r2InsertRigidBody(world, &groundBody); + r2InsertCollider(groundBodyHandle, &collider); + for (int p = 0; p < 8; p++) { + for (int i = 0; i < 55; i++) { + for (int j = i; j < 55; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = + r2Vector(p * 65 - 260 + i * 0.5 + j - i, i * 1.001 + 0.5); + rigidBody.canSleep = 0; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 27.5, 3); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/many_pyramids2.c b/c/testbed/examples2d/stress_tests/many_pyramids2.c new file mode 100644 index 000000000..05d083221 --- /dev/null +++ b/c/testbed/examples2d/stress_tests/many_pyramids2.c @@ -0,0 +1,57 @@ +/* Port of examples2d/stress_tests/many_pyramids2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsManyPyramids2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyHandle floor; + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = r2Vector(0, 0); + groundBody.canSleep = !testbed->noSleep; + floor = r2InsertRigidBody(world, &groundBody); + + for (int i = 0; i < 20; i++) { + R2SharedShape *shape = r2SegmentSharedShape(r2Vector(-110, i * 11), r2Vector(110, i * 11)); + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + r2InsertCollider(floor, &collider); + + r2FreeSharedShape(shape); + } + for (int row = 0; row < 20; row++) { + for (int col = 0; col < 20; col++) { + for (int i = 0; i < 10; i++) { + for (int j = i; j < 10; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = + r2Vector((i + 1) * 0.5 + (j - i) - 110 + col * 11 + 1 - 0.5, + (2 * i + 1) * 0.5 + row * 11); + rigidBody.canSleep = 0; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 110, 3); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/pyramid2.c b/c/testbed/examples2d/stress_tests/pyramid2.c new file mode 100644 index 000000000..0a7c87155 --- /dev/null +++ b/c/testbed/examples2d/stress_tests/pyramid2.c @@ -0,0 +1,38 @@ +/* Port of examples2d/stress_tests/pyramid2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsPyramid2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(100, 1)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + for (int i = 0; i < 100; i++) { + for (int j = i; j < 100; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(i * 0.5 + j - i - 50, i + 2.25); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 5); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/ragdolls2.c b/c/testbed/examples2d/stress_tests/ragdolls2.c new file mode 100644 index 000000000..09625bdcd --- /dev/null +++ b/c/testbed/examples2d/stress_tests/ragdolls2.c @@ -0,0 +1,92 @@ +/* Port of examples2d/stress_tests/ragdolls2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +typedef struct Part { + R2RigidBodyHandle handle; + R2Vector offset; +} Part; + +/* A body and its offset from the ragdoll's torso. */ +static Part part(R2World *world, R2Vector origin, R2Vector offset, R2ColliderDesc collider, + int noSleep) { + R2RigidBodyDesc body = r2DynamicRigidBodyDesc(); + body.position.translation = r2VectorAdd(origin, offset); + body.canSleep = !noSleep; + Part result = {.offset = offset}; + result.handle = r2InsertRigidBody(world, &body); + r2InsertCollider(result.handle, &collider); + + return result; +} + +static R2JointDesc revolute(Part parent, Part child, R2Vector anchor, R2Real min, R2Real max) { + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2VectorSub(anchor, parent.offset); + joint.localFrame2.translation = r2VectorSub(anchor, child.offset); + r2JointDesc_SetLimits(&joint, R2_AXIS_ANG_X, min, max); + joint.contactsEnabled = 0; + return joint; +} + +/* Ten bodies and nine limited joints, matching the Rust ragdoll. */ +static void ragdoll(R2World *world, R2Vector origin, int noSleep) { + R2ColliderDesc collider = r2CapsuleYColliderDesc(.3, .15); + Part torso = part(world, origin, r2Vector(0, 0), collider, noSleep); + collider = r2BallColliderDesc(.15); + Part head = part(world, origin, r2Vector(0, 0.55), collider, noSleep); + R2JointDesc neck = revolute(torso, head, r2Vector(0, 0.42), -.5, .5); + R2ImpulseJointHandle jointHandle = + r2InsertImpulseJoint(torso.handle, head.handle, &neck); + + for (int side = -1; side <= 1; side += 2) { + collider = r2CapsuleXColliderDesc(.14, .06); + Part upperArm = part(world, origin, r2Vector(side * .36, .25), collider, noSleep); + collider = r2CapsuleXColliderDesc(.14, .06); + Part forearm = part(world, origin, r2Vector(side * .70, .25), collider, noSleep); + collider = r2CapsuleYColliderDesc(.16, .07); + Part thigh = part(world, origin, r2Vector(side * .09, -.52), collider, noSleep); + collider = r2CapsuleYColliderDesc(.16, .07); + Part shin = part(world, origin, r2Vector(side * .09, -.92), collider, noSleep); + R2JointDesc shoulder = revolute(torso, upperArm, r2Vector(side * .19, .25), -1.2, 1.2); + jointHandle = r2InsertImpulseJoint(torso.handle, upperArm.handle, &shoulder); + + R2JointDesc elbow = revolute(upperArm, forearm, r2Vector(side * .53, .25), 0, 2.5); + jointHandle = r2InsertImpulseJoint(upperArm.handle, forearm.handle, &elbow); + + R2JointDesc hip = revolute(torso, thigh, r2Vector(side * .09, -.33), -1, 1); + jointHandle = r2InsertImpulseJoint(torso.handle, thigh.handle, &hip); + + R2JointDesc knee = revolute(thigh, shin, r2Vector(side * .09, -.72), 0, 2.3); + jointHandle = r2InsertImpulseJoint(thigh.handle, shin.handle, &knee); + } +} + +void tbStressTestsRagdolls2(Testbed *testbed) { + R2World *world = r2NewWorld(); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -1); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(1000, 1)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* 200 ragdolls: 20 columns and 10 layers. */ + for (int layer = 0; layer < 10; ++layer) { + for (int col = 0; col < 20; ++col) { + ragdoll(world, r2Vector(col * 2.2, 1.5 + layer * 2.6), testbed->noSleep); + } + } + tbCamera2(testbed, 22, 6, 15); + + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/ropes2.c b/c/testbed/examples2d/stress_tests/ropes2.c new file mode 100644 index 000000000..48fce02e9 --- /dev/null +++ b/c/testbed/examples2d/stress_tests/ropes2.c @@ -0,0 +1,48 @@ +/* Port of examples2d/stress_tests/ropes2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsRopes2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + for (int i = 0; i < 64; i++) { + R2Vector top = r2Vector(i * 4, 0); + R2RigidBodyHandle parent; + R2RigidBodyDesc groundBody = r2FixedRigidBodyDesc(); + groundBody.position.translation = top; + groundBody.canSleep = !testbed->noSleep; + parent = r2InsertRigidBody(world, &groundBody); + + for (int s = 0; s < 60; s++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2VectorAdd(top, r2Vector(0, -(s + 0.5))); + rigidBody.linvel = r2Vector(2, 0); + R2RigidBodyHandle handle; + R2ColliderDesc collider = r2CapsuleYColliderDesc(0.35, 0.1); + rigidBody.canSleep = !testbed->noSleep; + handle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(handle, &collider); + + R2JointDesc joint = r2RevoluteJointDesc(); + joint.localFrame1.translation = r2Vector(0, s ? -0.5 : 0); + joint.localFrame2.translation = r2Vector(0, 0.5); + joint.contactsEnabled = 0; + r2InsertImpulseJoint(parent, handle, &joint); + parent = handle; + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 128, -30, 4); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/soft_blobs2.c b/c/testbed/examples2d/stress_tests/soft_blobs2.c new file mode 100644 index 000000000..e9c5fb8db --- /dev/null +++ b/c/testbed/examples2d/stress_tests/soft_blobs2.c @@ -0,0 +1,58 @@ +/* Port of examples2d/stress_tests/soft_blobs2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsSoftBlobs2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(20, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 20, 30); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 30)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int j = 0; j < 60; j++) { + for (int i = 0; i < 19; i++) { + R2SoftBodyDesc softBody = r2DiskSoftBodyDesc( + r2Vector(-19 + i * 2 + j % 2 * 0.5, 3 + j * 2), 0.45 + 0.1 * ((i + j) % 3), 20); + softBody.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){60, 3}); + softBody.volumeFactor = 1.05; + softBody.selfContacts = 1; + softBody.particleMass = 0.05; + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r2InsertSoftBody(world, &softBody); + } + } + R2SoftBodyDesc softBodyValue = r2GridSoftBodyDesc(r2Vector(0, 16), r2Vector(3, 0.15), 40, 3); + softBodyValue.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){120, 1}); + softBodyValue.selfContacts = 1; + softBodyValue.particleMass = 0.1; + if (testbed->noSleep) { + softBodyValue.canSleep = 0; + } + r2InsertSoftBody(world, &softBodyValue); + /* Set up the viewer. */ + tbCamera2(testbed, 0, 6, 30); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/soft_cloth_keva2.c b/c/testbed/examples2d/stress_tests/soft_cloth_keva2.c new file mode 100644 index 000000000..73b9211ec --- /dev/null +++ b/c/testbed/examples2d/stress_tests/soft_cloth_keva2.c @@ -0,0 +1,68 @@ +/* Port of examples2d/stress_tests/soft_cloth_keva2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsSoftClothKeva2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(40, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + + R2Real y = 0; + for (int pair = 0; pair < 18; pair++) { + int n = 20 - pair; + for (int k = 0; k <= n; k++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-n + k * 2, y + 1); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.1, 1)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int k = 0; k < n; k++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-n + k * 2 + 1, y + 2.1); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(1, 0.1)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + y += 2.2; + } + R2SoftBodyDesc softBody = r2GridSoftBodyDesc(r2Vector(0, y + 6), r2Vector(24, 0.1), 241, 2); + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + softBody.cellModel = R2_SOFT_CELL_COROTATIONAL; + material.youngModulus = 2.0e4; + material.poissonRatio = 0.4; + material.elasticDampingRatio = 0.5; + softBody.material = material; + softBody.selfContacts = 1; + softBody.particleMass = 0.05; + softBody.particleRadius = (R2OptionalReal){1, 0.06}; + R2ColliderDesc surfaceCollider = r2BallColliderDesc(0.06); + surfaceCollider.friction = 0.6; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r2InsertSoftBody(world, &softBody); + /* Set up the viewer. */ + tbCamera2(testbed, 0, 20, 12); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/soft_fem_beams2.c b/c/testbed/examples2d/stress_tests/soft_fem_beams2.c new file mode 100644 index 000000000..fa650e69b --- /dev/null +++ b/c/testbed/examples2d/stress_tests/soft_fem_beams2.c @@ -0,0 +1,83 @@ +/* Port of examples2d/stress_tests/soft_fem_beams2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#ifdef RAPIER_FEM + +void tbStressTestsSoftFemBeams2(Testbed *testbed) { + R2World *world = r2NewWorld(); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(40, 0.5)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Cantilevers bolted at their left end, stiffer on successive rows. */ + const R2Real length = 4.0, thickness = 0.4; + for (int row = 0; row < 5; ++row) { + for (int col = 0; col < 10; ++col) { + const R2Real x0 = -30.0 + col * 6.0; + const R2Real y = 3.0 + row * 5.0; + R2SoftBodyDesc beam = r2GridSoftBodyDesc( + r2Vector(x0 + length * 0.5, y), r2Vector(length * 0.5, thickness * 0.5), 33, 5); + beam.cellModel = R2_SOFT_CELL_COROTATIONAL; + beam.totalMass = (R2OptionalReal){1, 8.0}; + beam.solver = R2_SOFT_SOLVER_FEM; + { + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + material.youngModulus = 1.0e6 * (1.0 + row); + material.poissonRatio = 0.3; + material.elasticDampingRatio = 1.0; + beam.material = material; + } + beam.canSleep = !testbed->noSleep; + uint32_t *beamPins = NULL; + { + size_t count = r2SoftBodyDesc_ParticlePositions(&beam, NULL, 0); + R2Vector *positions = malloc(count * sizeof(*positions)); + beamPins = malloc(count * sizeof(*beamPins)); + if (!positions || !beamPins) { + abort(); + } + count = r2SoftBodyDesc_ParticlePositions(&beam, positions, count); + size_t beamPinsCount = 0; + for (size_t i = 0; i < count; ++i) { + if (positions[i].x < x0 + 1.0e-4) { + beamPins[beamPinsCount++] = (uint32_t)i; + } + } + r2SoftBodyDesc_SetPinnedParticles( + &beam, (R2IndexView){(const uint32_t *)beamPins, beamPinsCount}); + + free(positions); + } + r2InsertSoftBody(world, &beam); + free(beamPins); + + /* Load dropped on the free end. */ + { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(x0 + length - 0.5, y + 2.0); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.4, 0.4)); + collider.density = 20.0; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + } + tbCamera2(testbed, 0, 12, 15); + + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} +#endif diff --git a/c/testbed/examples2d/stress_tests/soft_jellies2.c b/c/testbed/examples2d/stress_tests/soft_jellies2.c new file mode 100644 index 000000000..8b63958e5 --- /dev/null +++ b/c/testbed/examples2d/stress_tests/soft_jellies2.c @@ -0,0 +1,61 @@ +/* Port of examples2d/stress_tests/soft_jellies2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsSoftJellies2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(16, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 16, 30); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 30)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + int k = 0; + for (int layer = 0; layer < 40; layer++) { + for (int i = 0; i < 12; i++, k++) { + R2SoftBodyDesc softBody = + r2GridSoftBodyDesc(r2Vector(-14 + i * 2.5 + layer % 2 * 0.8, 2 + layer * 2.4), + r2Vector(0.6, 0.6), 5, 5); + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + softBody.cellModel = k % 3 ? R2_SOFT_CELL_COROTATIONAL : R2_SOFT_CELL_NEO_HOOKEAN; + material.youngModulus = 2.0e3 * (1 + k % 5 * 3); + material.poissonRatio = 0.4; + material.elasticDampingRatio = 0.5; + softBody.material = material; + softBody.particleMass = 0.1; + softBody.particleRadius = (R2OptionalReal){1, 0.08}; + R2ColliderDesc surfaceCollider = r2BallColliderDesc(0.08); + surfaceCollider.friction = 0.7; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r2InsertSoftBody(world, &softBody); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 8, 25); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/soft_ropes2.c b/c/testbed/examples2d/stress_tests/soft_ropes2.c new file mode 100644 index 000000000..78f5d1a75 --- /dev/null +++ b/c/testbed/examples2d/stress_tests/soft_ropes2.c @@ -0,0 +1,67 @@ +/* Port of examples2d/stress_tests/soft_ropes2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsSoftRopes2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(10, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 10, 5); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 10)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int i = 0; i < 5; i++) { + R2SharedShape *shape = r2CapsuleSharedShape(r2Vector(-0.6, 0), r2Vector(0.6, 0), 0.1); + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-6 + i * 3, 5); + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + + r2FreeSharedShape(shape); + } + for (int layer = 0; layer < 80; layer++) { + for (int i = 0; i < 4; i++) { + R2Real x = -8.6 + i * 4.4 + layer % 2 * 0.6; + R2Real y = 8 + layer * 1.2; + R2SoftBodyDesc softBody = + r2RopeSoftBodyDesc(r2Vector(x, y), r2Vector(x + 4, y + 0.4), 40); + softBody.particleMass = 0.03; + R2ColliderDesc surfaceCollider = r2BallColliderDesc(0.4); + surfaceCollider.friction = 0.6; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r2InsertSoftBody(world, &softBody); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 8, 25); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/soft_slab2.c b/c/testbed/examples2d/stress_tests/soft_slab2.c new file mode 100644 index 000000000..51272890a --- /dev/null +++ b/c/testbed/examples2d/stress_tests/soft_slab2.c @@ -0,0 +1,81 @@ +/* Port of examples2d/stress_tests/soft_slab2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsSoftSlab2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(14, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 14, 30); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 30)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int i = 0; i < 12; i++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-11 + i * 2, 0.5); + R2ColliderDesc collider; + if (i % 2) { + collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + } else { + collider = r2BallColliderDesc(0.5); + } + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + R2SoftBodyDesc softBody = r2GridSoftBodyDesc(r2Vector(0, 3), r2Vector(12, 1.5), 161, 21); + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + softBody.cellModel = R2_SOFT_CELL_COROTATIONAL; + material.youngModulus = 4.0e4; + material.poissonRatio = 0.4; + material.elasticDampingRatio = 0.5; + softBody.material = material; + softBody.particleMass = 0.2; + R2ColliderDesc surfaceCollider = r2BallColliderDesc(0.1); + surfaceCollider.friction = 0.7; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r2InsertSoftBody(world, &softBody); + for (int j = 0; j < 40; j++) { + for (int i = 0; i < 24; i++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-11.5 + i + j % 2 * 0.5, 7.5 + j * 1.2); + R2ColliderDesc collider; + if ((i + j) % 2) { + collider = r2CuboidColliderDesc(r2Vector(0.3, 0.3)); + } else { + collider = r2BallColliderDesc(0.3); + } + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 8, 25); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/soft_strips2.c b/c/testbed/examples2d/stress_tests/soft_strips2.c new file mode 100644 index 000000000..271eff5f8 --- /dev/null +++ b/c/testbed/examples2d/stress_tests/soft_strips2.c @@ -0,0 +1,71 @@ +/* Port of examples2d/stress_tests/soft_strips2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsSoftStrips2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, -0.5); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(10, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + + for (int side = -1; side <= 1; side += 2) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(side * 10, 30); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 30)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + for (int row = 0; row < 4; row++) { + for (int i = 0; i < 6; i++) { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-7.5 + i * 3 + row % 2 * 1.5, 4 + row * 3); + R2ColliderDesc collider = r2BallColliderDesc(0.25); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + for (int layer = 0; layer < 30; layer++) { + for (int i = 0; i < 5; i++) { + R2SoftBodyDesc softBody = + r2GridSoftBodyDesc(r2Vector(-7.8 + i * 3.7 + layer % 2 * 0.7, 17 + layer * 1.5), + r2Vector(1.6, 0.1), 33, 2); + R2SoftBodyMaterial material = r2DefaultSoftBodyMaterial(); + softBody.cellModel = R2_SOFT_CELL_COROTATIONAL; + material.youngModulus = 2.0e4; + material.poissonRatio = 0.4; + material.elasticDampingRatio = 0.5; + softBody.material = material; + softBody.selfContacts = 1; + softBody.particleMass = 0.05; + softBody.particleRadius = (R2OptionalReal){1, 0.06}; + R2ColliderDesc surfaceCollider = r2BallColliderDesc(0.06); + surfaceCollider.friction = 0.6; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r2InsertSoftBody(world, &softBody); + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 8, 25); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/stress_tests/vertical_stacks2.c b/c/testbed/examples2d/stress_tests/vertical_stacks2.c new file mode 100644 index 000000000..e2f03da9e --- /dev/null +++ b/c/testbed/examples2d/stress_tests/vertical_stacks2.c @@ -0,0 +1,41 @@ +/* Port of examples2d/stress_tests/vertical_stacks2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsVerticalStacks2(Testbed *testbed) { + /* World. */ + R2World *world = r2NewWorld(); + + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(400, 1)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + for (int side = 0; side < 2; side++) { + for (int i = 0; i < 80; i++) { + for (int j = 0; j < 1 + i * 2; j++) { + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = + r2Vector((j - i) * (side ? 1.5 : 1) + (side ? 120 : -120), 80 - i - 1 + 1.5); + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(0.5, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera2(testbed, 0, 2.5, 5); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/trimesh2.c b/c/testbed/examples2d/trimesh2.c new file mode 100644 index 000000000..69b30c27a --- /dev/null +++ b/c/testbed/examples2d/trimesh2.c @@ -0,0 +1,61 @@ +/* Port of examples2d/trimesh2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#include "utils/logo_mesh.h" + +void tbTrimesh2(Testbed *testbed) { + R2World *world = r2NewWorld(); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(0, 0); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(25, 1.2)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(25, 25); + rigidBody.position.rotation = r2Rotation(R2_PI / 2); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(25, 1.2)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-25, 25); + rigidBody.position.rotation = r2Rotation(R2_PI / 2); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2CuboidColliderDesc(r2Vector(25, 1.2)); + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + /* Tessellated by the same SVG utility as the Rust example. */ + for (size_t ith = 0; ith < TB_COUNT(logoMeshes); ++ith) { + const LogoMesh *mesh = &logoMeshes[ith]; + for (int k = 0; k < 5; ++k) { + R2ColliderDesc collider = r2DefaultColliderDesc(); + r2ShapeDesc_SetTrimesh( + &collider.shape, (R2VectorView){mesh->vertices, mesh->vertexCount}, + (R2TriangleView){(const R2Triangle *)mesh->indices, mesh->triangleCount}, 0); + collider.contactSkin = .2; + R2RigidBodyDesc rigidBody = r2DynamicRigidBodyDesc(); + rigidBody.position.translation = r2Vector(ith * 8.0 - 20, 20 + k * 11); + rigidBody.canSleep = !testbed->noSleep; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + } + tbCamera2(testbed, 0, 20, 17); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples2d/utils/character.h b/c/testbed/examples2d/utils/character.h new file mode 100644 index 000000000..18adb2e95 --- /dev/null +++ b/c/testbed/examples2d/utils/character.h @@ -0,0 +1,111 @@ +/* Port of examples2d/utils/character.rs. Shared by the character and tether demos. */ +#ifndef EXAMPLE_CHARACTER_2D_H +#define EXAMPLE_CHARACTER_2D_H +#include "testbed.h" +#include "rapier_math.h" + +typedef enum CharacterControlMode { CHARACTER_KINEMATIC, CHARACTER_PID } CharacterControlMode; + +static void updateCharacter(Testbed *viewer, R2World *world, CharacterControlMode *controlMode, + R2KinematicCharacterController *controller, R2PidController *pid, + R2RigidBodyHandle characterHandle) { + R2Real dt = r2TimeStep(world); + + static const char *const modes[] = {"Kinematic", "PID"}; + const CharacterControlMode mode = + (CharacterControlMode)tbChoice(viewer, "Control mode", 0, modes, TB_COUNT(modes), 1, 0); + if (mode != *controlMode) { + r2RigidBody_SetBodyType(characterHandle, + mode == CHARACTER_KINEMATIC ? R2_KINEMATIC_POSITION_BASED + : R2_DYNAMIC, + mode == CHARACTER_PID); + *controlMode = mode; + } + R2Real speed = tbLiveSetting(viewer, "Character speed", .1, 0, 1, 0); + if (viewer->slow) { + speed /= 10; + } + R2Vector desiredMovement = r2Vector(viewer->inputDirection.x, 0); + desiredMovement.y = (viewer->jump ? 2 : 0) - (viewer->descend ? 1 : 0); + desiredMovement = r2VectorScale(desiredMovement, speed); + R2Vector translation = r2RigidBody_Translation(characterHandle); + if (mode == CHARACTER_PID) { + R2PidGains gains = r2PidController_Gains(pid); + const R2Real linKp = tbLiveSetting(viewer, "Linear Kp", 60, 0, 100, 0); + gains.lin_kp = r2Vector(linKp, linKp); + const R2Real linKi = tbLiveSetting(viewer, "Linear Ki", 1, 0, 10, 0); + gains.lin_ki = r2Vector(linKi, linKi); + const R2Real linKd = tbLiveSetting(viewer, "Linear Kd", 0.8, 0, 1, 0); + gains.lin_kd = r2Vector(linKd, linKd); + const R2Real angKp = tbLiveSetting(viewer, "Angular Kp", 60, 0, 100, 0); + gains.ang_kp = angKp; + const R2Real angKi = tbLiveSetting(viewer, "Angular Ki", 1, 0, 10, 0); + gains.ang_ki = angKi; + const R2Real angKd = tbLiveSetting(viewer, "Angular Kd", 0.8, 0, 1, 0); + gains.ang_kd = angKd; + r2PidController_SetGains(pid, gains); + uint32_t axes = 32; /* Angular axes. */ + if (desiredMovement.x != 0 || desiredMovement.y != 0) { + axes |= desiredMovement.y == 0 ? 1 : 3; + } + r2PidController_SetAxes(pid, axes); + R2Pose target = r2TranslationPose(r2VectorAdd(translation, desiredMovement)); + R2Vector correctiveLinear, linvel; + R2AngVector correctiveAngular, angvel; + R2VelocityCorrection pidControllerRigidBodyCorrectionResult = r2PidController_RigidBodyCorrection(pid, dt, characterHandle, target, r2Vector(0, 0), 0); + correctiveLinear = pidControllerRigidBodyCorrectionResult.linear; + correctiveAngular = pidControllerRigidBodyCorrectionResult.angularVelocity; + linvel = r2RigidBody_Linvel(characterHandle); + angvel = r2RigidBody_Angvel(characterHandle); + r2RigidBody_SetLinvel(characterHandle, r2VectorAdd(linvel, correctiveLinear), 1); + r2RigidBody_SetAngvel(characterHandle, angvel + correctiveAngular, 1); + return; + } + /* Kinematic character settings, applied live. */ + R2CharacterControllerSettings settings = r2KinematicCharacterController_Settings(controller); + settings.slide = (R2Bool)tbLiveSetting(viewer, "Slide", settings.slide, 0, 1, 1); + settings.max_slope_climb_angle = tbLiveSetting( + viewer, "Maximum climb angle", settings.max_slope_climb_angle, 0, 2 * R2_PI, 0); + settings.min_slope_slide_angle = tbLiveSetting( + viewer, "Minimum slide angle", settings.min_slope_slide_angle, 0, R2_PI / 2, 0); + settings.snap_to_ground = + (R2Bool)tbLiveSetting(viewer, "Snap to ground", settings.snap_to_ground, 0, 1, 1); + settings.snap_distance.value = tbLiveSetting(viewer, "Snap distance (relative height)", + settings.snap_distance.value, 0, 10, 0); + r2KinematicCharacterController_SetSlide(controller, settings.slide); + r2KinematicCharacterController_SetSlopes(controller, settings.max_slope_climb_angle, + settings.min_slope_slide_angle); + r2KinematicCharacterController_SetSnapToGround(controller, settings.snap_to_ground, + settings.snap_distance); + desiredMovement.y -= speed; /* Artificial gravity, as in the Rust utility. */ + size_t colliderCount = r2RigidBody_Colliders(characterHandle, NULL, 0); + R2ColliderHandle *handles = malloc(colliderCount * sizeof(*handles)); + if (!colliderCount || !handles) { + abort(); + } + colliderCount = r2RigidBody_Colliders(characterHandle, handles, colliderCount); + + const R2ColliderHandle colliderHandle = handles[0]; + free(handles); + R2Pose pose; + R2SharedShape *shape = NULL; + R2Real mass; + pose = r2Collider_Position(colliderHandle); + shape = r2Collider_CloneShape(colliderHandle); + mass = r2RigidBody_Mass(characterHandle); + R2QueryFilter filter = r2DefaultQueryFilter(); + filter.exclude_rigid_body = characterHandle; + R2QueryOptions query = r2DefaultQueryOptions(); + query.filter = filter; + R2CharacterMovement movement = r2KinematicCharacterController_MoveShape(world, &query, controller, dt, shape, pose, desiredMovement); + + tbBodyColor(viewer, characterHandle, movement.grounded ? .1f : .8f, + movement.grounded ? .8f : .1f, .1f, 1); + r2KinematicCharacterController_SolveCharacterCollisionImpulses(controller, shape, dt, + mass, &filter); + r2FreeSharedShape(shape); + + r2RigidBody_SetNextKinematicTranslation(characterHandle, + r2VectorAdd(translation, movement.translation)); +} +#endif diff --git a/c/testbed/examples2d/utils/logo_mesh.h b/c/testbed/examples2d/utils/logo_mesh.h new file mode 100644 index 000000000..0b4022c3b --- /dev/null +++ b/c/testbed/examples2d/utils/logo_mesh.h @@ -0,0 +1,236 @@ +/* Generated from examples2d/utils/svg.rs by tools/logo-mesh. Do not edit. */ +#ifndef EXAMPLE_LOGO_MESH_H +#define EXAMPLE_LOGO_MESH_H +#include "rapier.h" + +typedef struct LogoMesh { + const R2Vector *vertices; + size_t vertexCount; + const uint32_t *indices; + size_t triangleCount; + const uint32_t *outline; + size_t edgeCount; +} LogoMesh; + +static const R2Vector logoVertices0[] = { + {3.670558691e0, 9.392311096e0}, {2.995823622e0, 9.351827621e0}, + {5.119515896e0, 9.262369156e0}, {1.187533617e0, 9.257364273e0}, + {3.238728046e-1, 9.243869781e0}, {8.501661420e-1, 9.243869781e0}, + {3.373675048e-1, 9.014459610e0}, {3.076791763e0, 8.973976135e0}, + {3.306201696e0, 8.973976135e0}, {2.833887100e0, 8.946986198e0}, + {6.018636703e0, 8.933491707e0}, {9.041449428e-1, 8.919997215e0}, + {4.271835804e0, 8.841166496e0}, {1.322480559e0, 8.812039375e0}, + {1.538395882e0, 8.650102615e0}, {4.952555180e0, 8.474672318e0}, + {1.632858753e0, 8.407198906e0}, {6.594491482e0, 8.328379631e0}, + {1.646353483e0, 8.056336403e0}, {5.385685921e0, 7.887979984e0}, + {6.801329136e0, 7.327621937e0}, {5.532826900e0, 7.111707211e0}, + {6.693478584e0, 6.598976612e0}, {5.265688896e0, 6.164303780e0}, + {6.396488190e0, 6.072615147e0}, {4.466856956e0, 5.590919018e0}, + {2.833887100e0, 5.397880077e0}, {5.168469906e0, 5.222448826e0}, + {2.833887100e0, 5.020028591e0}, {3.549106359e0, 5.020028591e0}, + {3.945671082e0, 4.918076515e0}, {4.223841190e0, 4.601692677e0}, + {6.652887344e0, 2.617971897e0}, {2.833887100e0, 1.862268567e0}, + {1.646353483e0, 1.781300426e0}, {7.192675114e0, 1.781300426e0}, + {2.968834162e0, 1.389954090e0}, {7.651494980e0, 1.335975289e0}, + {1.524901152e0, 1.241512418e0}, {3.414159060e0, 1.160544276e0}, + {8.123809814e0, 1.147049546e0}, {9.986078739e-1, 1.079576015e0}, + {8.731071472e0, 1.039091945e0}, {2.833887041e-1, 9.986078739e-1}, + {4.075399399e0, 9.986078739e-1}, {2.105173349e0, 8.096820116e-1}, + {6.720360756e0, 8.096820116e-1}, {8.110315323e0, 8.096820116e-1}, + {6.275035858e0, 7.961873412e-1}, {8.731071472e0, 7.961873412e-1}, + {2.833887041e-1, 7.557032704e-1}, {4.075399399e0, 7.557032704e-1}, +}; +static const uint32_t logoIndices0[] = { + 5, 4, 6, 0, 1, 2, 2, 1, 3, 3, 5, 6, 6, 7, 8, 3, 6, 8, 2, 3, 8, 2, 8, + 10, 10, 8, 12, 10, 12, 15, 10, 15, 19, 10, 19, 21, 17, 10, 20, 22, 20, 24, 20, 10, 24, 10, + 21, 24, 21, 23, 24, 24, 23, 25, 24, 25, 27, 25, 26, 27, 27, 28, 29, 27, 29, 30, 27, 30, 31, + 27, 31, 32, 32, 31, 35, 35, 31, 37, 42, 40, 46, 40, 37, 46, 37, 31, 46, 46, 31, 48, 42, 46, + 47, 42, 47, 49, 7, 6, 9, 9, 6, 11, 9, 11, 13, 9, 13, 14, 9, 14, 16, 9, 16, 18, 9, + 18, 26, 27, 26, 28, 26, 18, 28, 28, 18, 33, 33, 18, 34, 33, 34, 36, 36, 34, 38, 36, 38, 39, + 39, 38, 41, 44, 39, 45, 39, 41, 43, 39, 43, 45, 45, 43, 50, 44, 45, 51, +}; +static const uint32_t logoOutline0[] = { + 5, 4, 4, 6, 0, 1, 2, 0, 1, 3, 3, 5, 7, 8, 10, 2, 8, 12, 12, 15, 15, + 19, 19, 21, 17, 10, 20, 17, 22, 20, 24, 22, 21, 23, 23, 25, 27, 24, 25, 26, 28, 29, + 29, 30, 30, 31, 32, 27, 35, 32, 37, 35, 42, 40, 40, 37, 31, 48, 48, 46, 46, 47, 47, + 49, 49, 42, 9, 7, 6, 11, 11, 13, 13, 14, 14, 16, 16, 18, 26, 9, 33, 28, 18, 34, + 36, 33, 34, 38, 39, 36, 38, 41, 44, 39, 41, 43, 43, 50, 50, 45, 45, 51, 51, 44, +}; +static const R2Vector logoVertices1[] = { + {5.330406666e0, 6.140089035e0}, {5.451858997e0, 6.099604607e0}, + {3.279212236e0, 6.086110115e0}, {3.778516054e0, 6.032131195e0}, + {4.183357239e0, 5.910678864e0}, {2.280604362e0, 5.897184372e0}, + {4.574703217e0, 5.748742580e0}, {3.130770445e0, 5.667774200e0}, + {2.537003517e0, 5.559816360e0}, {3.832494974e0, 5.532826900e0}, + {1.389954090e0, 5.343901157e0}, {1.956731558e0, 5.181964874e0}, + {4.277820110e0, 5.127985954e0}, {1.538395882e0, 4.507229805e0}, + {4.534219265e0, 4.480240345e0}, {7.557032704e-1, 4.439756393e0}, + {5.465353489e0, 4.007925987e0}, {4.601692677e0, 3.643569231e0}, + {1.362964749e0, 3.508621931e0}, {4.601692677e0, 3.400664568e0}, + {5.127986073e-1, 3.171254635e0}, {1.484417081e0, 2.658455849e0}, + {4.466745853e0, 2.550498247e0}, {6.477456093e-1, 2.186141491e0}, + {4.601692677e0, 2.159152031e0}, {1.808289886e0, 1.929742217e0}, + {5.465353489e0, 1.902752757e0}, {4.129378319e0, 1.875763297e0}, + {4.601692677e0, 1.754310966e0}, {4.372282982e0, 1.605869412e0}, + {6.356003761e0, 1.592374682e0}, {6.490951061e0, 1.511406541e0}, + {5.584416389e0, 1.498087645e0}, {6.234551907e0, 1.497911811e0}, + {2.334583044e0, 1.443933010e0}, {6.099604607e0, 1.430438280e0}, + {3.657063723e0, 1.416943550e0}, {1.052586675e0, 1.389954090e0}, + {5.924173832e0, 1.389954090e0}, {3.049802303e0, 1.255007148e0}, + {3.980936527e0, 1.133554816e0}, {6.248046398e0, 1.079576015e0}, + {4.790618420e0, 9.581237435e-1}, {1.740816236e0, 8.636608720e-1}, + {3.441148520e0, 7.961873412e-1}, {5.910678864e0, 7.961873412e-1}, + {2.712434769e0, 6.612402797e-1}, {5.438364029e0, 6.612402797e-1}, +}; +static const uint32_t logoIndices1[] = { + 24, 22, 27, 24, 27, 29, 29, 27, 36, 29, 36, 39, 29, 39, 40, 31, 30, 33, 31, 33, 35, 31, 35, 38, + 31, 38, 41, 3, 2, 4, 4, 2, 5, 4, 5, 6, 6, 5, 7, 7, 5, 8, 8, 5, 10, 8, 10, 11, + 11, 10, 13, 13, 10, 15, 13, 15, 18, 18, 15, 21, 15, 20, 23, 21, 15, 23, 21, 23, 25, 25, 23, 34, + 34, 23, 37, 34, 37, 39, 40, 39, 44, 39, 37, 43, 39, 43, 44, 44, 43, 46, 1, 0, 6, 6, 7, 9, + 6, 9, 12, 1, 6, 12, 1, 12, 14, 1, 14, 16, 16, 14, 17, 16, 17, 19, 19, 22, 24, 16, 19, 24, + 16, 24, 26, 26, 24, 28, 26, 28, 32, 38, 32, 41, 32, 45, 41, 32, 28, 42, 32, 42, 45, 45, 42, 47, +}; +static const uint32_t logoOutline1[] = { + 22, 27, 29, 24, 27, 36, 36, 39, 40, 29, 31, 30, 30, 33, 33, 35, 35, 38, 41, 31, 3, 2, 4, 3, + 2, 5, 6, 4, 8, 7, 5, 10, 11, 8, 13, 11, 10, 15, 18, 13, 21, 18, 15, 20, 20, 23, 25, 21, + 34, 25, 23, 37, 39, 34, 44, 40, 37, 43, 43, 46, 46, 44, 1, 0, 0, 6, 7, 9, 9, 12, 12, 14, + 16, 1, 14, 17, 17, 19, 19, 22, 26, 16, 24, 28, 32, 26, 38, 32, 45, 41, 28, 42, 42, 47, 47, 45, +}; +static const R2Vector logoVertices2[] = { + {1.902752757e0, 6.693371296e0}, {2.105173349e0, 6.693371296e0}, + {1.673342824e0, 6.409982681e0}, {4.480240345e0, 6.194067478e0}, + {1.376459360e0, 6.180572987e0}, {3.364475727e0, 6.046182632e0}, + {9.986078739e-1, 5.978152275e0}, {5.337097168e0, 5.969727993e0}, + {4.048410058e-1, 5.721753120e0}, {3.643569350e-1, 5.532826900e0}, + {3.495127439e0, 5.532826900e0}, {4.331799030e0, 5.451858997e0}, + {1.025597215e0, 5.424869537e0}, {6.005141735e0, 5.424869537e0}, + {2.105173349e0, 5.370890617e0}, {4.993039131e0, 4.993039131e0}, + {2.105173349e0, 4.952555180e0}, {1.201028347e0, 4.912070751e0}, + {6.420537472e0, 4.628257275e0}, {5.411374569e0, 4.237336159e0}, + {6.571918964e0, 3.535611391e0}, {5.559816360e0, 3.211738825e0}, + {6.356003761e0, 2.388561964e0}, {5.424869537e0, 2.294099092e0}, + {2.105173349e0, 2.051194429e0}, {2.212646961e0, 1.611583352e0}, + {5.033523083e0, 1.578879952e0}, {5.748742580e0, 1.457427740e0}, + {2.537003517e0, 1.255007148e0}, {4.426261902e0, 1.106565475e0}, + {2.105173349e0, 9.986078739e-1}, {3.643569231e0, 9.311342835e-1}, + {4.831102848e0, 8.366714120e-1}, {3.670558691e0, 6.072615385e-1}, + {1.201028347e0, -1.039091945e0}, {2.105173349e0, -1.052586675e0}, + {2.226625681e0, -1.335975289e0}, {1.147049546e0, -1.389954090e0}, + {2.671950579e0, -1.484417081e0}, {8.771554828e-1, -1.511406541e0}, + {3.103781044e-1, -1.619364023e0}, {3.481632471e0, -1.619364023e0}, + {1.956731558e0, -1.794795156e0}, {2.510014296e0, -1.794795156e0}, + {1.066081405e0, -1.808289886e0}, {3.063297033e0, -1.821784616e0}, + {3.103781044e-1, -1.848773837e0}, {3.481632471e0, -1.848773837e0}, +}; +static const uint32_t logoIndices2[] = { + 3, 5, 7, 7, 5, 10, 10, 5, 14, 10, 14, 16, 7, 10, 11, 7, 11, 13, 13, 11, 15, 13, 15, 19, + 13, 19, 21, 18, 13, 20, 13, 22, 20, 13, 21, 22, 22, 21, 23, 22, 23, 26, 22, 26, 27, 27, 26, 29, + 27, 29, 32, 29, 31, 32, 28, 30, 31, 31, 30, 32, 32, 30, 33, 1, 0, 2, 1, 2, 4, 6, 8, 9, + 6, 9, 12, 4, 6, 12, 1, 4, 12, 14, 1, 16, 1, 12, 16, 16, 12, 17, 16, 17, 24, 24, 17, 25, + 28, 25, 30, 25, 17, 30, 30, 17, 34, 30, 34, 35, 35, 34, 36, 36, 34, 37, 36, 37, 38, 38, 37, 39, + 38, 39, 40, 41, 38, 42, 38, 40, 42, 42, 40, 44, 44, 40, 46, 41, 42, 43, 41, 43, 45, 41, 45, 47, +}; +static const uint32_t logoOutline2[] = { + 3, 5, 7, 3, 5, 14, 16, 10, 10, 11, 13, 7, 11, 15, 15, 19, 19, 21, 18, 13, 20, 18, 22, 20, + 21, 23, 23, 26, 27, 22, 26, 29, 32, 27, 29, 31, 31, 28, 30, 33, 33, 32, 1, 0, 0, 2, 2, 4, + 6, 8, 8, 9, 9, 12, 4, 6, 14, 1, 12, 17, 24, 16, 25, 24, 28, 25, 17, 34, 35, 30, 36, 35, + 34, 37, 38, 36, 37, 39, 39, 40, 41, 38, 44, 42, 40, 46, 46, 44, 42, 43, 43, 45, 45, 47, 47, 41, +}; +static const R2Vector logoVertices3[] = { + {1.848773837e0, 9.000965118e0}, {1.430438280e0, 8.825533867e0}, + {2.294099092e0, 8.825533867e0}, {1.241512418e0, 8.380208969e0}, + {2.483024836e0, 8.380208969e0}, {1.416943550e0, 7.975368023e0}, + {2.294099092e0, 7.975368023e0}, {1.821784616e0, 7.799936771e0}, + {2.132162809e0, 6.207562447e0}, {2.361572504e0, 6.207562447e0}, + {1.524901152e0, 5.600300789e0}, {6.612402797e-1, 5.235943794e0}, + {6.612402797e-1, 5.033523083e0}, {1.308985949e0, 4.777123928e0}, + {1.470922351e0, 4.304809570e0}, {1.470922351e0, 1.619364023e0}, + {2.361572504e0, 1.578879952e0}, {1.369922280e0, 1.297711253e0}, + {2.429046154e0, 1.281996489e0}, {2.658455849e0, 1.147049546e0}, + {1.052586675e0, 1.133554816e0}, {3.238728046e-1, 9.851131439e-1}, + {3.319696426e0, 9.851131439e-1}, {1.821784616e0, 8.096820116e-1}, + {9.176396728e-1, 7.961873412e-1}, {2.739423990e0, 7.961873412e-1}, + {3.238728046e-1, 7.557032704e-1}, {3.319696426e0, 7.557032704e-1}, +}; +static const uint32_t logoIndices3[] = { + 0, 1, 2, 1, 3, 5, 4, 2, 6, 2, 1, 5, 2, 5, 6, 6, 5, 7, 9, 8, 10, 10, 11, 12, + 10, 12, 13, 9, 10, 13, 9, 13, 14, 9, 14, 15, 9, 15, 16, 16, 15, 17, 16, 17, 18, 18, 17, 19, + 19, 17, 20, 19, 20, 21, 22, 19, 23, 19, 21, 23, 23, 21, 24, 24, 21, 26, 22, 23, 25, 22, 25, 27, +}; +static const uint32_t logoOutline3[] = { + 0, 1, 2, 0, 1, 3, 3, 5, 4, 2, 6, 4, 5, 7, 7, 6, 9, 8, 8, + 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 16, 9, 15, 17, 18, 16, 19, 18, + 17, 20, 20, 21, 22, 19, 24, 23, 21, 26, 26, 24, 23, 25, 25, 27, 27, 22, +}; +static const R2Vector logoVertices4[] = { + {3.144265175e0, 6.194067478e0}, {3.967442036e0, 6.045626163e0}, + {2.091678619e0, 5.978152275e0}, {3.117275953e0, 5.870194912e0}, + {4.601692677e0, 5.640784740e0}, {3.886473656e0, 5.586805820e0}, + {2.105173349e0, 5.505837440e0}, {1.255007148e0, 5.384385109e0}, + {5.033523083e0, 4.993039131e0}, {4.210346699e0, 4.952555180e0}, + {4.237336159e0, 4.682661057e0}, {4.115883827e0, 4.547714233e0}, + {7.152191401e-1, 4.493735313e0}, {3.872978926e0, 4.493735313e0}, + {1.565385222e0, 4.480240345e0}, {3.522116899e0, 4.480240345e0}, + {1.497911811e0, 4.169862270e0}, {5.208954334e0, 4.169862270e0}, + {1.457427740e0, 3.657063723e0}, {5.127986073e-1, 3.373675108e0}, + {1.605237007e0, 2.695984364e0}, {6.882296801e-1, 2.280604362e0}, + {5.249438286e0, 2.132162809e0}, {2.010710478e0, 1.983721018e0}, + {5.303417206e0, 1.821784616e0}, {4.399272442e0, 1.524901152e0}, + {2.652937412e0, 1.503908396e0}, {1.187533617e0, 1.389954090e0}, + {3.535611391e0, 1.335975289e0}, {4.250830650e0, 9.041449428e-1}, + {1.929742217e0, 8.096820116e-1}, {2.847381830e0, 5.937668085e-1}, +}; +static const uint32_t logoIndices4[] = { + 1, 0, 2, 1, 2, 3, 1, 3, 4, 4, 3, 5, 4, 5, 8, 8, 5, 9, 8, 9, 10, 8, 10, 11, + 13, 15, 16, 8, 11, 17, 11, 13, 17, 13, 16, 17, 24, 22, 25, 24, 25, 29, 25, 28, 29, 3, 2, 6, + 6, 2, 7, 6, 7, 12, 6, 12, 14, 15, 14, 16, 14, 12, 16, 16, 12, 18, 18, 12, 20, 12, 19, 21, + 20, 12, 21, 20, 21, 23, 23, 21, 26, 26, 21, 27, 28, 26, 29, 26, 27, 29, 29, 27, 30, 29, 30, 31, +}; +static const uint32_t logoOutline4[] = { + 1, 0, 0, 2, 4, 1, 3, 5, 8, 4, 5, 9, 9, 10, 10, 11, 13, 15, 17, 8, 11, 13, + 16, 17, 24, 22, 22, 25, 29, 24, 25, 28, 6, 3, 2, 7, 7, 12, 14, 6, 15, 14, 18, 16, + 20, 18, 12, 19, 19, 21, 23, 20, 26, 23, 21, 27, 28, 26, 27, 30, 30, 31, 31, 29, +}; +static const R2Vector logoVertices5[] = { + {2.010710478e0, 6.504445553e0}, {2.253614902e0, 6.504445553e0}, + {3.845989466e0, 6.194067478e0}, {4.372282982e0, 6.005141735e0}, + {3.022813082e0, 5.748742580e0}, {2.294099092e0, 5.721753120e0}, + {1.349470019e0, 5.694763660e0}, {4.561208725e0, 5.519332409e0}, + {2.617971897e0, 5.303417206e0}, {4.453251064e-1, 5.249438286e0}, + {3.130770445e0, 5.208954334e0}, {4.412766933e0, 5.100996494e0}, + {2.860876560e0, 5.074007034e0}, {3.549106359e0, 5.074007034e0}, + {4.183357060e-1, 5.047018051e0}, {7.691979408e-1, 4.966049671e0}, + {4.021420956e0, 4.939060211e0}, {2.307593822e0, 4.831102848e0}, + {1.281996489e0, 4.763628960e0}, {2.590982437e0, 4.750134468e0}, + {1.389954090e0, 4.372282982e0}, {2.388561964e0, 4.358788490e0}, + {2.307593822e0, 4.007925987e0}, {1.389954090e0, 1.781300426e0}, + {2.307593822e0, 1.754310966e0}, {1.322480559e0, 1.295491219e0}, + {2.429046154e0, 1.295491219e0}, {2.914855480e0, 1.120060086e0}, + {9.716184139e-1, 1.093070745e0}, {3.238728046e-1, 9.716184139e-1}, + {3.886473656e0, 9.716184139e-1}, {1.970226288e0, 8.096820116e-1}, + {2.159152031e0, 8.096820116e-1}, {1.093070745e0, 7.961873412e-1}, + {3.103781223e0, 7.961873412e-1}, {3.238728046e-1, 7.557032704e-1}, + {3.886473656e0, 7.557032704e-1}, +}; +static const uint32_t logoIndices5[] = { + 3, 2, 4, 4, 8, 10, 3, 4, 10, 7, 3, 11, 3, 10, 11, 11, 10, 13, 11, 13, 16, + 1, 0, 5, 5, 0, 6, 6, 9, 14, 6, 14, 15, 5, 6, 15, 5, 15, 17, 17, 15, 18, + 10, 8, 12, 12, 8, 17, 12, 17, 19, 17, 18, 19, 19, 18, 20, 19, 20, 21, 21, 20, 22, + 22, 20, 23, 22, 23, 24, 24, 23, 25, 24, 25, 26, 26, 25, 27, 27, 25, 28, 27, 28, 29, + 30, 27, 31, 27, 29, 31, 31, 29, 33, 33, 29, 35, 30, 31, 32, 30, 32, 34, 30, 34, 36, +}; +static const uint32_t logoOutline5[] = { + 3, 2, 2, 4, 4, 8, 7, 3, 11, 7, 10, 13, 13, 16, 16, 11, 1, 0, 5, + 1, 0, 6, 6, 9, 9, 14, 14, 15, 17, 5, 15, 18, 12, 10, 8, 17, 19, 12, + 18, 20, 21, 19, 22, 21, 20, 23, 24, 22, 23, 25, 26, 24, 27, 26, 25, 28, 28, + 29, 30, 27, 33, 31, 29, 35, 35, 33, 31, 32, 32, 34, 34, 36, 36, 30, +}; +static const LogoMesh logoMeshes[] = { + {logoVertices0, 52, logoIndices0, 52, logoOutline0, 52}, + {logoVertices1, 48, logoIndices1, 48, logoOutline1, 48}, + {logoVertices2, 48, logoIndices2, 48, logoOutline2, 48}, + {logoVertices3, 28, logoIndices3, 24, logoOutline3, 28}, + {logoVertices4, 32, logoIndices4, 32, logoOutline4, 32}, + {logoVertices5, 37, logoIndices5, 35, logoOutline5, 37}, +}; +#endif diff --git a/c/testbed/examples2d/voxels2.c b/c/testbed/examples2d/voxels2.c new file mode 100644 index 000000000..bda08b0db --- /dev/null +++ b/c/testbed/examples2d/voxels2.c @@ -0,0 +1,82 @@ +/* Port of examples2d/voxels2.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbVoxels2(Testbed *testbed) { + R2World *world = r2NewWorld(); + const int fallingObjects = (int)tbSetting( + testbed, "Falling objects: 0 Ball, 1 Cuboid, 2 Capsule, 3 Mixed", 3, 0, 3, 1); + const R2Real voxelSizeY = tbSetting(testbed, "Voxel size y", 1, .5, 2, 0); + const R2Vector voxelSize = r2Vector(1, voxelSizeY); + const int testCcd = (int)tbSetting(testbed, "Test CCD", 0, 0, 1, 1); + const int nx = 50; + for (int i = 0; i < nx; ++i) { + for (int j = 0; j < 10; ++j) { + R2RigidBodyDesc rb = r2DynamicRigidBodyDesc(); + rb.position.translation = r2Vector(i * 2.0 - nx / 2.0, 20 + j * 2); + rb.canSleep = !testbed->noSleep; + if (testCcd) { + rb.linvel = r2Vector(0, -1000); + rb.ccdEnabled = 1; + } + const int type = fallingObjects == 3 ? j % 3 : fallingObjects; + R2ColliderDesc co; + switch (type) { + case 0: + co = r2BallColliderDesc(.5); + break; + case 1: + co = r2CuboidColliderDesc(r2Vector(.5, .5)); + break; + case 2: + co = r2CapsuleYColliderDesc(.5, .5); + break; + } + R2RigidBodyHandle rbHandle = r2InsertRigidBody(world, &rb); + r2InsertCollider(rbHandle, &co); + } + } + const R2Vector polyline[] = {{0, 0}, {0, 10}, {7, 4}, {14, 10}, + {14, 0}, {13, 7}, {7, 2}, {1, 7}}; + uint32_t indices[16]; + for (uint32_t i = 0; i < 8; ++i) { + indices[2 * i] = i; + indices[2 * i + 1] = (i + 1) % 8; + } + R2SharedShape *shape = + r2VoxelizedMeshSharedShape((R2VectorView){polyline, TB_COUNT(polyline)}, + (R2SurfaceElementView){(const R2Edge *)indices, 8}, .2); + { + R2RigidBodyDesc rigidBody = r2FixedRigidBodyDesc(); + rigidBody.position.translation = r2Vector(-20, -10); + rigidBody.canSleep = !testbed->noSleep; + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + R2RigidBodyHandle rigidBodyHandle = r2InsertRigidBody(world, &rigidBody); + r2InsertCollider(rigidBodyHandle, &collider); + } + r2FreeSharedShape(shape); + R2Vector voxels[300]; + for (size_t i = 0; i < TB_COUNT(voxels); ++i) { + const R2Real y = fmax(-.5, fmin(.5, sin(i / 20.0))) * 20; + voxels[i] = r2Vector((i - 125.0) * voxelSize.x / 2, y * voxelSize.y); + } + shape = r2VoxelsSharedShapeFromPoints(voxelSize, (R2VectorView){voxels, TB_COUNT(voxels)}); + R2ColliderDesc collider = r2DefaultColliderDesc(); + collider.shape.kind = R2_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + r2InsertColliderWithoutParent(world, &collider); + + r2FreeSharedShape(shape); + tbCamera2(testbed, 0, 20, 17); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r2Step(world, NULL, NULL); + } + } + r2FreeWorld(world); +} diff --git a/c/testbed/examples3d/b3d_joint_grid.c b/c/testbed/examples3d/b3d_joint_grid.c new file mode 100644 index 000000000..4c21cf8e1 --- /dev/null +++ b/c/testbed/examples3d/b3d_joint_grid.c @@ -0,0 +1,60 @@ +/* Port of examples3d/b3d_joint_grid.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB3dJointGrid(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + r3SetGravity(world, r3Vector(0, -10, 0)); + R3RigidBodyHandle *handles = calloc(1, 10000 * sizeof(*handles)); + if (!handles) { + abort(); + } + for (int k = 0; k < 100; k++) { + for (int i = 0; i < 100; i++) { + int fixed = i == 0; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = fixed ? R3_FIXED : R3_DYNAMIC; + rigidBody.position.translation = r3Vector(k, -i, 0); + if (!fixed) { + rigidBody.canSleep = 0; + } + R3ColliderDesc collider = r3BallColliderDesc(0.4); + R3RigidBodyHandle handle; + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + for (int dir = 0; dir < 2; dir++) { + if (dir ? k > 0 : i > 0) { + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = R3_JOINT_SPHERICAL_AXES; + joint.localFrame1.translation = + dir ? r3Vector(0.5, 0, 0) : r3Vector(0, -0.5, 0); + joint.localFrame2.translation = + dir ? r3Vector(-0.5, 0, 0) : r3Vector(0, 0.5, 0); + r3InsertImpulseJoint(handles[k * 100 + i - (dir ? 100 : 1)], handle, + &joint); + } + } + handles[k * 100 + i] = handle; + } + } + /* Set up the viewer. */ + tbCamera(testbed, 50, -25, 90, 50, -50, 0); + free(handles); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/b3d_junkyard.c b/c/testbed/examples3d/b3d_junkyard.c new file mode 100644 index 000000000..4b5a5affa --- /dev/null +++ b/c/testbed/examples3d/b3d_junkyard.c @@ -0,0 +1,114 @@ +/* Port of examples3d/b3d_junkyard.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static void createCylinder(R3Real height, R3Real radius, R3Real yOffset, size_t sides, + R3Vector *points) { + const R3Real deltaAlpha = 2 * R3_PI / sides; + R3Real alpha = 0; + for (size_t i = 0; i < sides; ++i) { + const R3Real sinA = sin(alpha), cosA = cos(alpha); + points[2 * i] = r3Vector(radius * cosA, yOffset, radius * sinA); + points[2 * i + 1] = r3Vector(radius * cosA, yOffset + height, radius * sinA); + alpha += deltaAlpha; + } +} + +static void createRock(R3Real radius, R3Vector points[10]) { + const R3Real phi = (1 + sqrt(5)) / 2; + const R3Real theta = 2 * R3_PI / phi; + const R3Real deltaSin = sin(theta), deltaCos = cos(theta); + R3Real c = 1, s = 0; + for (int i = 0; i < 10; ++i) { + const R3Real z = 1 - (2.0 * i + 1) / 10; + const R3Real radiusXy = sqrt(1 - z * z); + points[i] = r3Vector(radius * radiusXy * c, radius * radiusXy * s, radius * z); + const R3Real c0 = c, s0 = s; + c = deltaCos * c0 - deltaSin * s0; + s = deltaSin * c0 + deltaCos * s0; + } +} + +void tbB3dJunkyard(Testbed *testbed) { + R3World *world = r3NewWorld(); + r3SetGravity(world, r3Vector(0, -10, 0)); + R3RigidBodyHandle ground; + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(120, 1, 120)); + ground = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(ground, &collider); + } + { + R3ColliderDesc wall = r3CuboidColliderDesc(r3Vector(1, 8, 50)); + wall.position.translation = r3Vector(-50, 8, 0); + r3InsertCollider(ground, &wall); + } + { + R3ColliderDesc wall = r3CuboidColliderDesc(r3Vector(1, 8, 50)); + wall.position.translation = r3Vector(50, 8, 0); + r3InsertCollider(ground, &wall); + } + { + R3ColliderDesc wall = r3CuboidColliderDesc(r3Vector(50, 8, 1)); + wall.position.translation = r3Vector(0, 8, -50); + r3InsertCollider(ground, &wall); + } + { + R3ColliderDesc wall = r3CuboidColliderDesc(r3Vector(50, 8, 1)); + wall.position.translation = r3Vector(0, 8, 50); + r3InsertCollider(ground, &wall); + } + R3Vector rockPoints[10]; + createRock(1.5, rockPoints); + R3SharedShape *rock = r3ConvexHullSharedShape((R3VectorView){rockPoints, 10}); + for (int y = 0; y < 24; ++y) { + for (int x = 0; x <= 20; ++x) { + for (int z = 0; z <= 20; ++z) { + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-40 + 4 * x, 4 * y + 25, -40 + 4 * z); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_SHARED; + collider.shape.sharedShape = rock; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + } + r3FreeSharedShape(rock); + const R3Real radius = 35; + R3Vector pusherHull[32]; + createCylinder(24, 4, 0, 16, pusherHull); + R3RigidBodyHandle pusher; + { + R3RigidBodyDesc rigidBody = r3KinematicPositionBasedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(radius, 0, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3DefaultColliderDesc(); + r3ShapeDesc_SetConvexHull(&collider.shape, (R3VectorView){pusherHull, 32}); + pusher = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(pusher, &collider); + } + tbCamera(testbed, 0, 90, 125, 0, 0, 0); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + R3Real degrees = 0; + const R3Real timeStep = 1.0 / 60.0, omega = -6; + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + degrees += omega * timeStep; + const R3Real rad = degrees * R3_PI / 180; + + r3RigidBody_SetNextKinematicTranslation( + pusher, r3Vector(radius * cos(rad), 0, radius * sin(rad))); + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/b3d_large_pyramid.c b/c/testbed/examples3d/b3d_large_pyramid.c new file mode 100644 index 000000000..5e4fcdc00 --- /dev/null +++ b/c/testbed/examples3d/b3d_large_pyramid.c @@ -0,0 +1,45 @@ +/* Port of examples3d/b3d_large_pyramid.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB3dLargePyramid(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + r3SetGravity(world, r3Vector(0, -10, 0)); + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, -1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(400, 1, 400)); + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &boxCollider); + for (int i = 0; i < 200; i++) { + for (int j = i; j < 200; j++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector((i + 1) * 0.5 + j - i - 100, (2 * i + 1) * 0.5, 0); + rigidBody.canSleep = 0; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 0.5)); + collider.density = 100; + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera(testbed, 0, 40, 110, 0, 20, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/b3d_large_world.c b/c/testbed/examples3d/b3d_large_world.c new file mode 100644 index 000000000..f5a649ed8 --- /dev/null +++ b/c/testbed/examples3d/b3d_large_world.c @@ -0,0 +1,55 @@ +/* Port of examples3d/b3d_large_world.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB3dLargeWorld(Testbed *testbed) { + R3World *world = r3NewWorld(); + /* One million parentless floor colliders, matching the Rust benchmark. */ + r3SetGravity(world, r3Vector(0, -10, 0)); + const R3Real cell = 10; + const int grid = 1000, spheres = 100, dropInterval = 5; + const R3Real halfSpan = .5 * cell * grid; + for (int i = 0; i < grid; ++i) { + const R3Real x = -halfSpan + (i + .5) * cell; + for (int j = 0; j < grid; ++j) { + const R3Real z = -halfSpan + (j + .5) * cell; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(.5 * cell, .25, .5 * cell)); + collider.position.translation = r3Vector(x, 0, z); + r3InsertColliderWithoutParent(world, &collider); + } + } + tbCamera(testbed, 0, 60, 250, 0, 0, 0); + + tbSetWorld(testbed, world); + int side = 1; + while (side * side < spheres) { + ++side; + } + int stepCount = 0, dropped = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + if (dropped < spheres && stepCount > 0 && stepCount % dropInterval == 0) { + const int gi = dropped % side, gj = dropped / side; + const R3Real inset = .1 * 2 * halfSpan; + const R3Real usable = 2 * halfSpan - 2 * inset; + const R3Real x = -halfSpan + inset + (gi + .5) * (usable / side); + const R3Real z = -halfSpan + inset + (gj + .5) * (usable / side); + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x, 1.5, z); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3BallColliderDesc(.5); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + ++dropped; + } + ++stepCount; + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/b3d_many_pyramids.c b/c/testbed/examples3d/b3d_many_pyramids.c new file mode 100644 index 000000000..368eb5927 --- /dev/null +++ b/c/testbed/examples3d/b3d_many_pyramids.c @@ -0,0 +1,50 @@ +/* Port of examples3d/b3d_many_pyramids.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB3dManyPyramids(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + r3SetGravity(world, r3Vector(0, -10, 0)); + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, -1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(77, 1, 77)); + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &boxCollider); + for (int row = 0; row < 14; row++) { + for (int col = 0; col < 14; col++) { + for (int i = 0; i < 10; i++) { + for (int j = i; j < 10; j++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector((i + 1) * 0.5 + j - i - 77 + col * 11 + 1 - 0.5, (2 * i + 1) * 0.5, + -76 + row * (152.0 / 13)); + rigidBody.canSleep = 0; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 0.5)); + collider.density = 100; + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 0, 30, 120, 0, 5, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/b3d_rain.c b/c/testbed/examples3d/b3d_rain.c new file mode 100644 index 000000000..3e7a22d8c --- /dev/null +++ b/c/testbed/examples3d/b3d_rain.c @@ -0,0 +1,451 @@ +/* Port of examples3d/b3d_rain.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +enum { + GRID_COUNT = 10, + GROUP_SIZE = 3, + BONE_COUNT = 14, + PELVIS = 0, + SPINE_01 = 1, + SPINE_02 = 2, + SPINE_03 = 3, + NECK = 4, + HEAD = 5, + THIGH_L = 6, + CALF_L = 7, + THIGH_R = 8, + CALF_R = 9, + UPPER_ARM_L = 10, + LOWER_ARM_L = 11, + UPPER_ARM_R = 12, + LOWER_ARM_R = 13 +}; + +const R3Real GRID_SIZE = 15; + +enum JointKind { Spherical, Revolute }; + +typedef struct BoneDef { + int parent; + R3Vector refP; + R3Rotation refQ; + R3Vector capA, capB; + R3Real capR; + enum JointKind kind; + R3Vector frameAP; + R3Rotation frameAQ; + R3Vector frameBP; + R3Rotation frameBQ; + R3Real swingDeg, twistDeg[2]; + int filtered; +} BoneDef; + +static const BoneDef boneDefs[BONE_COUNT] = { + // pelvis + {.parent = -1, + .refP = {0.0, 0.932087, -0.051708}, + .refQ = {0.739169, 0.0, 0.0, 0.673520}, + .capA = {0.07, 0.0, -0.08}, + .capB = {-0.07, 0.0, -0.08}, + .capR = 0.13, + .kind = Spherical, + .frameAP = {0, 0, 0}, + .frameAQ = {0.0, 0.0, 0.0, 1.0}, + .frameBP = {0, 0, 0}, + .frameBQ = {0.0, 0.0, 0.0, 1.0}, + .swingDeg = 0.0, + .twistDeg = {0.0, 0.0}, + .filtered = 0}, + // spine_01 + {.parent = PELVIS, + .refP = {0.0, 1.113505, -0.03481}, + .refQ = {0.739973, 0.0, 0.0, 0.672637}, + .capA = {0.06, 0.0, -0.052264}, + .capB = {-0.06, 0.0, -0.052264}, + .capR = 0.12, + .kind = Spherical, + .frameAP = {0.0, 0.0, -0.182204}, + .frameAQ = {-0.999999, 0.0, 0.0, 0.001194}, + .frameBP = {0.0, 0.0, -0.007736}, + .frameBQ = {-1.0, 0.0, 0.0, 0.0}, + .swingDeg = 25.0, + .twistDeg = {-15.0, 15.0}, + .filtered = 1}, + // spine_02 + {.parent = SPINE_01, + .refP = {0.0, 1.194336, -0.027087}, + .refQ = {0.703611, 0.0, 0.0, 0.710586}, + .capA = {0.08, -0.015133, -0.091801}, + .capB = {-0.08, -0.015133, -0.091801}, + .capR = 0.10, + .kind = Spherical, + .frameAP = {0.0, 0.0, -0.088935}, + .frameAQ = {-0.998619, 0.0, 0.0, -0.052540}, + .frameBP = {0.0, 0.0, -0.008199}, + .frameBQ = {-1.0, 0.0, 0.0, 0.0}, + .swingDeg = 25.0, + .twistDeg = {-15.0, 15.0}, + .filtered = 0}, + // spine_03 + {.parent = SPINE_02, + .refP = {0.0, 1.31043, -0.028232}, + .refQ = {0.669856, 0.000001, -0.000001, 0.742491}, + .capA = {0.11, -0.039753, -0.13}, + .capB = {-0.11, -0.039753, -0.13}, + .capR = 0.145, + .kind = Spherical, + .frameAP = {0.0, 0.0, -0.124298}, + .frameAQ = {-0.998921, 0.000001, -0.000001, -0.046434}, + .frameBP = {0.0, 0.0, 0.0}, + .frameBQ = {-1.0, 0.0, -0.000001, 0.0}, + .swingDeg = 15.0, + .twistDeg = {-10.0, 10.0}, + .filtered = 0}, + // neck + {.parent = SPINE_03, + .refP = {0.0, 1.575582, -0.055837}, + .refQ = {0.879922, 0.0, 0.0, 0.475118}, + .capA = {-0.000001, 0.0, -0.02}, + .capB = {0.0, -0.005, -0.08}, + .capR = 0.07, + .kind = Spherical, + .frameAP = {0.000001, -0.000259, -0.266585}, + .frameAQ = {-0.942192, -0.000001, 0.0, 0.335074}, + .frameBP = {0.0, 0.0, 0.0}, + .frameBQ = {-1.0, 0.0, -0.000001, 0.0}, + .swingDeg = 45.0, + .twistDeg = {-15.0, 15.0}, + .filtered = 0}, + // head + {.parent = NECK, + .refP = {0.0, 1.653348, -0.003241}, + .refQ = {0.750288, 0.0, 0.0, 0.661111}, + .capA = {-0.000001, 0.016892, -0.05869}, + .capB = {0.0, -0.003629, -0.115072}, + .capR = 0.0975, + .kind = Spherical, + .frameAP = {0.0, 0.001321, -0.093873}, + .frameAQ = {-0.974301, 0.0, 0.0, -0.225251}, + .frameBP = {0.0, 0.001268, -0.005104}, + .frameBQ = {-1.0, 0.0, 0.0, 0.0}, + .swingDeg = 15.0, + .twistDeg = {-15.0, 15.0}, + .filtered = 0}, + // thigh_l + {.parent = PELVIS, + .refP = {0.090416, 0.986104, -0.035090}, + .refQ = {-0.703287, -0.070715, 0.053866, 0.705327}, + .capA = {0.023719, 0.006008, -0.039068}, + .capB = {-0.064492, -0.004664, -0.424718}, + .capR = 0.09, + .kind = Spherical, + .frameAP = {0.05, 0.011537, -0.055325}, + .frameAQ = {-0.714896, -0.022305, -0.698361, -0.026790}, + .frameBP = {0.0, 0.0, 0.0}, + .frameBQ = {-0.002064, 0.758987, 0.017046, 0.650880}, + .swingDeg = 10.0, + .twistDeg = {-60.0, 40.0}, + .filtered = 1}, + // calf_l + {.parent = THIGH_L, + .refP = {0.101198, 0.527027, -0.037374}, + .refQ = {-0.653328, -0.066860, 0.058582, 0.751838}, + .capA = {0.001778, 0.0, 0.009841}, + .capB = {-0.078577, 0.014707, -0.41816}, + .capR = 0.075, + .kind = Revolute, + .frameAP = {-0.069989, 0.000253, -0.453844}, + .frameAQ = {-0.000677, 0.760087, 0.105674, 0.641171}, + .frameBP = {0.0, 0.0, 0.0}, + .frameBQ = {-0.044589, 0.765540, 0.053368, 0.639619}, + .swingDeg = 0.0, + .twistDeg = {-5.0, 45.0}, + .filtered = 0}, + // thigh_r + {.parent = PELVIS, + .refP = {-0.090416, 0.986104, -0.03509}, + .refQ = {-0.703287, 0.070715, -0.053865, 0.705326}, + .capA = {-0.023719, 0.006008, -0.039068}, + .capB = {0.064492, -0.004664, -0.424718}, + .capR = 0.09, + .kind = Spherical, + .frameAP = {-0.05, 0.011537, -0.055326}, + .frameAQ = {-0.039089, -0.714094, 0.043177, 0.697623}, + .frameBP = {0.0, 0.0, 0.0}, + .frameBQ = {0.758805, -0.019886, -0.651012, -0.001759}, + .swingDeg = 10.0, + .twistDeg = {-30.0, 60.0}, + .filtered = 1}, + // calf_r + {.parent = THIGH_R, + .refP = {-0.101198, 0.527027, -0.037373}, + .refQ = {-0.653327, 0.06686, -0.058582, 0.751839}, + .capA = {-0.001820, 0.0, 0.010071}, + .capB = {0.077883, 0.014825, -0.418047}, + .capR = 0.075, + .kind = Revolute, + .frameAP = {0.069988, 0.000253, -0.453844}, + .frameAQ = {0.760086, -0.000675, -0.641171, -0.105676}, + .frameBP = {0.0, 0.0, 0.0}, + .frameBQ = {0.765540, -0.044589, -0.639619, -0.053368}, + .swingDeg = 0.0, + .twistDeg = {-45.0, 5.0}, + .filtered = 0}, + // upper_arm_l + {.parent = SPINE_03, + .refP = {0.20378, 1.484275, -0.115897}, + .refQ = {0.143082, 0.695980, -0.690130, 0.13733}, + .capA = {0.0, 0.0, 0.0}, + .capB = {-0.091118, 0.037775, 0.229719}, + .capR = 0.075, + .kind = Spherical, + .frameAP = {0.203780, -0.069369, -0.181921}, + .frameAQ = {-0.278486, 0.445600, -0.097014, 0.845266}, + .frameBP = {0.0, 0.0, 0.0}, + .frameBQ = {-0.201396, -0.001586, 0.901850, 0.382234}, + .swingDeg = 60.0, + .twistDeg = {-5.0, 5.0}, + .filtered = 0}, + // lower_arm_l + {.parent = UPPER_ARM_L, + .refP = {0.305614, 1.242908, -0.117599}, + .refQ = {0.165048, 0.563437, -0.802002, 0.109959}, + .capA = {0.0, 0.0, 0.0}, + .capB = {-0.142406, 0.039392, 0.261092}, + .capR = 0.05, + .kind = Revolute, + .frameAP = {-0.095482, 0.039584, 0.240723}, + .frameAQ = {0.512487, -0.180629, 0.839474, 0.003742}, + .frameBP = {0.0, 0.0, 0.0}, + .frameBQ = {0.503803, -0.029831, 0.858168, 0.094017}, + .swingDeg = 0.0, + .twistDeg = {-5.0, 60.0}, + .filtered = 0}, + // upper_arm_r + {.parent = SPINE_03, + .refP = {-0.20378, 1.484276, -0.115899}, + .refQ = {0.143083, -0.695978, 0.690132, 0.137329}, + .capA = {0.0, 0.0, 0.0}, + .capB = {0.091118, 0.037775, 0.229718}, + .capR = 0.075, + .kind = Spherical, + .frameAP = {-0.203779, -0.069371, -0.181922}, + .frameAQ = {-0.253621, -0.414842, 0.106962, 0.867261}, + .frameBP = {0.0, 0.0, 0.0}, + .frameBQ = {-0.201397, 0.001587, -0.901850, 0.382233}, + .swingDeg = 60.0, + .twistDeg = {-5.0, 5.0}, + .filtered = 0}, + // lower_arm_r + {.parent = UPPER_ARM_R, + .refP = {-0.305614, 1.242907, -0.117599}, + .refQ = {0.165048, -0.563437, 0.802002, 0.109959}, + .capA = {0.0, 0.0, 0.0}, + .capB = {0.142406, 0.039392, 0.261092}, + .capR = 0.05, + .kind = Revolute, + .frameAP = {0.095484, 0.039585, 0.240723}, + .frameAQ = {-0.180627, 0.512487, -0.003744, -0.839474}, + .frameBP = {0.0, 0.0, 0.0}, + .frameBQ = {-0.029831, 0.503803, -0.094017, -0.858169}, + .swingDeg = 0.0, + .twistDeg = {-60.0, 5.0}, + .filtered = 0}, +}; + +typedef struct HumanHandles { + R3RigidBodyHandle bones[BONE_COUNT]; +} HumanHandles; + +static R3Rotation quat(R3Rotation q) { + const R3Real norm = sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); + return (R3Rotation){q.x / norm, q.y / norm, q.z / norm, q.w / norm}; +} + +static HumanHandles createHuman(Testbed *testbed, R3World *world, R3Vector position, + R3Real frictionTorque, R3Real hertz, R3Real damping, + uint32_t groupBit) { + HumanHandles human; + const uint32_t bit = UINT32_C(1) << (groupBit % 24); + const R3InteractionGroups groups = {bit, ~bit, 0}; + (void)frictionTorque; /* The Rust port also omits the friction torque clamp. */ + for (size_t i = 0; i < BONE_COUNT; ++i) { + const BoneDef *def = &boneDefs[i]; + R3RigidBodyDesc body = r3DynamicRigidBodyDesc(); + body.canSleep = !testbed->noSleep; + body.position = r3Pose(r3VectorAdd(position, def->refP), quat(def->refQ)); + human.bones[i] = r3InsertRigidBody(world, &body); + + R3ColliderDesc collider = r3CapsuleColliderDesc(def->capA, def->capB, def->capR); + collider.density = 1000; + if (def->filtered) { + collider.collisionGroups = groups; + } + r3InsertCollider(human.bones[i], &collider); + } + const R3Real omega = 2 * R3_PI * hertz, stiffness = omega * omega, + motorDamping = 2 * damping * omega; + for (size_t i = 0; i < BONE_COUNT; ++i) { + const BoneDef *def = &boneDefs[i]; + if (def->parent < 0) { + continue; + } + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = 7; + joint.localFrame1 = r3Pose(def->frameAP, quat(def->frameAQ)); + joint.localFrame2 = r3Pose(def->frameBP, quat(def->frameBQ)); + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_X, def->twistDeg[0] * R3_PI / 180, + def->twistDeg[1] * R3_PI / 180); + joint.contactsEnabled = 0; + r3JointDesc_SetMotorModel(&joint, R3_AXIS_ANG_X, 0); + r3JointDesc_SetMotorPosition(&joint, R3_AXIS_ANG_X, 0, stiffness, motorDamping); + if (def->kind == Spherical) { + const R3Real swing = def->swingDeg * R3_PI / 180; + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_Y, -swing, swing); + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_Z, -swing, swing); + r3JointDesc_SetMotorModel(&joint, R3_AXIS_ANG_Y, 0); + r3JointDesc_SetMotorModel(&joint, R3_AXIS_ANG_Z, 0); + r3JointDesc_SetMotorPosition(&joint, R3_AXIS_ANG_Y, 0, stiffness, motorDamping); + r3JointDesc_SetMotorPosition(&joint, R3_AXIS_ANG_Z, 0, stiffness, motorDamping); + } else { + joint.lockedAxes = 7 | 16 | 32; + } + r3InsertImpulseJoint(human.bones[def->parent], human.bones[i], &joint); + } + return human; +} + +typedef struct RainState { + HumanHandles groups[GRID_COUNT * GRID_COUNT][GROUP_SIZE]; + size_t columnCount, columnIndex; +} RainState; + +static void createGroup(Testbed *testbed, R3World *world, RainState *state, size_t row, + size_t col) { + const size_t groupIndex = row * GRID_COUNT + col; + const R3Real span = GRID_COUNT * GRID_SIZE, groupDistance = span / GRID_COUNT; + R3Real x = -.5 * span + groupDistance * (col + .5); + const R3Real y = 20; + const R3Real z = -.5 * span + groupDistance * (row + .5); + for (size_t i = 0; i < GROUP_SIZE; ++i) { + state->groups[groupIndex][i] = + createHuman(testbed, world, r3Vector(x, y, z), 5, 1, .7, (uint32_t)groupIndex); + x += .75; + } +} + +static void destroyGroup(RainState *state, size_t row, size_t col) { + for (size_t i = 0; i < GROUP_SIZE; ++i) { + for (size_t j = 0; j < BONE_COUNT; ++j) { + r3RemoveRigidBody(state->groups[row * GRID_COUNT + col][i].bones[j], 1); + } + } +} + +static void stepRain(Testbed *testbed, R3World *world, RainState *state, size_t stepCount) { + if (stepCount & 0x2f) { + return; + } + if (state->columnCount < GRID_COUNT) { + const size_t col = state->columnCount; + for (size_t row = 0; row < GRID_COUNT; ++row) { + createGroup(testbed, world, state, row, col); + } + ++state->columnCount; + } else { + const size_t col = state->columnIndex; + for (size_t row = 0; row < GRID_COUNT; ++row) { + destroyGroup(state, row, col); + createGroup(testbed, world, state, row, col); + } + state->columnIndex = (state->columnIndex + 1) % GRID_COUNT; + } +} + +void tbB3dRain(Testbed *testbed) { + R3World *world = r3NewWorld(); + r3SetGravity(world, r3Vector(0, -10, 0)); + /* Flat grid and torus, reused in each static cell. */ + R3Vector gridVerts[81]; + uint32_t gridIndices[8 * 8 * 6]; + size_t next = 0; + R3Real x = -GRID_SIZE / 2; + for (size_t ix = 0; ix <= 8; ++ix) { + R3Real z = -GRID_SIZE / 2; + for (size_t iz = 0; iz <= 8; ++iz) { + gridVerts[ix * 9 + iz] = r3Vector(x, 0, z); + z += GRID_SIZE / 8; + } + x += GRID_SIZE / 8; + } + for (uint32_t ix = 0; ix < 8; ++ix) { + for (uint32_t iz = 0; iz < 8; ++iz) { + uint32_t i1 = iz + 9 * ix, i2 = i1 + 1, i3 = i2 + 9, i4 = i3 - 1; + uint32_t pair[] = {i1, i2, i3, i3, i4, i1}; + memcpy(&gridIndices[next], pair, sizeof(pair)); + next += 6; + } + } + R3Vector torusVerts[16 * 16]; + uint32_t torusIndices[16 * 16 * 6]; + next = 0; + for (size_t radial = 0; radial < 16; ++radial) { + for (size_t tubular = 0; tubular < 16; ++tubular) { + const R3Real u = tubular / 16.0 * 2 * R3_PI, v = radial / 16.0 * 2 * R3_PI; + torusVerts[radial * 16 + tubular] = r3Vector( + (.25 * GRID_SIZE + cos(v)) * cos(u), (.25 * GRID_SIZE + cos(v)) * sin(u), sin(v)); + } + } + for (uint32_t radial = 0; radial < 16; ++radial) { + for (uint32_t tubular = 0; tubular < 16; ++tubular) { + const uint32_t r2 = (radial + 1) % 16, t2 = (tubular + 1) % 16; + uint32_t i1 = radial * 16 + tubular, i2 = radial * 16 + t2, i3 = r2 * 16 + t2, + i4 = r2 * 16 + tubular; + uint32_t pair[] = {i1, i2, i3, i3, i4, i1}; + memcpy(&torusIndices[next], pair, sizeof(pair)); + next += 6; + } + } + const R3Real span = GRID_SIZE * GRID_COUNT; + x = -.5 * span + .5 * GRID_SIZE; + for (size_t i = 0; i < GRID_COUNT; ++i) { + R3Real z = -.5 * span + .5 * GRID_SIZE; + for (size_t j = 0; j < GRID_COUNT; ++j) { + R3RigidBodyDesc builder = r3FixedRigidBodyDesc(); + builder.position.translation = r3Vector(x, 0, z); + R3RigidBodyHandle cell = r3InsertRigidBody(world, &builder); + + R3ColliderDesc collider = r3DefaultColliderDesc(); + r3ShapeDesc_SetTrimesh(&collider.shape, (R3VectorView){gridVerts, 81}, + (R3TriangleView){(const R3Triangle *)gridIndices, 128}, 0); + r3InsertCollider(cell, &collider); + + collider = r3DefaultColliderDesc(); + r3ShapeDesc_SetTrimesh(&collider.shape, (R3VectorView){torusVerts, 256}, + (R3TriangleView){(const R3Triangle *)torusIndices, 512}, 0); + r3InsertCollider(cell, &collider); + + z += GRID_SIZE; + } + x += GRID_SIZE; + } + RainState *state = calloc(1, sizeof(*state)); + if (!state) { + abort(); + } + tbCamera(testbed, 70, 30, 70, 0, 5, 0); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + size_t stepCount = 0; + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + stepRain(testbed, world, state, stepCount); + ++stepCount; + r3Step(world, NULL, NULL); + } + } + free(state); + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/b3d_trees.c b/c/testbed/examples3d/b3d_trees.c new file mode 100644 index 000000000..23658c365 --- /dev/null +++ b/c/testbed/examples3d/b3d_trees.c @@ -0,0 +1,121 @@ +/* Port of examples3d/b3d_trees.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static void createCylinder(R3Real height, R3Real radius, R3Real yOffset, size_t sides, + R3Vector *points) { + const R3Real deltaAlpha = 2 * R3_PI / sides; + R3Real alpha = 0; + for (size_t i = 0; i < sides; ++i) { + const R3Real sinA = sin(alpha), cosA = cos(alpha); + points[2 * i] = r3Vector(radius * cosA, yOffset, radius * sinA); + points[2 * i + 1] = r3Vector(radius * cosA, yOffset + height, radius * sinA); + alpha += deltaAlpha; + } +} + +static void createWaveMesh(size_t xCount, size_t zCount, R3Real cellWidth, R3Real amplitude, + R3Real rowFrequency, R3Real columnFrequency, R3Vector *vertices, + uint32_t *indices) { + const R3Real omegaZ = 2 * R3_PI * rowFrequency * cellWidth; + const R3Real omegaX = 2 * R3_PI * columnFrequency * cellWidth; + R3Real x = -.5 * cellWidth * xCount; + for (size_t ix = 0; ix <= xCount; ++ix) { + R3Real z = -.5 * cellWidth * zCount; + for (size_t iz = 0; iz <= zCount; ++iz) { + vertices[ix * (zCount + 1) + iz] = + r3Vector(x, amplitude * sin(omegaX * ix) * sin(omegaZ * iz), z); + z += cellWidth; + } + x += cellWidth; + } + size_t next = 0; + for (size_t ix = 0; ix < xCount; ++ix) { + for (size_t iz = 0; iz < zCount; ++iz) { + const uint32_t i1 = iz + (zCount + 1) * ix, i2 = i1 + 1; + const uint32_t i3 = i2 + zCount + 1, i4 = i3 - 1; + indices[next++] = i1; + indices[next++] = i2; + indices[next++] = i3; + indices[next++] = i3; + indices[next++] = i4; + indices[next++] = i1; + } + } +} + +void b3dTreesRun(Testbed *testbed, size_t scale) { + R3World *world = r3NewWorld(); + r3SetGravity(world, r3Vector(0, -10, 0)); + const size_t xCount = scale * 150, zCount = scale * 200; + const size_t vertexCount = (xCount + 1) * (zCount + 1), triangleCount = 2 * xCount * zCount; + R3Vector *vertices = malloc(vertexCount * sizeof(*vertices)); + uint32_t *indices = malloc(triangleCount * 3 * sizeof(*indices)); + if (!vertices || !indices) { + abort(); + } + createWaveMesh(xCount, zCount, 1.0 / scale, .4, .05, .1, vertices, indices); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3DefaultColliderDesc(); + r3ShapeDesc_SetTrimesh(&collider.shape, (R3VectorView){vertices, vertexCount}, + (R3TriangleView){(const R3Triangle *)indices, triangleCount}, 0); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + free(vertices); + free(indices); + R3SharedShape *hulls[22] = {0}; + R3Real y = 1, r = .75; + const R3Real l = 1.5; + for (size_t i = 0; i < TB_COUNT(hulls); ++i) { + R3Vector points[12]; + createCylinder(l + 2 * r, r, y - r, 6, points); + hulls[i] = r3ConvexHullSharedShape((R3VectorView){points, TB_COUNT(points)}); + y += l + 2 * r; + r *= .95; + } + + R3Real angularVelocity = -.5, z = -70; + const int bodyCount = 50; + for (int bodyIndex = 0; bodyIndex < bodyCount; ++bodyIndex) { + const R3Vector position = r3Vector(0, 1, z); + R3RigidBodyDesc builder = r3DynamicRigidBodyDesc(); + builder.position.translation = position; + builder.canSleep = !testbed->noSleep; + R3RigidBodyHandle handle = r3InsertRigidBody(world, &builder); + + for (size_t i = 0; i < TB_COUNT(hulls); ++i) { + R3ColliderDesc collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_SHARED; + collider.shape.sharedShape = hulls[i]; + collider.density = 1; + collider.friction = .9; + r3InsertCollider(handle, &collider); + } + const R3Real velocityScale = .5 + .5 * bodyIndex / bodyCount; + + R3Vector center = r3RigidBody_CenterOfMass(handle); + const R3Vector omega = r3Vector(0, 0, velocityScale * angularVelocity); + const R3Vector velocity = r3VectorCross(omega, r3VectorSub(center, position)); + r3RigidBody_SetAngvel(handle, omega, 1); + r3RigidBody_SetLinvel(handle, velocity, 1); + z += 3; + angularVelocity = -angularVelocity; + } + for (size_t i = 0; i < TB_COUNT(hulls); ++i) { + r3FreeSharedShape(hulls[i]); + } + tbCamera(testbed, 0, 30, 140, 0, 15, 0); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/b3d_trees_run100.c b/c/testbed/examples3d/b3d_trees_run100.c new file mode 100644 index 000000000..271bbea77 --- /dev/null +++ b/c/testbed/examples3d/b3d_trees_run100.c @@ -0,0 +1,7 @@ +/* Port of examples3d/b3d_trees.rs: run100. */ +#include "testbed.h" +void b3dTreesRun(Testbed *testbed, size_t scale); + +void tbB3dTreesRun100(Testbed *testbed) { + b3dTreesRun(testbed, 1); +} diff --git a/c/testbed/examples3d/b3d_trees_run25.c b/c/testbed/examples3d/b3d_trees_run25.c new file mode 100644 index 000000000..5e29a08e9 --- /dev/null +++ b/c/testbed/examples3d/b3d_trees_run25.c @@ -0,0 +1,7 @@ +/* Port of examples3d/b3d_trees.rs: run25. */ +#include "testbed.h" +void b3dTreesRun(Testbed *testbed, size_t scale); + +void tbB3dTreesRun25(Testbed *testbed) { + b3dTreesRun(testbed, 4); +} diff --git a/c/testbed/examples3d/b3d_trees_run50.c b/c/testbed/examples3d/b3d_trees_run50.c new file mode 100644 index 000000000..e2595fa52 --- /dev/null +++ b/c/testbed/examples3d/b3d_trees_run50.c @@ -0,0 +1,7 @@ +/* Port of examples3d/b3d_trees.rs: run50. */ +#include "testbed.h" +void b3dTreesRun(Testbed *testbed, size_t scale); + +void tbB3dTreesRun50(Testbed *testbed) { + b3dTreesRun(testbed, 2); +} diff --git a/c/testbed/examples3d/b3d_washer.c b/c/testbed/examples3d/b3d_washer.c new file mode 100644 index 000000000..36d3ddfcd --- /dev/null +++ b/c/testbed/examples3d/b3d_washer.c @@ -0,0 +1,79 @@ +/* Port of examples3d/b3d_washer.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbB3dWasher(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + r3SetGravity(world, r3Vector(0, -10, 0)); + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, -1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(60, 1, 60)); + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &boxCollider); + + R3RigidBodyDesc rigidBody = r3KinematicVelocityBasedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 21, 0); + rigidBody.angvel = r3Vector(0, 0, R3_PI / 180 * 25); + rigidBody.linvel = r3Vector(0.001, -0.002, 0); + R3RigidBodyHandle washer; + rigidBody.canSleep = !testbed->noSleep; + washer = r3InsertRigidBody(world, &rigidBody); + R3Real angle = R3_PI / 18; + R3Vector u1 = r3Vector(1, 0, 0); + for (int i = 0; i < 36; i++) { + R3Vector u2 = i == 35 ? r3Vector(1, 0, 0) + : r3Vector(cos(angle) * u1.x - sin(angle) * u1.y, + sin(angle) * u1.x + cos(angle) * u1.y, 0); + R3Vector a1 = r3Vector(cos(angle * 0.1) * u1.x + sin(angle * 0.1) * u1.y, + -sin(angle * 0.1) * u1.x + cos(angle * 0.1) * u1.y, 0); + R3Vector a2 = r3Vector(cos(angle * 0.1) * u2.x - sin(angle * 0.1) * u2.y, + sin(angle * 0.1) * u2.x + cos(angle * 0.1) * u2.y, 0); + for (int part = 0; part < (i % 9 == 0 ? 2 : 1); part++) { + R3Vector vertices[8]; + R3Vector left = part ? u1 : a1; + R3Vector right = part ? u2 : a2; + R3Real rmin = part ? 14 : 16; + R3Real rmax = part ? 16 : 18; + for (int z = 0; z < 2; z++) { + R3Vector off = r3Vector(0, 0, z ? 10 : -10); + vertices[z * 4] = r3VectorAdd(r3VectorScale(left, rmin), off); + vertices[z * 4 + 1] = r3VectorAdd(r3VectorScale(left, rmax), off); + vertices[z * 4 + 2] = r3VectorAdd(r3VectorScale(right, rmin), off); + vertices[z * 4 + 3] = r3VectorAdd(r3VectorScale(right, rmax), off); + } + R3ColliderDesc collider = r3DefaultColliderDesc(); + r3ShapeDesc_SetConvexHull(&collider.shape, (R3VectorView){vertices, TB_COUNT(vertices)}); + r3InsertCollider(washer, &collider); + } + u1 = u2; + } + for (int i = 0; i < 20; i++) { + for (int j = 0; j < 20; j++) { + for (int k = 0; k < 20; k++) { + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.2, 0.2, 0.2)); + collider.density = 1000; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-8 + i * 0.8, 13 + j * 0.8, -8 + k * 0.8); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 60, 35, 60, 0, 15, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/ccd3.c b/c/testbed/examples3d/ccd3.c new file mode 100644 index 000000000..99469f140 --- /dev/null +++ b/c/testbed/examples3d/ccd3.c @@ -0,0 +1,100 @@ +/* Port of examples3d/ccd3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static int same(R3RigidBodyHandle handle, R3RigidBodyHandle handleB) { + return handle.world == handleB.world && handle.index == handleB.index && + handle.generation == handleB.generation; +} + +void tbCcd3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle ground = {0}; + R3RigidBodyHandle sensor = {0}; + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(50, 0.1, 50)); + groundBody.canSleep = !testbed->noSleep; + ground = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(ground, &boxCollider); + + for (int wall = 0; wall < 5; wall++) { + for (int row = 0; row < 2; row++) { + int k = 0; + for (int i = 0; i < 8; i++) { + for (int j = i; j < 8; j++) { + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(wall * 6, i + 0.6, i + (j - i) * 2 + row * 20 - 8); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 1)); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + + k++; + tbBodyColor(testbed, handle, k % 2 ? 131.0f / 255 : 1, k % 2 ? 1 : 131.0f / 255, + 244.0f / 255, 1); + } + } + } + } + for (int i = 0; i < 2; i++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-20, 2.6, i * 20); + rigidBody.linvel = r3Vector(1000, 0, 0); + rigidBody.ccdEnabled = 1; + R3ColliderDesc collider = r3BallColliderDesc(1); + collider.density = 10; + if (!i) { + collider.isSensor = 1; + collider.activeEvents = R3_COLLISION_EVENTS; + } + R3RigidBodyHandle handleH; + rigidBody.canSleep = !testbed->noSleep; + handleH = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handleH, &collider); + if (!i) { + sensor = handleH; + } else { + tbBodyColor(testbed, handleH, 0.2, 0.2, 1, 1); + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + R3EventCollector *eventHandler = r3NewEventCollector(); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3EventCollector_Clear(eventHandler); + r3Step(world, NULL, eventHandler); + + size_t n = r3EventCollector_CollisionEvents(eventHandler, NULL, 0); + R3CollisionEvent *events = calloc(n ? n : 1, sizeof(*events)); + if (!events) { + abort(); + } + n = r3EventCollector_CollisionEvents(eventHandler, events, n); + for (size_t i = 0; i < n; i++) { + R3ColliderHandle colliderHandles[] = {events[i].collider1, events[i].collider2}; + for (size_t j = 0; j < 2; j++) { + R3RigidBodyHandle handle = r3Collider_Parent(colliderHandles[j]); + if (!same(handle, ground) && !same(handle, sensor)) { + tbBodyColor(testbed, handle, events[i].started ? 1 : 0.5f, + events[i].started ? 1 : 0.5f, events[i].started ? 0 : 1, 1); + } + } + } + free(events); + } + } + r3FreeEventCollector(eventHandler); + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/character_controller3.c b/c/testbed/examples3d/character_controller3.c new file mode 100644 index 000000000..85f34e048 --- /dev/null +++ b/c/testbed/examples3d/character_controller3.c @@ -0,0 +1,169 @@ +/* Port of examples3d/character_controller3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#include "utils/character.h" + +void tbCharacterController3(Testbed *testbed) { + R3World *world = r3NewWorld(); + + const R3Real groundSize = 5, groundHeight = .1; + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -groundHeight, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = + r3CuboidColliderDesc(r3Vector(groundSize, groundHeight, groundSize)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -groundHeight, -groundSize); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = + r3CuboidColliderDesc(r3Vector(groundSize, groundSize, groundHeight)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Character with stronger gravity and predictive CCD for PID control. */ + R3RigidBodyHandle characterHandle; + { + R3RigidBodyDesc rigidBody = r3KinematicPositionBasedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0.5, 0); + rigidBody.gravityScale = 10; + rigidBody.softCcdPrediction = 10; + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CapsuleYColliderDesc(.3, .15); + characterHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(characterHandle, &collider); + } + tbBodyColor(testbed, characterHandle, .8f, .1f, .1f, 1); + /* Cubes. */ + const int num = 8; + const R3Real rad = .1, shift = rad * 2, centerx = shift * (num / 2), centery = rad; + for (int j = 0; j < 4; ++j) { + for (int k = 0; k < 4; ++k) { + for (int i = 0; i < num; ++i) { + const R3Real x = i * shift - centerx, y = j * shift + centery; + const R3Real z = k * shift + centerx; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x, y, z); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(rad, rad, rad)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + } + /* Stairs. */ + const R3Real stairWidth = 1, stairHeight = .1; + for (int i = 0; i < 10; ++i) { + const R3Real x = i * stairWidth / 2, y = i * stairHeight * 1.5 + 3; + { + R3ColliderDesc collider = + r3CuboidColliderDesc(r3Vector(stairWidth / 2, stairHeight / 2, stairWidth)); + collider.position.translation = r3Vector(x, y, 0); + r3InsertColliderWithoutParent(world, &collider); + } + } + /* Climbable and unclimbable slopes. */ + const R3Real slopeAngle = .2, slopeSize = 2, impossibleSlopeSize = 2; + const R3Real impossibleSlopeAngle = .6; + { + R3ColliderDesc collider = + r3CuboidColliderDesc(r3Vector(slopeSize, groundHeight, slopeSize)); + collider.position.translation = r3Vector(.1 + slopeSize, -groundHeight + .4, 0); + collider.position.rotation = r3RotationFromAxisAngle(r3Vector(0, 0, 1), slopeAngle); + r3InsertColliderWithoutParent(world, &collider); + } + { + R3ColliderDesc collider = + r3CuboidColliderDesc(r3Vector(slopeSize, groundHeight, groundSize)); + collider.position.translation = + r3Vector(.1 + slopeSize * 2 + impossibleSlopeSize - .9, -groundHeight + 1.7, 0); + collider.position.rotation = + r3RotationFromAxisAngle(r3Vector(0, 0, 1), impossibleSlopeAngle); + r3InsertColliderWithoutParent(world, &collider); + } + /* Moving platform. */ + R3RigidBodyHandle platformHandle; + { + R3RigidBodyDesc rigidBody = r3KinematicVelocityBasedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-8, 0, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(2, groundHeight, 2)); + platformHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(platformHandle, &collider); + } + /* Wavy heightfield. */ + const int nsubdivs = 20; + R3Real heights[21 * 21]; + for (int j = 0; j <= nsubdivs; ++j) { + for (int i = 0; i <= nsubdivs; ++i) { + heights[j * 21 + i] = cos(i * 10.0 / nsubdivs / 2) + cos(j * 10.0 / nsubdivs / 2); + } + } + { + R3ColliderDesc collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_HEIGHTFIELD; + collider.shape.heights = (R3RealView){heights, (21) * (21)}; + collider.shape.rows = 21; + collider.shape.columns = 21; + collider.shape.scale = r3Vector(10, 1, 10); + collider.shape.flags = 0; + collider.position.translation = r3Vector(-8, 5, 0); + r3InsertColliderWithoutParent(world, &collider); + } + /* Tilting dynamic body with a limited joint. */ + R3RigidBodyDesc ground = r3FixedRigidBodyDesc(); + ground.position.translation = r3Vector(0, 5, 0); + R3RigidBodyHandle groundHandle, handle; + groundHandle = r3InsertRigidBody(world, &ground); + + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 5, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 0.1, 2)); + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + } + { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_X, -.3, .3); + r3InsertImpulseJoint(groundHandle, handle, &joint); + } + CharacterControlMode controlMode = CHARACTER_KINEMATIC; + R3KinematicCharacterController *controller = NULL; + R3PidController *pid = NULL; + controller = r3NewKinematicCharacterController(); + pid = r3NewPidController(); + r3KinematicCharacterController_SetSlopes(controller, impossibleSlopeAngle - .02, + impossibleSlopeAngle - .02); + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + tbSetWorld(testbed, world); + size_t stepId = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + ++stepId; + + R3Real dt = r3TimeStep(world); + const R3Vector linvel = + r3Vector(sin(stepId * dt * 2) * 2, sin(stepId * dt * 5) * 1.5, 0); + + r3RigidBody_SetLinvel(platformHandle, linvel, 1); + updateCharacter(testbed, world, &controlMode, controller, pid, characterHandle); + } + } + r3FreePidController(pid); + r3FreeKinematicCharacterController(controller); + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/collision_groups3.c b/c/testbed/examples3d/collision_groups3.c new file mode 100644 index 000000000..292b9068b --- /dev/null +++ b/c/testbed/examples3d/collision_groups3.c @@ -0,0 +1,53 @@ +/* Port of examples3d/collision_groups3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbCollisionGroups3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle floor; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(5, 0.1, 5)); + rigidBody.canSleep = !testbed->noSleep; + floor = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(floor, &boxCollider); + for (int i = 1; i <= 2; i++) { + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 0.1, 1)); + collider.position.translation = r3Vector(0, i, 0); + collider.collisionGroups = (R3InteractionGroups){(uint32_t)i, (uint32_t)i, R3_GROUPS_AND}; + R3ColliderHandle colliderHandle = r3InsertCollider(floor, &collider); + tbColliderColor(testbed, colliderHandle, 0, i == 2, i == 1, 1); + } + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 8; i++) { + for (int k = 0; k < 8; k++) { + uint32_t group = k % 2 ? 2 : 1; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.1, 0.1, 0.1)); + collider.collisionGroups = (R3InteractionGroups){group, group, R3_GROUPS_AND}; + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * 0.2 - 0.8, j * 0.2 + 2.5, k * 0.2 - 0.8); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + tbBodyColor(testbed, handle, 0, group == 1, group == 2, 1); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/compound3.c b/c/testbed/examples3d/compound3.c new file mode 100644 index 000000000..a5005b62c --- /dev/null +++ b/c/testbed/examples3d/compound3.c @@ -0,0 +1,97 @@ +/* Port of examples3d/compound3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbCompound3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + /* Ground. */ + const R3Real groundSize = 50.0; + const R3Real groundHeight = 0.1; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0.0, -groundHeight, 0.0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(groundSize, groundHeight, groundSize)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + /* Create the cubes. */ + const int num = 8; + const int numy = 15; + const R3Real rad = 0.2; + const R3Real shift = rad * 4.0 + rad; + const R3Real centerx = shift * (num / 2); + const R3Real centery = shift / 2.0; + const R3Real centerz = shift * (num / 2); + R3Real offset = -2.4; + + for (int j = 0; j < numy; j++) { + for (int i = 0; i < num; i++) { + for (int k = 0; k < num; k++) { + const R3Real x = i * shift * 5.0 - centerx + offset; + const R3Real y = j * (shift * 5.0) + centery + 3.0; + const R3Real z = k * shift * 2.0 - centerz + offset; + rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x, y, z); + rigidBody.canSleep = !testbed->noSleep; + + /* First option: attach several colliders to a single rigid body. */ + if (j < numy / 2) { + R3ColliderDesc collider1 = r3CuboidColliderDesc(r3Vector(rad * 10.0, rad, rad)); + R3ColliderDesc collider2 = r3CuboidColliderDesc(r3Vector(rad, rad * 10.0, rad)); + collider2.position.translation = r3Vector(rad * 10.0, rad * 10.0, 0.0); + R3ColliderDesc collider3 = r3CuboidColliderDesc(r3Vector(rad, rad * 10.0, rad)); + collider3.position.translation = r3Vector(-rad * 10.0, rad * 10.0, 0.0); + R3RigidBodyHandle handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider1); + r3InsertCollider(handle, &collider2); + r3InsertCollider(handle, &collider3); + } else { + /* Second option: attach one collider with a compound shape. */ + const R3Pose poses[] = { + r3TranslationPose(r3Vector(0.0, 0.0, 0.0)), + r3TranslationPose(r3Vector(rad * 10.0, rad * 10.0, 0.0)), + r3TranslationPose(r3Vector(-rad * 10.0, rad * 10.0, 0.0)), + }; + R3SharedShape *shapes[3] = {NULL}; + shapes[0] = r3CuboidSharedShape(r3Vector(rad * 10.0, rad, rad)); + shapes[1] = r3CuboidSharedShape(r3Vector(rad, rad * 10.0, rad)); + shapes[2] = r3CuboidSharedShape(r3Vector(rad, rad * 10.0, rad)); + R3CompoundShapeDesc colliderParts[TB_COUNT(shapes)]; + for (size_t part = 0; part < TB_COUNT(shapes); ++part) { + colliderParts[part].pose = poses[part]; + colliderParts[part].shape = r3DefaultShapeDesc(); + colliderParts[part].shape.kind = R3_SHAPE_DESC_SHARED; + colliderParts[part].shape.sharedShape = + ((const R3SharedShape *const *)shapes)[part]; + } + collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_COMPOUND; + collider.shape.children = + (R3CompoundShapeView){colliderParts, TB_COUNT(shapes)}; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + for (size_t part = 0; part < TB_COUNT(shapes); part++) { + r3FreeSharedShape(shapes[part]); + } + } + } + } + offset -= 0.07; + } + + /* Set up the viewer. */ + tbCamera(testbed, 100.0, 100.0, 100.0, 0.0, 0.0, 0.0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/convex_decomposition3.c b/c/testbed/examples3d/convex_decomposition3.c new file mode 100644 index 000000000..fb862607e --- /dev/null +++ b/c/testbed/examples3d/convex_decomposition3.c @@ -0,0 +1,7 @@ +/* Port of examples3d/convex_decomposition3.rs. */ +#include "testbed.h" +void dynamicTrimesh3RunImpl(Testbed *testbed, int useConvexDecomposition); + +void tbConvexDecomposition3(Testbed *testbed) { + dynamicTrimesh3RunImpl(testbed, 1); +} diff --git a/c/testbed/examples3d/convex_polyhedron3.c b/c/testbed/examples3d/convex_polyhedron3.c new file mode 100644 index 000000000..92c3b62aa --- /dev/null +++ b/c/testbed/examples3d/convex_polyhedron3.c @@ -0,0 +1,58 @@ +/* Port of examples3d/convex_polyhedron3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "example_math.h" + +static R3SharedShape *randomHull(Testbed *testbed) { + R3Vector points[10]; + for (size_t i = 0; i < 10; i++) { + R3Real x = exampleRandom(&testbed->randomState) * 2; + R3Real y = exampleRandom(&testbed->randomState) * 2; + R3Real z = exampleRandom(&testbed->randomState) * 2; + points[i] = r3Vector(x, y, z); + } + R3SharedShape *shape = r3RoundConvexHullSharedShape((R3VectorView){points, 10}, 0.1); + return shape; +} + +void tbConvexPolyhedron3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(40, 0.1, 40)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + for (int j = 0; j < 25; j++) { + for (int i = 0; i < 5; i++) { + for (int k = 0; k < 5; k++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * 2.2 - 4.4, j * 2.2 + 4.1, k * 2.2 - 4.4); + rigidBody.canSleep = !testbed->noSleep; + R3SharedShape *shape = randomHull(testbed); + R3ColliderDesc collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + r3FreeSharedShape(shape); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 30, 30, 30, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/damping3.c b/c/testbed/examples3d/damping3.c new file mode 100644 index 000000000..60b326091 --- /dev/null +++ b/c/testbed/examples3d/damping3.c @@ -0,0 +1,39 @@ +/* Port of examples3d/damping3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDamping3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + r3SetGravity(world, r3Vector(0, 0, 0)); + const int num = 10; + const R3Real subdiv = (R3Real)1 / num; + for (int i = 0; i < num; i++) { + R3Real x = (R3Real)sin(i * subdiv * R3_PI * 2); + R3Real y = (R3Real)cos(i * subdiv * R3_PI * 2); + R3RigidBodyDesc body = r3DynamicRigidBodyDesc(); + body.position.translation = r3Vector(x, y, 0); + body.linvel = r3Vector(x * 10, y * 10, 0); + body.angvel = r3Vector(0, 0, 100); + body.linearDamping = (i + 1) * subdiv * 10; + body.angularDamping = (num - i) * subdiv * 10; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.2, 0.2, 0.2)); + body.canSleep = !testbed->noSleep; + R3RigidBodyHandle bodyHandle = r3InsertRigidBody(world, &body); + r3InsertCollider(bodyHandle, &collider); + } + /* Set up the viewer. */ + tbCamera(testbed, 2, 2.5, 20, 2, 2.5, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_add_remove_collider3.c b/c/testbed/examples3d/debug_add_remove_collider3.c new file mode 100644 index 000000000..dff284dff --- /dev/null +++ b/c/testbed/examples3d/debug_add_remove_collider3.c @@ -0,0 +1,41 @@ +/* Port of examples3d/debug_add_remove_collider3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugAddRemoveCollider3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle groundHandle = {0}; + R3ColliderHandle groundCollider = {0}; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + groundHandle = r3InsertRigidBody(world, &rigidBody); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(3, 0.1, 0.4)); + groundCollider = r3InsertCollider(groundHandle, &boxCollider); + R3ColliderDesc collider = r3BallColliderDesc(0.1); + collider.density = 100; + R3RigidBodyDesc dynamicBody = r3DynamicRigidBodyDesc(); + dynamicBody.position.translation = r3Vector(0, 0.2, 0); + dynamicBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle dynamicBodyHandle = r3InsertRigidBody(world, &dynamicBody); + r3InsertCollider(dynamicBodyHandle, &collider); + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + + r3RemoveCollider(groundCollider, 1); + groundCollider = r3InsertCollider(groundHandle, &boxCollider); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_angular_limits3.c b/c/testbed/examples3d/debug_angular_limits3.c new file mode 100644 index 000000000..ec2cf926b --- /dev/null +++ b/c/testbed/examples3d/debug_angular_limits3.c @@ -0,0 +1,60 @@ +/* Port of examples3d/debug_angular_limits3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugAngularLimits3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + r3SetGravity(world, r3Vector(0, 0, 0)); + int multi = (int)tbSetting(testbed, "Multibody joints", 0, 0, 1, 1); + const R3Real limits[][2] = {{-45, 45}, {0, 270}, {135, 225}, {-350, 0}, {-200, 200}}; + for (int i = 0; i < 5; i++) { + for (int dir = 1; dir >= -1; dir -= 2) { + R3Vector position = r3Vector(i * 4, dir > 0 ? 0 : -4, 0); + R3RigidBodyHandle anchor; + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = position; + R3ColliderDesc collider = r3BallColliderDesc(0.2); + groundBody.canSleep = !testbed->noSleep; + anchor = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(anchor, &collider); + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(position, r3Vector(1, 0, 0)); + rigidBody.angularDamping = 3; + rigidBody.canSleep = 0; + R3RigidBodyHandle handle; + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(0.5, 0.1, 0.1)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &boxCollider); + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + joint.localFrame1.translation = r3Vector(0, 0, 0); + joint.localFrame2.translation = r3Vector(-1, 0, 0); + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_X, limits[i][0] * R3_PI / 180, + limits[i][1] * R3_PI / 180); + r3JointDesc_SetMotor(&joint, R3_AXIS_ANG_X, 0, dir * 5, 0, 20); + if (multi) { + r3InsertMultibodyJoint(anchor, handle, &joint); + } else { + r3InsertImpulseJoint(anchor, handle, &joint); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 8, -2, 25, 8, -2, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_articulations3.c b/c/testbed/examples3d/debug_articulations3.c new file mode 100644 index 000000000..e8e51dbd2 --- /dev/null +++ b/c/testbed/examples3d/debug_articulations3.c @@ -0,0 +1,70 @@ +/* Port of examples3d/debug_articulations3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugArticulations3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3Rotation rotation = r3RotationFromAxisAngle(r3Vector(0.1, 0, 0.1), (R3Real)sqrt(0.02)); + for (int n = 0; n < 2; n++) { + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(30, 0.01, 30)); + collider.position = r3Pose(r3Vector(0, n ? -3 : -3.02, 0), rotation); + if (n) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0, 0); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } else { + r3InsertColliderWithoutParent(world, &collider); + } + } + R3RigidBodyHandle handles[225]; + for (int k = 0; k < 15; k++) { + for (int i = 0; i < 15; i++) { + R3SharedShape *shape = + r3CapsuleSharedShape(r3Vector(0, 0, -0.5), r3Vector(0, 0, 0.5), 0.4); + R3RigidBodyHandle child; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = i ? R3_DYNAMIC : R3_FIXED; + rigidBody.position.translation = r3Vector(k, 0, i * 2); + R3ColliderDesc collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + rigidBody.canSleep = !testbed->noSleep; + child = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(child, &collider); + + if (i) { + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = R3_JOINT_SPHERICAL_AXES; + joint.localFrame1.translation = r3Vector(0, 0, 0); + joint.localFrame2.translation = r3Vector(0, 0, -2); + r3InsertMultibodyJoint(handles[k * 15 + i - 1], child, &joint); + } + if (k && i) { + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = R3_JOINT_SPHERICAL_AXES; + joint.localFrame1.translation = r3Vector(0, 0, 0); + joint.localFrame2.translation = r3Vector(-1, 0, 0); + r3InsertImpulseJoint(handles[(k - 1) * 15 + i], child, &joint); + } + handles[k * 15 + i] = child; + r3FreeSharedShape(shape); + } + } + /* Set up the viewer. */ + tbCamera(testbed, 15, 5, 42, 13, 1, 1); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_balls3.c b/c/testbed/examples3d/debug_balls3.c new file mode 100644 index 000000000..22513a21f --- /dev/null +++ b/c/testbed/examples3d/debug_balls3.c @@ -0,0 +1,40 @@ +/* Port of examples3d/debug_balls3.rs. */ +#include "testbed.h" +#include "rapier_math.h" + +void tbDebugBalls3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + for (int k = 0; k < 10; k++) { + int fixed = j == 0 || i == 0 || k == 0 || i == 9 || k == 9; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = fixed ? R3_FIXED : R3_DYNAMIC; + rigidBody.position.translation = r3Vector(i - 5, j * (fixed ? 1 : 2) + 0.5, k - 5); + rigidBody.canSleep = 0; + R3ColliderDesc collider = r3BallColliderDesc(0.5); + collider.friction = 0; + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_big_colliders3.c b/c/testbed/examples3d/debug_big_colliders3.c new file mode 100644 index 000000000..135ead67d --- /dev/null +++ b/c/testbed/examples3d/debug_big_colliders3.c @@ -0,0 +1,47 @@ +/* Port of examples3d/debug_big_colliders3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugBigColliders3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3SharedShape *shape = r3HalfspaceSharedShape(r3Vector(0, 1, 0)); + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0, 0); + R3ColliderDesc collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + R3Real y = 0; + R3Real width = 10000; + for (int i = 0; i < 12; i++) { + R3Real height = (R3Real)fmin(0.1, width); + y += height * 4; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, y, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(width, height, width)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + width /= 5; + } + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + r3FreeSharedShape(shape); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_boxes3.c b/c/testbed/examples3d/debug_boxes3.c new file mode 100644 index 000000000..d47f111bc --- /dev/null +++ b/c/testbed/examples3d/debug_boxes3.c @@ -0,0 +1,42 @@ +/* Port of examples3d/debug_boxes3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugBoxes3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + for (int i = 0; i < 6; i++) { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(100.1, 0.1, 100.1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + for (int i = 0; i < 2; i++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(1.1, 0, 0); + rigidBody.canSleep = 0; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(2, 0.1, 1)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_chain_high_mass_ratio3.c b/c/testbed/examples3d/debug_chain_high_mass_ratio3.c new file mode 100644 index 000000000..66b0d4fcd --- /dev/null +++ b/c/testbed/examples3d/debug_chain_high_mass_ratio3.c @@ -0,0 +1,46 @@ +/* Port of examples3d/debug_chain_high_mass_ratio3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugChainHighMassRatio3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle last = {0}; + for (int i = 0; i < 17; i++) { + R3Real r = i == 16 ? 2 : 0.2; + R3Real a = (R3Real)0.2 * (R3Real)1.1; + R3Real b = r + (R3Real)0.2 * (R3Real)0.1; + R3Real z = i ? (i - 1) * 2 * a + a + b : 0; + R3RigidBodyDesc body = r3DynamicRigidBodyDesc(); + body.bodyType = i ? R3_DYNAMIC : R3_FIXED; + body.position.translation = r3Vector(0, 0, z); + body.additionalSolverIterations = 16; + R3RigidBodyHandle handle; + R3ColliderDesc collider = r3BallColliderDesc(r); + body.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &body); + r3InsertCollider(handle, &collider); + if (i) { + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = R3_JOINT_SPHERICAL_AXES; + joint.localFrame1.translation = r3Vector(0, 0, i == 1 ? 0 : a); + joint.localFrame2.translation = r3Vector(0, 0, i == 1 ? -a * 2 : -b); + r3InsertImpulseJoint(last, handle, &joint); + } + last = handle; + } + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_cube_high_mass_ratio3.c b/c/testbed/examples3d/debug_cube_high_mass_ratio3.c new file mode 100644 index 000000000..822849c38 --- /dev/null +++ b/c/testbed/examples3d/debug_cube_high_mass_ratio3.c @@ -0,0 +1,51 @@ +/* Port of examples3d/debug_cube_high_mass_ratio3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugCubeHighMassRatio3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, -2.2, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(2, 2, 2)); + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &collider); + for (int i = 0; i < 4; i++) { + for (int side = -1; side <= 1; side += 2) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, i * 0.8, side * 0.8); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 0.2, 0.2)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + R3RigidBodyDesc dynamicBody = r3DynamicRigidBodyDesc(); + dynamicBody.position.translation = r3Vector(side * 0.8, (i + 0.5) * 0.8, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(0.2, 0.2, 1)); + dynamicBody.canSleep = !testbed->noSleep; + rigidBodyHandle = r3InsertRigidBody(world, &dynamicBody); + r3InsertCollider(rigidBodyHandle, &boxCollider); + } + } + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 2 + (4 - 0.25) * 0.2 * 4, 0); + rigidBody.additionalSolverIterations = 36; + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(2, 2, 2)); + rigidBody.canSleep = !testbed->noSleep; + groundBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(groundBodyHandle, &boxCollider); + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_cylinder3.c b/c/testbed/examples3d/debug_cylinder3.c new file mode 100644 index 000000000..d64f3a11f --- /dev/null +++ b/c/testbed/examples3d/debug_cylinder3.c @@ -0,0 +1,36 @@ +/* Port of examples3d/debug_cylinder3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugCylinder3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(100.1, 0.1, 100.1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + R3RigidBodyDesc dynamicBody = r3DynamicRigidBodyDesc(); + dynamicBody.position.translation = r3Vector(-1.5, 4.5, -1.5); + R3ColliderDesc objectCollider = r3CylinderColliderDesc(1, 1); + dynamicBody.canSleep = !testbed->noSleep; + rigidBodyHandle = r3InsertRigidBody(world, &dynamicBody); + r3InsertCollider(rigidBodyHandle, &objectCollider); + + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_deserialize3.c b/c/testbed/examples3d/debug_deserialize3.c new file mode 100644 index 000000000..4a744c4e1 --- /dev/null +++ b/c/testbed/examples3d/debug_deserialize3.c @@ -0,0 +1,50 @@ +/* Port of examples3d/debug_deserialize3.rs. */ +#include "testbed.h" +#include + +void tbDebugDeserialize3(Testbed *testbed) { + const unsigned frameId = (unsigned)tbSetting(testbed, "frame", 0, 0, 1400, 1); + const char *frameDirs = getenv("RAPIER_SNAPSHOT_DIR"); + if (!frameDirs) { + frameDirs = "/Users/sebcrozet/work/hytopia/sdk/examples/bug-demo"; + } + char path[4096]; + snprintf(path, sizeof(path), "%s/snapshot%u.bincode", frameDirs, frameId); + FILE *file = fopen(path, "rb"); + if (!file) { + snprintf(testbed->error, sizeof(testbed->error), + "Cannot open snapshot%u.bincode: %s. Set RAPIER_SNAPSHOT_DIR to the snapshot " + "directory.", + frameId, strerror(errno)); + return; + } + if (fseek(file, 0, SEEK_END)) { + fclose(file); + return; + } + long length = ftell(file); + if (length < 0 || length > 256L * 1024 * 1024 || fseek(file, 0, SEEK_SET)) { + fclose(file); + return; + } + unsigned char *bytes = malloc(length ? (size_t)length : 1); + if (!bytes) { + abort(); + } + size_t count = fread(bytes, 1, (size_t)length, file); + fclose(file); + if (count != (size_t)length) { + free(bytes); + return; + } + R3World *world = r3DeserializeRigidState(bytes, count); + free(bytes); + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + tbSetWorld(testbed, world); + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_disabled3.c b/c/testbed/examples3d/debug_disabled3.c new file mode 100644 index 000000000..1b5e29748 --- /dev/null +++ b/c/testbed/examples3d/debug_disabled3.c @@ -0,0 +1,54 @@ +/* Port of examples3d/debug_disabled3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugDisabled3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3ColliderHandle platformCollider = {0}; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -2.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(10.1, 2.1, 10.1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + R3RigidBodyHandle handle; + R3RigidBodyDesc dynamicBody = r3DynamicRigidBodyDesc(); + dynamicBody.position.translation = r3Vector(0, 5, 0); + dynamicBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &dynamicBody); + + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(5, 1, 5)); + platformCollider = r3InsertCollider(handle, &boxCollider); + + /* Set up the viewer. */ + tbCamera(testbed, 30, 4, 30, 0, 1, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + + uint64_t step = testbed->step + 1; + if (step % 250 == 0) { + R3Bool enabled = r3Collider_IsEnabled(platformCollider); + r3Collider_SetEnabled(platformCollider, !enabled); + } + if (step % 25 == 0) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 20, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_dynamic_collider_add3.c b/c/testbed/examples3d/debug_dynamic_collider_add3.c new file mode 100644 index 000000000..1cbf2791c --- /dev/null +++ b/c/testbed/examples3d/debug_dynamic_collider_add3.c @@ -0,0 +1,72 @@ +/* Port of examples3d/debug_dynamic_collider_add3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugDynamicColliderAdd3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle ballHandle = {0}; + R3RigidBodyHandle groundHandle = {0}; + R3Vector savedLinvel = {0}; + R3AngVector savedAngvel = {0}; + R3Pose savedPosition = {0}; + unsigned stepId = {0}; + R3ColliderHandle addedColliders[200] = {0}; + size_t numAddedColliders = {0}; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(20, 0.1, 0.4)); + collider.friction = 0.15; + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, -0.1, 0); + groundBody.canSleep = !testbed->noSleep; + groundHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundHandle, &collider); + + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0.2, 0); + rigidBody.linvel = r3Vector(10, 0, 0); + + collider = r3BallColliderDesc(0.1); + collider.density = 100; + rigidBody.canSleep = !testbed->noSleep; + ballHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(ballHandle, &collider); + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + + stepId++; + R3ColliderDesc collider = r3BallColliderDesc(0.1 + 0.01 * stepId); + collider.density = 100; + addedColliders[numAddedColliders++] = r3InsertCollider(ballHandle, &collider); + + if (stepId == 51) { + savedLinvel = r3RigidBody_Linvel(ballHandle); + savedAngvel = r3RigidBody_Angvel(ballHandle); + savedPosition = r3RigidBody_Position(ballHandle); + } + if (stepId == 100) { + r3RigidBody_SetLinvel(ballHandle, savedLinvel, 1); + r3RigidBody_SetAngvel(ballHandle, savedAngvel, 1); + r3RigidBody_SetPosition(ballHandle, savedPosition, 1); + stepId = 51; + for (size_t i = 0; i < numAddedColliders; i++) { + r3RemoveCollider(addedColliders[i], 1); + } + numAddedColliders = 0; + } + R3ColliderDesc floor = r3CuboidColliderDesc(r3Vector(20, 0.1 + stepId * 0.01, 0.4)); + floor.friction = 0.15; + addedColliders[numAddedColliders++] = r3InsertCollider(groundHandle, &floor); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_friction3.c b/c/testbed/examples3d/debug_friction3.c new file mode 100644 index 000000000..704d5a0dc --- /dev/null +++ b/c/testbed/examples3d/debug_friction3.c @@ -0,0 +1,39 @@ +/* Port of examples3d/debug_friction3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugFriction3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3ColliderDesc floor = r3CuboidColliderDesc(r3Vector(100, 0.1, 100)); + floor.friction = 1.5; + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, 0, 0); + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &floor); + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 1.1, 0); + rigidBody.position = + r3Pose(r3Vector(0, 1.1, 0), r3RotationFromAxisAngle(r3Vector(0, 1, 0), 0.3)); + rigidBody.linvel = r3Vector(sin(0.3) * 50, 0, cos(0.3) * 50); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(2, 1, 3)); + collider.friction = 1.5; + rigidBody.canSleep = !testbed->noSleep; + groundBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(groundBodyHandle, &collider); + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_infinite_fall3.c b/c/testbed/examples3d/debug_infinite_fall3.c new file mode 100644 index 000000000..4e3dcadc4 --- /dev/null +++ b/c/testbed/examples3d/debug_infinite_fall3.c @@ -0,0 +1,40 @@ +/* Port of examples3d/debug_infinite_fall3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugInfiniteFall3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, 4, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(100.1, 2.1, 100.1)); + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &collider); + for (int i = 0; i < 2; i++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, i ? 2 : 7, 0); + rigidBody.canSleep = 0; + R3ColliderDesc collider = r3BallColliderDesc(1); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Set up the viewer. */ + tbCamera(testbed, 100, -10, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_internal_edges3.c b/c/testbed/examples3d/debug_internal_edges3.c new file mode 100644 index 000000000..c26a9ce43 --- /dev/null +++ b/c/testbed/examples3d/debug_internal_edges3.c @@ -0,0 +1,61 @@ +/* Port of examples3d/debug_internal_edges3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugInternalEdges3(Testbed *testbed) { + R3World *world = r3NewWorld(); + R3Real *heights = calloc(100 * 100, sizeof(*heights)); + if (!heights) { + abort(); + } + R3ColliderDesc heightfield = r3DefaultColliderDesc(); + heightfield.shape.kind = R3_SHAPE_DESC_HEIGHTFIELD; + heightfield.shape.heights = (R3RealView){heights, (100) * (100)}; + heightfield.shape.rows = 100; + heightfield.shape.columns = 100; + heightfield.shape.scale = r3Vector(60, 1, 60); + heightfield.shape.flags = R3_HEIGHTFIELD_FIX_INTERNAL_EDGES; + r3InsertColliderWithoutParent(world, &heightfield); + + free(heights); + /* Dynamic rigid bodies. */ + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(4, 0.5, 0); + rigidBody.linvel = r3Vector(0, -40, 20); + rigidBody.canSleep = 0; + R3ColliderDesc collider = r3BallColliderDesc(.5); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-3, 5, 0); + rigidBody.linvel = r3Vector(0, -4, 20); + rigidBody.canSleep = 0; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 0.5)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(8, 0.2, 0); + rigidBody.linvel = r3Vector(0, -4, 20); + rigidBody.canSleep = 0; + R3ColliderDesc collider = r3CylinderColliderDesc(.5, .2); + collider.position.rotation = r3RotationFromAxisAngle(r3Vector(0, 0, 1), R3_PI / 2); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_long_chain3.c b/c/testbed/examples3d/debug_long_chain3.c new file mode 100644 index 000000000..ad51e9327 --- /dev/null +++ b/c/testbed/examples3d/debug_long_chain3.c @@ -0,0 +1,42 @@ +/* Port of examples3d/debug_long_chain3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugLongChain3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle last = {0}; + const R3Real shift = (R3Real)0.2 * (R3Real)2.2; + for (int i = 0; i < 85; i++) { + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = i ? R3_DYNAMIC : R3_FIXED; + rigidBody.position.translation = r3Vector(0, 0, i * shift); + R3ColliderDesc collider = r3BallColliderDesc(0.2); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + if (i) { + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = R3_JOINT_SPHERICAL_AXES; + joint.localFrame1.translation = r3Vector(0, 0, i == 1 ? 0 : shift / 2); + joint.localFrame2.translation = r3Vector(0, 0, i == 1 ? -shift : -shift / 2); + r3InsertMultibodyJoint(last, handle, &joint); + } + last = handle; + } + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_multi_collider_body3.c b/c/testbed/examples3d/debug_multi_collider_body3.c new file mode 100644 index 000000000..c1c869d14 --- /dev/null +++ b/c/testbed/examples3d/debug_multi_collider_body3.c @@ -0,0 +1,41 @@ +/* Port of examples3d/debug_multi_collider_body3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugMultiColliderBody3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + r3SetGravity(world, r3Vector(0, -9.81, 0)); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(100, 0.5, 100)); + r3InsertColliderWithoutParent(world, &boxCollider); + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 40, 0); + rigidBody.position = + r3Pose(r3Vector(0, 40, 0), r3RotationFromAxisAngle(r3Vector(1, 2, 3), (R3Real)sqrt(14))); + R3RigidBodyHandle handle; + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + for (int i = 0; i < 20; i++) { + for (int j = 0; j < 20; j++) { + for (int k = 0; k < 20; k++) { + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 0.5)); + collider.position.translation = r3Vector(i - 10, j - 10, k - 10); + r3InsertCollider(handle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 40, 30, 40, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_multibody_ang_motor_pos3.c b/c/testbed/examples3d/debug_multibody_ang_motor_pos3.c new file mode 100644 index 000000000..f35f28dfd --- /dev/null +++ b/c/testbed/examples3d/debug_multibody_ang_motor_pos3.c @@ -0,0 +1,44 @@ +/* Port of examples3d/debug_multibody_ang_motor_pos3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugMultibodyAngMotorPos3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + R3RigidBodyHandle handleB; + R3RigidBodyDesc dynamicBody = r3DynamicRigidBodyDesc(); + dynamicBody.position.translation = r3Vector(0, 1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + dynamicBody.canSleep = !testbed->noSleep; + handleB = r3InsertRigidBody(world, &dynamicBody); + r3InsertCollider(handleB, &boxCollider); + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = R3_JOINT_SPHERICAL_AXES; + joint.localFrame1.translation = r3Vector(0, 4, 0); + joint.localFrame2.translation = r3Vector(0, 0, 0); + for (uint32_t axis = R3_AXIS_ANG_X; axis <= R3_AXIS_ANG_Z; axis++) { + r3JointDesc_SetMotor(&joint, axis, axis == R3_AXIS_ANG_X ? 1 : 0, 0, 1000, 200); + } + r3InsertMultibodyJoint(handle, handleB, &joint); + /* Set up the viewer. */ + tbCamera(testbed, 20, 0, 0, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_pop3.c b/c/testbed/examples3d/debug_pop3.c new file mode 100644 index 000000000..50c9a2ed5 --- /dev/null +++ b/c/testbed/examples3d/debug_pop3.c @@ -0,0 +1,38 @@ +/* Port of examples3d/debug_pop3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugPop3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, -10, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(10, 10, 10)); + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &collider); + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0, 0); + rigidBody.canSleep = 0; + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + groundBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(groundBodyHandle, &boxCollider); + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_prismatic3.c b/c/testbed/examples3d/debug_prismatic3.c new file mode 100644 index 000000000..55d2f4bcb --- /dev/null +++ b/c/testbed/examples3d/debug_prismatic3.c @@ -0,0 +1,57 @@ +/* Port of examples3d/debug_prismatic3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugPrismatic3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(50, 0.1, 50)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + R3RigidBodyHandle box; + R3RigidBodyDesc dynamicBody = r3DynamicRigidBodyDesc(); + dynamicBody.position.translation = r3Vector(0, 5, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(1, 0.25, 1)); + dynamicBody.canSleep = !testbed->noSleep; + box = r3InsertRigidBody(world, &dynamicBody); + r3InsertCollider(box, &boxCollider); + R3Vector offsets[] = {r3Vector(1, -1, -1), r3Vector(-1, -1, -1), r3Vector(1, -1, 1), + r3Vector(-1, -1, 1)}; + for (int i = 0; i < 4; i++) { + R3RigidBodyHandle wheel; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(r3Vector(0, 5, 0), offsets[i]); + R3ColliderDesc collider = r3BallColliderDesc(0.5); + rigidBody.canSleep = !testbed->noSleep; + wheel = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(wheel, &collider); + R3JointDesc joint = r3PrismaticJointDesc(r3Vector(0, 1, 0)); + joint.localFrame1.translation = offsets[i]; + joint.localFrame2.translation = r3Vector(0, 0, 0); + r3JointDesc_SetMotor(&joint, R3_AXIS_LIN_X, 0, 0, 0.05, 0.2); + r3InsertImpulseJoint(box, wheel, &joint); + } + R3RigidBodyDesc payloadBody = r3DynamicRigidBodyDesc(); + payloadBody.position.translation = r3Vector(1, 2.6, -1); + R3ColliderDesc payloadCollider = r3CuboidColliderDesc(r3Vector(0.5, 0.1, 0.5)); + payloadBody.canSleep = !testbed->noSleep; + rigidBodyHandle = r3InsertRigidBody(world, &payloadBody); + r3InsertCollider(rigidBodyHandle, &payloadCollider); + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_rollback3.c b/c/testbed/examples3d/debug_rollback3.c new file mode 100644 index 000000000..271240cf3 --- /dev/null +++ b/c/testbed/examples3d/debug_rollback3.c @@ -0,0 +1,59 @@ +/* Port of examples3d/debug_rollback3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugRollback3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle ballHandle = {0}; + R3Vector savedLinvel = {0}; + R3AngVector savedAngvel = {0}; + R3Pose savedPosition = {0}; + unsigned stepId = {0}; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(20, 0.1, 0.4)); + collider.friction = 0.15; + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, -0.1, 0); + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &collider); + + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0.2, 0); + rigidBody.linvel = r3Vector(10, 0, 0); + + collider = r3BallColliderDesc(0.1); + collider.density = 100; + rigidBody.canSleep = !testbed->noSleep; + ballHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(ballHandle, &collider); + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + + stepId++; + + if (stepId == 51) { + savedLinvel = r3RigidBody_Linvel(ballHandle); + savedAngvel = r3RigidBody_Angvel(ballHandle); + savedPosition = r3RigidBody_Position(ballHandle); + } + if (stepId == 100) { + r3RigidBody_SetLinvel(ballHandle, savedLinvel, 1); + r3RigidBody_SetAngvel(ballHandle, savedAngvel, 1); + r3RigidBody_SetPosition(ballHandle, savedPosition, 1); + stepId = 51; + } + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_self_intersect3.c b/c/testbed/examples3d/debug_self_intersect3.c new file mode 100644 index 000000000..66cd94619 --- /dev/null +++ b/c/testbed/examples3d/debug_self_intersect3.c @@ -0,0 +1,247 @@ +/* Port of examples3d/debug_self_intersect3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +typedef struct Shot { + R3SoftBodyHandle handle; + R3Vector *rest; + size_t count; + R3Real speed; + int onlyParticle; +} Shot; + +static void reset(const Shot *shot) { + for (size_t i = 0; i < shot->count; ++i) { + r3SoftBody_SetParticlePosition(shot->handle, i, shot->rest[i]); + const R3Real speed = + shot->onlyParticle < 0 || i == (size_t)shot->onlyParticle ? shot->speed : 0; + r3SoftBody_SetParticleVelocity(shot->handle, i, r3Vector(0, speed, 0)); + } +} + +static void refire(const Shot shots[3]) { + for (size_t i = 0; i < 3; ++i) { + reset(&shots[i]); + } +} + +void tbDebugSelfIntersect3(Testbed *testbed) { + R3World *world = r3NewWorld(); + + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -1.5, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(8, 0.5, 4)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Slab pinned at both ends, with its top-middle vertex below the bottom face. */ + R3SoftBodyDesc builder = + r3CuboidSoftBodyDesc(r3Vector(-3, 0.15, 0), r3Vector(1.5, 0.15, 0.15), 3, 2, 2); + builder.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){5, 1}); + builder.selfContacts = 1; + builder.canSleep = !testbed->noSleep; + uint32_t *builderPins = NULL; + { + size_t count = r3SoftBodyDesc_ParticlePositions(&builder, NULL, 0); + R3Vector *positions = malloc(count * sizeof(*positions)); + builderPins = malloc(count * sizeof(*builderPins)); + if (!positions || !builderPins) { + abort(); + } + count = r3SoftBodyDesc_ParticlePositions(&builder, positions, count); + size_t builderPinsCount = 0; + for (size_t i = 0; i < count; ++i) { + if (fabs(positions[i].x + 3) > 1.4) { + builderPins[builderPinsCount++] = (uint32_t)i; + } + } + r3SoftBodyDesc_SetPinnedParticles( + &builder, (R3IndexView){(const uint32_t *)builderPins, builderPinsCount}); + + free(positions); + } + R3Vector slabPositions[12]; + size_t slabCount = r3SoftBodyDesc_ParticlePositions(&builder, slabPositions, 12); + size_t captured = 0; + R3Real closest = INFINITY; + for (size_t i = 0; i < slabCount; ++i) { + R3Real distance = r3VectorLength(r3VectorSub(slabPositions[i], r3Vector(-3, .3, .15))); + if (distance < closest) { + captured = i; + closest = distance; + } + } + R3SoftBodyHandle slab = r3InsertSoftBody(world, &builder); + free(builderPins); + + r3SoftBody_SetParticlePosition(slab, captured, r3Vector(-2.7, -.4, 0)); + const size_t nu = 8, nv = 3; + uint32_t endPins[] = {0, 1, 2, 21, 22, 23}; + Shot shots[3] = {0}; + R3SoftBodyHandle clothHandle; + const R3Vector clothOrigin = r3Vector(1.5, 0, -0.25); + R3SoftBodyDesc cloth = + r3ClothSoftBodyDesc(clothOrigin, r3Vector(0.25, 0, 0), r3Vector(0, 0, 0.25), nu, nv); + cloth.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){40, 1}); + cloth.particleMass = .02; + cloth.particleRadius = (R3OptionalReal){1, .05}; + r3SoftBodyDesc_SetPinnedParticles(&cloth, + (R3IndexView){(const uint32_t *)endPins, TB_COUNT(endPins)}); + cloth.selfContacts = 1; + cloth.canSleep = !testbed->noSleep; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){40, 1.0}; + material.bendSoftness = (R3SpringCoefficients){40, 1.0}; + material.volumeSoftness = (R3SpringCoefficients){40, 1.0}; + material.shapeMatchingSoftness = (R3SpringCoefficients){40, 1.0}; + cloth.material = material; + } + clothHandle = r3InsertSoftBody(world, &cloth); + + for (size_t i = 4; i < nu; ++i) { + for (size_t j = 0; j < nv; ++j) { + r3SoftBody_SetParticlePosition( + clothHandle, i * nv + j, + r3VectorAdd(clothOrigin, r3Vector(.25 * (7 - i), 0.15, .25 * j))); + } + } + r3SoftBody_SetParticlePosition(clothHandle, 5 * nv + 1, + r3VectorAdd(clothOrigin, r3Vector(.5, -.15, .25))); + R3SoftBodyHandle flickHandle; + const R3Vector flickOrigin = r3Vector(-6.5, 0, -0.25); + R3SoftBodyDesc flick = + r3ClothSoftBodyDesc(flickOrigin, r3Vector(0.25, 0, 0), r3Vector(0, 0, 0.25), nu, nv); + flick.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){40, 1}); + flick.particleMass = .02; + flick.particleRadius = (R3OptionalReal){1, .05}; + r3SoftBodyDesc_SetPinnedParticles(&flick, + (R3IndexView){(const uint32_t *)endPins, TB_COUNT(endPins)}); + flick.selfContacts = 1; + flick.canSleep = !testbed->noSleep; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){40, 1.0}; + material.bendSoftness = (R3SpringCoefficients){40, 1.0}; + material.volumeSoftness = (R3SpringCoefficients){40, 1.0}; + material.shapeMatchingSoftness = (R3SpringCoefficients){40, 1.0}; + flick.material = material; + } + flickHandle = r3InsertSoftBody(world, &flick); + + for (size_t i = 4; i < nu; ++i) { + for (size_t j = 0; j < nv; ++j) { + r3SoftBody_SetParticlePosition( + flickHandle, i * nv + j, + r3VectorAdd(flickOrigin, r3Vector(.25 * (7 - i), 0.2, .25 * j))); + } + } + shots[0].handle = flickHandle; + shots[0].speed = -40; + shots[0].onlyParticle = 16; + const size_t n = 12; + const R3Real extent = .12 * (n - 1) / 2; + uint32_t edgePins[144]; + size_t edgeCount = 0; + for (uint32_t k = 0; k < n * n; ++k) { + size_t i = k / n, j = k % n; + if (i == 0 || j == 0 || i == n - 1 || j == n - 1) { + edgePins[edgeCount++] = k; + } + } + R3SoftBodyDesc net = r3ClothSoftBodyDesc(r3Vector(5 - extent, 1.2, -extent), + r3Vector(0.12, 0, 0), r3Vector(0, 0, 0.12), n, n); + net.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){40, 1}); + net.particleMass = .02; + net.particleRadius = (R3OptionalReal){1, .05}; + r3SoftBodyDesc_SetPinnedParticles(&net, (R3IndexView){(const uint32_t *)edgePins, edgeCount}); + net.selfContacts = 1; + net.canSleep = !testbed->noSleep; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){40, 1.0}; + material.bendSoftness = (R3SpringCoefficients){40, 1.0}; + material.volumeSoftness = (R3SpringCoefficients){40, 1.0}; + material.shapeMatchingSoftness = (R3SpringCoefficients){40, 1.0}; + net.material = material; + } + r3InsertSoftBody(world, &net); + + R3SoftBodyDesc bullet = r3SphereSoftBodyDesc(r3Vector(5, 2.6, 0), .22, 1); + bullet.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){20, 1}); + bullet.particleMass = .05; + bullet.canSleep = !testbed->noSleep; + shots[1].handle = r3InsertSoftBody(world, &bullet); + + shots[1].speed = -60; + shots[1].onlyParticle = -1; + R3SoftBodyDesc bottomStrip = r3ClothSoftBodyDesc(r3Vector(7.4, 0.3, -0.1), r3Vector(0.2, 0, 0), + r3Vector(0, 0, 0.2), 12, 2); + bottomStrip.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){40, 1}); + bottomStrip.particleMass = .02; + bottomStrip.particleRadius = (R3OptionalReal){1, .05}; + bottomStrip.selfContacts = 1; + bottomStrip.canSleep = !testbed->noSleep; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){40, 1.0}; + material.bendSoftness = (R3SpringCoefficients){40, 1.0}; + material.volumeSoftness = (R3SpringCoefficients){40, 1.0}; + material.shapeMatchingSoftness = (R3SpringCoefficients){40, 1.0}; + bottomStrip.material = material; + } + const uint32_t stripPins[] = {0, 1, 22, 23}; + r3SoftBodyDesc_SetPinnedParticles(&bottomStrip, (R3IndexView){(const uint32_t *)stripPins, 4}); + r3InsertSoftBody(world, &bottomStrip); + + R3SoftBodyDesc topStrip = r3ClothSoftBodyDesc(r3Vector(8.4, 1.3, -1.1), r3Vector(0, 0, 0.2), + r3Vector(0.2, 0, 0), 12, 2); + topStrip.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){40, 1}); + topStrip.particleMass = .02; + topStrip.particleRadius = (R3OptionalReal){1, .05}; + topStrip.selfContacts = 1; + topStrip.canSleep = !testbed->noSleep; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){40, 1.0}; + material.bendSoftness = (R3SpringCoefficients){40, 1.0}; + material.volumeSoftness = (R3SpringCoefficients){40, 1.0}; + material.shapeMatchingSoftness = (R3SpringCoefficients){40, 1.0}; + topStrip.material = material; + } + shots[2].handle = r3InsertSoftBody(world, &topStrip); + + shots[2].speed = -30; + shots[2].onlyParticle = -1; + for (size_t i = 0; i < TB_COUNT(shots); ++i) { + shots[i].count = r3SoftBody_NumParticles(shots[i].handle); + shots[i].rest = malloc(shots[i].count * sizeof(*shots[i].rest)); + if (!shots[i].rest) { + abort(); + } + shots[i].count = + r3SoftBody_ParticlePositions(shots[i].handle, shots[i].rest, shots[i].count); + } + refire(shots); + tbCamera(testbed, 1, 3.5, 11, 1, .5, 0); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + R3Real t = 0; + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + R3Real dt = r3TimeStep(world); + t += dt; + if (fmod(t, 4) < dt && t > 0) { + refire(shots); + } + r3Step(world, NULL, NULL); + } + } + for (size_t i = 0; i < TB_COUNT(shots); ++i) { + free(shots[i].rest); + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_shape_modification3.c b/c/testbed/examples3d/debug_shape_modification3.c new file mode 100644 index 000000000..4750a4450 --- /dev/null +++ b/c/testbed/examples3d/debug_shape_modification3.c @@ -0,0 +1,116 @@ +/* Port of examples3d/debug_shape_modification3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugShapeModification3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(20, 0.1, 20)); + collider.friction = .15; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + R3RigidBodyHandle ballHandle; + R3ColliderHandle ballCollHandle; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0.2, 0); + rigidBody.linvel = r3Vector(10, 0, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3BallColliderDesc(.1); + collider.density = 100; + ballHandle = r3InsertRigidBody(world, &rigidBody); + ballCollHandle = r3InsertCollider(ballHandle, &collider); + } + { + R3ColliderDesc staticCollider = r3BallColliderDesc(3); + staticCollider.position.translation = r3Vector(-15, 3, 18); + r3InsertColliderWithoutParent(world, &staticCollider); + } + R3SharedShape *shapes[4] = {0}; + shapes[0] = r3BallSharedShape(3); + shapes[1] = r3CuboidSharedShape(r3Vector(3, 3, 3)); + shapes[2] = r3ConeSharedShape(3, 3); + shapes[3] = r3CylinderSharedShape(3, 3); + R3ColliderDesc shapeshiftingCollider = r3DefaultColliderDesc(); + shapeshiftingCollider.shape.kind = R3_SHAPE_DESC_SHARED; + shapeshiftingCollider.shape.sharedShape = shapes[0]; + shapeshiftingCollider.position.translation = r3Vector(-15, 3, 9); + R3ColliderHandle shapeshiftingCollHandle = + r3InsertColliderWithoutParent(world, &shapeshiftingCollider); + + R3Real offset = -12; + for (int j = 0; j < 20; ++j) { + for (int i = 0; i < 8; ++i) { + for (int k = 0; k < 8; ++k) { + R3ColliderDesc collider; + switch (j % 5) { + case 0: + collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + break; + case 1: + collider = r3BallColliderDesc(1); + break; + case 2: + collider = r3RoundCylinderColliderDesc(1, 1, .1); + break; + case 3: + collider = r3ConeColliderDesc(1, 1); + break; + default: + collider = r3CapsuleYColliderDesc(1, 1); + break; + } + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * 3 - 12 + offset + 5, j * 3 + 4.5, k * 3 - 12 + offset); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + offset -= .35; + } + tbCamera(testbed, 40, 40, 40, 0, 0, 0); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + size_t shapeIdx = 0, step = 0; + const size_t snappedFrame = 51; + R3Vector linvel = {0}, angvel = {0}; + R3Pose pos = r3TranslationPose(r3Vector(0, 0, 0)); + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + ++step; + + if (step == snappedFrame) { + linvel = r3RigidBody_Linvel(ballHandle); + angvel = r3RigidBody_Angvel(ballHandle); + pos = r3RigidBody_Position(ballHandle); + } + + if (step % 50 == 0) { + shapeIdx = (shapeIdx + 1) % 4; + r3Collider_SetShape(shapeshiftingCollHandle, shapes[shapeIdx]); + } + if (step == 100) { + r3RigidBody_SetLinvel(ballHandle, linvel, 1); + r3RigidBody_SetAngvel(ballHandle, angvel, 1); + r3RigidBody_SetPosition(ballHandle, pos, 1); + step = snappedFrame; + } + + R3SharedShape *shape = r3BallSharedShape(.1 * step * 2); + r3Collider_SetShape(ballCollHandle, shape); + r3FreeSharedShape(shape); + } + } + for (size_t i = 0; i < TB_COUNT(shapes); ++i) { + r3FreeSharedShape(shapes[i]); + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_sleeping_kinematic3.c b/c/testbed/examples3d/debug_sleeping_kinematic3.c new file mode 100644 index 000000000..ba2a3733f --- /dev/null +++ b/c/testbed/examples3d/debug_sleeping_kinematic3.c @@ -0,0 +1,61 @@ +/* Port of examples3d/debug_sleeping_kinematic3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugSleepingKinematic3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle handles[2] = {0}; + for (int i = 0; i < 2; i++) { + R3RigidBodyDesc rigidBody = r3KinematicVelocityBasedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, i ? 0 : 2.3, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(5, 0.5, 5)); + rigidBody.canSleep = !testbed->noSleep; + handles[i] = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handles[i], &collider); + } + /* Set up the viewer. */ + tbCamera(testbed, 10, 5, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + + uint64_t step = testbed->step + 1; + R3Real dt = r3TimeStep(world); + R3Real time = (R3Real)step * dt; + if (!testbed->noSleep && (step == 500 || step == 1000 || step > 1500)) { + for (int i = 0; i < 2; i++) { + R3Bool sleeping = r3RigidBody_IsSleeping(handles[i]); + if (sleeping != 0 != (step != 1000)) { + snprintf(testbed->error, sizeof(testbed->error), + "kinematic sleeping assertion failed at step %llu", + (unsigned long long)step); + fprintf(stderr, "%s\n", testbed->error); + abort(); + } + } + } + if (step == 1000) { + for (int i = 0; i < 2; i++) { + r3RigidBody_SetLinvel(handles[i], r3Vector(0, 0, 0), 1); + } + } + if (step >= 500 && step < 1000) { + if (step == 500) { + r3RigidBody_SetLinvel(handles[1], r3Vector(0, 0.01, 0), 1); + } + + r3RigidBody_SetLinvel(handles[0], r3Vector(0, cos(time * 2), sin(time) * 2), + 1); + } + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_thin_cube_on_mesh3.c b/c/testbed/examples3d/debug_thin_cube_on_mesh3.c new file mode 100644 index 000000000..76f9a1962 --- /dev/null +++ b/c/testbed/examples3d/debug_thin_cube_on_mesh3.c @@ -0,0 +1,43 @@ +/* Port of examples3d/debug_thin_cube_on_mesh3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugThinCubeOnMesh3(Testbed *testbed) { + R3World *world = r3NewWorld(); + R3Real *heights = calloc(2 * 2, sizeof(*heights)); + if (!heights) { + abort(); + } + R3ColliderDesc heightfield = r3DefaultColliderDesc(); + heightfield.shape.kind = R3_SHAPE_DESC_HEIGHTFIELD; + heightfield.shape.heights = (R3RealView){heights, (2) * (2)}; + heightfield.shape.rows = 2; + heightfield.shape.columns = 2; + heightfield.shape.scale = r3Vector(50, 1, 50); + heightfield.shape.flags = R3_HEIGHTFIELD_FIX_INTERNAL_EDGES; + r3InsertColliderWithoutParent(world, &heightfield); + + free(heights); + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 5, 0); + rigidBody.position.rotation = r3RotationFromAxisAngle(r3Vector(.5, 0, .5), sqrt(.5)); + rigidBody.linvel = r3Vector(0, -100, 0); + rigidBody.softCcdPrediction = 10; + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(5, 0.015, 5)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_triangle3.c b/c/testbed/examples3d/debug_triangle3.c new file mode 100644 index 000000000..fc3a7c299 --- /dev/null +++ b/c/testbed/examples3d/debug_triangle3.c @@ -0,0 +1,46 @@ +/* Port of examples3d/debug_triangle3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugTriangle3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3SharedShape *shape = + r3TriangleSharedShape(r3Vector(-10, 0, -10), r3Vector(10, 0, -10), r3Vector(0, 0, 10)); + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, 0, 0); + R3ColliderDesc collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &collider); + + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(1.1, 0.01, 0); + rigidBody.canSleep = 0; + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(20, 0.1, 1)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + groundBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(groundBodyHandle, &boxCollider); + + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + r3FreeSharedShape(shape); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_trimesh3.c b/c/testbed/examples3d/debug_trimesh3.c new file mode 100644 index 000000000..b9c562831 --- /dev/null +++ b/c/testbed/examples3d/debug_trimesh3.c @@ -0,0 +1,48 @@ +/* Port of examples3d/debug_trimesh3.rs. */ +#include "testbed.h" +#include "rapier_math.h" +#include "rapier_helpers.h" + +void tbDebugTrimesh3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3Vector vertices[] = {r3Vector(-0.5, 0, -0.5), r3Vector(0.5, 0, -0.5), + r3Vector(0.5, 0, 0.5), r3Vector(-0.5, 0, 0.5), + r3Vector(-0.5, -0.5, -0.5), r3Vector(0.5, -0.5, -0.5), + r3Vector(0.5, -0.5, 0.5), r3Vector(-0.5, -0.5, 0.5)}; + R3Triangle triangles[] = {{0, 2, 1}, {0, 3, 2}, {4, 5, 6}, {4, 6, 7}, {0, 4, 7}, {0, 7, 3}, + {1, 6, 5}, {1, 2, 6}, {3, 7, 2}, {2, 7, 6}, {0, 1, 5}, {0, 5, 4}}; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 35, 0); + rigidBody.canSleep = 0; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 2, 1)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + R3RigidBodyHandle ground; + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.canSleep = !testbed->noSleep; + R3ColliderDesc groundCollider = r3DefaultColliderDesc(); + r3ShapeDesc_SetTrimesh(&groundCollider.shape, (R3VectorView){vertices, TB_COUNT(vertices)}, + (R3TriangleView){triangles, TB_COUNT(triangles)}, 0); + ground = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(ground, &groundCollider); + tbBodyColor(testbed, ground, 0.75, 0.75, 0.75, 1); + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/debug_two_cubes3.c b/c/testbed/examples3d/debug_two_cubes3.c new file mode 100644 index 000000000..e89147f32 --- /dev/null +++ b/c/testbed/examples3d/debug_two_cubes3.c @@ -0,0 +1,34 @@ +/* Port of examples3d/debug_two_cubes3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDebugTwoCubes3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 2, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, 0, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 0.5)); + groundBody.canSleep = !testbed->noSleep; + rigidBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(rigidBodyHandle, &boxCollider); + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/domino3.c b/c/testbed/examples3d/domino3.c new file mode 100644 index 000000000..04b6aa004 --- /dev/null +++ b/c/testbed/examples3d/domino3.c @@ -0,0 +1,60 @@ +/* Port of examples3d/domino3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbDomino3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(200.1, 0.1, 200.1)); + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &collider); + R3Real angle = 0; + R3Real radius = 10; + int skip = 0; + for (int i = 0; i < 4000; i++) { + R3Real perimeter = 2 * R3_PI * radius; + R3Real prev = angle; + angle += 2 * R3_PI * 0.4 / perimeter; + R3Real x = (R3Real)sin(angle); + R3Real z = (R3Real)cos(angle); + int nudged = fmod(angle, 2 * R3_PI) < fmod(prev, 2 * R3_PI); + R3Real tilt = nudged || i == 3999 ? 0.2 : 0; + if (!skip) { + R3Rotation rotation = r3RotationMul(r3RotationFromAxisAngle(r3Vector(x, 0, z), tilt), + r3RotationFromAxisAngle(r3Vector(0, 1, 0), angle)); + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x * radius, 2.1, z * radius); + rigidBody.position = r3Pose(r3Vector(x * radius, 2.1, z * radius), rotation); + R3RigidBodyHandle handle; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.1, 2, 1)); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + tbBodyColor(testbed, handle, i % 2 ? 0.6f : 0.7f, i % 2 ? 1 : 0.5f, i % 2 ? 0.6f : 0.9f, + 1); + } else { + skip--; + } + if (nudged) { + skip = 5; + } + radius += 1.5 / perimeter; + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/dynamic_trimesh3.c b/c/testbed/examples3d/dynamic_trimesh3.c new file mode 100644 index 000000000..a30239d1e --- /dev/null +++ b/c/testbed/examples3d/dynamic_trimesh3.c @@ -0,0 +1,110 @@ +/* Port of examples3d/dynamic_trimesh3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#include "utils/obj.h" + +void dynamicTrimesh3RunImpl(Testbed *testbed, int useConvexDecomposition) { + R3World *world = r3NewWorld(); + /* Floor made of a wavy mesh. */ + R3Real heights[101 * 101]; + for (size_t j = 0; j <= 100; ++j) { + for (size_t i = 0; i <= 100; ++i) { + heights[i + j * 101] = -cos(i * .2) - cos(j * .2); + } + } + R3SharedShape *heightfield = r3HeightfieldSharedShape((R3RealView){heights, (101) * (101)}, 101, + 101, r3Vector(100, 2, 100)); + R3TriMeshData *floor = r3SharedShape_ToTrimesh(heightfield, 3, 2); + r3FreeSharedShape(heightfield); + size_t vertexCount = 0, indexCount = 0; + vertexCount = r3TriMeshData_Vertices(floor, NULL, 0); + indexCount = r3TriMeshData_Indices(floor, NULL, 0); + R3Vector *vertices = malloc(vertexCount * sizeof(*vertices)); + uint32_t *indices = malloc(indexCount * sizeof(*indices)); + if (!vertices || !indices) { + abort(); + } + vertexCount = r3TriMeshData_Vertices(floor, vertices, vertexCount); + indexCount = r3TriMeshData_Indices(floor, indices, indexCount); + R3ColliderDesc collider = r3DefaultColliderDesc(); + r3ShapeDesc_SetTrimesh(&collider.shape, (R3VectorView){vertices, vertexCount}, + (R3TriangleView){(const R3Triangle *)indices, indexCount / 3}, + R3_TRIMESH_FIX_INTERNAL_EDGES); + r3InsertColliderWithoutParent(world, &collider); + + free(vertices); + free(indices); + r3FreeTriMeshData(floor); + + const char *geoms[] = {"camel_decimated.obj", "chair.obj", + "cup_decimated.obj", "dilo_decimated.obj", + "tstTorusModel2.obj", "feline_decimated.obj", + "genus3_decimated.obj", "hornbug.obj", + "tstTorusModel.obj", "octopus_decimated.obj", + "rabbit_decimated.obj", "rust_logo_simplified.obj", + "screwdriver_decimated.obj", "table.obj", + "tstTorusModel3.obj"}; + const size_t width = (size_t)sqrt(TB_COUNT(geoms)); + const int numDuplications = 4; + const R3Real shiftY = 8, shiftXz = 9; + for (size_t igeom = 0; igeom < TB_COUNT(geoms); ++igeom) { + ObjMesh mesh; + if (!loadObj(testbed->assetRoot, geoms[igeom], &mesh)) { + r3FreeWorld(world); + return; + } + R3Vector mins = mesh.vertices[0], maxs = mins; + for (size_t i = 1; i < mesh.vertexCount; ++i) { + R3Vector p = mesh.vertices[i]; + mins = r3Vector(fmin(mins.x, p.x), fmin(mins.y, p.y), fmin(mins.z, p.z)); + maxs = r3Vector(fmax(maxs.x, p.x), fmax(maxs.y, p.y), fmax(maxs.z, p.z)); + } + const R3Vector center = r3VectorScale(r3VectorAdd(mins, maxs), .5); + const R3Real diag = r3VectorLength(r3VectorSub(maxs, mins)); + for (size_t i = 0; i < mesh.vertexCount; ++i) { + mesh.vertices[i] = r3VectorScale(r3VectorSub(mesh.vertices[i], center), 10 / diag); + } + R3SharedShape *decomposedShape = NULL; + if (useConvexDecomposition) { + decomposedShape = r3ConvexDecompositionSharedShape( + (R3VectorView){mesh.vertices, mesh.vertexCount}, + (R3SurfaceElementView){(const R3Triangle *)mesh.indices, mesh.indexCount / 3}); + } else { + decomposedShape = r3TrimeshSharedShapeWithFlags( + (R3VectorView){mesh.vertices, mesh.vertexCount}, + (R3TriangleView){(const R3Triangle *)mesh.indices, mesh.indexCount / 3}, + R3_TRIMESH_FIX_INTERNAL_EDGES); + } + freeObj(&mesh); + for (int k = 1; k <= numDuplications; ++k) { + const R3Real x = (igeom % width) * shiftXz - numDuplications * shiftXz / 2; + const R3Real y = (igeom / width) * shiftY + 7; + const R3Real z = k * shiftXz - numDuplications * shiftXz / 2; + R3RigidBodyDesc body = r3DynamicRigidBodyDesc(); + body.position.translation = r3Vector(x, y, z); + body.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_SHARED; + collider.shape.sharedShape = decomposedShape; + collider.contactSkin = .1; + R3RigidBodyHandle bodyHandle = r3InsertRigidBody(world, &body); + r3InsertCollider(bodyHandle, &collider); + } + r3FreeSharedShape(decomposedShape); + } + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} + +void tbDynamicTrimesh3(Testbed *testbed) { + dynamicTrimesh3RunImpl(testbed, 0); +} diff --git a/c/testbed/examples3d/fountain3.c b/c/testbed/examples3d/fountain3.c new file mode 100644 index 000000000..311c22b54 --- /dev/null +++ b/c/testbed/examples3d/fountain3.c @@ -0,0 +1,73 @@ +/* Port of examples3d/fountain3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbFountain3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -2.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(40, 2.1, 40)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + /* Set up the viewer. */ + tbCamera(testbed, 30, 4, 30, 0, 1, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + + unsigned k = (unsigned)((testbed->step + 1) % 3); + R3ColliderDesc collider; + if (k == 0) { + collider = r3RoundCylinderColliderDesc(0.5, 0.5, 0.05); + } else { + if (k == 1) { + collider = r3ConeColliderDesc(0.5, 0.5); + } else { + collider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 0.5)); + } + } + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 10, 0); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + size_t n = r3RigidBodyCount(world); + if (n > 2001) { + R3RigidBodyHandle *handle = malloc(n * sizeof(*handle)); + if (!handle) { + abort(); + } + n = r3RigidBodyHandles(world, handle, n); + R3Real largest = -1; + R3RigidBodyHandle remove = {NULL, UINT32_MAX, UINT32_MAX}; + for (size_t i = 0; i < n; i++) { + R3Bool dynamic = r3RigidBody_IsDynamic(handle[i]); + if (dynamic) { + R3Vector position = r3RigidBody_Translation(handle[i]); + R3Real distance = (R3Real)(fabs(position.x) + fabs(position.z)); + if (distance > largest) { + largest = distance; + remove = handle[i]; + } + } + } + free(handle); + if (remove.index != UINT32_MAX) { + r3RemoveRigidBody(remove, 1); + } + } + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/gyroscopic3.c b/c/testbed/examples3d/gyroscopic3.c new file mode 100644 index 000000000..e0075875e --- /dev/null +++ b/c/testbed/examples3d/gyroscopic3.c @@ -0,0 +1,50 @@ +/* Port of examples3d/gyroscopic3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbGyroscopic3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3Pose poses[] = {r3TranslationPose(r3Vector(0, 0, 0)), r3TranslationPose(r3Vector(0, 0.8, 0))}; + R3SharedShape *shapes[2] = {NULL}; + shapes[0] = r3CuboidSharedShape(r3Vector(2.0, 0.2, 0.2)); + shapes[1] = r3CuboidSharedShape(r3Vector(0.2, 0.4, 0.2)); + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0, 0); + rigidBody.gravityScale = 0; + rigidBody.angvel = r3Vector(0, 20, 0.1); + rigidBody.gyroscopicForcesEnabled = 1; + R3ColliderDesc collider; + R3CompoundShapeDesc colliderParts[2]; + for (size_t part = 0; part < 2; ++part) { + colliderParts[part].pose = poses[part]; + colliderParts[part].shape = r3DefaultShapeDesc(); + colliderParts[part].shape.kind = R3_SHAPE_DESC_SHARED; + colliderParts[part].shape.sharedShape = ((const R3SharedShape *const *)shapes)[part]; + } + collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_COMPOUND; + collider.shape.children = (R3CompoundShapeView){colliderParts, 2}; + + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + for (size_t part = 0; part < 2; part++) { + r3FreeSharedShape(shapes[part]); + } + /* Set up the viewer. */ + tbCamera(testbed, 8, 0, 8, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/heightfield3.c b/c/testbed/examples3d/heightfield3.c new file mode 100644 index 000000000..26afb5070 --- /dev/null +++ b/c/testbed/examples3d/heightfield3.c @@ -0,0 +1,102 @@ +/* Port of examples3d/heightfield3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbHeightfield3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3Real heights[441]; + for (int j = 0; j <= 20; j++) { + for (int i = 0; i <= 20; i++) { + heights[i + j * 21] = + i == 0 || i == 20 || j == 0 || j == 20 ? 10 : (R3Real)(sin(i * 5) + cos(j * 5)); + } + } + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0, 0); + R3ColliderDesc groundCollider = r3DefaultColliderDesc(); + groundCollider.shape.kind = R3_SHAPE_DESC_HEIGHTFIELD; + groundCollider.shape.heights = (R3RealView){heights, (21) * (21)}; + groundCollider.shape.rows = 21; + groundCollider.shape.columns = 21; + groundCollider.shape.scale = r3Vector(100, 1, 100); + groundCollider.shape.flags = 0; + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &groundCollider); + + for (int j = 0; j < 20; j++) { + for (int i = 0; i < 8; i++) { + for (int k = 0; k < 8; k++) { + R3ColliderDesc collider; + switch (j % 6) { + case 0: + collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + break; + case 1: + collider = r3BallColliderDesc(1); + break; + case 2: + collider = r3RoundCylinderColliderDesc(1, 1, 0.1); + break; + case 3: + collider = r3ConeColliderDesc(1, 1); + break; + case 4: + collider = r3CapsuleYColliderDesc(1, 1); + break; + default: { + R3Pose poses[] = {r3TranslationPose(r3Vector(0, 0, 0)), + r3TranslationPose(r3Vector(1, 0, 0)), + r3TranslationPose(r3Vector(-1, 0, 0))}; + R3SharedShape *shapes[3] = {NULL}; + shapes[0] = r3CuboidSharedShape(r3Vector(1.0, 0.5, 0.5)); + shapes[1] = r3CuboidSharedShape(r3Vector(0.5, 1.0, 0.5)); + shapes[2] = r3CuboidSharedShape(r3Vector(0.5, 1.0, 0.5)); + R3CompoundShapeDesc colliderParts[TB_COUNT(shapes)]; + for (size_t part = 0; part < TB_COUNT(shapes); ++part) { + colliderParts[part].pose = poses[part]; + colliderParts[part].shape = r3DefaultShapeDesc(); + colliderParts[part].shape.kind = R3_SHAPE_DESC_SHARED; + colliderParts[part].shape.sharedShape = + ((const R3SharedShape *const *)shapes)[part]; + } + collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_COMPOUND; + collider.shape.children = + (R3CompoundShapeView){colliderParts, TB_COUNT(shapes)}; + R3SharedShape *compoundShape = r3ShapeDesc_Build(&collider.shape); + collider.shape.kind = R3_SHAPE_DESC_SHARED; + collider.shape.sharedShape = compoundShape; + for (size_t part = 0; part < TB_COUNT(shapes); part++) { + r3FreeSharedShape(shapes[part]); + } + break; + } + } + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(i * 3 - 12, j * 3 + 4.5, k * 3 - 12); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + if (collider.shape.kind == R3_SHAPE_DESC_SHARED) { + r3FreeSharedShape((R3SharedShape *)collider.shape.sharedShape); + } + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/inverse_kinematics3.c b/c/testbed/examples3d/inverse_kinematics3.c new file mode 100644 index 000000000..5c97be9ed --- /dev/null +++ b/c/testbed/examples3d/inverse_kinematics3.c @@ -0,0 +1,75 @@ +/* Port of examples3d/inverse_kinematics3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbInverseKinematics3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.01, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.2, 0.01, 0.2)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + + const int numSegments = 10; + R3RigidBodyDesc body = r3FixedRigidBodyDesc(); + R3RigidBodyHandle lastBody = r3InsertRigidBody(world, &body); + R3MultibodyJointHandle lastLink = {NULL, UINT32_MAX, UINT32_MAX}; + for (int i = 0; i < numSegments; ++i) { + const R3Real size = 1.0 / numSegments; + R3RigidBodyHandle newBody; + /* Sensors draw the links; IK does not require colliders. */ + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0, 0); + rigidBody.canSleep = 0; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(size / 8, size / 2, size / 8)); + collider.density = 0; + collider.isSensor = 1; + newBody = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(newBody, &collider); + } + R3JointDesc linkAb = r3SphericalJointDesc(); + linkAb.localFrame1.translation = r3Vector(0, size / 2 * (i != 0), 0); + linkAb.localFrame2.translation = r3Vector(0, -size / 2, 0); + lastLink = r3InsertMultibodyJoint(lastBody, newBody, &linkAb); + + lastBody = newBody; + } + tbCamera(testbed, 0, .5, 2.5, 0, .5, 0); + + tbSetWorld(testbed, world); + R3Real *displacements = NULL; + size_t capacity = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + if (!testbed->cursorValid) { + continue; + } + size_t ndofs = r3MultibodyJoint_Ndofs(lastLink); + if (capacity < ndofs) { + R3Real *resized = realloc(displacements, ndofs * sizeof(*resized)); + if (!resized) { + abort(); + } + displacements = resized; + capacity = ndofs; + } + memset(displacements, 0, ndofs * sizeof(*displacements)); + R3InverseKinematicsOptions options = r3DefaultInverseKinematicsOptions(); + options.constrained_axes = 7; /* Linear axes only. */ + const R3Pose target = r3TranslationPose(testbed->cursor); + r3MultibodyJoint_InverseKinematics(lastLink, &options, target, NULL, NULL, + displacements, ndofs); + r3MultibodyJoint_ApplyDisplacements(lastLink, displacements, ndofs); + } + } + free(displacements); + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/joint_motor_position3.c b/c/testbed/examples3d/joint_motor_position3.c new file mode 100644 index 000000000..dd1827894 --- /dev/null +++ b/c/testbed/examples3d/joint_motor_position3.c @@ -0,0 +1,60 @@ +/* Port of examples3d/joint_motor_position3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbJointMotorPosition3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle ground; + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, 0, 0); + groundBody.canSleep = !testbed->noSleep; + ground = r3InsertRigidBody(world, &groundBody); + for (int row = 0; row < 2; row++) { + for (int num = 0; num < (row ? 8 : 9); num++) { + R3Real x = -6 + 1.5 * num; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x, row ? 4.5 : 2, 0); + rigidBody.canSleep = 0; + if (row) { + rigidBody.position = + r3Pose(r3Vector(x, 4.5, 0), r3RotationFromAxisAngle(r3Vector(0, 0, 1), R3_PI)); + } + R3RigidBodyHandle handle; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.1, 0.5, 0.1)); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + joint.localFrame1.translation = r3Vector(x, row ? 5 : 1.5, 0); + joint.localFrame2.translation = r3Vector(0, -0.5, 0); + R3Real angle = -R3_PI + R3_PI / 4 * num; + if (row) { + r3JointDesc_SetMotor(&joint, R3_AXIS_ANG_X, 0, 1.5, 0, 30); + r3JointDesc_SetMotorMaxForce(&joint, R3_AXIS_ANG_X, 100); + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_X, -R3_PI, angle); + } else { + r3JointDesc_SetMotor(&joint, R3_AXIS_ANG_X, angle, 0, 1000, 150); + } + r3InsertImpulseJoint(ground, handle, &joint); + } + } + r3SetGravity(world, r3Vector(0, 0, 0)); + /* Set up the viewer. */ + tbCamera(testbed, 15, 5, 42, 13, 1, 1); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/joints3.c b/c/testbed/examples3d/joints3.c new file mode 100644 index 000000000..691b9a4b3 --- /dev/null +++ b/c/testbed/examples3d/joints3.c @@ -0,0 +1,486 @@ +/* Port of examples3d/joints3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#include + +static void createPrismaticJoints(Testbed *testbed, R3World *world, R3Vector origin, size_t num, + int useArticulations) { + R3RigidBodyHandle currParent; + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = origin; + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(.4, .4, .4)); + currParent = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(currParent, &collider); + } + for (size_t i = 0; i < num; ++i) { + R3RigidBodyHandle currChild; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(origin.x, origin.y, origin.z + (i + 1) * 2); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(.4, .4, .4)); + currChild = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(currChild, &collider); + } + const R3Vector axis = r3VectorNormalize(r3Vector(i % 2 == 0 ? 1 : -1, 1, 0)); + R3JointDesc prism = r3PrismaticJointDesc(axis); + prism.localFrame1.translation = r3Vector(0, 0, 0); + prism.localFrame2.translation = r3Vector(0, 0, -2); + r3JointDesc_SetLimits(&prism, R3_AXIS_LIN_X, -2, 2); + if (useArticulations) { + r3InsertMultibodyJoint(currParent, currChild, &prism); + } else { + r3InsertImpulseJoint(currParent, currChild, &prism); + } + + currParent = currChild; + } +} + +static void createActuatedPrismaticJoints(Testbed *testbed, R3World *world, R3Vector origin, + size_t num, int useArticulations) { + R3RigidBodyHandle currParent; + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = origin; + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(.4, .4, .4)); + currParent = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(currParent, &collider); + } + for (size_t i = 0; i < num; ++i) { + R3RigidBodyHandle currChild; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(origin.x, origin.y, origin.z + (i + 1) * 2); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(.4, .4, .4)); + currChild = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(currChild, &collider); + } + const R3Vector axis = r3VectorNormalize(r3Vector(i % 2 == 0 ? 1 : -1, 1, 0)); + R3JointDesc prism = r3PrismaticJointDesc(axis); + prism.localFrame1.translation = r3Vector(0, 0, 2); + prism.localFrame2.translation = r3Vector(0, 0, 0); + if (i == 0) { + r3JointDesc_SetMotorVelocity(&prism, R3_AXIS_LIN_X, 2, 1e5); + r3JointDesc_SetLimits(&prism, R3_AXIS_LIN_X, -2, 5); + r3JointDesc_SetMotorMaxForce(&prism, R3_AXIS_LIN_X, 100); + } else if (i == 1) { + r3JointDesc_SetLimits(&prism, R3_AXIS_LIN_X, -FLT_MAX, 5); + r3JointDesc_SetMotorVelocity(&prism, R3_AXIS_LIN_X, 6, 1e3); + r3JointDesc_SetMotorMaxForce(&prism, R3_AXIS_LIN_X, 100); + } else { + r3JointDesc_SetMotorPosition(&prism, R3_AXIS_LIN_X, 2, 1e3, 1e2); + r3JointDesc_SetMotorMaxForce(&prism, R3_AXIS_LIN_X, 60); + } + if (useArticulations) { + r3InsertMultibodyJoint(currParent, currChild, &prism); + } else { + r3InsertImpulseJoint(currParent, currChild, &prism); + } + + currParent = currChild; + } +} + +static void createRevoluteJoints(Testbed *testbed, R3World *world, R3Vector origin, size_t num, + int useArticulations) { + R3RigidBodyHandle currParent; + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(origin.x, origin.y, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(.4, .4, .4)); + currParent = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(currParent, &collider); + } + for (size_t i = 0; i < num; ++i) { + const R3Real z = origin.z + i * 4 + 2; + const R3Vector positions[] = {{origin.x, origin.y, z}, + {origin.x + 2, origin.y, z}, + {origin.x + 2, origin.y, z + 2}, + {origin.x, origin.y, z + 2}}; + R3RigidBodyHandle handles[4]; + for (size_t k = 0; k < 4; ++k) { + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = positions[k]; + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.4, 0.4, 0.4)); + handles[k] = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handles[k], &collider); + } + } + { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + joint.localFrame2.translation = r3Vector(0, 0, -2); + if (useArticulations) { + r3InsertMultibodyJoint(currParent, handles[0], &joint); + } else { + r3InsertImpulseJoint(currParent, handles[0], &joint); + } + } + { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(1, 0, 0)); + joint.localFrame2.translation = r3Vector(-2, 0, 0); + if (useArticulations) { + r3InsertMultibodyJoint(handles[0], handles[1], &joint); + } else { + r3InsertImpulseJoint(handles[0], handles[1], &joint); + } + } + { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + joint.localFrame2.translation = r3Vector(0, 0, -2); + if (useArticulations) { + r3InsertMultibodyJoint(handles[1], handles[2], &joint); + } else { + r3InsertImpulseJoint(handles[1], handles[2], &joint); + } + } + { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(1, 0, 0)); + joint.localFrame2.translation = r3Vector(2, 0, 0); + if (useArticulations) { + r3InsertMultibodyJoint(handles[2], handles[3], &joint); + } else { + r3InsertImpulseJoint(handles[2], handles[3], &joint); + } + } + currParent = handles[3]; + } +} + +static void createRevoluteJointsWithLimits(Testbed *testbed, R3World *world, R3Vector origin, + int useArticulations) { + R3RigidBodyDesc groundBuilder = r3FixedRigidBodyDesc(); + groundBuilder.position.translation = origin; + R3RigidBodyHandle ground = r3InsertRigidBody(world, &groundBuilder); + + R3RigidBodyHandle body1; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(origin, r3Vector(0, 0, 0)); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(4, 0.2, 2)); + body1 = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(body1, &collider); + } + R3RigidBodyHandle body2; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(origin, r3Vector(0, 0, 6)); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(4, 0.2, 2)); + body2 = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(body2, &collider); + } + { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_X, -.2, .2); + if (useArticulations) { + r3InsertMultibodyJoint(ground, body1, &joint); + } else { + r3InsertImpulseJoint(ground, body1, &joint); + } + } + { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + joint.localFrame2.translation = r3Vector(0, 0, -6); + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_X, -.2, .2); + if (useArticulations) { + r3InsertMultibodyJoint(body1, body2, &joint); + } else { + r3InsertImpulseJoint(body1, body2, &joint); + } + } + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(origin, r3Vector(-2, 4, 0)); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.6, 0.6, 0.6)); + collider.friction = 1; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(origin, r3Vector(2, 16, 6)); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.6, 0.6, 0.6)); + collider.friction = 1; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } +} + +static void createSphericalJointsWithLimits(Testbed *testbed, R3World *world, R3Vector origin, + int useArticulations) { + R3RigidBodyDesc groundBuilder = r3FixedRigidBodyDesc(); + groundBuilder.position.translation = origin; + R3RigidBodyHandle ground = r3InsertRigidBody(world, &groundBuilder); + + R3RigidBodyHandle body1; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(origin, r3Vector(0, 0, 3)); + rigidBody.linvel = r3Vector(20, 20, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + body1 = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(body1, &collider); + } + R3RigidBodyHandle body2; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(origin, r3Vector(0, 0, 6)); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + body2 = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(body2, &collider); + } + { + R3JointDesc joint = r3SphericalJointDesc(); + joint.localFrame2.translation = r3Vector(0, 0, -3); + r3JointDesc_SetLimits(&joint, R3_AXIS_LIN_X, -0.2, 0.2); + r3JointDesc_SetLimits(&joint, R3_AXIS_LIN_Y, -0.2, 0.2); + if (useArticulations) { + r3InsertMultibodyJoint(ground, body1, &joint); + } else { + r3InsertImpulseJoint(ground, body1, &joint); + } + } + { + R3JointDesc joint = r3SphericalJointDesc(); + joint.localFrame2.translation = r3Vector(0, 0, -3); + r3JointDesc_SetLimits(&joint, R3_AXIS_LIN_X, -0.3, 0.3); + r3JointDesc_SetLimits(&joint, R3_AXIS_LIN_Y, -0.3, 0.3); + if (useArticulations) { + r3InsertMultibodyJoint(body1, body2, &joint); + } else { + r3InsertImpulseJoint(body1, body2, &joint); + } + } +} + +static void createFixedJoints(Testbed *testbed, R3World *world, R3Vector origin, size_t num, + int useArticulations) { + R3RigidBodyHandle *bodyHandles = malloc(num * num * sizeof(*bodyHandles)); + if (!bodyHandles) { + abort(); + } + size_t count = 0; + for (size_t i = 0; i < num; ++i) { + for (size_t k = 0; k < num; ++k) { + const int fixed = i == 0 && ((k % 4 == 0 && k != num - 2) || k == num - 1); + R3RigidBodyHandle childHandle; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = fixed ? R3_FIXED : R3_DYNAMIC; + rigidBody.position.translation = r3Vector(origin.x + k, origin.y, origin.z + i); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3BallColliderDesc(.4); + childHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(childHandle, &collider); + } + if (i > 0) { + { + R3JointDesc joint = r3FixedJointDesc(); + joint.localFrame2.translation = r3Vector(0, 0, -1); + if (useArticulations) { + r3InsertMultibodyJoint(bodyHandles[count - num], childHandle, + &joint); + } else { + r3InsertImpulseJoint(bodyHandles[count - num], childHandle, &joint); + } + } + } + if (k > 0) { + { + R3JointDesc joint = r3FixedJointDesc(); + joint.localFrame2.translation = r3Vector(-1, 0, 0); + r3InsertImpulseJoint(bodyHandles[count - 1], childHandle, &joint); + } + } + bodyHandles[count++] = childHandle; + } + } + free(bodyHandles); +} + +static void createSphericalJoints(Testbed *testbed, R3World *world, size_t num, + int useArticulations) { + R3RigidBodyHandle *bodyHandles = malloc(num * num * sizeof(*bodyHandles)); + if (!bodyHandles) { + abort(); + } + size_t count = 0; + for (size_t k = 0; k < num; ++k) { + for (size_t i = 0; i < num; ++i) { + const int fixed = i == 0 && (k % 4 == 0 || k == num - 1); + R3RigidBodyHandle childHandle; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = fixed ? R3_FIXED : R3_DYNAMIC; + rigidBody.position.translation = r3Vector(k, 0, i * 2); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = + r3CapsuleColliderDesc(r3Vector(0, 0, -0.5), r3Vector(0, 0, 0.5), .4); + childHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(childHandle, &collider); + } + if (i > 0) { + { + R3JointDesc joint = r3SphericalJointDesc(); + joint.localFrame2.translation = r3Vector(0, 0, -2); + if (useArticulations) { + r3InsertMultibodyJoint(bodyHandles[count - 1], childHandle, &joint); + } else { + r3InsertImpulseJoint(bodyHandles[count - 1], childHandle, &joint); + } + } + } + if (k > 0) { + { + R3JointDesc joint = r3SphericalJointDesc(); + joint.localFrame2.translation = r3Vector(-1, 0, 0); + r3InsertImpulseJoint(bodyHandles[count - num], childHandle, &joint); + } + } + bodyHandles[count++] = childHandle; + } + } + free(bodyHandles); +} + +static void createActuatedRevoluteJoints(Testbed *testbed, R3World *world, R3Vector origin, + size_t num, int useArticulations) { + R3RigidBodyHandle parentHandle = {NULL, UINT32_MAX, UINT32_MAX}; + for (size_t i = 0; i < num; ++i) { + R3RigidBodyHandle childHandle; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = i == 0 ? R3_FIXED : R3_DYNAMIC; + rigidBody.position.translation = + r3Vector(origin.x, origin.y + (i >= 1 ? -2 : 0), origin.z + i * 2); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.8, 2.4 / (i + 1), 0.4)); + childHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(childHandle, &collider); + } + if (i > 0) { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + joint.localFrame2.translation = r3Vector(0, 0, -2); + r3JointDesc_SetMotorModel(&joint, R3_AXIS_ANG_X, 0); + if (i % 3 == 1) { + r3JointDesc_SetMotorVelocity(&joint, R3_AXIS_ANG_X, -20, 100); + } else if (i == num - 1) { + r3JointDesc_SetMotorPosition(&joint, R3_AXIS_ANG_X, R3_PI / 2, 200, 100); + } + if (i == 1) { + joint.localFrame2.translation = r3Vector(0, 2, -2); + r3JointDesc_SetMotorVelocity(&joint, R3_AXIS_ANG_X, -2, 1000); + } + if (useArticulations) { + r3InsertMultibodyJoint(parentHandle, childHandle, &joint); + } else { + r3InsertImpulseJoint(parentHandle, childHandle, &joint); + } + } + parentHandle = childHandle; + } +} + +static void createActuatedSphericalJoints(Testbed *testbed, R3World *world, R3Vector origin, + size_t num, int useArticulations) { + R3RigidBodyHandle parentHandle = {NULL, UINT32_MAX, UINT32_MAX}; + for (size_t i = 0; i < num; ++i) { + R3RigidBodyHandle childHandle; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = i == 0 ? R3_FIXED : R3_DYNAMIC; + rigidBody.position.translation = r3Vector(origin.x, origin.y, origin.z + i * 2); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CapsuleYColliderDesc(.8 / (i + 1), .4); + childHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(childHandle, &collider); + } + if (i > 0) { + R3JointDesc joint = r3SphericalJointDesc(); + joint.localFrame1.translation = r3Vector(0, 0, 2); + if (i == 1) { + r3JointDesc_SetMotorVelocity(&joint, R3_AXIS_ANG_X, 0, .1); + r3JointDesc_SetMotorVelocity(&joint, R3_AXIS_ANG_Y, .5, .1); + r3JointDesc_SetMotorVelocity(&joint, R3_AXIS_ANG_Z, -2, .1); + } else if (i == num - 1) { + r3JointDesc_SetMotorPosition(&joint, R3_AXIS_ANG_X, 0, .2, 1); + r3JointDesc_SetMotorPosition(&joint, R3_AXIS_ANG_Y, 1, .2, 1); + r3JointDesc_SetMotorPosition(&joint, R3_AXIS_ANG_Z, R3_PI / 2, .2, 1); + } + if (useArticulations) { + r3InsertMultibodyJoint(parentHandle, childHandle, &joint); + } else { + r3InsertImpulseJoint(parentHandle, childHandle, &joint); + } + } + parentHandle = childHandle; + } +} + +static void createCoupledJoints(Testbed *testbed, R3World *world, R3Vector origin, + int useArticulations) { + R3RigidBodyDesc builder = r3FixedRigidBodyDesc(); + builder.position.translation = origin; + R3RigidBodyHandle ground = r3InsertRigidBody(world, &builder); + + R3RigidBodyHandle body1; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = origin; + rigidBody.linvel = r3Vector(5, 5, 5); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + body1 = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(body1, &collider); + } + { + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = 0; + r3JointDesc_SetLimits(&joint, R3_AXIS_LIN_X, -3, 3); + r3JointDesc_SetLimits(&joint, R3_AXIS_LIN_Y, 0, 3); + joint.coupledAxes = 2 | 4; + if (useArticulations) { + r3InsertMultibodyJoint(ground, body1, &joint); + } else { + r3InsertImpulseJoint(ground, body1, &joint); + } + } +} + +void joints3Run(Testbed *testbed, int useArticulations) { + R3World *world = r3NewWorld(); + createPrismaticJoints(testbed, world, r3Vector(20, 5, 0), 4, useArticulations); + createActuatedPrismaticJoints(testbed, world, r3Vector(25, 5, 0), 4, useArticulations); + createRevoluteJoints(testbed, world, r3Vector(20, 0, 0), 3, useArticulations); + createRevoluteJointsWithLimits(testbed, world, r3Vector(34, 0, 0), useArticulations); + createFixedJoints(testbed, world, r3Vector(0, 10, 0), 10, useArticulations); + createActuatedRevoluteJoints(testbed, world, r3Vector(20, 10, 0), 6, useArticulations); + createActuatedSphericalJoints(testbed, world, r3Vector(13, 10, 0), 3, useArticulations); + createSphericalJoints(testbed, world, 15, useArticulations); + createSphericalJointsWithLimits(testbed, world, r3Vector(-5, 0, 0), useArticulations); + createCoupledJoints(testbed, world, r3Vector(0, 20, 0), useArticulations); + tbCamera(testbed, 15, 5, 42, 13, 1, 1); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/joints3_run_impulse_joints.c b/c/testbed/examples3d/joints3_run_impulse_joints.c new file mode 100644 index 000000000..05e9c5a6c --- /dev/null +++ b/c/testbed/examples3d/joints3_run_impulse_joints.c @@ -0,0 +1,7 @@ +/* Port of examples3d/joints3.rs: run_impulse_joints. */ +#include "testbed.h" +void joints3Run(Testbed *testbed, int useArticulations); + +void tbJoints3RunImpulseJoints(Testbed *testbed) { + joints3Run(testbed, 0); +} diff --git a/c/testbed/examples3d/joints3_run_multibody_joints.c b/c/testbed/examples3d/joints3_run_multibody_joints.c new file mode 100644 index 000000000..da2093fe4 --- /dev/null +++ b/c/testbed/examples3d/joints3_run_multibody_joints.c @@ -0,0 +1,7 @@ +/* Port of examples3d/joints3.rs: run_multibody_joints. */ +#include "testbed.h" +void joints3Run(Testbed *testbed, int useArticulations); + +void tbJoints3RunMultibodyJoints(Testbed *testbed) { + joints3Run(testbed, 1); +} diff --git a/c/testbed/examples3d/keva3.c b/c/testbed/examples3d/keva3.c new file mode 100644 index 000000000..76632d0fb --- /dev/null +++ b/c/testbed/examples3d/keva3.c @@ -0,0 +1,97 @@ +/* Port of examples3d/keva3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +/* Corresponds to keva3::buildBlock, shared by the Keva and cloth examples. */ +void buildBlock(Testbed *testbed, R3World *world, R3Vector halfExtents, R3Vector shift, int numx, + int numy, int numz) { + const R3Vector dimensions[] = { + halfExtents, + r3Vector(halfExtents.z, halfExtents.y, halfExtents.x), + }; + const R3Real blockWidth = 2.0 * halfExtents.z * numx; + const R3Real blockHeight = 2.0 * halfExtents.y * numy; + const R3Real spacing = (halfExtents.z * numx - halfExtents.x) / (numz - 1); + + for (int i = 0; i < numy; i++) { + const int oldNumx = numx; + numx = numz; + numz = oldNumx; + const R3Vector dim = dimensions[i % 2]; + const R3Real y = dim.y * i * 2.0; + + for (int j = 0; j < numx; j++) { + const R3Real x = i % 2 == 0 ? spacing * j * 2.0 : dim.x * j * 2.0; + for (int k = 0; k < numz; k++) { + const R3Real z = i % 2 == 0 ? dim.z * k * 2.0 : spacing * k * 2.0; + + /* Build the rigid body. */ + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(x + dim.x + shift.x, y + dim.y + shift.y, z + dim.z + shift.z); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(dim); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + + /* Close the top. */ + const R3Vector dim = r3Vector(halfExtents.z, halfExtents.x, halfExtents.y); + for (int i = 0; i < (int)(blockWidth / (dim.x * 2.0)); i++) { + for (int j = 0; j < (int)(blockWidth / (dim.z * 2.0)); j++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * dim.x * 2.0 + dim.x + shift.x, dim.y + blockHeight + shift.y, + j * dim.z * 2.0 + dim.z + shift.z); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(dim); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } +} + +void tbKeva3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + /* Ground. */ + const R3Real groundSize = 50.0; + const R3Real groundHeight = 0.1; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0.0, -groundHeight, 0.0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(groundSize, groundHeight, groundSize)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + /* Create the cubes. Odd layer counts keep adjacent blocks aligned. */ + const R3Vector halfExtents = r3Vector(0.1, 0.5, 2.0); + R3Real blockHeight = 0.0; + const int layers[] = {0, 9, 13, 17, 21, 41}; + + for (int i = 5; i >= 1; i--) { + const int numx = i; + const int numy = layers[i]; + const int numz = numx * 3 + 1; + const R3Real blockWidth = numx * halfExtents.z * 2.0; + buildBlock(testbed, world, halfExtents, + r3Vector(-blockWidth / 2.0, blockHeight, -blockWidth / 2.0), numx, numy, numz); + blockHeight += numy * halfExtents.y * 2.0 + halfExtents.x * 2.0; + } + + /* Set up the viewer. */ + tbCamera(testbed, 100.0, 100.0, 100.0, 0.0, 0.0, 0.0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/locked_rotations3.c b/c/testbed/examples3d/locked_rotations3.c new file mode 100644 index 000000000..e952ba9d2 --- /dev/null +++ b/c/testbed/examples3d/locked_rotations3.c @@ -0,0 +1,55 @@ +/* Port of examples3d/locked_rotations3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbLockedRotations3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(5, 0.1, 5)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + R3RigidBodyDesc rectangle = r3DynamicRigidBodyDesc(); + rectangle.position.translation = r3Vector(0, 3, 0); + rectangle.lockedAxes = R3_LOCK_TRANSLATION_X | R3_LOCK_TRANSLATION_Y | R3_LOCK_TRANSLATION_Z | + R3_LOCK_ROTATION_Y | R3_LOCK_ROTATION_Z; + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(0.2, 0.6, 2)); + rectangle.canSleep = !testbed->noSleep; + rigidBodyHandle = r3InsertRigidBody(world, &rectangle); + r3InsertCollider(rigidBodyHandle, &boxCollider); + + R3RigidBodyDesc capsule = r3DynamicRigidBodyDesc(); + capsule.position.translation = r3Vector(0, 5, 0); + capsule.position = r3Pose(r3Vector(0, 5, 0), r3RotationFromAxisAngle(r3Vector(1, 0, 0), 1)); + capsule.lockedAxes = R3_LOCK_ROTATION_X | R3_LOCK_ROTATION_Y | R3_LOCK_ROTATION_Z; + R3RigidBodyHandle handle; + R3ColliderDesc capsuleCollider = r3CapsuleYColliderDesc(0.6, 0.4); + capsule.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &capsule); + r3InsertCollider(handle, &capsuleCollider); + + R3SharedShape *shape = r3CapsuleSharedShape(r3Vector(-0.6, 0, 0), r3Vector(0.6, 0, 0), 0.4); + R3ColliderDesc shapeCollider = r3DefaultColliderDesc(); + shapeCollider.shape.kind = R3_SHAPE_DESC_SHARED; + shapeCollider.shape.sharedShape = shape; + r3InsertCollider(handle, &shapeCollider); + + /* Set up the viewer. */ + tbCamera(testbed, 10, 3, 0, 0, 3, 0); + r3FreeSharedShape(shape); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/mjcf3.c b/c/testbed/examples3d/mjcf3.c new file mode 100644 index 000000000..534c76db8 --- /dev/null +++ b/c/testbed/examples3d/mjcf3.c @@ -0,0 +1,38 @@ +/* Port of examples3d/mjcf3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" +#ifdef RAPIER_ROBOTICS +void tbMjcf3(Testbed *testbed) { + R3World *world = r3NewWorld(); + R3MjcfLoaderOptions options = r3DefaultMjcfLoaderOptions(); + + options.makeRootsFixed = 1; + /* Z-up to Y-up, matching the Rust example's model convention. */ + options.shift = r3Pose(r3Vector(0, 0, 0), r3RotationFromAxisAngle(r3Vector(1, 0, 0), -R3_PI / 2)); + R3RigidBodyDesc blueprint = r3DynamicRigidBodyDesc(); + blueprint.canSleep = !testbed->noSleep; + options.rigidBodyBlueprint = blueprint; + + char path[4096]; + snprintf(path, sizeof(path), "%s/3d/agility_cassie/scene.xml", testbed->assetRoot); + R3MjcfRobot *robot = r3MjcfRobotFromFile(path, &options); + /* Insert the same robot with each joint representation. */ + R3MjcfRobotHandles *impulse = NULL, *multibody = NULL; + impulse = r3MjcfRobot_InsertUsingImpulseJoints(world, robot); + r3MjcfRobot_AppendTransform(robot, r3TranslationPose(r3Vector(0, 0, 1))); + multibody = r3MjcfRobot_InsertUsingMultibodyJoints( + world, robot, R3_MULTIBODY_SKIP_LOOP_CLOSURES | R3_MULTIBODY_DISABLE_SELF_CONTACTS); + r3FreeMjcfRobotHandles(impulse); + r3FreeMjcfRobotHandles(multibody); + r3FreeMjcfRobot(robot); + tbSetWorld(testbed, world); + tbCamera(testbed, 2, 2, 2, 0, 0, 0); + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} +#endif diff --git a/c/testbed/examples3d/mujoco_menagerie3.c b/c/testbed/examples3d/mujoco_menagerie3.c new file mode 100644 index 000000000..df4ff3900 --- /dev/null +++ b/c/testbed/examples3d/mujoco_menagerie3.c @@ -0,0 +1,372 @@ +/* Port of examples3d/mujoco_menagerie3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" +#ifdef RAPIER_ROBOTICS +#include "utils/files.h" + +static size_t lastFramedScene = SIZE_MAX; + +static int comparePaths(const void *a, const void *b) { + return strcmp(*(const char *const *)a, *(const char *const *)b); +} + +/* Like the Rust discovery loop, look one directory below the root for scene*.xml. */ +static FileNames discoverScenes(const char *root) { + FileNames scenes = {0}, robots = listDirectory(root); + for (size_t i = 0; i < robots.count; ++i) { + char directory[4096]; + snprintf(directory, sizeof(directory), "%s/%s", root, robots.names[i]); + FileNames files = listDirectory(directory); + for (size_t j = 0; j < files.count; ++j) { + const char *name = files.names[j]; + size_t length = strlen(name); + if (strncmp(name, "scene", 5) || length < 4 || strcmp(name + length - 4, ".xml")) { + continue; + } + char path[8192]; + snprintf(path, sizeof(path), "%s/%s", directory, name); + addFilename(&scenes, path); + } + freeFilenames(&files); + } + freeFilenames(&robots); + qsort(scenes.names, scenes.count, sizeof(*scenes.names), comparePaths); + return scenes; +} + +static R3MjcfLoaderOptions loaderOptions(Testbed *testbed) { + R3MjcfLoaderOptions options = r3DefaultMjcfLoaderOptions(); + options.skipPlaneGeoms = 1; + options.makeRootsFixed = 0; + options.createCollidersFromVisualShapes = 0; + R3ColliderDesc collider = r3BallColliderDesc(.5); + collider.density = 0; + options.colliderBlueprint = collider; + + R3RigidBodyDesc body = r3DynamicRigidBodyDesc(); + body.canSleep = !testbed->noSleep; + options.rigidBodyBlueprint = body; + + return options; +} + +static char *keyframeName(const R3MjcfRobot *robot, size_t key) { + size_t count = r3MjcfRobot_KeyframeName(robot, key, NULL, 0); + char *name = malloc(count); + if (!name) { + abort(); + } + count = r3MjcfRobot_KeyframeName(robot, key, name, count); + return name; +} + +static void mergeSiblingKeyframes(R3MjcfRobot *robot, const char *path, + const R3MjcfLoaderOptions *options) { + const char *slash = strrchr(path, '/'); + if (!slash) { + return; + } + char sibling[8192]; + snprintf(sibling, sizeof(sibling), "%.*s/keyframes.xml", (int)(slash - path), path); + FILE *file = fopen(sibling, "rb"); + if (!file) { + return; + } + fclose(file); + R3MjcfRobot *keys = NULL; + R3ErrorHandler handler = r3SetErrorHandler((R3ErrorHandler){0}); + keys = r3MjcfRobotFromFile(sibling, options); + R3Status status = r3LastStatus(); + r3SetErrorHandler(handler); + if (status != R3_OK) { + fprintf(stderr, "Failed to load sibling keyframes %s: %s\n", sibling, r3LastError()); + return; + } + size_t originalCount = 0, count = 0; + originalCount = r3MjcfRobot_KeyframeCount(robot); + count = r3MjcfRobot_KeyframeCount(keys); + for (size_t i = 0; i < count; ++i) { + char *name = keyframeName(keys, i); + int exists = 0; + for (size_t j = 0; name[0] && j < originalCount; ++j) { + char *existing = keyframeName(robot, j); + exists |= !strcmp(existing, name); + free(existing); + } + if (!exists) { + r3MjcfRobot_AppendKeyframe(robot, keys, i); + } + free(name); + } + r3FreeMjcfRobot(keys); +} + +static void addFloor(R3World *world) { + size_t count = r3ColliderHandles(world, NULL, 0); + if (!count) { + return; + } + R3ColliderHandle *handles = malloc(count * sizeof(*handles)); + if (!handles) { + abort(); + } + count = r3ColliderHandles(world, handles, count); + R3Aabb total = {r3Vector(INFINITY, INFINITY, INFINITY), + r3Vector(-INFINITY, -INFINITY, -INFINITY)}; + for (size_t i = 0; i < count; ++i) { + R3Aabb bounds = r3Collider_ComputeAabb(handles[i]); + total.mins.x = fmin(total.mins.x, bounds.mins.x); + total.mins.y = fmin(total.mins.y, bounds.mins.y); + total.mins.z = fmin(total.mins.z, bounds.mins.z); + total.maxs.x = fmax(total.maxs.x, bounds.maxs.x); + total.maxs.y = fmax(total.maxs.y, bounds.maxs.y); + total.maxs.z = fmax(total.maxs.z, bounds.maxs.z); + } + free(handles); + R3Vector half = r3VectorScale(r3VectorSub(total.maxs, total.mins), .5); + R3Vector center = r3VectorScale(r3VectorAdd(total.maxs, total.mins), .5); + half.x *= 10; + half.y *= 10; + center.z -= half.z; + half.z = .2; + center.z -= .2; + R3ColliderDesc floor = r3CuboidColliderDesc(half); + floor.position.translation = center; + r3InsertColliderWithoutParent(world, &floor); +} + +static void registerVisualMeshes(Testbed *testbed, const R3MjcfRobot *robot, + const R3MjcfRobotHandles *handles, int includePrimitives) { + size_t bodyCount = r3MjcfRobotHandles_Bodies(handles, NULL, 0); + R3RigidBodyHandle *bodies = malloc(bodyCount * sizeof(*bodies)); + if (!bodies && bodyCount) { + abort(); + } + bodyCount = r3MjcfRobotHandles_Bodies(handles, bodies, bodyCount); + for (size_t i = 0; i < bodyCount; ++i) { + if (bodies[i].index == UINT32_MAX) { + continue; + } + size_t count = r3MjcfRobot_BodyVisualCount(robot, i); + for (size_t j = 0; j < count; ++j) { + const R3MjcfVisualMesh *visual = NULL; + R3MjcfVisualMeshInfo info; + visual = r3MjcfRobot_BodyVisual(robot, i, j); + info = r3MjcfVisualMesh_Info(visual); + if (!info.is_trimesh && !includePrimitives) { + continue; + } + R3SharedShape *shape = NULL; + R3TriMeshData *geometry = NULL; + shape = r3MjcfVisualMesh_CloneShape(visual); + geometry = r3SharedShape_ToTrimesh(shape, 24, 12); + r3FreeSharedShape(shape); + TbRenderMesh mesh = {.body = bodies[i], + .localPose = info.local_pose, + .metallic = info.material.metallic, + .roughness = info.material.roughness, + .reflectance = info.material.reflectance}; + memcpy(mesh.rgba, info.rgba, sizeof(mesh.rgba)); + memcpy(mesh.emissive, info.material.emissive, sizeof(mesh.emissive)); + mesh.vertexCount = r3TriMeshData_Vertices(geometry, NULL, 0); + mesh.indexCount = r3TriMeshData_Indices(geometry, NULL, 0); + mesh.vertices = malloc(mesh.vertexCount * sizeof(*mesh.vertices)); + mesh.indices = malloc(mesh.indexCount * sizeof(*mesh.indices)); + if (!mesh.vertices || !mesh.indices) { + abort(); + } + mesh.vertexCount = r3TriMeshData_Vertices(geometry, mesh.vertices, mesh.vertexCount); + mesh.indexCount = r3TriMeshData_Indices(geometry, mesh.indices, mesh.indexCount); + r3FreeTriMeshData(geometry); + size_t uvCount = 0, normalCount = 0, pathCount = 0; + uvCount = r3MjcfVisualMesh_Uvs(visual, NULL, 0); + normalCount = r3MjcfVisualMesh_Normals(visual, NULL, 0); + pathCount = r3MjcfVisualMesh_Texture(visual, NULL, 0); + if (uvCount == mesh.vertexCount * 2) { + mesh.uvs = malloc(uvCount * sizeof(float)); + if (!mesh.uvs) { + abort(); + } + uvCount = r3MjcfVisualMesh_Uvs(visual, mesh.uvs, uvCount); + } + if (normalCount == mesh.vertexCount * 3) { + mesh.normals = malloc(normalCount * sizeof(float)); + if (!mesh.normals) { + abort(); + } + normalCount = r3MjcfVisualMesh_Normals(visual, mesh.normals, normalCount); + } + mesh.texture = malloc(pathCount); + if (!mesh.texture) { + abort(); + } + pathCount = r3MjcfVisualMesh_Texture(visual, mesh.texture, pathCount); + if (mesh.texture[0] && !info.has_color) { + for (int k = 0; k < 4; ++k) { + mesh.rgba[k] = 1; + } + } + tbAddBodyRenderMesh(testbed, &mesh); + free(mesh.vertices); + free(mesh.indices); + free(mesh.uvs); + free(mesh.normals); + free(mesh.texture); + } + } + free(bodies); +} + +void tbMujocoMenagerie3(Testbed *testbed) { + const char *root = getenv("RAPIER_MENAGERIE_DIR"); + if (!root) { + root = "../mujoco_menagerie"; + } + FileNames scenes = discoverScenes(root), labels = {0}, keyNames = {0}; + size_t defaultScene = 0; + const char *rootName = strrchr(root, '/'); + rootName = rootName ? rootName + 1 : root; + for (size_t i = 0; i < scenes.count; ++i) { + if (strstr(scenes.names[i], "unitree_a1")) { + defaultScene = i; + } + char label[8192]; + snprintf(label, sizeof(label), "%s/%s", rootName, scenes.names[i] + strlen(root) + 1); + addFilename(&labels, label); + } + const int useMultibody = tbSetting(testbed, "Use multibody joints", 1, 0, 1, 1); + const int renderColliders = tbSetting(testbed, "Render colliders", 0, 0, 1, 1); + const int renderVisualMeshes = tbSetting(testbed, "Render visual meshes", 1, 0, 1, 1); + const int renderVisualPrimitives = tbSetting(testbed, "Render visual primitives", 0, 0, 1, 1); + const int disableCollisions = tbSetting(testbed, "Disable collisions", 1, 0, 1, 1); + const int enableControls = tbSetting(testbed, "Enable joint controls", 1, 0, 1, 1); + tbLiveSetting(testbed, "Actuator strength", 1, .02, 2, 0); + const int enableSprings = tbSetting(testbed, "Enable joint springs", 1, 0, 1, 1); + /* Reserve the keyframe slot above the scene list. */ + tbSetting(testbed, "Keyframe", 0, 0, 0, 1); + size_t selected = tbChoice(testbed, "Scene", defaultScene, (const char *const *)labels.names, + labels.count, 0, 0); + int sceneChanged = selected != lastFramedScene; + lastFramedScene = selected; + R3World *world = r3NewWorld(); + R3MjcfRobot *robot = NULL; + R3MjcfRobotHandles *handles = NULL; + size_t keyCount = 0, actuatorCount = 0; + R3Real **controls = NULL; + size_t *controlCounts = NULL; + if (!scenes.count) { + tbLabel(testbed, "NO MODEL FOUND", + "Set RAPIER_MENAGERIE_DIR to your google-deepmind/mujoco_menagerie checkout."); + } else { + R3MjcfLoaderOptions options = loaderOptions(testbed); + robot = r3MjcfRobotFromFile(scenes.names[selected], &options); + mergeSiblingKeyframes(robot, scenes.names[selected], &options); + if (disableCollisions) { + size_t bodyCount = r3MjcfRobot_BodyCount(robot); + for (size_t i = 0; i < bodyCount; ++i) { + size_t colliderCount = r3MjcfRobot_BodyColliderCount(robot, i); + for (size_t j = 0; j < colliderCount; ++j) { + r3MjcfRobot_SetBodyColliderCollisionGroups(robot, i, j, + (R3InteractionGroups){1, 2, 0}); + } + } + } + R3Vector gravity = r3MjcfRobot_Gravity(robot); + r3SetGravity(world, r3Vector(0, 0, -r3VectorLength(gravity))); + uint8_t flags = disableCollisions ? R3_MULTIBODY_DISABLE_SELF_CONTACTS : 0; + if (!enableSprings) { + flags |= R3_MULTIBODY_SKIP_JOINT_SPRINGS; + } + keyCount = r3MjcfRobot_KeyframeCount(robot); + addFilename(&keyNames, "(none)"); + size_t defaultKey = keyCount ? 1 : 0; + for (size_t i = 0; i < keyCount; ++i) { + char *name = keyframeName(robot, i); + char fallback[64]; + snprintf(fallback, sizeof(fallback), "key %zu", i); + addFilename(&keyNames, name[0] ? name : fallback); + if (!strcmp(name, "home")) { + defaultKey = i + 1; + } + free(name); + } + size_t key = tbChoice(testbed, "Keyframe", defaultKey, (const char *const *)keyNames.names, + keyNames.count, useMultibody && enableControls, sceneChanged); + if (useMultibody) { + handles = r3MjcfRobot_InsertUsingMultibodyJoints(world, robot, flags); + } else { + handles = r3MjcfRobot_InsertUsingImpulseJoints(world, robot); + } + if (key) { + r3MjcfRobotHandles_ApplyKeyframe(handles, robot, key - 1); + } + addFloor(world); + if (renderVisualMeshes) { + registerVisualMeshes(testbed, robot, handles, renderVisualPrimitives); + } + if (useMultibody && enableControls) { + actuatorCount = r3MjcfRobotHandles_ActuatorCount(handles); + controls = calloc(keyCount + 1, sizeof(*controls)); + controlCounts = calloc(keyCount + 1, sizeof(*controlCounts)); + if (!controls || !controlCounts) { + abort(); + } + controls[0] = calloc(actuatorCount ? actuatorCount : 1, sizeof(R3Real)); + controlCounts[0] = actuatorCount; + if (!controls[0]) { + abort(); + } + for (size_t i = 0; i < keyCount; ++i) { + controlCounts[i + 1] = r3MjcfRobot_KeyframeControls(robot, i, NULL, 0); + size_t count = controlCounts[i + 1]; + controls[i + 1] = calloc(count ? count : 1, sizeof(R3Real)); + if (!controls[i + 1]) { + abort(); + } + controlCounts[i + 1] = + r3MjcfRobot_KeyframeControls(robot, i, controls[i + 1], count); + } + } + } + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; /* Source robot and actuator handles are scene-local. */ + testbed->collidersVisible = renderColliders; + testbed->up[1] = 0; + testbed->up[2] = 1; + testbed->frameAll = sceneChanged; + testbed->preserveCamera = !sceneChanged; + tbCamera(testbed, 2, 2, 2, 0, 0, .5); + if (!useMultibody) { + r3SetTimeStep(testbed->world, 1.0 / 240.0); + r3SetNumSolverIterations(testbed->world, 12); + } else { + r3SetNumInternalPgsIterations(testbed->world, 4); + } + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + if (controls) { + size_t key = tbChoice(testbed, "Keyframe", 0, (const char *const *)keyNames.names, + keyNames.count, 1, 0); + R3Real gain = tbLiveSetting(testbed, "Actuator strength", 1, .02, 2, 0); + r3MjcfRobotHandles_ApplyControlsScaled(handles, controls[key], + controlCounts[key], gain); + } + } + } + if (controls) { + for (size_t i = 0; i <= keyCount; ++i) { + free(controls[i]); + } + } + free(controls); + free(controlCounts); + r3FreeMjcfRobotHandles(handles); + r3FreeMjcfRobot(robot); + r3FreeWorld(world); + freeFilenames(&scenes); + freeFilenames(&labels); + freeFilenames(&keyNames); +} +#endif diff --git a/c/testbed/examples3d/newton_cradle3.c b/c/testbed/examples3d/newton_cradle3.c new file mode 100644 index 000000000..92f81bb92 --- /dev/null +++ b/c/testbed/examples3d/newton_cradle3.c @@ -0,0 +1,43 @@ +/* Port of examples3d/newton_cradle3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbNewtonCradle3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + for (int i = 0; i < 5; i++) { + R3RigidBodyHandle ground; + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(i * 1.01, 5, 0); + groundBody.canSleep = !testbed->noSleep; + ground = r3InsertRigidBody(world, &groundBody); + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(i * 1.01, 0, 0); + rigidBody.linvel = r3Vector(i == 4 ? 7 : 0, 0, 0); + R3ColliderDesc collider = r3BallColliderDesc(0.5); + collider.restitution = 1; + R3RigidBodyHandle handle; + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = 7; + joint.localFrame1.translation = r3Vector(0, 0, 0); + joint.localFrame2.translation = r3Vector(0, 5, 0); + r3InsertImpulseJoint(ground, handle, &joint); + } + /* Set up the viewer. */ + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/one_way_platforms3.c b/c/testbed/examples3d/one_way_platforms3.c new file mode 100644 index 000000000..aaee250f4 --- /dev/null +++ b/c/testbed/examples3d/one_way_platforms3.c @@ -0,0 +1,96 @@ +/* Port of examples3d/one_way_platforms3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +typedef struct OneWayPlatformHook { + R3ColliderHandle platform1, platform2; +} OneWayPlatformHook; + +static int sameCollider(R3ColliderHandle a, R3ColliderHandle b) { + return a.world == b.world && a.index == b.index && a.generation == b.generation; +} + +static void RAPIER_CALL modifySolverContacts(void *userData, const R3ReadContext *read, + R3ColliderHandle collider1, R3ColliderHandle collider2, + R3ContactModificationContext *context) { + (void)read; + const OneWayPlatformHook *hook = userData; + R3Vector allowedLocalN1 = r3Vector(0, 0, 0); + /* Flip the allowed normal when the platform is collider2. */ + if (sameCollider(collider1, hook->platform1)) { + allowedLocalN1 = r3Vector(0, 1, 0); + } else if (sameCollider(collider2, hook->platform1)) { + allowedLocalN1 = r3Vector(0, -1, 0); + } + if (sameCollider(collider1, hook->platform2)) { + allowedLocalN1 = r3Vector(0, -1, 0); + } else if (sameCollider(collider2, hook->platform2)) { + allowedLocalN1 = r3Vector(0, 1, 0); + } + r3ContactModificationContext_UpdateAsOnewayPlatform(context, allowedLocalN1, .1); + const R3Real tangentVelocity = + sameCollider(collider1, hook->platform1) || sameCollider(collider2, hook->platform2) ? -12 + : 12; + r3ContactModificationContext_SetTangentVelocity(context, r3Vector(0, 0, tangentVelocity)); +} + +void tbOneWayPlatforms3(Testbed *testbed) { + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(9, 0.5, 25)); + collider.position.translation = r3Vector(0, 2, 30); + collider.activeHooks = R3_MODIFY_SOLVER_CONTACTS; + R3RigidBodyHandle handle; + OneWayPlatformHook platformHook; + handle = r3InsertRigidBody(world, &rigidBody); + platformHook.platform1 = r3InsertCollider(handle, &collider); + collider = r3CuboidColliderDesc(r3Vector(9, 0.5, 25)); + collider.position.translation = r3Vector(0, -2, -30); + collider.activeHooks = R3_MODIFY_SOLVER_CONTACTS; + platformHook.platform2 = r3InsertCollider(handle, &collider); + R3PhysicsHooks physicsHooks = {0}; + physicsHooks.user_data = &platformHook; + physicsHooks.modify_solver_contacts_context = modifySolverContacts; + tbCamera(testbed, 100, 0, 0, 0, 0, 0); + + tbSetWorld(testbed, world); + size_t stepId = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, &physicsHooks, NULL); + ++stepId; + size_t bodyCount = r3RigidBodyCount(world); + /* Spawn cubes periodically and reverse gravity below the lower platform. */ + if (stepId % 200 == 0 && bodyCount <= 7) { + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 6, 20); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 2, 1.5)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + size_t activeCount = r3ActiveRigidBodies(world, NULL, 0); + R3RigidBodyHandle *active = malloc(activeCount * sizeof(*active)); + if (activeCount && !active) { + abort(); + } + activeCount = r3ActiveRigidBodies(world, active, activeCount); + for (size_t i = 0; i < activeCount; ++i) { + R3Vector position = r3RigidBody_Translation(active[i]); + if (position.y > 1) { + r3RigidBody_SetGravityScale(active[i], 1, 0); + } else if (position.y < -1) { + r3RigidBody_SetGravityScale(active[i], -1, 0); + } + } + free(active); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/platform3.c b/c/testbed/examples3d/platform3.c new file mode 100644 index 000000000..3e64827b0 --- /dev/null +++ b/c/testbed/examples3d/platform3.c @@ -0,0 +1,72 @@ +/* Port of examples3d/platform3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbPlatform3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(10, 0.1, 10)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 6; j++) { + for (int k = 0; k < 6; k++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * 0.4 - 1.2, j * 0.4 + (j >= 3 ? 5 : 3), k * 0.4 - 1.2); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.2, 0.2, 0.2)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + R3RigidBodyHandle velocityBasedPlatformHandle = {0}; + R3RigidBodyHandle positionBasedPlatformHandle = {0}; + R3RigidBodyDesc platformBody = r3KinematicVelocityBasedRigidBodyDesc(); + platformBody.position.translation = r3Vector(0, 2.3, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(2, 0.2, 2)); + platformBody.canSleep = !testbed->noSleep; + velocityBasedPlatformHandle = r3InsertRigidBody(world, &platformBody); + r3InsertCollider(velocityBasedPlatformHandle, &boxCollider); + R3RigidBodyDesc positionBasedPlatform = r3KinematicPositionBasedRigidBodyDesc(); + positionBasedPlatform.position.translation = r3Vector(0, 5.3, 0); + R3ColliderDesc positionBasedCollider = r3CuboidColliderDesc(r3Vector(2, 0.2, 2)); + positionBasedPlatform.canSleep = !testbed->noSleep; + positionBasedPlatformHandle = r3InsertRigidBody(world, &positionBasedPlatform); + r3InsertCollider(positionBasedPlatformHandle, &positionBasedCollider); + /* Set up the viewer. */ + tbCamera(testbed, 10, 5, 10, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + + R3Real dt = r3TimeStep(world); + R3Real time = (R3Real)(testbed->step + 1) * dt; + R3Vector velocity = r3Vector(0, cos(time * 2), sin(time) * 2); + + r3RigidBody_SetLinvel(velocityBasedPlatformHandle, velocity, 1); + r3RigidBody_SetAngvel(velocityBasedPlatformHandle, r3Vector(0, 1, 0), 1); + + R3Vector position = r3RigidBody_Translation(positionBasedPlatformHandle); + r3RigidBody_SetNextKinematicTranslation( + positionBasedPlatformHandle, + r3VectorAdd(position, r3VectorScale(velocity, -dt))); + R3Rotation rotation = r3RigidBody_Rotation(positionBasedPlatformHandle); + r3RigidBody_SetNextKinematicRotation( + positionBasedPlatformHandle, + r3RotationMul(r3RotationFromAxisAngle(r3Vector(0, 1, 0), -0.5 * dt), rotation)); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/primitives3.c b/c/testbed/examples3d/primitives3.c new file mode 100644 index 000000000..c154c1ffe --- /dev/null +++ b/c/testbed/examples3d/primitives3.c @@ -0,0 +1,81 @@ +/* Port of examples3d/primitives3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbPrimitives3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + /* Ground. */ + const R3Real groundSize = 100.1; + const R3Real groundHeight = 2.1; + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0.0, -groundHeight, 0.0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(groundSize, groundHeight, groundSize)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + /* Create the primitives. */ + const int num = 8; + const R3Real rad = 1.0; + + const R3Real shiftx = rad * 2.0 + rad; + const R3Real shifty = rad * 2.0 + rad; + const R3Real shiftz = rad * 2.0 + rad; + const R3Real centerx = shiftx * (num / 2); + const R3Real centery = shifty / 2.0; + const R3Real centerz = shiftz * (num / 2); + + R3Real offset = -num * (rad * 2.0 + rad) * 0.5; + + for (int j = 0; j < 20; j++) { + for (int i = 0; i < num; i++) { + for (int k = 0; k < num; k++) { + const R3Real x = i * shiftx - centerx + offset; + const R3Real y = j * shifty + centery + 3.0; + const R3Real z = k * shiftz - centerz + offset; + + /* Build the rigid body. */ + rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x, y, z); + rigidBody.canSleep = !testbed->noSleep; + + switch (j % 5) { + case 1: + collider = r3BallColliderDesc(rad); + break; + case 2: + /* Rounded cylinders are faster even with a small rounding margin. */ + collider = r3RoundCylinderColliderDesc(rad, rad, rad / 10.0); + break; + case 3: + collider = r3ConeColliderDesc(rad, rad); + break; + default: + collider = r3CapsuleYColliderDesc(rad, rad); + break; + } + + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + + offset -= 0.05 * rad * (num - 1.0); + } + + /* Set up the viewer. */ + tbCamera(testbed, 100.0, 100.0, 100.0, 0.0, 0.0, 0.0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/restitution3.c b/c/testbed/examples3d/restitution3.c new file mode 100644 index 000000000..b02f0373b --- /dev/null +++ b/c/testbed/examples3d/restitution3.c @@ -0,0 +1,41 @@ +/* Port of examples3d/restitution3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbRestitution3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3ColliderDesc floor = r3CuboidColliderDesc(r3Vector(20, 1, 2)); + floor.restitution = 1; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &floor); + const int num = 10; + for (int j = 0; j < 2; j++) { + for (int i = 0; i <= num; i++) { + R3ColliderDesc collider = r3BallColliderDesc(0.5); + collider.restitution = (R3Real)i / num; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector((i - num / 2.0) * 2, 10 * (j + 1), 0); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera(testbed, 0, 3, 30, 0, 3, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/rope_joints3.c b/c/testbed/examples3d/rope_joints3.c new file mode 100644 index 000000000..7266dbc70 --- /dev/null +++ b/c/testbed/examples3d/rope_joints3.c @@ -0,0 +1,94 @@ +/* Port of examples3d/rope_joints3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#include "utils/character.h" + +void tbRopeJoints3(Testbed *testbed) { + R3World *world = r3NewWorld(); + + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.75, 0.1, 0.75)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-0.85, 0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.1, 0.1, 0.75)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0.85, 0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.1, 0.1, 0.75)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0.1, -0.85); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.75, 0.1, 0.1)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0.1, 0.85); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.75, 0.1, 0.1)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Manually controlled character, tethered to a ball. */ + R3RigidBodyHandle characterHandle; + { + R3RigidBodyDesc rigidBody = r3KinematicPositionBasedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0.3, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.15, 0.3, 0.15)); + characterHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(characterHandle, &collider); + } + tbBodyColor(testbed, characterHandle, 1, 131.0f / 255, 244.0f / 255, 1); + R3RigidBodyHandle childHandle; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(1, 1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3BallColliderDesc(.04); + childHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(childHandle, &collider); + } + { + R3JointDesc joint = r3RopeJointDesc(2); + r3InsertImpulseJoint(characterHandle, childHandle, &joint); + } + CharacterControlMode controlMode = CHARACTER_KINEMATIC; + R3KinematicCharacterController *controller = NULL; + R3PidController *pid = NULL; + controller = r3NewKinematicCharacterController(); + pid = r3NewPidController(); + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + updateCharacter(testbed, world, &controlMode, controller, pid, characterHandle); + } + } + r3FreePidController(pid); + r3FreeKinematicCharacterController(controller); + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/sensor3.c b/c/testbed/examples3d/sensor3.c new file mode 100644 index 000000000..8a8f306d4 --- /dev/null +++ b/c/testbed/examples3d/sensor3.c @@ -0,0 +1,84 @@ +/* Port of examples3d/sensor3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static int same(R3RigidBodyHandle handle, R3RigidBodyHandle handleB) { + return handle.world == handleB.world && handle.index == handleB.index && + handle.generation == handleB.generation; +} + +void tbSensor3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle ground = {0}; + R3RigidBodyHandle sensor = {0}; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(10.1, 0.1, 10.1)); + rigidBody.canSleep = !testbed->noSleep; + ground = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(ground, &boxCollider); + + for (int i = 0; i < 10; i++) { + for (int k = 0; k < 10; k++) { + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(i * 0.4 - 2, 3, k * 0.4 - 2); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.2, 0.2, 0.2)); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + + tbBodyColor(testbed, handle, 0.5, 0.5, 1, 1); + } + } + R3RigidBodyDesc dynamicBody = r3DynamicRigidBodyDesc(); + dynamicBody.position.translation = r3Vector(0, 5, 0); + R3ColliderDesc sensorBodyCollider = r3CuboidColliderDesc(r3Vector(0.2, 0.2, 0.2)); + dynamicBody.canSleep = !testbed->noSleep; + sensor = r3InsertRigidBody(world, &dynamicBody); + r3InsertCollider(sensor, &sensorBodyCollider); + + R3ColliderDesc collider = r3BallColliderDesc(1); + collider.density = 0; + collider.isSensor = 1; + collider.activeEvents = R3_COLLISION_EVENTS; + r3InsertCollider(sensor, &collider); + tbBodyColor(testbed, sensor, 0.5, 1, 1, 1); + /* Set up the viewer. */ + tbCamera(testbed, 6, 4, 6, 0, 1, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + R3EventCollector *eventHandler = r3NewEventCollector(); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3EventCollector_Clear(eventHandler); + r3Step(world, NULL, eventHandler); + + size_t n = r3EventCollector_CollisionEvents(eventHandler, NULL, 0); + R3CollisionEvent *events = calloc(n ? n : 1, sizeof(*events)); + if (!events) { + abort(); + } + n = r3EventCollector_CollisionEvents(eventHandler, events, n); + for (size_t i = 0; i < n; i++) { + R3ColliderHandle colliderHandles[] = {events[i].collider1, events[i].collider2}; + for (size_t j = 0; j < 2; j++) { + R3RigidBodyHandle handle = r3Collider_Parent(colliderHandles[j]); + if (!same(handle, ground) && !same(handle, sensor)) { + tbBodyColor(testbed, handle, events[i].started ? 1 : 0.5f, + events[i].started ? 1 : 0.5f, events[i].started ? 0 : 1, 1); + } + } + } + free(events); + } + } + r3FreeEventCollector(eventHandler); + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_bodies3.c b/c/testbed/examples3d/soft_bodies3.c new file mode 100644 index 000000000..752411808 --- /dev/null +++ b/c/testbed/examples3d/soft_bodies3.c @@ -0,0 +1,132 @@ +/* Port of examples3d/soft_bodies3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSoftBodies3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + /* Ground. */ + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(12, 0.1, 12)); + + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + + /* A cloth pinned by its four corners, with a box dropped on it. */ + const uint32_t n = 24; + const uint32_t pinnedParticles[] = {0, n - 1, n * (n - 1), n * n - 1}; + R3SoftBodyDesc cloth = r3DefaultSoftBodyDesc(); + cloth.kind = R3_SOFT_DESC_CLOTH; + cloth.a = r3Vector(-3.5, 2.5, -1.2); + cloth.du = r3Vector(0.1, 0.0, 0.0); + cloth.dv = r3Vector(0.0, 0.0, 0.1); + cloth.nx = n; + cloth.ny = n; + cloth.pinned = (R3IndexView){pinnedParticles, TB_COUNT(pinnedParticles)}; + cloth.material.edgeSoftness = cloth.material.bendSoftness = cloth.material.volumeSoftness = + cloth.material.shapeMatchingSoftness = (R3SpringCoefficients){30.0, 1.0}; + cloth.particleMass = 0.05; + cloth.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &cloth); + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-2.35, 4, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.3, 0.3, 0.3)); + collider.density = 0.5; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + + /* A balloon: hollow sphere with pressure (global volume preservation). */ + R3SoftBodyDesc balloon = r3SphereSoftBodyDesc(r3Vector(0.5, 3.0, 0.0), 0.8, 2); + balloon.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){15.0, 1.0}); + balloon.volumeFactor = 1.2; + balloon.particleMass = 0.05; + balloon.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &balloon); + + /* Jelly bodies: corotational, Neo-Hookean, and per-cell volume constraints. */ + { + R3SoftBodyDesc jelly = + r3CuboidSoftBodyDesc(r3Vector(3, 1, 1.5), r3Vector(0.6, 0.6, 0.6), 5, 5, 5); + jelly.cellModel = R3_SOFT_CELL_COROTATIONAL; + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = 2.0e3; + material.poissonRatio = 0.35; + material.elasticDampingRatio = 0.5; + jelly.material = material; + jelly.particleMass = 0.2; + jelly.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &jelly); + } + { + R3SoftBodyDesc jelly = + r3CuboidSoftBodyDesc(r3Vector(3, 1, 4.5), r3Vector(0.6, 0.6, 0.6), 5, 5, 5); + jelly.cellModel = R3_SOFT_CELL_NEO_HOOKEAN; + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = 2.0e3; + material.poissonRatio = 0.35; + material.elasticDampingRatio = 0.5; + jelly.material = material; + jelly.particleMass = 0.2; + jelly.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &jelly); + } + { + R3SoftBodyDesc jelly = + r3CuboidSoftBodyDesc(r3Vector(3, 1, -1.5), r3Vector(0.6, 0.6, 0.6), 5, 5, 5); + jelly.cellModel = R3_SOFT_CELL_VOLUME; + jelly.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){20.0, 1.0}); + jelly.particleMass = 0.2; + jelly.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &jelly); + } + + /* A rope hanging from a fixed anchor, holding a rigid weight. */ + const uint32_t pinnedParticle = 0; + R3SoftBodyDesc rope = r3DefaultSoftBodyDesc(); + rope.kind = R3_SOFT_DESC_ROPE; + rope.a = r3Vector(-0.5, 5, 3); + rope.b = r3Vector(2.5, 5, 3); + rope.nx = 30; + rope.pinned = (R3IndexView){&pinnedParticle, 1}; + rope.material.edgeSoftness = rope.material.bendSoftness = rope.material.volumeSoftness = + rope.material.shapeMatchingSoftness = (R3SpringCoefficients){40.0, 1.0}; + rope.particleMass = 0.05; + rope.canSleep = !testbed->noSleep; + R3SoftBodyHandle ropeHandle = r3InsertSoftBody(world, &rope); + + R3Vector lastPos = r3SoftBody_ParticlePosition(ropeHandle, 29); + R3RigidBodyHandle weight; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(lastPos, r3Vector(0, -0.3, 0)); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3BallColliderDesc(0.25); + collider.density = 2.0; + weight = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(weight, &collider); + } + + r3SoftBody_AttachParticle(ropeHandle, 29, weight); + + /* Set up the viewer. */ + tbCamera(testbed, 9.0, 6.0, 12.0, 0.0, 1.5, 0.0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_cloth3.c b/c/testbed/examples3d/soft_cloth3.c new file mode 100644 index 000000000..a7e083ac5 --- /dev/null +++ b/c/testbed/examples3d/soft_cloth3.c @@ -0,0 +1,88 @@ +/* Port of examples3d/soft_cloth3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSoftCloth3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(12, 0.1, 12)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* A sheet dropped over a ball and a box. */ + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-1, 1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3BallColliderDesc(1); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(1.5, 0.6, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.6, 0.6, 0.6)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + const size_t n = 40; + R3SoftBodyDesc sheet = r3ClothSoftBodyDesc(r3Vector(-3, 3, -2), r3Vector(0.1, 0, 0), + r3Vector(0, 0, 0.1), n + 20, n); + sheet.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30, 1}); + sheet.particleMass = .02; + sheet.canSleep = !testbed->noSleep; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){30, 1.0}; + material.bendSoftness = (R3SpringCoefficients){30, 1.0}; + material.volumeSoftness = (R3SpringCoefficients){30, 1.0}; + material.shapeMatchingSoftness = (R3SpringCoefficients){30, 1.0}; + material.bendSoftness = (R3SpringCoefficients){3, 1}; + sheet.material = material; + } + { + R3ColliderDesc surface = r3BallColliderDesc(.05); + surface.friction = .8; + sheet.collider = surface; + } + r3InsertSoftBody(world, &sheet); + + /* Curtain pinned along its top edge, with a ball rolling into it. */ + uint32_t pinned[40]; + for (uint32_t i = 0; i < 40; ++i) { + pinned[i] = i * 30; + } + R3SoftBodyDesc curtain = + r3ClothSoftBodyDesc(r3Vector(-2, 4, 5), r3Vector(0.1, 0, 0), r3Vector(0, -0.1, 0), 40, 30); + r3SoftBodyDesc_SetPinnedParticles(&curtain, (R3IndexView){(const uint32_t *)pinned, 40}); + curtain.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30, 1}); + curtain.particleMass = .02; + curtain.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &curtain); + + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0.5, 9); + rigidBody.linvel = r3Vector(0, 0, -6); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3BallColliderDesc(.5); + collider.density = 3; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + tbCamera(testbed, 8, 6, 14, 0, 1.5, 2); + + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_cloth_stress3.c b/c/testbed/examples3d/soft_cloth_stress3.c new file mode 100644 index 000000000..2588d374b --- /dev/null +++ b/c/testbed/examples3d/soft_cloth_stress3.c @@ -0,0 +1,144 @@ +/* Port of examples3d/soft_cloth_stress3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static R3SoftBodyDesc cloth(R3Vector origin, R3Vector du, R3Vector dv, size_t nu, size_t nv) { + R3SoftBodyDesc builder = r3ClothSoftBodyDesc(origin, du, dv, nu, nv); + builder.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30, 1}); + builder.particleMass = .02; + builder.particleRadius = (R3OptionalReal){1, .05}; + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){30, 1.0}; + material.bendSoftness = (R3SpringCoefficients){30, 1.0}; + material.volumeSoftness = (R3SpringCoefficients){30, 1.0}; + material.shapeMatchingSoftness = (R3SpringCoefficients){30, 1.0}; + material.bendSoftness = (R3SpringCoefficients){3, 1}; + builder.material = material; + { + R3ColliderDesc surface = r3BallColliderDesc(.05); + surface.friction = .5; + builder.collider = surface; + } + return builder; +} + +void tbSoftClothStress3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.5, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(30, 0.5, 30)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Sheets slide down a slope into a stopper. */ + const R3Real slope = .5; + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-6, 2, 0); + rigidBody.position.rotation = r3RotationFromAxisAngle(r3Vector(0, 0, 1), -slope); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(5, 0.2, 4)); + collider.friction = .4; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-0.6, 0.6, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.2, 0.6, 4)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + const R3Rotation rot = r3RotationFromAxisAngle(r3Vector(0, 0, 1), -slope); + for (int k = 0; k < 6; ++k) { + const R3Vector origin = r3VectorAdd( + r3Vector(-6, 2, 0), + r3RotationTransformVector(rot, r3Vector(-3.5 + k * .3, .3 + k * .12, -1.5))); + { + R3SoftBodyDesc sheet = + cloth(origin, r3RotationTransformVector(rot, r3Vector(.15, 0, 0)), + r3Vector(0, 0, .15), 21, 21); + sheet.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &sheet); + } + } + /* Banner pinned along both ends. */ + const size_t nu = 31, nv = 13; + uint32_t right[13], bannerPinned[26]; + for (size_t j = 0; j < nv; ++j) { + right[j] = (uint32_t)((nu - 1) * nv + j); + bannerPinned[j] = (uint32_t)j; + bannerPinned[nv + j] = right[j]; + } + R3SoftBodyHandle banner; + { + R3SoftBodyDesc body = + cloth(r3Vector(2, 4, -4), r3Vector(.15, 0, 0), r3Vector(0, -.15, 0), nu, nv); + r3SoftBodyDesc_SetPinnedParticles(&body, (R3IndexView){(const uint32_t *)bannerPinned, 26}); + body.canSleep = !testbed->noSleep; + banner = r3InsertSoftBody(world, &body); + } + + R3Vector bannerRightRest[13]; + for (size_t j = 0; j < nv; ++j) { + bannerRightRest[j] = r3SoftBody_ParticlePosition(banner, right[j]); + } + /* Strip pinned top and bottom; its bottom clamp rotates. */ + const size_t su = 11, sv = 41; + uint32_t bottom[11], stripPinned[22]; + for (size_t i = 0; i < su; ++i) { + bottom[i] = (uint32_t)(i * sv + sv - 1); + stripPinned[i] = (uint32_t)(i * sv); + stripPinned[su + i] = bottom[i]; + } + R3SoftBodyHandle strip; + { + R3SoftBodyDesc body = + cloth(r3Vector(9, 6.5, -.75), r3Vector(0, 0, .15), r3Vector(0, -.15, 0), su, sv); + r3SoftBodyDesc_SetPinnedParticles(&body, (R3IndexView){(const uint32_t *)stripPinned, 22}); + body.selfContacts = 1; + body.canSleep = !testbed->noSleep; + strip = r3InsertSoftBody(world, &body); + } + const R3Vector stripAxis = r3Vector(9, 0, 0); + + R3Vector stripBottomRest[11]; + for (size_t i = 0; i < su; ++i) { + stripBottomRest[i] = r3SoftBody_ParticlePosition(strip, bottom[i]); + } + + tbCamera(testbed, 2, 8, 18, 1.5, 3, 0); + + tbSetWorld(testbed, world); + R3Real t = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + R3Real dt = r3TimeStep(world); + t += dt; + /* Stretch/release the banner and twist the strip. */ + const R3Real stretch = 1.5 * (1 - cos(.5 * t)); + + for (size_t j = 0; j < nv; ++j) { + r3SoftBody_SetParticleKinematicTarget( + banner, right[j], + r3VectorAdd(bannerRightRest[j], r3Vector(stretch, 0, 0))); + } + const R3Rotation twist = r3RotationFromAxisAngle(r3Vector(0, 1, 0), .8 * t); + + for (size_t i = 0; i < su; ++i) { + const R3Vector target = r3VectorAdd( + stripAxis, + r3RotationTransformVector(twist, r3VectorSub(stripBottomRest[i], stripAxis))); + r3SoftBody_SetParticleKinematicTarget(strip, bottom[i], target); + } + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_dress3.c b/c/testbed/examples3d/soft_dress3.c new file mode 100644 index 000000000..264e538d3 --- /dev/null +++ b/c/testbed/examples3d/soft_dress3.c @@ -0,0 +1,206 @@ +/* Port of examples3d/soft_dress3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +enum { + PELVIS, + TORSO, + HEAD, + UPPER_ARM_LEFT, + UPPER_ARM_RIGHT, + FOREARM_LEFT, + FOREARM_RIGHT, + THIGH_LEFT, + THIGH_RIGHT, + SHIN_LEFT, + SHIN_RIGHT, + NUM_PARTS +}; + +static const R3Real UPPER_ARM = .34, FOREARM = .32, THIGH = .5, SHIN = .5, TORSO_RADIUS = .15; + +static R3Pose limbPose(R3Vector joint, R3Vector dir, R3Real halfLength) { + dir = r3VectorNormalize(dir); + R3Vector axis = r3VectorCross(r3Vector(0, 1, 0), dir); + R3Rotation rotation; + if (r3VectorLength(axis) < 1e-7) { + rotation = r3RotationFromAxisAngle(r3Vector(1, 0, 0), dir.y < 0 ? R3_PI : 0); + } else { + rotation = r3RotationFromAxisAngle(axis, acos(fmax(-1, fmin(1, dir.y)))); + } + return r3Pose(r3VectorAdd(joint, r3VectorScale(dir, halfLength)), rotation); +} + +/* Procedural dance, matching frameAt in the Rust example. */ +static void frameAt(R3Real t, R3Pose poses[NUM_PARTS]) { + const R3Real beat = 2 * t, yaw = .7 * sin(.35 * t) + .25 * t; + const R3Real sway = .25 * sin(beat), bounce = .04 * fabs(sin(2 * beat)), roll = .12 * sin(beat); + const R3Rotation turn = r3RotationFromAxisAngle(r3Vector(0, 1, 0), yaw); + const R3Rotation hipsRot = + r3RotationMul(turn, r3RotationFromAxisAngle(r3Vector(0, 0, 1), roll)); + const R3Vector hips = r3VectorAdd(r3Vector(0, .95 + bounce, 0), + r3RotationTransformVector(turn, r3Vector(sway, 0, 0))); + const R3Vector up = r3RotationTransformVector(hipsRot, r3Vector(0, 1, 0)); + const R3Vector side = r3RotationTransformVector(hipsRot, r3Vector(1, 0, 0)); + const R3Vector forward = r3RotationTransformVector(hipsRot, r3Vector(0, 0, 1)); + const R3Rotation lean = r3RotationFromAxisAngle(forward, -.5 * roll); + const R3Rotation torsoRot = r3RotationMul(lean, hipsRot); + const R3Vector torsoCenter = r3VectorAdd(hips, r3VectorScale(up, .42)); + poses[PELVIS] = r3Pose(hips, hipsRot); + poses[TORSO] = r3Pose(torsoCenter, torsoRot); + const R3Vector neck = + r3VectorAdd(torsoCenter, r3RotationTransformVector(torsoRot, r3Vector(0, .28, 0))); + poses[HEAD] = r3Pose(r3VectorAdd(neck, r3VectorScale(up, .13)), torsoRot); + for (size_t i = 0; i < 2; ++i) { + const R3Real s = i == 0 ? 1 : -1; + const R3Vector hip = + r3VectorSub(r3VectorAdd(hips, r3VectorScale(side, .11 * s)), r3VectorScale(up, .05)); + const R3Real phase = i == 0 ? 0 : R3_PI; + const R3Real swing = .45 * sin(beat + phase); + const R3Vector thighDir = r3VectorNormalize(r3VectorAdd( + r3VectorAdd(r3VectorScale(up, -cos(swing)), r3VectorScale(forward, sin(swing))), + r3VectorScale(side, .05 * s))); + const R3Vector knee = r3VectorAdd(hip, r3VectorScale(thighDir, THIGH)); + const R3Real bend = .9 * fmax(swing, 0); + const R3Vector shinDir = r3VectorAdd(r3VectorScale(up, -cos(swing - bend)), + r3VectorScale(forward, sin(swing - bend))); + poses[THIGH_LEFT + i] = limbPose(hip, thighDir, THIGH * .5); + poses[SHIN_LEFT + i] = limbPose(knee, shinDir, SHIN * .5); + } + for (size_t i = 0; i < 2; ++i) { + const R3Real s = i == 0 ? 1 : -1; + const R3Vector shoulder = + r3VectorAdd(r3VectorSub(neck, r3VectorScale(up, .06)), + r3RotationTransformVector(torsoRot, r3Vector(.22 * s, 0, 0))); + const R3Real raise = .6 + .6 * sin(beat + (i == 0 ? 0 : 1.5)); + const R3Vector armDir = r3VectorNormalize(r3VectorAdd( + r3VectorSub(r3VectorScale(side, s * cos(raise)), r3VectorScale(up, sin(raise) * .6)), + r3VectorScale(forward, .2 * sin(.7 * beat)))); + const R3Vector elbow = r3VectorAdd(shoulder, r3VectorScale(armDir, UPPER_ARM)); + const R3Vector foreDir = r3VectorNormalize( + r3VectorAdd(r3VectorAdd(armDir, r3VectorScale(up, .9)), r3VectorScale(forward, .5))); + poses[UPPER_ARM_LEFT + i] = limbPose(shoulder, armDir, UPPER_ARM * .5); + poses[FOREARM_LEFT + i] = limbPose(elbow, foreDir, FOREARM * .5); + } +} + +typedef struct Pin { + R3SoftBodyHandle handle; + size_t particle, part; + R3Vector offset; +} Pin; + +static void piece(Testbed *testbed, R3World *world, R3SoftBodyDesc *tube, size_t numAround, + size_t part, int selfContacts, const float color[4], + const R3Pose frame0[NUM_PARTS], Pin *pins, size_t *pinCount) { + uint32_t *pinned = malloc(numAround * sizeof(*pinned)); + if (!pinned) { + abort(); + } + for (uint32_t i = 0; i < numAround; ++i) { + pinned[i] = i; + } + r3SoftBodyDesc_SetPinnedParticles(tube, (R3IndexView){(const uint32_t *)pinned, numAround}); + + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){80, 1.0}; + material.bendSoftness = (R3SpringCoefficients){80, 1.0}; + material.volumeSoftness = (R3SpringCoefficients){80, 1.0}; + material.shapeMatchingSoftness = (R3SpringCoefficients){80, 1.0}; + material.bendSoftness = (R3SpringCoefficients){3, 1}; + tube->material = material; + tube->particleMass = .01; + tube->particleRadius = (R3OptionalReal){1, .02}; + tube->selfContacts = selfContacts; + tube->canSleep = !testbed->noSleep; + { + R3ColliderDesc surface = r3BallColliderDesc(.02); + surface.friction = .5; + tube->collider = surface; + } + R3SoftBodyHandle handle = r3InsertSoftBody(world, tube); + free(pinned); + + R3RigidBodyHandle root = r3SoftBody_RootBody(handle); + tbBodyColor(testbed, root, color[0], color[1], color[2], color[3]); + const R3Pose inverse = r3PoseInverse(frame0[part]); + for (size_t k = 0; k < numAround; ++k) { + R3Vector position = r3SoftBody_ParticlePosition(handle, k); + pins[(*pinCount)++] = (Pin){handle, k, part, r3PoseTransformPoint(inverse, position)}; + } +} + +void tbSoftDress3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(10, .1, 10)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + R3Pose frame0[NUM_PARTS]; + frameAt(0, frame0); + R3ColliderDesc shapes[NUM_PARTS] = {0}; + shapes[PELVIS] = r3CapsuleXColliderDesc(.08, .13); + shapes[TORSO] = r3CapsuleYColliderDesc(.18, TORSO_RADIUS); + shapes[HEAD] = r3BallColliderDesc(.12); + shapes[UPPER_ARM_LEFT] = r3CapsuleYColliderDesc(UPPER_ARM * .5 - .03, .05); + shapes[UPPER_ARM_RIGHT] = r3CapsuleYColliderDesc(UPPER_ARM * .5 - .03, .05); + shapes[FOREARM_LEFT] = r3CapsuleYColliderDesc(FOREARM * .5 - .03, .045); + shapes[FOREARM_RIGHT] = r3CapsuleYColliderDesc(FOREARM * .5 - .03, .045); + shapes[THIGH_LEFT] = r3CapsuleYColliderDesc(THIGH * .5 - .05, .085); + shapes[THIGH_RIGHT] = r3CapsuleYColliderDesc(THIGH * .5 - .05, .085); + shapes[SHIN_LEFT] = r3CapsuleYColliderDesc(SHIN * .5 - .04, .065); + shapes[SHIN_RIGHT] = r3CapsuleYColliderDesc(SHIN * .5 - .04, .065); + R3RigidBodyHandle parts[NUM_PARTS]; + for (size_t i = 0; i < NUM_PARTS; ++i) { + R3RigidBodyDesc body = r3KinematicPositionBasedRigidBodyDesc(); + body.position = frame0[i]; + shapes[i].friction = .4; + parts[i] = r3InsertRigidBody(world, &body); + r3InsertCollider(parts[i], &shapes[i]); + + tbBodyColor(testbed, parts[i], .93, .8, .68, 1); + } + Pin pins[56 + 48]; + size_t pinCount = 0; + const R3Vector waist = r3PoseTransformPoint(frame0[PELVIS], r3Vector(0, .1, 0)); + const R3Vector hem = {waist.x, .25, waist.z}; + R3SoftBodyDesc skirt = r3ClothTubeSoftBodyDesc(waist, r3VectorSub(hem, waist), .2, .62, 56, 18); + const float red[] = {.75, .15, .3, 1}; + piece(testbed, world, &skirt, 56, PELVIS, 1, red, frame0, pins, &pinCount); + + const R3Vector shirtTop = r3PoseTransformPoint(frame0[TORSO], r3Vector(0, .26, 0)); + const R3Vector shirtBottom = r3PoseTransformPoint(frame0[TORSO], r3Vector(0, -.26, 0)); + R3SoftBodyDesc shirt = r3ClothTubeSoftBodyDesc(shirtTop, r3VectorSub(shirtBottom, shirtTop), + TORSO_RADIUS + .012, TORSO_RADIUS + .09, 48, 16); + const float white[] = {.95, .95, .9, 1}; + piece(testbed, world, &shirt, 48, TORSO, 0, white, frame0, pins, &pinCount); + + tbCamera(testbed, 3, 2, 4, 0, .9, 0); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + R3Real t = 0; + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + R3Real dt = r3TimeStep(world); + t += dt; + R3Pose frame[NUM_PARTS]; + frameAt(t, frame); + + for (size_t i = 0; i < NUM_PARTS; ++i) { + r3RigidBody_SetNextKinematicPosition(parts[i], frame[i]); + } + for (size_t i = 0; i < pinCount; ++i) { + r3SoftBody_SetParticleKinematicTarget( + pins[i].handle, pins[i].particle, + r3PoseTransformPoint(frame[pins[i].part], pins[i].offset)); + } + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_fem3.c b/c/testbed/examples3d/soft_fem3.c new file mode 100644 index 000000000..da9de8374 --- /dev/null +++ b/c/testbed/examples3d/soft_fem3.c @@ -0,0 +1,132 @@ +/* Port of examples3d/soft_fem3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#ifdef RAPIER_FEM + +void tbSoftFem3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(20, 0.1, 20)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Compare FEM (1) with the constraint solver (0). */ + const uint32_t solvers[] = {R3_SOFT_SOLVER_FEM, R3_SOFT_SOLVER_CONSTRAINTS}; + for (size_t row = 0; row < TB_COUNT(solvers); ++row) { + const R3Real z = row == 0 ? -3.5 : 3.5; + /* Cantilever bolted to a wall. */ + R3SoftBodyDesc beam = + r3CuboidSoftBodyDesc(r3Vector(-3.5, 3, z), r3Vector(1.5, 0.15, 0.15), 13, 3, 3); + beam.cellModel = R3_SOFT_CELL_COROTATIONAL; + beam.totalMass = (R3OptionalReal){1, 12.0}; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = 2.0e6; + material.poissonRatio = .3; + material.elasticDampingRatio = 1; + beam.material = material; + } + beam.canSleep = !testbed->noSleep; + uint32_t *beamPins = NULL; + { + size_t count = r3SoftBodyDesc_ParticlePositions(&beam, NULL, 0); + R3Vector *positions = malloc(count * sizeof(*positions)); + beamPins = malloc(count * sizeof(*beamPins)); + if (!positions || !beamPins) { + abort(); + } + count = r3SoftBodyDesc_ParticlePositions(&beam, positions, count); + size_t beamPinsCount = 0; + for (size_t i = 0; i < count; ++i) { + if (positions[i].x < -5.0 + 1.0e-4) { + beamPins[beamPinsCount++] = (uint32_t)i; + } + } + r3SoftBodyDesc_SetPinnedParticles( + &beam, (R3IndexView){(const uint32_t *)beamPins, beamPinsCount}); + + free(positions); + } + beam.solver = solvers[row]; + r3InsertSoftBody(world, &beam); + free(beamPins); + + /* Plank pinned at both ends. */ + R3SoftBodyDesc plank = + r3CuboidSoftBodyDesc(r3Vector(1, 2, z), r3Vector(2, 0.15, 0.6), 17, 3, 5); + plank.cellModel = R3_SOFT_CELL_COROTATIONAL; + plank.totalMass = (R3OptionalReal){1, 20.0}; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = 4.0e6; + material.poissonRatio = .3; + material.elasticDampingRatio = 1; + plank.material = material; + } + plank.canSleep = !testbed->noSleep; + uint32_t *plankPins = NULL; + { + size_t count = r3SoftBodyDesc_ParticlePositions(&plank, NULL, 0); + R3Vector *positions = malloc(count * sizeof(*positions)); + plankPins = malloc(count * sizeof(*plankPins)); + if (!positions || !plankPins) { + abort(); + } + count = r3SoftBodyDesc_ParticlePositions(&plank, positions, count); + size_t plankPinsCount = 0; + for (size_t i = 0; i < count; ++i) { + if (fabs(positions[i].x - 1.0) > 1.9) { + plankPins[plankPinsCount++] = (uint32_t)i; + } + } + r3SoftBodyDesc_SetPinnedParticles( + &plank, (R3IndexView){(const uint32_t *)plankPins, plankPinsCount}); + + free(positions); + } + plank.solver = solvers[row]; + r3InsertSoftBody(world, &plank); + free(plankPins); + + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(1, 4.5, z); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.35, 0.35, 0.35)); + collider.density = 30; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Neo-Hookean jelly. */ + R3SoftBodyDesc jelly = + r3CuboidSoftBodyDesc(r3Vector(6, 2, z), r3Vector(0.7, 0.7, 0.7), 5, 5, 5); + jelly.cellModel = R3_SOFT_CELL_NEO_HOOKEAN; + jelly.particleMass = .1; + jelly.solver = solvers[row]; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = 2.0e4; + material.poissonRatio = .4; + material.elasticDampingRatio = .5; + jelly.material = material; + } + jelly.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &jelly); + } + tbCamera(testbed, 2, 8, 16, .5, 2, 0); + + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} +#endif diff --git a/c/testbed/examples3d/soft_jelly3.c b/c/testbed/examples3d/soft_jelly3.c new file mode 100644 index 000000000..88c0aecf1 --- /dev/null +++ b/c/testbed/examples3d/soft_jelly3.c @@ -0,0 +1,183 @@ +/* Port of examples3d/soft_jelly3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static R3SoftBodyDesc *jelly(R3SoftBodyDesc *builder, R3Real young) { + builder->cellModel = R3_SOFT_CELL_COROTATIONAL; + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = young; + material.poissonRatio = .35; + material.elasticDampingRatio = .5; + builder->material = material; + builder->particleMass = .05; + { + R3ColliderDesc surface = r3BallColliderDesc(.1); + surface.friction = .7; + builder->collider = surface; + } + return builder; +} + +void tbSoftJelly3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(12, 0.1, 12)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + const R3Real walls[][4] = { + {-2.5, 0, .1, 2.5}, {2.5, 0, .1, 2.5}, {0, -2.5, 2.5, .1}, {0, 2.5, 2.5, .1}}; + for (size_t i = 0; i < TB_COUNT(walls); ++i) { + const R3Real dx = walls[i][0], dz = walls[i][1], hx = walls[i][2], hz = walls[i][3]; + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(dx + 5, 1, dz); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(hx, 1, hz)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + /* Elastic cubes, softer at the top. */ + const R3Real stiffness[] = {1.0e4, 4.0e3, 1.5e3}; + for (size_t i = 0; i < TB_COUNT(stiffness); ++i) { + R3SoftBodyDesc cube = + r3CuboidSoftBodyDesc(r3Vector(-4, .7 + 1.5 * i, 0), r3Vector(0.6, 0.6, 0.6), 5, 5, 5); + cube.cellModel = R3_SOFT_CELL_COROTATIONAL; + cube.particleMass = .1; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = stiffness[i]; + material.poissonRatio = .4; + material.elasticDampingRatio = .5; + cube.material = material; + } + cube.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &cube); + } + /* Balloons in the container. */ + for (int i = 0; i < 6; ++i) { + const R3Real x = 5 + (i % 3 - 1) * 1.2; + const R3Real z = (i / 3 - .5) * 1.2; + R3SoftBodyDesc balloon = r3SphereSoftBodyDesc(r3Vector(x, 2 + i * 1.5, z), .6, 2); + balloon.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){15, 1}); + balloon.volumeFactor = 1.1; + balloon.particleMass = .03; + balloon.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &balloon); + } + /* Shape-matched cube driven toward an animated pose. */ + R3SoftBodyHandle driven; + R3SoftBodyDesc cube = + r3CuboidSoftBodyDesc(r3Vector(0.45, 0.45, 0.45), r3Vector(0.45, 0.45, 0.45), 4, 4, 4); + cube.shapeMatching = (R3OptionalBool){1, 1}; + cube.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){15, 1}); + cube.particleRadius = (R3OptionalReal){1, .1}; + cube.particleMass = .2; + cube.gravityScale = 0; + cube.canSleep = 0; + driven = r3InsertSoftBody(world, &cube); + + /* Volumetric ball from its boundary mesh. */ + { + R3SharedShape *shape = r3BallSharedShape(.7); + R3TriMeshData *mesh = r3SharedShape_ToTrimesh(shape, 16, 16); + r3FreeSharedShape(shape); + size_t vertexCount, indexCount; + vertexCount = r3TriMeshData_Vertices(mesh, NULL, 0); + indexCount = r3TriMeshData_Indices(mesh, NULL, 0); + R3Vector *vertices = malloc(vertexCount * sizeof(*vertices)); + uint32_t *indices = malloc(indexCount * sizeof(*indices)); + if (!vertices || !indices) { + abort(); + } + vertexCount = r3TriMeshData_Vertices(mesh, vertices, vertexCount); + indexCount = r3TriMeshData_Indices(mesh, indices, indexCount); + for (size_t i = 0; i < vertexCount; ++i) { + vertices[i] = r3VectorAdd(vertices[i], r3Vector(-4, 3, -4)); + } + + R3VolumeMeshParameters ballMeshing = r3NewVolumeMeshParameters(.2); + R3SoftBodyDesc ball = r3VolumetricSoftBodyDesc( + (R3VectorView){vertices, vertexCount}, + (R3SurfaceElementView){(const R3Triangle *)indices, indexCount / 3}, ballMeshing); + { + jelly(&ball, 1.0e4); + ball.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &ball); + } + free(vertices); + free(indices); + r3FreeTriMeshData(mesh); + } + /* Volumetric capsule from its boundary mesh. */ + { + R3SharedShape *shape = r3CapsuleSharedShape(r3Vector(0, -.6, 0), r3Vector(0, .6, 0), .45); + R3TriMeshData *mesh = r3SharedShape_ToTrimesh(shape, 12, 12); + r3FreeSharedShape(shape); + size_t vertexCount, indexCount; + vertexCount = r3TriMeshData_Vertices(mesh, NULL, 0); + indexCount = r3TriMeshData_Indices(mesh, NULL, 0); + R3Vector *vertices = malloc(vertexCount * sizeof(*vertices)); + uint32_t *indices = malloc(indexCount * sizeof(*indices)); + if (!vertices || !indices) { + abort(); + } + vertexCount = r3TriMeshData_Vertices(mesh, vertices, vertexCount); + indexCount = r3TriMeshData_Indices(mesh, indices, indexCount); + for (size_t i = 0; i < vertexCount; ++i) { + vertices[i] = + r3VectorAdd(r3RotationTransformVector( + r3RotationFromAxisAngle(r3Vector(0, 0, 1), 1.2), vertices[i]), + r3Vector(-4, 6, -4)); + } + + R3VolumeMeshParameters capsuleMeshing = r3NewVolumeMeshParameters(.2); + R3SoftBodyDesc capsule = r3VolumetricSoftBodyDesc( + (R3VectorView){vertices, vertexCount}, + (R3SurfaceElementView){(const R3Triangle *)indices, indexCount / 3}, capsuleMeshing); + { + jelly(&capsule, 3.0e4); + capsule.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &capsule); + } + free(vertices); + free(indices); + r3FreeTriMeshData(mesh); + } + /* Boxes shoved by the driven cube. */ + for (int i = 0; i < 8; ++i) { + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-.5 + (i % 4) * .5, 0.25, 4 + (i / 4) * .5); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.2, 0.2, 0.2)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + + tbCamera(testbed, 8, 7, 14, 0, 1.5, 1); + + tbSetWorld(testbed, world); + R3Real t = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + R3Real dt = r3TimeStep(world); + t += dt; + const R3Vector center = r3Vector(2 * cos(t), .6, 4 + 2 * sin(t)); + const R3Rotation rotation = r3RotationFromAxisAngle(r3Vector(0, 1, 0), 2 * t); + const R3Pose target = r3Pose(center, rotation); + + r3SoftBody_SetClusterShapeMatchingTarget(driven, 0, &target); + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_joints3.c b/c/testbed/examples3d/soft_joints3.c new file mode 100644 index 000000000..f12eceacc --- /dev/null +++ b/c/testbed/examples3d/soft_joints3.c @@ -0,0 +1,396 @@ +/* Port of examples3d/soft_joints3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static R3SoftBodyDesc jelly(R3Vector center, R3Vector half, R3Real young) { + R3SoftBodyDesc builder = r3CuboidSoftBodyDesc(center, half, 4, 4, 4); + builder.cellModel = R3_SOFT_CELL_COROTATIONAL; + builder.particleMass = .08; + builder.particleRadius = (R3OptionalReal){1, .06}; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = young; + material.poissonRatio = .35; + material.elasticDampingRatio = .8; + builder.material = material; + } + { + R3ColliderDesc surface = r3BallColliderDesc(.06); + surface.friction = .6; + builder.collider = surface; + } + return builder; +} + +void tbSoftJoints3(Testbed *testbed) { + R3World *world = r3NewWorld(); + + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(18, 0.1, 18)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Revolute joint and velocity motor. */ + R3SoftBodyHandle spinner; + { + R3SoftBodyDesc builder = jelly(r3Vector(-6, 1.6, -4), r3Vector(0.5, 0.5, 0.5), 8e3); + builder.canSleep = !testbed->noSleep; + spinner = r3InsertSoftBody(world, &builder); + } + R3RigidBodyHandle spinnerRoot = r3SoftBody_RootBody(spinner); + R3Vector com = r3SoftBody_CenterOfMass(spinner); + R3RigidBodyHandle pivot; + { + R3RigidBodyDesc builder = r3FixedRigidBodyDesc(); + builder.position.translation = com; + pivot = r3InsertRigidBody(world, &builder); + } + { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 1, 0)); + r3JointDesc_SetMotorVelocity(&joint, R3_AXIS_ANG_X, 1.5, 60); + r3InsertImpulseJoint(pivot, spinnerRoot, &joint); + } + /* A cloth corner follows a circling kinematic mover. */ + R3SoftBodyHandle cloth; + R3SoftBodyDesc clothBuilder = r3ClothSoftBodyDesc(r3Vector(0, 2.6, 2.5), r3Vector(0.16, 0, 0), + r3Vector(0, 0, 0.16), 12, 12); + clothBuilder.canSleep = !testbed->noSleep; + cloth = r3InsertSoftBody(world, &clothBuilder); + + const uint32_t cornerParticles[] = {0, 1, 12}; + uint32_t corner = r3SoftBody_AddCluster(cloth, cornerParticles, 3); + R3RigidBodyHandle cornerProxy = r3SoftBody_ClusterProxy(cloth, corner); + R3Vector cornerPos; + { cornerPos = r3RigidBody_Translation(cornerProxy); } + R3RigidBodyHandle mover; + { + R3RigidBodyDesc builder = r3KinematicPositionBasedRigidBodyDesc(); + builder.position.translation = cornerPos; + mover = r3InsertRigidBody(world, &builder); + } + { + R3JointDesc joint = r3SphericalJointDesc(); + r3InsertImpulseJoint(mover, cornerProxy, &joint); + } + /* Weld a rigid plate to the jelly's top cluster. */ + R3SoftBodyHandle wobbler; + { + R3SoftBodyDesc builder = jelly(r3Vector(-2.5, 0.61, -4), r3Vector(0.6, 0.6, 0.6), 2.5e3); + builder.canSleep = !testbed->noSleep; + wobbler = r3InsertSoftBody(world, &builder); + } + + R3Vector positions[64]; + size_t particleCount = + r3SoftBody_ParticlePositions(wobbler, positions, TB_COUNT(positions)); + R3Real maxY = 0; + for (size_t i = 0; i < particleCount; ++i) { + maxY = fmax(maxY, positions[i].y); + } + uint32_t top[16]; + size_t topCount = 0; + for (size_t i = 0; i < particleCount; ++i) { + if (fabs(positions[i].y - maxY) < 1e-3) { + top[topCount++] = (uint32_t)i; + } + } + uint32_t topCluster = r3SoftBody_AddCluster(wobbler, top, topCount); + R3RigidBodyHandle topProxy = r3SoftBody_ClusterProxy(wobbler, topCluster); + R3Vector topPos; + { topPos = r3RigidBody_Translation(topProxy); } + R3RigidBodyHandle plate; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(topPos, r3Vector(0, 0.12, 0)); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.7, 0.06, 0.7)); + collider.density = .4; + plate = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(plate, &collider); + } + { + R3JointDesc joint = r3FixedJointDesc(); + joint.localFrame1.translation = r3Vector(0, -0.12, 0); + r3InsertImpulseJoint(plate, topProxy, &joint); + } + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(topPos, r3Vector(0.3, 1.4, 0.2)); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.15, 0.15, 0.15)); + collider.density = 1.5; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Prismatic motor with two stops. */ + R3SoftBodyHandle shuttle; + { + R3SoftBodyDesc builder = jelly(r3Vector(2, 0.85, -4), r3Vector(0.4, 0.4, 0.4), 6e3); + builder.canSleep = !testbed->noSleep; + shuttle = r3InsertSoftBody(world, &builder); + } + R3RigidBodyHandle shuttleRoot = r3SoftBody_RootBody(shuttle); + R3Vector shuttleCom = r3SoftBody_CenterOfMass(shuttle); + R3RigidBodyHandle rail; + { + R3RigidBodyDesc builder = r3FixedRigidBodyDesc(); + builder.position.translation = shuttleCom; + rail = r3InsertRigidBody(world, &builder); + } + R3ImpulseJointHandle railJoint; + { + R3JointDesc joint = r3PrismaticJointDesc(r3Vector(1, 0, 0)); + r3JointDesc_SetLimits(&joint, R3_AXIS_LIN_X, -1.8, 1.8); + r3JointDesc_SetMotorPosition(&joint, R3_AXIS_LIN_X, 0, 40, 8); + railJoint = r3InsertImpulseJoint(rail, shuttleRoot, &joint); + } + /* Rope over a ledge. */ + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-1, 1, -9); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1.6, 1, 1.2)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + R3SoftBodyHandle anchorJelly; + { + R3SoftBodyDesc builder = jelly(r3Vector(-1, 2.6, -9), r3Vector(0.5, 0.5, 0.5), 1.2e4); + builder.canSleep = !testbed->noSleep; + anchorJelly = r3InsertSoftBody(world, &builder); + } + R3SoftBodyHandle hangingJelly; + { + R3SoftBodyDesc builder = jelly(r3Vector(1.6, 2.6, -9), r3Vector(0.5, 0.5, 0.5), 1.2e4); + builder.canSleep = !testbed->noSleep; + hangingJelly = r3InsertSoftBody(world, &builder); + } + R3RigidBodyHandle anchorRoot = r3SoftBody_RootBody(anchorJelly); + R3RigidBodyHandle hangingRoot = r3SoftBody_RootBody(hangingJelly); + { + R3JointDesc joint = r3RopeJointDesc(2.2); + r3InsertImpulseJoint(anchorRoot, hangingRoot, &joint); + } + /* Spring bungee under a gantry. */ + R3SoftBodyHandle bungee; + { + R3SoftBodyDesc builder = jelly(r3Vector(5.5, 3.2, 2.5), r3Vector(0.45, 0.45, 0.45), 6e3); + builder.canSleep = !testbed->noSleep; + bungee = r3InsertSoftBody(world, &builder); + } + R3RigidBodyHandle bungeeRoot = r3SoftBody_RootBody(bungee); + R3RigidBodyHandle gantry; + { + R3RigidBodyDesc builder = r3FixedRigidBodyDesc(); + builder.position.translation = r3Vector(5.5, 5.5, 2.5); + gantry = r3InsertRigidBody(world, &builder); + } + { + R3JointDesc joint = r3SpringJointDesc(1.2, 25, 1.5); + r3InsertImpulseJoint(gantry, bungeeRoot, &joint); + } + /* Bead on a visual-only pole. */ + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(8.5, 2.5, -4); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CylinderColliderDesc(2.5, .05); + collider.collisionGroups = (R3InteractionGroups){0, 0, 0}; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + R3SoftBodyHandle bead; + { + R3SoftBodyDesc builder = jelly(r3Vector(8.5, 4.2, -4), r3Vector(0.35, 0.35, 0.35), 8e3); + builder.canSleep = !testbed->noSleep; + bead = r3InsertSoftBody(world, &builder); + } + R3RigidBodyHandle beadRoot = r3SoftBody_RootBody(bead); + R3RigidBodyHandle pole; + { + R3RigidBodyDesc builder = r3FixedRigidBodyDesc(); + builder.position.translation = r3Vector(8.5, 2.5, -4); + pole = r3InsertRigidBody(world, &builder); + } + { + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = 1 | 4; + r3JointDesc_SetLocalAxis1(&joint, r3Vector(0, 1, 0)); + r3JointDesc_SetLocalAxis2(&joint, r3Vector(0, 1, 0)); + r3JointDesc_SetLimits(&joint, R3_AXIS_LIN_Y, -1.8, 1.8); + r3InsertImpulseJoint(pole, beadRoot, &joint); + } + /* Hinge two disjoint clusters of one soft bar. */ + R3SoftBodyHandle bar; + R3SoftBodyDesc barBuilder = + r3CuboidSoftBodyDesc(r3Vector(2.5, 2.2, 5.5), r3Vector(1, 0.22, 0.4), 7, 3, 3); + barBuilder.cellModel = R3_SOFT_CELL_COROTATIONAL; + barBuilder.particleMass = .08; + barBuilder.particleRadius = (R3OptionalReal){1, .06}; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = 2e4; + material.poissonRatio = .35; + material.elasticDampingRatio = 1; + barBuilder.material = material; + } + { + R3ColliderDesc surface = r3BallColliderDesc(.06); + surface.friction = .5; + barBuilder.collider = surface; + } + barBuilder.canSleep = !testbed->noSleep; + bar = r3InsertSoftBody(world, &barBuilder); + + R3Vector barCom = r3SoftBody_CenterOfMass(bar); + uint32_t leftHalf[64], rightHalf[64]; + size_t leftCount = 0, rightCount = 0; + particleCount = r3SoftBody_ParticlePositions(bar, positions, TB_COUNT(positions)); + for (size_t i = 0; i < particleCount; ++i) { + if (positions[i].x < barCom.x - 1e-3) { + leftHalf[leftCount++] = i; + } else if (positions[i].x > barCom.x + 1e-3) { + rightHalf[rightCount++] = i; + } + } + uint32_t leftCluster = r3SoftBody_AddCluster(bar, leftHalf, leftCount); + uint32_t rightCluster = r3SoftBody_AddCluster(bar, rightHalf, rightCount); + R3RigidBodyHandle leftProxy = r3SoftBody_ClusterProxy(bar, leftCluster); + R3RigidBodyHandle rightProxy = r3SoftBody_ClusterProxy(bar, rightCluster); + R3Vector leftPos; + { leftPos = r3RigidBody_Translation(leftProxy); } + R3Vector rightPos; + { rightPos = r3RigidBody_Translation(rightProxy); } + R3RigidBodyHandle barAnchor; + { + R3RigidBodyDesc builder = r3FixedRigidBodyDesc(); + builder.position.translation = leftPos; + barAnchor = r3InsertRigidBody(world, &builder); + } + { + R3JointDesc joint = r3FixedJointDesc(); + r3InsertImpulseJoint(barAnchor, leftProxy, &joint); + } + R3ImpulseJointHandle flapJoint; + { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + joint.localFrame1.translation = r3VectorSub(barCom, leftPos); + joint.localFrame2.translation = r3VectorSub(barCom, rightPos); + r3JointDesc_SetMotorPosition(&joint, R3_AXIS_ANG_X, 0, 80, 10); + flapJoint = r3InsertImpulseJoint(leftProxy, rightProxy, &joint); + } + /* Multibody arm and rope attached to one jelly. */ + R3RigidBodyHandle armRoot; + { + R3RigidBodyDesc builder = r3FixedRigidBodyDesc(); + builder.position.translation = r3Vector(7, 5, 6); + armRoot = r3InsertRigidBody(world, &builder); + } + R3RigidBodyHandle link1; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(8.2, 5, 6); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CapsuleXColliderDesc(.5, .08); + collider.density = 2; + link1 = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(link1, &collider); + } + { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + joint.localFrame1.translation = r3Vector(0, 0, 0); + joint.localFrame2.translation = r3Vector(-1.2, 0, 0); + r3InsertMultibodyJoint(armRoot, link1, &joint); + } + R3RigidBodyHandle link2; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(9.4, 5, 6); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CapsuleXColliderDesc(.5, .08); + collider.density = 2; + link2 = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(link2, &collider); + } + { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + joint.localFrame1.translation = r3Vector(0.6, 0, 0); + joint.localFrame2.translation = r3Vector(-0.6, 0, 0); + r3InsertMultibodyJoint(link1, link2, &joint); + } + R3SoftBodyHandle pendulum; + { + R3SoftBodyDesc builder = jelly(r3Vector(10.2, 4.2, 6), r3Vector(0.5, 0.5, 0.5), 5e3); + builder.canSleep = !testbed->noSleep; + pendulum = r3InsertSoftBody(world, &builder); + } + R3RigidBodyHandle pendulumRoot = r3SoftBody_RootBody(pendulum); + { + R3JointDesc joint = r3SphericalJointDesc(); + joint.localFrame1.translation = r3Vector(0.7, 0, 0); + joint.localFrame2.translation = r3Vector(0, 0.6, 0); + r3InsertImpulseJoint(link2, pendulumRoot, &joint); + } + R3RigidBodyHandle crateBody; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(10.2, 0.3, 6); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.3, 0.3, 0.3)); + collider.density = .5; + crateBody = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(crateBody, &collider); + } + { + R3JointDesc joint = r3RopeJointDesc(3.2); + r3InsertImpulseJoint(pendulumRoot, crateBody, &joint); + } + /* Wave a pinned cluster without a joint. */ + R3SoftBodyHandle banner; + R3SoftBodyDesc gripBuilder = r3ClothSoftBodyDesc(r3Vector(-8, 3.2, 4), r3Vector(0.18, 0, 0), + r3Vector(0, -0.18, 0), 14, 10); + gripBuilder.canSleep = !testbed->noSleep; + banner = r3InsertSoftBody(world, &gripBuilder); + + uint32_t gripParticles[14]; + for (uint32_t i = 0; i < 14; ++i) { + gripParticles[i] = i; + } + uint32_t grip = r3SoftBody_AddCluster(banner, gripParticles, 14); + R3RigidBodyHandle gripProxy = r3SoftBody_ClusterProxy(banner, grip); + r3SoftBody_SetClusterPinned(banner, grip, 1); + R3Vector gripHome; + { gripHome = r3RigidBody_Translation(gripProxy); } + tbCamera(testbed, 13, 9, 15, 0, 1.5, 0); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + R3Real t = 0; + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + R3Real dt = r3TimeStep(world); + t += dt; + + r3RigidBody_SetNextKinematicTranslation( + mover, + r3VectorAdd(cornerPos, r3Vector(1.2 * sin(.8 * t), .4 * sin(1.3 * t), + 1.2 * cos(.8 * t) - 1.2))); + + r3ImpulseJoint_SetMotorPosition(railJoint, R3_AXIS_LIN_X, 1.5 * sin(.6 * t), 40, + 8, 1); + + r3ImpulseJoint_SetMotorPosition(flapJoint, R3_AXIS_ANG_Z, .8 * sin(1.4 * t), 80, + 10, 1); + + r3SoftBody_SetClusterKinematicTarget( + banner, grip, + r3Pose(r3VectorAdd(gripHome, r3Vector(1.5 * sin(.7 * t), .2 * sin(1.9 * t), 0)), + r3RotationFromAxisAngle(r3Vector(1, 0, 0), .35 * sin(1.1 * t)))); + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_meshes3.c b/c/testbed/examples3d/soft_meshes3.c new file mode 100644 index 000000000..fa211af13 --- /dev/null +++ b/c/testbed/examples3d/soft_meshes3.c @@ -0,0 +1,275 @@ +/* Port of examples3d/soft_meshes3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +typedef struct Face { + uint32_t key[3], vertices[3]; + R3Vector centroid; +} Face; + +static int compareFaces(const void *a, const void *b) { + const Face *fa = a, *fb = b; + for (size_t i = 0; i < 3; ++i) { + if (fa->key[i] != fb->key[i]) { + return fa->key[i] < fb->key[i] ? -1 : 1; + } + } + return 0; +} + +/* Closed boundary of cells owned by a cluster, capped over the cut. */ +static void clusterSurface(R3SoftBodyHandle soft, const uint32_t *particles, + size_t count, R3Vector **vertices, uint32_t **indices, + size_t *triangleCount) { + size_t n, cellIndexCount; + n = r3SoftBody_NumParticles(soft); + uint32_t *vertexOf = malloc(n * sizeof(*vertexOf)); + *vertices = malloc(count * sizeof(**vertices)); + if (!vertexOf || !*vertices) { + abort(); + } + for (size_t i = 0; i < n; ++i) { + vertexOf[i] = UINT32_MAX; + } + for (size_t i = 0; i < count; ++i) { + vertexOf[particles[i]] = i; + (*vertices)[i] = r3SoftBody_ParticlePosition(soft, particles[i]); + } + cellIndexCount = r3SoftBody_Cells(soft, NULL, 0); + uint32_t *cells = malloc(cellIndexCount * sizeof(*cells)); + Face *faces = malloc(cellIndexCount * sizeof(*faces)); + if (!cells || !faces) { + abort(); + } + cellIndexCount = r3SoftBody_Cells(soft, cells, cellIndexCount); + size_t faceCount = 0; + for (size_t i = 0; i < cellIndexCount; i += 4) { + const uint32_t *cell = &cells[i]; + int owned = 1; + R3Vector centroid = {0}; + for (size_t j = 0; j < 4; ++j) { + if (vertexOf[cell[j]] == UINT32_MAX) { + owned = 0; + break; + } + centroid = r3VectorAdd(centroid, (*vertices)[vertexOf[cell[j]]]); + } + if (!owned) { + continue; + } + centroid = r3VectorScale(centroid, .25); + for (size_t k = 0; k < 4; ++k) { + Face *face = &faces[faceCount++]; + face->centroid = centroid; + for (size_t j = 0; j < 3; ++j) { + face->key[j] = face->vertices[j] = cell[(k + j + 1) % 4]; + } + for (size_t a = 0; a < 3; ++a) { + for (size_t b = a + 1; b < 3; ++b) { + if (face->key[a] > face->key[b]) { + uint32_t tmp = face->key[a]; + face->key[a] = face->key[b]; + face->key[b] = tmp; + } + } + } + } + } + qsort(faces, faceCount, sizeof(*faces), compareFaces); + *indices = malloc(faceCount * 3 * sizeof(**indices)); + if (faceCount && !*indices) { + abort(); + } + *triangleCount = 0; + for (size_t i = 0; i < faceCount;) { + size_t next = i + 1; + while (next < faceCount && !compareFaces(&faces[i], &faces[next])) { + ++next; + } + if (next == i + 1) { + const Face *face = &faces[i]; + uint32_t mapped[3]; + R3Vector p[3]; + for (size_t j = 0; j < 3; ++j) { + mapped[j] = vertexOf[face->vertices[j]]; + p[j] = (*vertices)[mapped[j]]; + } + const R3Vector normal = r3VectorCross(r3VectorSub(p[1], p[0]), r3VectorSub(p[2], p[0])); + const R3Vector center = + r3VectorScale(r3VectorAdd(r3VectorAdd(p[0], p[1]), p[2]), 1.0 / 3); + if (r3VectorDot(normal, r3VectorSub(center, face->centroid)) < 0) { + uint32_t tmp = mapped[1]; + mapped[1] = mapped[2]; + mapped[2] = tmp; + } + memcpy(&(*indices)[3 * (*triangleCount)++], mapped, sizeof(mapped)); + } + i = next; + } + free(faces); + free(cells); + free(vertexOf); +} + +static R3SoftBodyDesc jelly(R3Vector center, R3Real halfExtents, size_t n, R3Real young) { + R3SoftBodyDesc builder = + r3CuboidSoftBodyDesc(center, r3Vector(halfExtents, halfExtents, halfExtents), n, n, n); + builder.cellModel = R3_SOFT_CELL_COROTATIONAL; + builder.particleMass = .1; + builder.particleRadius = (R3OptionalReal){1, .05}; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = young; + material.poissonRatio = .4; + material.elasticDampingRatio = .5; + builder.material = material; + } + { + R3ColliderDesc surface = r3BallColliderDesc(.05); + surface.friction = .7; + builder.collider = surface; + } + return builder; +} + +void tbSoftMeshes3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(12, 0.1, 12)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + + /* Skinned jelly. */ + R3SoftBodyHandle skinned; + { + const R3Vector center = {-3, 1.2, 0}; + R3SoftBodyDesc builder = jelly(center, .6, 4, 200); + builder.canSleep = !testbed->noSleep; + skinned = r3InsertSoftBody(world, &builder); + + R3SharedShape *ball = NULL; + R3TriMeshData *mesh = NULL; + ball = r3BallSharedShape(1.2); + mesh = r3SharedShape_ToTrimesh(ball, 20, 20); + r3FreeSharedShape(ball); + size_t vertexCount, indexCount; + vertexCount = r3TriMeshData_Vertices(mesh, NULL, 0); + indexCount = r3TriMeshData_Indices(mesh, NULL, 0); + R3Vector *vertices = malloc(vertexCount * sizeof(*vertices)); + uint32_t *indices = malloc(indexCount * sizeof(*indices)); + if (!vertices || !indices) { + abort(); + } + vertexCount = r3TriMeshData_Vertices(mesh, vertices, vertexCount); + indexCount = r3TriMeshData_Indices(mesh, indices, indexCount); + r3FreeTriMeshData(mesh); + for (size_t i = 0; i < vertexCount; ++i) { + vertices[i] = r3VectorAdd(vertices[i], center); + } + R3RigidBodyHandle proxy = r3SoftBody_RootBody(skinned); + R3Pose pose = r3RigidBody_Position(proxy); + R3ColliderDesc collider = r3DefaultColliderDesc(); + r3ShapeDesc_SetTrimesh(&collider.shape, (R3VectorView){vertices, vertexCount}, + (R3TriangleView){(const R3Triangle *)indices, indexCount / 3}, + R3_TRIMESH_DEFORMABLE); + + collider.position = r3PoseInverse(pose); + collider.isSensor = 1; + R3SoftMeshBindingDesc binding = r3DefaultSoftMeshBindingDesc(); + r3InsertDeformableCollider(&collider, &binding, proxy); + free(vertices); + free(indices); + } + /* Plated jelly. */ + R3SoftBodyHandle plated; + { + const R3Vector center = {0, 1.2, 0}; + R3SoftBodyDesc builder = jelly(center, .6, 4, 400); + builder.canSleep = !testbed->noSleep; + plated = r3InsertSoftBody(world, &builder); + + R3Vector positions[64]; + size_t count = r3SoftBody_ParticlePositions(plated, positions, 64); + uint32_t top[64]; + size_t topCount = 0; + for (size_t i = 0; i < count; ++i) { + if (positions[i].y > center.y + .3) { + top[topCount++] = i; + } + } + uint32_t cluster = r3SoftBody_AddCluster(plated, top, topCount); + + R3RigidBodyHandle proxy = r3SoftBody_ClusterProxy(plated, cluster); + R3ColliderDesc plate = r3CuboidColliderDesc(r3Vector(.7, .05, .7)); + plate.position.translation = r3Vector(0, .65, 0); + r3InsertCollider(proxy, &plate); + } + /* Split jelly. */ + R3SoftBodyHandle split; + { + const R3Vector center = {3, 1.2, 0}; + R3SoftBodyDesc builder = jelly(center, .6, 4, 400); + builder.canSleep = !testbed->noSleep; + builder.collisionEnabled = 0; + split = r3InsertSoftBody(world, &builder); + + R3Vector positions[64]; + size_t count = r3SoftBody_ParticlePositions(split, positions, 64); + uint32_t halves[2][64]; + size_t counts[2] = {0}; + for (size_t i = 0; i < count; ++i) { + size_t side = positions[i].x < center.x ? 0 : 1; + halves[side][counts[side]++] = i; + } + for (size_t side = 0; side < 2; ++side) { + uint32_t cluster = r3SoftBody_AddCluster(split, halves[side], counts[side]); + + R3RigidBodyHandle proxy = r3SoftBody_ClusterProxy(split, cluster); + R3Vector *vertices; + uint32_t *indices; + size_t triangleCount; + clusterSurface(split, halves[side], counts[side], &vertices, &indices, + &triangleCount); + R3Pose pose = r3RigidBody_Position(proxy); + R3ColliderDesc collider = r3DefaultColliderDesc(); + r3ShapeDesc_SetTrimesh(&collider.shape, (R3VectorView){vertices, counts[side]}, + (R3TriangleView){(const R3Triangle *)indices, triangleCount}, + R3_TRIMESH_DEFORMABLE); + + collider.position = r3PoseInverse(pose); + collider.friction = .7; + R3SoftMeshBindingDesc binding = r3DefaultSoftMeshBindingDesc(); + binding.kind = R3_SOFT_BINDING_DIRECT; + binding.particles = (R3IndexView){halves[side], counts[side]}; + binding.selfContacts = 1; + r3InsertDeformableCollider(&collider, &binding, proxy); + free(vertices); + free(indices); + } + } + for (int i = 0; i < 3; ++i) { + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-3 + 3 * i, 4 + i, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.3, 0.3, 0.3)); + collider.density = 4; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + tbCamera(testbed, -6, 4, 8, 0, 1, 0); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_pile3.c b/c/testbed/examples3d/soft_pile3.c new file mode 100644 index 000000000..7fa18ce81 --- /dev/null +++ b/c/testbed/examples3d/soft_pile3.c @@ -0,0 +1,94 @@ +/* Port of examples3d/soft_pile3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSoftPile3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle floor; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.5, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(5.5, 0.5, 5.5)); + rigidBody.canSleep = !testbed->noSleep; + floor = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(floor, &boxCollider); + + tbBodyColor(testbed, floor, 0.6, 0.7, 1, 0.3); + const int dx[] = {1, -1, 0, 0}; + const int dz[] = {0, 0, 1, -1}; + for (int i = 0; i < 4; i++) { + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(dx[i] * 5.25, 4, dz[i] * 5.25); + R3ColliderDesc collider = + r3CuboidColliderDesc(r3Vector(dx[i] ? 0.25 : 5.5, 4, dx[i] ? 5.5 : 0.25)); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + + tbBodyColor(testbed, handle, 0.6, 0.7, 1, 0.3); + } + int k = 0; + for (int layer = 0; layer < 40; layer++) { + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++, k++) { + R3Vector position = r3Vector(-3 + i * 3 + layer % 2 * 0.7, 3 + layer * 2.5, + -3 + j * 3 + layer % 3 * 0.5); + if (k % 5 == 4) { + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.4, 0.4, 0.4)); + collider.density = 0.5; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = position; + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } else { + int large = k % 5 == 0 || k % 5 == 2; + R3Real radius = large ? 0.08 : 0.07; + R3SoftBodyDesc softBody = r3SphereSoftBodyDesc(position, large ? 0.6 : 0.45, 1); + softBody.material = + r3UniformSoftBodyMaterial((R3SpringCoefficients){large ? 15 : 10, 1}); + softBody.volumeFactor = large ? 1.15 : 1.3; + softBody.particleMass = 0.03; + softBody.particleRadius = (R3OptionalReal){1, radius}; + R3ColliderDesc surfaceCollider = r3BallColliderDesc(radius); + surfaceCollider.friction = 0.6; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); + } + } + } + } + for (int i = 0; i < 3; i++) { + R3SoftBodyDesc softBody = r3RopeSoftBodyDesc(r3Vector(-3.5, 24 + i, -2 + i * 2), + r3Vector(3.5, 24 + i, -1.5 + i * 2), 30); + softBody.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30, 1}); + softBody.particleMass = 0.03; + R3ColliderDesc surfaceCollider2 = r3BallColliderDesc(0.08); + surfaceCollider2.friction = 0.6; + softBody.collider = surfaceCollider2; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); + } + /* Set up the viewer. */ + tbCamera(testbed, 14, 12, 14, 0, 4, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_plasticity3.c b/c/testbed/examples3d/soft_plasticity3.c new file mode 100644 index 000000000..5817ca473 --- /dev/null +++ b/c/testbed/examples3d/soft_plasticity3.c @@ -0,0 +1,186 @@ +/* Port of examples3d/soft_plasticity3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static R3SoftBodyMaterial clay(R3Real young, R3Real plasticYield, R3Real plasticCreep) { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = young; + material.poissonRatio = .35; + material.elasticDampingRatio = 1; + material.plasticYield = plasticYield; + material.plasticCreep = plasticCreep; + material.deformationDamping = 4; + return material; +} + +void tbSoftPlasticity3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(14, 0.1, 14)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(9, 2, 5); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.2, 2, 3)); + collider.friction = .8; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Yield ladder: elastic to increasingly plastic, hit by identical balls. */ + const R3Real yields[] = {0, .2, .08, .02}; + for (size_t i = 0; i < TB_COUNT(yields); ++i) { + const R3Real x = -7 + i * 2.6; + R3SoftBodyDesc block = + r3CuboidSoftBodyDesc(r3Vector(x, 0.6, -4), r3Vector(0.6, 0.6, 0.6), 5, 5, 5); + block.cellModel = R3_SOFT_CELL_COROTATIONAL; + block.particleMass = .1; + block.canSleep = !testbed->noSleep; + { + R3SoftBodyMaterial material = clay(1.0e4, yields[i], 20); + block.material = material; + } + { + R3ColliderDesc surface = r3BallColliderDesc(.12); + surface.friction = .8; + block.collider = surface; + } + r3InsertSoftBody(world, &block); + + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x, 5, -4); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3BallColliderDesc(.4); + collider.density = 5; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + /* Clay slab stamped by a kinematic press. */ + R3SoftBodyDesc slab = + r3CuboidSoftBodyDesc(r3Vector(0, 0.4, 1.5), r3Vector(2.4, 0.4, 1.4), 13, 3, 8); + slab.cellModel = R3_SOFT_CELL_COROTATIONAL; + slab.particleMass = .1; + slab.canSleep = !testbed->noSleep; + { + R3SoftBodyMaterial material = clay(3.0e4, .02, 50); + slab.material = material; + } + { + R3ColliderDesc surface = r3BallColliderDesc(.15); + surface.friction = .8; + slab.collider = surface; + } + r3InsertSoftBody(world, &slab); + + const R3Vector pressRest = r3Vector(-1.5, 2.2, 1.5); + R3RigidBodyHandle press; + { + R3RigidBodyDesc rigidBody = r3KinematicPositionBasedRigidBodyDesc(); + rigidBody.position.translation = pressRest; + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.35, 0.35, 0.35)); + collider.position.rotation = r3RotationFromAxisAngle(r3Vector(0, 0, 1), R3_PI / 4); + collider.friction = .5; + press = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(press, &collider); + } + /* Elastic and creeping columns under their own weight. */ + for (int i = 0; i < 2; ++i) { + const R3Real x = -6 + i * 2; + R3SoftBodyDesc column = + r3CuboidSoftBodyDesc(r3Vector(x, 1.2, 5.5), r3Vector(0.3, 1.2, 0.3), 3, 12, 3); + column.cellModel = R3_SOFT_CELL_COROTATIONAL; + column.particleMass = .05; + column.canSleep = !testbed->noSleep; + { + R3SoftBodyMaterial material = clay(2.0e3, i == 0 ? 0 : .05, .5); + column.material = material; + } + { + R3ColliderDesc surface = r3BallColliderDesc(.1); + surface.friction = 1; + column.collider = surface; + } + r3InsertSoftBody(world, &column); + } + /* Volumetric clay balls thrown at the wall. */ + R3SharedShape *shape = r3BallSharedShape(.5); + R3TriMeshData *mesh = r3SharedShape_ToTrimesh(shape, 12, 12); + r3FreeSharedShape(shape); + size_t vertexCount, indexCount; + vertexCount = r3TriMeshData_Vertices(mesh, NULL, 0); + indexCount = r3TriMeshData_Indices(mesh, NULL, 0); + R3Vector *vertices = malloc(vertexCount * sizeof(*vertices)); + uint32_t *indices = malloc(indexCount * sizeof(*indices)); + if (!vertices || !indices) { + abort(); + } + indexCount = r3TriMeshData_Indices(mesh, indices, indexCount); + for (int i = 0; i < 3; ++i) { + const R3Vector center = r3Vector(4 - i * 1.5, 2 + i * .3, 3.5 + i * 1.5); + vertexCount = r3TriMeshData_Vertices(mesh, vertices, vertexCount); + for (size_t k = 0; k < vertexCount; ++k) { + vertices[k] = r3VectorAdd(vertices[k], center); + } + + R3VolumeMeshParameters ballMeshing = r3NewVolumeMeshParameters(.2); + R3SoftBodyDesc ball = r3VolumetricSoftBodyDesc( + (R3VectorView){vertices, vertexCount}, + (R3SurfaceElementView){(const R3Triangle *)indices, indexCount / 3}, ballMeshing); + { + ball.cellModel = R3_SOFT_CELL_COROTATIONAL; + R3SoftBodyMaterial material = clay(1.0e4, .03, 60); + ball.material = material; + ball.particleMass = .05; + ball.canSleep = !testbed->noSleep; + { + R3ColliderDesc surface = r3BallColliderDesc(.1); + surface.friction = .8; + ball.collider = surface; + } + R3SoftBodyHandle handle = r3InsertSoftBody(world, &ball); + + size_t count = r3SoftBody_NumParticles(handle); + for (size_t k = 0; k < count; ++k) { + r3SoftBody_SetParticleVelocity(handle, k, r3Vector(10, 1, 0)); + } + } + } + free(vertices); + free(indices); + r3FreeTriMeshData(mesh); + + tbCamera(testbed, 4, 9, 16, 0, .5, 0); + + tbSetWorld(testbed, world); + R3Real t = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + R3Real dt = r3TimeStep(world); + t += dt; + /* One second down, one second up, then move to the next stamp. */ + const R3Real period = 3; + const R3Real cycle = floor(t / period); + const R3Real phase = t - cycle * period; + const R3Real x = pressRest.x + fmod(cycle, 4); + const R3Real depth = 1; + const R3Real y = pressRest.y - (phase < 1 ? depth * phase + : phase < 2 ? depth * (2 - phase) + : 0); + + r3RigidBody_SetNextKinematicTranslation(press, r3Vector(x, y, pressRest.z)); + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_surface3.c b/c/testbed/examples3d/soft_surface3.c new file mode 100644 index 000000000..8dfdb9d8a --- /dev/null +++ b/c/testbed/examples3d/soft_surface3.c @@ -0,0 +1,154 @@ +/* Port of examples3d/soft_surface3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSoftSurface3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(40, 1, 40)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &boxCollider); + + uint32_t pins[76]; + size_t np = 0; + for (uint32_t i = 0; i < 400; i++) { + if (i / 20 == 0 || i / 20 == 19 || i % 20 == 0 || i % 20 == 19) { + pins[np++] = i; + } + } + R3SoftBodyDesc softBody = + r3ClothSoftBodyDesc(r3Vector(-2, 3, -2), r3Vector(0.2, 0, 0), r3Vector(0, 0, 0.2), 20, 20); + r3SoftBodyDesc_SetPinnedParticles(&softBody, (R3IndexView){(const uint32_t *)pins, np}); + softBody.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){40, 1}); + softBody.particleMass = 0.05; + softBody.particleRadius = (R3OptionalReal){1, 0.05}; + R3ColliderDesc surfaceCollider = r3BallColliderDesc(0.05); + surfaceCollider.friction = 0.6; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 6; j++) { + for (int h = 0; h < 3; h++) { + R3ColliderDesc collider = r3BallColliderDesc(0.08); + collider.density = 2; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-1.2 + i * 0.5 + h % 2 * 0.2, 5 + h * 0.6, + -1.2 + j * 0.5 + h % 2 * 0.2); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + for (int i = 0; i < 5; i++) { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-3 + i * 1.5, 0.5, 6); + R3ColliderDesc collider = r3CapsuleYColliderDesc(0.5, 0.08); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + R3RigidBodyDesc bar = r3FixedRigidBodyDesc(); + bar.position.translation = r3Vector(0, 1, 8); + bar.position = r3Pose(r3Vector(0, 1, 8), r3RotationFromAxisAngle(r3Vector(1, 0, 0), R3_PI / 4)); + R3ColliderDesc barCollider = r3CuboidColliderDesc(r3Vector(3.5, 0.4, 0.4)); + bar.canSleep = !testbed->noSleep; + rigidBodyHandle = r3InsertRigidBody(world, &bar); + r3InsertCollider(rigidBodyHandle, &barCollider); + + softBody = + r3ClothSoftBodyDesc(r3Vector(-4, 3, 5), r3Vector(0.2, 0, 0), r3Vector(0, 0, 0.2), 40, 25); + softBody.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30, 1}); + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){30, 1}; + material.volumeSoftness = (R3SpringCoefficients){30, 1}; + material.shapeMatchingSoftness = (R3SpringCoefficients){30, 1}; + material.bendSoftness = (R3SpringCoefficients){3, 1}; + softBody.material = material; + softBody.particleMass = 0.02; + softBody.particleRadius = (R3OptionalReal){1, 0.04}; + R3ColliderDesc drapedClothCollider = r3BallColliderDesc(0.04); + drapedClothCollider.friction = 0.8; + softBody.collider = drapedClothCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); + for (int i = 0; i < 3; i++) { + softBody = r3CuboidSoftBodyDesc(r3Vector(9, 0.8 + i * 1.7, 6), r3Vector(0.75, 0.75, 0.75), + 4, 4, 4); + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + softBody.cellModel = R3_SOFT_CELL_COROTATIONAL; + material.youngModulus = 6.0e3; + material.poissonRatio = 0.4; + material.elasticDampingRatio = 0.5; + softBody.material = material; + softBody.particleMass = 0.1; + softBody.particleRadius = (R3OptionalReal){1, 0.05}; + R3ColliderDesc jellySurfaceCollider = r3BallColliderDesc(0.05); + jellySurfaceCollider.friction = 0.8; + softBody.collider = jellySurfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); + } + R3ColliderDesc colliderC = r3CuboidColliderDesc(r3Vector(0.4, 0.4, 0.4)); + colliderC.density = 2; + R3RigidBodyDesc dynamicBody = r3DynamicRigidBodyDesc(); + dynamicBody.position.translation = r3Vector(9, 6.5, 6); + dynamicBody.canSleep = !testbed->noSleep; + rigidBodyHandle = r3InsertRigidBody(world, &dynamicBody); + r3InsertCollider(rigidBodyHandle, &colliderC); + + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(-9, 0.75, 6); + R3ColliderDesc obstacleCollider = r3CuboidColliderDesc(r3Vector(0.4, 0.75, 0.4)); + groundBody.canSleep = !testbed->noSleep; + rigidBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(rigidBodyHandle, &obstacleCollider); + + softBody = + r3ClothSoftBodyDesc(r3Vector(-13, 4, 5.6), r3Vector(0.1, 0, 0), r3Vector(0, 0, 0.1), 80, 8); + softBody.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30, 1}); + R3SoftBodyMaterial materialValue = r3DefaultSoftBodyMaterial(); + materialValue.edgeSoftness = (R3SpringCoefficients){30, 1}; + materialValue.volumeSoftness = (R3SpringCoefficients){30, 1}; + materialValue.shapeMatchingSoftness = (R3SpringCoefficients){30, 1}; + materialValue.bendSoftness = (R3SpringCoefficients){2, 1}; + softBody.material = materialValue; + softBody.selfContacts = 1; + softBody.particleMass = 0.02; + softBody.particleRadius = (R3OptionalReal){1, 0.05}; + R3ColliderDesc stripSurfaceCollider = r3BallColliderDesc(0.05); + stripSurfaceCollider.friction = 0.6; + softBody.collider = stripSurfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); + /* Set up the viewer. */ + tbCamera(testbed, 0, 12, 24, 0, 1.5, 3); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_tearing3.c b/c/testbed/examples3d/soft_tearing3.c new file mode 100644 index 000000000..86b349231 --- /dev/null +++ b/c/testbed/examples3d/soft_tearing3.c @@ -0,0 +1,217 @@ +/* Port of examples3d/soft_tearing3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +typedef struct DrivenParticle { + R3SoftBodyHandle body; + uint32_t index; + R3Vector rest; + int right; +} DrivenParticle; + +/* Follow driven particle indices through compaction and newly split bodies. */ +static void followTears(R3EventCollector *events, DrivenParticle *ends, size_t endCount) { + size_t count = r3EventCollector_TearEventCount(events); + for (size_t i = 0; i < count; ++i) { + R3SoftBodyTearEvent *event = r3EventCollector_TearEvent(events, i); + R3SoftBodyHandle origin = r3SoftBodyTearEvent_SoftBody(event); + for (size_t j = 0; j < endCount; ++j) { + if (ends[j].body.index == origin.index && + ends[j].body.generation == origin.generation) { + R3Bool found; + R3SoftBodyHandle destination; + uint32_t index; + R3OptionalParticleDestination softBodyTearEventTryParticleDestinationResult = + r3SoftBodyTearEvent_TryParticleDestination(event, ends[j].index); + destination = softBodyTearEventTryParticleDestinationResult.body; + index = softBodyTearEventTryParticleDestinationResult.index; + found = softBodyTearEventTryParticleDestinationResult.found; + if (found) { + ends[j].body = destination; + ends[j].index = index; + } + } + } + r3FreeSoftBodyTearEvent(event); + } +} + +void tbSoftTearing3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(30, 0.1, 30)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Sheet. */ + { + const size_t nu = 40, nv = 40; + uint32_t pinned[1600]; + size_t pinnedCount = 0; + for (size_t i = 0; i < nu; ++i) { + for (size_t j = 0; j < nv; ++j) { + if (i == 0 || j == 0 || i == nu - 1 || j == nv - 1) { + pinned[pinnedCount++] = (uint32_t)(i * nv + j); + } + } + } + R3SoftBodyDesc sheet = r3ClothSoftBodyDesc(r3Vector(-6, 3, -2), r3Vector(0.1, 0, 0), + r3Vector(0, 0, 0.1), nu, nv); + r3SoftBodyDesc_SetPinnedParticles(&sheet, + (R3IndexView){(const uint32_t *)pinned, pinnedCount}); + sheet.particleMass = .02; + sheet.canSleep = !testbed->noSleep; + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){100, 1.0}; + material.bendSoftness = (R3SpringCoefficients){100, 1.0}; + material.volumeSoftness = (R3SpringCoefficients){100, 1.0}; + material.shapeMatchingSoftness = (R3SpringCoefficients){100, 1.0}; + material.bendSoftness = (R3SpringCoefficients){3, 1}; + material.tearStrain = (R3OptionalReal){1, .4}; + sheet.material = material; + { + R3ColliderDesc surface = r3BallColliderDesc(.05); + surface.friction = .8; + sheet.collider = surface; + } + r3InsertSoftBody(world, &sheet); + } + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-4, 6, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3BallColliderDesc(.6); + collider.density = 30; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Curtain. */ + { + const size_t nu = 50, nv = 40; + uint32_t pinned[2000]; + size_t pinnedCount = 0; + for (size_t i = 0; i < nu; ++i) { + for (size_t j = 0; j < nv; ++j) { + if (j == 0) { + pinned[pinnedCount++] = (uint32_t)(i * nv + j); + } + } + } + R3SoftBodyDesc curtain = r3ClothSoftBodyDesc(r3Vector(0, 4.5, 4), r3Vector(0.1, 0, 0), + r3Vector(0, -0.1, 0), nu, nv); + r3SoftBodyDesc_SetPinnedParticles(&curtain, + (R3IndexView){(const uint32_t *)pinned, pinnedCount}); + curtain.particleMass = .02; + curtain.canSleep = !testbed->noSleep; + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){100, 1.0}; + material.bendSoftness = (R3SpringCoefficients){100, 1.0}; + material.volumeSoftness = (R3SpringCoefficients){100, 1.0}; + material.shapeMatchingSoftness = (R3SpringCoefficients){100, 1.0}; + material.bendSoftness = (R3SpringCoefficients){3, 1}; + material.tearStrain = (R3OptionalReal){1, .2}; + curtain.material = material; + r3InsertSoftBody(world, &curtain); + } + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(2.5, 2.5, 12); + rigidBody.linvel = r3Vector(0, 0, -25); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.3, 0.3, 0.3)); + collider.position.rotation = r3RotationFromAxisAngle(r3Vector(.5, .5, .5), sqrt(.75)); + collider.density = 20; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Jelly bar pulled apart by its two pinned ends. */ + R3SoftBodyHandle bar; + R3SoftBodyDesc builder = + r3CuboidSoftBodyDesc(r3Vector(3, 1, -4), r3Vector(2, 0.4, 0.4), 21, 5, 5); + builder.cellModel = R3_SOFT_CELL_COROTATIONAL; + builder.particleMass = .05; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = 5.0e4; + material.poissonRatio = .3; + material.elasticDampingRatio = 1; + material.tearStrain = (R3OptionalReal){1, .4}; + builder.material = material; + } + { + R3ColliderDesc surface = r3BallColliderDesc(.1); + surface.friction = .8; + builder.collider = surface; + } + builder.canSleep = !testbed->noSleep; + bar = r3InsertSoftBody(world, &builder); + + size_t count = r3SoftBody_NumParticles(bar); + DrivenParticle *ends = malloc(count * sizeof(*ends)); + if (!ends) { + abort(); + } + size_t endCount = 0; + for (size_t i = 0; i < count; ++i) { + R3Vector position = r3SoftBody_ParticlePosition(bar, i); + int left = position.x < 1.01; + int right = position.x > 4.99; + if (left || right) { + ends[endCount++] = (DrivenParticle){bar, (uint32_t)i, position, right}; + r3SoftBody_SetParticlePinned(bar, i, 1); + } + } + R3EventCollector *events = r3NewEventCollector(); + + tbCamera(testbed, 9, 8, 16, 0, 1.5, 1); + + tbSetWorld(testbed, world); + R3Real t = 0; + uint32_t previousMinPiece = 0; + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + const int useDefault = (int)tbLiveSetting(testbed, "Default minimum piece", 1, 0, 1, 1); + const uint32_t selected = + (uint32_t)tbLiveSetting(testbed, "Minimum piece (elements)", 6, 1, 40, 1); + const uint32_t minPiece = useDefault ? 0 : selected; + if (minPiece != previousMinPiece) { + size_t bodyCount = r3SoftBodyCount(world); + R3SoftBodyHandle *handles = malloc(bodyCount * sizeof(*handles)); + if (bodyCount && !handles) { + abort(); + } + bodyCount = r3SoftBodyHandles(world, handles, bodyCount); + for (size_t i = 0; i < bodyCount; ++i) { + R3SoftBodyMaterial material = r3SoftBody_Material(handles[i]); + material.minPiece = (R3OptionalU32){minPiece != 0, minPiece}; + r3SoftBody_SetMaterial(handles[i], &material); + } + free(handles); + previousMinPiece = minPiece; + } + if (tbSimulating(testbed)) { + R3Real dt = r3TimeStep(world); + t += dt; + /* Move the right clamp after one second, stopping at twice the bar's length. */ + const R3Real shift = fmin(fmax(t - 1, 0) * .5, 4); + for (size_t i = 0; i < endCount; ++i) { + if (ends[i].right) { + r3SoftBody_SetParticleKinematicTarget( + ends[i].body, ends[i].index, + r3VectorAdd(ends[i].rest, r3Vector(shift, 0, 0))); + } + } + r3EventCollector_Clear(events); + r3Step(world, NULL, events); + followTears(events, ends, endCount); + } + } + free(ends); + r3FreeEventCollector(events); + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_thin_features3.c b/c/testbed/examples3d/soft_thin_features3.c new file mode 100644 index 000000000..cadcbe20e --- /dev/null +++ b/c/testbed/examples3d/soft_thin_features3.c @@ -0,0 +1,240 @@ +/* Port of examples3d/soft_thin_features3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +static R3SoftBodyDesc jelly(R3Vector center, R3Real half, size_t n, R3Real young) { + R3SoftBodyDesc builder = r3CuboidSoftBodyDesc(center, r3Vector(half, half, half), n, n, n); + builder.cellModel = R3_SOFT_CELL_COROTATIONAL; + builder.particleMass = .1; + builder.particleRadius = (R3OptionalReal){1, .06}; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = young; + material.poissonRatio = .4; + material.elasticDampingRatio = .5; + builder.material = material; + } + { + R3ColliderDesc surface = r3BallColliderDesc(.06); + surface.friction = .7; + builder.collider = surface; + } + return builder; +} + +static R3SoftBodyDesc cloth(R3Vector origin, R3Vector du, R3Vector dv, size_t nu, size_t nv) { + R3SoftBodyDesc builder = r3ClothSoftBodyDesc(origin, du, dv, nu, nv); + builder.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30, 1}); + builder.particleMass = .02; + builder.particleRadius = (R3OptionalReal){1, .05}; + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){30, 1.0}; + material.bendSoftness = (R3SpringCoefficients){30, 1.0}; + material.volumeSoftness = (R3SpringCoefficients){30, 1.0}; + material.shapeMatchingSoftness = (R3SpringCoefficients){30, 1.0}; + material.bendSoftness = (R3SpringCoefficients){3, 1}; + builder.material = material; + { + R3ColliderDesc surface = r3BallColliderDesc(.05); + surface.friction = .6; + builder.collider = surface; + } + return builder; +} + +void tbSoftThinFeatures3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.5, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(30, 0.5, 30)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Bed of nails. */ + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 10; ++j) { + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-8 + i * .45, 0.6, -2 + j * .45); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CapsuleYColliderDesc(.6, .03); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + { + R3SoftBodyDesc body = jelly(r3Vector(-7, 3.5, -1), .75, 5, 2.0e4); + body.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &body); + } + { + R3SoftBodyDesc balloon = r3SphereSoftBodyDesc(r3Vector(-4.8, 3.5, 1.2), .7, 2); + balloon.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){15, 1}); + balloon.volumeFactor = 1.2; + balloon.particleMass = .03; + balloon.particleRadius = (R3OptionalReal){1, .06}; + { + R3ColliderDesc surface = r3BallColliderDesc(.06); + surface.friction = .6; + balloon.collider = surface; + } + balloon.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &balloon); + } + { + R3SoftBodyDesc sheet = + cloth(r3Vector(-8.5, 5.5, -2.5), r3Vector(.12, 0, 0), r3Vector(0, 0, .12), 30, 30); + sheet.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &sheet); + } + /* Knife edges. */ + for (int i = 0; i < 4; ++i) { + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-1.5, 0.75, -3 + i * .8); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1.2, 0.75, 0.015)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + { + R3SoftBodyDesc body = jelly(r3Vector(-1.5, 3, -1.8), .9, 5, 5.0e3); + body.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &body); + } + /* Wire grid. */ + for (int i = 0; i < 7; ++i) { + const R3Real t = -1.8 + i * .6; + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(t, 2.5, 5); + rigidBody.position.rotation = r3RotationFromAxisAngle(r3Vector(1, 0, 0), R3_PI / 2); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CapsuleYColliderDesc(1.8, .02); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 2.5, 5 + t); + rigidBody.position.rotation = r3RotationFromAxisAngle(r3Vector(0, 0, 1), R3_PI / 2); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CapsuleYColliderDesc(1.8, .02); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + { + R3SoftBodyDesc sheet = + cloth(r3Vector(-1.5, 4.5, 3.5), r3Vector(.12, 0, 0), r3Vector(0, 0, .12), 26, 26); + sheet.selfContacts = 1; + sheet.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &sheet); + } + /* Needle rain on a trampoline and a jelly block. */ + const size_t n = 26; + uint32_t pinned[26 * 26]; + size_t pinnedCount = 0; + for (size_t i = 0; i < n; ++i) { + for (size_t j = 0; j < n; ++j) { + if (i == 0 || j == 0 || i == n - 1 || j == n - 1) { + pinned[pinnedCount++] = (uint32_t)(i * n + j); + } + } + } + { + R3SoftBodyDesc trampoline = + cloth(r3Vector(3, 2.5, -3.5), r3Vector(.12, 0, 0), r3Vector(0, 0, .12), n, n); + r3SoftBodyDesc_SetPinnedParticles(&trampoline, + (R3IndexView){(const uint32_t *)pinned, pinnedCount}); + trampoline.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){40, 1}); + trampoline.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &trampoline); + } + { + R3SoftBodyDesc body = jelly(r3Vector(4.5, .9, 3), .9, 5, 3.0e4); + body.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &body); + } + const R3Real locations[][3] = {{4.5, -2, 6}, {4.5, 3, 5}}; + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 6; ++j) { + for (size_t k = 0; k < TB_COUNT(locations); ++k) { + const R3Real cx = locations[k][0], cz = locations[k][1], h = locations[k][2]; + const R3Vector rotation = r3Vector(.3 * i, 0, .2 * j); + const R3Real angle = sqrt(rotation.x * rotation.x + rotation.z * rotation.z); + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(cx - 1 + i * .4, h + (i + j) * .3, cz - 1 + j * .4); + rigidBody.position.rotation = r3RotationFromAxisAngle(rotation, angle); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CapsuleYColliderDesc(.5, .02); + collider.density = 3; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + } + /* Thin plates and a long rod. */ + { + R3SoftBodyDesc body = jelly(r3Vector(9, .9, -2), .9, 5, 1.0e4); + body.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &body); + } + for (int i = 0; i < 4; ++i) { + const R3Vector rotation = r3Vector(.1 * i, .5 * i, .05); + const R3Real angle = + sqrt(rotation.x * rotation.x + rotation.y * rotation.y + rotation.z * rotation.z); + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(9, 3.5 + i * .5, -2); + rigidBody.position.rotation = r3RotationFromAxisAngle(rotation, angle); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.8, 0.015, 0.8)); + collider.density = 1; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + { + R3SoftBodyDesc balloon = r3SphereSoftBodyDesc(r3Vector(9, 1, 3), .9, 2); + balloon.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){15, 1}); + balloon.volumeFactor = 1.2; + balloon.particleMass = .03; + balloon.particleRadius = (R3OptionalReal){1, .06}; + { + R3ColliderDesc surface = r3BallColliderDesc(.06); + surface.friction = .6; + balloon.collider = surface; + } + balloon.canSleep = !testbed->noSleep; + r3InsertSoftBody(world, &balloon); + } + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(9, 3.5, 3); + rigidBody.position.rotation = r3RotationFromAxisAngle(r3Vector(0, 0, 1), R3_PI / 2); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CapsuleYColliderDesc(2.5, .03); + collider.density = 2; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + tbCamera(testbed, 2, 10, 18, 1, 1.5, 1); + + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/soft_trimesh3.c b/c/testbed/examples3d/soft_trimesh3.c new file mode 100644 index 000000000..9215fc180 --- /dev/null +++ b/c/testbed/examples3d/soft_trimesh3.c @@ -0,0 +1,165 @@ +/* Port of examples3d/soft_trimesh3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#include "utils/obj.h" + +void tbSoftTrimesh3(Testbed *testbed) { + R3World *world = r3NewWorld(); + tbLabel(testbed, "Tetrahedra", "..."); + static const char *const modelNames[] = { + "All", "Camel", "Chair", "Cup", "Dinosaur", "Torus 2", "Feline", "Genus 3", + "Torus", "Octopus", "Rabbit", "Rust logo", "Screwdriver", "Table", "Torus 3", "Hornbug"}; + const int selectedModel = + (int)tbChoice(testbed, "Model", 0, modelNames, TB_COUNT(modelNames), 0, 0); + R3VolumeMeshParameters meshing = + r3NewVolumeMeshParameters(tbSetting(testbed, "Cell size", 2, .4, 4, 0)); + meshing.enclosure = (uint32_t)tbSetting(testbed, "Crust (surface shell only)", 0, 0, 1, 1); + meshing.cover_smoothing = (uint32_t)tbSetting(testbed, "Cover smoothing", 20, 0, 50, 1); + meshing.cover_guard = tbSetting(testbed, "Cover guard", .15, .02, .5, 0); + meshing.cover_subdivisions = (uint32_t)tbSetting(testbed, "Cover subdivision", 1, 0, 3, 1); + const int wearSkin = (int)tbSetting(testbed, "Wear the model as a skin", 1, 0, 1, 1); + const int skinCollision = wearSkin ? (int)tbSetting(testbed, "Skin collisions", 0, 0, 1, 1) : 0; +#ifdef RAPIER_FEM + const int femSolver = (int)tbSetting(testbed, "FEM solver", 0, 0, 1, 1); +#endif + const int cellModel = + (int)tbSetting(testbed, "Cell model: 0 Corotational, 1 Neo-Hookean, 2 Volume", 0, 0, 2, 1); + const R3Real youngModulus = + cellModel == 2 ? 1e5 : tbSetting(testbed, "Young modulus", 1e5, 1e4, 1e6, 0); + const int selfContacts = (int)tbSetting(testbed, "Self contacts", 0, 0, 1, 1); + /* Floor made of a wavy mesh. */ + R3Real heights[101 * 101]; + for (size_t j = 0; j <= 100; ++j) { + for (size_t i = 0; i <= 100; ++i) { + heights[i + j * 101] = -cos(i * .2) - cos(j * .2); + } + } + R3SharedShape *heightfield = r3HeightfieldSharedShape((R3RealView){heights, (101) * (101)}, 101, + 101, r3Vector(100, 2, 100)); + R3TriMeshData *floor = r3SharedShape_ToTrimesh(heightfield, 3, 2); + r3FreeSharedShape(heightfield); + size_t vertexCount = 0, indexCount = 0; + vertexCount = r3TriMeshData_Vertices(floor, NULL, 0); + indexCount = r3TriMeshData_Indices(floor, NULL, 0); + R3Vector *vertices = malloc(vertexCount * sizeof(*vertices)); + uint32_t *indices = malloc(indexCount * sizeof(*indices)); + if (!vertices || !indices) { + abort(); + } + vertexCount = r3TriMeshData_Vertices(floor, vertices, vertexCount); + indexCount = r3TriMeshData_Indices(floor, indices, indexCount); + R3ColliderDesc collider = r3DefaultColliderDesc(); + r3ShapeDesc_SetTrimesh(&collider.shape, (R3VectorView){vertices, vertexCount}, + (R3TriangleView){(const R3Triangle *)indices, indexCount / 3}, + R3_TRIMESH_FIX_INTERNAL_EDGES); + r3InsertColliderWithoutParent(world, &collider); + + free(vertices); + free(indices); + r3FreeTriMeshData(floor); + + const char *models[] = {"camel_decimated.obj", + "chair.obj", + "cup_decimated.obj", + "dilo_decimated.obj", + "tstTorusModel2.obj", + "feline_decimated.obj", + "genus3_decimated.obj", + "tstTorusModel.obj", + "octopus_decimated.obj", + "rabbit_decimated.obj", + "rust_logo_simplified.obj", + "screwdriver_decimated.obj", + "table.obj", + "tstTorusModel3.obj", + "hornbug.obj"}; + const size_t ngeoms = selectedModel == 0 ? TB_COUNT(models) : 1; + const size_t width = (size_t)fmax(ceil(sqrt(ngeoms)), 1); + size_t totalCells = 0, totalBodies = 0, igeom = 0; + for (size_t modelIndex = 0; modelIndex < TB_COUNT(models); ++modelIndex) { + if (selectedModel && modelIndex + 1 != (size_t)selectedModel) { + continue; + } + const size_t slot = igeom++; + ObjMesh mesh; + if (!loadObj(testbed->assetRoot, models[modelIndex], &mesh)) { + continue; + } + R3Vector mins = mesh.vertices[0], maxs = mins; + for (size_t i = 1; i < mesh.vertexCount; ++i) { + R3Vector p = mesh.vertices[i]; + mins = r3Vector(fmin(mins.x, p.x), fmin(mins.y, p.y), fmin(mins.z, p.z)); + maxs = r3Vector(fmax(maxs.x, p.x), fmax(maxs.y, p.y), fmax(maxs.z, p.z)); + } + const R3Vector center = r3VectorScale(r3VectorAdd(mins, maxs), .5); + const R3Real diag = r3VectorLength(r3VectorSub(maxs, mins)); + for (size_t i = 0; i < mesh.vertexCount; ++i) { + mesh.vertices[i] = r3VectorScale(r3VectorSub(mesh.vertices[i], center), 10 / diag); + } + R3SoftBodyDesc filled = r3VolumetricSoftBodyDesc( + (R3VectorView){mesh.vertices, mesh.vertexCount}, + (R3SurfaceElementView){(const R3Triangle *)mesh.indices, mesh.indexCount / 3}, meshing); + const R3Real x = (slot % width) * 9.0 - (width - 1) * 9.0 / 2; + const R3Real y = (slot / width) * 8.0 + 7; + if (wearSkin) { + r3SoftBodyDesc_SetSkin( + &filled, (R3VectorView){mesh.vertices, mesh.vertexCount}, + (R3SurfaceElementView){(const R3Triangle *)mesh.indices, mesh.indexCount / 3}); + filled.skinCollision = skinCollision; + } + filled.translation = r3Vector(x, y, 0); + filled.cellModel = cellModel == 1 ? R3_SOFT_CELL_NEO_HOOKEAN + : cellModel == 2 ? R3_SOFT_CELL_VOLUME + : R3_SOFT_CELL_COROTATIONAL; + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = youngModulus; + material.poissonRatio = .35; + material.elasticDampingRatio = .5; + material.deformationDamping = 2.5; + filled.material = material; + filled.particleMass = .05; + filled.particleRadius = + (R3OptionalReal){1, meshing.cell_size / (1u << meshing.cover_subdivisions) * .25}; + filled.selfContacts = selfContacts; + filled.canSleep = !testbed->noSleep; + { + R3ColliderDesc surface = r3BallColliderDesc(.1); + surface.friction = .6; + filled.collider = surface; + } +#ifdef RAPIER_FEM + filled.solver = femSolver ? 1 : 0; +#endif + R3SoftBodyHandle handle; + /* As in the Rust demo, skip models the volume mesher cannot fill. */ + R3ErrorHandler handler = r3SetErrorHandler((R3ErrorHandler){0}); + handle = r3InsertSoftBody(world, &filled); + R3Status status = r3LastStatus(); + r3SetErrorHandler(handler); + freeObj(&mesh); + if (status != R3_OK) { + continue; + } + + size_t cellIndices = r3SoftBody_Cells(handle, NULL, 0); + totalCells += cellIndices / 4; + ++totalBodies; + + R3RigidBodyHandle proxy = r3SoftBody_RootBody(handle); + tbBodyColor(testbed, proxy, .85, .35, .3, 1); + } + char label[128]; + snprintf(label, sizeof(label), "%zu in %zu bodies", totalCells, totalBodies); + tbLabel(testbed, "Tetrahedra", label); + tbCamera(testbed, 60, 40, 60, 0, 5, 0); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/spring_joints3.c b/c/testbed/examples3d/spring_joints3.c new file mode 100644 index 000000000..6271e9df6 --- /dev/null +++ b/c/testbed/examples3d/spring_joints3.c @@ -0,0 +1,52 @@ +/* Port of examples3d/spring_joints3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbSpringJoints3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle ground; + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, 0, 0); + groundBody.canSleep = !testbed->noSleep; + ground = r3InsertRigidBody(world, &groundBody); + R3Real damping = 2 * (R3Real)sqrt(1000 * 4 * R3_PI / 3 * 0.125); + for (int i = 0; i <= 30; i++) { + R3Real x = -6 + 1.5 * i; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x, 4.5, 0); + rigidBody.canSleep = 0; + R3RigidBodyHandle handle; + R3ColliderDesc ballCollider = r3BallColliderDesc(0.5); + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &ballCollider); + R3JointDesc joint = r3SpringJointDesc(0, 1000, i / 15.0 * damping); + joint.localFrame1.translation = r3Vector(x, 1.5, 0); + r3InsertImpulseJoint(ground, handle, &joint); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 0.5)); + collider.density = 100; + R3RigidBodyDesc dynamicBody = r3DynamicRigidBodyDesc(); + dynamicBody.position.translation = r3Vector(x, 9.5, 0); + dynamicBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle dynamicBodyHandle = r3InsertRigidBody(world, &dynamicBody); + r3InsertCollider(dynamicBodyHandle, &collider); + } + /* Set up the viewer. */ + tbCamera(testbed, 15, 5, 42, 13, 1, 1); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/balls3.c b/c/testbed/examples3d/stress_tests/balls3.c new file mode 100644 index 000000000..d92006185 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/balls3.c @@ -0,0 +1,35 @@ +/* Port of examples3d/stress_tests/balls3.rs. */ +#include "testbed.h" +#include "rapier_math.h" + +void tbStressTestsBalls3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + for (int i = 0; i < 20; i++) { + for (int j = 0; j < 20; j++) { + for (int k = 0; k < 20; k++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = j ? R3_DYNAMIC : R3_FIXED; + rigidBody.position.translation = r3Vector(i * 3 - 30.0, j * 3 + 1.5, k * 3 - 30.0); + R3ColliderDesc collider = r3BallColliderDesc(1); + collider.density = 0.477; + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/boxes3.c b/c/testbed/examples3d/stress_tests/boxes3.c new file mode 100644 index 000000000..99ec3d7ad --- /dev/null +++ b/c/testbed/examples3d/stress_tests/boxes3.c @@ -0,0 +1,42 @@ +/* Port of examples3d/stress_tests/boxes3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsBoxes3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(200.1, 0.1, 200.1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + R3Real offset = -10.0; + for (int j = 0; j < 10; j++, offset -= 0.45) { + for (int i = 0; i < 10; i++) { + for (int k = 0; k < 10; k++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * 2 - 10.0 + offset, j * 2 + 1, k * 2 - 10.0 + offset); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/capsules3.c b/c/testbed/examples3d/stress_tests/capsules3.c new file mode 100644 index 000000000..db455a7e9 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/capsules3.c @@ -0,0 +1,43 @@ +/* Port of examples3d/stress_tests/capsules3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsCapsules3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(200.1, 0.1, 200.1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + R3Real offset = -12.0; + for (int j = 0; j < 47; j++, offset -= 0.35) { + for (int i = 0; i < 8; i++) { + for (int k = 0; k < 8; k++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * 3 - 12.0 + offset, j * 4 + 4.5, k * 3 - 12.0 + offset); + R3ColliderDesc collider = r3CapsuleYColliderDesc(1, 1); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/ccd3.c b/c/testbed/examples3d/stress_tests/ccd3.c new file mode 100644 index 000000000..585c14c3d --- /dev/null +++ b/c/testbed/examples3d/stress_tests/ccd3.c @@ -0,0 +1,62 @@ +/* Port of examples3d/stress_tests/ccd3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsCcd3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(100.1, 0.1, 100.1)); + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &boxCollider); + + R3Real offset = -6; + for (int j = 0; j < 20; j++, offset -= 0.15) { + for (int i = 0; i < 4; i++) { + for (int k = 0; k < 4; k++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * 3 - 6 + offset, j * 3 + 4.5, k * 3 - 6 + offset); + rigidBody.linvel = r3Vector(0, -1000, 0); + rigidBody.ccdEnabled = 1; + R3ColliderDesc collider; + switch (j % 5) { + case 0: + collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + break; + case 1: + collider = r3BallColliderDesc(1); + break; + case 2: + collider = r3RoundCylinderColliderDesc(1, 1, 0.1); + break; + case 3: + collider = r3ConeColliderDesc(1, 1); + break; + default: + collider = r3CapsuleYColliderDesc(1, 1); + break; + } + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/compound3.c b/c/testbed/examples3d/stress_tests/compound3.c new file mode 100644 index 000000000..2397d0976 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/compound3.c @@ -0,0 +1,48 @@ +/* Port of examples3d/stress_tests/compound3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsCompound3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(200.1, 0.1, 200.1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &boxCollider); + R3Real offset = -2.4; + for (int j = 0; j < 25; j++, offset -= 0.07) { + for (int i = 0; i < 8; i++) { + for (int k = 0; k < 8; k++) { + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * 5 - 4 + offset, j * 5 + 3.5, k * 2 - 4 + offset); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(2, 0.2, 0.2)); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &boxCollider); + for (int side = -1; side <= 1; side += 2) { + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.2, 2, 0.2)); + collider.position.translation = r3Vector(side * 2, 2, 0); + r3InsertCollider(handle, &collider); + } + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/convex_polyhedron3.c b/c/testbed/examples3d/stress_tests/convex_polyhedron3.c new file mode 100644 index 000000000..056a5ee82 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/convex_polyhedron3.c @@ -0,0 +1,65 @@ +/* Port of examples3d/stress_tests/convex_polyhedron3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "example_math.h" + +static R3SharedShape *randomHull(Testbed *testbed) { + R3Vector points[10]; + for (size_t i = 0; i < 10; i++) { + R3Real x = exampleRandom(&testbed->randomState) * 2; + R3Real y = exampleRandom(&testbed->randomState) * 2; + R3Real z = exampleRandom(&testbed->randomState) * 2; + points[i] = r3Vector(x, y, z); + } + R3SharedShape *shape = r3RoundConvexHullSharedShape((R3VectorView){points, 10}, 0.1); + return shape; +} + +void tbStressTestsConvexPolyhedron3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(200.1, 0.1, 200.1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + R3SharedShape *shapes[5]; + for (int i = 0; i < 5; i++) { + shapes[i] = randomHull(testbed); + } + R3Real offset = -8.8; + for (int j = 0; j < 47; j++) { + for (int i = 0; i < 8; i++) { + for (int k = 0; k < 8; k++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * 2.2 - 8.8 + offset, j * 2.2 + 4.1, k * 2.2 - 8.8 + offset); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shapes[(i + k) % 5]; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + offset -= 0.35; + } + for (size_t i = 0; i < TB_COUNT(shapes); ++i) { + r3FreeSharedShape(shapes[i]); + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/heightfield3.c b/c/testbed/examples3d/stress_tests/heightfield3.c new file mode 100644 index 000000000..9d6db6106 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/heightfield3.c @@ -0,0 +1,59 @@ +/* Port of examples3d/stress_tests/heightfield3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsHeightfield3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3Real heights[441]; + for (int j = 0; j <= 20; j++) { + for (int i = 0; i <= 20; i++) { + heights[i + j * 21] = + i == 0 || i == 20 || j == 0 || j == 20 ? 10 : (R3Real)(sin(i * 10) + cos(j * 10)); + } + } + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 0, 0); + R3ColliderDesc groundCollider = r3DefaultColliderDesc(); + groundCollider.shape.kind = R3_SHAPE_DESC_HEIGHTFIELD; + groundCollider.shape.heights = (R3RealView){heights, (21) * (21)}; + groundCollider.shape.rows = 21; + groundCollider.shape.columns = 21; + groundCollider.shape.scale = r3Vector(200, 1, 200); + groundCollider.shape.flags = 0; + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &groundCollider); + + for (int j = 0; j < 47; j++) { + for (int i = 0; i < 8; i++) { + for (int k = 0; k < 8; k++) { + R3ColliderDesc collider; + if (j % 2) { + collider = r3BallColliderDesc(1); + } else { + collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + } + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(i * 3 - 12, j * 3 + 4.5, k * 3 - 12); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/joint_ball3.c b/c/testbed/examples3d/stress_tests/joint_ball3.c new file mode 100644 index 000000000..11d1261a4 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/joint_ball3.c @@ -0,0 +1,55 @@ +/* Port of examples3d/stress_tests/joint_ball3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsJointBall3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle *handles = calloc(1, 10000 * sizeof(*handles)); + if (!handles) { + abort(); + } + for (int k = 0; k < 100; k++) { + for (int i = 0; i < 100; i++) { + int fixed = i == 0 && (k % 4 == 0 || k == 99); + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = fixed ? R3_FIXED : R3_DYNAMIC; + rigidBody.position.translation = r3Vector(k, 0, i); + R3ColliderDesc collider = r3BallColliderDesc(0.4); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + if (i) { + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = R3_JOINT_SPHERICAL_AXES; + joint.localFrame1.translation = r3Vector(0, 0, 0); + joint.localFrame2.translation = r3Vector(0, 0, -1); + r3InsertImpulseJoint(handles[k * 100 + i - 1], handle, &joint); + } + if (k) { + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = R3_JOINT_SPHERICAL_AXES; + joint.localFrame1.translation = r3Vector(0, 0, 0); + joint.localFrame2.translation = r3Vector(-1, 0, 0); + r3InsertImpulseJoint(handles[k * 100 + i - 100], handle, &joint); + } + handles[k * 100 + i] = handle; + } + } + /* Set up the viewer. */ + tbCamera(testbed, -110, -46, 170, 54, -38, 29); + free(handles); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/joint_fixed3.c b/c/testbed/examples3d/stress_tests/joint_fixed3.c new file mode 100644 index 000000000..61ea63b3c --- /dev/null +++ b/c/testbed/examples3d/stress_tests/joint_fixed3.c @@ -0,0 +1,57 @@ +/* Port of examples3d/stress_tests/joint_fixed3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsJointFixed3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle handles[25]; + for (int m = 0; m < 10; m++) { + for (int l = 0; l < 10; l++) { + for (int j = 0; j < 5; j++) { + for (int k = 0; k < 5; k++) { + for (int i = 0; i < 5; i++) { + int fixed = i == 0 && ((k % 4 == 0 && k != 3) || k == 4); + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = fixed ? R3_FIXED : R3_DYNAMIC; + rigidBody.position.translation = r3Vector(j * 10 + k, l * 3, m * 7 + i); + R3ColliderDesc collider = r3BallColliderDesc(0.4); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + if (i) { + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = R3_JOINT_FIXED_AXES; + joint.localFrame1.translation = r3Vector(0, 0, 0); + joint.localFrame2.translation = r3Vector(0, 0, -1); + r3InsertImpulseJoint(handles[k * 5 + i - 1], handle, &joint); + } + if (k) { + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = R3_JOINT_FIXED_AXES; + joint.localFrame1.translation = r3Vector(0, 0, 0); + joint.localFrame2.translation = r3Vector(-1, 0, 0); + r3InsertImpulseJoint(handles[k * 5 + i - 5], handle, &joint); + } + handles[k * 5 + i] = handle; + } + } + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, -38, 14, 108, 46, 12, 23); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/joint_prismatic3.c b/c/testbed/examples3d/stress_tests/joint_prismatic3.c new file mode 100644 index 000000000..73f2b6a38 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/joint_prismatic3.c @@ -0,0 +1,53 @@ +/* Port of examples3d/stress_tests/joint_prismatic3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsJointPrismatic3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + for (int m = 0; m < 8; m++) { + for (int l = 0; l < 8; l++) { + for (int j = 0; j < 50; j++) { + R3Real x = j * 4; + R3Real y = l * 10; + R3Real z = m * 7; + R3RigidBodyHandle parent; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x, y, z); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.4, 0.4, 0.4)); + rigidBody.canSleep = !testbed->noSleep; + parent = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(parent, &collider); + for (int i = 0; i < 5; i++) { + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x, y, z + i + 1); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.4, 0.4, 0.4)); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + R3JointDesc joint = r3PrismaticJointDesc(r3Vector(i % 2 ? -1 : 1, 1, 0)); + joint.localFrame1.translation = r3Vector(0, 0, 0); + joint.localFrame2.translation = r3Vector(0, 0, -1); + r3JointDesc_SetLimits(&joint, R3_AXIS_LIN_X, -2, 0); + r3InsertImpulseJoint(parent, handle, &joint); + parent = handle; + } + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 262, 63, 124, 101, 4, -3); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/joint_revolute3.c b/c/testbed/examples3d/stress_tests/joint_revolute3.c new file mode 100644 index 000000000..76eec266d --- /dev/null +++ b/c/testbed/examples3d/stress_tests/joint_revolute3.c @@ -0,0 +1,60 @@ +/* Port of examples3d/stress_tests/joint_revolute3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsJointRevolute3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + for (int l = 0; l < 4; l++) { + for (int j = 0; j < 50; j++) { + R3Real x = j * 8; + R3Real y = l * 60; + R3RigidBodyHandle parent; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x, y, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.4, 0.4, 0.4)); + rigidBody.canSleep = !testbed->noSleep; + parent = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(parent, &collider); + for (int i = 0; i < 10; i++) { + R3Real z = i * 4 + 2; + R3Vector positions[] = {r3Vector(x, y, z), r3Vector(x + 2, y, z), + r3Vector(x + 2, y, z + 2), r3Vector(x, y, z + 2)}; + R3Vector axes[] = {r3Vector(0, 0, 1), r3Vector(1, 0, 0), r3Vector(0, 0, 1), + r3Vector(1, 0, 0)}; + R3Vector anchors[] = {r3Vector(0, 0, -2), r3Vector(-2, 0, 0), r3Vector(0, 0, -2), + r3Vector(2, 0, 0)}; + R3RigidBodyHandle handles[4]; + for (int k = 0; k < 4; k++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = positions[k]; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.4, 0.4, 0.4)); + rigidBody.canSleep = !testbed->noSleep; + handles[k] = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handles[k], &collider); + } + for (int k = 0; k < 4; k++) { + R3JointDesc joint = r3RevoluteJointDesc(axes[k]); + joint.localFrame1.translation = r3Vector(0, 0, 0); + joint.localFrame2.translation = anchors[k]; + r3InsertImpulseJoint(k ? handles[k - 1] : parent, handles[k], &joint); + } + parent = handles[3]; + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 478, 83, 228, 134, 83, -116); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/keva3.c b/c/testbed/examples3d/stress_tests/keva3.c new file mode 100644 index 000000000..4d6a8afbe --- /dev/null +++ b/c/testbed/examples3d/stress_tests/keva3.c @@ -0,0 +1,48 @@ +/* Port of examples3d/stress_tests/keva3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void buildBlock(Testbed *, R3World *, R3Vector, R3Vector, int, int, int); + +void tbStressTestsKeva3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + /* Ground. */ + const R3Real groundSize = 50.0; + const R3Real groundHeight = 0.1; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0.0, -groundHeight, 0.0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(groundSize, groundHeight, groundSize)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + /* Create the cubes. Odd layer counts keep adjacent blocks aligned. */ + const R3Vector halfExtents = r3Vector(0.1, 0.5, 2.0); + R3Real blockHeight = 0.0; + const int layers[] = {0, 13, 17, 21, 41, 83}; + + for (int i = 5; i >= 1; i--) { + const int numx = i * 2; + const int numy = layers[i]; + const int numz = numx * 3 + 1; + const R3Real blockWidth = numx * halfExtents.z * 2.0; + buildBlock(testbed, world, halfExtents, + r3Vector(-blockWidth / 2.0, blockHeight, -blockWidth / 2.0), numx, numy, numz); + blockHeight += numy * halfExtents.y * 2.0 + halfExtents.x * 2.0; + } + + /* Set up the viewer. */ + tbCamera(testbed, 100.0, 100.0, 100.0, 0.0, 0.0, 0.0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/many_kinematics3.c b/c/testbed/examples3d/stress_tests/many_kinematics3.c new file mode 100644 index 000000000..d93723cb6 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/many_kinematics3.c @@ -0,0 +1,62 @@ +/* Port of examples3d/stress_tests/many_kinematics3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "example_math.h" + +static R3Real bounce(R3Real p, R3Real v) { + return (v > 0 && p > 105) || (v < 0 && p < -105) ? -v : v; +} + +void tbStressTestsManyKinematics3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + for (int i = 0; i < 30; i++) { + for (int j = 0; j < 30; j++) { + for (int k = 0; k < 30; k++) { + R3RigidBodyDesc rigidBody = r3KinematicVelocityBasedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(i * 7 - 105, j * 7 - 105, k * 7 - 105); + R3Real vx = (exampleRandom(&testbed->randomState) - 0.5) * 30; + R3Real vy = (exampleRandom(&testbed->randomState) - 0.5) * 30; + R3Real vz = (exampleRandom(&testbed->randomState) - 0.5) * 30; + rigidBody.linvel = r3Vector(vx, vy, vz); + R3ColliderDesc collider = r3BallColliderDesc(1); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + testbed->snapshotSupported = 0; + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + + size_t n = r3RigidBodyHandles(world, NULL, 0); + R3RigidBodyHandle *handle = malloc(n * sizeof(*handle)); + if (!handle) { + abort(); + } + n = r3RigidBodyHandles(world, handle, n); + for (size_t i = 0; i < n; i++) { + R3Vector position; + R3Vector translation; + position = r3RigidBody_Translation(handle[i]); + translation = r3RigidBody_Linvel(handle[i]); + r3RigidBody_SetLinvel(handle[i], + r3Vector(bounce(position.x, translation.x), + bounce(position.y, translation.y), + bounce(position.z, translation.z)), + 0); + } + free(handle); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/many_pyramids3.c b/c/testbed/examples3d/stress_tests/many_pyramids3.c new file mode 100644 index 000000000..8b36b3591 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/many_pyramids3.c @@ -0,0 +1,40 @@ +/* Port of examples3d/stress_tests/many_pyramids3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsManyPyramids3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(50, 0.1, 130)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + for (int p = 0; p < 40; p++) { + for (int i = 0; i < 20; i++) { + for (int j = i; j < 20; j++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(i * 0.5 + j - i, i + 0.5, (p - 20) * 4); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 0.5)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/many_sleep3.c b/c/testbed/examples3d/stress_tests/many_sleep3.c new file mode 100644 index 000000000..89dc64d1f --- /dev/null +++ b/c/testbed/examples3d/stress_tests/many_sleep3.c @@ -0,0 +1,39 @@ +/* Port of examples3d/stress_tests/many_sleep3.rs. */ +#include "testbed.h" +#include "rapier_math.h" + +void tbStressTestsManySleep3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + for (int i = 0; i < 50; i++) { + for (int j = 0; j < 50; j++) { + for (int k = 0; k < 50; k++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = j ? R3_DYNAMIC : R3_FIXED; + rigidBody.position.translation = r3Vector(i * 3 - 75.0, j * 3 + 1.5, k * 3 - 75.0); + rigidBody.sleeping = 1; + R3ColliderDesc collider = r3BallColliderDesc(1); + collider.density = 0.477; + if (testbed->noSleep) { + rigidBody.canSleep = 0; + rigidBody.sleeping = 0; + } + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/many_static3.c b/c/testbed/examples3d/stress_tests/many_static3.c new file mode 100644 index 000000000..f2e7c7ecd --- /dev/null +++ b/c/testbed/examples3d/stress_tests/many_static3.c @@ -0,0 +1,35 @@ +/* Port of examples3d/stress_tests/many_static3.rs. */ +#include "testbed.h" +#include "rapier_math.h" + +void tbStressTestsManyStatic3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + for (int i = 0; i < 50; i++) { + for (int j = 0; j < 50; j++) { + for (int k = 0; k < 50; k++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.bodyType = j < 49 ? R3_FIXED : R3_DYNAMIC; + rigidBody.position.translation = r3Vector(i * 3 - 75.0, j * 3 + 1.5, k * 3 - 75.0); + R3ColliderDesc collider = r3BallColliderDesc(1); + collider.density = 0.477; + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/pyramid3.c b/c/testbed/examples3d/stress_tests/pyramid3.c new file mode 100644 index 000000000..35432dbda --- /dev/null +++ b/c/testbed/examples3d/stress_tests/pyramid3.c @@ -0,0 +1,43 @@ +/* Port of examples3d/stress_tests/pyramid3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsPyramid3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + r3SetGravity(world, r3Vector(0, -9.81, 0)); + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(100, 1, 100)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &boxCollider); + for (int i = 0; i < 50; i++) { + for (int j = i / 2; j < 50 - (i + 1) / 2; j++) { + for (int k = i / 2; k < 50 - (i + 1) / 2; k++) { + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.975, 0.975, 0.975)); + collider.density = 1000; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(-50 + 2.25 * j + (i & 1), 1 + 2.5 * i, -50 + 2.25 * k + (i & 1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 200, 130, 200, 5, 50, 5); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/ragdolls3.c b/c/testbed/examples3d/stress_tests/ragdolls3.c new file mode 100644 index 000000000..60f4a2b59 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/ragdolls3.c @@ -0,0 +1,104 @@ +/* Port of examples3d/stress_tests/ragdolls3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +typedef struct Part { + R3RigidBodyHandle handle; + R3Vector offset; +} Part; + +/* A body and its offset from the ragdoll's torso. */ +static Part part(R3World *world, R3Vector origin, R3Vector offset, R3ColliderDesc collider, + int noSleep) { + R3RigidBodyDesc body = r3DynamicRigidBodyDesc(); + body.position.translation = r3VectorAdd(origin, offset); + body.canSleep = !noSleep; + Part result = {.offset = offset}; + result.handle = r3InsertRigidBody(world, &body); + r3InsertCollider(result.handle, &collider); + + return result; +} + +static R3JointDesc revolute(Part parent, Part child, R3Vector anchor, R3Real min, R3Real max) { + R3JointDesc joint = r3RevoluteJointDesc(r3Vector(0, 0, 1)); + joint.localFrame1.translation = r3VectorSub(anchor, parent.offset); + joint.localFrame2.translation = r3VectorSub(anchor, child.offset); + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_X, min, max); + joint.contactsEnabled = 0; + return joint; +} + +static R3JointDesc spherical(Part parent, Part child, R3Vector anchor, R3Real limit) { + R3JointDesc joint = r3SphericalJointDesc(); + joint.localFrame1.translation = r3VectorSub(anchor, parent.offset); + joint.localFrame2.translation = r3VectorSub(anchor, child.offset); + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_X, -limit, limit); + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_Y, -limit, limit); + r3JointDesc_SetLimits(&joint, R3_AXIS_ANG_Z, -limit, limit); + joint.contactsEnabled = 0; + return joint; +} + +/* Ten bodies and nine limited joints, matching the Rust ragdoll. */ +static void ragdoll(R3World *world, R3Vector origin, int noSleep) { + R3ColliderDesc collider = r3CapsuleYColliderDesc(.3, .15); + Part torso = part(world, origin, r3Vector(0, 0, 0), collider, noSleep); + collider = r3BallColliderDesc(.15); + Part head = part(world, origin, r3Vector(0, 0.55, 0), collider, noSleep); + R3JointDesc neck = spherical(torso, head, r3Vector(0, 0.42, 0), .5); + r3InsertImpulseJoint(torso.handle, head.handle, &neck); + + for (int side = -1; side <= 1; side += 2) { + collider = r3CapsuleXColliderDesc(.14, .06); + Part upperArm = part(world, origin, r3Vector(side * .36, .25, 0), collider, noSleep); + collider = r3CapsuleXColliderDesc(.14, .06); + Part forearm = part(world, origin, r3Vector(side * .70, .25, 0), collider, noSleep); + collider = r3CapsuleYColliderDesc(.16, .07); + Part thigh = part(world, origin, r3Vector(side * .09, -.52, 0), collider, noSleep); + collider = r3CapsuleYColliderDesc(.16, .07); + Part shin = part(world, origin, r3Vector(side * .09, -.92, 0), collider, noSleep); + R3JointDesc shoulder = spherical(torso, upperArm, r3Vector(side * .19, .25, 0), 1.2); + r3InsertImpulseJoint(torso.handle, upperArm.handle, &shoulder); + + R3JointDesc elbow = revolute(upperArm, forearm, r3Vector(side * .53, .25, 0), 0, 2.5); + r3InsertImpulseJoint(upperArm.handle, forearm.handle, &elbow); + + R3JointDesc hip = spherical(torso, thigh, r3Vector(side * .09, -.33, 0), 1); + r3InsertImpulseJoint(torso.handle, thigh.handle, &hip); + + R3JointDesc knee = revolute(thigh, shin, r3Vector(side * .09, -.72, 0), 0, 2.3); + r3InsertImpulseJoint(thigh.handle, shin.handle, &knee); + } +} + +void tbStressTestsRagdolls3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(100, 1, 100)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* 125 ragdolls: a 5 by 5 grid with 5 layers. */ + for (int layer = 0; layer < 5; ++layer) { + for (int row = 0; row < 5; ++row) { + for (int col = 0; col < 5; ++col) { + ragdoll(world, r3Vector(col * 2.2, 1.5 + layer * 2.6, row * 2.2), testbed->noSleep); + } + } + } + tbCamera(testbed, -12, 10, -12, 4.5, 1, 4.5); + + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/ray_cast3.c b/c/testbed/examples3d/stress_tests/ray_cast3.c new file mode 100644 index 000000000..5ed31175d --- /dev/null +++ b/c/testbed/examples3d/stress_tests/ray_cast3.c @@ -0,0 +1,95 @@ +/* Port of examples3d/stress_tests/ray_cast3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsRayCast3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(200.1, 0.1, 200.1)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + R3Real offset = -10; + for (int j = 0; j < 10; ++j) { + for (int i = 0; i < 10; ++i) { + for (int k = 0; k < 10; ++k) { + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * 2 - 10 + offset, j * 2 + 1, k * 2 - 10 + offset); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + offset -= .45; + } + const R3Real rayBallRadius = 100; + R3SharedShape *rayBall = r3BallSharedShape(rayBallRadius); + R3TriMeshData *mesh = r3SharedShape_ToTrimesh(rayBall, 100, 100); + r3FreeSharedShape(rayBall); + size_t rayCount = r3TriMeshData_Vertices(mesh, NULL, 0); + R3Vector *rayOrigins = malloc(rayCount * sizeof(*rayOrigins)); + R3Vector *directions = malloc(rayCount * sizeof(*directions)); + R3Vector *centeredRays = malloc(rayCount * sizeof(*centeredRays)); + if (!rayOrigins || !directions || !centeredRays) { + abort(); + } + rayCount = r3TriMeshData_Vertices(mesh, rayOrigins, rayCount); + r3FreeTriMeshData(mesh); + for (size_t i = 0; i < rayCount; ++i) { + directions[i] = r3VectorScale(r3VectorNormalize(rayOrigins[i]), -1); + } + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + + size_t bodyCount = r3RigidBodyCount(world); + R3RigidBodyHandle *handles = malloc(bodyCount * sizeof(*handles)); + if (!handles) { + abort(); + } + bodyCount = r3RigidBodyHandles(world, handles, bodyCount); + R3Vector center = {0}; + for (size_t i = 0; i < bodyCount; ++i) { + R3Vector translation = r3RigidBody_Translation(handles[i]); + center = r3VectorAdd(center, translation); + } + free(handles); + center = r3VectorScale(center, 1.0 / bodyCount); + for (size_t i = 0; i < rayCount; ++i) { + centeredRays[i] = r3VectorAdd(center, rayOrigins[i]); + } + const double t1 = tbClock(); + + size_t hits = 0; + for (size_t i = 0; i < rayCount; ++i) { + R3RayToi hit = + r3CastRayToi(world, NULL, centeredRays[i], directions[i], rayBallRadius - 1, 1); + hits += hit.found != 0; + } + const double mainCheckTime = tbClock() - t1; + + char label[64]; + snprintf(label, sizeof(label), "%zu", rayCount); + tbLabel(testbed, "Ray count:", label); + snprintf(label, sizeof(label), "%zu", hits); + tbLabel(testbed, "Ray hits:", label); + snprintf(label, sizeof(label), "%.2f ms", mainCheckTime * 1000); + tbLabel(testbed, "Ray-cast time:", label); + } + } + free(rayOrigins); + free(directions); + free(centeredRays); + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/ropes3.c b/c/testbed/examples3d/stress_tests/ropes3.c new file mode 100644 index 000000000..11506f322 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/ropes3.c @@ -0,0 +1,49 @@ +/* Port of examples3d/stress_tests/ropes3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsRopes3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + for (int i = 0; i < 64; i++) { + R3Vector top = r3Vector(i / 8 * 4, 0, i % 8 * 4); + R3RigidBodyHandle parent; + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = top; + groundBody.canSleep = !testbed->noSleep; + parent = r3InsertRigidBody(world, &groundBody); + + for (int s = 0; s < 60; s++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(top, r3Vector(0, -(s + 0.5), 0)); + rigidBody.linvel = r3Vector(2, 0, 0); + R3RigidBodyHandle handle; + R3ColliderDesc collider = r3CapsuleYColliderDesc(0.35, 0.1); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + + R3JointDesc joint = r3DefaultJointDesc(); + joint.lockedAxes = R3_JOINT_SPHERICAL_AXES; + joint.localFrame1.translation = r3Vector(0, s ? -0.5 : 0, 0); + joint.localFrame2.translation = r3Vector(0, 0.5, 0); + joint.contactsEnabled = 0; + r3InsertImpulseJoint(parent, handle, &joint); + parent = handle; + } + } + /* Set up the viewer. */ + tbCamera(testbed, -45, -10, -45, 14, -30, 14); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/soft_blobs3.c b/c/testbed/examples3d/stress_tests/soft_blobs3.c new file mode 100644 index 000000000..b4a3702f6 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/soft_blobs3.c @@ -0,0 +1,87 @@ +/* Port of examples3d/stress_tests/soft_blobs3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsSoftBlobs3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle floor; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.5, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(7.5, 0.5, 7.5)); + rigidBody.canSleep = !testbed->noSleep; + floor = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(floor, &collider); + + tbBodyColor(testbed, floor, 0.6, 0.7, 1, 0.3); + const int dx[] = {1, -1, 0, 0}; + const int dz[] = {0, 0, 1, -1}; + for (int k = 0; k < 4; k++) { + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(dx[k] * 7.25, 4, dz[k] * 7.25); + R3ColliderDesc collider = + r3CuboidColliderDesc(r3Vector(dx[k] ? 0.25 : 7.5, 4, dx[k] ? 7.5 : 0.25)); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + + tbBodyColor(testbed, handle, 0.6, 0.7, 1, 0.3); + } + for (int layer = 0; layer < 8; layer++) { + for (int i = 0; i < 7; i++) { + for (int j = 0; j < 7; j++) { + R3SoftBodyDesc softBody = + r3SphereSoftBodyDesc(r3Vector(-5.7 + i * 1.9 + layer % 2 * 0.4, 2 + layer * 2, + -5.7 + j * 1.9 + layer % 3 * 0.3), + 0.45 + 0.1 * ((i + j + layer) % 3), 1); + softBody.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){20, 1}); + softBody.volumeFactor = 1.1; + softBody.particleMass = 0.03; + softBody.particleRadius = (R3OptionalReal){1, 0.08}; + R3ColliderDesc surfaceCollider = r3BallColliderDesc(0.08); + surfaceCollider.friction = 0.6; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); + } + } + } + R3SoftBodyDesc softBodyValue = r3ClothSoftBodyDesc(r3Vector(-4, 28, -0.6), r3Vector(0.15, 0, 0), + r3Vector(0, 0, 0.15), 54, 9); + softBodyValue.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30, 1}); + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){30, 1}; + material.volumeSoftness = (R3SpringCoefficients){30, 1}; + material.shapeMatchingSoftness = (R3SpringCoefficients){30, 1}; + material.bendSoftness = (R3SpringCoefficients){3, 1}; + softBodyValue.material = material; + softBodyValue.selfContacts = 1; + softBodyValue.particleMass = 0.02; + softBodyValue.particleRadius = (R3OptionalReal){1, 0.05}; + R3ColliderDesc surfaceCollider2 = r3BallColliderDesc(0.05); + surfaceCollider2.friction = 0.5; + softBodyValue.collider = surfaceCollider2; + + if (testbed->noSleep) { + softBodyValue.canSleep = 0; + } + r3InsertSoftBody(world, &softBodyValue); + /* Set up the viewer. */ + tbCamera(testbed, 20, 18, 20, 0, 6, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/soft_cloth_drape3.c b/c/testbed/examples3d/stress_tests/soft_cloth_drape3.c new file mode 100644 index 000000000..3ed5f0691 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/soft_cloth_drape3.c @@ -0,0 +1,81 @@ +/* Port of examples3d/stress_tests/soft_cloth_drape3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsSoftClothDrape3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc boxCollider = r3CuboidColliderDesc(r3Vector(12, 0.1, 12)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &boxCollider); + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + int shape = (i + j) % 3; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-4.5 + i * 3, 1, -4.5 + j * 3); + R3ColliderDesc collider; + if (shape == 0) { + collider = r3BallColliderDesc(0.8); + } else { + if (shape == 1) { + collider = r3CuboidColliderDesc(r3Vector(0.6, 1, 0.6)); + } else { + collider = r3CapsuleYColliderDesc(0.6, 0.4); + } + } + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + R3SoftBodyDesc softBody = r3ClothSoftBodyDesc(r3Vector(-6, 3.5, -6), r3Vector(0.1, 0, 0), + r3Vector(0, 0, 0.1), 101, 101); + softBody.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30, 1}); + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){30, 1}; + material.volumeSoftness = (R3SpringCoefficients){30, 1}; + material.shapeMatchingSoftness = (R3SpringCoefficients){30, 1}; + material.bendSoftness = (R3SpringCoefficients){3, 1}; + softBody.material = material; + softBody.selfContacts = 1; + softBody.particleMass = 0.02; + softBody.particleRadius = (R3OptionalReal){1, 0.05}; + R3ColliderDesc surfaceCollider = r3BallColliderDesc(0.05); + surfaceCollider.friction = 0.6; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + R3ColliderDesc collider = r3BallColliderDesc(0.4); + collider.density = 2; + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(-4 + i * 2 + j % 2 * 0.5, 6 + (i + j) * 0.5, -4 + j * 2); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera(testbed, 14, 10, 14, 0, 1, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/soft_cloth_keva3.c b/c/testbed/examples3d/stress_tests/soft_cloth_keva3.c new file mode 100644 index 000000000..89436d6f5 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/soft_cloth_keva3.c @@ -0,0 +1,70 @@ +/* Port of examples3d/stress_tests/soft_cloth_keva3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +extern void buildBlock(Testbed *, R3World *, R3Vector, R3Vector, int, int, int); + +static void cloth(Testbed *testbed, R3World *world, R3Real side, R3Real spacing, R3Real y) { + size_t n = (size_t)round(side / spacing) + 1; + R3Real width = (n - 1) * spacing; + R3SoftBodyDesc softBody = + r3ClothSoftBodyDesc(r3Vector(-width * 0.5, y, -width * 0.5), r3Vector(spacing, 0, 0), + r3Vector(0, 0, spacing), n, n); + softBody.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30, 1}); + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.edgeSoftness = (R3SpringCoefficients){30, 1}; + material.volumeSoftness = (R3SpringCoefficients){30, 1}; + material.shapeMatchingSoftness = (R3SpringCoefficients){30, 1}; + material.bendSoftness = (R3SpringCoefficients){3, 1}; + softBody.material = material; + softBody.selfContacts = 1; + softBody.particleMass = 0.05; + softBody.particleRadius = (R3OptionalReal){1, 0.15}; + R3ColliderDesc surfaceCollider = r3BallColliderDesc(0.15); + surfaceCollider.friction = 0.6; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); +} + +void tbStressTestsSoftClothKeva3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(50, 0.1, 50)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + + R3Real height = 0; + const int layers[] = {0, 9, 13, 17}; + for (int i = 3; i >= 1; i--) { + R3Real width = i * 4; + buildBlock(testbed, world, r3Vector(0.1, 0.5, 2), r3Vector(-width / 2, height, -width / 2), + i, layers[i], i * 3 + 1); + height += layers[i] + 0.2; + if (i > 1) { + cloth(testbed, world, width + 4, 0.5, height + 0.15); + height += 0.3; + } + } + cloth(testbed, world, 18, 0.75, height + 6); + /* Set up the viewer. */ + tbCamera(testbed, 50, 50, 50, 0, 15, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/soft_fem_beams3.c b/c/testbed/examples3d/stress_tests/soft_fem_beams3.c new file mode 100644 index 000000000..60d679bfb --- /dev/null +++ b/c/testbed/examples3d/stress_tests/soft_fem_beams3.c @@ -0,0 +1,84 @@ +/* Port of examples3d/stress_tests/soft_fem_beams3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#ifdef RAPIER_FEM + +void tbStressTestsSoftFemBeams3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(30, 0.1, 30)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + /* Cantilevers bolted at their left end, stiffer on successive rows. */ + const R3Real length = 3.0, thickness = 0.3; + for (int row = 0; row < 8; ++row) { + for (int col = 0; col < 8; ++col) { + const R3Real x0 = -20.0 + col * 5.0; + const R3Real z = -17.5 + row * 5.0; + R3SoftBodyDesc beam = r3CuboidSoftBodyDesc( + r3Vector(x0 + length * 0.5, 2.5, z), + r3Vector(length * 0.5, thickness * 0.5, thickness * 0.5), 13, 3, 3); + beam.cellModel = R3_SOFT_CELL_COROTATIONAL; + beam.totalMass = (R3OptionalReal){1, 12.0}; + beam.solver = R3_SOFT_SOLVER_FEM; + { + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + material.youngModulus = 1.0e6 * (1.0 + row); + material.poissonRatio = 0.3; + material.elasticDampingRatio = 1.0; + beam.material = material; + } + beam.canSleep = !testbed->noSleep; + uint32_t *beamPins = NULL; + { + size_t count = r3SoftBodyDesc_ParticlePositions(&beam, NULL, 0); + R3Vector *positions = malloc(count * sizeof(*positions)); + beamPins = malloc(count * sizeof(*beamPins)); + if (!positions || !beamPins) { + abort(); + } + count = r3SoftBodyDesc_ParticlePositions(&beam, positions, count); + size_t beamPinsCount = 0; + for (size_t i = 0; i < count; ++i) { + if (positions[i].x < x0 + 1.0e-4) { + beamPins[beamPinsCount++] = (uint32_t)i; + } + } + r3SoftBodyDesc_SetPinnedParticles( + &beam, (R3IndexView){(const uint32_t *)beamPins, beamPinsCount}); + + free(positions); + } + r3InsertSoftBody(world, &beam); + free(beamPins); + + /* Load dropped on the free end. */ + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(x0 + length - 0.4, 4, z); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.3, 0.3, 0.3)); + collider.density = 30.0; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + tbCamera(testbed, 0, 28, 34, 0, 1, 0); + + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} +#endif diff --git a/c/testbed/examples3d/stress_tests/soft_jellies3.c b/c/testbed/examples3d/stress_tests/soft_jellies3.c new file mode 100644 index 000000000..1218f0b2d --- /dev/null +++ b/c/testbed/examples3d/stress_tests/soft_jellies3.c @@ -0,0 +1,72 @@ +/* Port of examples3d/stress_tests/soft_jellies3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsSoftJellies3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle floor; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.5, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(6.5, 0.5, 6.5)); + rigidBody.canSleep = !testbed->noSleep; + floor = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(floor, &collider); + + tbBodyColor(testbed, floor, 0.6, 0.7, 1, 0.3); + const int dx[] = {1, -1, 0, 0}; + const int dz[] = {0, 0, 1, -1}; + for (int k = 0; k < 4; k++) { + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(dx[k] * 6.25, 3.5, dz[k] * 6.25); + R3ColliderDesc collider = + r3CuboidColliderDesc(r3Vector(dx[k] ? 0.25 : 6.5, 3.5, dx[k] ? 6.5 : 0.25)); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + + tbBodyColor(testbed, handle, 0.6, 0.7, 1, 0.3); + } + int k = 0; + for (int layer = 0; layer < 8; layer++) { + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++, k++) { + R3SoftBodyDesc softBody = + r3CuboidSoftBodyDesc(r3Vector(-4.8 + i * 2.4 + layer % 2 * 0.6, 2 + layer * 2.2, + -4.8 + j * 2.4 + layer % 3 * 0.4), + r3Vector(0.55, 0.55, 0.55), 4, 4, 4); + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + softBody.cellModel = k % 3 ? R3_SOFT_CELL_COROTATIONAL : R3_SOFT_CELL_NEO_HOOKEAN; + material.youngModulus = 2.0e3 * (1 + k % 5 * 3); + material.poissonRatio = 0.4; + material.elasticDampingRatio = 0.5; + softBody.material = material; + softBody.particleMass = 0.1; + softBody.particleRadius = (R3OptionalReal){1, 0.08}; + R3ColliderDesc surfaceCollider = r3BallColliderDesc(0.08); + surfaceCollider.friction = 0.7; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 18, 16, 18, 0, 5, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/soft_ropes3.c b/c/testbed/examples3d/stress_tests/soft_ropes3.c new file mode 100644 index 000000000..0d822c7ed --- /dev/null +++ b/c/testbed/examples3d/stress_tests/soft_ropes3.c @@ -0,0 +1,84 @@ +/* Port of examples3d/stress_tests/soft_ropes3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsSoftRopes3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle floor; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.5, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(6.5, 0.5, 6.5)); + rigidBody.canSleep = !testbed->noSleep; + floor = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(floor, &collider); + + tbBodyColor(testbed, floor, 0.6, 0.7, 1, 0.3); + const int dx[] = {1, -1, 0, 0}; + const int dz[] = {0, 0, 1, -1}; + for (int k = 0; k < 4; k++) { + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(dx[k] * 6.25, 3, dz[k] * 6.25); + R3ColliderDesc collider = + r3CuboidColliderDesc(r3Vector(dx[k] ? 0.25 : 6.5, 3, dx[k] ? 6.5 : 0.25)); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + + tbBodyColor(testbed, handle, 0.6, 0.7, 1, 0.3); + } + for (int k = 0; k < 4; k++) { + R3SharedShape *shape = r3CapsuleSharedShape(r3Vector(-5, 0, 0), r3Vector(5, 0, 0), 0.12); + R3RigidBodyHandle handleH; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 4, -4.5 + k * 3); + R3ColliderDesc collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_SHARED; + collider.shape.sharedShape = shape; + rigidBody.canSleep = !testbed->noSleep; + handleH = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handleH, &collider); + + tbBodyColor(testbed, handleH, 0.6, 0.7, 1, 0.3); + r3FreeSharedShape(shape); + } + for (int layer = 0; layer < 10; layer++) { + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 3; j++) { + R3Real across = -5 + i * 2 + layer % 2 * 0.5; + R3Real along = -5.7 + j * 3.8; + R3Real y = 8 + layer * 1.5; + R3Vector start = + layer % 2 ? r3Vector(across, y, along) : r3Vector(along, y, across); + R3Vector end = layer % 2 ? r3Vector(across + 0.4, y + 0.3, along + 3.5) + : r3Vector(along + 3.5, y + 0.3, across + 0.4); + R3SoftBodyDesc softBody = r3RopeSoftBodyDesc(start, end, 40); + softBody.material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30, 1}); + softBody.particleMass = 0.03; + R3ColliderDesc surfaceCollider = r3BallColliderDesc(0.08); + surfaceCollider.friction = 0.6; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 16, 14, 16, 0, 4, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/soft_slab3.c b/c/testbed/examples3d/stress_tests/soft_slab3.c new file mode 100644 index 000000000..f30321fab --- /dev/null +++ b/c/testbed/examples3d/stress_tests/soft_slab3.c @@ -0,0 +1,96 @@ +/* Port of examples3d/stress_tests/soft_slab3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsSoftSlab3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyHandle floor; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.5, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(9.5, 0.5, 9.5)); + rigidBody.canSleep = !testbed->noSleep; + floor = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(floor, &collider); + + tbBodyColor(testbed, floor, 0.6, 0.7, 1, 0.3); + const int dx[] = {1, -1, 0, 0}; + const int dz[] = {0, 0, 1, -1}; + for (int k = 0; k < 4; k++) { + R3RigidBodyHandle handle; + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(dx[k] * 9.25, 3.5, dz[k] * 9.25); + R3ColliderDesc collider = + r3CuboidColliderDesc(r3Vector(dx[k] ? 0.25 : 9.5, 3.5, dx[k] ? 9.5 : 0.25)); + rigidBody.canSleep = !testbed->noSleep; + handle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(handle, &collider); + + tbBodyColor(testbed, handle, 0.6, 0.7, 1, 0.3); + } + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(-6 + i * 3, 0.5, -6 + j * 3); + R3ColliderDesc collider; + if ((i + j) % 2) { + collider = r3CuboidColliderDesc(r3Vector(0.5, 0.5, 0.5)); + } else { + collider = r3BallColliderDesc(0.5); + } + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + R3SoftBodyDesc softBody = + r3CuboidSoftBodyDesc(r3Vector(0, 2.2, 0), r3Vector(8, 1, 8), 25, 4, 25); + R3SoftBodyMaterial material = r3DefaultSoftBodyMaterial(); + softBody.cellModel = R3_SOFT_CELL_COROTATIONAL; + material.youngModulus = 4.0e4; + material.poissonRatio = 0.4; + material.elasticDampingRatio = 0.5; + softBody.material = material; + softBody.particleMass = 0.2; + R3ColliderDesc surfaceCollider = r3BallColliderDesc(0.3); + surfaceCollider.friction = 0.7; + softBody.collider = surfaceCollider; + + if (testbed->noSleep) { + softBody.canSleep = 0; + } + r3InsertSoftBody(world, &softBody); + for (int wave = 0; wave < 3; wave++) { + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(-7.5 + i + wave % 2 * 0.5, 5.5 + wave * 3 + (i + j) % 3 * 0.7, + -7.5 + j + wave % 3 * 0.3); + R3ColliderDesc collider; + if ((i + j + wave) % 2) { + collider = r3CuboidColliderDesc(r3Vector(0.35, 0.35, 0.35)); + } else { + collider = r3BallColliderDesc(0.35); + } + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + /* Set up the viewer. */ + tbCamera(testbed, 20, 14, 20, 0, 2, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/stacks3.c b/c/testbed/examples3d/stress_tests/stacks3.c new file mode 100644 index 000000000..199dc2539 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/stacks3.c @@ -0,0 +1,71 @@ +/* Port of examples3d/stress_tests/stacks3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsStacks3(Testbed *testbed) { + /* World. */ + R3World *world = r3NewWorld(); + + R3RigidBodyDesc groundBody = r3FixedRigidBodyDesc(); + groundBody.position.translation = r3Vector(0, -0.1, 0); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(200, 0.1, 200)); + groundBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle groundBodyHandle = r3InsertRigidBody(world, &groundBody); + r3InsertCollider(groundBodyHandle, &collider); + for (int p = 0; p < 4; p++) { + for (int i = 0; i < 12; i++) { + for (int j = i; j < 12; j++) { + for (int k = i; k < 12; k++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(i + (k - i) * 2 - 110 + p * 30 - 12, + i * 2 + 50, i + (j - i) * 2 - 12); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + } + for (int w = 0; w < 3; w++) { + for (int i = 0; i < 12; i++) { + for (int j = i; j < 12; j++) { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(-2 + w * 6, i * 2 + 50, i + (j - i) * 2 - 12); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + R3Real radius = 1.3 * 24 / R3_PI; + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 24; j++) { + R3Real angle = (i / 2.0 + j) * R3_PI * 2 / 24; + R3Vector position = r3Vector(25 + sin(angle) * radius, 50 + i * 2, cos(angle) * radius); + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = position; + rigidBody.position = + r3Pose(position, r3RotationFromAxisAngle(r3Vector(0, 1, 0), angle)); + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + /* Set up the viewer. */ + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + + /* Set up rendering and run the simulation. */ + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/stress_tests/trimesh3.c b/c/testbed/examples3d/stress_tests/trimesh3.c new file mode 100644 index 000000000..79a390ff4 --- /dev/null +++ b/c/testbed/examples3d/stress_tests/trimesh3.c @@ -0,0 +1,71 @@ +/* Port of examples3d/stress_tests/trimesh3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbStressTestsTrimesh3(Testbed *testbed) { + R3World *world = r3NewWorld(); + const R3Vector groundSize = r3Vector(200, 1, 200); + const size_t nsubdivs = 20; + R3Real heights[21 * 21]; + for (size_t j = 0; j <= nsubdivs; ++j) { + for (size_t i = 0; i <= nsubdivs; ++i) { + const R3Real x = i * groundSize.x / nsubdivs; + const R3Real z = j * groundSize.z / nsubdivs; + heights[i + j * 21] = + i == 0 || i == nsubdivs || j == 0 || j == nsubdivs ? 10 : sin(x) + cos(z); + } + } + /* Build the triangle mesh from the native heightfield's mesh representation. */ + R3SharedShape *heightfield = + r3HeightfieldSharedShape((R3RealView){heights, (21) * (21)}, 21, 21, groundSize); + R3TriMeshData *mesh = r3SharedShape_ToTrimesh(heightfield, 3, 2); + r3FreeSharedShape(heightfield); + size_t vertexCount = 0, indexCount = 0; + vertexCount = r3TriMeshData_Vertices(mesh, NULL, 0); + indexCount = r3TriMeshData_Indices(mesh, NULL, 0); + R3Vector *vertices = malloc(vertexCount * sizeof(*vertices)); + uint32_t *indices = malloc(indexCount * sizeof(*indices)); + if (!vertices || !indices) { + abort(); + } + vertexCount = r3TriMeshData_Vertices(mesh, vertices, vertexCount); + indexCount = r3TriMeshData_Indices(mesh, indices, indexCount); + R3ColliderDesc collider = r3DefaultColliderDesc(); + r3ShapeDesc_SetTrimesh(&collider.shape, (R3VectorView){vertices, vertexCount}, + (R3TriangleView){(const R3Triangle *)indices, indexCount / 3}, 0); + + r3FreeTriMeshData(mesh); + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + free(vertices); + free(indices); + + for (int j = 0; j < 47; ++j) { + for (int i = 0; i < 8; ++i) { + for (int k = 0; k < 8; ++k) { + R3ColliderDesc collider; + if (j % 2 == 0) { + collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + } else { + collider = r3BallColliderDesc(1); + } + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(i * 3 - 12, j * 3 + 4.5, k * 3 - 12); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/trimesh3.c b/c/testbed/examples3d/trimesh3.c new file mode 100644 index 000000000..c5a8f9458 --- /dev/null +++ b/c/testbed/examples3d/trimesh3.c @@ -0,0 +1,115 @@ +/* Port of examples3d/trimesh3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbTrimesh3(Testbed *testbed) { + R3World *world = r3NewWorld(); + const R3Vector groundSize = r3Vector(100, 1, 100); + const size_t nsubdivs = 20; + R3Real heights[21 * 21]; + for (size_t j = 0; j <= nsubdivs; ++j) { + for (size_t i = 0; i <= nsubdivs; ++i) { + const R3Real x = i * groundSize.x / nsubdivs; + const R3Real z = j * groundSize.z / nsubdivs; + heights[i + j * 21] = + i == 0 || i == nsubdivs || j == 0 || j == nsubdivs ? 10 : sin(x) + cos(z); + } + } + /* Build the triangle mesh from the native heightfield's mesh representation. */ + R3SharedShape *heightfield = + r3HeightfieldSharedShape((R3RealView){heights, (21) * (21)}, 21, 21, groundSize); + R3TriMeshData *mesh = r3SharedShape_ToTrimesh(heightfield, 3, 2); + r3FreeSharedShape(heightfield); + size_t vertexCount = 0, indexCount = 0; + vertexCount = r3TriMeshData_Vertices(mesh, NULL, 0); + indexCount = r3TriMeshData_Indices(mesh, NULL, 0); + R3Vector *vertices = malloc(vertexCount * sizeof(*vertices)); + uint32_t *indices = malloc(indexCount * sizeof(*indices)); + if (!vertices || !indices) { + abort(); + } + vertexCount = r3TriMeshData_Vertices(mesh, vertices, vertexCount); + indexCount = r3TriMeshData_Indices(mesh, indices, indexCount); + R3ColliderDesc collider = r3DefaultColliderDesc(); + r3ShapeDesc_SetTrimesh(&collider.shape, (R3VectorView){vertices, vertexCount}, + (R3TriangleView){(const R3Triangle *)indices, indexCount / 3}, + R3_TRIMESH_MERGE_DUPLICATE_VERTICES); + + r3FreeTriMeshData(mesh); + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + free(vertices); + free(indices); + + for (int j = 0; j < 20; j++) { + for (int i = 0; i < 8; i++) { + for (int k = 0; k < 8; k++) { + R3ColliderDesc collider; + switch (j % 6) { + case 0: + collider = r3CuboidColliderDesc(r3Vector(1, 1, 1)); + break; + case 1: + collider = r3BallColliderDesc(1); + break; + case 2: + collider = r3RoundCylinderColliderDesc(1, 1, 0.1); + break; + case 3: + collider = r3ConeColliderDesc(1, 1); + break; + case 4: + collider = r3CapsuleYColliderDesc(1, 1); + break; + default: { + R3Pose poses[] = {r3TranslationPose(r3Vector(0, 0, 0)), + r3TranslationPose(r3Vector(1, 0, 0)), + r3TranslationPose(r3Vector(-1, 0, 0))}; + R3SharedShape *shapes[3] = {NULL}; + shapes[0] = r3CuboidSharedShape(r3Vector(1.0, 0.5, 0.5)); + shapes[1] = r3CuboidSharedShape(r3Vector(0.5, 1.0, 0.5)); + shapes[2] = r3CuboidSharedShape(r3Vector(0.5, 1.0, 0.5)); + R3CompoundShapeDesc colliderParts[TB_COUNT(shapes)]; + for (size_t part = 0; part < TB_COUNT(shapes); ++part) { + colliderParts[part].pose = poses[part]; + colliderParts[part].shape = r3DefaultShapeDesc(); + colliderParts[part].shape.kind = R3_SHAPE_DESC_SHARED; + colliderParts[part].shape.sharedShape = + ((const R3SharedShape *const *)shapes)[part]; + } + collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_COMPOUND; + collider.shape.children = + (R3CompoundShapeView){colliderParts, TB_COUNT(shapes)}; + R3SharedShape *compoundShape = r3ShapeDesc_Build(&collider.shape); + collider.shape.kind = R3_SHAPE_DESC_SHARED; + collider.shape.sharedShape = compoundShape; + for (size_t part = 0; part < TB_COUNT(shapes); part++) { + r3FreeSharedShape(shapes[part]); + } + break; + } + } + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(i * 3 - 12, j * 3 + 4.5, k * 3 - 12); + rigidBody.canSleep = !testbed->noSleep; + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + if (collider.shape.kind == R3_SHAPE_DESC_SHARED) { + r3FreeSharedShape((R3SharedShape *)collider.shape.sharedShape); + } + } + } + } + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/urdf3.c b/c/testbed/examples3d/urdf3.c new file mode 100644 index 000000000..cafc2d8bb --- /dev/null +++ b/c/testbed/examples3d/urdf3.c @@ -0,0 +1,39 @@ +/* Port of examples3d/urdf3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" +#ifdef RAPIER_ROBOTICS +void tbUrdf3(Testbed *testbed) { + R3World *world = r3NewWorld(); + R3UrdfLoaderOptions options = r3DefaultUrdfLoaderOptions(); + options.createCollidersFromVisualShapes = 1; + options.createCollidersFromCollisionShapes = 0; + options.makeRootsFixed = 1; + /* Z-up to Y-up, matching the Rust example's model convention. */ + options.shift = r3Pose(r3Vector(0, 0, 0), r3RotationFromAxisAngle(r3Vector(1, 0, 0), R3_PI / 2)); + R3RigidBodyDesc blueprint = r3DynamicRigidBodyDesc(); + blueprint.canSleep = !testbed->noSleep; + options.rigidBodyBlueprint = blueprint; + + char path[4096]; + snprintf(path, sizeof(path), "%s/3d/T12/urdf/T12.URDF", testbed->assetRoot); + R3UrdfRobot *robot = r3UrdfRobotFromFile(path, &options); + /* Insert the same robot with each joint representation. */ + R3UrdfRobotHandles *impulse = NULL, *multibody = NULL; + impulse = r3UrdfRobot_InsertUsingImpulseJoints(world, robot); + r3UrdfRobot_AppendTransform(robot, r3TranslationPose(r3Vector(10, 0, 0))); + multibody = + r3UrdfRobot_InsertUsingMultibodyJoints(world, robot, R3_MULTIBODY_DISABLE_SELF_CONTACTS); + r3FreeUrdfRobotHandles(impulse); + r3FreeUrdfRobotHandles(multibody); + r3FreeUrdfRobot(robot); + tbSetWorld(testbed, world); + tbCamera(testbed, 20, 20, 20, 5, 0, 0); + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + } + } + r3FreeWorld(world); +} +#endif diff --git a/c/testbed/examples3d/utils/character.h b/c/testbed/examples3d/utils/character.h new file mode 100644 index 000000000..df30869fb --- /dev/null +++ b/c/testbed/examples3d/utils/character.h @@ -0,0 +1,114 @@ +/* Port of examples3d/utils/character.rs. Shared by the character and tether demos. */ +#ifndef EXAMPLE_CHARACTER_3D_H +#define EXAMPLE_CHARACTER_3D_H +#include "testbed.h" +#include "rapier_math.h" + +typedef enum CharacterControlMode { CHARACTER_KINEMATIC, CHARACTER_PID } CharacterControlMode; + +static void updateCharacter(Testbed *viewer, R3World *world, CharacterControlMode *controlMode, + R3KinematicCharacterController *controller, R3PidController *pid, + R3RigidBodyHandle characterHandle) { + R3Real dt = r3TimeStep(world); + + static const char *const modes[] = {"Kinematic", "PID"}; + const CharacterControlMode mode = + (CharacterControlMode)tbChoice(viewer, "Control mode", 0, modes, TB_COUNT(modes), 1, 0); + if (mode != *controlMode) { + r3RigidBody_SetBodyType(characterHandle, + mode == CHARACTER_KINEMATIC ? R3_KINEMATIC_POSITION_BASED + : R3_DYNAMIC, + mode == CHARACTER_PID); + *controlMode = mode; + } + R3Real speed = tbLiveSetting(viewer, "Character speed", .1, 0, 1, 0); + if (viewer->slow) { + speed /= 10; + } + R3Vector desiredMovement = + r3VectorAdd(r3VectorScale(viewer->cameraRight, viewer->inputDirection.x), + r3VectorScale(viewer->cameraForward, viewer->inputDirection.y)); + desiredMovement.y = (viewer->jump ? 2 : 0) - (viewer->descend ? 1 : 0); + desiredMovement = r3VectorScale(desiredMovement, speed); + R3Vector translation = r3RigidBody_Translation(characterHandle); + if (mode == CHARACTER_PID) { + R3PidGains gains = r3PidController_Gains(pid); + const R3Real linKp = tbLiveSetting(viewer, "Linear Kp", 60, 0, 100, 0); + gains.lin_kp = r3Vector(linKp, linKp, linKp); + const R3Real linKi = tbLiveSetting(viewer, "Linear Ki", 1, 0, 10, 0); + gains.lin_ki = r3Vector(linKi, linKi, linKi); + const R3Real linKd = tbLiveSetting(viewer, "Linear Kd", 0.8, 0, 1, 0); + gains.lin_kd = r3Vector(linKd, linKd, linKd); + const R3Real angKp = tbLiveSetting(viewer, "Angular Kp", 60, 0, 100, 0); + gains.ang_kp = r3Vector(angKp, angKp, angKp); + const R3Real angKi = tbLiveSetting(viewer, "Angular Ki", 1, 0, 10, 0); + gains.ang_ki = r3Vector(angKi, angKi, angKi); + const R3Real angKd = tbLiveSetting(viewer, "Angular Kd", 0.8, 0, 1, 0); + gains.ang_kd = r3Vector(angKd, angKd, angKd); + r3PidController_SetGains(pid, gains); + uint32_t axes = 56; /* Angular axes. */ + if (desiredMovement.x != 0 || desiredMovement.y != 0 || desiredMovement.z != 0) { + axes |= desiredMovement.y == 0 ? 5 : 7; + } + r3PidController_SetAxes(pid, axes); + R3Pose target = r3TranslationPose(r3VectorAdd(translation, desiredMovement)); + target.rotation = r3RigidBody_Rotation(characterHandle); + R3Vector correctiveLinear, linvel; + R3AngVector correctiveAngular, angvel; + R3VelocityCorrection pidControllerRigidBodyCorrectionResult = r3PidController_RigidBodyCorrection(pid, dt, characterHandle, target, r3Vector(0, 0, 0), r3Vector(0, 0, 0)); + correctiveLinear = pidControllerRigidBodyCorrectionResult.linear; + correctiveAngular = pidControllerRigidBodyCorrectionResult.angularVelocity; + linvel = r3RigidBody_Linvel(characterHandle); + angvel = r3RigidBody_Angvel(characterHandle); + r3RigidBody_SetLinvel(characterHandle, r3VectorAdd(linvel, correctiveLinear), 1); + r3RigidBody_SetAngvel(characterHandle, r3VectorAdd(angvel, correctiveAngular), 1); + return; + } + /* Kinematic character settings, applied live. */ + R3CharacterControllerSettings settings = r3KinematicCharacterController_Settings(controller); + settings.slide = (R3Bool)tbLiveSetting(viewer, "Slide", settings.slide, 0, 1, 1); + settings.max_slope_climb_angle = tbLiveSetting( + viewer, "Maximum climb angle", settings.max_slope_climb_angle, 0, 2 * R3_PI, 0); + settings.min_slope_slide_angle = tbLiveSetting( + viewer, "Minimum slide angle", settings.min_slope_slide_angle, 0, R3_PI / 2, 0); + settings.snap_to_ground = + (R3Bool)tbLiveSetting(viewer, "Snap to ground", settings.snap_to_ground, 0, 1, 1); + settings.snap_distance.value = tbLiveSetting(viewer, "Snap distance (relative height)", + settings.snap_distance.value, 0, 10, 0); + r3KinematicCharacterController_SetSlide(controller, settings.slide); + r3KinematicCharacterController_SetSlopes(controller, settings.max_slope_climb_angle, + settings.min_slope_slide_angle); + r3KinematicCharacterController_SetSnapToGround(controller, settings.snap_to_ground, + settings.snap_distance); + desiredMovement.y -= speed; /* Artificial gravity, as in the Rust utility. */ + size_t colliderCount = r3RigidBody_Colliders(characterHandle, NULL, 0); + R3ColliderHandle *handles = malloc(colliderCount * sizeof(*handles)); + if (!colliderCount || !handles) { + abort(); + } + colliderCount = r3RigidBody_Colliders(characterHandle, handles, colliderCount); + + const R3ColliderHandle colliderHandle = handles[0]; + free(handles); + R3Pose pose; + R3SharedShape *shape = NULL; + R3Real mass; + pose = r3Collider_Position(colliderHandle); + shape = r3Collider_CloneShape(colliderHandle); + mass = r3RigidBody_Mass(characterHandle); + R3QueryFilter filter = r3DefaultQueryFilter(); + filter.exclude_rigid_body = characterHandle; + R3QueryOptions query = r3DefaultQueryOptions(); + query.filter = filter; + R3CharacterMovement movement = r3KinematicCharacterController_MoveShape(world, &query, controller, dt, shape, pose, desiredMovement); + + tbBodyColor(viewer, characterHandle, movement.grounded ? .1f : .8f, + movement.grounded ? .8f : .1f, .1f, 1); + r3KinematicCharacterController_SolveCharacterCollisionImpulses(controller, shape, dt, + mass, &filter); + r3FreeSharedShape(shape); + + r3RigidBody_SetNextKinematicTranslation(characterHandle, + r3VectorAdd(translation, movement.translation)); +} +#endif diff --git a/c/testbed/examples3d/utils/files.h b/c/testbed/examples3d/utils/files.h new file mode 100644 index 000000000..338f32c25 --- /dev/null +++ b/c/testbed/examples3d/utils/files.h @@ -0,0 +1,69 @@ +#ifndef EXAMPLE_FILES_H +#define EXAMPLE_FILES_H + +/* Directory enumeration shared by the example's scene-discovery code. */ +typedef struct FileNames { + char **names; + size_t count; +} FileNames; + +static void addFilename(FileNames *files, const char *name) { + char **names = realloc(files->names, (files->count + 1) * sizeof(*names)); + if (!names) { + abort(); + } + files->names = names; + names[files->count] = malloc(strlen(name) + 1); + if (!names[files->count]) { + abort(); + } + strcpy(names[files->count++], name); +} + +static void freeFilenames(FileNames *files) { + for (size_t i = 0; i < files->count; ++i) { + free(files->names[i]); + } + free(files->names); + *files = (FileNames){0}; +} +#ifdef _WIN32 +#include + +static FileNames listDirectory(const char *path) { + FileNames result = {0}; + char pattern[8192]; + snprintf(pattern, sizeof(pattern), "%s/*", path); + WIN32_FIND_DATAA entry; + HANDLE directory = FindFirstFileA(pattern, &entry); + if (directory == INVALID_HANDLE_VALUE) { + return result; + } + do { + if (strcmp(entry.cFileName, ".") && strcmp(entry.cFileName, "..")) { + addFilename(&result, entry.cFileName); + } + } while (FindNextFileA(directory, &entry)); + FindClose(directory); + return result; +} +#else +#include + +static FileNames listDirectory(const char *path) { + FileNames result = {0}; + DIR *directory = opendir(path); + if (!directory) { + return result; + } + struct dirent *entry; + while ((entry = readdir(directory))) { + if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) { + addFilename(&result, entry->d_name); + } + } + closedir(directory); + return result; +} +#endif +#endif diff --git a/c/testbed/examples3d/utils/obj.h b/c/testbed/examples3d/utils/obj.h new file mode 100644 index 000000000..f42e622f7 --- /dev/null +++ b/c/testbed/examples3d/utils/obj.h @@ -0,0 +1,128 @@ +/* Position/index subset of OBJ used by the Rust mesh examples. No runtime dependency. */ +#ifndef EXAMPLE_OBJ_H +#define EXAMPLE_OBJ_H +#include "testbed.h" +#include "rapier_math.h" +#include +#include +#include + +typedef struct ObjMesh { + R3Vector *vertices; + uint32_t *indices; + size_t vertexCount, indexCount; +} ObjMesh; + +static void freeObj(ObjMesh *mesh) { + free(mesh->vertices); + free(mesh->indices); + *mesh = (ObjMesh){0}; +} + +/* Preserve polygon index order, as obj::raw::parse_obj does in the Rust examples. */ +static int loadObj(const char *root, const char *name, ObjMesh *mesh) { + char path[4096]; + if (snprintf(path, sizeof(path), "%s/3d/%s", root, name) >= (int)sizeof(path)) { + return 0; + } + FILE *file = fopen(path, "rb"); + if (!file) { + fprintf(stderr, "Cannot open %s: %s\n", path, strerror(errno)); + return 0; + } + *mesh = (ObjMesh){0}; + char *line = NULL; + size_t capacity = 0, vertexCapacity = 0, indexCapacity = 0; + int valid = 1; + while (valid) { + size_t length = 0; + int ch; + while ((ch = fgetc(file)) != EOF && ch != '\n') { + if (length + 1 >= capacity) { + capacity = capacity ? capacity * 2 : 256; + char *next = realloc(line, capacity); + if (!next) { + abort(); + } + line = next; + } + line[length++] = (char)ch; + } + if (ch == EOF && !length) { + break; + } + if (!line) { + capacity = 256; + line = malloc(capacity); + if (!line) { + abort(); + } + } + line[length] = '\0'; + char *p = line; + while (isspace((unsigned char)*p)) { + ++p; + } + if (p[0] == 'v' && isspace((unsigned char)p[1])) { + double x, y, z; + if (sscanf(p + 1, "%lf %lf %lf", &x, &y, &z) != 3) { + valid = 0; + break; + } + if (mesh->vertexCount == vertexCapacity) { + vertexCapacity = vertexCapacity ? vertexCapacity * 2 : 256; + R3Vector *next = realloc(mesh->vertices, vertexCapacity * sizeof(*next)); + if (!next) { + abort(); + } + mesh->vertices = next; + } + mesh->vertices[mesh->vertexCount++] = r3Vector(x, y, z); + } else if (p[0] == 'f' && isspace((unsigned char)p[1])) { + ++p; + while (*p) { + while (isspace((unsigned char)*p)) { + ++p; + } + if (!*p || *p == '#') { + break; + } + char *end; + long index = strtol(p, &end, 10); + if (end == p || index == 0) { + valid = 0; + break; + } + long vertex = index > 0 ? index - 1 : (long)mesh->vertexCount + index; + if (vertex < 0 || (size_t)vertex >= mesh->vertexCount || + (unsigned long)vertex > UINT32_MAX) { + valid = 0; + break; + } + if (mesh->indexCount == indexCapacity) { + indexCapacity = indexCapacity ? indexCapacity * 2 : 768; + uint32_t *next = realloc(mesh->indices, indexCapacity * sizeof(*next)); + if (!next) { + abort(); + } + mesh->indices = next; + } + mesh->indices[mesh->indexCount++] = (uint32_t)vertex; + p = end; + while (*p && !isspace((unsigned char)*p)) { + ++p; + } + } + } + } + valid = valid && !ferror(file) && mesh->vertexCount && mesh->indexCount && + mesh->indexCount % 3 == 0; + free(line); + fclose(file); + if (!valid) { + fprintf(stderr, "Invalid triangle OBJ: %s\n", path); + freeObj(mesh); + } + return valid; +} +#endif diff --git a/c/testbed/examples3d/vehicle_controller3.c b/c/testbed/examples3d/vehicle_controller3.c new file mode 100644 index 000000000..e1cdb9458 --- /dev/null +++ b/c/testbed/examples3d/vehicle_controller3.c @@ -0,0 +1,105 @@ +/* Port of examples3d/vehicle_controller3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbVehicleController3(Testbed *testbed) { + R3World *world = r3NewWorld(); + { + R3RigidBodyDesc rigidBody = r3FixedRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, -0.1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(5, 0.1, 5)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + const R3Real hw = .3, hh = .15; + R3RigidBodyHandle vehicleHandle; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3Vector(0, 1, 0); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(hw * 2, hh, hw)); + collider.density = 100; + vehicleHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(vehicleHandle, &collider); + } + + R3WheelTuning tuning = r3DefaultWheelTuning(); + tuning.suspension_stiffness = 100; + tuning.suspension_damping = 10; + R3DynamicRayCastVehicleController *vehicle = + r3NewDynamicRayCastVehicleController(vehicleHandle); + const R3Vector wheelPositions[] = { + {hw * 1.5, -hh, hw}, {hw * 1.5, -hh, -hw}, {-hw * 1.5, -hh, hw}, {-hw * 1.5, -hh, -hw}}; + for (size_t i = 0; i < TB_COUNT(wheelPositions); ++i) { + r3DynamicRayCastVehicleController_AddWheel(vehicle, wheelPositions[i], r3Vector(0, -1, 0), + r3Vector(0, 0, 1), hh, hh / 4, &tuning); + } + for (int j = 0; j < 1; ++j) { + for (int k = 0; k < 4; ++k) { + for (int i = 0; i < 8; ++i) { + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = + r3Vector(i * .2 - .8, j * .2 + .1, k * .2 + .8); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.1, 0.1, 0.1)); + R3RigidBodyHandle rigidBodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(rigidBodyHandle, &collider); + } + } + } + } + { + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(2, .1, 5)); + collider.position.translation = r3Vector(7, 0.3, 0); + collider.position.rotation = r3RotationFromAxisAngle(r3Vector(0, 0, 1), 0.2); + r3InsertColliderWithoutParent(world, &collider); + } + { + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(2, .1, 5)); + collider.position.translation = r3Vector(10.1, 2.2, 0); + collider.position.rotation = r3RotationFromAxisAngle(r3Vector(0, 0, 1), 0.9); + r3InsertColliderWithoutParent(world, &collider); + } + R3Real heights[21 * 21]; + for (int j = 0; j <= 20; ++j) { + for (int i = 0; i <= 20; ++i) { + heights[i + j * 21] = -cos(i * .25) - cos(j * .25); + } + } + R3ColliderDesc collider = r3DefaultColliderDesc(); + collider.shape.kind = R3_SHAPE_DESC_HEIGHTFIELD; + collider.shape.heights = (R3RealView){heights, (21) * (21)}; + collider.shape.rows = 21; + collider.shape.columns = 21; + collider.shape.scale = r3Vector(10, .4, 10); + collider.shape.flags = 0; + collider.position.translation = r3Vector(-7, 0, 0); + r3InsertColliderWithoutParent(world, &collider); + + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + const R3Real engineForce = 30 * testbed->inputDirection.y; + const R3Real steeringAngle = -.7 * testbed->inputDirection.x; + for (size_t i = 0; i < 2; ++i) { + r3DynamicRayCastVehicleController_SetWheelControls(vehicle, i, steeringAngle, + engineForce, 0); + } + R3QueryFilter filter = r3DefaultQueryFilter(); + filter.flags = R3_QUERY_EXCLUDE_DYNAMIC; + filter.exclude_rigid_body = vehicleHandle; + + R3Real dt = r3TimeStep(world); + r3DynamicRayCastVehicleController_UpdateVehicle(vehicle, dt, &filter); + r3Step(world, NULL, NULL); + } + } + r3FreeDynamicRayCastVehicleController(vehicle); + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/vehicle_joints3.c b/c/testbed/examples3d/vehicle_joints3.c new file mode 100644 index 000000000..bcb7784e1 --- /dev/null +++ b/c/testbed/examples3d/vehicle_joints3.c @@ -0,0 +1,130 @@ +/* Port of examples3d/vehicle_joints3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +void tbVehicleJoints3(Testbed *testbed) { + R3World *world = r3NewWorld(); + R3Real heights[101 * 101]; + for (int j = 0; j <= 100; ++j) { + for (int i = 0; i <= 100; ++i) { + heights[i + j * 101] = -cos(i * .3) - cos(j * .3); + } + } + R3ColliderDesc ground = r3DefaultColliderDesc(); + ground.shape.kind = R3_SHAPE_DESC_HEIGHTFIELD; + ground.shape.heights = (R3RealView){heights, (101) * (101)}; + ground.shape.rows = 101; + ground.shape.columns = 101; + ground.shape.scale = r3Vector(60, .4, 60); + ground.shape.flags = 0; + ground.position.translation = r3Vector(-7, 0, 0); + ground.friction = 1; + r3InsertColliderWithoutParent(world, &ground); + + const R3InteractionGroups carGroups = {1, ~UINT32_C(1), 0}; + const R3Vector wheelParams[] = {{.6874, .2783, -.7802}, + {-.6874, .2783, -.7802}, + {.64, .2783, 1.0254}, + {-.64, .2783, 1.0254}}; + const R3Real suspensionHeight = .12, maxSteeringAngle = 35 * R3_PI / 180; + const R3Real driveStrength = 1, wheelRadius = .28; + const R3Vector carPosition = {0, wheelRadius + suspensionHeight, 0}; + const R3Vector bodyPositionInCarSpace = {0, .4739, 0}; + R3RigidBodyHandle bodyHandle; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = r3VectorAdd(carPosition, bodyPositionInCarSpace); + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3CuboidColliderDesc(r3Vector(0.65, 0.3, 0.9)); + collider.density = 100; + collider.collisionGroups = carGroups; + bodyHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(bodyHandle, &collider); + } + + R3ImpulseJointHandle steeringJoints[2], motorJoints[2]; + for (size_t wheelId = 0; wheelId < TB_COUNT(wheelParams); ++wheelId) { + const int isFront = wheelId >= 2; + const R3Vector wheelCenter = r3VectorAdd(carPosition, wheelParams[wheelId]); + R3SharedShape *ball = r3BallSharedShape(wheelRadius); + R3MassProperties axleMassProps = r3SharedShape_MassProperties(ball, 100); + r3FreeSharedShape(ball); + R3RigidBodyDesc axleRb = r3DynamicRigidBodyDesc(); + axleRb.position.translation = wheelCenter; + axleRb.canSleep = !testbed->noSleep; + R3RigidBodyHandle axleHandle = r3InsertRigidBody(world, &axleRb); + + r3RigidBody_SetAdditionalMassProperties(axleHandle, axleMassProps, 1); + R3RigidBodyHandle wheelHandle; + { + R3RigidBodyDesc rigidBody = r3DynamicRigidBodyDesc(); + rigidBody.position.translation = wheelCenter; + rigidBody.canSleep = !testbed->noSleep; + R3ColliderDesc collider = r3BallColliderDesc(wheelRadius); + collider.density = 100; + collider.collisionGroups = carGroups; + collider.friction = 1; + wheelHandle = r3InsertRigidBody(world, &rigidBody); + r3InsertCollider(wheelHandle, &collider); + } + R3ColliderDesc wheelFakeCo = r3CylinderColliderDesc(wheelRadius / 2, wheelRadius); + wheelFakeCo.position.rotation = r3RotationFromAxisAngle(r3Vector(0, 0, 1), R3_PI / 2); + wheelFakeCo.isSensor = 1; + wheelFakeCo.density = 0; + wheelFakeCo.collisionGroups = (R3InteractionGroups){0, 0, 0}; + r3InsertCollider(wheelHandle, &wheelFakeCo); + + uint8_t lockedAxes = 1 | 4 | 8 | 32; + if (!isFront) { + lockedAxes |= 16; + } + R3JointDesc suspensionJoint = r3DefaultJointDesc(); + suspensionJoint.lockedAxes = lockedAxes; + r3JointDesc_SetLimits(&suspensionJoint, R3_AXIS_LIN_Y, 0, suspensionHeight); + r3JointDesc_SetMotorPosition(&suspensionJoint, R3_AXIS_LIN_Y, 0, 1e4, 1e3); + suspensionJoint.localFrame1.translation = + r3VectorSub(wheelParams[wheelId], bodyPositionInCarSpace); + if (isFront) { + r3JointDesc_SetLimits(&suspensionJoint, R3_AXIS_ANG_Y, -maxSteeringAngle, + maxSteeringAngle); + } + R3ImpulseJointHandle bodyAxleJointHandle = + r3InsertImpulseJoint(bodyHandle, axleHandle, &suspensionJoint); + + R3JointDesc wheelJoint = r3RevoluteJointDesc(r3Vector(1, 0, 0)); + R3ImpulseJointHandle wheelJointHandle = + r3InsertImpulseJoint(axleHandle, wheelHandle, &wheelJoint); + + if (isFront) { + steeringJoints[wheelId - 2] = bodyAxleJointHandle; + motorJoints[wheelId - 2] = wheelJointHandle; + } + } + tbCamera(testbed, 10, 10, 10, 0, 0, 0); + testbed->snapshotSupported = 0; + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + const R3Real thrust = -driveStrength * testbed->inputDirection.y; + const R3Real steering = -testbed->inputDirection.x; + const R3Real boost = testbed->boost ? 1.5 : 1; + const R3Bool shouldWakeUp = thrust != 0 || steering != 0; + for (size_t i = 0; i < TB_COUNT(steeringJoints); ++i) { + r3ImpulseJoint_SetMotorPosition(steeringJoints[i], R3_AXIS_ANG_Y, + maxSteeringAngle * steering, 1e4, 1e3, shouldWakeUp); + } + const R3Real sidewaysShift = sin(maxSteeringAngle * steering) * .5; + const R3Real speedDiff = + sidewaysShift > 0 ? hypot(1, sidewaysShift) : 1 / hypot(1, sidewaysShift); + const R3Real ms[] = {1 / speedDiff, speedDiff}; + for (size_t i = 0; i < TB_COUNT(motorJoints); ++i) { + r3ImpulseJoint_SetMotorVelocity(motorJoints[i], R3_AXIS_ANG_X, + -30 * thrust * ms[i] * boost, 1e2, shouldWakeUp); + } + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/examples3d/voxels3.c b/c/testbed/examples3d/voxels3.c new file mode 100644 index 000000000..7880461eb --- /dev/null +++ b/c/testbed/examples3d/voxels3.c @@ -0,0 +1,177 @@ +/* Port of examples3d/voxels3.rs. */ +#include "testbed.h" +#include "rapier_helpers.h" +#include "rapier_math.h" + +#include + +static R3Bool RAPIER_CALL isVoxel(void *userData, const R3ReadContext *read, + R3ColliderHandle handle) { + (void)userData; + R3Bool result = r3ReadCollider_IsVoxels(read, handle); + return result; +} + +void tbVoxels3(Testbed *testbed) { + R3World *world = r3NewWorld(); + const int fallingObjects = (int)tbSetting( + testbed, "Falling objects: 0 Ball, 1 Cuboid, 2 Cylinder, 3 Cone, 4 Capsule, 5 Mixed", 5, 0, + 5, 1); + const R3Real voxelSizeY = tbSetting(testbed, "Voxel size y", 1, .5, 2, 0); + const R3Vector voxelSize = {1, voxelSizeY, 1}; + const int testCcd = (int)tbSetting(testbed, "Test CCD", 0, 0, 1, 1); + /* The Rust example leaves its optional OBJ block disabled too. */ + const int n = 200; + R3Vector *samples = malloc((200 * 200 + 4 * 4 * 200) * sizeof(*samples)); + if (!samples) { + abort(); + } + size_t sampleCount = 0; + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + const R3Real y = fmax(-.8, fmin(.8, sin(i / (R3Real)n * 10))) * + fmax(-.8, fmin(.8, cos(j / (R3Real)n * 10))) * 16; + samples[sampleCount++] = r3Vector(i, y * voxelSizeY, j); + if (i == 0 || i == n - 1 || j == 0 || j == n - 1) { + for (int k = 0; k < 4; ++k) { + samples[sampleCount++] = r3Vector(i, (y + k) * voxelSizeY, j); + } + } + } + } + R3SharedShape *shape = + r3VoxelsSharedShapeFromPoints(voxelSize, (R3VectorView){samples, sampleCount}); + free(samples); + R3Aabb floorAabb = r3SharedShape_ComputeAabb(shape, r3TranslationPose(r3Vector(0, 0, 0))); + R3ColliderDesc floor = r3DefaultColliderDesc(); + floor.shape.kind = R3_SHAPE_DESC_SHARED; + floor.shape.sharedShape = shape; + + r3InsertColliderWithoutParent(world, &floor); + r3FreeSharedShape(shape); + + const R3Vector size = r3VectorSub(floorAabb.maxs, floorAabb.mins); + const R3Vector extents = r3VectorScale(size, .75); + const R3Vector margin = r3VectorScale(r3VectorSub(size, extents), .5); + const int nik = 30; + for (int i = 0; i < nik; ++i) { + for (int j = 0; j < 5; ++j) { + for (int k = 0; k < nik; ++k) { + R3RigidBodyDesc rb = r3DynamicRigidBodyDesc(); + rb.position.translation = r3Vector( + floorAabb.mins.x + margin.x + i * extents.x / nik, floorAabb.maxs.y + j * 2, + floorAabb.mins.z + margin.z + k * extents.z / nik); + rb.canSleep = !testbed->noSleep; + if (testCcd) { + rb.linvel = r3Vector(0, -1000, 0); + rb.ccdEnabled = 1; + } + R3ColliderDesc co; + switch (fallingObjects == 5 ? j % 5 : fallingObjects) { + case 0: + co = r3BallColliderDesc(.5); + break; + case 1: + co = r3CuboidColliderDesc(r3Vector(.5, .5, .5)); + break; + case 2: + co = r3CylinderColliderDesc(.5, .5); + break; + case 3: + co = r3ConeColliderDesc(.5, .5); + break; + case 4: + co = r3CapsuleYColliderDesc(.5, .5); + break; + } + R3RigidBodyHandle rbHandle = r3InsertRigidBody(world, &rb); + r3InsertCollider(rbHandle, &co); + } + } + } + R3ColliderHandle hitIndicatorHandle, hitHighlightHandle; + R3ColliderDesc indicator = r3BallColliderDesc(.1); + indicator.collisionGroups = (R3InteractionGroups){0, 0, 0}; + hitIndicatorHandle = r3InsertColliderWithoutParent(world, &indicator); + + R3ColliderDesc highlight = r3CuboidColliderDesc(r3Vector(.51, .51, .51)); + highlight.collisionGroups = (R3InteractionGroups){0, 0, 0}; + hitHighlightHandle = r3InsertColliderWithoutParent(world, &highlight); + + tbColliderColor(testbed, hitIndicatorHandle, .5, .5, .1, 1); + tbColliderColor(testbed, hitHighlightHandle, .1, .5, .1, 1); + tbCamera(testbed, 100, 100, 100, 0, 0, 0); + testbed->snapshotSupported = 0; + tbLabel(testbed, "Voxel editing:", "Space: add | Left Shift + Space: remove"); + tbSetWorld(testbed, world); + + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + r3Step(world, NULL, NULL); + if (!testbed->rayValid) { + continue; + } + R3QueryOptions query = r3DefaultQueryOptions(); + + query.predicate = isVoxel; + R3RayHit hit; + R3Bool found; + R3OptionalRayHit tryCastRayResult = + r3TryCastRay(world, &query, testbed->rayOrigin, testbed->rayDirection, FLT_MAX, 1); + hit = tryCastRayResult.hit; + found = tryCastRayResult.found; + + if (found) { + R3Pose hitPos = r3Collider_Position(hit.collider); + const R3Vector hitLocalNormal = + r3RotationTransformVector(r3RotationInverse(hitPos.rotation), hit.normal); + R3VoxelKey voxelKey; + R3Vector voxelCenterLocal, size; + R3Bool voxelFound; + R3VoxelQuery colliderVoxelAtFlatIdResult2 = + r3Collider_VoxelAtFlatId(hit.collider, hit.feature_id); + voxelKey = colliderVoxelAtFlatIdResult2.key; + voxelCenterLocal = colliderVoxelAtFlatIdResult2.center; + size = colliderVoxelAtFlatIdResult2.size; + voxelFound = colliderVoxelAtFlatIdResult2.found; + if (!voxelFound) { + continue; + } + const R3Vector voxelCenter = r3PoseTransformPoint(hitPos, voxelCenterLocal); + r3Collider_SetTranslation(hitHighlightHandle, voxelCenter); + R3SharedShape *shape = r3CuboidSharedShape( + r3VectorAdd(r3VectorScale(size, .5), r3Vector(.001, .001, .001))); + r3Collider_SetShape(hitHighlightHandle, shape); + r3FreeSharedShape(shape); + const R3Vector hitPt = r3VectorAdd( + testbed->rayOrigin, r3VectorScale(testbed->rayDirection, hit.time_of_impact)); + r3Collider_SetTranslation(hitIndicatorHandle, hitPt); + shape = r3BallSharedShape(r3VectorLength(size) / 3.5); + r3Collider_SetShape(hitIndicatorHandle, shape); + r3FreeSharedShape(shape); + if (testbed->jump) { + R3VoxelKey affectedKey = voxelKey; + if (!testbed->removeVoxel) { + const R3Vector a = {fabs(hitLocalNormal.x), fabs(hitLocalNormal.y), + fabs(hitLocalNormal.z)}; + if (a.x >= a.y && a.x >= a.z) { + affectedKey.x += hitLocalNormal.x >= 0 ? 1 : -1; + } else if (a.y >= a.z) { + affectedKey.y += hitLocalNormal.y >= 0 ? 1 : -1; + } else { + affectedKey.z += hitLocalNormal.z >= 0 ? 1 : -1; + } + } + + r3Collider_SetVoxel(hit.collider, affectedKey, !testbed->removeVoxel); + } + } else { + const R3Vector behindCamera = + r3VectorSub(testbed->rayOrigin, r3VectorScale(testbed->rayDirection, 1000)); + r3Collider_SetTranslation(hitIndicatorHandle, behindCamera); + r3Collider_SetTranslation(hitHighlightHandle, behindCamera); + } + } + } + r3FreeWorld(world); +} diff --git a/c/testbed/font_data.h.in b/c/testbed/font_data.h.in new file mode 100644 index 000000000..44caafda0 --- /dev/null +++ b/c/testbed/font_data.h.in @@ -0,0 +1,5 @@ +/* Generated from the unmodified SIL OFL font in vendor/fira. Do not edit. */ +#ifndef RAPIER_TESTBED_FONT_DATA_H +#define RAPIER_TESTBED_FONT_DATA_H +static const unsigned char tbFontData[] = { @TB_FONT_BYTES@ }; +#endif diff --git a/c/testbed/grab.c b/c/testbed/grab.c new file mode 100644 index 000000000..3ee82e32b --- /dev/null +++ b/c/testbed/grab.c @@ -0,0 +1,309 @@ +#include "grab.h" +#include "rapier_math.h" +#include "rapier_helpers.h" +#include + +#define TRY(call) \ + do { \ + status = (call); \ + if (status != RAPIER_CONST(OK)) \ + goto done; \ + } while (0) + +static bool contains(Testbed *t, RAPIER_TYPE(RigidBodyHandle) handle) { + if (handle.world != t->world) return false; + RAPIER_TYPE(Bool) found = RAPIER_FN(RigidBody_Contains)(handle); + return RAPIER_FN(LastStatus)() == RAPIER_CONST(OK) && found; +} + +static RAPIER_TYPE(RigidBodyHandle) pulledBody(Testbed *t, const TbGrab *grab) { + if (grab->joint.world != t->world) return RAPIER_CONST(INVALID_RIGID_BODY_HANDLE); + RAPIER_TYPE(RigidBodyHandle) second = + RAPIER_FN(ImpulseJoint_Bodies)(grab->joint).body2; + if (RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)) { + return second; /* Tearing may transfer the joint to another proxy. */ + } + return grab->body; +} + +RAPIER_TYPE(Status) tbGrabRelease(Testbed *t, TbGrab *grab) { + if (grab->active && grab->body.world != t->world) { + *grab = (TbGrab){0}; /* The old world was replaced, so its handles are unusable. */ + } + if (!grab->active) { + return RAPIER_CONST(OK); + } + RAPIER_TYPE(Status) status = RAPIER_CONST(OK); + RAPIER_TYPE(RigidBodyHandle) pulled = pulledBody(t, grab); + grab->active = false; + if (contains(t, pulled)) { + TRY(RAPIER_FN(RigidBody_WakeUp)(pulled, 1)); + } + RAPIER_FN(RemoveRigidBody)(grab->mouseBody, 1); + TRY(RAPIER_FN(LastStatus)()); + if (grab->soft) { + RAPIER_TYPE(RigidBodyHandle) proxies[] = {grab->body, pulled}; + for (size_t i = 0; i < TB_COUNT(proxies); ++i) { + if (!contains(t, proxies[i])) { + continue; + } + + RAPIER_TYPE(Bool) isFrame; + TRY(RAPIER_FN(RigidBody_ValidateHandle)(proxies[i])); + isFrame = RAPIER_FN(RigidBody_IsSoftFrame)(proxies[i]); + TRY(RAPIER_FN(LastStatus)()); + if (isFrame) { + RAPIER_FN(RemoveRigidBody)(proxies[i], 1); + TRY(RAPIER_FN(LastStatus)()); + } + } + } +done: + return status; +} + +static RAPIER_TYPE(Status) pick(Testbed *t, RAPIER_TYPE(Real) radius, + RAPIER_TYPE(RigidBodyHandle) *body, RAPIER_TYPE(Vector) *point, + bool *found) { + RAPIER_TYPE(Status) status = RAPIER_CONST(OK); + RAPIER_TYPE(QueryOptions) query; + RAPIER_TYPE(QueryFilter) filter; +#if defined(RAPIER_DIM2) + RAPIER_TYPE(ColliderHandle) *hits = NULL; +#endif + RAPIER_TYPE(ColliderHandle) collider; + *found = false; + filter = RAPIER_FN(DefaultQueryFilter)(); + filter.flags = RAPIER_CONST(QUERY_EXCLUDE_FIXED) | RAPIER_CONST(QUERY_EXCLUDE_KINEMATIC) | + RAPIER_CONST(QUERY_EXCLUDE_SENSORS); + query = RAPIER_FN(DefaultQueryOptions)(); + query.filter = filter; +#if defined(RAPIER_DIM2) + if (!t->cursorValid) { + goto done; + } + /* Prefer a solid interior, but ignore the outward side of an open rope. */ + size_t count = RAPIER_FN(IntersectPoint)(t->world, &query, t->cursor, NULL, 0); + TRY(RAPIER_FN(LastStatus)()); + if (count) { + hits = malloc(count * sizeof(*hits)); + if (!hits) { + abort(); + } + count = RAPIER_FN(IntersectPoint)(t->world, &query, t->cursor, hits, count); + TRY(RAPIER_FN(LastStatus)()); + for (size_t i = 0; i < count; ++i) { + RAPIER_TYPE(SoftBodyHandle) soft; + TRY(RAPIER_FN(Collider_ValidateHandle)(hits[i])); + *body = RAPIER_FN(Collider_Parent)(hits[i]); + TRY(RAPIER_FN(LastStatus)()); + if (body->index == UINT32_MAX) { + continue; + } + TRY(RAPIER_FN(RigidBody_ValidateHandle)(*body)); + soft = RAPIER_FN(RigidBody_SoftBody)(*body); + TRY(RAPIER_FN(LastStatus)()); + if (soft.index != UINT32_MAX) { + RAPIER_TYPE(Bool) closed; + TRY(RAPIER_FN(SoftBody_ValidateHandle)(soft)); + closed = RAPIER_FN(SoftBody_MeshIsClosed)(soft, hits[i]); + TRY(RAPIER_FN(LastStatus)()); + if (!closed) { + continue; + } + } + *point = t->cursor; + *found = true; + goto done; + } + } + /* Thin surfaces can also be picked within eight screen pixels. Project onto + * the boundary so an open polyline's nominal inside half-plane is not a hit. */ + RAPIER_TYPE(PointProjection) + hit = RAPIER_FN(ProjectPoint)(t->world, &query, t->cursor, radius, 0); + status = RAPIER_FN(LastStatus)(); + if (status == RAPIER_CONST(NOT_FOUND)) { + status = RAPIER_CONST(OK); + goto done; + } + if (status != RAPIER_CONST(OK)) { + goto done; + } + collider = hit.collider; + *point = hit.point; +#else + (void)radius; + if (!t->rayValid) { + goto done; + } + RAPIER_TYPE(Real) toi; + RAPIER_TYPE(Bool) hit; + RAPIER_TYPE(RayToi) + castRayToiResult2 = + RAPIER_FN(CastRayToi)(t->world, &query, t->rayOrigin, t->rayDirection, FLT_MAX, 1); + collider = castRayToiResult2.collider; + toi = castRayToiResult2.toi; + hit = castRayToiResult2.found; + TRY(RAPIER_FN(LastStatus)()); + if (!hit) { + goto done; + } + *point = RAPIER_FN(VectorAdd)(t->rayOrigin, RAPIER_FN(VectorScale)(t->rayDirection, toi)); +#endif + + TRY(RAPIER_FN(Collider_ValidateHandle)(collider)); + *body = RAPIER_FN(Collider_Parent)(collider); + TRY(RAPIER_FN(LastStatus)()); + *found = body->index != UINT32_MAX; +done: +#if defined(RAPIER_DIM2) + free(hits); +#endif + return status; +} + +RAPIER_TYPE(Status) tbGrabBegin(Testbed *t, TbGrab *grab, RAPIER_TYPE(Real) pickRadius) { + if (grab->active) { + return RAPIER_CONST(OK); + } + RAPIER_TYPE(Status) status = RAPIER_CONST(OK); + RAPIER_TYPE(RigidBodyDesc) builder; + RAPIER_TYPE(JointDesc) joint; + RAPIER_TYPE(Vector) point, localAnchor = V(0, 0, 0); + RAPIER_TYPE(RigidBodyHandle) body; + bool found; + TRY(pick(t, pickRadius, &body, &point, &found)); + if (!found) { + goto done; + } + + RAPIER_TYPE(Bool) dynamic; + RAPIER_TYPE(SoftBodyHandle) soft; + TRY(RAPIER_FN(RigidBody_ValidateHandle)(body)); + dynamic = RAPIER_FN(RigidBody_IsDynamic)(body); + TRY(RAPIER_FN(LastStatus)()); + if (!dynamic) { + goto done; + } + soft = RAPIER_FN(RigidBody_SoftBody)(body); + TRY(RAPIER_FN(LastStatus)()); + *grab = (TbGrab){.body = body, + .mouseBody = {NULL, UINT32_MAX, UINT32_MAX}, + .joint = {NULL, UINT32_MAX, UINT32_MAX}, + .soft = soft.index != UINT32_MAX}; + if (grab->soft) { + size_t count; + uint32_t nearest = 0, cluster; + RAPIER_TYPE(Real) best = FLT_MAX; + TRY(RAPIER_FN(SoftBody_ValidateHandle)(soft)); + count = RAPIER_FN(SoftBody_NumParticles)(soft); + TRY(RAPIER_FN(LastStatus)()); + if (!count) { + goto done; + } + for (size_t i = 0; i < count; ++i) { + RAPIER_TYPE(Vector) position = RAPIER_FN(SoftBody_ParticlePosition)(soft, i); + TRY(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(Vector) delta = RAPIER_FN(VectorSub)(position, point); + RAPIER_TYPE(Real) distance = RAPIER_FN(VectorDot)(delta, delta); + if (distance < best) { + best = distance; + nearest = (uint32_t)i; + } + } + point = RAPIER_FN(SoftBody_ParticlePosition)(soft, nearest); + TRY(RAPIER_FN(LastStatus)()); + cluster = RAPIER_FN(SoftBody_AddCluster)(soft, &nearest, 1); + TRY(RAPIER_FN(LastStatus)()); + TRY(RAPIER_FN(SoftBody_ValidateHandle)(soft)); + grab->body = RAPIER_FN(SoftBody_ClusterProxy)(soft, cluster); + TRY(RAPIER_FN(LastStatus)()); + } else { + RAPIER_TYPE(Pose) pose = RAPIER_FN(RigidBody_Position)(body); + TRY(RAPIER_FN(LastStatus)()); + localAnchor = RAPIER_FN(PoseTransformPoint)(RAPIER_FN(PoseInverse)(pose), point); + } + grab->active = true; + grab->planePoint = point; + builder = RAPIER_FN(KinematicPositionBasedRigidBodyDesc)(); + builder.position.translation = point; + grab->mouseBody = RAPIER_FN(InsertRigidBody)(t->world, &builder); + TRY(RAPIER_FN(LastStatus)()); + joint = RAPIER_FN(DefaultJointDesc)(); + joint.localFrame2.translation = localAnchor; + joint.motorAxes = (1u << RAPIER_CONST(DIMENSION)) - 1; + for (uint32_t axis = 0; axis < RAPIER_CONST(DIMENSION); ++axis) { + joint.motors[axis].stiffness = 1000; + joint.motors[axis].damping = 50; + } + grab->joint = RAPIER_FN(InsertImpulseJoint)(grab->mouseBody, grab->body, &joint); + TRY(RAPIER_FN(LastStatus)()); + TRY(RAPIER_FN(RigidBody_WakeUp)(grab->body, 1)); +done: + if (status != RAPIER_CONST(OK)) { + (void)tbGrabRelease(t, grab); + } + return status; +} + +RAPIER_TYPE(Status) tbGrabUpdate(Testbed *t, TbGrab *grab, RAPIER_TYPE(Vector) cameraForward) { + if (!grab->active) { + return RAPIER_CONST(OK); + } + RAPIER_TYPE(RigidBodyHandle) pulled = pulledBody(t, grab); + if (!contains(t, pulled) || !contains(t, grab->mouseBody)) { + return tbGrabRelease(t, grab); + } + RAPIER_TYPE(Vector) target; +#if defined(RAPIER_DIM2) + (void)cameraForward; + if (!t->cursorValid) { + return RAPIER_CONST(OK); + } + target = t->cursor; +#else + if (!t->rayValid) { + return RAPIER_CONST(OK); + } + RAPIER_TYPE(Real) denominator = RAPIER_FN(VectorDot)(t->rayDirection, cameraForward); + if (fabs(denominator) < 1.0e-6) { + return RAPIER_CONST(OK); + } + RAPIER_TYPE(Real) + distance = + RAPIER_FN(VectorDot)(RAPIER_FN(VectorSub)(grab->planePoint, t->rayOrigin), cameraForward) / + denominator; + if (distance <= 0) { + return RAPIER_CONST(OK); + } + target = RAPIER_FN(VectorAdd)(t->rayOrigin, RAPIER_FN(VectorScale)(t->rayDirection, distance)); +#endif + RAPIER_TYPE(Status) status = RAPIER_CONST(OK); + + TRY(RAPIER_FN(RigidBody_ValidateHandle)(grab->mouseBody)); + TRY(RAPIER_FN(RigidBody_SetNextKinematicTranslation)(grab->mouseBody, target)); + TRY(RAPIER_FN(RigidBody_WakeUp)(pulled, 1)); +done: + return status; +} + +void tbGrabDrawCue(Testbed *t, const TbGrab *grab) { + if (!grab->active) { + return; + } + RAPIER_TYPE(RigidBodyHandle) pulled = pulledBody(t, grab); + if (!contains(t, pulled) || !contains(t, grab->mouseBody)) { + return; + } + + RAPIER_TYPE(Vector) a, b; + a = RAPIER_FN(RigidBody_Translation)(grab->mouseBody); + b = RAPIER_FN(RigidBody_Translation)(pulled); + if (RAPIER_FN(RigidBody_ValidateHandle)(grab->mouseBody) != RAPIER_CONST(OK) || + RAPIER_FN(LastStatus)() != RAPIER_CONST(OK) || + RAPIER_FN(RigidBody_ValidateHandle)(pulled) != RAPIER_CONST(OK) || + RAPIER_FN(LastStatus)() != RAPIER_CONST(OK)) { + return; + } + tbLine(t, a, b, .2f, .3f, .4f, 1); +} diff --git a/c/testbed/grab.h b/c/testbed/grab.h new file mode 100644 index 000000000..3aa77ee72 --- /dev/null +++ b/c/testbed/grab.h @@ -0,0 +1,18 @@ +#ifndef RAPIER_TESTBED_GRAB_H +#define RAPIER_TESTBED_GRAB_H +#include "testbed.h" +#include + +/* Mirrors src_testbed/grab.rs: a motor joint pulls a body or one soft particle. */ +typedef struct TbGrab { + bool active, soft; + RAPIER_TYPE(RigidBodyHandle) body, mouseBody; + RAPIER_TYPE(ImpulseJointHandle) joint; + RAPIER_TYPE(Vector) planePoint; +} TbGrab; + +RAPIER_TYPE(Status) tbGrabBegin(Testbed *, TbGrab *, RAPIER_TYPE(Real) pickRadius); +RAPIER_TYPE(Status) tbGrabUpdate(Testbed *, TbGrab *, RAPIER_TYPE(Vector) cameraForward); +RAPIER_TYPE(Status) tbGrabRelease(Testbed *, TbGrab *); +void tbGrabDrawCue(Testbed *, const TbGrab *); +#endif diff --git a/c/testbed/graphics.c b/c/testbed/graphics.c new file mode 100644 index 000000000..2bcfd335b --- /dev/null +++ b/c/testbed/graphics.c @@ -0,0 +1,816 @@ +#include "testbed_internal.h" +#include "graphics.h" +#include "raymath.h" +#include "rlgl.h" +#include +#include + +/* Cache by collider generation and immutable shape identity. Geometry is deduplicated + * by content so separately constructed equal shapes still share instanced draws. */ +typedef struct Geometry { + Mesh mesh; + RAPIER_TYPE(Vector) *triangles, *lines; + size_t nt, nl; + uint64_t hash; + bool allocated, live; +} Geometry; + +typedef struct Entry { + uint32_t generation; + uintptr_t identity; + RAPIER_TYPE(SharedShape) *shape; + size_t geometry; + bool valid, soft, seen; +} Entry; + +typedef struct Batch { + size_t geometry; + Color color; + Matrix *transforms; + size_t count, capacity; +} Batch; + +typedef struct TransparentDraw { + size_t batch, instance; + float depth; + bool triangle; + Vector3 vertices[3]; + Color color; +} TransparentDraw; + +typedef struct VisualMesh { + Mesh mesh; + Material material; + Texture2D texture; +} VisualMesh; + +struct TbGraphics { + Geometry *geometries; + size_t geometryCount, geometryCapacity; + Entry *entries; + size_t entryCapacity; + Batch *batches; + size_t batchCount, batchCapacity; + TransparentDraw *transparent; + size_t transparentCount, transparentCapacity; + RAPIER_TYPE(ColliderHandle) *handles; + RAPIER_TYPE(SoftMeshInfo) *softMeshes; + size_t handlesCapacity, softMeshCapacity; + RAPIER_TYPE(SoftBodyHandle) *softHandles; + size_t softHandlesCapacity; + RAPIER_TYPE(Vector) *vertices; + size_t vertexCapacity; + uint32_t *indices; + size_t indexCapacity; + RAPIER_TYPE(DebugLine) *debug; + size_t debugCapacity; + VisualMesh *visuals; + size_t visualCount; + Shader visualShader; + Shader shader; + Material material; +}; + +static void *grow(void *p, size_t *cap, size_t n, size_t elem) { + if (n <= *cap) { + return p; + } + size_t old = *cap; + size_t next = old ? old * 2 : 16; + if (next < n) { + next = n; + } + if (next > SIZE_MAX / elem) { + abort(); + } + void *q = realloc(p, next * elem); + if (!q) { + abort(); + } + memset((char *)q + old * elem, 0, (next - old) * elem); + *cap = next; + return q; +} + +static Vector3 vec(RAPIER_TYPE(Vector) p) { +#if defined(RAPIER_DIM3) + return (Vector3){(float)p.x, (float)p.y, (float)p.z}; +#else + return (Vector3){(float)p.x, (float)p.y, 0}; +#endif +} + +static Matrix transform(RAPIER_TYPE(Pose) p) { +#if defined(RAPIER_DIM3) + Matrix m = QuaternionToMatrix((Quaternion){(float)p.rotation.x, (float)p.rotation.y, + (float)p.rotation.z, (float)p.rotation.w}); +#else + Matrix m = MatrixRotateZ((float)p.rotation.angle); +#endif + Vector3 v = vec(p.translation); + m.m12 = v.x; + m.m13 = v.y; + m.m14 = v.z; + return m; +} + +static const Color palette[] = {{82, 130, 150, 255}, {222, 139, 89, 255}, {121, 165, 108, 255}, + {159, 133, 184, 255}, {217, 180, 75, 255}, {94, 162, 164, 255}, + {193, 115, 136, 255}, {132, 149, 178, 255}}; + +static Color color(Testbed *t, RAPIER_TYPE(ColliderHandle) h) { + RAPIER_TYPE(RigidBodyHandle) p; + RAPIER_TYPE(Bool) sensor; + p = RAPIER_FN(Collider_Parent)(h); + TB(t, RAPIER_FN(LastStatus)()); + sensor = RAPIER_FN(Collider_IsSensor)(h); + TB(t, RAPIER_FN(LastStatus)()); + Color result = palette[(p.index == UINT32_MAX ? h.index : p.index) % TB_COUNT(palette)]; + if (p.index == UINT32_MAX) { + result = (Color){170, 174, 175, 255}; + } else { + RAPIER_TYPE(Bool) fixed; + TB(t, RAPIER_FN(RigidBody_ValidateHandle)(p)); + fixed = RAPIER_FN(RigidBody_IsFixed)(p); + TB(t, RAPIER_FN(LastStatus)()); + if (fixed) { + result = (Color){170, 174, 175, 255}; + } + } + const float *rgba = tbFindColor(t, p, h); + if (rgba) { + result = (Color){(unsigned char)(rgba[0] * 255), (unsigned char)(rgba[1] * 255), + (unsigned char)(rgba[2] * 255), (unsigned char)(rgba[3] * 255)}; + } + if (sensor) { + result.a = (unsigned char)(result.a * 0.4f); + } + return result; +} + +TbGraphics *tbGraphicsNew(void) { + TbGraphics *g = calloc(1, sizeof(*g)); + if (!g) { + abort(); + } + const char *vs = + "#version 330\nin vec3 vertexPosition;in vec3 vertexNormal;in mat4 " + "instanceTransform;uniform mat4 mvp;out float light;void main(){vec3 " + "n=normalize(mat3(instanceTransform)*vertexNormal);light=0.5+0.5*abs(dot(n,normalize(vec3(" + "0.5,0.8,0.6))));gl_Position=mvp*instanceTransform*vec4(vertexPosition,1.0);}"; + const char *fs = "#version 330\nin float light;uniform vec4 colDiffuse;out vec4 " + "finalColor;void main(){finalColor=vec4(colDiffuse.rgb*light,colDiffuse.a);}"; + g->shader = LoadShaderFromMemory(vs, fs); + if (!IsShaderValid(g->shader)) { + fputs("instancing shader failed\n", stderr); + abort(); + } + g->shader.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] = + GetShaderLocationAttrib(g->shader, "instanceTransform"); + const char *visualVs = + "#version 330\nin vec3 vertexPosition;in vec3 vertexNormal;in vec2 vertexTexCoord;" + "in mat4 instanceTransform;uniform mat4 mvp;out vec3 position;out vec3 normal;out vec2 uv;" + "void main(){position=vec3(instanceTransform*vec4(vertexPosition,1));" + "normal=mat3(instanceTransform)*vertexNormal;uv=vertexTexCoord;" + "gl_Position=mvp*vec4(position,1);}"; + const char *visualFs = + "#version 330\nin vec3 position;in vec3 normal;in vec2 uv;out vec4 finalColor;" + "uniform sampler2D texture0;uniform vec4 colDiffuse;uniform vec3 eye;" + "uniform float metallic;uniform float roughness;uniform float reflectance;uniform vec3 " + "emissive;" + "void main(){vec4 base=texture(texture0,uv)*colDiffuse;vec3 n=normalize(normal);" + "if(!gl_FrontFacing)n=-n;vec3 l=normalize(vec3(0.5,0.8,0.6));vec3 " + "v=normalize(eye-position);" + "vec3 h=normalize(l+v);float nl=max(dot(n,l),0.0),nv=max(dot(n,v),0.001);" + "float nh=max(dot(n,h),0.0),vh=max(dot(v,h),0.0);float a=max(roughness*roughness,0.002);" + "float a2=a*a,d=nh*nh*(a2-1.0)+1.0;float D=a2/(3.141593*d*d);" + "float k=(roughness+1.0)*(roughness+1.0)/8.0;" + "float G=nl/(nl*(1.0-k)+k)*nv/(nv*(1.0-k)+k);" + "vec3 F0=mix(vec3(0.16*reflectance*reflectance),base.rgb,metallic);" + "vec3 F=F0+(1.0-F0)*pow(1.0-vh,5.0);" + "vec3 diffuse=(1.0-F)*(1.0-metallic)*base.rgb/3.141593;" + "vec3 specular=D*G*F/max(4.0*nl*nv,0.001);" + "vec3 color=base.rgb*0.22+(diffuse+specular)*nl*2.5+emissive;" + "finalColor=vec4(color,base.a);}"; + g->visualShader = LoadShaderFromMemory(visualVs, visualFs); + if (!IsShaderValid(g->visualShader)) { + abort(); + } + g->visualShader.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] = + GetShaderLocationAttrib(g->visualShader, "instanceTransform"); + g->material = LoadMaterialDefault(); + g->material.shader = g->shader; + return g; +} + +void tbGraphicsFree(TbGraphics *g) { + if (!g) { + return; + } + for (size_t i = 0; i < g->visualCount; ++i) { + UnloadMesh(g->visuals[i].mesh); + if (g->visuals[i].texture.id) { + UnloadTexture(g->visuals[i].texture); + } + MemFree(g->visuals[i].material.maps); + } + free(g->visuals); + UnloadShader(g->visualShader); + for (size_t i = 0; i < g->geometryCount; i++) { + Geometry *m = &g->geometries[i]; + if (m->nt) { + UnloadMesh(m->mesh); + } + free(m->triangles); + free(m->lines); + } + for (size_t i = 0; i < g->entryCapacity; i++) { + (void)RAPIER_FN(FreeSharedShape)(g->entries[i].shape); + } + for (size_t i = 0; i < g->batchCount; i++) { + free(g->batches[i].transforms); + } + free(g->geometries); + free(g->entries); + free(g->batches); + free(g->transparent); + free(g->handles); + free(g->softMeshes); + free(g->softHandles); + free(g->vertices); + free(g->indices); + free(g->debug); /* shader is owned separately from Material */ + g->material.shader = (Shader){0}; + UnloadMaterial(g->material); + UnloadShader(g->shader); + free(g); +} + +static uint64_t hashBytes(uint64_t h, const void *v, size_t n) { + const unsigned char *p = v; + while (n--) { + h = (h ^ *p++) * UINT64_C(1099511628211); + } + return h; +} + +static size_t geometry(TbGraphics *g, Testbed *t, RAPIER_TYPE(SharedShape) *shape) { + RAPIER_TYPE(ShapeMesh) *source = RAPIER_FN(SharedShape_Tessellate)(shape, 16); + TB(t, RAPIER_FN(LastStatus)()); + size_t nt = 0, nl = 0; + nt = RAPIER_FN(ShapeMesh_Triangles)(source, NULL, 0); + TB(t, RAPIER_FN(LastStatus)()); + nl = RAPIER_FN(ShapeMesh_Lines)(source, NULL, 0); + TB(t, RAPIER_FN(LastStatus)()); + RAPIER_TYPE(Vector) *tri = calloc(nt ? nt : 1, sizeof(*tri)), + *lines = calloc(nl ? nl : 1, sizeof(*lines)); + if (!tri || !lines) { + abort(); + } + nt = RAPIER_FN(ShapeMesh_Triangles)(source, tri, nt); + TB(t, RAPIER_FN(LastStatus)()); + nl = RAPIER_FN(ShapeMesh_Lines)(source, lines, nl); + TB(t, RAPIER_FN(LastStatus)()); + TB(t, RAPIER_FN(FreeShapeMesh)(source)); + uint64_t hash = hashBytes(hashBytes(UINT64_C(14695981039346656037), tri, nt * sizeof(*tri)), + lines, nl * sizeof(*lines)); + for (size_t i = 0; i < g->geometryCount; i++) { + Geometry *m = &g->geometries[i]; + if (m->allocated && m->hash == hash && m->nt == nt && m->nl == nl && + !memcmp(m->triangles, tri, nt * sizeof(*tri)) && + !memcmp(m->lines, lines, nl * sizeof(*lines))) { + free(tri); + free(lines); + return i; + } + } + g->geometries = + grow(g->geometries, &g->geometryCapacity, g->geometryCount + 1, sizeof(*g->geometries)); + size_t id = 0; + while (id < g->geometryCount && g->geometries[id].allocated) { + id++; + } + if (id == g->geometryCount) { + g->geometryCount++; + } + Geometry *m = &g->geometries[id]; + m->allocated = true; + m->triangles = tri; + m->lines = lines; + m->nt = nt; + m->nl = nl; + m->hash = hash; + if (nt) { + if (nt > INT_MAX / 3) { + abort(); + } + m->mesh.vertexCount = (int)nt; + m->mesh.triangleCount = (int)(nt / 3); + m->mesh.vertices = MemAlloc((unsigned int)(nt * 3 * sizeof(float))); + m->mesh.normals = MemAlloc((unsigned int)(nt * 3 * sizeof(float))); + if (!m->mesh.vertices || !m->mesh.normals) { + abort(); + } + for (size_t i = 0; i < nt; i += 3) { + Vector3 a = vec(tri[i]), b = vec(tri[i + 1]), c = vec(tri[i + 2]); + Vector3 n = + Vector3Normalize(Vector3CrossProduct(Vector3Subtract(b, a), Vector3Subtract(c, a))); + for (size_t j = 0; j < 3; j++) { + Vector3 v = vec(tri[i + j]); + memcpy(&m->mesh.vertices[(i + j) * 3], &v, sizeof(v)); + memcpy(&m->mesh.normals[(i + j) * 3], &n, sizeof(n)); + } + } + UploadMesh(&m->mesh, false); + } + return id; +} + +static void instance(TbGraphics *g, size_t geometryId, Color c, Matrix m) { + Batch *b = NULL; + for (size_t i = 0; i < g->batchCount; i++) { + if (g->batches[i].geometry == geometryId && !memcmp(&g->batches[i].color, &c, sizeof(c))) { + b = &g->batches[i]; + break; + } + } + if (!b) { + g->batches = grow(g->batches, &g->batchCapacity, g->batchCount + 1, sizeof(*g->batches)); + b = &g->batches[g->batchCount++]; + memset(b, 0, sizeof(*b)); + b->geometry = geometryId; + b->color = c; + } + b->transforms = grow(b->transforms, &b->capacity, b->count + 1, sizeof(*b->transforms)); + b->transforms[b->count++] = m; +} + +static Entry *entry(TbGraphics *g, RAPIER_TYPE(ColliderHandle) h) { + /* A render-only mesh has no collider and must never index this cache. */ + if (h.index == UINT32_MAX) { + abort(); + } + g->entries = grow(g->entries, &g->entryCapacity, (size_t)h.index + 1, sizeof(*g->entries)); + return &g->entries[h.index]; +} + +/* Deforming triangles share the same sorted transparency pass as rigid sensors. */ +static void softTriangle(TbGraphics *g, Vector3 a, Vector3 b, Vector3 c, Color color, + Camera3D camera) { + if (color.a == 255) { + DrawTriangle3D(a, b, c, color); + return; + } + Vector3 center = Vector3Scale(Vector3Add(Vector3Add(a, b), c), 1.0f / 3); + Vector3 forward = Vector3Normalize(Vector3Subtract(camera.target, camera.position)); + g->transparent = grow(g->transparent, &g->transparentCapacity, g->transparentCount + 1, + sizeof(*g->transparent)); + g->transparent[g->transparentCount++] = (TransparentDraw){ + .depth = Vector3DotProduct(Vector3Subtract(center, camera.position), forward), + .triangle = true, + .vertices = {a, b, c}, + .color = color}; +} + +/* Screen-facing ribbons keep deformable polylines readable on every GL backend, + * including those that only support one-pixel native lines. */ +static void drawSoftSegment(TbGraphics *g, Vector3 a, Vector3 b, Color color, Camera3D camera) { + Vector3 middle = Vector3Scale(Vector3Add(a, b), .5f); + Vector3 view = Vector3Normalize(Vector3Subtract(camera.target, camera.position)); + Vector3 side = Vector3Normalize(Vector3CrossProduct(Vector3Subtract(b, a), view)); + float height = fmaxf(1, (float)GetScreenHeight()); + float worldPerPixel = camera.fovy / height; + if (camera.projection == CAMERA_PERSPECTIVE) { + float depth = Vector3DotProduct(Vector3Subtract(middle, camera.position), view); + worldPerPixel = 2 * fmaxf(.001f, depth) * tanf(camera.fovy * DEG2RAD * .5f) / height; + } + side = Vector3Scale(side, worldPerPixel * 1.5f); /* Three pixels wide. */ + Vector3 a0 = Vector3Subtract(a, side), a1 = Vector3Add(a, side); + Vector3 b0 = Vector3Subtract(b, side), b1 = Vector3Add(b, side); + softTriangle(g, a0, b0, b1, color, camera); + softTriangle(g, a0, b1, a1, color, camera); +} + +static void drawSoft(TbGraphics *g, Testbed *t, bool surfaces, Camera3D camera) { + size_t n = RAPIER_FN(SoftBodyHandles)(t->world, NULL, 0); + TB(t, RAPIER_FN(LastStatus)()); + g->softHandles = grow(g->softHandles, &g->softHandlesCapacity, n, sizeof(*g->softHandles)); + n = RAPIER_FN(SoftBodyHandles)(t->world, g->softHandles, g->softHandlesCapacity); + TB(t, RAPIER_FN(LastStatus)()); + for (size_t i = 0; i < n; i++) { + TB(t, RAPIER_FN(SoftBody_ValidateHandle)(g->softHandles[i])); + size_t nm = RAPIER_FN(SoftBody_Meshes)(g->softHandles[i], NULL, 0); + TB(t, RAPIER_FN(LastStatus)()); + g->softMeshes = grow(g->softMeshes, &g->softMeshCapacity, nm, sizeof(*g->softMeshes)); + nm = RAPIER_FN(SoftBody_Meshes)(g->softHandles[i], g->softMeshes, + g->softMeshCapacity); + TB(t, RAPIER_FN(LastStatus)()); + bool drawnSkin = false; + for (size_t j = 0; j < nm; ++j) { + drawnSkin |= g->softMeshes[j].is_skinned && !g->softMeshes[j].collision_enabled; + } + for (size_t j = 0; j < nm; j++) { + const RAPIER_TYPE(SoftMeshInfo) *mesh = &g->softMeshes[j]; + RAPIER_TYPE(ColliderHandle) h = mesh->collider; + if (h.index != UINT32_MAX) { + entry(g, h)->soft = true; + entry(g, h)->seen = true; + } + /* Match the Rust viewer: drawn skins replace collision boundaries; + * non-colliding computational boundaries are not render objects. */ + if (!surfaces || (!mesh->collision_enabled && !mesh->is_skinned) || + (drawnSkin && mesh->collision_enabled)) { + continue; + } + size_t nv = 0, ni = 0, arity = mesh->arity; + nv = + RAPIER_FN(SoftBody_MeshVerticesById)(g->softHandles[i], mesh->id, NULL, 0); + TB(t, RAPIER_FN(LastStatus)()); + ni = RAPIER_FN(SoftBody_MeshIndicesById)(g->softHandles[i], mesh->id, NULL, 0); + TB(t, RAPIER_FN(LastStatus)()); + g->vertices = grow(g->vertices, &g->vertexCapacity, nv, sizeof(*g->vertices)); + g->indices = grow(g->indices, &g->indexCapacity, ni, sizeof(*g->indices)); + nv = RAPIER_FN(SoftBody_MeshVerticesById)(g->softHandles[i], mesh->id, + g->vertices, g->vertexCapacity); + TB(t, RAPIER_FN(LastStatus)()); + ni = RAPIER_FN(SoftBody_MeshIndicesById)(g->softHandles[i], mesh->id, + g->indices, g->indexCapacity); + TB(t, RAPIER_FN(LastStatus)()); + Color c = palette[g->softHandles[i].index % TB_COUNT(palette)]; + RAPIER_TYPE(RigidBodyHandle) + root = RAPIER_FN(SoftBody_RootBody)(g->softHandles[i]); + TB(t, RAPIER_FN(LastStatus)()); + const float *tint = tbFindColor(t, root, h); + if (tint) { + c = (Color){(unsigned char)(tint[0] * 255), (unsigned char)(tint[1] * 255), + (unsigned char)(tint[2] * 255), (unsigned char)(tint[3] * 255)}; + } + if (h.index != UINT32_MAX) { + RAPIER_TYPE(Bool) sensor; + TB(t, RAPIER_FN(Collider_ValidateHandle)(h)); + sensor = RAPIER_FN(Collider_IsSensor)(h); + TB(t, RAPIER_FN(LastStatus)()); + if (sensor) { + c.a = (unsigned char)(c.a * .4f); + } + } + if (arity == 3) { + for (size_t k = 0; k + 2 < ni; k += 3) { + Vector3 a = vec(g->vertices[g->indices[k]]), + b = vec(g->vertices[g->indices[k + 1]]), + v = vec(g->vertices[g->indices[k + 2]]); + Vector3 norm = Vector3Normalize( + Vector3CrossProduct(Vector3Subtract(b, a), Vector3Subtract(v, a))); + float light = + 0.5f + 0.5f * fabsf(Vector3DotProduct( + norm, Vector3Normalize((Vector3){0.5f, 0.8f, 0.6f}))); + softTriangle(g, a, b, v, + (Color){(unsigned char)(c.r * light), (unsigned char)(c.g * light), + (unsigned char)(c.b * light), c.a}, + camera); + } + } else { + for (size_t k = 0; k + 1 < ni; k += 2) { + drawSoftSegment(g, vec(g->vertices[g->indices[k]]), + vec(g->vertices[g->indices[k + 1]]), c, camera); + } + } + } + } +} + +static void drawVisualMeshes(TbGraphics *g, Testbed *t, Camera3D camera) { + if (!g->visuals && t->renderMeshCount) { + g->visuals = calloc(t->renderMeshCount, sizeof(*g->visuals)); + if (!g->visuals) { + abort(); + } + g->visualCount = t->renderMeshCount; + for (size_t i = 0; i < g->visualCount; ++i) { + const TbRenderMesh *source = &t->renderMeshes[i]; + VisualMesh *visual = &g->visuals[i]; + Mesh *mesh = &visual->mesh; + if (source->indexCount > INT_MAX || + source->indexCount > UINT_MAX / (3 * sizeof(float))) { + abort(); + } + mesh->vertexCount = (int)source->indexCount; + mesh->triangleCount = mesh->vertexCount / 3; + mesh->vertices = MemAlloc((unsigned)(source->indexCount * 3 * sizeof(float))); + mesh->normals = MemAlloc((unsigned)(source->indexCount * 3 * sizeof(float))); + mesh->texcoords = MemAlloc((unsigned)(source->indexCount * 2 * sizeof(float))); + if (!mesh->vertices || !mesh->normals || !mesh->texcoords) { + abort(); + } + for (size_t j = 0; j < source->indexCount; j += 3) { + Vector3 a = vec(source->vertices[source->indices[j]]); + Vector3 b = vec(source->vertices[source->indices[j + 1]]); + Vector3 c = vec(source->vertices[source->indices[j + 2]]); + Vector3 normal = Vector3Normalize( + Vector3CrossProduct(Vector3Subtract(b, a), Vector3Subtract(c, a))); + for (size_t k = j; k < j + 3; ++k) { + uint32_t id = source->indices[k]; + Vector3 vertex = vec(source->vertices[id]); + memcpy(mesh->vertices + k * 3, &vertex, sizeof(vertex)); + memcpy(mesh->normals + k * 3, + source->normals ? source->normals + id * 3 : (float *)&normal, + 3 * sizeof(float)); + mesh->texcoords[k * 2] = source->uvs ? source->uvs[id * 2] : 0; + mesh->texcoords[k * 2 + 1] = source->uvs ? source->uvs[id * 2 + 1] : 0; + } + } + UploadMesh(mesh, false); + visual->material = LoadMaterialDefault(); + visual->material.shader = g->visualShader; + const float *color = source->rgba; + visual->material.maps[MATERIAL_MAP_DIFFUSE].color = + (Color){(unsigned char)(color[0] * 255), (unsigned char)(color[1] * 255), + (unsigned char)(color[2] * 255), (unsigned char)(color[3] * 255)}; + if (source->texture && source->texture[0]) { + visual->texture = LoadTexture(source->texture); + if (visual->texture.id) { + visual->material.maps[MATERIAL_MAP_DIFFUSE].texture = visual->texture; + } + } + } + } + SetShaderValue(g->visualShader, GetShaderLocation(g->visualShader, "eye"), &camera.position, + SHADER_UNIFORM_VEC3); + for (size_t i = 0; i < g->visualCount; ++i) { + const TbRenderMesh *source = &t->renderMeshes[i]; + + RAPIER_TYPE(Pose) pose; + TB(t, RAPIER_FN(RigidBody_ValidateHandle)(source->body)); + pose = RAPIER_FN(RigidBody_Position)(source->body); + TB(t, RAPIER_FN(LastStatus)()); + Matrix matrix = MatrixMultiply(transform(source->localPose), transform(pose)); + SetShaderValue(g->visualShader, GetShaderLocation(g->visualShader, "metallic"), + &source->metallic, SHADER_UNIFORM_FLOAT); + SetShaderValue(g->visualShader, GetShaderLocation(g->visualShader, "roughness"), + &source->roughness, SHADER_UNIFORM_FLOAT); + SetShaderValue(g->visualShader, GetShaderLocation(g->visualShader, "reflectance"), + &source->reflectance, SHADER_UNIFORM_FLOAT); + SetShaderValue(g->visualShader, GetShaderLocation(g->visualShader, "emissive"), + source->emissive, SHADER_UNIFORM_VEC3); + DrawMeshInstanced(g->visuals[i].mesh, g->visuals[i].material, &matrix, 1); + } +} + +static int backToFront(const void *left, const void *right) { + float a = ((const TransparentDraw *)left)->depth; + float b = ((const TransparentDraw *)right)->depth; + return (a < b) - (a > b); +} + +static void drawInstances(TbGraphics *g, Batch *batch, const Matrix *transforms, size_t count) { + Geometry *geometry = &g->geometries[batch->geometry]; + g->material.maps[MATERIAL_MAP_DIFFUSE].color = batch->color; + if (geometry->nt) { + DrawMeshInstanced(geometry->mesh, g->material, transforms, (int)count); + } + for (size_t j = 0; j < count; ++j) { + for (size_t k = 0; k + 1 < geometry->nl; k += 2) { + DrawLine3D(Vector3Transform(vec(geometry->lines[k]), transforms[j]), + Vector3Transform(vec(geometry->lines[k + 1]), transforms[j]), batch->color); + } + } +} + +static void drawTransparent(TbGraphics *g) { + if (!g->transparentCount) { + return; + } + qsort(g->transparent, g->transparentCount, sizeof(*g->transparent), backToFront); + rlDrawRenderBatchActive(); + rlDisableDepthMask(); + for (size_t i = 0; i < g->transparentCount; ++i) { + TransparentDraw *draw = &g->transparent[i]; + if (draw->triangle) { + DrawTriangle3D(draw->vertices[0], draw->vertices[1], draw->vertices[2], draw->color); + } else { + /* Instanced meshes draw immediately; flush queued triangles first to + * preserve the sorted order across both rendering paths. */ + rlDrawRenderBatchActive(); + Batch *batch = &g->batches[draw->batch]; + drawInstances(g, batch, &batch->transforms[draw->instance], 1); + } + } + rlDrawRenderBatchActive(); + rlEnableDepthMask(); +} + +int tbGraphicsDraw(TbGraphics *g, Testbed *t, Camera3D camera, uint32_t debug, bool surfaces) { + if (!t->world || t->error[0]) { + return 0; + } + if (setjmp(t->failure)) { + EndMode3D(); + rlEnableBackfaceCulling(); + return 0; + } + BeginMode3D(camera); + rlDisableBackfaceCulling(); + for (size_t i = 0; i < g->entryCapacity; i++) { + g->entries[i].soft = false; + g->entries[i].seen = false; + } + for (size_t i = 0; i < g->batchCount; i++) { + g->batches[i].count = 0; + } + g->transparentCount = 0; + drawSoft(g, t, surfaces && t->collidersVisible, camera); + if (surfaces) { + drawVisualMeshes(g, t, camera); + } + size_t n = RAPIER_FN(ColliderHandles)(t->world, NULL, 0); + TB(t, RAPIER_FN(LastStatus)()); + g->handles = grow(g->handles, &g->handlesCapacity, n, sizeof(*g->handles)); + n = RAPIER_FN(ColliderHandles)(t->world, g->handles, g->handlesCapacity); + TB(t, RAPIER_FN(LastStatus)()); + for (size_t i = 0; i < n; i++) { + RAPIER_TYPE(ColliderHandle) h = g->handles[i]; + Entry *e = entry(g, h); + e->seen = true; + if (!surfaces || !t->collidersVisible || e->soft) { + continue; + } + TB(t, RAPIER_FN(Collider_ValidateHandle)(h)); + RAPIER_TYPE(Bool) enabled = RAPIER_FN(Collider_IsEnabled)(h); + TB(t, RAPIER_FN(LastStatus)()); + if (!enabled) { + continue; + } + uintptr_t identity = 0; + identity = RAPIER_FN(Collider_ShapeIdentity)(h); + TB(t, RAPIER_FN(LastStatus)()); + if (!e->valid || e->generation != h.generation || e->identity != identity) { + (void)RAPIER_FN(FreeSharedShape)(e->shape); + e->shape = NULL; + e->shape = RAPIER_FN(Collider_CloneShape)(h); + TB(t, RAPIER_FN(LastStatus)()); + e->geometry = geometry(g, t, e->shape); + e->valid = true; + e->generation = h.generation; + e->identity = identity; + } + RAPIER_TYPE(Pose) p = RAPIER_FN(Collider_Position)(h); + TB(t, RAPIER_FN(LastStatus)()); + instance(g, e->geometry, color(t, h), transform(p)); + } + /* Opaque objects retain instancing. Sort translucent instances by depth so + * sensors reveal objects behind them and do not occlude later sensors. */ + Vector3 forward = Vector3Normalize(Vector3Subtract(camera.target, camera.position)); + for (size_t i = 0; i < g->batchCount; ++i) { + Batch *batch = &g->batches[i]; + if (!batch->count) { + continue; + } + if (batch->color.a == 255) { + drawInstances(g, batch, batch->transforms, batch->count); + } else { + g->transparent = grow(g->transparent, &g->transparentCapacity, + g->transparentCount + batch->count, sizeof(*g->transparent)); + for (size_t j = 0; j < batch->count; ++j) { + Matrix transform = batch->transforms[j]; + Vector3 center = {transform.m12, transform.m13, transform.m14}; + g->transparent[g->transparentCount++] = (TransparentDraw){ + .batch = i, + .instance = j, + .depth = Vector3DotProduct(Vector3Subtract(center, camera.position), forward)}; + } + } + } + drawTransparent(g); + if (debug) { + size_t count = RAPIER_FN(DebugRender)(t->world, debug, NULL, 0); + TB(t, RAPIER_FN(LastStatus)()); + g->debug = grow(g->debug, &g->debugCapacity, count, sizeof(*g->debug)); + count = RAPIER_FN(DebugRender)(t->world, debug, g->debug, g->debugCapacity); + TB(t, RAPIER_FN(LastStatus)()); + for (size_t i = 0; i < count; i++) { + RAPIER_TYPE(DebugLine) *line = &g->debug[i]; /* Rapier colors are HSLA, not RGBA. */ + float h = line->color[0], s = line->color[1], l = line->color[2]; + float v = l + s * fminf(l, 1 - l); + Color c = ColorFromHSV(h, v > 0 ? 2 * (1 - l / v) : 0, v); + c.a = (unsigned char)(255 * line->color[3]); + DrawLine3D(vec(line->a), vec(line->b), c); + } + } + for (size_t i = 0; i < t->lineCount; ++i) { + const float *rgba = t->lines[i].rgba; + Color color = {(unsigned char)(255 * rgba[0]), (unsigned char)(255 * rgba[1]), + (unsigned char)(255 * rgba[2]), (unsigned char)(255 * rgba[3])}; + DrawLine3D(vec(t->lines[i].a), vec(t->lines[i].b), color); + } + t->lineCount = 0; + /* Reclaim removed/replaced geometry so fountains and shape edits have bounded caches. */ + for (size_t i = 0; i < g->geometryCount; i++) { + g->geometries[i].live = false; + } + for (size_t i = 0; i < g->entryCapacity; i++) { + Entry *e = &g->entries[i]; + if (!e->seen || e->soft) { + (void)RAPIER_FN(FreeSharedShape)(e->shape); + e->shape = NULL; + e->valid = false; + } else if (e->valid) { + g->geometries[e->geometry].live = true; + } + } + for (size_t i = 0; i < g->geometryCount; i++) { + Geometry *m = &g->geometries[i]; + if (m->allocated && !m->live) { + if (m->nt) { + UnloadMesh(m->mesh); + } + free(m->triangles); + free(m->lines); + memset(m, 0, sizeof(*m)); + } + } + size_t kept = 0; + for (size_t i = 0; i < g->batchCount; i++) { + if (g->batches[i].count) { + g->batches[kept++] = g->batches[i]; + } else { + free(g->batches[i].transforms); + } + } + g->batchCount = kept; + /* EndMode3D flushes queued soft triangles. Keep both faces visible until + * that batch has been drawn, then restore raylib's default for the UI. */ + EndMode3D(); + rlEnableBackfaceCulling(); + return 1; +} + +void tbGraphicsFrameAll(Testbed *t, Camera3D *camera) { + if (!t->world) { + return; + } + if (setjmp(t->failure)) { + return; + } + size_t n = RAPIER_FN(ColliderHandles)(t->world, NULL, 0); + TB(t, RAPIER_FN(LastStatus)()); + RAPIER_TYPE(ColliderHandle) *h = malloc((n ? n : 1) * sizeof(*h)); + if (!h) { + abort(); + } + n = RAPIER_FN(ColliderHandles)(t->world, h, n); + TB(t, RAPIER_FN(LastStatus)()); + Vector3 lo = {1e20f, 1e20f, 1e20f}, hi = {-1e20f, -1e20f, -1e20f}; + bool found = false; + for (size_t i = 0; i < t->renderMeshCount; ++i) { + const TbRenderMesh *mesh = &t->renderMeshes[i]; + + RAPIER_TYPE(Pose) pose; + TB(t, RAPIER_FN(RigidBody_ValidateHandle)(mesh->body)); + pose = RAPIER_FN(RigidBody_Position)(mesh->body); + TB(t, RAPIER_FN(LastStatus)()); + Matrix matrix = MatrixMultiply(transform(mesh->localPose), transform(pose)); + for (size_t j = 0; j < mesh->vertexCount; ++j) { + Vector3 point = Vector3Transform(vec(mesh->vertices[j]), matrix); + lo = Vector3Min(lo, point); + hi = Vector3Max(hi, point); + found = true; + } + } + for (size_t i = 0; t->collidersVisible && i < n; i++) { + RAPIER_TYPE(Aabb) a; + TB(t, RAPIER_FN(Collider_ValidateHandle)(h[i])); + RAPIER_TYPE(RigidBodyHandle) p = RAPIER_FN(Collider_Parent)(h[i]); + TB(t, RAPIER_FN(LastStatus)()); + if (p.index != UINT32_MAX) { + RAPIER_TYPE(Bool) fixed; + TB(t, RAPIER_FN(RigidBody_ValidateHandle)(p)); + fixed = RAPIER_FN(RigidBody_IsFixed)(p); + TB(t, RAPIER_FN(LastStatus)()); + if (fixed) { + continue; + } + } + a = RAPIER_FN(Collider_ComputeAabb)(h[i]); + TB(t, RAPIER_FN(LastStatus)()); + Vector3 l = vec(a.mins), u = vec(a.maxs); + if (Vector3Distance(l, u) > 1e8f) { + continue; + } + lo = Vector3Min(lo, l); + hi = Vector3Max(hi, u); + found = true; + } + free(h); + if (!found) { + return; + } + camera->target = Vector3Scale(Vector3Add(lo, hi), 0.5f); + float size = fmaxf(Vector3Distance(lo, hi), 1.0f); + Vector3 dir = Vector3Normalize(Vector3Subtract(camera->position, camera->target)); +#if defined(RAPIER_DIM2) + dir = (Vector3){0, 0, 1}; + camera->fovy = + fmaxf((hi.y - lo.y) * 1.2f, (hi.x - lo.x) * 1.2f * GetScreenHeight() / GetScreenWidth()); +#endif + camera->position = Vector3Add(camera->target, Vector3Scale(dir, size * 1.2f)); +} diff --git a/c/testbed/graphics.h b/c/testbed/graphics.h new file mode 100644 index 000000000..9136b567b --- /dev/null +++ b/c/testbed/graphics.h @@ -0,0 +1,10 @@ +#ifndef TB_GRAPHICS_H +#define TB_GRAPHICS_H +#include "testbed.h" +#include "raylib.h" +typedef struct TbGraphics TbGraphics; +TbGraphics *tbGraphicsNew(void); +void tbGraphicsFree(TbGraphics *); +int tbGraphicsDraw(TbGraphics *, Testbed *, Camera3D, uint32_t, bool); +void tbGraphicsFrameAll(Testbed *, Camera3D *); +#endif diff --git a/c/testbed/gui.c b/c/testbed/gui.c new file mode 100644 index 000000000..565efe85b --- /dev/null +++ b/c/testbed/gui.c @@ -0,0 +1,928 @@ +#include "testbed_internal.h" +#include "font_data.h" +#include "graphics.h" +#include "grab.h" +#include "raymath.h" +#define CIMGUI_DEFINE_ENUMS_AND_STRUCTS +#include "cimgui.h" +#include "rlImGui.h" +#include +#include +#include +#include + +#define UI2(x, y) ((ImVec2_c){(float)(x), (float)(y)}) +#define UI4(r, g, b, a) ((ImVec4_c){(r), (g), (b), (a)}) +#define UI_HISTORY 180 + +typedef struct UiState { + bool running, surfaces, debug[6]; + bool stepOnce, restart, frameAll, save, restore; + int selected, next, frame, historyCount, historyOffset; + char search[96]; + int threadInput; + char threadError[256]; + const char *profile, *initialTab; + double physicsMs, renderMs; + int physicsSteps; + float physicsHistory[UI_HISTORY], renderHistory[UI_HISTORY]; + RAPIER_TYPE(Bytes) *snapshot; + uint64_t snapshotStep; + double snapshotTime; +} UiState; + +static const uint32_t debugModes[] = {RAPIER_CONST(DEBUG_COLLIDER_SHAPES), + RAPIER_CONST(DEBUG_RIGID_BODY_AXES), + RAPIER_CONST(DEBUG_CONTACTS), + RAPIER_CONST(DEBUG_IMPULSE_JOINTS) | + RAPIER_CONST(DEBUG_MULTIBODY_JOINTS), + RAPIER_CONST(DEBUG_SOFT_BODIES), + RAPIER_CONST(DEBUG_SOFT_BODY_STRESS)}; +static const char *debugNames[] = {"Wireframes", "Body axes", "Contacts", + "Joints", "Soft constraints", "Soft stress"}; + +static bool configureUi(void) { + ImGuiIO *io = igGetIO_Nil(); + io->IniFilename = NULL; + io->LogFilename = NULL; + io->ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + ImFontConfig *config = ImFontConfig_ImFontConfig(); + // The embedded bytes remain alive for the entire ImGui context lifetime. + config->FontDataOwnedByAtlas = false; + io->FontDefault = ImFontAtlas_AddFontFromMemoryTTF( + io->Fonts, (void *)tbFontData, (int)sizeof(tbFontData), 16.0f, config, NULL); + ImFontConfig_destroy(config); + if (!io->FontDefault) { + return false; + } + // ImGui 1.92 rasterizes at the framebuffer density reported by rlImGui. + ImGuiStyle *style = igGetStyle(); + style->FontSizeBase = 16; + style->WindowPadding = UI2(16, 14); + style->FramePadding = UI2(8, 5); + style->ItemSpacing = UI2(8, 7); + style->WindowRounding = 6; + style->FrameRounding = 4; + style->GrabRounding = 4; + style->ChildRounding = 4; + style->ScrollbarRounding = 6; + style->Colors[ImGuiCol_Text] = UI4(0.235f, 0.227f, 0.204f, 1); + style->Colors[ImGuiCol_TextDisabled] = UI4(0.52f, 0.52f, 0.49f, 1); + style->Colors[ImGuiCol_WindowBg] = UI4(0.988f, 0.988f, 0.973f, 1); + style->Colors[ImGuiCol_Border] = UI4(0.784f, 0.776f, 0.745f, 0.6f); + style->Colors[ImGuiCol_FrameBg] = UI4(0.922f, 0.922f, 0.882f, 1); + style->Colors[ImGuiCol_FrameBgHovered] = UI4(0.882f, 0.882f, 0.843f, 1); + style->Colors[ImGuiCol_FrameBgActive] = UI4(0.843f, 0.843f, 0.804f, 1); + style->Colors[ImGuiCol_Button] = UI4(0.922f, 0.922f, 0.882f, 1); + style->Colors[ImGuiCol_ButtonHovered] = UI4(0.78f, 0.85f, 0.86f, 1); + style->Colors[ImGuiCol_ButtonActive] = UI4(0.62f, 0.76f, 0.80f, 1); + style->Colors[ImGuiCol_Header] = UI4(0.88f, 0.91f, 0.88f, 1); + style->Colors[ImGuiCol_HeaderHovered] = UI4(0.78f, 0.85f, 0.86f, 1); + style->Colors[ImGuiCol_HeaderActive] = UI4(0.62f, 0.76f, 0.80f, 1); + style->Colors[ImGuiCol_Tab] = UI4(0.92f, 0.92f, 0.88f, 1); + style->Colors[ImGuiCol_TabHovered] = UI4(0.78f, 0.85f, 0.86f, 1); + style->Colors[ImGuiCol_TabSelected] = UI4(0.78f, 0.85f, 0.86f, 1); + style->Colors[ImGuiCol_CheckMark] = UI4(0.322f, 0.510f, 0.588f, 1); + style->Colors[ImGuiCol_SliderGrab] = UI4(0.322f, 0.510f, 0.588f, 1); + style->Colors[ImGuiCol_PlotLines] = UI4(0.322f, 0.510f, 0.588f, 1); + return true; +} + +static bool setupUi(void) { + rlImGuiSetup(false); + return configureUi(); +} + +static void keyboardShortcuts(UiState *ui) { + ImGuiIO *io = igGetIO_Nil(); + if (io->WantCaptureKeyboard || io->WantTextInput) { + return; + } + if (igIsKeyPressed_Bool(ImGuiKey_T, false)) { + ui->running = !ui->running; + } + if (igIsKeyPressed_Bool(ImGuiKey_S, false)) { + ui->stepOnce = true; + } + if (igIsKeyPressed_Bool(ImGuiKey_R, false)) { + ui->restart = true; + } + if (igIsKeyPressed_Bool(ImGuiKey_F, false)) { + ui->frameAll = true; + } +} + +static void tooltip(const char *text) { + if (igIsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && igBeginTooltip()) { + igPushTextWrapPos(igGetFontSize() * 26); + igTextUnformatted(text, NULL); + igPopTextWrapPos(); + igEndTooltip(); + } +} + +// UI setters report ordinary errors without unwinding an open ImGui window. +static void uiStatus(Testbed *t, RAPIER_TYPE(Status) status) { + if (status != RAPIER_CONST(OK)) { + snprintf(t->error, sizeof(t->error), "%s", RAPIER_FN(LastError)()); + } +} + +static Camera3D cameraFor(Testbed *t) { + Camera3D c = {0}; + c.position = (Vector3){t->eye[0], t->eye[1], t->eye[2]}; + c.target = (Vector3){t->target[0], t->target[1], t->target[2]}; + c.up = (Vector3){t->up[0], t->up[1], t->up[2]}; + c.fovy = 45; + c.projection = CAMERA_PERSPECTIVE; +#if defined(RAPIER_DIM2) + c.position = (Vector3){t->target[0], t->target[1], 100}; + c.target.z = 0; + c.fovy = t->viewWidth * (float)GetScreenHeight() / GetScreenWidth(); + c.projection = CAMERA_ORTHOGRAPHIC; +#endif + return c; +} + +static bool matches(const char *text, const char *query) { + for (; *text; text++) { + size_t i = 0; + while (query[i] && text[i] && + tolower((unsigned char)text[i]) == tolower((unsigned char)query[i])) { + i++; + } + if (!query[i]) { + return true; + } + } + return !*query; +} + +static int abi(void) { + RAPIER_TYPE(Status) + s = RAPIER_FN(CheckAbi)(RAPIER_CONST(ABI_VERSION), RAPIER_CONST(DIMENSION), + sizeof(RAPIER_TYPE(Real)), sizeof(RAPIER_TYPE(Vector)), + sizeof(RAPIER_TYPE(Pose))); + if (s) { + fprintf(stderr, "ABI mismatch: %s\n", RAPIER_FN(LastError)()); + } + return !s; +} + +static void worldSnapshot(Testbed *t, RAPIER_TYPE(Bytes) **snapshot, int save, uint64_t *step, + double *time) { + if (setjmp(t->failure)) { + return; + } + if (save) { + (void)RAPIER_FN(FreeBytes)(*snapshot); + *snapshot = NULL; + *snapshot = RAPIER_FN(SerializeWorld)(t->world); + TB(t, RAPIER_FN(LastStatus)()); + *step = t->step; + *time = t->time; + } else if (*snapshot) { + const uint8_t *data; + size_t n; + RAPIER_TYPE(World) *w = NULL; + RAPIER_TYPE(ByteView) bytesDataResult = RAPIER_FN(Bytes_Data)(*snapshot); + data = bytesDataResult.data; + n = bytesDataResult.count; + TB(t, RAPIER_FN(LastStatus)()); + w = RAPIER_FN(DeserializeWorld)(data, n); + TB(t, RAPIER_FN(LastStatus)()); + TB(t, RAPIER_FN(FreeWorld)(t->world)); + t->world = w; + t->step = *step; + t->time = *time; + tbRefreshWorld(t); + } +} + +static bool exampleMatches(const TbExample *e, const UiState *ui) { + return !e-> + requires && (matches(e->name, ui->search) || matches(e->group, ui->search) || + matches(e->id, ui->search)); +} + +/* Navigation follows the filtered list, wraps, and skips unavailable builds. */ +static int adjacentExample(const UiState *ui, int direction) { + int count = (int)tbExampleCount; + for (int offset = 1; offset < count; ++offset) { + int index = (ui->selected + direction * offset + count) % count; + if (exampleMatches(&tbExamples[index], ui)) { + return index; + } + } + return ui->selected; +} + +static void examplesUi(UiState *ui) { + igSetNextItemWidth(-FLT_MIN); + igInputTextWithHint("##search", "Search examples...", ui->search, sizeof(ui->search), 0, NULL, + NULL); + for (size_t start = 0; start < tbExampleCount;) { + size_t end = start + 1, count = 0; + while (end < tbExampleCount && !strcmp(tbExamples[start].group, tbExamples[end].group)) { + end++; + } + for (size_t i = start; i < end; i++) { + count += exampleMatches(&tbExamples[i], ui); + } + if (count) { + igPushID_Str(tbExamples[start].group); + if (ui->search[0]) { + igSetNextItemOpen(true, ImGuiCond_Always); + } else { + igSetNextItemOpen((size_t)ui->selected >= start && (size_t)ui->selected < end, + ImGuiCond_Once); + } + char label[128]; + snprintf(label, sizeof(label), "%s (%zu)", tbExamples[start].group, count); + if (igCollapsingHeader_TreeNodeFlags(label, 0)) { + for (size_t i = start; i < end; i++) { + const TbExample *e = &tbExamples[i]; + if (!exampleMatches(e, ui)) { + continue; + } + igPushID_Str(e->id); + igBeginDisabled(e->requires != NULL); + if (igSelectable_Bool(e->name, i == (size_t)ui->selected, 0, UI2(0, 0))) { + ui->next = (int)i; + } + igEndDisabled(); + tooltip(e->requires ? e->requires : e->source); + igPopID(); + } + } + igPopID(); + } + start = end; + } +} + +typedef size_t (*ParameterGet)(const RAPIER_TYPE(World) *); +typedef RAPIER_TYPE(Status) (*ParameterSet)(RAPIER_TYPE(World) *, size_t); + +static void iterationSlider(Testbed *t, const char *name, ParameterGet get, ParameterSet set, + int min, int max) { + size_t value = get(t->world); + if (RAPIER_FN(LastStatus)() != RAPIER_CONST(OK)) { + return; + } + int edit = value > (size_t)INT32_MAX ? INT32_MAX : (int)value; + igTextUnformatted(name, NULL); + igSetNextItemWidth(-FLT_MIN); + igPushID_Str(name); + if (igSliderInt("##value", &edit, min, max, "%d", ImGuiSliderFlags_AlwaysClamp)) { + uiStatus(t, set(t->world, (size_t)edit)); + } + igPopID(); +} + +static void threadingUi(Testbed *t, UiState *ui) { + igText("SIMD: %u lanes", t->buildFeatures.simd_lanes); + if (!t->buildFeatures.parallel) { + igTextUnformatted("Parallelism: disabled in this build", NULL); + igTextWrapped("Rebuild with RAPIER_ENABLE_PARALLEL=ON to select multiple workers."); + return; + } + igText("Active workers: %zu", t->activeThreads); + igSetNextItemWidth(145); + igInputInt("Requested workers", &ui->threadInput, 1, 4, 0); + tooltip("0 = automatic selection, 1 = one worker. Applies without resetting " + "the simulation."); + bool valid = ui->threadInput >= 0 && ui->threadInput <= TB_MAX_THREADS; + igBeginDisabled(!valid || (size_t)ui->threadInput == t->requestedThreads); + if (igButton("Apply threads", UI2(0, 0))) { + RAPIER_TYPE(Status) status = tbSetThreads(t, (size_t)ui->threadInput); + if (status != RAPIER_CONST(OK)) { + snprintf(ui->threadError, sizeof(ui->threadError), "%s", RAPIER_FN(LastError)()); + } else { + ui->threadError[0] = 0; + } + } + igEndDisabled(); + igTextDisabled("0 = automatic | 1 = single worker"); + if (!valid) { + igTextWrapped("Choose a worker count from 0 to %d.", TB_MAX_THREADS); + } + if (ui->threadError[0]) { + igTextWrapped("%s", ui->threadError); + } +} + +static void settingsUi(Testbed *t, UiState *ui) { + if (!t->world) { + return; + } + if (igCollapsingHeader_TreeNodeFlags("Execution", ImGuiTreeNodeFlags_DefaultOpen)) { + threadingUi(t, ui); + } + if (igCollapsingHeader_TreeNodeFlags("Simulation", ImGuiTreeNodeFlags_DefaultOpen)) { + RAPIER_TYPE(Real) dt = RAPIER_FN(TimeStep)(t->world); + uiStatus(t, RAPIER_FN(LastStatus)()); + float value = (float)dt; + igTextUnformatted("Timestep (seconds)", NULL); + igSetNextItemWidth(-FLT_MIN); + if (igSliderFloat("##dt", &value, 0.001f, 0.05f, "%.4f", ImGuiSliderFlags_AlwaysClamp)) { + uiStatus(t, RAPIER_FN(SetTimeStep)(t->world, (RAPIER_TYPE(Real))value)); + } + RAPIER_TYPE(Vector) gravity = RAPIER_FN(Gravity)(t->world); + uiStatus(t, RAPIER_FN(LastStatus)()); + igTextUnformatted("Gravity", NULL); + igSetNextItemWidth(-FLT_MIN); + RAPIER_TYPE(Real) + g[] = { + gravity.x, + gravity.y, +#if defined(RAPIER_DIM3) + gravity.z, +#endif + }; + if (igInputScalarN("##gravity", + sizeof(RAPIER_TYPE(Real)) == 4 ? ImGuiDataType_Float + : ImGuiDataType_Double, + g, RAPIER_CONST(DIMENSION), NULL, NULL, "%.2f", 0)) { + uiStatus(t, RAPIER_FN(SetGravity)(t->world, V(g[0], g[1], g[2]))); + } + } + if (igCollapsingHeader_TreeNodeFlags("Solver", 0)) { + iterationSlider(t, "Solver iterations", RAPIER_FN(NumSolverIterations), + RAPIER_FN(SetNumSolverIterations), 1, 32); + iterationSlider(t, "Internal PGS iterations", RAPIER_FN(NumInternalPgsIterations), + RAPIER_FN(SetNumInternalPgsIterations), 1, 32); + iterationSlider(t, "Stabilization iterations", + RAPIER_FN(NumInternalStabilizationIterations), + RAPIER_FN(SetNumInternalStabilizationIterations), 0, 32); + iterationSlider(t, "CCD substeps", RAPIER_FN(MaxCcdSubsteps), RAPIER_FN(SetMaxCcdSubsteps), + 1, 32); + } + if (igCollapsingHeader_TreeNodeFlags("Example settings", ImGuiTreeNodeFlags_DefaultOpen)) { + igTextWrapped("Settings marked Live apply immediately; other changes require a restart."); + bool noSleep = t->noSleep != 0; + if (igCheckbox("Disable sleeping", &noSleep)) { + t->noSleep = noSleep; + } + for (size_t i = 0; i < t->labelCount; ++i) { + igText("%s %s", t->labels[i].name, t->labels[i].value); + } + if (!t->settingCount && !t->labelCount) { + igTextDisabled("No example-specific parameters."); + } + for (size_t i = 0; i < t->settingCount; i++) { + TbSetting *s = &t->settings[i]; + igPushID_Int((int)i); + if (!s->choices && s->integer && s->min == 0 && s->max == 1) { + bool value = s->value != 0; + if (igCheckbox(s->name, &value)) { + s->value = value; + } + igPopID(); + continue; + } + igTextWrapped("%s%s", s->name, s->live ? " (Live)" : ""); + igSetNextItemWidth(-FLT_MIN); + if (s->choices && s->choiceCount) { + int selected = (int)s->value; + if (igBeginCombo("##value", s->choices[selected], ImGuiComboFlags_HeightLarge)) { + for (size_t j = 0; j < s->choiceCount; ++j) { + if (igSelectable_Bool(s->choices[j], selected == (int)j, 0, UI2(0, 0))) { + s->value = (double)j; + } + if (selected == (int)j) { + igSetItemDefaultFocus(); + } + } + igEndCombo(); + } + } else if (s->integer && s->min == 0 && s->max == 1) { + bool value = s->value != 0; + if (igCheckbox("##value", &value)) { + s->value = value; + } + } else if (igSliderScalar("##value", ImGuiDataType_Double, &s->value, &s->min, &s->max, + s->integer ? "%.0f" : "%.3g", ImGuiSliderFlags_AlwaysClamp) && + s->integer) { + s->value = round(s->value); + } + igPopID(); + } + } +} + +static void performanceUi(Testbed *t, UiState *ui) { + igText("%.0f FPS", igGetIO_Nil()->Framerate); + igText("Physics: %.2f ms/step", t->physicsStepMs); + igText("Simulation: %.2f ms/frame (%d step%s)", ui->physicsMs, ui->physicsSteps, + ui->physicsSteps == 1 ? "" : "s"); + igText("Draw CPU: %.2f ms/frame", ui->renderMs); + igTextWrapped("Physics uses the same per-step engine counter as the Rust " + "testbed. Simulation includes the step and scene callbacks " + "in a rendered frame. Draw CPU excludes the UI and GPU execution."); + if (ui->historyCount) { + int offset = ui->historyCount == UI_HISTORY ? ui->historyOffset : 0; + igPlotLines_FloatPtr("##physics", ui->physicsHistory, ui->historyCount, offset, + "Physics (ms/step)", 0, FLT_MAX, UI2(-1, 100), sizeof(float)); + igPlotLines_FloatPtr("##draw", ui->renderHistory, ui->historyCount, offset, "Draw CPU (ms)", + 0, FLT_MAX, UI2(-1, 100), sizeof(float)); + } + igSeparator(); + igText("Simulation time: %.2f s", t->time); + igText("Steps: %" PRIu64, t->step); +} + +static void sidebar(Testbed *t, UiState *ui, float width) { + igSetNextWindowPos(UI2(0, 0), ImGuiCond_Always, UI2(0, 0)); + igSetNextWindowSize(UI2(width, GetScreenHeight()), ImGuiCond_Always); + igBegin("Rapier Testbed", NULL, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings); + igPushFont(NULL, 22); + igTextUnformatted("Rapier C testbed", NULL); + igPopFont(); + const bool release = !strcmp(ui->profile, "release"); + igTextColored(release ? UI4(0.21f, 0.45f, 0.34f, 1) : UI4(0.65f, 0.38f, 0.10f, 1), + "Physics: %s | %dD / f%d", release ? "Release" : "Debug", + RAPIER_CONST(DIMENSION), (int)sizeof(RAPIER_TYPE(Real)) * 8); + tooltip("Cargo build profile reported by the loaded Rapier physics library, " + "independent of the viewer's C/C++ build mode."); + igText("SIMD: %u lanes | Parallel: %s", t->buildFeatures.simd_lanes, + t->buildFeatures.parallel ? "enabled" : "disabled"); + if (t->buildFeatures.parallel && t->activeThreads) { + igTextDisabled("Workers: %zu%s", t->activeThreads, + t->requestedThreads == 0 ? " (automatic)" : ""); + } + tooltip("SIMD lane count and parallel support are reported by the loaded " + "physics library. Configure workers in Settings > Execution."); + igSeparator(); + int previous = adjacentExample(ui, -1), next = adjacentExample(ui, 1); + float navigationWidth = (igGetContentRegionAvail().x - 8) / 2; + igBeginDisabled(previous == ui->selected); + if (igButton("Prev", UI2(navigationWidth, 0))) { + ui->next = previous; + } + igEndDisabled(); + igSameLine(0, -1); + igBeginDisabled(next == ui->selected); + if (igButton("Next", UI2(navigationWidth, 0))) { + ui->next = next; + } + igEndDisabled(); + float contentHeight = fmaxf(96, igGetContentRegionAvail().y - 196); + if (igBeginTabBar("Main tabs", ImGuiTabBarFlags_None)) { + const char *tabs[] = {"Examples", "Settings", "Performance", "Debug"}; + for (size_t i = 0; i < TB_COUNT(tabs); i++) { + ImGuiTabItemFlags flags = (ui->frame == 0 && !strcmp(ui->initialTab, tabs[i])) + ? ImGuiTabItemFlags_SetSelected + : 0; + if (igBeginTabItem(tabs[i], NULL, flags)) { + if (igBeginChild_Str(tabs[i], UI2(0, contentHeight), 0, 0)) { + if (i == 0) { + examplesUi(ui); + } else if (i == 1) { + settingsUi(t, ui); + } else if (i == 2) { + performanceUi(t, ui); + } else { + igCheckbox("Surfaces", &ui->surfaces); + igSeparator(); + for (size_t j = 0; j < TB_COUNT(debugNames); j++) { + igCheckbox(debugNames[j], &ui->debug[j]); + } + igSpacing(); + igTextWrapped("Wireframes and overlays use Rapier's debug renderer."); + } + } + igEndChild(); + igEndTabItem(); + } + } + igEndTabBar(); + } + igSeparator(); + float buttonWidth = (igGetContentRegionAvail().x - 16) / 3; + if (igButton(ui->running ? "Pause [T]" : "Play [T]", UI2(buttonWidth, 0))) { + ui->running = !ui->running; + } + igSameLine(0, -1); + if (igButton("Step [S]", UI2(buttonWidth, 0))) { + ui->stepOnce = true; + } + igSameLine(0, -1); + if (igButton("Restart [R]", UI2(buttonWidth, 0))) { + ui->restart = true; + } + if (igButton("Frame [F]", UI2(buttonWidth, 0))) { + ui->frameAll = true; + } + igSameLine(0, -1); + bool canSnapshot = t->snapshotSupported && !t->error[0]; + igBeginDisabled(!canSnapshot); + if (igButton("Save", UI2(buttonWidth, 0))) { + ui->save = true; + } + tooltip(canSnapshot ? "Save a physics snapshot." + : "This example has local simulation state that physics " + "snapshots do not serialize."); + igSameLine(0, -1); + igBeginDisabled(!ui->snapshot); + if (igButton("Restore", UI2(buttonWidth, 0))) { + ui->restore = true; + } + igEndDisabled(); + igEndDisabled(); + size_t nb = 0, nc = 0, ns = 0; + if (t->world) { + nb = RAPIER_FN(RigidBodyCount)(t->world); + uiStatus(t, RAPIER_FN(LastStatus)()); + nc = RAPIER_FN(ColliderCount)(t->world); + uiStatus(t, RAPIER_FN(LastStatus)()); + ns = RAPIER_FN(SoftBodyCount)(t->world); + uiStatus(t, RAPIER_FN(LastStatus)()); + } + igText("Bodies %zu | Colliders %zu | Soft %zu", nb, nc, ns); + igText("Step %" PRIu64 " | Physics %.2f ms/step", t->step, t->physicsStepMs); + igTextDisabled("Draw CPU %.2f ms | %d FPS", ui->renderMs, GetFPS()); + igEnd(); +} + +static void sceneOverlay(Testbed *t, float sidebarWidth) { + igSetNextWindowPos(UI2(sidebarWidth + 18, 12), ImGuiCond_Always, UI2(0, 0)); + igSetNextWindowSize(UI2(fmaxf(100, GetScreenWidth() - sidebarWidth - 36), 0), ImGuiCond_Always); + igBegin("Scene", NULL, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_AlwaysAutoResize); + igPushFont(NULL, 22); + igTextUnformatted(t->example->name, NULL); + igPopFont(); +#if defined(RAPIER_DIM2) + igTextDisabled("Left drag: grab | Right drag: pan | Wheel: zoom"); +#else + igTextDisabled("Left drag: grab | Right drag: orbit | Wheel: zoom"); + igTextDisabled("Shift + right drag / middle drag: pan"); +#endif + igTextDisabled("Arrows: move | Space: jump | Enter: action | Hold C: cut"); + if (t->error[0]) { + igSpacing(); + igTextColored(UI4(0.70f, 0.18f, 0.14f, 1), "Scene error"); + igTextWrapped("%s", t->error); + } + igEnd(); +} + +#if defined(RAPIER_DIM3) +/* Orbit about the scene target, preserving distance and the example's up axis. */ +static void orbitCamera(Camera3D *camera, Vector2 delta, bool pan, float wheel, + float viewportHeight) { + Vector3 offset = Vector3Subtract(camera->position, camera->target); + float distance = fmaxf(Vector3Length(offset), .001f); + Vector3 up = Vector3Normalize(camera->up); + Vector3 right = Vector3Normalize(Vector3CrossProduct(up, offset)); + if (Vector3LengthSqr(right) < 1.0e-8f) { + right = Vector3Normalize( + Vector3CrossProduct(fabsf(up.x) < .9f ? (Vector3){1, 0, 0} : (Vector3){0, 0, 1}, up)); + } + if (pan) { + Vector3 screenUp = Vector3Normalize(Vector3CrossProduct(offset, right)); + float scale = 2 * distance * tanf(camera->fovy * DEG2RAD * .5f) / viewportHeight; + Vector3 shift = Vector3Add(Vector3Scale(right, -delta.x * scale), + Vector3Scale(screenUp, delta.y * scale)); + camera->target = Vector3Add(camera->target, shift); + } else { + offset = Vector3RotateByAxisAngle(offset, up, -delta.x * .005f); + Vector3 rotatedRight = Vector3CrossProduct(up, offset); + if (Vector3LengthSqr(rotatedRight) > 1.0e-8f) { + right = Vector3Normalize(rotatedRight); + } + float cosine = Vector3DotProduct(Vector3Normalize(offset), up); + float polar = acosf(fmaxf(-1, fminf(1, cosine))); + float nextPolar = fmaxf(.01f, fminf(PI - .01f, polar - delta.y * .005f)); + offset = Vector3RotateByAxisAngle(offset, right, nextPolar - polar); + } + float zoom = fmaxf(.001f, distance * expf(-wheel * .12f)); + camera->position = Vector3Add(camera->target, Vector3Scale(Vector3Normalize(offset), zoom)); +} +#endif + +typedef struct GuiViewer { + UiState *ui; + TbGraphics *graphics; + TbGrab grab; + Camera3D camera; + int frames, sceneStarted; + double physicsStart; + uint64_t previousStep; +} GuiViewer; + +static int renderFrame(Testbed *t, void *context) { + GuiViewer *viewer = context; + UiState *ui = viewer->ui; + if (!viewer->sceneStarted) { + viewer->sceneStarted = 1; + viewer->grab = (TbGrab){0}; + for (size_t i = 0; i < TB_COUNT(debugModes); ++i) { + ui->debug[i] = (t->initialDebug & debugModes[i]) != 0; + } + if (!t->preserveCamera || !ui->frame) { + viewer->camera = cameraFor(t); + } + if (t->frameAll) { + tbGraphicsFrameAll(t, &viewer->camera); + } + viewer->previousStep = 0; + ui->physicsSteps = 0; + ui->physicsMs = 0; + TraceLog(LOG_INFO, "UI: SIMD %u lanes; parallel %s; workers %zu (requested %zu)", + t->buildFeatures.simd_lanes, t->buildFeatures.parallel ? "enabled" : "disabled", + t->activeThreads, t->requestedThreads); + } else { + ui->physicsSteps = t->step > viewer->previousStep; + ui->physicsMs = ui->physicsSteps ? (GetTime() - viewer->physicsStart) * 1000 : 0; + } + viewer->previousStep = t->step; + if (WindowShouldClose() || (viewer->frames && ui->frame >= viewer->frames)) { + uiStatus(t, tbGrabRelease(t, &viewer->grab)); + return 0; + } + Camera3D camera = viewer->camera; + TbGraphics *g = viewer->graphics; + + ui->stepOnce = ui->restart = ui->frameAll = ui->save = ui->restore = false; + // Build the UI first so widgets can consume mouse and keyboard input. + rlImGuiBegin(); + const float sidebarWidth = 390; + sidebar(t, ui, sidebarWidth); + sceneOverlay(t, sidebarWidth); + ImGuiIO *io = igGetIO_Nil(); + bool captureKeyboard = io->WantCaptureKeyboard || io->WantTextInput; + keyboardShortcuts(ui); + Vector2 mouse = GetMousePosition(); + bool captureMouse = io->WantCaptureMouse || mouse.x < sidebarWidth; + if (!captureMouse && !viewer->grab.active) { +#if defined(RAPIER_DIM2) + float wheel = GetMouseWheelMove(); + camera.fovy = fmaxf(0.05f, camera.fovy * expf(-wheel * 0.12f)); + if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { + Vector2 d = GetMouseDelta(); + float scale = camera.fovy / GetScreenHeight(); + camera.position.x -= d.x * scale; + camera.target.x -= d.x * scale; + camera.position.y += d.y * scale; + camera.target.y += d.y * scale; + } +#else + bool pan = IsMouseButtonDown(MOUSE_BUTTON_MIDDLE) || IsKeyDown(KEY_LEFT_SHIFT) || + IsKeyDown(KEY_RIGHT_SHIFT); + Vector2 delta = + (IsMouseButtonDown(MOUSE_BUTTON_RIGHT) || IsMouseButtonDown(MOUSE_BUTTON_MIDDLE)) + ? GetMouseDelta() + : (Vector2){0}; + orbitCamera(&camera, delta, pan, GetMouseWheelMove(), (float)GetScreenHeight()); +#endif + } + t->inputDirection = captureKeyboard + ? V(0, 0, 0) + : V((IsKeyDown(KEY_RIGHT) ? 1 : 0) - (IsKeyDown(KEY_LEFT) ? 1 : 0), + (IsKeyDown(KEY_UP) ? 1 : 0) - (IsKeyDown(KEY_DOWN) ? 1 : 0), 0); + t->jump = !captureKeyboard && IsKeyDown(KEY_SPACE); + t->boost = !captureKeyboard && IsKeyDown(KEY_RIGHT_SHIFT); + t->descend = !captureKeyboard && IsKeyDown(KEY_RIGHT_CONTROL); + t->slow = !captureKeyboard && (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)); +#if defined(RAPIER_DIM3) + Vector3 forward = Vector3Normalize(Vector3Subtract(camera.target, camera.position)); + Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, camera.up)); + t->cameraRight = V(right.x, 0, right.z); + t->cameraForward = V(forward.x, 0, forward.z); +#endif + t->action = !captureKeyboard && IsKeyPressed(KEY_ENTER); + t->cutting = !captureKeyboard && IsKeyDown(KEY_C); + t->cursorValid = 0; + t->rayValid = 0; + t->removeVoxel = !captureKeyboard && IsKeyDown(KEY_LEFT_SHIFT); +#if defined(RAPIER_DIM2) + if (!captureMouse) { + Ray ray = GetScreenToWorldRay(mouse, camera); + t->rayValid = 1; + t->rayOrigin = V(ray.position.x, ray.position.y, ray.position.z); + t->rayDirection = V(ray.direction.x, ray.direction.y, ray.direction.z); + if (fabsf(ray.direction.z) > 1.0e-6f) { + float distance = -ray.position.z / ray.direction.z; + t->cursor = V(ray.position.x + distance * ray.direction.x, + ray.position.y + distance * ray.direction.y, 0); + t->cursorValid = 1; + } + } +#else + if (!captureMouse) { + Ray ray = GetScreenToWorldRay(mouse, camera); + t->rayValid = 1; + t->rayOrigin = V(ray.position.x, ray.position.y, ray.position.z); + t->rayDirection = V(ray.direction.x, ray.direction.y, ray.direction.z); + Vector3 normal = Vector3Normalize(Vector3Subtract(camera.position, camera.target)); + float denominator = Vector3DotProduct(ray.direction, normal); + if (fabsf(denominator) > 1.0e-6f) { + float distance = -Vector3DotProduct(ray.position, normal) / denominator; + if (distance >= 0) { + Vector3 point = Vector3Add(ray.position, Vector3Scale(ray.direction, distance)); + t->cursor = V(point.x, point.y, point.z); + t->cursorValid = 1; + } + } + } +#endif + bool transition = ui->next != ui->selected || ui->restart; + if (!IsMouseButtonDown(MOUSE_BUTTON_LEFT) || transition || ui->save || ui->restore || + !IsWindowFocused()) { + uiStatus(t, tbGrabRelease(t, &viewer->grab)); + } else if (!captureMouse && !t->error[0]) { + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + RAPIER_TYPE(Real) radius = (RAPIER_TYPE(Real))(camera.fovy * 8 / GetScreenHeight()); + uiStatus(t, tbGrabBegin(t, &viewer->grab, radius)); + } + Vector3 direction = Vector3Normalize(Vector3Subtract(camera.target, camera.position)); + uiStatus(t, tbGrabUpdate(t, &viewer->grab, V(direction.x, direction.y, direction.z))); + } + tbGrabDrawCue(t, &viewer->grab); + if (ui->save) { + worldSnapshot(t, &ui->snapshot, 1, &ui->snapshotStep, &ui->snapshotTime); + } + if (ui->restore) { + worldSnapshot(t, &ui->snapshot, 0, &ui->snapshotStep, &ui->snapshotTime); + tbGraphicsFree(g); + g = tbGraphicsNew(); + } + if (ui->frameAll) { + tbGraphicsFrameAll(t, &camera); + } + if (t->error[0]) { + ui->running = false; + } + t->simulating = !transition && !t->error[0] && (ui->running || ui->stepOnce); + ui->stepOnce = false; + double start; + BeginDrawing(); + ClearBackground((Color){250, 250, 245, 255}); + uint32_t debug = 0; + for (size_t i = 0; i < TB_COUNT(debugModes); i++) { + if (ui->debug[i]) { + debug |= debugModes[i]; + } + } + start = GetTime(); + tbGraphicsDraw(g, t, camera, debug, ui->surfaces); + ui->renderMs = (GetTime() - start) * 1000; + rlImGuiEnd(); + EndDrawing(); + ui->physicsHistory[ui->historyOffset] = (float)(ui->physicsSteps ? t->physicsStepMs : 0); + ui->renderHistory[ui->historyOffset] = (float)ui->renderMs; + ui->historyOffset = (ui->historyOffset + 1) % UI_HISTORY; + if (ui->historyCount < UI_HISTORY) { + ui->historyCount++; + } + ui->frame++; + + viewer->graphics = g; + viewer->camera = camera; + viewer->physicsStart = GetTime(); + return !transition; +} + +int tbGui(int argc, char **argv) { + if (!abi()) { + return 1; + } + int initial = 0, noSleep = 0, frames = 0; + size_t threads = 0; + const char *initialTab = "Examples"; + const char *screenshot = NULL, *assets = TB_ASSET_ROOT; + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--headless") || !strcmp(argv[i], "--list") || + !strcmp(argv[i], "--all")) { + return tbHeadless(argc, argv); + } + if (!strcmp(argv[i], "--example") && i + 1 < argc) { + const char *name = argv[++i]; + initial = -1; + for (size_t j = 0; j < tbExampleCount; j++) { + if (!strcmp(name, tbExamples[j].id)) { + initial = (int)j; + } + } + if (initial < 0) { + fprintf(stderr, "Unknown example: %s\n", name); + return 2; + } + } else if (!strcmp(argv[i], "--no-sleep")) { + noSleep = 1; + } else if (!strcmp(argv[i], "--frames") && i + 1 < argc) { + frames = atoi(argv[++i]); + if (frames <= 0) { + return 2; + } + } else if (!strcmp(argv[i], "--screenshot") && i + 1 < argc) { + screenshot = argv[++i]; + } else if (!strcmp(argv[i], "--ui-tab") && i + 1 < argc) { + initialTab = argv[++i]; + if (strcmp(initialTab, "Examples") && strcmp(initialTab, "Settings") && + strcmp(initialTab, "Performance") && strcmp(initialTab, "Debug")) { + fputs("--ui-tab expects Examples, Settings, Performance, or Debug\n", stderr); + return 2; + } + } else if (!strcmp(argv[i], "--threads") && i + 1 < argc) { + if (!tbParseThreads(argv[++i], &threads)) { + fprintf(stderr, "--threads expects an integer from 0 (automatic) to %d\n", + TB_MAX_THREADS); + return 2; + } + } else if (!strcmp(argv[i], "--assets") && i + 1 < argc) { + assets = argv[++i]; + } else { + printf("Usage: %s [--example ID] [--no-sleep] [--frames N --screenshot " + "FILE] [--assets PATH] [--ui-tab TAB] [--threads N]\n " + "--headless [--all | " + "--example ID] " + "[--steps N]\n", + argv[0]); + return !strcmp(argv[i], "--help") ? 0 : 2; + } + } + + SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_MSAA_4X_HINT | FLAG_WINDOW_HIGHDPI); + InitWindow(1440, 900, "Rapier C testbed - Dear ImGui"); + if (!IsWindowReady()) { + fputs("Cannot create a graphics window. Use rapier_testbed_headless " + "without a display.\n", + stderr); + return 1; + } + SetWindowMinSize(850, 500); + SetTargetFPS(60); + SetExitKey(KEY_NULL); + if (!setupUi()) { + rlImGuiShutdown(); + CloseWindow(); + return 1; + } + UiState ui = {0}; + ui.running = true; + ui.surfaces = true; + ui.selected = initial; + ui.next = initial; + ui.initialTab = initialTab; + ui.threadInput = (int)threads; + ui.profile = RAPIER_FN(BuildProfile)(); + TraceLog(LOG_INFO, "UI: Dear ImGui %s; physics compiled in %s mode", igGetVersion(), + ui.profile); + Testbed *t = calloc(1, sizeof(*t)); + if (!t) { + abort(); + } + t->noSleep = noSleep; + t->requestedThreads = threads; + t->assetRoot = assets; + GuiViewer viewer = {.ui = &ui, .frames = frames}; + t->renderFrame = renderFrame; + t->viewer = &viewer; + int preserve = 0; + while (!WindowShouldClose() && (!frames || ui.frame < frames)) { + viewer.graphics = tbGraphicsNew(); + viewer.sceneStarted = 0; + ui.historyCount = ui.historyOffset = 0; + (void)tbRun(t, &tbExamples[ui.selected], preserve); + if (t->error[0]) { + ui.running = false; + /* Keep the error visible and allow selection of another example. */ + while (renderFrame(t, &viewer)) { + } + } + tbGraphicsFree(viewer.graphics); + viewer.graphics = NULL; + preserve = ui.next == ui.selected; + ui.selected = ui.next; + (void)RAPIER_FN(FreeBytes)(ui.snapshot); + ui.snapshot = NULL; + } + if (screenshot) { + Image capture = LoadImageFromScreen(); + if (!ExportImage(capture, screenshot)) { + snprintf(t->error, sizeof(t->error), "Cannot save screenshot: %s", screenshot); + } + UnloadImage(capture); + } + int result = t->error[0] ? 1 : 0; + TraceLog(LOG_INFO, "UI: completed %d frames, physics step %llu, profile %s", ui.frame, + (unsigned long long)t->step, ui.profile); + tbDestroy(t); + free(t); + (void)RAPIER_FN(FreeBytes)(ui.snapshot); + rlImGuiShutdown(); + CloseWindow(); + return result; +} +#ifndef TB_UI_TESTING +int main(int argc, char **argv) { + return tbGui(argc, argv); +} +#endif diff --git a/c/testbed/headless.c b/c/testbed/headless.c new file mode 100644 index 000000000..132ad4b8d --- /dev/null +++ b/c/testbed/headless.c @@ -0,0 +1,222 @@ +#include "testbed_internal.h" +#include "testbed.h" +#include +#include +#include + +static int finiteVector(RAPIER_TYPE(Vector) p) { +#if defined(RAPIER_DIM3) + if (!isfinite(p.z)) { + return 0; + } +#endif + return isfinite(p.x) && isfinite(p.y); +} + +int tbValidate(Testbed *t, size_t *nb, size_t *nc, size_t *ns) { + if (setjmp(t->failure)) { + return 0; + } + *nb = RAPIER_FN(RigidBodyCount)(t->world); + TB(t, RAPIER_FN(LastStatus)()); + *nc = RAPIER_FN(ColliderCount)(t->world); + TB(t, RAPIER_FN(LastStatus)()); + *ns = RAPIER_FN(SoftBodyCount)(t->world); + TB(t, RAPIER_FN(LastStatus)()); + RAPIER_TYPE(RigidBodyHandle) *b = malloc((*nb ? *nb : 1) * sizeof(*b)); + if (!b) { + return 0; + } + *nb = RAPIER_FN(RigidBodyHandles)(t->world, b, *nb); + RAPIER_TYPE(Status) status = RAPIER_FN(LastStatus)(); + if (status) { + free(b); + TB(t, status); + } + for (size_t i = 0; i < *nb; i++) { + RAPIER_TYPE(Vector) p, v; + status = RAPIER_FN(RigidBody_ValidateHandle)(b[i]); + if (!status) { + p = RAPIER_FN(RigidBody_Translation)(b[i]); + status = RAPIER_FN(LastStatus)(); + } + if (!status) { + v = RAPIER_FN(RigidBody_Linvel)(b[i]); + status = RAPIER_FN(LastStatus)(); + } + if (status || !finiteVector(p) || !finiteVector(v)) { + free(b); + snprintf(t->error, sizeof(t->error), "non-finite or invalid rigid body"); + return 0; + } + } + free(b); + RAPIER_TYPE(SoftBodyHandle) *s = malloc((*ns ? *ns : 1) * sizeof(*s)); + if (!s) { + return 0; + } + *ns = RAPIER_FN(SoftBodyHandles)(t->world, s, *ns); + status = RAPIER_FN(LastStatus)(); + if (status) { + free(s); + TB(t, status); + } + for (size_t i = 0; i < *ns; i++) { + size_t n = 0; + status = RAPIER_FN(SoftBody_ValidateHandle)(s[i]); + if (!status) { + n = RAPIER_FN(SoftBody_ParticlePositions)(s[i], NULL, 0); + status = RAPIER_FN(LastStatus)(); + } + if (status) { + free(s); + TB(t, status); + } + RAPIER_TYPE(Vector) *p = malloc((n ? n : 1) * sizeof(*p)); + if (!p) { + free(s); + return 0; + } + n = RAPIER_FN(SoftBody_ParticlePositions)(s[i], p, n); + status = RAPIER_FN(LastStatus)(); + for (size_t j = 0; j < n && !status; j++) { + if (!finiteVector(p[j])) { + status = RAPIER_CONST(INVALID_ARGUMENT); + } + } + free(p); + if (status) { + free(s); + snprintf(t->error, sizeof(t->error), "non-finite or invalid soft body"); + return 0; + } + } + free(s); + return 1; +} + +typedef struct HeadlessViewer { + unsigned long steps; + size_t bodies, colliders, softBodies; + int valid; +} HeadlessViewer; + +static int headlessFrame(Testbed *t, void *context) { + HeadlessViewer *viewer = context; + if (t->step == 0) { + printf("CONFIG %s SIMD=%u parallel=%s workers=%zu requested=%zu\n", t->example->id, + t->buildFeatures.simd_lanes, t->buildFeatures.parallel ? "on" : "off", + t->activeThreads, t->requestedThreads); + } + if (t->step < viewer->steps && !t->error[0]) { + t->simulating = 1; + return 1; + } + viewer->valid = tbValidate(t, &viewer->bodies, &viewer->colliders, &viewer->softBodies); + return 0; +} + +static void usage(const char *name) { + printf("Usage: %s [--list] [--example ID] [--all] [--steps N] [--no-sleep] [--threads N] " + "[--assets PATH]\n", + name); +} + +int tbHeadless(int argc, char **argv) { + const char *id = NULL, *assets = TB_ASSET_ROOT; + int all = 0, noSleep = 0; + unsigned long steps = 120; + size_t threads = 0; + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--headless")) { + continue; + } + if (!strcmp(argv[i], "--list")) { + for (size_t j = 0; j < tbExampleCount; j++) { + printf("%s\t%s / %s%s%s\n", tbExamples[j].id, tbExamples[j].group, + tbExamples[j].name, tbExamples[j].requires ? "\tUNAVAILABLE: " : "", + tbExamples[j].requires ? tbExamples[j].requires : ""); + } + return 0; + } + if (!strcmp(argv[i], "--all")) { + all = 1; + continue; + } + if (!strcmp(argv[i], "--no-sleep")) { + noSleep = 1; + continue; + } + if (!strcmp(argv[i], "--threads") && i + 1 < argc) { + if (!tbParseThreads(argv[++i], &threads)) { + fprintf(stderr, "--threads expects an integer from 0 (automatic) to %d\n", + TB_MAX_THREADS); + return 2; + } + continue; + } + if (!strcmp(argv[i], "--example") && i + 1 < argc) { + id = argv[++i]; + continue; + } + if (!strcmp(argv[i], "--assets") && i + 1 < argc) { + assets = argv[++i]; + continue; + } + if (!strcmp(argv[i], "--steps") && i + 1 < argc) { + char *end; + errno = 0; + const char *s = argv[++i]; + steps = strtoul(s, &end, 10); + if (errno || *end || s == end || s[0] == '-') { + usage(argv[0]); + return 2; + } + continue; + } + usage(argv[0]); + return !strcmp(argv[i], "--help") ? 0 : 2; + } + Testbed *t = calloc(1, sizeof(*t)); + if (!t) { + return 1; + } + t->noSleep = noSleep; + t->assetRoot = assets; + t->requestedThreads = threads; + size_t ran = 0, failed = 0, skipped = 0; + for (size_t i = 0; i < tbExampleCount; i++) { + const TbExample *e = &tbExamples[i]; + if (!all && ((id && strcmp(e->id, id)) || (!id && i))) { + continue; + } + if (e->requires) { + printf("SKIP %s: %s\n", e->id, e->requires); + skipped++; + continue; + } + HeadlessViewer viewer = {.steps = steps}; + t->renderFrame = headlessFrame; + t->viewer = &viewer; + int ok = tbRun(t, e, 0) && viewer.valid; + printf("%s %s steps=%" PRIu64 " bodies=%zu colliders=%zu soft=%zu%s%s\n", + ok ? "PASS" : "FAIL", e->id, t->step, viewer.bodies, viewer.colliders, + viewer.softBodies, ok ? "" : " ", ok ? "" : t->error); + fflush(stdout); + ran++; + failed += !ok; + } + tbDestroy(t); + free(t); + if (!ran && !skipped) { + fprintf(stderr, "unknown example: %s\n", id ? id : ""); + return 2; + } + printf("%zu passed, %zu failed, %zu unavailable\n", ran - failed, failed, skipped); + return failed || (!all && skipped) ? 1 : 0; +} +#ifdef TB_HEADLESS_ONLY +int main(int argc, char **argv) { + return tbHeadless(argc, argv); +} +#endif diff --git a/c/testbed/headless_main.c b/c/testbed/headless_main.c new file mode 100644 index 000000000..adfe13bc4 --- /dev/null +++ b/c/testbed/headless_main.c @@ -0,0 +1,5 @@ +#include "testbed.h" + +int main(int argc, char **argv) { + return tbHeadless(argc, argv); +} diff --git a/c/testbed/registry2.c b/c/testbed/registry2.c new file mode 100644 index 000000000..8977ca0c8 --- /dev/null +++ b/c/testbed/registry2.c @@ -0,0 +1,189 @@ +/* Generated by update_catalog.py; order matches the Rust testbed. */ +#include "testbed.h" +extern void tbAddRemove2(Testbed *); +extern void tbDrum2(Testbed *); +extern void tbInvPyramid2(Testbed *); +extern void tbPlatform2(Testbed *); +extern void tbPyramid2(Testbed *); +extern void tbSensor2(Testbed *); +extern void tbConvexPolygons2(Testbed *); +extern void tbHeightfield2(Testbed *); +extern void tbPolyline2(Testbed *); +extern void tbTrimesh2(Testbed *); +extern void tbVoxels2(Testbed *); +extern void tbCollisionGroups2(Testbed *); +extern void tbOneWayPlatforms2(Testbed *); +extern void tbLockedRotations2(Testbed *); +extern void tbRestitution2(Testbed *); +extern void tbDamping2(Testbed *); +extern void tbCcd2(Testbed *); +extern void tbJoints2(Testbed *); +extern void tbRopeJoints2(Testbed *); +extern void tbPinSlotJoint2(Testbed *); +extern void tbJointMotorPosition2(Testbed *); +extern void tbInverseKinematics2(Testbed *); +extern void tbMultiPendulum2(Testbed *); +extern void tbSoftBodies2(Testbed *); +extern void tbSoftBlobs2(Testbed *); +extern void tbSoftJelly2(Testbed *); +extern void tbSoftSurface2(Testbed *); +extern void tbSoftPile2(Testbed *); +extern void tbSoftThinFeatures2(Testbed *); +extern void tbSoftLetters2(Testbed *); +extern void tbSoftPlasticity2(Testbed *); +extern void tbSoftTearing2(Testbed *); +extern void tbSoftForceTearing2(Testbed *); +extern void tbSoftCutting2(Testbed *); +extern void tbSoftStress2(Testbed *); +extern void tbSoftFem2(Testbed *); +extern void tbSoftJoints2(Testbed *); +extern void tbCharacterController2(Testbed *); +extern void tbDebugAngularLimits2(Testbed *); +extern void tbDebugBoxBall2(Testbed *); +extern void tbDebugCompression2(Testbed *); +extern void tbDebugIntersection2(Testbed *); +extern void tbDebugManyColliders2(Testbed *); +extern void tbDebugTotalOverlap2(Testbed *); +extern void tbDebugVerticalColumn2(Testbed *); +extern void tbDebugSelfIntersect2(Testbed *); +extern void tbS2dHighMassRatio1(Testbed *); +extern void tbS2dHighMassRatio2(Testbed *); +extern void tbS2dHighMassRatio3(Testbed *); +extern void tbS2dConfined(Testbed *); +extern void tbS2dPyramid(Testbed *); +extern void tbS2dCardHouse(Testbed *); +extern void tbS2dArch(Testbed *); +extern void tbS2dBridge(Testbed *); +extern void tbS2dBallAndChain(Testbed *); +extern void tbS2dJointGrid(Testbed *); +extern void tbS2dFarPyramid(Testbed *); +extern void tbB2dCompounds(Testbed *); +extern void tbB2dJointGrid(Testbed *); +extern void tbB2dJunkyard(Testbed *); +extern void tbB2dLargePyramid(Testbed *); +extern void tbB2dManyPyramids(Testbed *); +extern void tbB2dRain(Testbed *); +extern void tbB2dSmash(Testbed *); +extern void tbB2dSpinner(Testbed *); +extern void tbB2dTumbler(Testbed *); +extern void tbB2dWasher(Testbed *); +extern void tbStressTestsBalls2(Testbed *); +extern void tbStressTestsBoxes2(Testbed *); +extern void tbStressTestsCapsules2(Testbed *); +extern void tbStressTestsConvexPolygons2(Testbed *); +extern void tbStressTestsHeightfield2(Testbed *); +extern void tbStressTestsLargePyramids2(Testbed *); +extern void tbStressTestsManyPyramids2(Testbed *); +extern void tbStressTestsPyramid2(Testbed *); +extern void tbStressTestsRagdolls2(Testbed *); +extern void tbStressTestsRopes2(Testbed *); +extern void tbStressTestsVerticalStacks2(Testbed *); +extern void tbStressTestsJointBall2(Testbed *); +extern void tbStressTestsJointFixed2(Testbed *); +extern void tbStressTestsJointPrismatic2(Testbed *); +extern void tbStressTestsSoftBlobs2(Testbed *); +extern void tbStressTestsSoftJellies2(Testbed *); +extern void tbStressTestsSoftRopes2(Testbed *); +extern void tbStressTestsSoftStrips2(Testbed *); +extern void tbStressTestsSoftClothKeva2(Testbed *); +extern void tbStressTestsSoftSlab2(Testbed *); +extern void tbStressTestsSoftFemBeams2(Testbed *); +const TbExample tbExamples[] = { + {"add_remove2", "Collisions", "Add remove", "examples2d/add_remove2.rs", tbAddRemove2, NULL}, + {"drum2", "Collisions", "Drum", "examples2d/drum2.rs", tbDrum2, NULL}, + {"inv_pyramid2", "Collisions", "Inv pyramid", "examples2d/inv_pyramid2.rs", tbInvPyramid2, NULL}, + {"platform2", "Collisions", "Platform", "examples2d/platform2.rs", tbPlatform2, NULL}, + {"pyramid2", "Collisions", "Pyramid", "examples2d/pyramid2.rs", tbPyramid2, NULL}, + {"sensor2", "Collisions", "Sensor", "examples2d/sensor2.rs", tbSensor2, NULL}, + {"convex_polygons2", "Collisions", "Convex polygons", "examples2d/convex_polygons2.rs", tbConvexPolygons2, NULL}, + {"heightfield2", "Collisions", "Heightfield", "examples2d/heightfield2.rs", tbHeightfield2, NULL}, + {"polyline2", "Collisions", "Polyline", "examples2d/polyline2.rs", tbPolyline2, NULL}, + {"trimesh2", "Collisions", "Trimesh", "examples2d/trimesh2.rs", tbTrimesh2, NULL}, + {"voxels2", "Collisions", "Voxels", "examples2d/voxels2.rs", tbVoxels2, NULL}, + {"collision_groups2", "Collisions", "Collision groups", "examples2d/collision_groups2.rs", tbCollisionGroups2, NULL}, + {"one_way_platforms2", "Collisions", "One-way platforms", "examples2d/one_way_platforms2.rs", tbOneWayPlatforms2, NULL}, + {"locked_rotations2", "Dynamics", "Locked rotations", "examples2d/locked_rotations2.rs", tbLockedRotations2, NULL}, + {"restitution2", "Dynamics", "Restitution", "examples2d/restitution2.rs", tbRestitution2, NULL}, + {"damping2", "Dynamics", "Damping", "examples2d/damping2.rs", tbDamping2, NULL}, + {"ccd2", "Dynamics", "CCD", "examples2d/ccd2.rs", tbCcd2, NULL}, + {"joints2", "Joints", "Joints", "examples2d/joints2.rs", tbJoints2, NULL}, + {"rope_joints2", "Joints", "Rope Joints", "examples2d/rope_joints2.rs", tbRopeJoints2, NULL}, + {"pin_slot_joint2", "Joints", "Pin Slot Joint", "examples2d/pin_slot_joint2.rs", tbPinSlotJoint2, NULL}, + {"joint_motor_position2", "Joints", "Joint motor position", "examples2d/joint_motor_position2.rs", tbJointMotorPosition2, NULL}, + {"inverse_kinematics2", "Joints", "Inverse kinematics", "examples2d/inverse_kinematics2.rs", tbInverseKinematics2, NULL}, + {"multi_pendulum2", "Joints", "Multi Pendulum", "examples2d/multi_pendulum2.rs", tbMultiPendulum2, NULL}, + {"soft_bodies2", "Soft bodies", "Soft bodies", "examples2d/soft_bodies2.rs", tbSoftBodies2, NULL}, + {"soft_blobs2", "Soft bodies", "Blobs", "examples2d/soft_blobs2.rs", tbSoftBlobs2, NULL}, + {"soft_jelly2", "Soft bodies", "Jelly", "examples2d/soft_jelly2.rs", tbSoftJelly2, NULL}, + {"soft_surface2", "Soft bodies", "Deformable polylines", "examples2d/soft_surface2.rs", tbSoftSurface2, NULL}, + {"soft_pile2", "Soft bodies", "Soft pile", "examples2d/soft_pile2.rs", tbSoftPile2, NULL}, + {"soft_thin_features2", "Soft bodies", "Thin features", "examples2d/soft_thin_features2.rs", tbSoftThinFeatures2, NULL}, + {"soft_letters2", "Soft bodies", "Soft letters", "examples2d/soft_letters2.rs", tbSoftLetters2, NULL}, + {"soft_plasticity2", "Soft bodies", "Plasticity", "examples2d/soft_plasticity2.rs", tbSoftPlasticity2, NULL}, + {"soft_tearing2", "Soft bodies", "Tearing", "examples2d/soft_tearing2.rs", tbSoftTearing2, NULL}, + {"soft_force_tearing2", "Soft bodies", "Force tearing", "examples2d/soft_force_tearing2.rs", tbSoftForceTearing2, NULL}, + {"soft_cutting2", "Soft bodies", "Cutting", "examples2d/soft_cutting2.rs", tbSoftCutting2, NULL}, + {"soft_stress2", "Soft bodies", "Stress coloring", "examples2d/soft_stress2.rs", tbSoftStress2, NULL}, +#ifdef RAPIER_FEM + {"soft_fem2", "Soft bodies", "Soft FEM", "examples2d/soft_fem2.rs", tbSoftFem2, NULL}, +#else + {"soft_fem2", "Soft bodies", "Soft FEM", "examples2d/soft_fem2.rs", NULL, "Requires RAPIER_FEATURES=fem"}, +#endif + {"soft_joints2", "Soft bodies", "Soft joints", "examples2d/soft_joints2.rs", tbSoftJoints2, NULL}, + {"character_controller2", "Controls", "Character controller", "examples2d/character_controller2.rs", tbCharacterController2, NULL}, + {"debug_angular_limits2", "Debug", "Angular limits", "examples2d/debug_angular_limits2.rs", tbDebugAngularLimits2, NULL}, + {"debug_box_ball2", "Debug", "Box ball", "examples2d/debug_box_ball2.rs", tbDebugBoxBall2, NULL}, + {"debug_compression2", "Debug", "Compression", "examples2d/debug_compression2.rs", tbDebugCompression2, NULL}, + {"debug_intersection2", "Debug", "Intersection", "examples2d/debug_intersection2.rs", tbDebugIntersection2, NULL}, + {"debug_many_colliders2", "Debug", "Many colliders", "examples2d/debug_many_colliders2.rs", tbDebugManyColliders2, NULL}, + {"debug_total_overlap2", "Debug", "Total overlap", "examples2d/debug_total_overlap2.rs", tbDebugTotalOverlap2, NULL}, + {"debug_vertical_column2", "Debug", "Vertical column", "examples2d/debug_vertical_column2.rs", tbDebugVerticalColumn2, NULL}, + {"debug_self_intersect2", "Debug", "Self intersect", "examples2d/debug_self_intersect2.rs", tbDebugSelfIntersect2, NULL}, + {"s2d_high_mass_ratio_1", "Inspired by Solver 2D", "High mass ratio 1", "examples2d/s2d_high_mass_ratio_1.rs", tbS2dHighMassRatio1, NULL}, + {"s2d_high_mass_ratio_2", "Inspired by Solver 2D", "High mass ratio 2", "examples2d/s2d_high_mass_ratio_2.rs", tbS2dHighMassRatio2, NULL}, + {"s2d_high_mass_ratio_3", "Inspired by Solver 2D", "High mass ratio 3", "examples2d/s2d_high_mass_ratio_3.rs", tbS2dHighMassRatio3, NULL}, + {"s2d_confined", "Inspired by Solver 2D", "Confined", "examples2d/s2d_confined.rs", tbS2dConfined, NULL}, + {"s2d_pyramid", "Inspired by Solver 2D", "Pyramid", "examples2d/s2d_pyramid.rs", tbS2dPyramid, NULL}, + {"s2d_card_house", "Inspired by Solver 2D", "Card house", "examples2d/s2d_card_house.rs", tbS2dCardHouse, NULL}, + {"s2d_arch", "Inspired by Solver 2D", "Arch", "examples2d/s2d_arch.rs", tbS2dArch, NULL}, + {"s2d_bridge", "Inspired by Solver 2D", "Bridge", "examples2d/s2d_bridge.rs", tbS2dBridge, NULL}, + {"s2d_ball_and_chain", "Inspired by Solver 2D", "Ball and chain", "examples2d/s2d_ball_and_chain.rs", tbS2dBallAndChain, NULL}, + {"s2d_joint_grid", "Inspired by Solver 2D", "Joint grid", "examples2d/s2d_joint_grid.rs", tbS2dJointGrid, NULL}, + {"s2d_far_pyramid", "Inspired by Solver 2D", "Far pyramid", "examples2d/s2d_far_pyramid.rs", tbS2dFarPyramid, NULL}, + {"b2d_compounds", "Third-party benchmarks", "Compounds", "examples2d/b2d_compounds.rs", tbB2dCompounds, NULL}, + {"b2d_joint_grid", "Third-party benchmarks", "Joint grid", "examples2d/b2d_joint_grid.rs", tbB2dJointGrid, NULL}, + {"b2d_junkyard", "Third-party benchmarks", "Junkyard", "examples2d/b2d_junkyard.rs", tbB2dJunkyard, NULL}, + {"b2d_large_pyramid", "Third-party benchmarks", "Large pyramid", "examples2d/b2d_large_pyramid.rs", tbB2dLargePyramid, NULL}, + {"b2d_many_pyramids", "Third-party benchmarks", "Many pyramids", "examples2d/b2d_many_pyramids.rs", tbB2dManyPyramids, NULL}, + {"b2d_rain", "Third-party benchmarks", "Rain", "examples2d/b2d_rain.rs", tbB2dRain, NULL}, + {"b2d_smash", "Third-party benchmarks", "Smash", "examples2d/b2d_smash.rs", tbB2dSmash, NULL}, + {"b2d_spinner", "Third-party benchmarks", "Spinner", "examples2d/b2d_spinner.rs", tbB2dSpinner, NULL}, + {"b2d_tumbler", "Third-party benchmarks", "Tumbler", "examples2d/b2d_tumbler.rs", tbB2dTumbler, NULL}, + {"b2d_washer", "Third-party benchmarks", "Washer", "examples2d/b2d_washer.rs", tbB2dWasher, NULL}, + {"stress_tests_balls2", "Stress tests", "Balls", "examples2d/stress_tests/balls2.rs", tbStressTestsBalls2, NULL}, + {"stress_tests_boxes2", "Stress tests", "Boxes", "examples2d/stress_tests/boxes2.rs", tbStressTestsBoxes2, NULL}, + {"stress_tests_capsules2", "Stress tests", "Capsules", "examples2d/stress_tests/capsules2.rs", tbStressTestsCapsules2, NULL}, + {"stress_tests_convex_polygons2", "Stress tests", "Convex polygons", "examples2d/stress_tests/convex_polygons2.rs", tbStressTestsConvexPolygons2, NULL}, + {"stress_tests_heightfield2", "Stress tests", "Heightfield", "examples2d/stress_tests/heightfield2.rs", tbStressTestsHeightfield2, NULL}, + {"stress_tests_large_pyramids2", "Stress tests", "Large pyramids", "examples2d/stress_tests/large_pyramids2.rs", tbStressTestsLargePyramids2, NULL}, + {"stress_tests_many_pyramids2", "Stress tests", "Many pyramids", "examples2d/stress_tests/many_pyramids2.rs", tbStressTestsManyPyramids2, NULL}, + {"stress_tests_pyramid2", "Stress tests", "Pyramid", "examples2d/stress_tests/pyramid2.rs", tbStressTestsPyramid2, NULL}, + {"stress_tests_ragdolls2", "Stress tests", "Ragdoll piles", "examples2d/stress_tests/ragdolls2.rs", tbStressTestsRagdolls2, NULL}, + {"stress_tests_ropes2", "Stress tests", "Ropes", "examples2d/stress_tests/ropes2.rs", tbStressTestsRopes2, NULL}, + {"stress_tests_vertical_stacks2", "Stress tests", "Verticals stacks", "examples2d/stress_tests/vertical_stacks2.rs", tbStressTestsVerticalStacks2, NULL}, + {"stress_tests_joint_ball2", "Stress tests", "(Stress test) joint ball", "examples2d/stress_tests/joint_ball2.rs", tbStressTestsJointBall2, NULL}, + {"stress_tests_joint_fixed2", "Stress tests", "(Stress test) joint fixed", "examples2d/stress_tests/joint_fixed2.rs", tbStressTestsJointFixed2, NULL}, + {"stress_tests_joint_prismatic2", "Stress tests", "(Stress test) joint prismatic", "examples2d/stress_tests/joint_prismatic2.rs", tbStressTestsJointPrismatic2, NULL}, + {"stress_tests_soft_blobs2", "Stress tests", "Soft blobs", "examples2d/stress_tests/soft_blobs2.rs", tbStressTestsSoftBlobs2, NULL}, + {"stress_tests_soft_jellies2", "Stress tests", "Soft jellies", "examples2d/stress_tests/soft_jellies2.rs", tbStressTestsSoftJellies2, NULL}, + {"stress_tests_soft_ropes2", "Stress tests", "Soft ropes", "examples2d/stress_tests/soft_ropes2.rs", tbStressTestsSoftRopes2, NULL}, + {"stress_tests_soft_strips2", "Stress tests", "Soft strips", "examples2d/stress_tests/soft_strips2.rs", tbStressTestsSoftStrips2, NULL}, + {"stress_tests_soft_cloth_keva2", "Stress tests", "Soft cloth on Keva tower", "examples2d/stress_tests/soft_cloth_keva2.rs", tbStressTestsSoftClothKeva2, NULL}, + {"stress_tests_soft_slab2", "Stress tests", "Soft slab shower", "examples2d/stress_tests/soft_slab2.rs", tbStressTestsSoftSlab2, NULL}, +#ifdef RAPIER_FEM + {"stress_tests_soft_fem_beams2", "Stress tests", "Soft FEM beams", "examples2d/stress_tests/soft_fem_beams2.rs", tbStressTestsSoftFemBeams2, NULL}, +#else + {"stress_tests_soft_fem_beams2", "Stress tests", "Soft FEM beams", "examples2d/stress_tests/soft_fem_beams2.rs", NULL, "Requires RAPIER_FEATURES=fem"}, +#endif +}; +const size_t tbExampleCount=TB_COUNT(tbExamples); diff --git a/c/testbed/registry3.c b/c/testbed/registry3.c new file mode 100644 index 000000000..59ae96c9e --- /dev/null +++ b/c/testbed/registry3.c @@ -0,0 +1,253 @@ +/* Generated by update_catalog.py; order matches the Rust testbed. */ +#include "testbed.h" +extern void tbFountain3(Testbed *); +extern void tbPrimitives3(Testbed *); +extern void tbKeva3(Testbed *); +extern void tbNewtonCradle3(Testbed *); +extern void tbDomino3(Testbed *); +extern void tbPlatform3(Testbed *); +extern void tbSensor3(Testbed *); +extern void tbCompound3(Testbed *); +extern void tbConvexDecomposition3(Testbed *); +extern void tbConvexPolyhedron3(Testbed *); +extern void tbTrimesh3(Testbed *); +extern void tbDynamicTrimesh3(Testbed *); +extern void tbHeightfield3(Testbed *); +extern void tbVoxels3(Testbed *); +extern void tbCollisionGroups3(Testbed *); +extern void tbOneWayPlatforms3(Testbed *); +extern void tbLockedRotations3(Testbed *); +extern void tbRestitution3(Testbed *); +extern void tbDamping3(Testbed *); +extern void tbGyroscopic3(Testbed *); +extern void tbCcd3(Testbed *); +extern void tbJoints3RunImpulseJoints(Testbed *); +extern void tbJoints3RunMultibodyJoints(Testbed *); +extern void tbRopeJoints3(Testbed *); +extern void tbSpringJoints3(Testbed *); +extern void tbJointMotorPosition3(Testbed *); +extern void tbInverseKinematics3(Testbed *); +extern void tbSoftBodies3(Testbed *); +extern void tbSoftCloth3(Testbed *); +extern void tbSoftJelly3(Testbed *); +extern void tbSoftJoints3(Testbed *); +extern void tbSoftMeshes3(Testbed *); +extern void tbSoftSurface3(Testbed *); +extern void tbSoftPile3(Testbed *); +extern void tbSoftThinFeatures3(Testbed *); +extern void tbSoftClothStress3(Testbed *); +extern void tbSoftPlasticity3(Testbed *); +extern void tbSoftTearing3(Testbed *); +extern void tbSoftDress3(Testbed *); +extern void tbSoftFem3(Testbed *); +extern void tbSoftTrimesh3(Testbed *); +extern void tbCharacterController3(Testbed *); +extern void tbVehicleController3(Testbed *); +extern void tbVehicleJoints3(Testbed *); +extern void tbUrdf3(Testbed *); +extern void tbMjcf3(Testbed *); +extern void tbMujocoMenagerie3(Testbed *); +extern void tbDebugAngularLimits3(Testbed *); +extern void tbDebugArticulations3(Testbed *); +extern void tbDebugAddRemoveCollider3(Testbed *); +extern void tbDebugMultiColliderBody3(Testbed *); +extern void tbDebugBigColliders3(Testbed *); +extern void tbDebugBoxes3(Testbed *); +extern void tbDebugBalls3(Testbed *); +extern void tbDebugDisabled3(Testbed *); +extern void tbDebugTwoCubes3(Testbed *); +extern void tbDebugPop3(Testbed *); +extern void tbDebugDynamicColliderAdd3(Testbed *); +extern void tbDebugFriction3(Testbed *); +extern void tbDebugInternalEdges3(Testbed *); +extern void tbDebugSelfIntersect3(Testbed *); +extern void tbDebugLongChain3(Testbed *); +extern void tbDebugChainHighMassRatio3(Testbed *); +extern void tbDebugCubeHighMassRatio3(Testbed *); +extern void tbDebugTriangle3(Testbed *); +extern void tbDebugTrimesh3(Testbed *); +extern void tbDebugThinCubeOnMesh3(Testbed *); +extern void tbDebugCylinder3(Testbed *); +extern void tbDebugInfiniteFall3(Testbed *); +extern void tbDebugPrismatic3(Testbed *); +extern void tbDebugRollback3(Testbed *); +extern void tbDebugShapeModification3(Testbed *); +extern void tbDebugSleepingKinematic3(Testbed *); +extern void tbDebugDeserialize3(Testbed *); +extern void tbDebugMultibodyAngMotorPos3(Testbed *); +extern void tbStressTestsBalls3(Testbed *); +extern void tbStressTestsBoxes3(Testbed *); +extern void tbStressTestsCapsules3(Testbed *); +extern void tbStressTestsCcd3(Testbed *); +extern void tbStressTestsCompound3(Testbed *); +extern void tbStressTestsConvexPolyhedron3(Testbed *); +extern void tbStressTestsManyKinematics3(Testbed *); +extern void tbStressTestsManyStatic3(Testbed *); +extern void tbStressTestsManySleep3(Testbed *); +extern void tbStressTestsHeightfield3(Testbed *); +extern void tbStressTestsStacks3(Testbed *); +extern void tbStressTestsPyramid3(Testbed *); +extern void tbStressTestsTrimesh3(Testbed *); +extern void tbStressTestsJointBall3(Testbed *); +extern void tbStressTestsJointFixed3(Testbed *); +extern void tbStressTestsJointRevolute3(Testbed *); +extern void tbStressTestsJointPrismatic3(Testbed *); +extern void tbStressTestsRagdolls3(Testbed *); +extern void tbStressTestsRopes3(Testbed *); +extern void tbStressTestsManyPyramids3(Testbed *); +extern void tbStressTestsKeva3(Testbed *); +extern void tbStressTestsRayCast3(Testbed *); +extern void tbStressTestsSoftBlobs3(Testbed *); +extern void tbStressTestsSoftJellies3(Testbed *); +extern void tbStressTestsSoftRopes3(Testbed *); +extern void tbStressTestsSoftClothDrape3(Testbed *); +extern void tbStressTestsSoftClothKeva3(Testbed *); +extern void tbStressTestsSoftSlab3(Testbed *); +extern void tbStressTestsSoftFemBeams3(Testbed *); +extern void tbB3dLargePyramid(Testbed *); +extern void tbB3dManyPyramids(Testbed *); +extern void tbB3dJointGrid(Testbed *); +extern void tbB3dJunkyard(Testbed *); +extern void tbB3dWasher(Testbed *); +extern void tbB3dTreesRun100(Testbed *); +extern void tbB3dTreesRun50(Testbed *); +extern void tbB3dTreesRun25(Testbed *); +extern void tbB3dRain(Testbed *); +extern void tbB3dLargeWorld(Testbed *); +const TbExample tbExamples[] = { + {"fountain3", "Collisions", "Fountain", "examples3d/fountain3.rs", tbFountain3, NULL}, + {"primitives3", "Collisions", "Primitives", "examples3d/primitives3.rs", tbPrimitives3, NULL}, + {"keva3", "Collisions", "Keva tower", "examples3d/keva3.rs", tbKeva3, NULL}, + {"newton_cradle3", "Collisions", "Newton cradle", "examples3d/newton_cradle3.rs", tbNewtonCradle3, NULL}, + {"domino3", "Collisions", "Domino", "examples3d/domino3.rs", tbDomino3, NULL}, + {"platform3", "Collisions", "Platform", "examples3d/platform3.rs", tbPlatform3, NULL}, + {"sensor3", "Collisions", "Sensor", "examples3d/sensor3.rs", tbSensor3, NULL}, + {"compound3", "Collisions", "Compound", "examples3d/compound3.rs", tbCompound3, NULL}, + {"convex_decomposition3", "Collisions", "Convex decomposition", "examples3d/convex_decomposition3.rs", tbConvexDecomposition3, NULL}, + {"convex_polyhedron3", "Collisions", "Convex polyhedron", "examples3d/convex_polyhedron3.rs", tbConvexPolyhedron3, NULL}, + {"trimesh3", "Collisions", "TriMesh", "examples3d/trimesh3.rs", tbTrimesh3, NULL}, + {"dynamic_trimesh3", "Collisions", "Dynamic trimeshes", "examples3d/dynamic_trimesh3.rs", tbDynamicTrimesh3, NULL}, + {"heightfield3", "Collisions", "Heightfield", "examples3d/heightfield3.rs", tbHeightfield3, NULL}, + {"voxels3", "Collisions", "Voxels", "examples3d/voxels3.rs", tbVoxels3, NULL}, + {"collision_groups3", "Collisions", "Collision groups", "examples3d/collision_groups3.rs", tbCollisionGroups3, NULL}, + {"one_way_platforms3", "Collisions", "One-way platforms", "examples3d/one_way_platforms3.rs", tbOneWayPlatforms3, NULL}, + {"locked_rotations3", "Dynamics", "Locked rotations", "examples3d/locked_rotations3.rs", tbLockedRotations3, NULL}, + {"restitution3", "Dynamics", "Restitution", "examples3d/restitution3.rs", tbRestitution3, NULL}, + {"damping3", "Dynamics", "Damping", "examples3d/damping3.rs", tbDamping3, NULL}, + {"gyroscopic3", "Dynamics", "Gyroscopic", "examples3d/gyroscopic3.rs", tbGyroscopic3, NULL}, + {"ccd3", "Dynamics", "CCD", "examples3d/ccd3.rs", tbCcd3, NULL}, + {"joints3_run_impulse_joints", "Joints", "Impulse Joints", "examples3d/joints3.rs", tbJoints3RunImpulseJoints, NULL}, + {"joints3_run_multibody_joints", "Joints", "Multibody Joints", "examples3d/joints3.rs", tbJoints3RunMultibodyJoints, NULL}, + {"rope_joints3", "Joints", "Rope Joints", "examples3d/rope_joints3.rs", tbRopeJoints3, NULL}, + {"spring_joints3", "Joints", "Spring Joints", "examples3d/spring_joints3.rs", tbSpringJoints3, NULL}, + {"joint_motor_position3", "Joints", "Joint Motor Position", "examples3d/joint_motor_position3.rs", tbJointMotorPosition3, NULL}, + {"inverse_kinematics3", "Joints", "Inverse kinematics", "examples3d/inverse_kinematics3.rs", tbInverseKinematics3, NULL}, + {"soft_bodies3", "Soft bodies", "Soft bodies", "examples3d/soft_bodies3.rs", tbSoftBodies3, NULL}, + {"soft_cloth3", "Soft bodies", "Cloth", "examples3d/soft_cloth3.rs", tbSoftCloth3, NULL}, + {"soft_jelly3", "Soft bodies", "Jelly", "examples3d/soft_jelly3.rs", tbSoftJelly3, NULL}, + {"soft_joints3", "Soft bodies", "Soft joints", "examples3d/soft_joints3.rs", tbSoftJoints3, NULL}, + {"soft_meshes3", "Soft bodies", "Cluster meshes", "examples3d/soft_meshes3.rs", tbSoftMeshes3, NULL}, + {"soft_surface3", "Soft bodies", "Deformable trimeshes", "examples3d/soft_surface3.rs", tbSoftSurface3, NULL}, + {"soft_pile3", "Soft bodies", "Soft pile", "examples3d/soft_pile3.rs", tbSoftPile3, NULL}, + {"soft_thin_features3", "Soft bodies", "Thin features", "examples3d/soft_thin_features3.rs", tbSoftThinFeatures3, NULL}, + {"soft_cloth_stress3", "Soft bodies", "Cloth stress", "examples3d/soft_cloth_stress3.rs", tbSoftClothStress3, NULL}, + {"soft_plasticity3", "Soft bodies", "Plasticity", "examples3d/soft_plasticity3.rs", tbSoftPlasticity3, NULL}, + {"soft_tearing3", "Soft bodies", "Tearing", "examples3d/soft_tearing3.rs", tbSoftTearing3, NULL}, + {"soft_dress3", "Soft bodies", "Dancing dress", "examples3d/soft_dress3.rs", tbSoftDress3, NULL}, +#ifdef RAPIER_FEM + {"soft_fem3", "Soft bodies", "Soft FEM", "examples3d/soft_fem3.rs", tbSoftFem3, NULL}, +#else + {"soft_fem3", "Soft bodies", "Soft FEM", "examples3d/soft_fem3.rs", NULL, "Requires RAPIER_FEATURES=fem"}, +#endif + {"soft_trimesh3", "Soft bodies", "Soft trimeshes", "examples3d/soft_trimesh3.rs", tbSoftTrimesh3, NULL}, + {"character_controller3", "Controls", "Character controller", "examples3d/character_controller3.rs", tbCharacterController3, NULL}, + {"vehicle_controller3", "Controls", "Vehicle controller", "examples3d/vehicle_controller3.rs", tbVehicleController3, NULL}, + {"vehicle_joints3", "Controls", "Vehicle joints", "examples3d/vehicle_joints3.rs", tbVehicleJoints3, NULL}, +#ifdef RAPIER_ROBOTICS + {"urdf3", "Robotics", "URDF", "examples3d/urdf3.rs", tbUrdf3, NULL}, +#else + {"urdf3", "Robotics", "URDF", "examples3d/urdf3.rs", NULL, "Requires RAPIER_FEATURES=robotics"}, +#endif +#ifdef RAPIER_ROBOTICS + {"mjcf3", "Robotics", "MJCF", "examples3d/mjcf3.rs", tbMjcf3, NULL}, +#else + {"mjcf3", "Robotics", "MJCF", "examples3d/mjcf3.rs", NULL, "Requires RAPIER_FEATURES=robotics"}, +#endif +#ifdef RAPIER_ROBOTICS + {"mujoco_menagerie3", "Robotics", "Mujoco Menagerie", "examples3d/mujoco_menagerie3.rs", tbMujocoMenagerie3, NULL}, +#else + {"mujoco_menagerie3", "Robotics", "Mujoco Menagerie", "examples3d/mujoco_menagerie3.rs", NULL, "Requires RAPIER_FEATURES=robotics"}, +#endif + {"debug_angular_limits3", "Debug", "Angular limits", "examples3d/debug_angular_limits3.rs", tbDebugAngularLimits3, NULL}, + {"debug_articulations3", "Debug", "Multibody joints", "examples3d/debug_articulations3.rs", tbDebugArticulations3, NULL}, + {"debug_add_remove_collider3", "Debug", "Add/rm collider", "examples3d/debug_add_remove_collider3.rs", tbDebugAddRemoveCollider3, NULL}, + {"debug_multi_collider_body3", "Debug", "Multi-collider body", "examples3d/debug_multi_collider_body3.rs", tbDebugMultiColliderBody3, NULL}, + {"debug_big_colliders3", "Debug", "Big colliders", "examples3d/debug_big_colliders3.rs", tbDebugBigColliders3, NULL}, + {"debug_boxes3", "Debug", "Boxes", "examples3d/debug_boxes3.rs", tbDebugBoxes3, NULL}, + {"debug_balls3", "Debug", "Balls", "examples3d/debug_balls3.rs", tbDebugBalls3, NULL}, + {"debug_disabled3", "Debug", "Disabled", "examples3d/debug_disabled3.rs", tbDebugDisabled3, NULL}, + {"debug_two_cubes3", "Debug", "Two cubes", "examples3d/debug_two_cubes3.rs", tbDebugTwoCubes3, NULL}, + {"debug_pop3", "Debug", "Pop", "examples3d/debug_pop3.rs", tbDebugPop3, NULL}, + {"debug_dynamic_collider_add3", "Debug", "Dyn. collider add", "examples3d/debug_dynamic_collider_add3.rs", tbDebugDynamicColliderAdd3, NULL}, + {"debug_friction3", "Debug", "Friction", "examples3d/debug_friction3.rs", tbDebugFriction3, NULL}, + {"debug_internal_edges3", "Debug", "Internal edges", "examples3d/debug_internal_edges3.rs", tbDebugInternalEdges3, NULL}, + {"debug_self_intersect3", "Debug", "Self intersect", "examples3d/debug_self_intersect3.rs", tbDebugSelfIntersect3, NULL}, + {"debug_long_chain3", "Debug", "Long chain", "examples3d/debug_long_chain3.rs", tbDebugLongChain3, NULL}, + {"debug_chain_high_mass_ratio3", "Debug", "High mass ratio: chain", "examples3d/debug_chain_high_mass_ratio3.rs", tbDebugChainHighMassRatio3, NULL}, + {"debug_cube_high_mass_ratio3", "Debug", "High mass ratio: cube", "examples3d/debug_cube_high_mass_ratio3.rs", tbDebugCubeHighMassRatio3, NULL}, + {"debug_triangle3", "Debug", "Triangle", "examples3d/debug_triangle3.rs", tbDebugTriangle3, NULL}, + {"debug_trimesh3", "Debug", "Trimesh", "examples3d/debug_trimesh3.rs", tbDebugTrimesh3, NULL}, + {"debug_thin_cube_on_mesh3", "Debug", "Thin cube", "examples3d/debug_thin_cube_on_mesh3.rs", tbDebugThinCubeOnMesh3, NULL}, + {"debug_cylinder3", "Debug", "Cylinder", "examples3d/debug_cylinder3.rs", tbDebugCylinder3, NULL}, + {"debug_infinite_fall3", "Debug", "Infinite fall", "examples3d/debug_infinite_fall3.rs", tbDebugInfiniteFall3, NULL}, + {"debug_prismatic3", "Debug", "Prismatic", "examples3d/debug_prismatic3.rs", tbDebugPrismatic3, NULL}, + {"debug_rollback3", "Debug", "Rollback", "examples3d/debug_rollback3.rs", tbDebugRollback3, NULL}, + {"debug_shape_modification3", "Debug", "Shape modification", "examples3d/debug_shape_modification3.rs", tbDebugShapeModification3, NULL}, + {"debug_sleeping_kinematic3", "Debug", "Sleeping kinematics", "examples3d/debug_sleeping_kinematic3.rs", tbDebugSleepingKinematic3, NULL}, + {"debug_deserialize3", "Debug", "Deserialize", "examples3d/debug_deserialize3.rs", tbDebugDeserialize3, NULL}, + {"debug_multibody_ang_motor_pos3", "Debug", "Multibody ang. motor pos.", "examples3d/debug_multibody_ang_motor_pos3.rs", tbDebugMultibodyAngMotorPos3, NULL}, + {"stress_tests_balls3", "Stress tests", "Balls", "examples3d/stress_tests/balls3.rs", tbStressTestsBalls3, NULL}, + {"stress_tests_boxes3", "Stress tests", "Boxes", "examples3d/stress_tests/boxes3.rs", tbStressTestsBoxes3, NULL}, + {"stress_tests_capsules3", "Stress tests", "Capsules", "examples3d/stress_tests/capsules3.rs", tbStressTestsCapsules3, NULL}, + {"stress_tests_ccd3", "Stress tests", "CCD", "examples3d/stress_tests/ccd3.rs", tbStressTestsCcd3, NULL}, + {"stress_tests_compound3", "Stress tests", "Compound", "examples3d/stress_tests/compound3.rs", tbStressTestsCompound3, NULL}, + {"stress_tests_convex_polyhedron3", "Stress tests", "Convex polyhedron", "examples3d/stress_tests/convex_polyhedron3.rs", tbStressTestsConvexPolyhedron3, NULL}, + {"stress_tests_many_kinematics3", "Stress tests", "Many kinematics", "examples3d/stress_tests/many_kinematics3.rs", tbStressTestsManyKinematics3, NULL}, + {"stress_tests_many_static3", "Stress tests", "Many static", "examples3d/stress_tests/many_static3.rs", tbStressTestsManyStatic3, NULL}, + {"stress_tests_many_sleep3", "Stress tests", "Many sleep", "examples3d/stress_tests/many_sleep3.rs", tbStressTestsManySleep3, NULL}, + {"stress_tests_heightfield3", "Stress tests", "Heightfield", "examples3d/stress_tests/heightfield3.rs", tbStressTestsHeightfield3, NULL}, + {"stress_tests_stacks3", "Stress tests", "Stacks", "examples3d/stress_tests/stacks3.rs", tbStressTestsStacks3, NULL}, + {"stress_tests_pyramid3", "Stress tests", "Pyramid", "examples3d/stress_tests/pyramid3.rs", tbStressTestsPyramid3, NULL}, + {"stress_tests_trimesh3", "Stress tests", "Trimesh", "examples3d/stress_tests/trimesh3.rs", tbStressTestsTrimesh3, NULL}, + {"stress_tests_joint_ball3", "Stress tests", "ImpulseJoint ball", "examples3d/stress_tests/joint_ball3.rs", tbStressTestsJointBall3, NULL}, + {"stress_tests_joint_fixed3", "Stress tests", "ImpulseJoint fixed", "examples3d/stress_tests/joint_fixed3.rs", tbStressTestsJointFixed3, NULL}, + {"stress_tests_joint_revolute3", "Stress tests", "ImpulseJoint revolute", "examples3d/stress_tests/joint_revolute3.rs", tbStressTestsJointRevolute3, NULL}, + {"stress_tests_joint_prismatic3", "Stress tests", "ImpulseJoint prismatic", "examples3d/stress_tests/joint_prismatic3.rs", tbStressTestsJointPrismatic3, NULL}, + {"stress_tests_ragdolls3", "Stress tests", "Ragdoll piles", "examples3d/stress_tests/ragdolls3.rs", tbStressTestsRagdolls3, NULL}, + {"stress_tests_ropes3", "Stress tests", "Ropes", "examples3d/stress_tests/ropes3.rs", tbStressTestsRopes3, NULL}, + {"stress_tests_many_pyramids3", "Stress tests", "Many pyramids", "examples3d/stress_tests/many_pyramids3.rs", tbStressTestsManyPyramids3, NULL}, + {"stress_tests_keva3", "Stress tests", "Keva tower", "examples3d/stress_tests/keva3.rs", tbStressTestsKeva3, NULL}, + {"stress_tests_ray_cast3", "Stress tests", "Ray cast", "examples3d/stress_tests/ray_cast3.rs", tbStressTestsRayCast3, NULL}, + {"stress_tests_soft_blobs3", "Stress tests", "Soft blobs", "examples3d/stress_tests/soft_blobs3.rs", tbStressTestsSoftBlobs3, NULL}, + {"stress_tests_soft_jellies3", "Stress tests", "Soft jellies", "examples3d/stress_tests/soft_jellies3.rs", tbStressTestsSoftJellies3, NULL}, + {"stress_tests_soft_ropes3", "Stress tests", "Soft ropes", "examples3d/stress_tests/soft_ropes3.rs", tbStressTestsSoftRopes3, NULL}, + {"stress_tests_soft_cloth_drape3", "Stress tests", "Soft cloth drape", "examples3d/stress_tests/soft_cloth_drape3.rs", tbStressTestsSoftClothDrape3, NULL}, + {"stress_tests_soft_cloth_keva3", "Stress tests", "Soft cloth on Keva tower", "examples3d/stress_tests/soft_cloth_keva3.rs", tbStressTestsSoftClothKeva3, NULL}, + {"stress_tests_soft_slab3", "Stress tests", "Soft slab shower", "examples3d/stress_tests/soft_slab3.rs", tbStressTestsSoftSlab3, NULL}, +#ifdef RAPIER_FEM + {"stress_tests_soft_fem_beams3", "Stress tests", "Soft FEM beams", "examples3d/stress_tests/soft_fem_beams3.rs", tbStressTestsSoftFemBeams3, NULL}, +#else + {"stress_tests_soft_fem_beams3", "Stress tests", "Soft FEM beams", "examples3d/stress_tests/soft_fem_beams3.rs", NULL, "Requires RAPIER_FEATURES=fem"}, +#endif + {"b3d_large_pyramid", "Third-party benchmarks", "Large pyramid", "examples3d/b3d_large_pyramid.rs", tbB3dLargePyramid, NULL}, + {"b3d_many_pyramids", "Third-party benchmarks", "Many pyramids", "examples3d/b3d_many_pyramids.rs", tbB3dManyPyramids, NULL}, + {"b3d_joint_grid", "Third-party benchmarks", "Joint grid", "examples3d/b3d_joint_grid.rs", tbB3dJointGrid, NULL}, + {"b3d_junkyard", "Third-party benchmarks", "Junkyard", "examples3d/b3d_junkyard.rs", tbB3dJunkyard, NULL}, + {"b3d_washer", "Third-party benchmarks", "Washer", "examples3d/b3d_washer.rs", tbB3dWasher, NULL}, + {"b3d_trees_run100", "Third-party benchmarks", "Trees 100", "examples3d/b3d_trees.rs", tbB3dTreesRun100, NULL}, + {"b3d_trees_run50", "Third-party benchmarks", "Trees 50", "examples3d/b3d_trees.rs", tbB3dTreesRun50, NULL}, + {"b3d_trees_run25", "Third-party benchmarks", "Trees 25", "examples3d/b3d_trees.rs", tbB3dTreesRun25, NULL}, + {"b3d_rain", "Third-party benchmarks", "Rain", "examples3d/b3d_rain.rs", tbB3dRain, NULL}, + {"b3d_large_world", "Third-party benchmarks", "Large world", "examples3d/b3d_large_world.rs", tbB3dLargeWorld, NULL}, +}; +const size_t tbExampleCount=TB_COUNT(tbExamples); diff --git a/c/testbed/testbed.c b/c/testbed/testbed.c new file mode 100644 index 000000000..ef76e2498 --- /dev/null +++ b/c/testbed/testbed.c @@ -0,0 +1,361 @@ +#include "testbed_internal.h" +#include "testbed.h" +#include +#include +#include + +void tbCheck(Testbed *t, RAPIER_TYPE(Status) s, const char *call, const char *file, int line) { + if (s == RAPIER_CONST(OK)) { + return; + } + snprintf(t->error, sizeof(t->error), "%s:%d: %s: %s", file, line, call, RAPIER_FN(LastError)()); + longjmp(t->failure, 1); +} + +void tbDestroy(Testbed *t) { + for (size_t i = 0; i < t->renderMeshCount; ++i) { + TbRenderMesh *mesh = &t->renderMeshes[i]; + free(mesh->vertices); + free(mesh->indices); + free(mesh->uvs); + free(mesh->normals); + free(mesh->texture); + } + free(t->renderMeshes); + t->renderMeshes = NULL; + t->renderMeshCount = 0; + free(t->bodyColors); + free(t->colliderColors); + t->bodyColors = t->colliderColors = NULL; + t->bodyColorCount = t->colliderColorCount = 0; + t->world = NULL; + t->activeThreads = 0; +} + +int tbParseThreads(const char *text, size_t *out) { + if (!text || !*text || !out) { + return 0; + } + for (const char *p = text; *p; p++) { + if (*p < '0' || *p > '9') { + return 0; + } + } + char *end; + errno = 0; + unsigned long value = strtoul(text, &end, 10); + if (errno || *end || value > TB_MAX_THREADS) { + return 0; + } + *out = (size_t)value; + return 1; +} + +RAPIER_TYPE(Status) tbSetThreads(Testbed *t, size_t count) { + RAPIER_TYPE(BuildFeatures) features = RAPIER_FN(BuildFeatures)(); + RAPIER_TYPE(Status) status; + size_t active = 1; + if (features.parallel || count > 1) { + status = RAPIER_FN(SetNumThreads)(t->world, count); + if (status != RAPIER_CONST(OK)) { + return status; + } + active = RAPIER_FN(NumThreads)(t->world); + status = RAPIER_FN(LastStatus)(); + if (status != RAPIER_CONST(OK)) { + return status; + } + } + t->requestedThreads = count; + t->activeThreads = active; + t->buildFeatures = features; + return RAPIER_CONST(OK); +} + +void tbRefreshWorld(Testbed *t) { + t->buildFeatures = RAPIER_FN(BuildFeatures)(); + TB(t, tbSetThreads(t, t->requestedThreads)); + TB(t, RAPIER_FN(SetCountersEnabled)(t->world, 1)); + t->physicsStepMs = 0; +} + +static void sceneError(RAPIER_TYPE(Status) status, const char *message, void *userData) { + const Testbed *t = userData; + fprintf(stderr, "Rapier error in %s (status %u): %s\n", t->example->id, (unsigned)status, + message); + /* Do not longjmp out of a Rust frame. Stop before using a failed call's outputs. */ + exit(EXIT_FAILURE); +} + +int tbRun(Testbed *t, const TbExample *e, int preserve) { + tbDestroy(t); + t->error[0] = 0; + t->example = e; + t->step = 0; + t->time = 0; + t->randomState = 42; + t->framePending = 0; + t->snapshotSupported = 1; + t->inputDirection = V(0, 0, 0); + t->action = 0; + t->cutting = 0; + t->cursorValid = 0; + t->jump = t->descend = t->slow = t->boost = 0; + t->cameraRight = V(1, 0, 0); + t->cameraForward = V(0, 0, -1); + t->initialDebug = 0; + t->collidersVisible = 1; + t->frameAll = 0; + t->preserveCamera = 0; + t->up[0] = 0; + t->up[1] = 1; + t->up[2] = 0; + t->rayValid = t->removeVoxel = 0; + t->lineCount = 0; + t->labelCount = 0; + if (!preserve) { + t->settingCount = 0; + } + if (e->requires) { + snprintf(t->error, sizeof(t->error), "%s", e->requires); + return 0; + } + t->previousErrorHandler = + RAPIER_FN(SetErrorHandler)((RAPIER_TYPE(ErrorHandler)){sceneError, t}); + if (setjmp(t->failure)) { + RAPIER_FN(SetErrorHandler)(t->previousErrorHandler); + (void)RAPIER_FN(FreeWorld)(t->world); + t->world = NULL; + return 0; + } + tbCamera(t, 20, 15, 25, 0, 3, 0); + t->viewWidth = 30; + e->run(t); + RAPIER_FN(SetErrorHandler)(t->previousErrorHandler); + t->world = NULL; + return !t->error[0]; +} + +int tbRenderFrame(Testbed *t, RAPIER_TYPE(World) **world) { + /* UI operations handle recoverable errors themselves. The example resumes + * with its fail-fast handler after rendering and input have completed. */ + RAPIER_TYPE(ErrorHandler) sceneHandler = RAPIER_FN(SetErrorHandler)(t->previousErrorHandler); + if (setjmp(t->failure)) { + RAPIER_FN(SetErrorHandler)(sceneHandler); + *world = t->world; + return 0; + } + if (t->framePending) { + t->step++; + t->time += t->frameDt; + t->physicsStepMs = RAPIER_FN(StepTimeMs)(t->world); + TB(t, RAPIER_FN(LastStatus)()); + } + t->framePending = 0; + t->simulating = 0; + int keepOpen = t->renderFrame && t->renderFrame(t, t->viewer); + *world = t->world; + if (keepOpen && t->simulating && !t->error[0]) { + t->frameDt = RAPIER_FN(TimeStep)(t->world); + RAPIER_TYPE(Status) status = RAPIER_FN(LastStatus)(); + if (status != RAPIER_CONST(OK)) { + snprintf(t->error, sizeof(t->error), "%s", RAPIER_FN(LastError)()); + keepOpen = 0; + } + } + RAPIER_FN(SetErrorHandler)(sceneHandler); + return keepOpen; +} + +int tbSimulating(Testbed *t) { + t->framePending = t->simulating && !t->error[0]; + return t->framePending; +} + +void tbCamera(Testbed *t, float x, float y, float z, float tx, float ty, float tz) { + t->eye[0] = x; + t->eye[1] = y; + t->eye[2] = z; + t->target[0] = tx; + t->target[1] = ty; + t->target[2] = tz; +} + +void tbCamera2(Testbed *t, float x, float y, float zoom) { + tbCamera(t, x, y, 100, x, y, 0); + t->viewWidth = 1440.0f / zoom; +} + +double tbSetting(Testbed *t, const char *name, double initial, double min, double max, + int integer) { + for (size_t i = 0; i < t->settingCount; i++) { + if (!strcmp(t->settings[i].name, name)) { + return t->settings[i].value; + } + } + if (t->settingCount == TB_COUNT(t->settings)) { + snprintf(t->error, sizeof(t->error), "too many settings"); + longjmp(t->failure, 1); + } + t->settings[t->settingCount++] = (TbSetting){.name = name, + .value = initial, + .initial = initial, + .min = min, + .max = max, + .integer = integer}; + return initial; +} + +double tbLiveSetting(Testbed *t, const char *name, double initial, double min, double max, + int integer) { + double value = tbSetting(t, name, initial, min, max, integer); + for (size_t i = 0; i < t->settingCount; ++i) { + if (!strcmp(t->settings[i].name, name)) { + t->settings[i].live = 1; + } + } + return value; +} + +static void tint(TbTint **colors, size_t *count, uint32_t index, uint32_t generation, float r, + float g, float b, float a) { + if (index == UINT32_MAX) { + return; + } + if (index >= *count) { + size_t n = (size_t)index + 16; + TbTint *p = realloc(*colors, n * sizeof(*p)); + if (!p) { + abort(); + } + memset(p + *count, 0, (n - *count) * sizeof(*p)); + *colors = p; + *count = n; + } + (*colors)[index] = (TbTint){generation, {r, g, b, a}, 1}; +} + +void tbBodyColor(Testbed *t, RAPIER_TYPE(RigidBodyHandle) h, float r, float g, float b, float a) { + tint(&t->bodyColors, &t->bodyColorCount, h.index, h.generation, r, g, b, a); +} + +void tbColliderColor(Testbed *t, RAPIER_TYPE(ColliderHandle) h, float r, float g, float b, + float a) { + tint(&t->colliderColors, &t->colliderColorCount, h.index, h.generation, r, g, b, a); +} + +const float *tbFindColor(Testbed *t, RAPIER_TYPE(RigidBodyHandle) b, + RAPIER_TYPE(ColliderHandle) c) { + if (c.index < t->colliderColorCount && t->colliderColors[c.index].valid && + t->colliderColors[c.index].generation == c.generation) { + return t->colliderColors[c.index].rgba; + } + if (b.index < t->bodyColorCount && t->bodyColors[b.index].valid && + t->bodyColors[b.index].generation == b.generation) { + return t->bodyColors[b.index].rgba; + } + return NULL; +} + +void tbSetWorld(Testbed *testbed, RAPIER_TYPE(World) *world) { + testbed->world = world; + tbRefreshWorld(testbed); +} + +void tbLine(Testbed *t, RAPIER_TYPE(Vector) a, RAPIER_TYPE(Vector) b, float r, float g, float blue, + float alpha) { + if (t->lineCount == TB_COUNT(t->lines)) { + return; + } + size_t i = t->lineCount++; + t->lines[i].a = a; + t->lines[i].b = b; + t->lines[i].rgba[0] = r; + t->lines[i].rgba[1] = g; + t->lines[i].rgba[2] = blue; + t->lines[i].rgba[3] = alpha; +} + +void tbLabel(Testbed *t, const char *name, const char *value) { + size_t i = 0; + for (; i < t->labelCount; ++i) { + if (!strcmp(t->labels[i].name, name)) { + break; + } + } + if (i == TB_COUNT(t->labels)) { + return; + } + if (i == t->labelCount) { + ++t->labelCount; + } + t->labels[i].name = name; + snprintf(t->labels[i].value, sizeof(t->labels[i].value), "%s", value); +} +#ifdef _WIN32 +#include + +double tbClock(void) { + LARGE_INTEGER frequency, counter; + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&counter); + return (double)counter.QuadPart / frequency.QuadPart; +} +#else +#include + +double tbClock(void) { + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + return time.tv_sec + time.tv_nsec * 1e-9; +} +#endif + +size_t tbChoice(Testbed *t, const char *name, size_t initial, const char *const *choices, + size_t count, int live, int reset) { + tbSetting(t, name, initial, 0, count ? count - 1 : 0, 1); + for (size_t i = 0; i < t->settingCount; ++i) { + TbSetting *s = &t->settings[i]; + if (strcmp(s->name, name)) { + continue; + } + s->choices = choices; + s->choiceCount = count; + s->live = live; + s->max = count ? count - 1 : 0; + if (reset || s->value > s->max) { + s->value = initial; + } + return (size_t)s->value; + } + return initial; +} + +static void *copyRenderData(const void *data, size_t bytes) { + if (!data || !bytes) { + return NULL; + } + void *copy = malloc(bytes); + if (!copy) { + abort(); + } + memcpy(copy, data, bytes); + return copy; +} + +void tbAddBodyRenderMesh(Testbed *t, const TbRenderMesh *source) { + TbRenderMesh *meshes = realloc(t->renderMeshes, (t->renderMeshCount + 1) * sizeof(*meshes)); + if (!meshes) { + abort(); + } + t->renderMeshes = meshes; + TbRenderMesh *mesh = &meshes[t->renderMeshCount++]; + *mesh = *source; + mesh->vertices = + copyRenderData(source->vertices, source->vertexCount * sizeof(*source->vertices)); + mesh->indices = copyRenderData(source->indices, source->indexCount * sizeof(*source->indices)); + mesh->uvs = copyRenderData(source->uvs, source->vertexCount * 2 * sizeof(float)); + mesh->normals = copyRenderData(source->normals, source->vertexCount * 3 * sizeof(float)); + mesh->texture = + source->texture ? copyRenderData(source->texture, strlen(source->texture) + 1) : NULL; +} diff --git a/c/testbed/testbed.h b/c/testbed/testbed.h new file mode 100644 index 000000000..1f91a484a --- /dev/null +++ b/c/testbed/testbed.h @@ -0,0 +1,130 @@ +#ifndef RAPIER_TESTBED_H +#define RAPIER_TESTBED_H +#include "rapier.h" +#include +#include +#include +#include +#include +#define TB_PI ((RAPIER_TYPE(Real))3.14159265358979323846) +#define TB_MAX_THREADS 256 +#define TB_COUNT(a) (sizeof(a) / sizeof((a)[0])) +#if defined(RAPIER_DIM2) +#define V(x, y, ...) ((RAPIER_TYPE(Vector)){(RAPIER_TYPE(Real))(x), (RAPIER_TYPE(Real))(y)}) +#else +#define V(x, y, z) ((RAPIER_TYPE(Vector)){(RAPIER_TYPE(Real))(x), (RAPIER_TYPE(Real))(y), (RAPIER_TYPE(Real))(z)}) +#endif +typedef struct Testbed Testbed; + +typedef struct TbSetting { + const char *name; + double value, initial, min, max; + int integer, live; + const char *const *choices; + size_t choiceCount; +} TbSetting; + +typedef struct TbExample { + const char *id, *group, *name, *source; + void (*run)(Testbed *); + const char * + requires; +} TbExample; + +typedef struct TbTint { + uint32_t generation; + float rgba[4]; + int valid; +} TbTint; + +/* Render-only body geometry. Arrays are copied by tbAddBodyRenderMesh. */ +typedef struct TbRenderMesh { + RAPIER_TYPE(RigidBodyHandle) body; + RAPIER_TYPE(Pose) localPose; + RAPIER_TYPE(Vector) *vertices; + uint32_t *indices; + size_t vertexCount, indexCount; + float *uvs, *normals; + char *texture; + float rgba[4], metallic, roughness, reflectance, emissive[3]; +} TbRenderMesh; + +struct Testbed { + TbTint *bodyColors, *colliderColors; + size_t bodyColorCount, colliderColorCount; + RAPIER_TYPE(World) *world; + double physicsStepMs; + /* The renderer returns control to the example once per frame. */ + int (*renderFrame)(Testbed *, void *); + void *viewer; + int simulating, framePending, snapshotSupported; + RAPIER_TYPE(Real) frameDt; + RAPIER_TYPE(ErrorHandler) previousErrorHandler; + jmp_buf failure; + char error[1024]; + const TbExample *example; + uint64_t step, randomState; + double time; + int noSleep; + size_t requestedThreads, activeThreads; + RAPIER_TYPE(BuildFeatures) buildFeatures; + float eye[3], target[3], up[3], viewWidth; + int collidersVisible, frameAll, preserveCamera; + TbRenderMesh *renderMeshes; + size_t renderMeshCount; + RAPIER_TYPE(Vector) inputDirection; + int action, cutting, cursorValid, jump, descend, slow, boost; + RAPIER_TYPE(Vector) cameraRight, cameraForward; + RAPIER_TYPE(Vector) cursor, rayOrigin, rayDirection; + int rayValid, removeVoxel; + uint32_t initialDebug; + + struct { + RAPIER_TYPE(Vector) a, b; + float rgba[4]; + } lines[256]; + + size_t lineCount; + + struct { + const char *name; + char value[256]; + } labels[32]; + + size_t labelCount; + TbSetting settings[64]; + size_t settingCount; + const char *assetRoot; +}; + +double tbClock(void); +size_t tbChoice(Testbed *, const char *, size_t, const char *const *, size_t, int live, int reset); +void tbAddBodyRenderMesh(Testbed *, const TbRenderMesh *); +void tbLabel(Testbed *, const char *, const char *); +void tbLine(Testbed *, RAPIER_TYPE(Vector), RAPIER_TYPE(Vector), float, float, float, float); +void tbBodyColor(Testbed *, RAPIER_TYPE(RigidBodyHandle), float, float, float, float); +void tbColliderColor(Testbed *, RAPIER_TYPE(ColliderHandle), float, float, float, float); +const float *tbFindColor(Testbed *, RAPIER_TYPE(RigidBodyHandle), RAPIER_TYPE(ColliderHandle)); +extern const TbExample tbExamples[]; +extern const size_t tbExampleCount; +void tbDestroy(Testbed *); +int tbRun(Testbed *, const TbExample *, int preserveSettings); +/* Render one frame; update *world if the viewer restored a snapshot. */ +int tbRenderFrame(Testbed *, RAPIER_TYPE(World) **world); +int tbSimulating(Testbed *); +void tbRefreshWorld(Testbed *); +/* Borrow a scene world for rendering and controls; the example owns its lifetime. */ +void tbSetWorld(Testbed *, RAPIER_TYPE(World) *); +RAPIER_TYPE(Status) tbSetThreads(Testbed *, size_t); +int tbParseThreads(const char *, size_t *); +void tbCamera(Testbed *, float, float, float, float, float, float); +void tbCamera2(Testbed *, float, float, float); +double tbSetting(Testbed *, const char *, double, double, double, int); + +/* A setting read each frame by the example, applied without restarting. */ +double tbLiveSetting(Testbed *, const char *, double, double, double, int); + +int tbValidate(Testbed *, size_t *, size_t *, size_t *); +int tbHeadless(int, char **); +int tbGui(int, char **); +#endif diff --git a/c/testbed/testbed_internal.h b/c/testbed/testbed_internal.h new file mode 100644 index 000000000..46f7bf008 --- /dev/null +++ b/c/testbed/testbed_internal.h @@ -0,0 +1,7 @@ +#ifndef RAPIER_TESTBED_INTERNAL_H +#define RAPIER_TESTBED_INTERNAL_H +#include "testbed.h" +void tbCheck(Testbed *, RAPIER_TYPE(Status), const char *, const char *, int); +/* Checked viewer operations return to a C-only recovery boundary. */ +#define TB(t, call) tbCheck((t), (call), #call, __FILE__, __LINE__) +#endif diff --git a/c/testbed/tests/errors.c b/c/testbed/tests/errors.c new file mode 100644 index 000000000..40d182725 --- /dev/null +++ b/c/testbed/tests/errors.c @@ -0,0 +1,46 @@ +#include "testbed.h" +#include "rapier_helpers.h" + +static int renderFrame(Testbed *testbed, void *context) { + (void)context; + testbed->simulating = 1; + return 1; +} + +static void invalidCall(void) { + RAPIER_TYPE(ColliderDesc) collider = RAPIER_FN(BallColliderDesc)(-1); + RAPIER_FN(ShapeDesc_Build)(&collider.shape); + fputs("continued after failed call\n", stderr); + abort(); +} + +static void run(Testbed *testbed) { + if (testbed->action) { + invalidCall(); + } + RAPIER_TYPE(World) *world = RAPIER_FN(NewWorld)(); + tbSetWorld(testbed, world); + while (tbRenderFrame(testbed, &world)) { + if (tbSimulating(testbed)) { + RAPIER_FN(Step)(world, NULL, NULL); + invalidCall(); + } + } + RAPIER_FN(FreeWorld)(world); +} + +static void failSetup(Testbed *testbed) { + (void)testbed; + invalidCall(); +} + +int main(int argc, char **argv) { + Testbed testbed = {0}; + testbed.requestedThreads = 1; + testbed.renderFrame = renderFrame; + TbExample example = {"error-test", "", "", "", run, NULL}; + if (argc == 2 && !strcmp(argv[1], "setup")) { + example.run = failSetup; + } + return tbRun(&testbed, &example, 0) ? 0 : 2; +} diff --git a/c/testbed/tests/errors.cmake b/c/testbed/tests/errors.cmake new file mode 100644 index 000000000..033872f76 --- /dev/null +++ b/c/testbed/tests/errors.cmake @@ -0,0 +1,8 @@ +foreach(phase setup frame) + execute_process(COMMAND "${EXECUTABLE}" "${phase}" + RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE diagnostic) + if(NOT result STREQUAL "1" OR NOT diagnostic MATCHES "Rapier error in error-test.*positive" OR + diagnostic MATCHES "continued after failed call") + message(FATAL_ERROR "Unexpected ${phase} error behavior: ${result}\n${output}\n${diagnostic}") + endif() +endforeach() diff --git a/c/testbed/tests/grab.c b/c/testbed/tests/grab.c new file mode 100644 index 000000000..2ee3b0f5b --- /dev/null +++ b/c/testbed/tests/grab.c @@ -0,0 +1,185 @@ +/* Drive the same picking and spring joints as the viewer, without a GPU. */ +#include "grab.h" +#include "rapier_math.h" +#include +#define CHECK(call) \ + do { \ + RAPIER_TYPE(Status) status = (call); \ + if (status != RAPIER_CONST(OK)) { \ + fprintf(stderr, "%s: %s\n", #call, RAPIER_FN(LastError)()); \ + abort(); \ + } \ + } while (0) + +static void pointCursor(Testbed *t, RAPIER_TYPE(Vector) point) { + t->cursorValid = t->rayValid = 1; + t->cursor = point; + t->rayOrigin = RAPIER_FN(VectorAdd)(point, V(0, 0, 10)); + t->rayDirection = V(0, 0, -1); +} + +static void init(Testbed *t) { + t->world = RAPIER_FN(NewWorld)(); + CHECK(RAPIER_FN(LastStatus)()); + t->requestedThreads = 1; + tbRefreshWorld(t); + CHECK(RAPIER_FN(SetGravity)(t->world, V(0, 0, 0))); +} + +static RAPIER_TYPE(RigidBodyHandle) addBall(Testbed *t, RAPIER_TYPE(Vector) center, uint32_t kind, + bool sensor) { + RAPIER_TYPE(RigidBodyDesc) body = RAPIER_FN(DynamicRigidBodyDesc)(); + body.bodyType = kind; + body.position.translation = center; + RAPIER_TYPE(ColliderDesc) collider = RAPIER_FN(DefaultColliderDesc)(); + collider.isSensor = sensor; + RAPIER_TYPE(RigidBodyHandle) handle = RAPIER_FN(InsertRigidBody)(t->world, &body); + RAPIER_FN(InsertCollider)(handle, &collider); + CHECK(RAPIER_FN(LastStatus)()); + return handle; +} + +static void checkCounts(Testbed *t, size_t bodies, size_t joints) { + size_t actual = RAPIER_FN(RigidBodyCount)(t->world); + CHECK(RAPIER_FN(LastStatus)()); + assert(actual == bodies); + actual = RAPIER_FN(ImpulseJointHandles)(t->world, NULL, 0); + CHECK(RAPIER_FN(LastStatus)()); + assert(actual == joints); +} + +static void rigidDrag(void) { + Testbed t = {0}; + init(&t); + TbGrab grab = {0}; + RAPIER_TYPE(RigidBodyHandle) picked = addBall(&t, V(0, 0, 0), RAPIER_CONST(DYNAMIC), false); + addBall(&t, V(-3, 0, 0), RAPIER_CONST(DYNAMIC), true); + addBall(&t, V(3, 0, 0), RAPIER_CONST(FIXED), false); + CHECK(RAPIER_FN(Step)(t.world, NULL, NULL)); + pointCursor(&t, V(-3, 0, 0)); + CHECK(tbGrabBegin(&t, &grab, .1)); + assert(!grab.active); + pointCursor(&t, V(3, 0, 0)); + CHECK(tbGrabBegin(&t, &grab, .1)); + assert(!grab.active); + pointCursor(&t, V(0, 0, 0)); + CHECK(tbGrabBegin(&t, &grab, .1)); + assert(grab.active && !grab.soft && grab.body.index == picked.index); + checkCounts(&t, 4, 1); + pointCursor(&t, V(2, 1, 0)); + for (int i = 0; i < 120; ++i) { + CHECK(tbGrabUpdate(&t, &grab, V(0, 0, -1))); + CHECK(RAPIER_FN(Step)(t.world, NULL, NULL)); + } + + RAPIER_TYPE(Vector) position; + CHECK(RAPIER_FN(RigidBody_ValidateHandle)(picked)); + position = RAPIER_FN(RigidBody_Translation)(picked); + CHECK(RAPIER_FN(LastStatus)()); + assert(position.x > 1.5 && position.y > .5); + CHECK(tbGrabRelease(&t, &grab)); + assert(!grab.active); + checkCounts(&t, 3, 0); + pointCursor(&t, position); + CHECK(tbGrabBegin(&t, &grab, .1)); + assert(grab.active); + RAPIER_TYPE(Bool) removed = RAPIER_FN(RemoveRigidBody)(picked, 1); + CHECK(RAPIER_FN(LastStatus)()); + assert(removed); + CHECK(tbGrabUpdate(&t, &grab, V(0, 0, -1))); + assert(!grab.active); + checkCounts(&t, 2, 0); + CHECK(RAPIER_FN(FreeWorld)(t.world)); +} + +static void articulatedDrag(void) { + Testbed t = {0}; + init(&t); + TbGrab grab = {0}; + RAPIER_TYPE(RigidBodyDesc) builder = RAPIER_FN(FixedRigidBodyDesc)(); + RAPIER_TYPE(RigidBodyHandle) fixed = RAPIER_FN(InsertRigidBody)(t.world, &builder); + CHECK(RAPIER_FN(LastStatus)()); + RAPIER_TYPE(RigidBodyHandle) link = addBall(&t, V(0, 0, 0), RAPIER_CONST(DYNAMIC), false); + + RAPIER_TYPE(JointDesc) joint = RAPIER_FN(PrismaticJointDesc)(V(1, 0, 0)); + RAPIER_FN(InsertMultibodyJoint)(fixed, link, &joint); + CHECK(RAPIER_FN(LastStatus)()); + CHECK(RAPIER_FN(Step)(t.world, NULL, NULL)); + pointCursor(&t, V(0, 0, 0)); + CHECK(tbGrabBegin(&t, &grab, .1)); + assert(grab.active && grab.body.index == link.index); + pointCursor(&t, V(2, 0, 0)); + for (int i = 0; i < 120; ++i) { + CHECK(tbGrabUpdate(&t, &grab, V(0, 0, -1))); + CHECK(RAPIER_FN(Step)(t.world, NULL, NULL)); + } + + RAPIER_TYPE(Vector) position; + CHECK(RAPIER_FN(RigidBody_ValidateHandle)(link)); + position = RAPIER_FN(RigidBody_Translation)(link); + CHECK(RAPIER_FN(LastStatus)()); + assert(position.x > 1.5 && fabs(position.y) < .01); + CHECK(tbGrabRelease(&t, &grab)); + checkCounts(&t, 2, 0); + CHECK(RAPIER_FN(FreeWorld)(t.world)); +} + +static void softDrag(void) { + Testbed t = {0}; + init(&t); + TbGrab grab = {0}; + +#if defined(RAPIER_DIM2) + RAPIER_TYPE(SoftBodyDesc) builder = RAPIER_FN(GridSoftBodyDesc)(V(0, 0, 0), V(.5, .5, 0), 3, 3); +#else + RAPIER_TYPE(SoftBodyDesc) + builder = RAPIER_FN(ClothSoftBodyDesc)(V(-.5, -.5, 0), V(.5, 0, 0), V(0, .5, 0), 3, 3); +#endif + + RAPIER_TYPE(ColliderDesc) surface = RAPIER_FN(BallColliderDesc)(.05); + builder.collider = surface; + RAPIER_TYPE(SoftBodyHandle) handle = RAPIER_FN(InsertSoftBody)(t.world, &builder); + CHECK(RAPIER_FN(LastStatus)()); + CHECK(RAPIER_FN(Step)(t.world, NULL, NULL)); + + size_t before, after, bodyCount; + CHECK(RAPIER_FN(SoftBody_ValidateHandle)(handle)); + before = RAPIER_FN(SoftBody_Clusters)(handle, NULL, 0); + CHECK(RAPIER_FN(LastStatus)()); + bodyCount = RAPIER_FN(RigidBodyCount)(t.world); + CHECK(RAPIER_FN(LastStatus)()); + pointCursor(&t, V(-.4, -.4, 0)); + CHECK(tbGrabBegin(&t, &grab, .2)); + assert(grab.active && grab.soft); + CHECK(RAPIER_FN(SoftBody_ValidateHandle)(handle)); + after = RAPIER_FN(SoftBody_Clusters)(handle, NULL, 0); + CHECK(RAPIER_FN(LastStatus)()); + assert(after == before + 1); + RAPIER_TYPE(Vector) start = RAPIER_FN(SoftBody_ParticlePosition)(handle, 0); + CHECK(RAPIER_FN(LastStatus)()); + pointCursor(&t, V(-1.5, 1, 0)); + for (int i = 0; i < 60; ++i) { + CHECK(tbGrabUpdate(&t, &grab, V(0, 0, -1))); + CHECK(RAPIER_FN(Step)(t.world, NULL, NULL)); + } + RAPIER_TYPE(Vector) end; + CHECK(RAPIER_FN(SoftBody_ValidateHandle)(handle)); + end = RAPIER_FN(SoftBody_ParticlePosition)(handle, 0); + CHECK(RAPIER_FN(LastStatus)()); + assert(RAPIER_FN(VectorLength)(RAPIER_FN(VectorSub)(end, start)) > .3); + CHECK(tbGrabRelease(&t, &grab)); + CHECK(RAPIER_FN(SoftBody_ValidateHandle)(handle)); + after = RAPIER_FN(SoftBody_Clusters)(handle, NULL, 0); + CHECK(RAPIER_FN(LastStatus)()); + assert(after == before); + checkCounts(&t, bodyCount, 0); + CHECK(RAPIER_FN(FreeWorld)(t.world)); +} + +int main(void) { + rigidDrag(); + articulatedDrag(); + softDrag(); + puts("Mouse picking, rigid/soft spring dragging, release, and deletion passed"); + return 0; +} diff --git a/c/testbed/tests/interactive.c b/c/testbed/tests/interactive.c new file mode 100644 index 000000000..401e419c3 --- /dev/null +++ b/c/testbed/tests/interactive.c @@ -0,0 +1,165 @@ +/* Exercise the real example loops with synthetic input; no renderer is involved. */ +#include "testbed.h" +#include "rapier_math.h" +#include + +#define CHECK(call) \ + do { \ + if ((call) != RAPIER_CONST(OK)) { \ + fprintf(stderr, "%s: %s\n", #call, RAPIER_FN(LastError)()); \ + abort(); \ + } \ + } while (0) + +typedef struct InputRun { + int kind, frame, mode; + RAPIER_TYPE(RigidBodyHandle) tracked; + RAPIER_TYPE(Vector) start, middle; + RAPIER_TYPE(Real) initialDistance; + uintptr_t initialShape; + RAPIER_TYPE(SharedShape) *retainedShape; + int verified; +} InputRun; + +static RAPIER_TYPE(Vector) bodyPosition(Testbed *t, RAPIER_TYPE(RigidBodyHandle) handle) { + assert(handle.world == t->world); + RAPIER_TYPE(Vector) position; + CHECK(RAPIER_FN(RigidBody_ValidateHandle)(handle)); + position = RAPIER_FN(RigidBody_Translation)(handle); + CHECK(RAPIER_FN(LastStatus)()); + return position; +} + +static int inputFrame(Testbed *t, void *context) { + InputRun *run = context; + size_t bodies, colliders, soft; + assert(tbValidate(t, &bodies, &colliders, &soft)); + if (run->kind == 0) { + t->cursorValid = 1; + t->cursor = V(.3, .65, 0); + if (!run->frame) { + RAPIER_TYPE(RigidBodyHandle) *handles = malloc(bodies * sizeof(*handles)); + assert(handles); + bodies = RAPIER_FN(RigidBodyHandles)(t->world, handles, bodies); + CHECK(RAPIER_FN(LastStatus)()); + run->tracked = handles[bodies - 1]; + free(handles); + } + RAPIER_TYPE(Vector) delta = RAPIER_FN(VectorSub)(bodyPosition(t, run->tracked), t->cursor); + RAPIER_TYPE(Real) distance = RAPIER_FN(VectorLength)(delta); + if (run->frame == 1) { + run->initialDistance = distance; + } + if (run->frame == 60) { + assert(distance < .03); + assert(distance < run->initialDistance * .25); + run->verified = 1; + return 0; + } + } else if (run->kind == 1) { + if (!run->frame) { + RAPIER_TYPE(RigidBodyHandle) *handles = malloc(bodies * sizeof(*handles)); + assert(handles); + bodies = RAPIER_FN(RigidBodyHandles)(t->world, handles, bodies); + CHECK(RAPIER_FN(LastStatus)()); + int found = 0; + for (size_t i = 0; i < bodies; ++i) { + uint32_t type; + CHECK(RAPIER_FN(RigidBody_ValidateHandle)(handles[i])); + type = RAPIER_FN(RigidBody_BodyType)(handles[i]); + CHECK(RAPIER_FN(LastStatus)()); + if (type == RAPIER_CONST(KINEMATIC_POSITION_BASED)) { + run->tracked = handles[i]; + found = 1; + break; + } + } + assert(found); + free(handles); + run->start = bodyPosition(t, run->tracked); + } + t->inputDirection = V(1, 0, 0); + t->jump = 1; + if (run->frame == 20) { + run->middle = bodyPosition(t, run->tracked); + assert(run->middle.x > run->start.x + .2); + for (size_t i = 0; i < t->settingCount; ++i) { + if (!strcmp(t->settings[i].name, "Control mode")) { + t->settings[i].value = 1; + } + } + } + if (run->frame == 40) { + uint32_t type; + CHECK(RAPIER_FN(RigidBody_ValidateHandle)(run->tracked)); + type = RAPIER_FN(RigidBody_BodyType)(run->tracked); + CHECK(RAPIER_FN(LastStatus)()); + assert(type == RAPIER_CONST(DYNAMIC)); + assert(bodyPosition(t, run->tracked).x > run->middle.x + .1); + run->verified = 1; + return 0; + } + } +#if defined(RAPIER_DIM3) + else if (run->kind == 2) { + + RAPIER_TYPE(ColliderHandle) handle = {t->world, 0, 0}; + CHECK(r3Collider_ValidateHandle(handle)); + uintptr_t identity; + identity = r3Collider_ShapeIdentity(handle); + CHECK(r3LastStatus()); + if (!run->frame) { + run->initialShape = identity; + run->retainedShape = r3Collider_CloneShape(handle); + CHECK(r3LastStatus()); + } + t->rayValid = 1; + t->rayOrigin = r3Vector(100, 100, 100); + t->rayDirection = r3Vector(0, -1, 0); + t->jump = 1; + t->removeVoxel = run->mode; + if (run->frame == 2) { + assert(identity != run->initialShape); + CHECK(r3FreeSharedShape(run->retainedShape)); + run->verified = 1; + return 0; + } + } +#endif + ++run->frame; + t->simulating = 1; + return 1; +} + +static void checkExample(const char *id, int kind, int mode) { + Testbed t = {0}; + InputRun input = {.kind = kind, .mode = mode}; + t.renderFrame = inputFrame; + t.viewer = &input; + t.noSleep = 1; + t.requestedThreads = 1; + t.assetRoot = TB_ASSET_ROOT; + const TbExample *example = NULL; + for (size_t i = 0; i < tbExampleCount; ++i) { + if (!strcmp(tbExamples[i].id, id)) { + example = &tbExamples[i]; + } + } + assert(example && tbRun(&t, example, 0)); + assert(input.verified); + tbDestroy(&t); + printf("PASS %s interaction mode %d\n", id, mode); +} + +int main(void) { +#if defined(RAPIER_DIM2) + checkExample("inverse_kinematics2", 0, 0); + checkExample("character_controller2", 1, 0); +#else + checkExample("inverse_kinematics3", 0, 0); + checkExample("character_controller3", 1, 0); + checkExample("voxels3", 2, 0); + checkExample("voxels3", 2, 1); +#endif + return 0; +} diff --git a/c/testbed/tests/soft_render.c b/c/testbed/tests/soft_render.c new file mode 100644 index 000000000..bff2d93e9 --- /dev/null +++ b/c/testbed/tests/soft_render.c @@ -0,0 +1,188 @@ +/* Exercise the real soft-mesh renderer without creating a window or GPU context. */ +#include "testbed.h" +#include "raylib.h" +#include "rlgl.h" +#include + +static size_t triangles, lines, translucent; +static bool depthWrite = true, pendingTranslucent; +static int culling = 1, flushed; + +static void begin3d(Camera3D camera) { + (void)camera; +} + +static void end3d(void) { + /* raylib flushes buffered triangles here, using the current culling state. */ + assert(!culling); + ++flushed; +} + +static void enableCulling(void) { + culling = 1; +} + +static void disableCulling(void) { + culling = 0; +} + +static void flushBatch(void) { + if (pendingTranslucent) { + assert(!depthWrite); + pendingTranslucent = false; + } +} + +static void disableDepthWrite(void) { + assert(!pendingTranslucent); + depthWrite = false; +} + +static void enableDepthWrite(void) { + assert(!pendingTranslucent); + depthWrite = true; +} + +static void checkPoint(Vector3 point) { + assert(isfinite(point.x) && isfinite(point.y) && isfinite(point.z)); +} + +static void recordTriangle(Vector3 a, Vector3 b, Vector3 c, Color color) { + if (color.a < 255) { + assert(color.a == 102); + assert(!depthWrite); + ++translucent; + pendingTranslucent = true; + } + checkPoint(a); + checkPoint(b); + checkPoint(c); + ++triangles; +} + +static void recordLine(Vector3 a, Vector3 b, Color color) { + (void)color; + checkPoint(a); + checkPoint(b); + ++lines; +} + +static void *boundedRealloc(void *pointer, size_t bytes) { + /* Catch a sentinel used as a cache index before allocating gigabytes. */ + if (bytes > 64 * 1024 * 1024) { + fprintf(stderr, "Unexpected renderer allocation: %zu bytes\n", bytes); + abort(); + } + return realloc(pointer, bytes); +} + +#define BeginMode3D begin3d +#define EndMode3D end3d +#define rlEnableBackfaceCulling enableCulling +#define rlDisableBackfaceCulling disableCulling +#define DrawTriangle3D recordTriangle +#define DrawLine3D recordLine +#define rlDrawRenderBatchActive flushBatch +#define rlDisableDepthMask disableDepthWrite +#define rlEnableDepthMask enableDepthWrite +#define realloc boundedRealloc +#include "../graphics.c" +#undef BeginMode3D +#undef EndMode3D +#undef rlEnableBackfaceCulling +#undef rlDisableBackfaceCulling +#undef rlDrawRenderBatchActive +#undef rlDisableDepthMask +#undef rlEnableDepthMask +#undef realloc +#undef DrawLine3D +#undef DrawTriangle3D + +typedef struct RenderTest { + TbGraphics graphics; + size_t frames; + bool forceSensors, expectTranslucent; +} RenderTest; + +static int renderFrame(Testbed *t, void *context) { + RenderTest *test = context; + if (!test->frames && test->forceSensors) { + size_t count = RAPIER_FN(ColliderHandles)(t->world, NULL, 0); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + RAPIER_TYPE(ColliderHandle) *handles = malloc(count * sizeof(*handles)); + assert(handles); + count = RAPIER_FN(ColliderHandles)(t->world, handles, count); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + for (size_t i = 0; i < count; ++i) { + assert(RAPIER_FN(Collider_ValidateHandle)(handles[i]) == RAPIER_CONST(OK)); + assert(RAPIER_FN(Collider_SetSensor)(handles[i], 1) == RAPIER_CONST(OK)); + } + free(handles); + } + triangles = lines = translucent = 0; + test->graphics.transparentCount = 0; + drawSoft(&test->graphics, t, true, (Camera3D){.position = {0, 0, 100}, .fovy = 20}); + assert(translucent == 0); /* No sensor geometry in the opaque pass. */ + size_t queued = test->graphics.transparentCount; + drawTransparent(&test->graphics); + assert(translucent == queued && depthWrite && !pendingTranslucent); + assert((translucent > 0) == test->expectTranslucent); + for (size_t i = 1; i < queued; ++i) { + assert(test->graphics.transparent[i - 1].depth >= test->graphics.transparent[i].depth); + } + assert(triangles + lines > 0); + assert(test->graphics.entryCapacity < 1024); + /* Turning surfaces off must also avoid indexing render-only colliders. */ + drawSoft(&test->graphics, t, false, (Camera3D){0}); + /* The full frame must flush before restoring culling, even on an empty + * surface pass. Mesh draw calls above are recorded without a GPU. */ + int previousFlushes = flushed; + assert(tbGraphicsDraw(&test->graphics, t, (Camera3D){0}, 0, false)); + assert(flushed == previousFlushes + 1 && culling); + if (++test->frames == 4) { + return 0; + } + t->simulating = 1; + return 1; +} + +static void checkScene(const char *id, bool forceSensors, bool expectTranslucent) { + const TbExample *example = NULL; + for (size_t i = 0; i < tbExampleCount; ++i) { + if (!strcmp(tbExamples[i].id, id)) { + example = &tbExamples[i]; + } + } + assert(example && !example->requires); + Testbed t = {0}; + RenderTest test = {.forceSensors = forceSensors, .expectTranslucent = expectTranslucent}; + t.renderFrame = renderFrame; + t.viewer = &test; + t.requestedThreads = 1; + t.assetRoot = TB_ASSET_ROOT; + t.noSleep = 1; + assert(tbRun(&t, example, 0)); + assert(test.frames == 4 && t.step == 3); + free(test.graphics.transparent); + free(test.graphics.entries); + free(test.graphics.softHandles); + free(test.graphics.softMeshes); + free(test.graphics.vertices); + free(test.graphics.indices); + tbDestroy(&t); + printf("PASS soft renderer: %s\n", id); +} + +int main(void) { +#if defined(RAPIER_DIM3) + checkScene("soft_meshes3", false, true); + checkScene("soft_trimesh3", false, false); + checkScene("soft_surface3", false, false); + checkScene("soft_cloth3", false, false); +#else + checkScene("soft_letters2", false, false); + checkScene("soft_surface2", false, false); + checkScene("soft_surface2", true, true); +#endif + return 0; +} diff --git a/c/testbed/tests/threading.c b/c/testbed/tests/threading.c new file mode 100644 index 000000000..21a06775e --- /dev/null +++ b/c/testbed/tests/threading.c @@ -0,0 +1,122 @@ +#include "testbed.h" +#include + +#define CHECK(call) \ + do { \ + if ((call) != RAPIER_CONST(OK)) { \ + fprintf(stderr, "%s: %s\n", #call, RAPIER_FN(LastError)()); \ + abort(); \ + } \ + } while (0) + +typedef struct Viewer { + unsigned frame; + uint64_t expectedStep, snapshotStep; + double snapshotTime; + RAPIER_TYPE(Bytes) *snapshot; + RAPIER_TYPE(World) *original; +} Viewer; + +static const TbExample *example(const char *name) { + for (size_t i = 0; i < tbExampleCount; i++) { + if (!strcmp(tbExamples[i].name, name)) { + return &tbExamples[i]; + } + } + abort(); +} + +static int renderFrame(Testbed *t, void *context) { + Viewer *viewer = context; + assert(t->step == viewer->expectedStep); + assert(t->activeThreads == (t->requestedThreads ? t->requestedThreads : t->activeThreads)); + if (viewer->frame == 0) { + viewer->original = t->world; + CHECK(RAPIER_FN(SetTimeStep)(t->world, (RAPIER_TYPE(Real))(1.0 / 120))); + } + if (viewer->frame == 3) { + if (t->buildFeatures.parallel) { + CHECK(tbSetThreads(t, 2)); + assert(t->activeThreads == 2); + CHECK(tbSetThreads(t, 0)); + assert(t->activeThreads > 0); + CHECK(tbSetThreads(t, 2)); + } else { + assert(tbSetThreads(t, 2) == RAPIER_CONST(UNSUPPORTED)); + assert(t->activeThreads == 1); + } + assert(t->world == viewer->original); + RAPIER_TYPE(Real) dt = RAPIER_FN(TimeStep)(t->world); + CHECK(RAPIER_FN(LastStatus)()); + assert(fabs(dt - 1.0 / 120) < 1.0e-6); + } + if (viewer->frame == 10) { + viewer->snapshot = RAPIER_FN(SerializeWorld)(t->world); + CHECK(RAPIER_FN(LastStatus)()); + viewer->snapshotStep = t->step; + viewer->snapshotTime = t->time; + } + if (viewer->frame == 15) { + const uint8_t *data; + size_t size; + RAPIER_TYPE(World) *restored = NULL; + RAPIER_TYPE(ByteView) bytesDataResult = RAPIER_FN(Bytes_Data)(viewer->snapshot); + data = bytesDataResult.data; + size = bytesDataResult.count; + CHECK(RAPIER_FN(LastStatus)()); + restored = RAPIER_FN(DeserializeWorld)(data, size); + CHECK(RAPIER_FN(LastStatus)()); + CHECK(RAPIER_FN(FreeWorld)(t->world)); + t->world = restored; + t->step = viewer->snapshotStep; + t->time = viewer->snapshotTime; + viewer->expectedStep = t->step; + tbRefreshWorld(t); + assert(t->activeThreads == t->requestedThreads); + } + if (viewer->frame == 30) { + size_t bodies, colliders, softBodies; + assert(tbValidate(t, &bodies, &colliders, &softBodies)); + assert(bodies > 0 && colliders > 0); + CHECK(RAPIER_FN(FreeBytes)(viewer->snapshot)); + viewer->snapshot = NULL; + return 0; + } + /* Paused frames still return to the example. One requested step advances once. */ + t->simulating = viewer->frame != 7 && viewer->frame != 9; + viewer->expectedStep += t->simulating; + viewer->frame++; + return 1; +} + +int main(void) { + size_t parsed; + assert(tbParseThreads("0", &parsed) && parsed == 0); + assert(tbParseThreads("1", &parsed) && parsed == 1); + assert(tbParseThreads("256", &parsed) && parsed == 256); + assert(!tbParseThreads("257", &parsed)); + assert(!tbParseThreads("-1", &parsed)); + assert(!tbParseThreads("", &parsed)); + assert(!tbParseThreads("2x", &parsed)); + assert(!tbParseThreads("999999999999999999999999999999999999999", &parsed)); + Testbed testbed = {0}; + testbed.requestedThreads = 1; + testbed.noSleep = 1; + testbed.assetRoot = TB_ASSET_ROOT; + testbed.renderFrame = renderFrame; + const char *names[] = {"Restitution", "Restitution", "Damping"}; + for (size_t i = 0; i < TB_COUNT(names); i++) { + Viewer viewer = {0}; + testbed.viewer = &viewer; + size_t requested = testbed.requestedThreads; + assert(tbRun(&testbed, example(names[i]), i == 1)); + assert(!testbed.world); /* The example freed its world after its loop. */ + assert(viewer.frame == 30); + if (i) { + assert(testbed.requestedThreads == requested); + } + } + tbDestroy(&testbed); + puts("Example-owned loops, pause/step, worker changes, restart/switch, and snapshot restore " + "passed"); +} diff --git a/c/testbed/tests/ui.c b/c/testbed/tests/ui.c new file mode 100644 index 000000000..4111b749b --- /dev/null +++ b/c/testbed/tests/ui.c @@ -0,0 +1,105 @@ +/* Exercise the actual C UI and ImGui input routing without a display or GPU. */ +#define TB_UI_TESTING +#include "../gui.c" +#include + +static void frame(UiState *ui, bool focusSearch) { + igNewFrame(); + igSetNextWindowPos(UI2(0, 0), ImGuiCond_Always, UI2(0, 0)); + igSetNextWindowSize(UI2(600, 800), ImGuiCond_Always); + igBegin("Input regression", NULL, ImGuiWindowFlags_NoSavedSettings); + if (focusSearch) { + igSetKeyboardFocusHere(0); + } + examplesUi(ui); + igEnd(); + keyboardShortcuts(ui); + igRender(); + /* A null renderer acknowledges atlas updates; no GPU upload is needed. */ + ImDrawData *draw = igGetDrawData(); + if (draw->Textures) { + for (int i = 0; i < draw->Textures->Size; i++) { + ImTextureData *texture = draw->Textures->Data[i]; + if (texture->Status == ImTextureStatus_WantCreate || + texture->Status == ImTextureStatus_WantUpdates) { + ImTextureData_SetTexID(texture, 1); + ImTextureData_SetStatus(texture, ImTextureStatus_OK); + } else if (texture->Status == ImTextureStatus_WantDestroy) { + ImTextureData_SetStatus(texture, ImTextureStatus_Destroyed); + } + } + } +} + +int main(void) { + ImGuiContext *context = igCreateContext(NULL); + assert(context); + ImGuiIO *io = igGetIO_Nil(); + io->DisplaySize = UI2(1440, 900); + io->DisplayFramebufferScale = UI2(1, 1); + io->DeltaTime = 1.0f / 60; + io->BackendFlags |= ImGuiBackendFlags_RendererHasTextures; + igStyleColorsLight(NULL); + assert(configureUi()); + UiState ui = {0}; + ui.running = true; + ui.initialTab = "Examples"; + /* Navigation wraps around the available, filtered examples. */ + ui.selected = 0; + int next = adjacentExample(&ui, 1); + assert(next != 0 && !tbExamples[next].requires); + ui.selected = next; + assert(adjacentExample(&ui, -1) == 0); + snprintf(ui.search, sizeof(ui.search), "no-such-example"); + assert(adjacentExample(&ui, 1) == next); + ui.search[0] = 0; +#if defined(RAPIER_DIM3) + Camera3D camera = {.position = {3, 2, 5}, .target = {1, 0, 0}, .up = {0, 1, 0}, .fovy = 45}; + float distance = Vector3Distance(camera.position, camera.target); + Vector3 target = camera.target; + orbitCamera(&camera, (Vector2){100, 50}, false, 0, 900); + assert(Vector3Distance(camera.target, target) < .0001f); + assert(fabsf(Vector3Distance(camera.position, camera.target) - distance) < .0001f); + Vector3 offset = Vector3Subtract(camera.position, camera.target); + orbitCamera(&camera, (Vector2){30, 20}, true, 0, 900); + assert(Vector3Distance(camera.target, target) > .01f); + assert(Vector3Distance(Vector3Subtract(camera.position, camera.target), offset) < .0001f); + orbitCamera(&camera, (Vector2){0}, false, 1, 900); + assert(Vector3Distance(camera.position, camera.target) < distance); +#endif + frame(&ui, true); + frame(&ui, false); + assert(io->WantCaptureKeyboard); + ImGuiIO_AddInputCharactersUTF8(io, "test"); + ImGuiKey keys[] = {ImGuiKey_T, ImGuiKey_S, ImGuiKey_R, ImGuiKey_F}; + for (size_t i = 0; i < TB_COUNT(keys); i++) { + ImGuiIO_AddKeyEvent(io, keys[i], true); + frame(&ui, false); + assert(ui.running && !ui.stepOnce && !ui.restart && !ui.frameAll); + ImGuiIO_AddKeyEvent(io, keys[i], false); + frame(&ui, false); + } + assert(!strcmp(ui.search, "test")); + assert(matches("Stress tests", ui.search)); + assert(!matches("Restitution", ui.search)); + /* Clicking the scene relinquishes capture; hotkeys work again. */ + ImGuiIO_AddMousePosEvent(io, 1000, 500); + frame(&ui, false); + ImGuiIO_AddMouseButtonEvent(io, 0, true); + frame(&ui, false); + ImGuiIO_AddMouseButtonEvent(io, 0, false); + frame(&ui, false); + frame(&ui, false); + assert(!io->WantCaptureKeyboard && !io->WantCaptureMouse); + ImGuiIO_AddKeyEvent(io, ImGuiKey_T, true); + frame(&ui, false); + assert(!ui.running); + ImGuiIO_AddKeyEvent(io, ImGuiKey_T, false); + frame(&ui, false); + ImGuiIO_AddKeyEvent(io, ImGuiKey_S, true); + frame(&ui, false); + assert(ui.stepOnce); + igDestroyContext(context); + puts("Dear ImGui text capture, scene input, and simulation shortcuts passed"); + return 0; +} diff --git a/c/testbed/tools/README.md b/c/testbed/tools/README.md new file mode 100644 index 000000000..273da62bc --- /dev/null +++ b/c/testbed/tools/README.md @@ -0,0 +1,38 @@ +# Comparing C and native Rust step costs + +`step_benchmark.c` runs the same C examples and their own physics loops as the +viewer, using a headless frame function to record timings and snapshots. `c/examples/compare_steps.rs` replays the C world's initial snapshot with +native `PhysicsWorld::step`. Both use one worker, disable sleeping, discard 120 +warmup steps, and measure the next 300 steps. Setup, rendering, and serialization +are outside the measured region. Native Rust checks every final rigid-body pose +and velocity for exact equality with C, and both verify that dynamic bodies stay +awake. Use only trusted snapshots from the same checkout/configuration. + +From the repository root, on a quiet machine: + +```sh +cmake -S c -B build/compare3 -DRAPIER_BUILD_TESTBED=ON \ + -DRAPIER_TESTBED_GRAPHICS=OFF -DRAPIER_PROFILE=release \ + -DCMAKE_BUILD_TYPE=Release -DRAPIER_ENABLE_PARALLEL=ON -DRAPIER_SIMD_LANES=4 +cmake --build build/compare3 --config Release --target rapier_testbed_step_benchmark +cargo build --release -p rapier3d-ffi --features parallel,profiler --example compare_steps \ + --target-dir build/compare3/cargo +build/compare3/testbed/rapier_testbed_step_benchmark primitives3 initial.bin final.bin +build/compare3/cargo/release/examples/compare_steps initial.bin final.bin +``` + +Repeat with `stress_tests_boxes3` or `stress_tests_keva3` (38,270 dynamic +bodies; a complete Keva pair takes a few minutes with one worker). For 2D, use a separate directory, set +`-DRAPIER_DIMENSION=2`, select `rapier2d-ffi`, and run `stress_tests_boxes2`. +Multi-configuration generators put the C executable under `testbed/Release/`; +Windows also needs the `.exe` suffix. The Rust example supports f32 builds only. +For a serial build, set `RAPIER_ENABLE_PARALLEL=OFF` and use Cargo's +`--features profiler`. Build both executables before timing, run them sequentially, +repeat the pair, and compare medians; do not benchmark while other builds run. + +This isolates C-boundary/testbed stepping overhead for identical initial worlds. +It does not establish scene-construction parity for the whole catalog, benchmark +renderers, or support scenes with C callbacks or soft bodies. `wall_ms` measures +each complete call; `engine_ms` uses Rapier's own per-step counter, matching the +primary timing shown in both UIs. Dedicated-pool dispatch is outside that engine +counter. Snapshots are temporary benchmark artifacts, not a stable file format. diff --git a/c/testbed/tools/logo-mesh/Cargo.toml b/c/testbed/tools/logo-mesh/Cargo.toml new file mode 100644 index 000000000..ccab60dbd --- /dev/null +++ b/c/testbed/tools/logo-mesh/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rapier-c-logo-mesh" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +rapier2d = { path = "../../../../crates/rapier2d" } +lyon = "0.17" +usvg = "0.14" diff --git a/c/testbed/tools/logo-mesh/src/main.rs b/c/testbed/tools/logo-mesh/src/main.rs new file mode 100644 index 000000000..d82793150 --- /dev/null +++ b/c/testbed/tools/logo-mesh/src/main.rs @@ -0,0 +1,37 @@ +//! Regenerate examples2d/utils/logo_mesh.h with the Rust demo's exact SVG tessellation. +#[path = "../../../../../examples2d/utils/svg.rs"] +mod svg; + +fn main() { + println!("/* Generated from examples2d/utils/svg.rs by tools/logo-mesh. Do not edit. */"); + println!("#ifndef EXAMPLE_LOGO_MESH_H\n#define EXAMPLE_LOGO_MESH_H\n#include \"rapier.h\""); + println!( + "typedef struct LogoMesh {{ const R2Vector *vertices; size_t vertex_count; const uint32_t *indices; size_t triangle_count; const uint32_t *outline; size_t edge_count; }} LogoMesh;" + ); + let meshes = svg::rapier_logo(); + for (i, (vertices, indices)) in meshes.iter().enumerate() { + println!("static const R2Vector logo_vertices_{i}[] = {{"); + for p in vertices { + println!(" {{{:.9e}, {:.9e}}},", p.x, p.y); + } + println!("}};\nstatic const uint32_t logo_indices_{i}[] = {{"); + for t in indices { + println!(" {}, {}, {},", t[0], t[1], t[2]); + } + println!("}};\nstatic const uint32_t logo_outline_{i}[] = {{"); + for e in svg::outline(indices) { + println!(" {}, {},", e[0], e[1]); + } + println!("}};"); + } + println!("static const LogoMesh logo_meshes[] = {{"); + for (i, (vertices, indices)) in meshes.iter().enumerate() { + println!( + " {{logo_vertices_{i}, {}, logo_indices_{i}, {}, logo_outline_{i}, {}}},", + vertices.len(), + indices.len(), + svg::outline(indices).len() + ); + } + println!("}};\n#endif"); +} diff --git a/c/testbed/tools/step_benchmark.c b/c/testbed/tools/step_benchmark.c new file mode 100644 index 000000000..0132b3af1 --- /dev/null +++ b/c/testbed/tools/step_benchmark.c @@ -0,0 +1,112 @@ +/* Manual no-sleep benchmark, linked to the actual C testbed and loaded library. + * Writes trusted snapshots for compare_steps.rs; excludes setup and + * serialization. Only rigid-body scenes without local animation state are accepted so native + * Rust can replay them. + */ +#include "testbed.h" +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +static double nowMs(void) { +#ifdef _WIN32 + LARGE_INTEGER value, frequency; + assert(QueryPerformanceCounter(&value)); + assert(QueryPerformanceFrequency(&frequency)); + return (double)value.QuadPart * 1000.0 / (double)frequency.QuadPart; +#else + struct timespec t; + assert(clock_gettime(CLOCK_MONOTONIC, &t) == 0); + return t.tv_sec * 1000.0 + t.tv_nsec * 1.0e-6; +#endif +} + +static void save(Testbed *t, const char *path) { + RAPIER_TYPE(Bytes) *bytes = NULL; + const uint8_t *data = NULL; + size_t size = 0; + bytes = RAPIER_FN(SerializeWorld)(t->world); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + RAPIER_TYPE(ByteView) bytesDataResult = RAPIER_FN(Bytes_Data)(bytes); + data = bytesDataResult.data; + size = bytesDataResult.count; + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + FILE *file = fopen(path, "wb"); + assert(file && fwrite(data, 1, size, file) == size); + assert(fclose(file) == 0); + assert(RAPIER_FN(FreeBytes)(bytes) == RAPIER_CONST(OK)); +} + +typedef struct Benchmark { + const char *initialPath, *finalPath; + double start, wall, engine; +} Benchmark; + +static int renderFrame(Testbed *t, void *context) { + Benchmark *benchmark = context; + if (t->step == 0) { + assert(t->snapshotSupported); + size_t softCount = RAPIER_FN(SoftBodyCount)(t->world); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + assert(softCount == 0); + save(t, benchmark->initialPath); + } else if (t->step > 120) { + benchmark->wall += nowMs() - benchmark->start; + benchmark->engine += t->physicsStepMs; + } + if (t->step < 420) { + t->simulating = 1; + benchmark->start = nowMs(); + return 1; + } + size_t n = RAPIER_FN(RigidBodyHandles)(t->world, NULL, 0); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + RAPIER_TYPE(RigidBodyHandle) *handles = malloc(n * sizeof(*handles)); + n = RAPIER_FN(RigidBodyHandles)(t->world, handles, n); + assert(handles && RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + size_t active = 0; + for (size_t i = 0; i < n; i++) { + RAPIER_TYPE(Bool) dynamic = 0, sleeping = 0; + assert(RAPIER_FN(RigidBody_ValidateHandle)(handles[i]) == RAPIER_CONST(OK)); + dynamic = RAPIER_FN(RigidBody_IsDynamic)(handles[i]); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + sleeping = RAPIER_FN(RigidBody_IsSleeping)(handles[i]); + assert(RAPIER_FN(LastStatus)() == RAPIER_CONST(OK)); + if (dynamic) { + assert(!sleeping); + active++; + } + } + save(t, benchmark->finalPath); + printf("C %s bodies=%zu awake_dynamic=%zu SIMD=%u workers=%zu warmup=120 " + "measured=300 wall_ms=%.6f engine_ms=%.6f\n", + t->example->id, n, active, t->buildFeatures.simd_lanes, t->activeThreads, + benchmark->wall / 300, benchmark->engine / 300); + free(handles); + return 0; +} + +int main(int argc, char **argv) { + if (argc != 4) { + fprintf(stderr, "Usage: %s SCENE INITIAL_SNAPSHOT FINAL_SNAPSHOT\n", argv[0]); + return 2; + } + Testbed testbed = {0}; + testbed.noSleep = 1; + testbed.requestedThreads = 1; + testbed.assetRoot = TB_ASSET_ROOT; + testbed.renderFrame = renderFrame; + Benchmark benchmark = {.initialPath = argv[2], .finalPath = argv[3]}; + testbed.viewer = &benchmark; + const TbExample *scene = NULL; + for (size_t i = 0; i < tbExampleCount; i++) { + if (!strcmp(tbExamples[i].id, argv[1])) { + scene = &tbExamples[i]; + } + } + assert(scene && tbRun(&testbed, scene, 0)); + tbDestroy(&testbed); +} diff --git a/c/testbed/tools/timing-results.json b/c/testbed/tools/timing-results.json new file mode 100644 index 000000000..dcb8c9863 --- /dev/null +++ b/c/testbed/tools/timing-results.json @@ -0,0 +1,194 @@ +[ + { + "scene": "primitives3", + "repeat": 0, + "language": "C", + "output": "C primitives3 bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.944213 engine_ms=1.934008", + "wall_ms": 1.944213, + "engine_ms": 1.934008 + }, + { + "scene": "primitives3", + "repeat": 0, + "language": "Rust", + "output": "Rust bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.963726 engine_ms=1.954551 final_state=exact_match", + "wall_ms": 1.963726, + "engine_ms": 1.954551 + }, + { + "scene": "primitives3", + "repeat": 1, + "language": "C", + "output": "C primitives3 bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.954273 engine_ms=1.943762", + "wall_ms": 1.954273, + "engine_ms": 1.943762 + }, + { + "scene": "primitives3", + "repeat": 1, + "language": "Rust", + "output": "Rust bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=2.374521 engine_ms=2.329513 final_state=exact_match", + "wall_ms": 2.374521, + "engine_ms": 2.329513 + }, + { + "scene": "primitives3", + "repeat": 2, + "language": "C", + "output": "C primitives3 bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=2.027337 engine_ms=2.017319", + "wall_ms": 2.027337, + "engine_ms": 2.017319 + }, + { + "scene": "primitives3", + "repeat": 2, + "language": "Rust", + "output": "Rust bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=2.002452 engine_ms=1.993252 final_state=exact_match", + "wall_ms": 2.002452, + "engine_ms": 1.993252 + }, + { + "scene": "stress_tests_boxes3", + "repeat": 0, + "language": "C", + "output": "C stress_tests_boxes3 bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.916960 engine_ms=1.906777", + "wall_ms": 1.91696, + "engine_ms": 1.906777 + }, + { + "scene": "stress_tests_boxes3", + "repeat": 0, + "language": "Rust", + "output": "Rust bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.939562 engine_ms=1.928838 final_state=exact_match", + "wall_ms": 1.939562, + "engine_ms": 1.928838 + }, + { + "scene": "stress_tests_boxes3", + "repeat": 1, + "language": "C", + "output": "C stress_tests_boxes3 bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=2.035717 engine_ms=2.000051", + "wall_ms": 2.035717, + "engine_ms": 2.000051 + }, + { + "scene": "stress_tests_boxes3", + "repeat": 1, + "language": "Rust", + "output": "Rust bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.945655 engine_ms=1.935830 final_state=exact_match", + "wall_ms": 1.945655, + "engine_ms": 1.93583 + }, + { + "scene": "stress_tests_boxes3", + "repeat": 2, + "language": "C", + "output": "C stress_tests_boxes3 bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.932433 engine_ms=1.921569", + "wall_ms": 1.932433, + "engine_ms": 1.921569 + }, + { + "scene": "stress_tests_boxes3", + "repeat": 2, + "language": "Rust", + "output": "Rust bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.933630 engine_ms=1.923563 final_state=exact_match", + "wall_ms": 1.93363, + "engine_ms": 1.923563 + }, + { + "scene": "stress_tests_boxes2", + "repeat": 0, + "language": "C", + "output": "C stress_tests_boxes2 bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.395520 engine_ms=1.386361", + "wall_ms": 1.39552, + "engine_ms": 1.386361 + }, + { + "scene": "stress_tests_boxes2", + "repeat": 0, + "language": "Rust", + "output": "Rust bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.392803 engine_ms=1.384000 final_state=exact_match", + "wall_ms": 1.392803, + "engine_ms": 1.384 + }, + { + "scene": "stress_tests_boxes2", + "repeat": 1, + "language": "C", + "output": "C stress_tests_boxes2 bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.395897 engine_ms=1.386971", + "wall_ms": 1.395897, + "engine_ms": 1.386971 + }, + { + "scene": "stress_tests_boxes2", + "repeat": 1, + "language": "Rust", + "output": "Rust bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.413250 engine_ms=1.403848 final_state=exact_match", + "wall_ms": 1.41325, + "engine_ms": 1.403848 + }, + { + "scene": "stress_tests_boxes2", + "repeat": 2, + "language": "C", + "output": "C stress_tests_boxes2 bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.469100 engine_ms=1.459211", + "wall_ms": 1.4691, + "engine_ms": 1.459211 + }, + { + "scene": "stress_tests_boxes2", + "repeat": 2, + "language": "Rust", + "output": "Rust bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.474797 engine_ms=1.464414 final_state=exact_match", + "wall_ms": 1.474797, + "engine_ms": 1.464414 + }, + { + "scene": "stress_tests_keva3", + "repeat": 0, + "language": "C", + "output": "C stress_tests_keva3 bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=118.190743 engine_ms=118.121810", + "wall_ms": 118.190743, + "engine_ms": 118.12181 + }, + { + "scene": "stress_tests_keva3", + "repeat": 0, + "language": "Rust", + "output": "Rust bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=121.134219 engine_ms=121.063087 final_state=exact_match", + "wall_ms": 121.134219, + "engine_ms": 121.063087 + }, + { + "scene": "stress_tests_keva3", + "repeat": 1, + "language": "C", + "output": "C stress_tests_keva3 bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=120.751150 engine_ms=120.728234", + "wall_ms": 120.75115, + "engine_ms": 120.728234 + }, + { + "scene": "stress_tests_keva3", + "repeat": 1, + "language": "Rust", + "output": "Rust bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=120.755595 engine_ms=120.734268 final_state=exact_match", + "wall_ms": 120.755595, + "engine_ms": 120.734268 + }, + { + "scene": "stress_tests_keva3", + "repeat": 2, + "language": "C", + "output": "C stress_tests_keva3 bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=120.229320 engine_ms=120.200951", + "wall_ms": 120.22932, + "engine_ms": 120.200951 + }, + { + "scene": "stress_tests_keva3", + "repeat": 2, + "language": "Rust", + "output": "Rust bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=123.052455 engine_ms=123.034317 final_state=exact_match", + "wall_ms": 123.052455, + "engine_ms": 123.034317 + } +] diff --git a/c/testbed/tools/timing-results.md b/c/testbed/tools/timing-results.md new file mode 100644 index 000000000..d7bd83a4b --- /dev/null +++ b/c/testbed/tools/timing-results.md @@ -0,0 +1,43 @@ +# C testbed timing investigation (2026-09-18) + +Measured on macOS arm64 with Rust 1.98.0, the `soft-bodies` revision +`73a93dd9b`, and the C bindings in this worktree. Both paths used release builds, +f32, four-lane SIMD, parallel support with one dedicated worker, and native +profiling. Every dynamic body was awake; sleeping was disabled. + +Each row is the median of three runs, each with 120 warmup steps followed by +300 measured steps. Times are complete step-call wall time, excluding rendering, +scene construction, and serialization. C used the actual testbed core and shared +library. Rust replayed the identical initial snapshot with `PhysicsWorld::step` +and checked every final rigid-body pose and velocity against C for exact equality. + +| Scene | C ms/step | Rust ms/step | C / Rust | +| --- | ---: | ---: | ---: | +| `primitives3` | 1.954 | 2.002 | 0.976 | +| `stress_tests_boxes3` | 1.932 | 1.940 | 0.996 | +| `stress_tests_boxes2` | 1.396 | 1.413 | 0.988 | +| `stress_tests_keva3` | 120.229 | 121.134 | 0.993 | + +Keva contained 38,271 rigid bodies, of which 38,270 were dynamic and awake. +A further C run with parallelism compiled out measured **122.037 ms/step** +(native counter: 122.030 ms), so the earlier serial build path also had similar +per-step cost. Its loaded library reported `parallel=0`, `profiling=1`, SIMD 4. + +There was no 7x per-step overhead in these matched runs. These checks isolate the +C call path; they do not establish construction parity for every ported scene. + +The old C viewer accumulated real elapsed time and executed up to eight physics +steps before drawing a frame. Its original Physics label timed that entire batch. +The Rust viewer executes one step per frame and displays the native single-step +counter. Replaying the old C loop on Keva showed **746.84 ms/frame for six steps**, +while the native counter read **124.70 ms/step** in that same frame. It executed +65 steps in a 12-frame smoke run. This reproduces a large apparent slowdown +without a comparable increase in cost per physics step. + +The C viewer now advances one step per frame, matching Rust. The primary Physics +label uses the native per-step counter. The Performance panel separately reports +the complete simulation call time, step count, and draw CPU time. The corrected +Keva viewer executed exactly 60 steps in 60 frames; its screenshot showed +119.04 ms/step and 119.07 ms/frame for one step. + +See [reproduction instructions](README.md) and [all measured runs](timing-results.json). diff --git a/c/testbed/update_catalog.py b/c/testbed/update_catalog.py new file mode 100644 index 000000000..ca27173d1 --- /dev/null +++ b/c/testbed/update_catalog.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""Regenerate the example registry and audit coverage against the Rust testbed.""" +from pathlib import Path +import re, json +root=Path(__file__).resolve().parents[2] +target=root/'c/testbed' +entries=[] +for dim in [2,3]: + src=(root/f'examples{dim}d/all_examples{dim}.rs').read_text() + groups=dict(re.findall(r'const (\w+): &str = "([^"]+)";',src)) + out=['/* Generated by update_catalog.py; order matches the Rust testbed. */','#include "testbed.h"'] + rows=[] + for group,name,fn in re.findall(r'\b([A-Z0-9]+), "([^"]+)", ([\w:]+);',src): + bits=fn.split('::');stem='/'.join(bits[:-1]);suffix='' if bits[-1]=='run' else '_'+bits[-1] + ident=stem.replace('/','_')+suffix + source=f'examples{dim}d/{stem}.rs';port=f'examples{dim}d/{stem}{suffix}.c' + present=(target/port).exists() + symbol='tb'+''.join(part[:1].upper()+part[1:] for part in ident.split('_')) + if present:out.append(f'extern void {symbol}(Testbed *);') + feature='fem' if 'fem' in stem else 'robotics' if stem in ('urdf3', 'mjcf3', 'mujoco_menagerie3') else None + entries.append(dict(id=ident,dimension=dim,group=groups[group],name=name,rust=source,c=port if present else None,feature=feature)) + fields=', '.join(json.dumps(x) for x in [ident,groups[group],name,source]) + if present and feature: + rows.extend([f'#ifdef RAPIER_{feature.upper()}',f' {{{fields}, {symbol}, NULL}},','#else',f' {{{fields}, NULL, "Requires RAPIER_FEATURES={feature}"}},','#endif']) + else:rows.append(f' {{{fields}, '+(f'{symbol}, NULL' if present else 'NULL, "Port pending"')+'},') + out+=['const TbExample tbExamples[] = {',*rows,'};','const size_t tbExampleCount=TB_COUNT(tbExamples);'] + (target/f'registry{dim}.c').write_text('\n'.join(out)+'\n') +(target/'coverage.json').write_text(json.dumps(entries,indent=2)+'\n') +print(f'{sum(e["c"] is not None for e in entries)}/{len(entries)} C scene ports') From 146b994c848f0a435d78addf8bb0ae3ea0a19040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 24 Sep 2026 15:32:17 +0200 Subject: [PATCH 3/8] chore: fetch C testbed dependencies with CMake --- c/CMakeLists.txt | 2 +- c/README.md | 4 - c/docs/coverage.md | 77 - c/docs/engines.md | 79 - c/docs/validation.md | 471 ------ c/testbed/CMakeLists.txt | 52 +- c/testbed/PORTING.md | 72 - c/testbed/README.md | 12 +- c/testbed/THIRD_PARTY_NOTICES.txt | 1912 +++++++++++++++++++++++ c/testbed/cmake/Dependencies.cmake | 43 + c/testbed/dependencies.md | 73 + c/testbed/font_data.h.in | 2 +- c/testbed/licenses/Apache-2.0.txt | 202 +++ c/testbed/licenses/CC0-1.0.txt | 121 ++ c/testbed/licenses/FiraSans-LICENSE.txt | 93 ++ c/testbed/licenses/GLAD.txt | 63 + c/testbed/licenses/LGPL-2.1.txt | 501 ++++++ c/testbed/licenses/MIT.txt | 18 + c/testbed/licenses/SOURCES.md | 12 + c/testbed/licenses/WTFPL.txt | 11 + c/testbed/tools/timing-results.json | 194 --- c/testbed/tools/timing-results.md | 43 - 22 files changed, 3089 insertions(+), 968 deletions(-) delete mode 100644 c/docs/coverage.md delete mode 100644 c/docs/engines.md delete mode 100644 c/docs/validation.md delete mode 100644 c/testbed/PORTING.md create mode 100644 c/testbed/THIRD_PARTY_NOTICES.txt create mode 100644 c/testbed/cmake/Dependencies.cmake create mode 100644 c/testbed/dependencies.md create mode 100644 c/testbed/licenses/Apache-2.0.txt create mode 100644 c/testbed/licenses/CC0-1.0.txt create mode 100644 c/testbed/licenses/FiraSans-LICENSE.txt create mode 100644 c/testbed/licenses/GLAD.txt create mode 100644 c/testbed/licenses/LGPL-2.1.txt create mode 100644 c/testbed/licenses/MIT.txt create mode 100644 c/testbed/licenses/SOURCES.md create mode 100644 c/testbed/licenses/WTFPL.txt delete mode 100644 c/testbed/tools/timing-results.json delete mode 100644 c/testbed/tools/timing-results.md diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt index 2be028c11..fd97d1176 100644 --- a/c/CMakeLists.txt +++ b/c/CMakeLists.txt @@ -17,7 +17,7 @@ option(RAPIER_SHARED "Link the shared library (otherwise static)" ON) option(RAPIER_BUILD_TESTS "Build C/C++ integration tests" ON) option(RAPIER_BUILD_EXAMPLES "Build the falling-ball example" ON) option(RAPIER_BUILD_TESTBED "Build the C example testbed" OFF) -option(RAPIER_TESTBED_GRAPHICS "Include the vendored raylib viewer" ON) +option(RAPIER_TESTBED_GRAPHICS "Include the raylib viewer (fetches graphics dependencies)" ON) set(RAPIER_PROFILE "release" CACHE STRING "Cargo release or debug profile") set_property(CACHE RAPIER_PROFILE PROPERTY STRINGS release debug) set(RAPIER_FEATURES "" CACHE STRING "Additional Cargo features: enhanced-determinism,fem,profiler,robotics") diff --git a/c/README.md b/c/README.md index 81d9c1154..d2aeb4084 100644 --- a/c/README.md +++ b/c/README.md @@ -512,10 +512,6 @@ standard fixed, revolute, prismatic, rope, spring, 2D pin-slot, and 3D spherical ## Coverage and examples -See [docs/coverage.md](docs/coverage.md) for the API surface and explicit gaps, -[docs/validation.md](docs/validation.md) for the local validation record, -and [docs/engines.md](docs/engines.md) for Unity/Unreal integration notes. - - [testbed/README.md](testbed/README.md): raylib/Dear ImGui viewer and headless C scenes. - [examples/falling_ball.c](examples/falling_ball.c): complete C simulation. - [include/rapier.hpp](include/rapier.hpp): optional C++ ownership helpers. diff --git a/c/docs/coverage.md b/c/docs/coverage.md deleted file mode 100644 index 470135e20..000000000 --- a/c/docs/coverage.md +++ /dev/null @@ -1,77 +0,0 @@ -# Rust API correspondence - -The header is generated from Rust. The C API preserves Rapier's operation and -handle semantics while making `R3World` the sole owner of simulation components. -It is a broad runtime binding, not a binding of every Rust public item, solver -implementation detail, or Parry re-export. - -| Rust area | C surface | -| --- | --- | -| Math | Dimension-specific vectors, angular vectors, rotations, poses; optional C11/C++17 math constructors and arithmetic; AABBs, mass properties, groups, 128-bit user data, spring coefficients | -| `PhysicsWorld` | Construction, insertion from reusable descriptions, gravity, stepping, collision-only updates, snapshots, queries, debug lines | -| `PhysicsPipeline`, `CollisionPipeline` | World-owned workspaces; `Step` and `DetectCollisions`, hooks/events, dedicated parallel thread pools, native step timing and counter enablement | -| `IntegrationParameters` | Timestep, CCD, solver iterations, contact softness, length scale, warmstarting, clustering, recycling, friction bias; soft-body recovery and optional FEM scalar settings | -| `RigidBodyBuilder`, `RigidBody` | Types, transforms, kinematic targets, velocities, forces, impulses, torque, damping, gravity, mass/inertia, locks, sleep/wake, CCD, gyroscopic forces, dominance, iteration counts, user data, collider handles | -| `RigidBodySet`, `ColliderSet` | World-based insertion, handle lookup, enumeration, containment, coordinated removal, body-to-collider pose propagation | -| `SharedShape` | Balls, boxes, rounded boxes, capsules, segments, triangles, halfspaces, 3D cylinders/cones, convex hulls, convex decomposition, polylines, triangle meshes, compounds, heightfields with flags, triangle-mesh flags, voxels from points/meshes and voxel editing; bounds, point containment, mass properties | -| `ColliderBuilder`, `Collider` | Direct shape constructors, pose/local pose, density/mass, friction/restitution and combine rules, sensor/enabled, groups, collision types, events/hooks, contact skin, force threshold, parent, user data, bounds | -| Joints | Generic joints; fixed/revolute/prismatic/rope/spring/spherical/pin-slot constructors; frames, axes, limits, motors, motor models/force, contacts, softness, user data | -| `ImpulseJointSet` | Insertion/removal, handles, connected bodies, copied joint descriptions and setters with wake control | -| `MultibodyJointSet` | Insertion/removal, handles, joint data, tuning updates preserving degrees of freedom, articulation velocity read/write, inverse kinematics, generalized displacements | -| `QueryPipeline` | World queries with reusable POD options, filters and scoped C predicates, ray and linear shape casts, point projection, point/shape/AABB intersections | -| `NarrowPhase` | Contact pair lookup/enumeration, aggregate impulses, rigid geometric contact points, sensor intersection pairs | -| Events/hooks | Thread-safe event collection for collisions, contact forces and tears; contact/intersection filtering; rigid manifold material/normal/enabled modification, native one-way-platform context and tangent velocity | -| `SoftBodyBuilder`, `SoftBodySet` | Custom particles/edges/cells/surfaces/masses; rope, cloth/tube, cuboid/grid, triangle-mesh/polyline and volumetric generators; particle settings, material, cell model, shape matching, self-contact, surface collider, insertion/removal | -| `SoftBody` | Particle positions/velocities/targets/pinning, forces/impulses, rigid attachments, material, enable/wake, topology, volume, clusters/proxies, piece handles, deformed mesh vertices/indices by stable mesh ID, including collider-free skins | -| Soft topology | Immediate cuts/tears, owned tear events, particle destinations, torn/removed/inserted elements, pieces, cluster splits and moved joints; adding/removing/configuring clusters; direct/by-position/skinned deformable collider bindings | -| `KinematicCharacterController` | Up/offset/sliding/slopes/autostep/ground snapping; shape movement, collision output and collision impulse solving | -| `DynamicRayCastVehicleController` | 3D chassis, wheel creation/tuning, controls, vehicle axes, stepping, speed and wheel/contact state | -| PID controller | Native gains, controlled axes, rigid-body velocity corrections | -| URDF/MJCF | Optional 3D/f32 importers, options, both joint insertion paths, body handles, keyframes, scaled actuator controls, visual shapes/UVs/normals/textures/materials | -| Debug rendering | Caller-owned HSLA line buffers using Rapier debug mode flags | - -## Deliberate adaptations - -- Descriptions and configuration snapshots are POD values. Constructors return them - directly; insertion copies them into the world. Owned builders and independent - sets/pipelines are removed, with no compatibility aliases. -- Insertions clone inputs and removals discard removed objects. This avoids ambiguous - ownership transfer. A shape clone shares its immutable geometry through an Arc. -- Runtime Rust references become world-and-handle operations. Iterators/slices become - caller-owned buffers or explicit accessor calls. Error/Option results become - statuses, invalid handles, or documented nullable outputs. -- Standard joint constructors return `R3JointDesc`, preserving the Rust - `Into` relationship without an additional C allocation layer for - each joint-builder family. -- The character controller retains the last movement's collisions for buffer access. - Its impulse helper and the vehicle's update helper accept `PhysicsWorld` to avoid - exporting a mutable query view with difficult cross-language lifetime rules. -- Physics callbacks receive a scoped read context, plus their user pointer. World - reentrancy conflicts return `R3_WORLD_BUSY`; contact edits use the contact context. - Events are collected for polling and mutations after stepping. - -## Explicit gaps - -These are not exposed in this initial ABI: - -- Every specialized shape parameter/flag, direct editable heightfield data, - detailed shape-type introspection, standalone Parry pairwise queries, nonlinear - shape casts. -- Per-contact anchor edits, soft-contact candidate modification callbacks, - and full solver-manifold/soft-contact internal views. The exposed contact points - are geometric manifolds; contact-pair totals handle clustering correctly. -- Link/Jacobian internals, standalone passive-spring configuration, every robotics - loader/sensor option, and detailed profiler data. -- Every soft-body meshing/generator option, all per-element material overrides, custom - skin-mapping internals, and the full graph of diagnostic soft-body recovery data. - Arbitrary particle/cell/surface inputs and common mesh/cluster workflows are covered. -- Individual serialization APIs for each set/type, a stable cross-version snapshot - format, simultaneous f32/f64 variants of the same dimension, and engine editor - plugins or full generated language wrappers. 2D and 3D already have distinct - exported namespaces. - -New wrappers can be added without changing existing object layouts. Incompatible -changes to released exports, public POD layouts, enum meanings, or signatures -require an ABI version change and coordinated consumer updates. During initial -unreleased development, the ABI stays at version 1 and consumers must use matching -header and library revisions. diff --git a/c/docs/engines.md b/c/docs/engines.md deleted file mode 100644 index 8378ba017..000000000 --- a/c/docs/engines.md +++ /dev/null @@ -1,79 +0,0 @@ -# Engine and language integration - -Keep one physics world per scene/simulation, and store generational handles in -engine components. Build shapes once and share them across colliders. Stepping -and entity creation/removal belong on an exclusive physics thread or behind the -engine's synchronization boundary. Copy transforms and events into engine-owned -buffers before exposing them to other threads; never persist borrowed body or -collider pointers across a frame. - -## Unity / C# - -`examples/RapierNative.cs` is a small executable 3D/f32 P/Invoke example. Native -structs use `LayoutKind.Sequential`, uint for Rapier booleans/enums, and pointer- -sized `UIntPtr` for `size_t`. Always declare `CallingConvention.Cdecl`. Use -`SafeHandle` for owned worlds; borrowed set/element pointers must not have owning -finalizers. Keep the owning SafeHandle alive while using borrowed pointers. - -Build the matching native target and place the DLL/shared library in the project's -native plugin location for that platform/architecture. Configure Unity's plugin -importer accordingly. For iOS/static builds, an application may need `__Internal` -as its import library and platform-specific native-link setup. The provided -example does not perform platform packaging or editor configuration. - -Call physics at a fixed timestep and set Rapier's `IntegrationParameters.dt` to -that interval. Synchronize dynamic body results into Transform objects after -stepping; author kinematic targets before stepping. Choose an explicit basis and -unit mapping. For a Unity mapping that reflects Z, convert positions with -`(x,y,-z)` and quaternion components with `(-x,-y,z,w)` in both directions. The -sample only uses vertical translation and does not impose an engine-wide mapping. -When rendering soft bodies, copy collision-mesh vertices and indices, and rebuild -topology when its version changes. Process tear piece/particle remaps to preserve -render attributes attached to particles. - -Keep delegates rooted for the whole step when using hooks. Parallel builds can -invoke hooks on worker threads: do not access Unity objects from those callbacks. -Event polling after the step is usually simpler. A full generated C# wrapper can -be generated from `rapier.h`; this example intentionally declares only its used -entry points. - -## Unreal / C++ - -Add the installed include directory, link the selected static or import library -from the module's Build.cs, and stage the shared runtime library through Unreal's -normal runtime dependency mechanism if dynamically linked. Define -`RAPIER_DIM3`, `RAPIER_F32` or `RAPIER_F64`, and `RAPIER_STATIC` for static linkage. -The C API does not require C++ exceptions; Unreal projects with exceptions disabled -can check statuses directly instead of calling the throwing `rapier::check` helper. - -Use a subsystem/scene object to own the world. Store `R3RigidBodyHandle` and -`R3ColliderHandle` in components, and translate Rapier user data into engine IDs. -Do not store an engine UObject pointer in a physics object without an engine-side -lifetime strategy. Event collection allows game-thread dispatch without invoking -Unreal object APIs from solver callbacks. - -Choose a unit and basis conversion consistently for positions, normals, forces, -velocities, rotations and inertia. Rapier defaults to meters and gravity along -Y; -Unreal commonly uses centimeters and Z-up. You can keep physics in meters and -convert at the boundary, or configure `length_unit` and gravity for your selected -units. A reflection changes angular-vector handedness as well as positions; use -a basis transform for rotations rather than merely swapping quaternion fields. - -## Build/ABI checklist for wrapper authors - -1. Select one dimension and scalar precision. Check `r3CheckAbi` before passing - POD math structures; reject an incompatible runtime library. -2. Generate bindings from the preprocessed header with those definitions. Optional - FEM and parallel declarations also require `RAPIER_FEM` / `RAPIER_PARALLEL`. -3. Preserve C layout, pointer width, constness, and the documented pointer lifetimes. - Do not use a language's default bool marshaling for `R3Bool`. -4. Copy the thread-local error text before another API call. Treat query misses and - buffer sizing distinctly from fatal errors. -5. Make owned/borrowed pointer distinctions visible in the wrapper. Prefer handles - and reacquisition for long-lived engine components. -6. Convert coordinate systems and units in one shared layer. Cover rotations, - angular velocities, inertia and soft meshes as well as translations. - -The repository tests exercise the C ABI and C++ ownership helpers. Engine editor, -IL2CPP/AOT, consoles, mobile signing, and Unreal build integration need validation -in the target projects. diff --git a/c/docs/validation.md b/c/docs/validation.md deleted file mode 100644 index a985e9c74..000000000 --- a/c/docs/validation.md +++ /dev/null @@ -1,471 +0,0 @@ -# Validation record - -Validated locally on 2026-09-18 on macOS arm64, with Rust 1.98.0, -Apple Clang 17, CMake 3.31.3, and cbindgen 0.29.4. - -## Mainline rebase (2026-09-24) - -- Rebased the 23 C-binding commits onto `master` at `79fd711a9`, excluding the - soft-body commits already merged upstream. The normal `c-bindings` branch is - checked out in the main repository. -- All 130 default-feature Rust binding tests and 36 full-feature 3D tests pass. - All four native ABI variants pass C/C++ shared/static linkage, symbol checks, - version checks, and dimension coexistence. Header regeneration is unchanged. -- Both graphical testbeds build with FEM and parallelism (3D also with robotics), - and all 28 CTests pass. One-step, no-sleep, single-worker smoke runs pass 88 2D - and 113 3D scenes. The remaining `debug_deserialize3` scene needs its external - `snapshot0.bincode` fixture. -- Audited all 214 upstream vendored files against pinned upstream Git blobs and - reviewed nested licenses. Supplemental license texts and collected notices - are included, and both viewers copy these plus LGPL fallback-header sources - into their redistribution directories. See `testbed/vendor/README.md`. - -## C release version reporting (2026-09-24) - -- `c/VERSION` defines `0.35.3+c.2` independently of ABI version 1. The shared - Cargo build script validates the Rust-version prefix and embeds the exact - release string. `r2Version()` / `r3Version()` return borrowed static C strings. -- CMake reads the same file, compares the numeric base for package discovery, - and exports the full release string as `Rapier_BINDINGS_VERSION`. -- All four native ABI variants pass C/C++ shared/static linking and exact - version-string assertions, with 538 exports in 2D and 563 in 3D. Dimension - coexistence, strict full-feature FFI Clippy, and formatting pass. -- A fresh Release 3D CMake build passes all six integration tests. A separate - C consumer finds the installed package and verifies its version metadata - matches the runtime string. Header regeneration and `git diff --check` pass. - -## Full-width snapshot headers (2026-09-24) - -- Snapshot headers now store ABI, dimension, and scalar byte size as little-endian - `u32` values after the `RPRS` magic. No snapshot header field is narrowed to a - byte. The payload begins after the shared 16-byte header length. -- Regression tests cover ABI values through `u32::MAX`, including the former - 1/257 collision, every mutated header byte, truncated headers/payloads, round - trips, and rejection of the obsolete six-byte format. Public ABI remains 1. -- All 130 default-feature Rust tests and 36 full-feature 3D tests pass. Strict - full-feature FFI Clippy, formatting, and all four native C/C++ ABI variants - pass, including shared/static linkage and dimension coexistence. Header - regeneration is unchanged and `git diff --check` passes. - -## Initial ABI version (2026-09-20) - -The unreleased bindings now use ABI version 1. Earlier ABI numbers below record -development checkpoints, not published compatibility guarantees. Future ABI -version increments apply to incompatible releases. - -All four native variants pass C/C++ shared/static linking, export checks, and -dimension coexistence with ABI 1. The 2D/3D legacy snapshot tests and C# example -pass. Header regeneration is reproducible, and `git diff --check` passes. - -## ABI 15: explicit free-function names (2026-09-20) - -- Renamed `RemoveBody`, `InsertDeformable`, and `ActiveBodies` to - `RemoveRigidBody`, `InsertDeformableCollider`, and `ActiveRigidBodies`. - Integration-parameter accessors and world serialization now name their subject. - `TimeStep` / `SetTimeStep` replace `Dt` / `SetDt`. -- All five collection `Len` functions now use `Count`, including callback-scoped - rigid-body and collider queries. All 14 renames apply to both dimensions; - replaced exports are absent and the ABI checks explicitly reject them. -- Migrated examples, tests, helpers, tools, and current documentation. Internal - Rust binding entry points follow the same names; native Rapier methods retain - their Rust naming conventions. -- All 122 default-feature Rust binding tests, three naming tests, and 34 - full-feature 3D tests pass. Formatting and strict full-feature FFI Clippy pass. -- Four native ABI variants pass C11/C++17 shared/static linking, exact export - checks (537 in 2D, 562 in 3D), and dimension coexistence. -- Both Release viewers rebuild with all demos; all 28 CTests pass. The C# - example passes with ABI 15. Header regeneration is byte-for-byte reproducible, - and `git diff --check` passes. - -## ABI 14: collider insertion from parent handles (2026-09-20) - -- `InsertCollider(parent, &desc)` takes the parent handle by value and uses its - world pointer. `InsertColliderWithoutParent(world, &desc)` creates a collider - without a rigid-body parent. The nullable parent-pointer signature is removed. -- All demos, native tests, and documentation use the new signatures. Regression - tests cover parent ownership, independent worlds, standalone colliders, null - worlds, and invalid or removed parents without unintended insertion. -- All 122 default-feature Rust binding tests and three naming tests pass. All 34 - full-feature 3D tests, formatting, and strict full-feature FFI Clippy pass. -- Four native ABI variants pass C11/C++17 shared/static linking, exact export - checks (537 in 2D, 562 in 3D), and dimension coexistence. -- Both Release viewers build and all 28 CTests pass. One-step, no-sleep, - single-worker smoke runs pass all 88 2D demos and 113 3D demos; the remaining - `debug_deserialize3` demo requires the external `snapshot0.bincode` fixture. -- The C# example passes with ABI 14. Header regeneration is byte-for-byte - reproducible, and `git diff --check` passes. - -## ABI 13: separate rigid-body and collider insertion (2026-09-20) - -- Removed `r2Insert` / `r3Insert` and the `BodyCollider` result type. The body-only - function is now `InsertRigidBody`. Examples insert the body, then call - `InsertCollider` with its handle. No combined helper or compatibility alias remains. -- All demos, native tests, C++ helpers, the C# example, and documentation use the - new sequence. Errors are checked between dependent calls in standalone examples - and native tests. Collider insertion failure leaves an already-created body intact; - an updated regression test verifies this behavior. -- All 118 default-feature Rust binding tests and three naming tests pass; all 33 - full-feature 3D tests and strict Clippy pass. Four native ABI variants pass - C11/C++17 shared/static linkage, exact symbol checks (536 in 2D, 561 in 3D), - and dimension coexistence. The ABI audit rejects the removed function/type. -- Both Release viewers build, and all 28 CTests pass. One-step, no-sleep, - single-worker smoke runs pass all 88 2D demos and 113 3D demos. The remaining - `debug_deserialize3` demo requires the existing external `snapshot0.bincode` fixture. -- C# P/Invoke passes with ABI 13. Header regeneration reproduces byte-for-byte, - and `git diff --check` passes. - -## ABI 12: configuration values and resource ownership (2026-09-20) - -- URDF/MJCF options are copyable PODs with embedded body/collider descriptions. - Default factories replace allocation, setters, and destructors. Options validate - before file I/O and borrow blueprint geometry only through loading. `PodLayout` - exposes their sizes (zero when robotics is unavailable). -- Removed redundant `Data` suffixes from nine configuration types and normalized - material/description getter-setter pairs. `UserData` and the owned `TriMeshData` - mesh buffer retain meaningful names. Public compatibility aliases are absent. -- Collider, callback collider, and MJCF visual shape getters now say `CloneShape` - and document the owned wrapper. C++ owners cover all 14 remaining resource types. - Demos, helpers, tests, documentation, and C# layout declarations are migrated. -- All 118 default-feature Rust binding tests and three naming tests pass. All 33 - full-feature 3D tests pass, including POD default preservation, independent copies, - validation before I/O, borrowed geometry retention, URDF blueprint loading, and - MJCF keyframes/actuators. Formatting and strict full-feature FFI Clippy pass. -- Four native ABIs pass C11/C++17 shared/static linking and dimension coexistence. - Default exports remain 537 in 2D and 562 in 3D; the full-feature 3D header exactly - matches all 601 exports. All 14 destructors have matching C++ owner aliases. -- Both Release viewers build with all demos; all 28 CTests pass. The final C++ - tests verify shape-clone lifetime after collider removal in both dimensions. - URDF and MJCF demos pass one-step, no-sleep smoke tests with one worker. -- The .NET 8 example passes with ABI 12. Header regeneration is byte-for-byte - reproducible, and `git diff --check` passes. - -## ABI 11: constructor and destructor names (2026-09-20) - -- Renamed 94 exported constructors/destructors, with no compatibility aliases: - `NewWorld`, `FreeWorld`, `DynamicRigidBodyDesc`, `CuboidColliderDesc`, and - `DefaultQueryOptions` illustrate the convention. All 16 destructors use `FreeType`. - The inline pure-translation constructor is now `TranslationPose`. -- Instance methods retain `Type_Method`; loading constructors retain `TypeFromFile`. - All demos, C/C++ helpers, tests, and the C# sample use the new names. The Rust - implementation uses the same word order without additional export-macro logic. -- All 118 Rust binding tests across 2D/3D and f32/f64 pass, along with three naming - tests. Formatting and strict FFI/macro Clippy checks pass. -- All four native variants pass C11/C++17 shared/static linking, exact export - checks (537 in 2D, 562 in 3D), and 2D+3D coexistence. Header regeneration is - byte-for-byte reproducible; no old lifecycle references remain in current source. -- Both Release viewers build with all demos and pass all 28 CTests. The .NET 8 - sample passes with ABI 11, reporting y=0.074563645 after 60 steps. - -## ABI 10: instance-method names (2026-09-20) - -- 414 instance methods use a separator between their receiver and method, for example - `r3RigidBody_SetTranslation` and `r3JointDesc_SetMotorPosition`. Constructors, - static helpers, and short world functions retain their existing names. -- The Rust export attribute records the receiver explicitly. The header generator - reads the same annotation; obsolete method exports and aliases are absent. - All C demos, shared testbed code, C++ helpers, and the C# example are migrated. -- Three macro tests cover method names, unchanged constructors/helpers, and invalid - annotations. All four dimension/precision variants pass C11/C++17 shared/static - linking, exact export checks (537 in 2D, 562 in 3D), and 2D+3D coexistence. -- Both Release testbeds build with all demos; all 28 CTests pass. The .NET 8 - example passes with ABI 10, reporting y=0.074563645 after 60 steps. -- Header regeneration is byte-for-byte reproducible. No obsolete method names - remain in tracked source consumers. `git diff --check` passes. - -## ABI 8: direct value returns (2026-09-19) - -- 313 value-producing operations now return their results directly. Related outputs - use POD aggregates; array fills retain caller buffers and return the element count. - Mutators without a produced value still return status codes. Old signatures are removed. -- `LastStatus` records the most recent fallible call on each thread. Error callbacks - cover both calling conventions, and nested callbacks preserve the original status - and diagnostic. Failures return type defaults; short-buffer errors retain the required - count. Infallible constructors and status/diagnostic reads preserve the recorded error. -- All 98 Rust binding tests pass across 2D/3D and f32/f64, both with default features - and with FEM + parallel. Strict FFI Clippy passes in both configurations. Tests cover - panic fallback, stale handles, thread-local status, callback reentrancy, and buffers. -- All four native variants pass C11/C++17 shared/static linking, complete symbol checks, - and 2D+3D coexistence. Default exports: 537 in 2D and 562 in 3D. The generated header - reproduces byte-for-byte; the ABI audit rejects scalar output-pointer declarations. -- The .NET 8 consumer passes with ABI 8, reporting y=0.074563645 after 60 steps. - Both Release viewers and step benchmarks build; all 28 testbed tests pass. -- Independent three-step, no-sleep, one-worker runs pass all 88 2D demos and 113/114 - 3D demos. `debug_deserialize3` still requires the external `snapshot0.bincode` fixture. - -## Dimension-specific C names (2026-09-19) - -- Public types and constants use `R2`/`R2_` or `R3`/`R3_`, with no old-name - aliases. All demos and consumers use the new names. Shared C/C++ support uses - `RAPIER_FN`, `RAPIER_TYPE`, and `RAPIER_CONST` selectors. -- The generator translates the shared Rust names without duplicating the Rust - implementation. This source rename preserves layouts, symbols, and ABI 7. -- All four dimension/precision variants pass C11/C++17 shared/static linking, - export checks, and 2D+3D coexistence. The audit rejects obsolete or wrong-dimension - type and constant names. Header regeneration is reproducible. -- Both Release testbeds build with all demos and pass all 28 CTests. - -## ABI 7: world ownership and scoped callback access (2026-09-19) - -- The public C API exposes one simulation owner, `RprWorld`. Sets, pipelines, - component getters, duplicate world accessors, and borrowed query views are - removed. New entry points include `WorldNew`, `Step`, `InsertBody`, - `RigidBodySetTranslation`, and `CastRay`; query options are owner-independent PODs. -- All C demos, the viewer, benchmarks, tests, C++ helpers, and the C# example use - the new interface. Export checks explicitly reject obsolete public types and - symbol prefixes; the generated header reproduces byte-for-byte. -- The world uses a nonblocking shared/exclusive access gate. Conflicting calls - return `RPR_WORLD_BUSY` before native borrows are created. Tests cover hook-scoped - reads, contact edits, rejected recursive stepping/mutation/destruction, nested - read-only queries, cross-thread conflicts, and guard release after errors/panics. -- Rust `PhysicsWorld` now owns collision-only workspace and provides - `detect_collisions` and `remove_body_with_colliders`. The C API uses these methods; - collider-preserving removal and collision refresh without time advancement are tested. -- All 90 Rust binding tests pass across 2D/3D and f32/f64 with default features and - with FEM + parallel. Strict FFI Clippy passes for both configurations. -- C11/C++17 shared/static behavior, POD layouts, all exports, and 2D+3D coexistence - pass for all four native variants. Default export counts are 536 in 2D and 561 in 3D. - C callbacks exercise scoped reads. The .NET 8 C# consumer passes with ABI 7, - reporting y=0.074563645 after 60 steps. -- Both Release viewers and the standalone step benchmarks build. Both viewers pass - all 14 CTests. Three-step no-sleep, one-worker smoke runs pass 88/88 2D and 113/114 - 3D demos. The remaining `debug_deserialize3` demo needs the external - `snapshot0.bincode` fixture; it is not counted as a pass. - -## ABI 6: description constructors return values (2026-09-19) - -- Collider, joint, and soft-body description constructors return POD values - directly. Uniform material and volume-meshing parameter constructors do too. - The 2D revolute constructor takes no axis, matching native Rust. -- All demos, standalone C/C++ examples, tests, and documentation use the new - signatures. Output-pointer constructor signatures are removed. Invalid input - is preserved in descriptions and rejected during build/insert or preview; - construction does not allocate geometry or invoke the error callback. -- All 74 Rust tests pass across 2D/3D and f32/f64. Added coverage compares native - constructor values and checks deferred validation of invalid shapes and axes. - Strict FFI Clippy passes in all four default variants. -- All four variants pass C11/C++17 shared/static tests, symbol checks, layout - checks, and dimensional coexistence. The C# example runs with the ABI 6 check. -- Both Release viewers build and pass all 14 CTests. Independent three-step, - no-sleep, one-worker runs pass all 88 2D demos and 113/114 3D demos; the remaining - snapshot demo requires the external `snapshot0.bincode` fixture. - -## ABI 5: complete example migration and API cleanup (2026-09-19) - -- Every C testbed example now uses POD construction, set-and-handle element - access, native value initializers, and typed geometry inputs where applicable. - The public header contains no owned builders, element-pointer accessors, or - allocated query wrappers. Their replacement is mandatory, with no aliases. -- Shape, soft-body, binding, compound, and heightfield descriptions use typed - views. Shared-shape geometry constructors use the same view types. Insertion - copies borrowed inputs; examples retain temporary arrays and shared shapes - through the last insertion that reads them. -- All four default variants pass C11/C++17 shared/static linking, ABI and POD - layout checks, export checks, and 2D+3D coexistence. Default exported function - counts are 560 in 2D and 584 in 3D. -- All 70 Rust tests pass with default features and again with FEM + parallel. - Tests compare procedural recipes against native Rust defaults, check stale - handles and failure atomicity, and cover infinite one-sided joint limits. - Strict FFI Clippy passes for all variants with both feature configurations. -- Both Release viewers build and pass all 14 CTests, including dragging, - deformable sensor rendering, UI, and threading. Builds use SIMD4 + parallel + - FEM; 3D also enables robotics. -- Independent catalog runs pass 88/88 2D and 113/114 3D demos for three steps, - with sleeping disabled and one physics worker. `debug_deserialize3` reports - the missing external `snapshot0.bincode` fixture. These are smoke tests for - stepping and finite state, not proof of numerical identity to every Rust demo. -- The .NET 8 C# example passes with ABI 5, reporting y=0.074563645 after 60 steps. - Generated header reproduction and whitespace checks pass. - -The entries below describe historical validation before ABI 5; their old API -names and export counts do not describe the current interface. - -## Handle-based access (2026-09-19) - -- All four native variants pass C/C++ tests, shared/static linkage, export checks, - and dimension coexistence (758 exports in 2D, 781 in 3D). -- Handle tests cover storage growth, stepping, slot reuse, collider removal, - soft-proxy restrictions, error callback delivery, invalid input preservation, - batch capacity checks and all-or-nothing output on stale handles. -- Strict FFI Clippy passes in all variants. The C# example uses no borrowed body - pointer and runs successfully; generated header reproduction passes. - -## ABI 4: explicit setter names (2026-09-19) - -- Property setters consistently include `Set`, including all owned builders and - bulk POD configuration. Incremental translation and clearing use action verbs. -- Updated exports, header, examples, testbed, tests, C# ABI check, and documentation. - Source audit found no references to replaced names; no legacy aliases remain. -- All four variants pass shared/static C/C++ ABI tests and dimension coexistence; - 62 Rust unit tests and strict FFI Clippy checks pass. Header generation is stable. -- Both release viewers build and pass all 11 CTests; the C# example runs against - ABI 4. This is a development ABI change requiring consumers to rebuild. - -## POD construction and configuration (2026-09-19) - -- All four default ABIs pass C11/C++17 behavior tests, POD layout checks, every - declared symbol, shared/static linking, and 2D+3D coexistence. Default exports - are now **670 in 2D** and **693 in 3D**. -- 62 Rust tests pass across the four variants, both with default features and - with FEM + parallel enabled. New tests compare construction - defaults and shape/soft recipes against native Rust values, verify copied array - lifetimes, reject recursive compound descriptions, and check failure atomicity. -- The C POD suite exercises direct set/world insertion, joint descriptions, - integration read/apply, material read/apply, borrowed query predicates, shared - shape retention, and deformable binding/vertex input lifetimes. -- Both Release f32 viewers pass all **11 CTests**, including mouse dragging, - soft sensor rendering and UI behavior. Builds include parallel + SIMD4 + FEM; - the 3D build also includes robotics. -- Full catalog smoke run: **88/88 2D** and **113/114 3D** demos pass three steps - with sleeping disabled and one worker. The remaining `debug_deserialize3` - requires the external `snapshot0.bincode` input; its failure reports the missing - file. This smoke test checks successful stepping and finite state, not numerical - identity of every demo with Rust. -- The updated C# example compiles/runs under .NET 8 using a blittable body - description, verifies its native size, and produces y=0.074563645 after 60 steps. -- Strict default and FEM/parallel FFI Clippy checks pass for all four variants. - Header regeneration is reproducible. No engine Rust source or existing - object layout was changed; owned builder APIs remain available. ABI 4 subsequently - renames setters to include `Set`. - -## Deformable sensor transparency - -- Deformable triangles and polyline ribbons preserve alpha and share the sorted - transparency pass with rigid sensors, with depth writes disabled until flush. -- Both Release viewers pass all ten CTests. The renderer regression now checks - the real Cluster meshes sensor and synthetic 2D deformable sensors: alpha 102, - no transparent draws in the opaque pass, depth ordering, and depth-write restore. -- Inspected a 30-frame Cluster meshes capture: the deformable sensor shell reveals - its enclosed solid mesh. - -## Testbed controls and rendering - -- Both Release f32 viewers pass all ten CTests. Mouse-grab coverage includes dynamic - rigid bodies, articulated links, soft particles, sensor/fixed-body exclusion, - release cleanup, and deletion during a drag. The UI test also checks filtered - previous/next navigation and orbit-camera distance, target, panning, and zoom. -- Inspected rendered 2D deformable polylines and the 3D sensor demo with its solid - collider visible through the transparent sensor surface. -- All four default variants pass shared/static C/C++ linkage, export comparison, - and dimension coexistence. Default exports are 617 in 2D and 640 in 3D after - adding soft-body ownership, cluster-proxy, and closed-mesh accessors. -- Clippy with `--no-deps -- -D warnings` passes for all four default FFI variants. - -## Soft-mesh renderer follow-up - -- Fixed renderer cache indexing for meshes without a collider. Mesh enumeration - now exposes native mesh IDs so render-only skins remain independently accessible. -- Both Release viewers pass all nine CTests. The added renderer regression runs - actual soft scenes without a GPU, bounds cache allocation, checks finite geometry, - and verifies that queued triangles flush before back-face culling is restored. -- The Cluster meshes and Soft trimeshes windows ran for 30 and 90 frames, - respectively; the latter screenshot was inspected after the two-sided fix. -- Both 3D precision variants pass all 12 Rust boundary/regression tests, including - direct comparison of render-only mesh vertices and indices with the native API. -- All four default variants pass shared/static C/C++ linkage, export comparison, - and dimension coexistence. Default exports are now 614 in 2D and 637 in 3D. -- Formatting and Clippy with `--no-deps -- -D warnings` pass for the updated bindings. - -## ABI 3: complete example catalog - -- All 202 catalog entries now have C ports: 88 2D and 114 3D. Added 62 entries. -- Release f32, SIMD4, one worker, sleeping disabled: all 201 scenes with available - inputs passed three steps. This includes 2D/3D FEM, URDF, Cassie MJCF, and a - locally installed Menagerie model. `debug_deserialize3` needs external snapshot - files; its legacy rigid-state reader passed a generated-state round-trip and - subsequent-step comparison in all four native variants. -- Longer runs exercised tearing (900 steps), self-intersection (260 steps), - one-way platforms, soft joints, vehicle controllers/joints, shape replacement, - articulated joints, ray casting, and OBJ-based scenes. These check execution - and finite state; they are not a blanket proof of C/Rust numerical identity. -- All four FFI variants passed 11 Rust boundary/regression tests each. The 3D/f32 - `fem,robotics` configuration passed 12, including imported keyframes and - actuator controls. -- Both Release viewers built with their optional features. All eight CTests - passed per dimension, including native C/C++, ImGui input, worker/snapshot - handling, and the new tests driving real IK, kinematic/PID, and voxel-edit loops. -- All four default ABIs passed shared/static C and C++ linkage, all-symbol - linkage, export comparison, and 2D/3D coexistence. Default exports: 611 in 2D, - 634 in 3D. Optional-feature exports also matched their headers: 619 for 2D/FEM, - 699 for 3D/FEM/robotics/parallel. -- All 2D and 3D scene sources passed strict f64 C11 compilation with FEM enabled. - Robotics stays 3D/f32, matching the native importer crates. -- Clippy passed with `--no-deps -- -D warnings` for all four default variants - and for 3D/f32 with FEM/robotics. Existing engine dependency warnings remain. -- The raylib Menagerie viewer was run and its screenshot inspected for model - framing, smooth normals, materials, and texture rendering. - -The catalog counts implemented scenes, not external asset availability. FEM and -robotics remain opt-in build features. No new platform/engine certification is -implied by these local macOS checks. - -## ABI 2: dimension prefixes and camelCase - -- All four FFI crates: 28 boundary/validation tests passed. The export macro's - 2 naming tests also passed. -- `cargo clippy` for the export macro and all four FFI crates with - `--no-deps -- -D warnings`: passed. Existing engine dependency warnings remain. -- `cargo fmt -p rapier-c-macros -p rapier3d-ffi -- --check`: passed. -- Regenerating `rapier.h` produces an identical header. -- `python3 c/tools/test-native.py`: every variant passed the C behavioral suite, - C++ ownership suite, and declared-symbol linking with shared and static - libraries. The default headers declare **564 functions for 2D** and - **582 functions for 3D**; optional features add more. -- Dynamic exports match the preprocessed declarations exactly. No old `rpr_*` - exports or opposite-dimension exports remain. Both 2D and 3D link and run in - one executable, tested with shared/static linkage and f32/f64 separately. -- Inline math headers: all 8 combinations of C11/C++17, 2D/3D, and f32/f64 passed - strict compilation and execution (`-pedantic -Wall -Wextra -Werror`). -- `cargo check` with `parallel,fem,enhanced-determinism` passed for all four FFI - crates. This checks feature compatibility, not solver numerical behavior. -- Release 3D/f32 parallel + SIMD4: all 7 CTests passed, including ImGui input, - example-owned loops, pause/step, worker changes, snapshots, and error reporting. -- Release 2D/f32 parallel + SIMD4, headless: all 6 CTests passed. -- The installed shared CMake package was consumed by a separate C++ application. -- `examples/RapierNative.cs` compiled under .NET 8 and ran through the renamed - P/Invoke entry points against the rebuilt 3D/f32 library; y=0.074563645 after - 60 default steps. A previous native library beside the application had to be - replaced with the ABI 2 build, as required for renamed entry points. - -The C behavioral tests also cover explicit pipeline construction, ownership, -collision/force events, hooks, contact inspection, ray/shape queries, filtering, -heightfields, mass properties, buffer sizing, invalid inputs, snapshots, joints, -soft-body particles and meshes, cutting/tear events, character movement, -3D vehicles, debug lines, removal cascades, and stale handles. - -## Earlier execution and packaging checks - -The following checks predate the ABI 2 naming change: - -- Installed shared/static CMake packages, including relocation of the shared - package before consumption, passed on macOS with `@rpath` loading. -- Debug 2D/f32 serial + SIMD4: all 6 CTests passed. -- Debug 3D/f32 parallel + SIMD8, headless: all 5 CTests passed. -- CMake rejected unsupported SIMD widths, SIMD8/f64, and SIMD8 with enhanced - determinism before invoking Cargo. -- All 140 existing demo ports preserved their initial/final physics state when - moved to direct API calls and example-owned loops. Animated scenes ran for - 260 steps; the other scenes ran for 3 steps. -- The Keva viewer's per-step timing uses the same native counter as the Rust UI. - See the [C/Rust comparison](../testbed/tools/timing-results.md) for measurements, - raw runs, and reproduction instructions. - -The workflow covers Linux, macOS, and Windows builds plus installed-package -consumers. Remote CI has not been run for this change. Unity editor/IL2CPP, -Unreal, mobile targets, and consoles need tests in their target projects. - -## Value initialization helpers (2026-09-19) - -The C11 and C++17 initialization tests pass with shared and static libraries for all four dimension/precision variants. Release CMake initializer tests also pass in both dimensions with FEM enabled. These exercise descriptor insertion, configuration application, invalid handles, and query defaults. - -## Typed array views (2026-09-19) - -- C11 and C++17 tests pass for all four dimension/precision variants with shared - and static linkage, export checks (771 in 2D, 796 in 3D), and dimension coexistence. -- View tests exercise edge/triangle/cell counts, soft surface/skin insertion, - copied input lifetimes, null/alignment/length errors, unchanged descriptions on - failure, and topology validation at insertion. -- All 62 Rust binding tests and strict FFI Clippy pass. Header regeneration is - reproducible; the existing ABI and descriptor layouts are unchanged. -- Both release viewers rebuild with FEM, parallelism and SIMD4 and pass all - 14 CTests per dimension. The updated polyline2 and debug_trimesh3 examples each - pass 120 steps with sleeping disabled and one worker. diff --git a/c/testbed/CMakeLists.txt b/c/testbed/CMakeLists.txt index a9c84a9df..ed44ef4ca 100644 --- a/c/testbed/CMakeLists.txt +++ b/c/testbed/CMakeLists.txt @@ -16,6 +16,10 @@ rapier_executable(rapier_testbed_step_benchmark tools/step_benchmark.c) set_target_properties(rapier_testbed_step_benchmark PROPERTIES EXCLUDE_FROM_ALL TRUE) target_link_libraries(rapier_testbed_step_benchmark PRIVATE rapier_testbed_core) if(RAPIER_TESTBED_GRAPHICS) + if(DEFINED PLATFORM AND NOT PLATFORM STREQUAL "Desktop") + message(FATAL_ERROR "The testbed renderer supports PLATFORM=Desktop only (macOS, Windows, Linux/X11). Use RAPIER_TESTBED_GRAPHICS=OFF for other targets.") + endif() + include(cmake/Dependencies.cmake) # Normal variables stay in this directory: don't alter the parent project's options. set(BUILD_EXAMPLES OFF) set(BUILD_SHARED_LIBS OFF) @@ -29,25 +33,33 @@ if(RAPIER_TESTBED_GRAPHICS) set(GLFW_BUILD_WAYLAND OFF CACHE BOOL "" FORCE) set(GLFW_BUILD_X11 ON CACHE BOOL "" FORCE) set(OPENGL_VERSION "3.3" CACHE STRING "" FORCE) - add_subdirectory(vendor/raylib EXCLUDE_FROM_ALL) + add_subdirectory("${raylib_SOURCE_DIR}" "${raylib_BINARY_DIR}" EXCLUDE_FROM_ALL) target_compile_definitions(raylib PRIVATE - SUPPORT_MODULE_RAUDIO=0 SUPPORT_CUSTOM_FRAME_CONTROL=0 SUPPORT_BUSY_WAIT_LOOP=0) + SUPPORT_MODULE_RAUDIO=0 SUPPORT_CUSTOM_FRAME_CONTROL=0 SUPPORT_BUSY_WAIT_LOOP=0 + # Rapier supplies meshes; Dear ImGui supplies font loading. Keep raylib's + # default font/white texture, image decoders, screenshots, and mesh drawing. + SUPPORT_FILEFORMAT_OBJ=0 SUPPORT_FILEFORMAT_MTL=0 SUPPORT_FILEFORMAT_IQM=0 + SUPPORT_FILEFORMAT_GLTF=0 SUPPORT_FILEFORMAT_GLTF_WRITE=0 + SUPPORT_FILEFORMAT_VOX=0 SUPPORT_FILEFORMAT_M3D=0 SUPPORT_MESH_GENERATION=0 + SUPPORT_FILEFORMAT_TTF=0 SUPPORT_FILEFORMAT_FNT=0 + SUPPORT_COMPRESSION_API=0 SUPPORT_GESTURES_SYSTEM=0 SUPPORT_MOUSE_GESTURES=0) # Version-matched C wrappers and renderer; the testbed itself stays C11. - set(TB_IMGUI_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/cimgui/imgui") + set(TB_IMGUI_DIR "${imgui_SOURCE_DIR}") add_library(rapier_testbed_imgui STATIC - vendor/cimgui/cimgui.cpp vendor/rlImGui/rlImGui.cpp + "${cimgui_SOURCE_DIR}/cimgui.cpp" "${rlimgui_SOURCE_DIR}/rlImGui.cpp" "${TB_IMGUI_DIR}/imgui.cpp" "${TB_IMGUI_DIR}/imgui_draw.cpp" "${TB_IMGUI_DIR}/imgui_widgets.cpp" "${TB_IMGUI_DIR}/imgui_tables.cpp" "${TB_IMGUI_DIR}/imgui_demo.cpp") target_compile_features(rapier_testbed_imgui PRIVATE cxx_std_17) target_compile_definitions(rapier_testbed_imgui PUBLIC NO_FONT_AWESOME CIMGUI_NO_EXPORT) target_include_directories(rapier_testbed_imgui SYSTEM PUBLIC - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/cimgui" - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/rlImGui" "${TB_IMGUI_DIR}") + "${cimgui_SOURCE_DIR}" + "${rlimgui_SOURCE_DIR}" "${TB_IMGUI_DIR}") + target_include_directories(rapier_testbed_imgui SYSTEM PRIVATE "${TB_CIMGUI_INCLUDE_DIR}") target_link_libraries(rapier_testbed_imgui PUBLIC raylib) # Embed the font with CMake alone: no runtime asset path, font installation, - # generator dependency, or network access is required by consumers. - set(TB_FONT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vendor/fira/FiraSans-Regular.ttf") + # generator dependency, or runtime network access is required. + set(TB_FONT_FILE "${fira_font_SOURCE_DIR}/FiraSans-Regular.ttf") set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${TB_FONT_FILE}") file(READ "${TB_FONT_FILE}" TB_FONT_HEX HEX) string(REGEX REPLACE "(..)" "0x\\1," TB_FONT_BYTES "${TB_FONT_HEX}") @@ -60,37 +72,39 @@ if(RAPIER_TESTBED_GRAPHICS) unset(TB_FONT_ROW) rapier_executable(rapier_testbed gui.c) target_include_directories(rapier_testbed PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + set_property(TARGET rapier_testbed APPEND PROPERTY LINK_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/THIRD_PARTY_NOTICES.txt") add_custom_command(TARGET rapier_testbed POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/NOTICES.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/THIRD_PARTY_NOTICES.txt" "$/THIRD_PARTY_NOTICES.txt" COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/licenses" + "${CMAKE_CURRENT_SOURCE_DIR}/licenses" "$/licenses" COMMAND "${CMAKE_COMMAND}" -E make_directory "$/licenses/glfw-mingw" COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/raylib/src/external/glfw/deps/mingw/dinput.h" - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/raylib/src/external/glfw/deps/mingw/xinput.h" - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/raylib/src/external/glfw/deps/mingw/_mingw_dxhelper.h" + "${raylib_SOURCE_DIR}/src/external/glfw/deps/mingw/dinput.h" + "${raylib_SOURCE_DIR}/src/external/glfw/deps/mingw/xinput.h" + "${raylib_SOURCE_DIR}/src/external/glfw/deps/mingw/_mingw_dxhelper.h" "$/licenses/glfw-mingw" COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/raylib/LICENSE" + "${raylib_SOURCE_DIR}/LICENSE" "$/raylib-LICENSE.txt" COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/raylib/src/external/glfw/LICENSE.md" + "${raylib_SOURCE_DIR}/src/external/glfw/LICENSE.md" "$/GLFW-LICENSE.txt" COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/fira/LICENSE" + "${CMAKE_CURRENT_SOURCE_DIR}/licenses/FiraSans-LICENSE.txt" "$/FiraSans-LICENSE.txt" COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/cimgui/LICENSE" + "${cimgui_SOURCE_DIR}/LICENSE" "$/cimgui-LICENSE.txt" COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/cimgui/imgui/LICENSE.txt" + "${imgui_SOURCE_DIR}/LICENSE.txt" "$/DearImGui-LICENSE.txt" COMMAND "${CMAKE_COMMAND}" -E copy_if_different - "${CMAKE_CURRENT_SOURCE_DIR}/vendor/rlImGui/LICENSE" + "${rlimgui_SOURCE_DIR}/LICENSE" "$/rlImGui-LICENSE.txt" VERBATIM) target_sources(rapier_testbed PRIVATE graphics.c) diff --git a/c/testbed/PORTING.md b/c/testbed/PORTING.md deleted file mode 100644 index 4a3649c7b..000000000 --- a/c/testbed/PORTING.md +++ /dev/null @@ -1,72 +0,0 @@ -# Writing C examples - -Treat the matching Rust file as the structure of the C example. Preserve its -construction order, variable meanings, parameters, comments, and distinct examples. -Rapier instance methods separate the receiver and method with an underscore, -for example `r3RigidBody_SetTranslation`. Constructors such as -`r3DynamicRigidBodyDesc` put the qualifier first. Creation and destruction use -`r3NewWorld` and `r3FreeWorld`; simple math values use `r3Vector` and `r3Pose`. - -Use camelCase for C functions, fields, parameters, and local variables, including -the testbed helpers. Keep types in PascalCase and macros in UPPER_SNAKE_CASE. -Keep the corresponding file and scene names. Do not combine separate Rust blocks -into loops or conditional expressions merely to shorten the C code. - -Use the public Rapier C API for world creation, descriptions, insertion, queries, -joints, soft bodies, and simulation callbacks. If a Rust operation is missing -from the bindings, add its native counterpart to the bindings instead of hiding -it in a testbed helper. Translate Rust's `PhysicsWorld::insert(body, collider)` -into `r3InsertRigidBody(world, &body)`, then -`r3InsertCollider(bodyHandle, &collider)`. Keep these calls directly in -the example. The `r3*ColliderDesc` functions correspond to `ColliderBuilder` -constructors. Description constructors return POD values directly. - -The testbed provides one-frame rendering, camera, colors, input, UI settings, -and run/pause controls. Each example owns its world and its simulation loop: - -```c -tbSetWorld(testbed, world); -while (tbRenderFrame(testbed, &world)) { - if (tbSimulating(testbed)) { - r3Step(world, NULL, NULL); - } -} -r3FreeWorld(world); -``` - -`tbSetWorld` borrows the world. `tbRenderFrame` renders and processes input; -it never advances physics. It returns false on close, restart, or scene switch. -The world pointer is passed by address so restoring a snapshot updates the -example's local pointer. Code outside `tbSimulating` runs on paused frames too. -Keep per-frame logic in the same place relative to stepping as in the Rust source. -Sensor examples own their event collectors and process events immediately after -stepping. Animation state stays in ordinary local variables; there are no -before/after-step callbacks or heap-allocated callback state. Examples with local -state that snapshots cannot restore set `snapshotSupported` to zero. - -Show ownership in the example. POD descriptions need no explicit cleanup; their -array views borrow data that must remain valid through the build or insertion call. -Inserted objects belong to their world and are accessed through handles. -Pass the world directly; do not cache component aliases or add physics wrappers. Shared -shapes retained by the example must be freed when no longer needed. The optional -`--no-sleep` override is explicit in the example's description setup. - -Call the dimension-specific `r2*` or `r3*` functions directly without wrapping them -in testbed checking macros. -The example dispatcher installs a thread-local error handler for the duration of -the example. Unexpected API errors print the scene and diagnostic and terminate -the process before invalid outputs can be used. Viewer operations handle their -recoverable errors separately. The C API reports failures through status returns -or `LastStatus()` by default; applications may choose their own error policy. Never longjmp or throw -from an error handler through Rust frames. - -Use named locals for positions, handles, and material parameters. Separate -construction, configuration, insertion, and release. Use `rapier_math.h` for the -public math types' value constructors and arithmetic. Keep dimension-specific -source files dimension-specific. Helpers implementing a particular example's -algorithm are appropriate when the Rust example has the same helper; general -physics convenience wrappers do not belong in the viewer. - -Format authored C and headers with the adjacent `.clang-format`; do not reformat -vendored code. `examples3d/primitives3.c`, `examples3d/compound3.c`, -`examples3d/soft_bodies3.c`, and `examples2d/add_remove2.c` show the intended layout. diff --git a/c/testbed/README.md b/c/testbed/README.md index 08a14aac1..ddb7eb7b1 100644 --- a/c/testbed/README.md +++ b/c/testbed/README.md @@ -1,11 +1,11 @@ # Rapier C testbed The viewer uses raylib for graphics and Dear ImGui through cimgui for its C UI. -Sources and Fira Sans Regular are vendored; configuring and building never downloads -graphics dependencies. A C/C++ compiler, Cargo, and CMake 3.22+ are required. +CMake downloads pinned sources and Fira Sans Regular during the first configuration. +A C/C++ compiler, Cargo, CMake 3.25+, and an internet connection for that initial +download are required. Subsequent builds reuse the downloaded sources. macOS uses system frameworks. Linux requires the usual X11/OpenGL development -packages needed to compile raylib's bundled GLFW. See [vendor/README.md](vendor/README.md) -for exact versions and licenses. +packages needed to compile raylib's bundled GLFW. See [dependencies.md](dependencies.md) for versions, offline builds, and licenses. From the repository root: @@ -85,7 +85,6 @@ CMake's options. `testbed_threading` verifies live worker changes and persistenc across restart, scene switches, and snapshot restore, including serial builds. [Manual C-versus-Rust timing checks](tools/README.md) replay identical no-sleep snapshots with both APIs and compare the final poses and velocities exactly. -See the [Keva timing investigation](tools/timing-results.md) for measured results. Configure `-DRAPIER_TESTBED_GRAPHICS=OFF` to build only the headless runner without raylib, ImGui, or display requirements. Viewer and headless runner use the same C @@ -96,8 +95,7 @@ scene sources. `--assets PATH` overrides the repository asset directory. Each scene uses the Rapier C API directly, following its matching Rust example. Each example owns its render loop, physics stepping, events, and local animation state. The viewer renders one frame and processes input when the example calls -`tbRenderFrame`; it does not wrap physics construction or stepping. See [Writing C examples](PORTING.md) for the correspondence -and ownership conventions. +`tbRenderFrame`; it does not wrap physics construction or stepping. ## Port coverage diff --git a/c/testbed/THIRD_PARTY_NOTICES.txt b/c/testbed/THIRD_PARTY_NOTICES.txt new file mode 100644 index 000000000..92d358ed0 --- /dev/null +++ b/c/testbed/THIRD_PARTY_NOTICES.txt @@ -0,0 +1,1912 @@ +Third-party notices for the Rapier C testbed + +These notices cover the testbed renderer and its desktop backends. Component names +and source paths identify the applicable notices; they do not change their terms. +Complete supplemental licenses are in the adjacent licenses directory. +GLAD-generated loaders use the upstream licensing terms reproduced in GLAD.txt; +Khronos Apache-2.0 material additionally uses Apache-2.0.txt. QOI and the +embedded Proggy fonts use the MIT terms in MIT.txt with the copyrights below. +The GLFW fallback dinput.h and xinput.h headers remain LGPL-2.1-or-later; complete +source copies accompany the viewer under licenses/glfw-mingw. + +============================================================================== +raylib/src/config.h +============================================================================== + +LICENSE: zlib/libpng + + Copyright (c) 2018-2026 Ahmad Fatoum and Ramon Santamaria (@raysan5) + + This software is provided "as-is", without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +******************************************************************************************** + +============================================================================== +raylib/src/external/fix_win32_compatibility.h +============================================================================== + +LICENSE: MIT + + Copyright (c) 2025 Jeffery Myers + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +******************************************************************************************** + +============================================================================== +raylib/src/external/glad.h +============================================================================== + +* Copyright (c) 2008-2018 The Khronos Group Inc. +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and/or associated documentation files (the +* "Materials"), to deal in the Materials without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Materials, and to +* permit persons to whom the Materials are furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Materials. +* +* THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +/ + +============================================================================== +raylib/src/external/glfw/deps/mingw/_mingw_dxhelper.h +============================================================================== + +This file has no copyright assigned and is placed in the Public Domain. +This file is part of the mingw-w64 runtime package. +No warranty is given; refer to the file DISCLAIMER within this package. +/ + +============================================================================== +raylib/src/external/glfw/deps/mingw/dinput.h +============================================================================== + +Copyright (C) the Wine project + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +/ + +============================================================================== +raylib/src/external/glfw/deps/mingw/xinput.h +============================================================================== + +The Wine project - Xinput Joystick Library +Copyright 2008 Andrew Fenn + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +/ + +============================================================================== +raylib/src/external/glfw/include/GLFW/glfw3.h +============================================================================== + +GLFW 3.4 - www.glfw.org +A library for OpenGL, window and input +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +*********************************************************************** + +============================================================================== +raylib/src/external/glfw/include/GLFW/glfw3native.h +============================================================================== + +GLFW 3.4 - www.glfw.org +A library for OpenGL, window and input +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2018 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +*********************************************************************** + +============================================================================== +raylib/src/external/glfw/src/cocoa_init.m +============================================================================== + +======================================================================== +GLFW 3.4 macOS (modified for raylib) - www.glfw.org; www.raylib.com +------------------------------------------------------------------------ +Copyright (c) 2009-2019 Camilla Löwy +Copyright (c) 2024 M374LX + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/cocoa_joystick.h +============================================================================== + +======================================================================== +GLFW 3.4 Cocoa - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2006-2017 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/cocoa_joystick.m +============================================================================== + +======================================================================== +GLFW 3.4 Cocoa - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2009-2019 Camilla Löwy +Copyright (c) 2012 Torsten Walluhn + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/cocoa_monitor.m +============================================================================== + +======================================================================== +GLFW 3.4 macOS (modified for raylib) - www.glfw.org; www.raylib.com +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2019 Camilla Löwy +Copyright (c) 2024 M374LX + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/cocoa_platform.h +raylib/src/external/glfw/src/cocoa_window.m +raylib/src/external/glfw/src/nsgl_context.m +============================================================================== + +======================================================================== +GLFW 3.4 macOS - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2009-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/cocoa_time.c +============================================================================== + +======================================================================== +GLFW 3.4 macOS - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2009-2016 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/cocoa_time.h +============================================================================== + +======================================================================== +GLFW 3.4 macOS - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2009-2021 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/context.c +============================================================================== + +======================================================================== +GLFW 3.4 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2016 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/egl_context.c +============================================================================== + +======================================================================== +GLFW 3.4 EGL - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/glx_context.c +============================================================================== + +======================================================================== +GLFW 3.4 GLX - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/init.c +raylib/src/external/glfw/src/platform.h +raylib/src/external/glfw/src/vulkan.c +============================================================================== + +======================================================================== +GLFW 3.4 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2018 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/input.c +raylib/src/external/glfw/src/internal.h +raylib/src/external/glfw/src/monitor.c +============================================================================== + +======================================================================== +GLFW 3.4 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/linux_joystick.c +============================================================================== + +======================================================================== +GLFW 3.4 Linux - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2017 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/linux_joystick.h +raylib/src/external/glfw/src/xkb_unicode.h +============================================================================== + +======================================================================== +GLFW 3.4 Linux - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2014 Jonas Ådahl + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/mappings.h +raylib/src/external/glfw/src/mappings.h.in +============================================================================== + +======================================================================== +GLFW 3.4 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2006-2018 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== +As mappings.h.in, this file is used by CMake to produce the mappings.h +header file. If you are adding a GLFW specific gamepad mapping, this is +where to put it. +======================================================================== +As mappings.h, this provides all pre-defined gamepad mappings, including +all available in SDL_GameControllerDB. Do not edit this file. Any gamepad +mappings not specific to GLFW should be submitted to SDL_GameControllerDB. +This file can be re-generated from mappings.h.in and the upstream +gamecontrollerdb.txt with the 'update_mappings' CMake target. +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/mappings.h +raylib/src/external/glfw/src/mappings.h.in +============================================================================== + +All gamepad mappings not labeled GLFW are copied from the +SDL_GameControllerDB project under the following license: + +Simple DirectMedia Layer +Copyright (C) 1997-2013 Sam Lantinga + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the +use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + +============================================================================== +raylib/src/external/glfw/src/null_init.c +raylib/src/external/glfw/src/null_platform.h +============================================================================== + +======================================================================== +GLFW 3.4 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2016 Google Inc. +Copyright (c) 2016-2017 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/null_joystick.c +============================================================================== + +======================================================================== +GLFW 3.4 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2016-2017 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/null_joystick.h +============================================================================== + +======================================================================== +GLFW 3.4 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2006-2017 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/null_monitor.c +============================================================================== + +======================================================================== +GLFW 3.4 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2016 Google Inc. +Copyright (c) 2016-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/null_window.c +============================================================================== + +======================================================================== +GLFW 3.4 (modified for raylib) - www.glfw.org; www.raylib.com +------------------------------------------------------------------------ +Copyright (c) 2016 Google Inc. +Copyright (c) 2016-2019 Camilla Löwy +Copyright (c) 2024 M374LX + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/osmesa_context.c +============================================================================== + +======================================================================== +GLFW 3.4 OSMesa - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2016 Google Inc. +Copyright (c) 2016-2017 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/platform.c +============================================================================== + +======================================================================== +GLFW 3.4 (modified for raylib) - www.glfw.org; www.raylib.com +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2018 Camilla Löwy +Copyright (c) 2024 M374LX + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/posix_module.c +============================================================================== + +======================================================================== +GLFW 3.4 POSIX - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2021 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/posix_poll.c +raylib/src/external/glfw/src/posix_poll.h +============================================================================== + +======================================================================== +GLFW 3.4 POSIX - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2022 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/posix_thread.c +raylib/src/external/glfw/src/posix_thread.h +raylib/src/external/glfw/src/posix_time.c +raylib/src/external/glfw/src/posix_time.h +============================================================================== + +======================================================================== +GLFW 3.4 POSIX - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2017 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/wgl_context.c +============================================================================== + +======================================================================== +GLFW 3.4 WGL - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/win32_init.c +raylib/src/external/glfw/src/win32_window.c +============================================================================== + +======================================================================== +GLFW 3.4 Win32 (modified for raylib) - www.glfw.org; www.raylib.com +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2019 Camilla Löwy +Copyright (c) 2024 M374LX + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/win32_joystick.c +raylib/src/external/glfw/src/win32_monitor.c +raylib/src/external/glfw/src/win32_platform.h +============================================================================== + +======================================================================== +GLFW 3.4 Win32 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/win32_joystick.h +============================================================================== + +======================================================================== +GLFW 3.4 Win32 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2006-2017 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/win32_module.c +============================================================================== + +======================================================================== +GLFW 3.4 Win32 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2021 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/win32_thread.c +raylib/src/external/glfw/src/win32_thread.h +raylib/src/external/glfw/src/win32_time.c +raylib/src/external/glfw/src/win32_time.h +============================================================================== + +======================================================================== +GLFW 3.4 Win32 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2017 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/window.c +============================================================================== + +======================================================================== +GLFW 3.4 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2019 Camilla Löwy +Copyright (c) 2012 Torsten Walluhn + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/x11_init.c +raylib/src/external/glfw/src/x11_window.c +============================================================================== + +======================================================================== +GLFW 3.4 X11 (modified for raylib) - www.glfw.org; www.raylib.com +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2019 Camilla Löwy +Copyright (c) 2024 M374LX + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/x11_monitor.c +raylib/src/external/glfw/src/x11_platform.h +============================================================================== + +======================================================================== +GLFW 3.4 X11 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/glfw/src/xkb_unicode.c +============================================================================== + +======================================================================== +GLFW 3.4 X11 - www.glfw.org +------------------------------------------------------------------------ +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2017 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +======================================================================== + +============================================================================== +raylib/src/external/qoi.h +============================================================================== + +Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org +SPDX-License-Identifier: MIT + +============================================================================== +raylib/src/external/rltexgpu.h +raylib/src/rmodels.c +raylib/src/rshapes.c +raylib/src/rtext.c +raylib/src/rtextures.c +============================================================================== + +LICENSE: zlib/libpng + + Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) + + This software is provided "as-is", without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +******************************************************************************************** + +============================================================================== +raylib/src/external/rprand.h +============================================================================== + +LICENSE: zlib/libpng + + Copyright (c) 2023 Ramon Santamaria (@raysan5) + + This software is provided "as-is", without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +******************************************************************************************** + +============================================================================== +raylib/src/external/stb_image.h +raylib/src/external/stb_image_resize2.h +raylib/src/external/stb_image_write.h +raylib/src/external/stb_perlin.h +imgui/imstb_rectpack.h +imgui/imstb_textedit.h +imgui/imstb_truetype.h +============================================================================== + +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +/ + +============================================================================== +raylib/src/platforms/rcore_desktop_glfw.c +raylib/src/rcore.c +============================================================================== + +LICENSE: zlib/libpng + + Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors + + This software is provided "as-is", without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +******************************************************************************************** + +============================================================================== +raylib/src/raylib.h +============================================================================== + +LICENSE: zlib/libpng + + raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, + BSD-like license that allows static linking with closed source software: + + Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) + + This software is provided "as-is", without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +******************************************************************************************** + +============================================================================== +raylib/src/raymath.h +============================================================================== + +LICENSE: zlib/libpng + + Copyright (c) 2015-2026 Ramon Santamaria (@raysan5) + + This software is provided "as-is", without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +******************************************************************************************** + +============================================================================== +raylib/src/rcamera.h +============================================================================== + +LICENSE: zlib/libpng + + Copyright (c) 2022-2026 Christoph Wagner (@Crydsch) and Ramon Santamaria (@raysan5) + + This software is provided "as-is", without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +******************************************************************************************** + +============================================================================== +raylib/src/rlgl.h +============================================================================== + +LICENSE: zlib/libpng + + Copyright (c) 2014-2026 Ramon Santamaria (@raysan5) + + This software is provided "as-is", without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +******************************************************************************************** + +============================================================================== +imgui/imgui_draw.cpp +============================================================================== + +----------------------------------------------------------------------------- +[SECTION] Default font data (ProggyClean.ttf) +----------------------------------------------------------------------------- +MIT License / Copyright (c) 2004, 2005 Tristan Grimmer +Download and more information at https://github.com/bluescan/proggyfonts +----------------------------------------------------------------------------- + +============================================================================== +imgui/imgui_draw.cpp +============================================================================== + +----------------------------------------------------------------------------- +[SECTION] Default font data (ProggyForever-Regular-minimal.ttf) +----------------------------------------------------------------------------- +Based on ProggyForever: https://github.com/ocornut/proggyforever +MIT license / Copyright (c) 2026 Disco Hello, Copyright (c) 2019,2023 Tristan Grimmer +----------------------------------------------------------------------------- + +============================================================================== +rlImGui/imgui_impl_raylib.h +============================================================================== + +raylibExtras * Utilities and Shared Components for Raylib + + rlImGui * basic ImGui integration + + LICENSE: ZLIB + Copyright (c) 2020-2021 Jeffery Myers + + This software is provided "as-is", without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + + +******************************************************************************************** + +============================================================================== +rlImGui/rlImGui.cpp +============================================================================== + +raylibExtras * Utilities and Shared Components for Raylib + + rlImGui * basic ImGui integration + + LICENSE: ZLIB + Copyright (c) 2020-2021 Jeffery Myers + + This software is provided "as-is", without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +******************************************************************************************** + +============================================================================== +rlImGui/rlImGui.h +============================================================================== + +raylibExtras * Utilities and Shared Components for Raylib + + rlImGui * basic ImGui integration + + LICENSE: ZLIB + Copyright (c) 2020-2021 Jeffery Myers + + This software is provided "as-is", without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + + + +******************************************************************************************** + +============================================================================== +raylib/LICENSE +============================================================================== + +Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) + +This software is provided "as-is", without any express or implied warranty. In no event +will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial +applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +============================================================================== +cimgui/LICENSE +============================================================================== + +The MIT License (MIT) + +Copyright (c) 2015 Stephan Dilly + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +============================================================================== +imgui/LICENSE.txt +============================================================================== + +The MIT License (MIT) + +Copyright (c) 2014-2026 Omar Cornut + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +============================================================================== +rlImGui/LICENSE +============================================================================== + +Copyright (c) 2020-2021 Jeffery Myers + +This software is provided "as-is", without any express or implied warranty. In no event +will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial +applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +============================================================================== +fira/LICENSE +============================================================================== + +Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +============================================================================== +raylib/src/external/glfw/LICENSE.md +============================================================================== + +Copyright (c) 2002-2006 Marcus Geelnard + +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + diff --git a/c/testbed/cmake/Dependencies.cmake b/c/testbed/cmake/Dependencies.cmake new file mode 100644 index 000000000..9ae0081ee --- /dev/null +++ b/c/testbed/cmake/Dependencies.cmake @@ -0,0 +1,43 @@ +# Fetch sources only: the testbed controls which modules and targets are built. +# SOURCE_SUBDIR deliberately names a nonexistent directory to avoid configuring +# the upstream examples, generators, and independent CMake projects. +if(CMAKE_VERSION VERSION_LESS 3.25) + message(FATAL_ERROR "The graphical testbed requires CMake 3.25 or newer (raylib 6.0).") +endif() +include(FetchContent) +FetchContent_Declare(raylib + URL https://codeload.github.com/raysan5/raylib/tar.gz/dbc56a87da87d973a9c5baa4e7438a9d20121d28 + URL_HASH SHA256=81b06ce7c19cf3b634b0271c23c361ba6ad8bf45fb8b036abbfeb4260ec1e126 + DOWNLOAD_EXTRACT_TIMESTAMP FALSE TLS_VERIFY TRUE + SOURCE_SUBDIR rapier-no-cmake) +FetchContent_Declare(cimgui + URL https://codeload.github.com/cimgui/cimgui/tar.gz/d3f0c2f4a7d4d116ef908295b971a36bdfdafe27 + URL_HASH SHA256=39bae7634da15173b6dd9b9aea6719148d9509c0cd625b6f81cab77720656ca9 + DOWNLOAD_EXTRACT_TIMESTAMP FALSE TLS_VERIFY TRUE + SOURCE_SUBDIR rapier-no-cmake) +# This is the imgui submodule revision recorded by the pinned cimgui commit. +FetchContent_Declare(imgui + URL https://codeload.github.com/ocornut/imgui/tar.gz/dac07199cfd761113d966eb8ad739254e10df2fe + URL_HASH SHA256=d95368fadc5a1665fc1b3f198d4c9ff026e8bd8edcc7bad5d82fa12cf379c5f3 + DOWNLOAD_EXTRACT_TIMESTAMP FALSE TLS_VERIFY TRUE + SOURCE_SUBDIR rapier-no-cmake) +FetchContent_Declare(rlimgui + URL https://codeload.github.com/raylib-extras/rlImGui/tar.gz/3bc5731c4216bb8caa67fbea24aa85ce80d57ccb + URL_HASH SHA256=2198c4eb4c0a2b8efc2204da046ef014a2c7b736e1483039badee6d19a7ed35a + DOWNLOAD_EXTRACT_TIMESTAMP FALSE TLS_VERIFY TRUE + SOURCE_SUBDIR rapier-no-cmake) +# Download just the UI font, not Mozilla's complete font collection. +FetchContent_Declare(fira_font + URL https://raw.githubusercontent.com/mozilla/Fira/fd8c8c0a3d353cd99e8ca1662942d165e6961407/ttf/FiraSans-Regular.ttf + URL_HASH SHA256=a389cef71891df1232370fcebd7cfde5f74e741967070399adc91fd069b2094b + DOWNLOAD_NO_EXTRACT TRUE TLS_VERIFY TRUE) +FetchContent_MakeAvailable(raylib cimgui imgui rlimgui fira_font) + +# cimgui uses "./imgui/..." includes, but GitHub archives omit its submodule. +# Reproduce that header layout in the build directory without modifying either +# downloaded sources or user-supplied FETCHCONTENT_SOURCE_DIR_* checkouts. +set(TB_CIMGUI_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/cimgui-include") +foreach(header imgui.h imgui_internal.h imconfig.h imstb_rectpack.h imstb_textedit.h imstb_truetype.h) + configure_file("${imgui_SOURCE_DIR}/${header}" + "${TB_CIMGUI_INCLUDE_DIR}/imgui/${header}" COPYONLY) +endforeach() diff --git a/c/testbed/dependencies.md b/c/testbed/dependencies.md new file mode 100644 index 000000000..b87e1fa2d --- /dev/null +++ b/c/testbed/dependencies.md @@ -0,0 +1,73 @@ +# Testbed dependencies + +The graphical viewer uses CMake `FetchContent` to download these exact revisions +at **configure time**, then compiles them with the testbed. Archive/file SHA-256 +hashes are pinned in [cmake/Dependencies.cmake](cmake/Dependencies.cmake). +Sources are stored under the build directory's `_deps/`; nothing is downloaded +into the repository. No Git checkout, package installation, or binding generator +is needed for these dependencies. + +| Dependency | Revision | License | +| --- | --- | --- | +| [raylib 6.0](https://github.com/raysan5/raylib) | `dbc56a87da87d973a9c5baa4e7438a9d20121d28` | zlib | +| [Dear ImGui 1.92.7](https://github.com/ocornut/imgui) | `dac07199cfd761113d966eb8ad739254e10df2fe` | MIT | +| [cimgui 1.92.7](https://github.com/cimgui/cimgui) | `d3f0c2f4a7d4d116ef908295b971a36bdfdafe27` | MIT | +| [rlImGui Raylib_6_0](https://github.com/raylib-extras/rlImGui) | `3bc5731c4216bb8caa67fbea24aa85ce80d57ccb` | zlib | +| [Fira Sans Regular](https://github.com/mozilla/Fira) | `fd8c8c0a3d353cd99e8ca1662942d165e6961407` | SIL OFL 1.1 | + +Dear ImGui matches cimgui's submodule revision. Only the regular Fira font is +downloaded, then embedded in the executable. There is no runtime network or font +installation requirement. The UI remains C11; ImGui and its wrappers use C++17. + +Raylib uses desktop GLFW and OpenGL 3.3 (macOS, Windows, Linux/X11). Audio, model +importers, mesh generators, raylib font-file loaders, compression helpers, and +gesture detection are disabled. Full upstream archives are downloaded, but their +examples, generators, and unused backends are not built. Texture loading, +screenshot export, mesh instancing, and the default font/white texture remain. + +## Offline builds + +An existing populated build directory can be reconfigured with +`-DFETCHCONTENT_FULLY_DISCONNECTED=ON`. This requires the sources to have already +been downloaded; it does not bootstrap an empty build directory. + +For a new offline build, provide extracted sources at the pinned revisions: + +```sh +cmake -S c -B build/c3 -DRAPIER_BUILD_TESTBED=ON \ + -DFETCHCONTENT_SOURCE_DIR_RAYLIB=/path/to/raylib \ + -DFETCHCONTENT_SOURCE_DIR_CIMGUI=/path/to/cimgui \ + -DFETCHCONTENT_SOURCE_DIR_IMGUI=/path/to/imgui \ + -DFETCHCONTENT_SOURCE_DIR_RLIMGUI=/path/to/rlImGui \ + -DFETCHCONTENT_SOURCE_DIR_FIRA_FONT=/path/to/font-directory +``` + +The font directory must contain `FiraSans-Regular.ttf`. Source overrides are +used as-is; archive hashes are checked only for downloaded dependencies. CMake +does not modify the supplied source trees. Cargo dependencies must also be +available separately for an offline physics build. + +`RAPIER_BUILD_TESTBED=OFF` or `RAPIER_TESTBED_GRAPHICS=OFF` skips all five downloads. +See the [FetchContent documentation](https://cmake.org/cmake/help/latest/module/FetchContent.html) +for its cache and source override options. + +## Redistribution + +Ship the license files, `THIRD_PARTY_NOTICES.txt`, and `licenses/` directory copied +beside the viewer. Downloading instead of vendoring does not change the licenses. +The collected notices cover the configured renderer; if redistributing complete +upstream archives, preserve their additional per-file notices too. + +Raylib includes separately licensed components: GLFW/rprand/rltexgpu use zlib-style +terms; stb uses MIT/public-domain alternatives; GLAD includes Khronos notices; +QOI and ImGui's embedded Proggy fonts use MIT terms; dirent has its own permissive +notice. The supplied notices and supplemental texts retain these terms. + +GLFW's Windows fallback `dinput.h` and `xinput.h` headers are LGPL-2.1-or-later. +They contain declarations and small macros rather than a linked LGPL library; +[LGPL 2.1 section 5](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) +addresses this use. Their complete sources and LGPL license are copied beside the +viewer. The testbed and bindings retain their own license. + +When updating dependency pins or enabling additional modules, update the hashes, +collected notices, and supplemental licenses together. diff --git a/c/testbed/font_data.h.in b/c/testbed/font_data.h.in index 44caafda0..3ec40f1e6 100644 --- a/c/testbed/font_data.h.in +++ b/c/testbed/font_data.h.in @@ -1,4 +1,4 @@ -/* Generated from the unmodified SIL OFL font in vendor/fira. Do not edit. */ +/* Generated from the unmodified SIL OFL font downloaded from Mozilla Fira. Do not edit. */ #ifndef RAPIER_TESTBED_FONT_DATA_H #define RAPIER_TESTBED_FONT_DATA_H static const unsigned char tbFontData[] = { @TB_FONT_BYTES@ }; diff --git a/c/testbed/licenses/Apache-2.0.txt b/c/testbed/licenses/Apache-2.0.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/c/testbed/licenses/Apache-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/c/testbed/licenses/CC0-1.0.txt b/c/testbed/licenses/CC0-1.0.txt new file mode 100644 index 000000000..0e259d42c --- /dev/null +++ b/c/testbed/licenses/CC0-1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/c/testbed/licenses/FiraSans-LICENSE.txt b/c/testbed/licenses/FiraSans-LICENSE.txt new file mode 100644 index 000000000..b82722716 --- /dev/null +++ b/c/testbed/licenses/FiraSans-LICENSE.txt @@ -0,0 +1,93 @@ +Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/c/testbed/licenses/GLAD.txt b/c/testbed/licenses/GLAD.txt new file mode 100644 index 000000000..4965a6bff --- /dev/null +++ b/c/testbed/licenses/GLAD.txt @@ -0,0 +1,63 @@ +The glad source code: + + The MIT License (MIT) + + Copyright (c) 2013-2022 David Herberth + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +The Khronos Specifications: + + Copyright (c) 2013-2020 The Khronos Group Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +The EGL Specification and various headers: + + Copyright (c) 2007-2016 The Khronos Group Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and/or associated documentation files (the + "Materials"), to deal in the Materials without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Materials, and to + permit persons to whom the Materials are furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Materials. + + THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. diff --git a/c/testbed/licenses/LGPL-2.1.txt b/c/testbed/licenses/LGPL-2.1.txt new file mode 100644 index 000000000..f6683e74e --- /dev/null +++ b/c/testbed/licenses/LGPL-2.1.txt @@ -0,0 +1,501 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Moe Ghoul, President of Vice + +That's all there is to it! diff --git a/c/testbed/licenses/MIT.txt b/c/testbed/licenses/MIT.txt new file mode 100644 index 000000000..d817195da --- /dev/null +++ b/c/testbed/licenses/MIT.txt @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/c/testbed/licenses/SOURCES.md b/c/testbed/licenses/SOURCES.md new file mode 100644 index 000000000..bc7b78013 --- /dev/null +++ b/c/testbed/licenses/SOURCES.md @@ -0,0 +1,12 @@ +# Supplemental license texts + +Retrieved on 2026-09-24. Original per-file notices remain in the downloaded sources. + +- `Apache-2.0.txt`: https://www.apache.org/licenses/LICENSE-2.0.txt +- `LGPL-2.1.txt`: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +- `GLAD.txt`: https://raw.githubusercontent.com/Dav1dde/glad/v2.0.2/LICENSE +- `MIT.txt`: https://raw.githubusercontent.com/spdx/license-list-data/main/text/MIT.txt +- `WTFPL.txt`: https://raw.githubusercontent.com/spdx/license-list-data/main/text/WTFPL.txt +- `CC0-1.0.txt`: https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt + +- `FiraSans-LICENSE.txt`: https://raw.githubusercontent.com/mozilla/Fira/fd8c8c0a3d353cd99e8ca1662942d165e6961407/LICENSE diff --git a/c/testbed/licenses/WTFPL.txt b/c/testbed/licenses/WTFPL.txt new file mode 100644 index 000000000..7a3094a82 --- /dev/null +++ b/c/testbed/licenses/WTFPL.txt @@ -0,0 +1,11 @@ +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. + +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/c/testbed/tools/timing-results.json b/c/testbed/tools/timing-results.json deleted file mode 100644 index dcb8c9863..000000000 --- a/c/testbed/tools/timing-results.json +++ /dev/null @@ -1,194 +0,0 @@ -[ - { - "scene": "primitives3", - "repeat": 0, - "language": "C", - "output": "C primitives3 bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.944213 engine_ms=1.934008", - "wall_ms": 1.944213, - "engine_ms": 1.934008 - }, - { - "scene": "primitives3", - "repeat": 0, - "language": "Rust", - "output": "Rust bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.963726 engine_ms=1.954551 final_state=exact_match", - "wall_ms": 1.963726, - "engine_ms": 1.954551 - }, - { - "scene": "primitives3", - "repeat": 1, - "language": "C", - "output": "C primitives3 bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.954273 engine_ms=1.943762", - "wall_ms": 1.954273, - "engine_ms": 1.943762 - }, - { - "scene": "primitives3", - "repeat": 1, - "language": "Rust", - "output": "Rust bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=2.374521 engine_ms=2.329513 final_state=exact_match", - "wall_ms": 2.374521, - "engine_ms": 2.329513 - }, - { - "scene": "primitives3", - "repeat": 2, - "language": "C", - "output": "C primitives3 bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=2.027337 engine_ms=2.017319", - "wall_ms": 2.027337, - "engine_ms": 2.017319 - }, - { - "scene": "primitives3", - "repeat": 2, - "language": "Rust", - "output": "Rust bodies=1281 awake_dynamic=1280 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=2.002452 engine_ms=1.993252 final_state=exact_match", - "wall_ms": 2.002452, - "engine_ms": 1.993252 - }, - { - "scene": "stress_tests_boxes3", - "repeat": 0, - "language": "C", - "output": "C stress_tests_boxes3 bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.916960 engine_ms=1.906777", - "wall_ms": 1.91696, - "engine_ms": 1.906777 - }, - { - "scene": "stress_tests_boxes3", - "repeat": 0, - "language": "Rust", - "output": "Rust bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.939562 engine_ms=1.928838 final_state=exact_match", - "wall_ms": 1.939562, - "engine_ms": 1.928838 - }, - { - "scene": "stress_tests_boxes3", - "repeat": 1, - "language": "C", - "output": "C stress_tests_boxes3 bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=2.035717 engine_ms=2.000051", - "wall_ms": 2.035717, - "engine_ms": 2.000051 - }, - { - "scene": "stress_tests_boxes3", - "repeat": 1, - "language": "Rust", - "output": "Rust bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.945655 engine_ms=1.935830 final_state=exact_match", - "wall_ms": 1.945655, - "engine_ms": 1.93583 - }, - { - "scene": "stress_tests_boxes3", - "repeat": 2, - "language": "C", - "output": "C stress_tests_boxes3 bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.932433 engine_ms=1.921569", - "wall_ms": 1.932433, - "engine_ms": 1.921569 - }, - { - "scene": "stress_tests_boxes3", - "repeat": 2, - "language": "Rust", - "output": "Rust bodies=1001 awake_dynamic=1000 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.933630 engine_ms=1.923563 final_state=exact_match", - "wall_ms": 1.93363, - "engine_ms": 1.923563 - }, - { - "scene": "stress_tests_boxes2", - "repeat": 0, - "language": "C", - "output": "C stress_tests_boxes2 bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.395520 engine_ms=1.386361", - "wall_ms": 1.39552, - "engine_ms": 1.386361 - }, - { - "scene": "stress_tests_boxes2", - "repeat": 0, - "language": "Rust", - "output": "Rust bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.392803 engine_ms=1.384000 final_state=exact_match", - "wall_ms": 1.392803, - "engine_ms": 1.384 - }, - { - "scene": "stress_tests_boxes2", - "repeat": 1, - "language": "C", - "output": "C stress_tests_boxes2 bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.395897 engine_ms=1.386971", - "wall_ms": 1.395897, - "engine_ms": 1.386971 - }, - { - "scene": "stress_tests_boxes2", - "repeat": 1, - "language": "Rust", - "output": "Rust bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.413250 engine_ms=1.403848 final_state=exact_match", - "wall_ms": 1.41325, - "engine_ms": 1.403848 - }, - { - "scene": "stress_tests_boxes2", - "repeat": 2, - "language": "C", - "output": "C stress_tests_boxes2 bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.469100 engine_ms=1.459211", - "wall_ms": 1.4691, - "engine_ms": 1.459211 - }, - { - "scene": "stress_tests_boxes2", - "repeat": 2, - "language": "Rust", - "output": "Rust bodies=3383 awake_dynamic=3380 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=1.474797 engine_ms=1.464414 final_state=exact_match", - "wall_ms": 1.474797, - "engine_ms": 1.464414 - }, - { - "scene": "stress_tests_keva3", - "repeat": 0, - "language": "C", - "output": "C stress_tests_keva3 bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=118.190743 engine_ms=118.121810", - "wall_ms": 118.190743, - "engine_ms": 118.12181 - }, - { - "scene": "stress_tests_keva3", - "repeat": 0, - "language": "Rust", - "output": "Rust bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=121.134219 engine_ms=121.063087 final_state=exact_match", - "wall_ms": 121.134219, - "engine_ms": 121.063087 - }, - { - "scene": "stress_tests_keva3", - "repeat": 1, - "language": "C", - "output": "C stress_tests_keva3 bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=120.751150 engine_ms=120.728234", - "wall_ms": 120.75115, - "engine_ms": 120.728234 - }, - { - "scene": "stress_tests_keva3", - "repeat": 1, - "language": "Rust", - "output": "Rust bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=120.755595 engine_ms=120.734268 final_state=exact_match", - "wall_ms": 120.755595, - "engine_ms": 120.734268 - }, - { - "scene": "stress_tests_keva3", - "repeat": 2, - "language": "C", - "output": "C stress_tests_keva3 bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=120.229320 engine_ms=120.200951", - "wall_ms": 120.22932, - "engine_ms": 120.200951 - }, - { - "scene": "stress_tests_keva3", - "repeat": 2, - "language": "Rust", - "output": "Rust bodies=38271 awake_dynamic=38270 SIMD=4 workers=1 warmup=120 measured=300 wall_ms=123.052455 engine_ms=123.034317 final_state=exact_match", - "wall_ms": 123.052455, - "engine_ms": 123.034317 - } -] diff --git a/c/testbed/tools/timing-results.md b/c/testbed/tools/timing-results.md deleted file mode 100644 index d7bd83a4b..000000000 --- a/c/testbed/tools/timing-results.md +++ /dev/null @@ -1,43 +0,0 @@ -# C testbed timing investigation (2026-09-18) - -Measured on macOS arm64 with Rust 1.98.0, the `soft-bodies` revision -`73a93dd9b`, and the C bindings in this worktree. Both paths used release builds, -f32, four-lane SIMD, parallel support with one dedicated worker, and native -profiling. Every dynamic body was awake; sleeping was disabled. - -Each row is the median of three runs, each with 120 warmup steps followed by -300 measured steps. Times are complete step-call wall time, excluding rendering, -scene construction, and serialization. C used the actual testbed core and shared -library. Rust replayed the identical initial snapshot with `PhysicsWorld::step` -and checked every final rigid-body pose and velocity against C for exact equality. - -| Scene | C ms/step | Rust ms/step | C / Rust | -| --- | ---: | ---: | ---: | -| `primitives3` | 1.954 | 2.002 | 0.976 | -| `stress_tests_boxes3` | 1.932 | 1.940 | 0.996 | -| `stress_tests_boxes2` | 1.396 | 1.413 | 0.988 | -| `stress_tests_keva3` | 120.229 | 121.134 | 0.993 | - -Keva contained 38,271 rigid bodies, of which 38,270 were dynamic and awake. -A further C run with parallelism compiled out measured **122.037 ms/step** -(native counter: 122.030 ms), so the earlier serial build path also had similar -per-step cost. Its loaded library reported `parallel=0`, `profiling=1`, SIMD 4. - -There was no 7x per-step overhead in these matched runs. These checks isolate the -C call path; they do not establish construction parity for every ported scene. - -The old C viewer accumulated real elapsed time and executed up to eight physics -steps before drawing a frame. Its original Physics label timed that entire batch. -The Rust viewer executes one step per frame and displays the native single-step -counter. Replaying the old C loop on Keva showed **746.84 ms/frame for six steps**, -while the native counter read **124.70 ms/step** in that same frame. It executed -65 steps in a 12-frame smoke run. This reproduces a large apparent slowdown -without a comparable increase in cost per physics step. - -The C viewer now advances one step per frame, matching Rust. The primary Physics -label uses the native per-step counter. The Performance panel separately reports -the complete simulation call time, step count, and draw CPU time. The corrected -Keva viewer executed exactly 60 steps in 60 frames; its screenshot showed -119.04 ms/step and 119.07 ms/frame for one step. - -See [reproduction instructions](README.md) and [all measured runs](timing-results.json). From c69c9c79b62308384ff2d142e8494baed4a12b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 24 Sep 2026 16:28:46 +0200 Subject: [PATCH 4/8] feat: document C bindings with Doxygen --- .github/workflows/c-bindings.yml | 16 + c/CMakeLists.txt | 4 + c/README.md | 27 + c/cbindgen.toml | 15 +- c/doxygen/CMakeLists.txt | 32 + c/doxygen/Doxyfile.in | 43 + c/doxygen/check.py | 54 + c/doxygen/index.html.in | 11 + c/doxygen/reference.dox | 192 + c/include/rapier.h | 9003 ++++++++++++++++++++++++++++-- c/include/rapier.hpp | 30 + c/include/rapier_helpers.h | 9 + c/include/rapier_math.h | 28 + c/src/array_views.rs | 119 +- c/src/config_data.rs | 107 + c/src/control.rs | 128 +- c/src/descriptors.rs | 146 +- c/src/dynamics.rs | 6 + c/src/error.rs | 25 + c/src/extra.rs | 66 +- c/src/geometry.rs | 23 + c/src/geometry_views.rs | 31 +- c/src/handle_access.rs | 150 +- c/src/joint_access.rs | 101 + c/src/joint_desc.rs | 52 +- c/src/joints.rs | 27 + c/src/objects.rs | 30 +- c/src/pipeline.rs | 243 +- c/src/queries.rs | 41 + c/src/read_access.rs | 262 +- c/src/render.rs | 29 +- c/src/return_values.rs | 35 + c/src/robotics.rs | 116 +- c/src/scoped_access.rs | 407 +- c/src/shape_desc.rs | 30 +- c/src/soft_body.rs | 73 + c/src/soft_desc.rs | 92 + c/src/soft_recipes.rs | 34 +- c/src/types.rs | 237 +- c/src/world.rs | 4 + c/src/world_queries.rs | 49 + c/tools/generate-header.py | 2 +- 42 files changed, 11320 insertions(+), 809 deletions(-) create mode 100644 c/doxygen/CMakeLists.txt create mode 100644 c/doxygen/Doxyfile.in create mode 100644 c/doxygen/check.py create mode 100644 c/doxygen/index.html.in create mode 100644 c/doxygen/reference.dox diff --git a/.github/workflows/c-bindings.yml b/.github/workflows/c-bindings.yml index b138e3647..268c3fe1d 100644 --- a/.github/workflows/c-bindings.yml +++ b/.github/workflows/c-bindings.yml @@ -61,3 +61,19 @@ jobs: cmake -S c -B build/simd8 -DRAPIER_BUILD_TESTBED=ON -DRAPIER_TESTBED_GRAPHICS=OFF -DRAPIER_PROFILE=debug -DRAPIER_SIMD_LANES=8 -DRAPIER_ENABLE_PARALLEL=ON -DRAPIER_CARGO_TARGET_DIR=${{ github.workspace }}/target cmake --build build/simd8 --parallel 2 ctest --test-dir build/simd8 --output-on-failure + + documentation: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install documentation tools + run: sudo apt-get update && sudo apt-get install -y doxygen + - name: Generate and validate all API variants + run: | + cmake -S c/doxygen -B build/c-docs + cmake --build build/c-docs --target rapier_docs --parallel 2 + - name: Upload HTML reference + uses: actions/upload-artifact@v4 + with: + name: rapier-c-api-docs + path: build/c-docs/html diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt index fd97d1176..dcc26e74f 100644 --- a/c/CMakeLists.txt +++ b/c/CMakeLists.txt @@ -14,6 +14,10 @@ set_property(CACHE RAPIER_DIMENSION PROPERTY STRINGS 2 3) set(RAPIER_PRECISION "32" CACHE STRING "32 or 64") set_property(CACHE RAPIER_PRECISION PROPERTY STRINGS 32 64) option(RAPIER_SHARED "Link the shared library (otherwise static)" ON) +option(RAPIER_BUILD_DOCS "Enable the optional Doxygen API reference target" OFF) +if(RAPIER_BUILD_DOCS) + add_subdirectory(doxygen) +endif() option(RAPIER_BUILD_TESTS "Build C/C++ integration tests" ON) option(RAPIER_BUILD_EXAMPLES "Build the falling-ball example" ON) option(RAPIER_BUILD_TESTBED "Build the C example testbed" OFF) diff --git a/c/README.md b/c/README.md index d2aeb4084..f2be1f1e6 100644 --- a/c/README.md +++ b/c/README.md @@ -13,6 +13,8 @@ callback-scoped read access and runtime borrow checks protect access to it. There are no compatibility aliases. Rebuild consumers using the matching header, library, dimension, precision, and feature configuration. +See [API reference](#api-reference) for searchable Doxygen documentation and build instructions. + ## C names Functions use a dimension prefix and camelCase: `r2NewWorld` in 2D and @@ -585,3 +587,28 @@ rapier::check(r3LastStatus()); Check errors after each fallible call. These owners wrap only owned pointers; borrowed callback contexts and MJCF visual meshes must not be wrapped or freed. Owners of resources referencing a world must be destroyed before that world. + +## API reference + +Generate searchable Doxygen documentation for 2D/3D and f32/f64: + +```sh +cmake -S c/doxygen -B build/c-docs +cmake --build build/c-docs --target rapier_docs --parallel +``` + +Open `build/c-docs/html/index.html`. This needs Doxygen 1.9.4+, Python 3, CMake, +and a C compiler; it does not build physics or fetch testbed dependencies. +Alternatively, configure the normal C build with `-DRAPIER_BUILD_DOCS=ON` and build +`rapier_docs`; the entry page is then under `doxygen/html/index.html` in that build. +Normal builds do not require documentation tools. + +The reference includes all optional APIs, with robotics limited to 3D/f32. +Descriptions of ownership, errors, arrays, callbacks, and snapshots accompany the +API groups. CI checks all four variants for Doxygen warnings and missing functions, +then uploads the HTML as the `rapier-c-api-docs` artifact. + +Edit API comments in `c/src/*.rs` and regenerate `include/rapier.h`; edit inline +math/C++ helper comments in their headers. Keep contracts specific: units, +coordinate frames, ownership, callback lifetime, and special failure cases. +The concise usage pages live in `doxygen/reference.dox`. diff --git a/c/cbindgen.toml b/c/cbindgen.toml index a283af715..30871b5e6 100644 --- a/c/cbindgen.toml +++ b/c/cbindgen.toml @@ -6,7 +6,8 @@ style = "both" documentation_style = "doxy" autogen_warning = "/* Generated by cbindgen. Regenerate with c/tools/generate-header.sh. */" header = """ -/* Rapier C ABI. Select dimension and precision before including this file. +/** @file + * Rapier C ABI. Select dimension and precision before including this file. * Select one variant per translation unit; flags must match its linked library. * Read README.md for ownership, pointer lifetime, callbacks and threading. */ #if !defined(RAPIER_DIM2) && !defined(RAPIER_DIM3) @@ -22,25 +23,37 @@ header = """ #error Select exactly one Rapier precision #endif #if defined(RAPIER_DIM2) +/** Spatial dimension selected by this header. */ #define R2_DIMENSION 2 +/** Select an R2/R3 public type name. */ #define RAPIER_TYPE(name) R2##name +/** Select an R2/R3 public constant name. */ #define RAPIER_CONST(name) R2_##name +/** Select an r2/r3 public function name. */ #define RAPIER_FN(name) r2##name #else +/** Spatial dimension selected by this header. */ #define R3_DIMENSION 3 +/** Select an R2/R3 public type name. */ #define RAPIER_TYPE(name) R3##name +/** Select an R2/R3 public constant name. */ #define RAPIER_CONST(name) R3_##name +/** Select an r2/r3 public function name. */ #define RAPIER_FN(name) r3##name #endif #if defined(_WIN32) +/** C ABI calling convention. */ #define RAPIER_CALL __cdecl #else +/** C ABI calling convention. */ #define RAPIER_CALL #endif #ifndef RAPIER_API #if defined(_WIN32) && !defined(RAPIER_STATIC) +/** Shared-library import annotation. */ #define RAPIER_API __declspec(dllimport) #else +/** Shared-library import annotation. */ #define RAPIER_API #endif #endif diff --git a/c/doxygen/CMakeLists.txt b/c/doxygen/CMakeLists.txt new file mode 100644 index 000000000..da08962bf --- /dev/null +++ b/c/doxygen/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.20) +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + project(RapierCDocs LANGUAGES C) +endif() +find_package(Doxygen 1.9.4 REQUIRED) +find_package(Python3 REQUIRED COMPONENTS Interpreter) +get_filename_component(RAPIER_C_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) +file(READ "${RAPIER_C_DIR}/VERSION" DOC_VERSION) +string(STRIP "${DOC_VERSION}" DOC_VERSION) +set(DOC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/html") +file(MAKE_DIRECTORY "${DOC_OUTPUT}") +configure_file(index.html.in "${DOC_OUTPUT}/index.html" @ONLY) +add_custom_target(rapier_docs) +foreach(DOC_DIM 2 3) + foreach(DOC_PRECISION 32 64) + set(DOC_VARIANT "${DOC_DIM}d-f${DOC_PRECISION}") + set(DOC_ROBOTICS "") + if(DOC_DIM EQUAL 3 AND DOC_PRECISION EQUAL 32) + set(DOC_ROBOTICS "RAPIER_ROBOTICS") + endif() + configure_file(Doxyfile.in "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile-${DOC_VARIANT}" @ONLY) + add_custom_target(rapier_docs_${DOC_VARIANT} + COMMAND "${DOXYGEN_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile-${DOC_VARIANT}" + COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/check.py" + "${DOC_OUTPUT}/${DOC_VARIANT}/xml" "${DOC_DIM}" "${DOC_PRECISION}" + "${CMAKE_C_COMPILER}" "${CMAKE_C_COMPILER_ID}" + WORKING_DIRECTORY "${RAPIER_C_DIR}" + COMMENT "Generating and checking ${DOC_VARIANT} API reference" + VERBATIM) + add_dependencies(rapier_docs rapier_docs_${DOC_VARIANT}) + endforeach() +endforeach() diff --git a/c/doxygen/Doxyfile.in b/c/doxygen/Doxyfile.in new file mode 100644 index 000000000..713068b3a --- /dev/null +++ b/c/doxygen/Doxyfile.in @@ -0,0 +1,43 @@ +PROJECT_NAME = "Rapier C — @DOC_DIM@D / f@DOC_PRECISION@" +PROJECT_NUMBER = "@DOC_VERSION@" +OUTPUT_DIRECTORY = "@DOC_OUTPUT@/@DOC_VARIANT@" +INPUT = "@RAPIER_C_DIR@/include/rapier.h" \ + "@RAPIER_C_DIR@/include/rapier_math.h" \ + "@RAPIER_C_DIR@/include/rapier_helpers.h" \ + "@RAPIER_C_DIR@/include/rapier.hpp" \ + "@RAPIER_C_DIR@/doxygen/reference.dox" +EXAMPLE_PATH = "@RAPIER_C_DIR@/examples" "@RAPIER_C_DIR@/testbed/examples@DOC_DIM@d" +EXAMPLE_RECURSIVE = YES +EXTRACT_ALL = NO +EXTRACT_STATIC = YES +EXTRACT_PRIVATE = NO +JAVADOC_AUTOBRIEF = YES +ALWAYS_DETAILED_SEC = YES +OPTIMIZE_OUTPUT_FOR_C = YES +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +EXPAND_AS_DEFINED = RAPIER_TYPE RAPIER_CONST RAPIER_FN +PREDEFINED = RAPIER_DIM@DOC_DIM@ RAPIER_F@DOC_PRECISION@ RAPIER_FEM RAPIER_PARALLEL @DOC_ROBOTICS@ \ + RAPIER_API= RAPIER_CALL= __cplusplus +INCLUDE_PATH = "@RAPIER_C_DIR@/include" +SKIP_FUNCTION_MACROS = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = FAIL_ON_WARNINGS +QUIET = YES +GENERATE_HTML = YES +HTML_OUTPUT = . +GENERATE_TREEVIEW = YES +SEARCHENGINE = YES +GENERATE_LATEX = NO +GENERATE_XML = YES +XML_PROGRAMLISTING = NO +HAVE_DOT = NO +SHOW_INCLUDE_FILES = NO +FULL_PATH_NAMES = NO +SORT_MEMBER_DOCS = YES + +EXCLUDE_SYMBOLS = RAPIER_H RAPIER_MATH_H RAPIER_HELPERS_H RAPIER_HPP diff --git a/c/doxygen/check.py b/c/doxygen/check.py new file mode 100644 index 000000000..a3be39742 --- /dev/null +++ b/c/doxygen/check.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Check that Doxygen documents every function visible to the C compiler.""" +from pathlib import Path +import re +import subprocess +import sys +import tempfile +import xml.etree.ElementTree as ET + +xml_dir, dimension, precision, compiler, compiler_id = sys.argv[1:] +xml_dir = Path(xml_dir) +c_dir = Path(__file__).resolve().parents[1] +prefix = f"r{dimension}" +definitions = [f"RAPIER_DIM{dimension}", f"RAPIER_F{precision}", "RAPIER_FEM", "RAPIER_PARALLEL", "RAPIER_STATIC"] +if (dimension, precision) == ("3", "32"): + definitions.append("RAPIER_ROBOTICS") +with tempfile.TemporaryDirectory(prefix="rapier-doc-check-") as tmp: + source = Path(tmp) / "api.c" + source.write_text('#include "rapier_math.h"\n#include "rapier_helpers.h"\n') + if compiler_id == "MSVC": + args = [compiler, "/nologo", "/EP", "/TC", f"/I{c_dir / 'include'}", *[f"/D{x}" for x in definitions], str(source)] + else: + args = [compiler, "-E", "-P", "-x", "c", "-I", str(c_dir / "include"), *[f"-D{x}" for x in definitions], str(source)] + if sys.platform == "darwin": + sdk = subprocess.check_output(["xcrun", "--show-sdk-path"], text=True).strip() + args[1:1] = ["-isysroot", sdk] + preprocessed = subprocess.check_output(args, text=True) +expected = set(re.findall(rf"\b({prefix}\w+)\s*\(", preprocessed)) +index = ET.parse(xml_dir / "index.xml") +actual = {m.findtext("name") for m in index.findall('.//member[@kind="function"]') if m.findtext("name", "").startswith(prefix)} +if missing := expected - actual: + raise SystemExit(f"Functions missing from reference: {sorted(missing)}") +if extra := actual - expected: + raise SystemExit(f"Functions from the wrong build variant: {sorted(extra)}") +# Function comments need a brief, not just an @ingroup/@see tag. Also reject +# accidental internal Rust names leaking through the header generator. +checked = set() +for compound in index.findall("compound"): + path = xml_dir / f"{compound.attrib['refid']}.xml" + tree = ET.parse(path) + for member in tree.findall(".//memberdef"): + name = member.findtext("name", "") + if name not in actual or name in checked: + continue + checked.add(name) + brief = "".join(member.find("briefdescription").itertext()).strip() + detail = "".join(member.find("detaileddescription").itertext()).strip() + if not brief: + raise SystemExit(f"Missing brief description: {name}") + if re.search(r"\brpr_\w+|\bRpr\w+|\bRPR_\w+", brief + detail): + raise SystemExit(f"Internal Rust spelling in documentation: {name}") +if not expected: + raise SystemExit("No C API functions found during preprocessing") +print(f"Verified {len(expected)} documented functions for {dimension}D/f{precision}.") diff --git a/c/doxygen/index.html.in b/c/doxygen/index.html.in new file mode 100644 index 000000000..037b5bb70 --- /dev/null +++ b/c/doxygen/index.html.in @@ -0,0 +1,11 @@ + + +Rapier C API @DOC_VERSION@ + +

Rapier C API

Version @DOC_VERSION@. Choose the dimension and precision of your linked library.

+ + +
Dimension32-bit floats64-bit floats
2Dr2 / f32r2 / f64
3Dr3 / f32r3 / f64
+

References include parallel and FEM APIs. Robotics is available only in 3D/f32. +Your build must enable each optional feature before you use it.

+ diff --git a/c/doxygen/reference.dox b/c/doxygen/reference.dox new file mode 100644 index 000000000..adcbae312 --- /dev/null +++ b/c/doxygen/reference.dox @@ -0,0 +1,192 @@ +/** @mainpage Rapier C API + +Choose the reference matching your library's dimension and scalar precision. +Function names use `r2` or `r3`; types and constants use `R2` or `R3`. +Instance methods separate their receiver with an underscore, for example +`r3RigidBody_SetTranslation`. Include `rapier.h`; `rapier_math.h` adds inline math +and `rapier_helpers.h` adds explicit invalid-handle constants. + +Start with @ref ownership and @ref api_errors, then @ref falling_ball.c. +The topic groups list the API by purpose. The optional @ref cpp helpers provide +RAII ownership; they do not change the C ABI. + +@section selection Build selection +Define exactly one of `RAPIER_DIM2` / `RAPIER_DIM3` and one of +`RAPIER_F32` / `RAPIER_F64` before including the headers. The defaults are 3D/f32. +Flags must match the linked library; CheckAbi checks version and core layouts. +PodLayout exposes additional structure sizes for foreign-language bindings. +Use uint32_t for ABI booleans, not C/C++ bool; inputs must be 0 or 1. + +These references include `RAPIER_PARALLEL` and `RAPIER_FEM` declarations. +Enable the matching library features before using them. Robotics requires +`RAPIER_ROBOTICS`, 3D, and f32. Version returns the release string; +BuildInfo and BuildFeatures describe the binary actually loaded. + +@section units Coordinates and units +Positions and lengths use your chosen consistent length unit; time is seconds, +angles are radians. A 2D rotation is an angle; a 3D rotation is a quaternion in +x,y,z,w order, normalized on input. Zero-initialized 3D quaternions are invalid: +use description constructors or TranslationPose with a zero translation. +World-space coordinates are used unless a parameter or field says local/relative. +LengthUnit scales solver tolerances; it does not convert supplied geometry. +*/ + +/** @page ownership Ownership and lifetimes + +| Value | Ownership rule | +|---|---| +| World pointer | Owns inserted bodies, colliders, joints, and soft bodies. Release with FreeWorld. | +| Entity handle | Small borrowed value carrying its world pointer, index, and generation. Never free it. | +| Description/configuration | Copyable POD; initialize with its constructor/default function. No destructor. | +| Input array view | Borrows elements through the consuming build/insert call. Copying the view does not copy data. | +| SharedShape pointer | Owned reference-counted geometry wrapper. Release each wrapper with FreeSharedShape. | +| Controller/event collector/snapshot/mesh | Owned pointer with a matching Free function. | +| Callback context or MJCF visual pointer | Borrowed; never free or retain beyond its documented lifetime. | + +Constructors supply meaningful defaults; `{0}` is not a replacement for them. +Insertion borrows descriptions, copies their data, and retains shared geometry. +After insertion returns, input arrays and temporary shared-shape wrappers can be +released. Setters on descriptions may retain borrowed views until insertion. + +Removing an entity invalidates its handle. Generations detect stale entities only +while the owning world is alive: no API can safely validate a freed world pointer. +Destroying a world invalidates all its handles, including copies held elsewhere. +Use the explicit INVALID_*_HANDLE constants instead of assuming zero is invalid. +Opaque owned pointers must be released through their matching Rapier Free function, +never C `free`. Free functions accept NULL unless stated otherwise. + +@section threads Access and threading +Ordinary world reads may overlap. A mutation/step requires exclusive access; +conflicting or reentrant access reports WORLD_BUSY rather than waiting. +The caller must keep the world alive throughout every call and synchronize its +destruction with all users. Controllers, collectors, and other independently +owned objects require external synchronization when shared between threads. + +Callbacks may run on physics worker threads in parallel builds. Keep callback code +and user data alive until the operation returns. Use the callback's ReadContext +with Read* accessors; ordinary world access may report WORLD_BUSY during stepping. +Do not mutate the world from callbacks. Never unwind a C++ exception or longjmp +through Rust frames. Copy callback data if it is needed after return. +*/ + +/** @page api_errors Errors and returned values + +Status-returning calls report OK or an error code. Fallible value-returning calls +report failure through LastStatus and LastError on the calling thread. Check +LastStatus immediately after such a call: later fallible calls replace it. +Value constructors that only initialize POD data do not change the status. + +A failed value-returning call returns a default value (often zero, NULL, or an +invalid handle). That value alone does not distinguish success from failure. +LastError is a borrowed UTF-8 string valid until the next fallible call on that +thread; copy it before making another call. Reading LastStatus/LastError does not +clear them. + +SetErrorHandler installs a thread-local synchronous notification callback and +returns the previous handler. A callback that returns does not suppress the error. +Nested failures do not recursively invoke it. NOT_FOUND also invokes this handler; +use TryCastRay or CastRayToi if an ordinary ray miss should return OK with found = 0. + +Pointer checks cannot prove that memory is live, large enough, or owned by the +caller. Supply correctly aligned buffers and valid pointers for their documented +lifetimes. A caught PANIC is not transactional: discard objects mutated by that +call. Validation errors are not a general rollback guarantee either. +*/ + +/** @page output_buffers Arrays and output buffers + +Input views pair a pointer with an element count. NULL is allowed with count zero. +Counts for typed views are elements: TriangleView counts triangles, not scalar +indices. Description setters borrow the view; they need not validate geometry +until a later build/insert call. + +Functions accepting `buffer` and `capacity` and returning a size use this protocol: +1. Pass NULL and capacity zero to get the required element count (OK). +2. Allocate that many elements and call again to copy them. +3. If too small, the function returns the required count, reports BUFFER_TOO_SMALL, + and writes no elements. Resize and retry. + +Check LastStatus after both calls. Arrays may grow between calls if the application +mutates the source. Returned counts use the declared element type; some geometry +outputs are flattened scalar indices, explicitly documented by that function. +String-copy functions count bytes including the terminating NUL. ByteView is an +exception: it borrows snapshot storage directly until FreeBytes, with no copy. +*/ + +/** @page snapshots Snapshots +SerializeWorld returns owned Bytes; Bytes_Data borrows their byte array. +Release the owner with FreeBytes after copying or consuming it. +DeserializeWorld creates a new owned world. DeserializeRigidState is a separate +importer for legacy Rust testbed rigid-state snapshots. Only load trusted +snapshots produced by the identical Rapier build. Snapshots are not a durable interchange format. + +Old handles still point to the original world. Discard them and enumerate handles +from the restored world. Configure any thread pool and external callback/user +state again; application-owned state is not reconstructed automatically. +*/ + +/** @defgroup errors ABI and errors +Build identity, layout checks, status codes, and thread-local diagnostics. +See @ref api_errors for the return-value convention. +*/ +/** @defgroup worlds Worlds and stepping +Create a world, configure integration, step, and serialize it. +Setters change the named setting for subsequent operations. +*/ +/** @defgroup math Math and array types +Plain values and inline arithmetic. Constructors do not allocate. +*/ +/** @defgroup rigid_bodies Rigid bodies +Descriptions and live, world-bound handles. Accessors return copies. +Handle operations report INVALID_HANDLE for a removed/stale entity in a live world. +*/ +/** @defgroup colliders Colliders +Collider descriptions, material properties, sensor flags, and filtering. +A parented collider's description pose is relative to its rigid body. +*/ +/** @defgroup shapes Shapes and mesh data +Shared immutable geometry and copied rendering data. Owned pointers require their +matching Free functions; description views remain borrowed until build/insert. +*/ +/** @defgroup joints Joints +Impulse joints and reduced-coordinate articulations. Axis indices are translation +X/Y[/Z], then rotation Z in 2D or X/Y/Z in 3D. Masks use one bit per axis. +*/ +/** @defgroup queries Scene queries +Queries read the broad phase from the latest Step or DetectCollisions. After +insertion or manual pose changes, update collision detection before querying. +NULL QueryOptions uses native defaults. Predicates borrow their ReadContext. +*/ +/** @defgroup callbacks Callback read access +Read* functions resolve handles through the callback context. This context is +borrowed for that callback only and cannot mutate the world. +*/ +/** @defgroup events Events, contacts, and debug drawing +Event collectors own their recorded events. Enable event flags on colliders and +pass a collector when stepping. Copying events does not drain the collector; +Clear discards them. Contact-force events also honor the collider force threshold. +*/ +/** @defgroup soft_bodies Soft bodies +Particle recipes, materials, clusters, deformable meshes, and tearing. +Mesh indices and particle indices can change after tearing; use topology versions +and tear remapping events to maintain application data. FEM requires RAPIER_FEM. +*/ +/** @defgroup controllers Character, vehicle, and PID controllers +Controllers compute motion/corrections or apply forces; they do not replace Step. +Keep referenced worlds and bodies alive for controller operations. +*/ +/** @defgroup robotics URDF and MJCF import +Available only with RAPIER_ROBOTICS in 3D/f32. Loaded robots and insertion-result +containers are independently owned; freeing these containers does not remove the +world's inserted entities. Visual pointers borrow the loaded MJCF robot. +*/ +/** @defgroup cpp C++ ownership helpers +`rapier.hpp` supplies unique_ptr-based Owner aliases and small value factories. +Keep owners alive while using their handles. `check` throws on non-OK status; +callers with exceptions disabled should use the C API and status checks directly. +Deleters cannot report failures: destroy owners only when no operation is active. +*/ +/** @example falling_ball.c +A complete dimension/precision-independent simulation using the same source built +by the CMake example target. The RAPIER_* macros select concrete r2/r3 names. +*/ diff --git a/c/include/rapier.h b/c/include/rapier.h index 80b798b4f..0e5dfc89b 100644 --- a/c/include/rapier.h +++ b/c/include/rapier.h @@ -1,4 +1,5 @@ -/* Rapier C ABI. Select dimension and precision before including this file. +/** @file + * Rapier C ABI. Select dimension and precision before including this file. * Select one variant per translation unit; flags must match its linked library. * Read README.md for ownership, pointer lifetime, callbacks and threading. */ #if !defined(RAPIER_DIM2) && !defined(RAPIER_DIM3) @@ -14,25 +15,37 @@ #error Select exactly one Rapier precision #endif #if defined(RAPIER_DIM2) +/** Spatial dimension selected by this header. */ #define R2_DIMENSION 2 +/** Select an R2/R3 public type name. */ #define RAPIER_TYPE(name) R2##name +/** Select an R2/R3 public constant name. */ #define RAPIER_CONST(name) R2_##name +/** Select an r2/r3 public function name. */ #define RAPIER_FN(name) r2##name #else +/** Spatial dimension selected by this header. */ #define R3_DIMENSION 3 +/** Select an R2/R3 public type name. */ #define RAPIER_TYPE(name) R3##name +/** Select an R2/R3 public constant name. */ #define RAPIER_CONST(name) R3_##name +/** Select an r2/r3 public function name. */ #define RAPIER_FN(name) r3##name #endif #if defined(_WIN32) +/** C ABI calling convention. */ #define RAPIER_CALL __cdecl #else +/** C ABI calling convention. */ #define RAPIER_CALL #endif #ifndef RAPIER_API #if defined(_WIN32) && !defined(RAPIER_STATIC) +/** Shared-library import annotation. */ #define RAPIER_API __declspec(dllimport) #else +/** Shared-library import annotation. */ #define RAPIER_API #endif #endif @@ -52,346 +65,846 @@ #if defined(RAPIER_DIM2) #if defined(RAPIER_DIM2) +/** + * @ingroup joints + * Number of translational and angular joint axes in this dimension. + */ #define R2_JOINT_DOF_COUNT 3 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Number of translational and angular joint axes in this dimension. + */ #define R2_JOINT_DOF_COUNT 6 #endif +/** + * @ingroup soft_bodies + * Soft-body selector: desc particles. + */ #define R2_SOFT_DESC_PARTICLES 0 +/** + * @ingroup soft_bodies + * Soft-body selector: desc rope. + */ #define R2_SOFT_DESC_ROPE 1 +/** + * @ingroup soft_bodies + * Soft-body selector: desc grid. + */ #define R2_SOFT_DESC_GRID 2 +/** + * @ingroup soft_bodies + * Soft-body selector: desc cloth. + */ #define R2_SOFT_DESC_CLOTH 3 +/** + * @ingroup soft_bodies + * Soft-body selector: desc cuboid. + */ #define R2_SOFT_DESC_CUBOID 4 +/** + * @ingroup soft_bodies + * Soft-body selector: desc surface. + */ #define R2_SOFT_DESC_SURFACE 5 +/** + * @ingroup soft_bodies + * Soft-body selector: desc disk. + */ #define R2_SOFT_DESC_DISK 6 +/** + * @ingroup soft_bodies + * Soft-body selector: desc sphere. + */ #define R2_SOFT_DESC_SPHERE 7 +/** + * @ingroup soft_bodies + * Soft-body selector: desc cloth tube. + */ #define R2_SOFT_DESC_CLOTH_TUBE 8 +/** + * @ingroup soft_bodies + * Soft-body selector: desc volumetric. + */ #define R2_SOFT_DESC_VOLUMETRIC 9 +/** + * @ingroup soft_bodies + * Soft-body selector: binding skinned. + */ #define R2_SOFT_BINDING_SKINNED 0 +/** + * @ingroup soft_bodies + * Soft-body selector: binding direct. + */ #define R2_SOFT_BINDING_DIRECT 1 +/** + * @ingroup soft_bodies + * Soft-body selector: binding direct by position. + */ #define R2_SOFT_BINDING_DIRECT_BY_POSITION 2 #if defined(RAPIER_DIM2) +/** + * @ingroup shapes + * Treat the 2D polyline as oriented when generating contact normals. + */ #define R2_POLYLINE_ORIENTED 1 #endif +/** + * @ingroup shapes + * Prepare polyline acceleration data for deformation. + */ #define R2_POLYLINE_DEFORMABLE 2 +/** + * @ingroup shapes + * ShapeDesc kind selecting a ball. + */ #define R2_SHAPE_DESC_BALL 0 +/** + * @ingroup shapes + * ShapeDesc kind selecting a cuboid. + */ #define R2_SHAPE_DESC_CUBOID 1 +/** + * @ingroup shapes + * ShapeDesc kind selecting a round cuboid. + */ #define R2_SHAPE_DESC_ROUND_CUBOID 2 +/** + * @ingroup shapes + * ShapeDesc kind selecting a capsule. + */ #define R2_SHAPE_DESC_CAPSULE 3 +/** + * @ingroup shapes + * ShapeDesc kind selecting a segment. + */ #define R2_SHAPE_DESC_SEGMENT 4 +/** + * @ingroup shapes + * ShapeDesc kind selecting a triangle. + */ #define R2_SHAPE_DESC_TRIANGLE 5 +/** + * @ingroup shapes + * ShapeDesc kind selecting a half-space. + */ #define R2_SHAPE_DESC_HALFSPACE 6 +/** + * @ingroup shapes + * ShapeDesc kind selecting a convex hull. + */ #define R2_SHAPE_DESC_CONVEX_HULL 7 +/** + * @ingroup shapes + * ShapeDesc kind selecting a triangle mesh. + */ #define R2_SHAPE_DESC_TRIMESH 8 +/** + * @ingroup shapes + * ShapeDesc kind selecting a polyline. + */ #define R2_SHAPE_DESC_POLYLINE 9 +/** + * @ingroup shapes + * ShapeDesc kind selecting borrowed shared geometry. + */ #define R2_SHAPE_DESC_SHARED 10 +/** + * @ingroup shapes + * ShapeDesc kind selecting a heightfield. + */ #define R2_SHAPE_DESC_HEIGHTFIELD 11 +/** + * @ingroup shapes + * ShapeDesc kind selecting a cylinder. + */ #define R2_SHAPE_DESC_CYLINDER 12 +/** + * @ingroup shapes + * ShapeDesc kind selecting a cone. + */ #define R2_SHAPE_DESC_CONE 13 +/** + * @ingroup shapes + * ShapeDesc kind selecting compound child shapes. + */ #define R2_SHAPE_DESC_COMPOUND 14 +/** + * @ingroup shapes + * ShapeDesc kind selecting a round cylinder. + */ #define R2_SHAPE_DESC_ROUND_CYLINDER 15 +/** + * @ingroup colliders + * Mass density. + */ #define R2_MASS_DENSITY 0 +/** + * @ingroup colliders + * Mass total. + */ #define R2_MASS_TOTAL 1 +/** + * @ingroup colliders + * Mass properties. + */ #define R2_MASS_PROPERTIES 2 +/** + * @ingroup errors + * C binary ABI revision expected by this header. + */ #define R2_ABI_VERSION 1 +/** + * @ingroup rigid_bodies + * Dynamic body affected by forces and contacts. + */ #define R2_DYNAMIC 0 +/** + * @ingroup rigid_bodies + * Immovable body. + */ #define R2_FIXED 1 +/** + * @ingroup rigid_bodies + * Kinematic body controlled by its next pose. + */ #define R2_KINEMATIC_POSITION_BASED 2 +/** + * @ingroup rigid_bodies + * Kinematic body controlled by its velocity. + */ #define R2_KINEMATIC_VELOCITY_BASED 3 +/** + * @ingroup colliders + * Enable collision-start and collision-stop events for this collider. + */ #define R2_COLLISION_EVENTS 1 +/** + * @ingroup colliders + * Enable contact-force events for this collider, subject to its force threshold. + */ #define R2_CONTACT_FORCE_EVENTS 2 +/** + * @ingroup colliders + * Combine the two material coefficients by their arithmetic mean. + */ #define R2_COMBINE_AVERAGE 0 +/** + * @ingroup colliders + * Use the smaller of the two material coefficients. + */ #define R2_COMBINE_MIN 1 +/** + * @ingroup colliders + * Multiply the two material coefficients. + */ #define R2_COMBINE_MULTIPLY 2 +/** + * @ingroup colliders + * Use the larger of the two material coefficients. + */ #define R2_COMBINE_MAX 3 +/** + * @ingroup colliders + * Invoke the contact-pair filtering hook for this collider. + */ #define R2_FILTER_CONTACT_PAIRS 1 +/** + * @ingroup colliders + * Invoke the sensor-intersection filtering hook for this collider. + */ #define R2_FILTER_INTERSECTION_PAIR 2 +/** + * @ingroup colliders + * Invoke the solver-contact modification hook for this collider. + */ #define R2_MODIFY_SOLVER_CONTACTS 4 +/** + * @ingroup queries + * Query filter: exclude fixed. + */ #define R2_QUERY_EXCLUDE_FIXED 1 +/** + * @ingroup queries + * Query filter: exclude kinematic. + */ #define R2_QUERY_EXCLUDE_KINEMATIC 2 +/** + * @ingroup queries + * Query filter: exclude dynamic. + */ #define R2_QUERY_EXCLUDE_DYNAMIC 4 +/** + * @ingroup queries + * Query filter: exclude sensors. + */ #define R2_QUERY_EXCLUDE_SENSORS 8 +/** + * @ingroup queries + * Query filter: exclude solids. + */ #define R2_QUERY_EXCLUDE_SOLIDS 16 +/** + * @ingroup queries + * Query filter: only dynamic. + */ #define R2_QUERY_ONLY_DYNAMIC 3 +/** + * @ingroup queries + * Query filter: only kinematic. + */ #define R2_QUERY_ONLY_KINEMATIC 5 +/** + * @ingroup queries + * Query filter: only fixed. + */ #define R2_QUERY_ONLY_FIXED 6 +/** + * @ingroup events + * Debug-render flag: collider shapes. + */ #define R2_DEBUG_COLLIDER_SHAPES 1 +/** + * @ingroup events + * Debug-render flag: rigid body axes. + */ #define R2_DEBUG_RIGID_BODY_AXES 2 +/** + * @ingroup events + * Debug-render flag: multibody joints. + */ #define R2_DEBUG_MULTIBODY_JOINTS 4 +/** + * @ingroup events + * Debug-render flag: impulse joints. + */ #define R2_DEBUG_IMPULSE_JOINTS 8 +/** + * @ingroup events + * Debug-render flag: solver contacts. + */ #define R2_DEBUG_SOLVER_CONTACTS 16 +/** + * @ingroup events + * Debug-render flag: contacts. + */ #define R2_DEBUG_CONTACTS 32 +/** + * @ingroup events + * Debug-render flag: collider aabbs. + */ #define R2_DEBUG_COLLIDER_AABBS 64 +/** + * @ingroup events + * Debug-render flag: soft bodies. + */ #define R2_DEBUG_SOFT_BODIES 128 +/** + * @ingroup events + * Debug-render flag: pseudo normals. + */ #define R2_DEBUG_PSEUDO_NORMALS 256 +/** + * @ingroup events + * Debug-render flag: soft volume contacts. + */ #define R2_DEBUG_SOFT_VOLUME_CONTACTS 512 +/** + * @ingroup events + * Debug-render flag: soft body stress. + */ #define R2_DEBUG_SOFT_BODY_STRESS 1024 +/** + * @ingroup colliders + * Require both membership/filter intersections to be nonempty. + */ #define R2_GROUPS_AND 0 +/** + * @ingroup colliders + * Accept either membership/filter intersection if both participants select OR; otherwise use AND. + */ #define R2_GROUPS_OR 1 +/** + * @ingroup joints + * Motor stiffness and damping are acceleration-based, independent of mass. + */ #define R2_MOTOR_ACCELERATION_BASED 0 +/** + * @ingroup joints + * Motor stiffness and damping are force-based, so response depends on mass. + */ #define R2_MOTOR_FORCE_BASED 1 +/** + * @ingroup soft_bodies + * Cell model that constrains volume without elastic shear response. + */ #define R2_SOFT_CELL_VOLUME 0 +/** + * @ingroup soft_bodies + * Corotational elastic cell model. + */ #define R2_SOFT_CELL_COROTATIONAL 1 +/** + * @ingroup soft_bodies + * Neo-Hookean elastic cell model. + */ #define R2_SOFT_CELL_NEO_HOOKEAN 2 +/** + * @ingroup soft_bodies + * Use the constraint-based soft-body solver. + */ #define R2_SOFT_SOLVER_CONSTRAINTS 0 +/** + * @ingroup soft_bodies + * Use the finite-element solver; requires RAPIER_FEM. + */ #define R2_SOFT_SOLVER_FEM 1 +/** + * @ingroup joints + * Joint axis index for translation along local X. + */ #define R2_AXIS_LIN_X 0 +/** + * @ingroup joints + * Joint axis index for translation along local Y. + */ #define R2_AXIS_LIN_Y 1 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: translation x. + */ #define R2_LOCK_TRANSLATION_X 1 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: translation y. + */ #define R2_LOCK_TRANSLATION_Y 2 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: translation z. + */ #define R2_LOCK_TRANSLATION_Z 4 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: rotation x. + */ #define R2_LOCK_ROTATION_X 8 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: rotation y. + */ #define R2_LOCK_ROTATION_Y 16 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: rotation z. + */ #define R2_LOCK_ROTATION_Z 32 #if defined(RAPIER_DIM2) +/** + * @ingroup joints + * Joint axis index for the first rotation: Z in 2D, local X in 3D. + */ #define R2_AXIS_ANG_X 2 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Joint axis index for the first rotation: Z in 2D, local X in 3D. + */ #define R2_AXIS_ANG_X 3 #endif #if defined(RAPIER_DIM2) +/** + * @ingroup joints + * Locked-axis mask for a fixed joint: all translations and rotations. + */ #define R2_JOINT_FIXED_AXES 7 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Locked-axis mask for a fixed joint: all translations and rotations. + */ #define R2_JOINT_FIXED_AXES 63 #endif #if defined(RAPIER_DIM2) +/** + * @ingroup joints + * Locked-axis mask for a revolute joint: only the first angular axis is free. + */ #define R2_JOINT_REVOLUTE_AXES 3 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Locked-axis mask for a revolute joint: only the first angular axis is free. + */ #define R2_JOINT_REVOLUTE_AXES 55 #endif #if defined(RAPIER_DIM2) +/** + * @ingroup joints + * Locked-axis mask for a prismatic joint: only translation along local X is free. + */ #define R2_JOINT_PRISMATIC_AXES 6 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Locked-axis mask for a prismatic joint: only translation along local X is free. + */ #define R2_JOINT_PRISMATIC_AXES 62 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Joint axis index for translation along local Z. + */ #define R2_AXIS_LIN_Z 2 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Joint axis index for rotation around local Y. + */ #define R2_AXIS_ANG_Y 4 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Joint axis index for rotation around local Z. + */ #define R2_AXIS_ANG_Z 5 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Locked-axis mask for a spherical joint: translations locked, rotations free. + */ #define R2_JOINT_SPHERICAL_AXES 7 #endif /** - * Read-only body type used by soft-body cluster proxies; not valid for builder_new. + * @ingroup soft_bodies + * Read-only body type of soft-body cluster proxies; cannot be used to construct a rigid body. */ #define R2_SOFT_FRAME 4 +/** + * @ingroup queries + * Shape-cast iteration limit reached before convergence. + */ #define R2_SHAPE_CAST_OUT_OF_ITERATIONS 0 +/** + * @ingroup queries + * Shape cast converged to the reported impact. + */ #define R2_SHAPE_CAST_CONVERGED 1 +/** + * @ingroup queries + * Shape-cast numerical solver failed to converge. + */ #define R2_SHAPE_CAST_FAILED 2 +/** + * @ingroup queries + * Shapes overlap or are within the target distance at the start of the cast. + */ #define R2_SHAPE_CAST_PENETRATING 3 +/** + * @ingroup queries + * The hit feature is unspecified. + */ #define R2_FEATURE_UNKNOWN 0 +/** + * @ingroup queries + * The feature ID denotes a vertex. + */ #define R2_FEATURE_VERTEX 1 +/** + * @ingroup queries + * The feature ID denotes an edge. + */ #define R2_FEATURE_EDGE 2 +/** + * @ingroup queries + * The feature ID denotes a face. + */ #define R2_FEATURE_FACE 3 /** - * Native URDF/MJCF multibody insertion flags. + * @ingroup joints + * Insert reduced-coordinate joints as kinematic articulations. */ #define R2_MULTIBODY_JOINTS_ARE_KINEMATIC 1 +/** + * @ingroup joints + * Disable contacts between colliders of the inserted articulation. + */ #define R2_MULTIBODY_DISABLE_SELF_CONTACTS 2 +/** + * @ingroup joints + * Skip joints that would close a loop in the articulation. + */ #define R2_MULTIBODY_SKIP_LOOP_CLOSURES 4 +/** + * @ingroup joints + * Do not import joint motors into the articulation. + */ #define R2_MULTIBODY_SKIP_JOINT_MOTORS 8 +/** + * @ingroup joints + * Do not import joint limits into the articulation. + */ #define R2_MULTIBODY_SKIP_JOINT_LIMITS 16 +/** + * @ingroup joints + * Do not import joint springs into the articulation. + */ #define R2_MULTIBODY_SKIP_JOINT_SPRINGS 32 /** - * Parry triangle-mesh and heightfield flags used by the public shape constructors. + * @ingroup shapes + * Merge triangle-mesh vertices with identical positions. */ #define R2_TRIMESH_MERGE_DUPLICATE_VERTICES 16 +/** + * @ingroup shapes + * Correct contact normals at internal mesh edges; includes duplicate-vertex merging. + */ #define R2_TRIMESH_FIX_INTERNAL_EDGES 144 +/** + * @ingroup shapes + * Prepare triangle-mesh acceleration data for deformation. + */ #define R2_TRIMESH_DEFORMABLE 256 +/** + * @ingroup shapes + * Correct internal-edge contacts on both sides of the triangle mesh. + */ #define R2_TRIMESH_FIX_INTERNAL_EDGES_TWO_SIDED 656 +/** + * @ingroup shapes + * Correct contact normals at internal heightfield edges. + */ #define R2_HEIGHTFIELD_FIX_INTERNAL_EDGES 1 /** * Immutable owned byte buffer. Release with the matching FreeBytes function. + * @ingroup worlds */ typedef struct R2Bytes R2Bytes; /** * Borrowed native contact context. Valid only during its callback; never retain or free it. + * @ingroup events */ typedef struct R2ContactModificationContext R2ContactModificationContext; #if defined(RAPIER_DIM3) +/** + * Vehicle controller borrowing its chassis world. Release with the matching Free function. + * @ingroup controllers + */ typedef struct R2DynamicRayCastVehicleController R2DynamicRayCastVehicleController; #endif /** - * Events accumulate until clear. Copying events never drains them, allowing two-call buffer sizing. + * Events accumulate until clear. Copying events never drains them, allowing two-call buffer + * sizing. + * @ingroup events */ typedef struct R2EventCollector R2EventCollector; /** * Controller plus reusable collision output from the last move_shape call. + * Kinematic character controller and its last collision list. Release with the matching Free + * function. + * @ingroup controllers */ typedef struct R2KinematicCharacterController R2KinematicCharacterController; #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Loaded MJCF robot and its visual/keyframe data. Release with the matching Free function. + * @ingroup robotics + */ typedef struct R2MjcfRobot R2MjcfRobot; #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Owned container of borrowed handles and actuators of an inserted MJCF robot. Release with the + * matching Free function. + * @ingroup robotics + */ typedef struct R2MjcfRobotHandles R2MjcfRobotHandles; #endif /** * PID controller with persistent integral state. + * Stateful proportional-integral-derivative controller. Release with the matching Free function. + * @ingroup controllers */ typedef struct R2PidController R2PidController; /** * Callback-scoped read access to bodies and colliders. Never retain or free it. * Only the Read* functions accept this context; it cannot mutate the world. + * @ingroup callbacks */ typedef struct R2ReadContext R2ReadContext; /** * Owned tessellated shape: flat triangle vertices and independent line segments, in local space. - * Rounded 3D shapes use their inner surface (as in the Rust testbed). Halfspaces use a finite patch. + * Rounded 3D shapes use their inner surface (as in the Rust testbed). Halfspaces use a finite + * patch. + * @ingroup shapes */ typedef struct R2ShapeMesh R2ShapeMesh; /** * Owned copy of a tear event. Read particle remapping before rebuilding render meshes. + * @ingroup events */ typedef struct R2SoftBodyTearEvent R2SoftBodyTearEvent; #if defined(RAPIER_DIM3) /** * Owned indexed geometry from Parry's shape tessellation, preserving its vertex order. + * @ingroup shapes */ typedef struct R2TriMeshData R2TriMeshData; #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Loaded URDF robot; insertion clones its simulation objects. Release with the matching Free + * function. + * @ingroup robotics + */ typedef struct R2UrdfRobot R2UrdfRobot; #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Owned container of borrowed handles to an inserted URDF robot. Release with the matching Free + * function. + * @ingroup robotics + */ typedef struct R2UrdfRobotHandles R2UrdfRobotHandles; #endif @@ -399,224 +912,621 @@ typedef struct R2UrdfRobotHandles R2UrdfRobotHandles; * Sole owner of simulation state. Handles belong to the world that created them. * Ordinary reads may overlap. A mutation or step requires exclusive access. * Destruction must be externally synchronized with all users of this pointer. + * @ingroup worlds */ typedef struct R2World R2World; #if defined(RAPIER_F32) +/** + * Floating-point scalar: float for f32, double for f64. + * @ingroup math + */ typedef float R2Real; #endif #if defined(RAPIER_F64) +/** + * Floating-point scalar: float for f32, double for f64. + * @ingroup math + */ typedef double R2Real; #endif +/** + * Spring softness expressed as natural frequency and damping ratio. + * @ingroup math + */ typedef struct R2SpringCoefficients { + /** + * Nonnegative spring natural frequency in Hz. + */ R2Real natural_frequency; + /** + * Nonnegative damping ratio; 1 is critical damping. + */ R2Real damping_ratio; } R2SpringCoefficients; /** * ABI booleans are uint32_t: zero is false, one is true. + * @ingroup math */ typedef uint32_t R2Bool; +/** + * Optional scalar override; enabled = 0 selects no override. + * @ingroup math + */ typedef struct R2OptionalReal { + /** + * Whether this setting/object is enabled (0 or 1). + */ R2Bool enabled; + /** + * Value used when enabled is 1. + */ R2Real value; } R2OptionalReal; +/** + * Optional unsigned integer override; enabled = 0 selects no override. + * @ingroup math + */ typedef struct R2OptionalU32 { + /** + * Whether this setting/object is enabled (0 or 1). + */ R2Bool enabled; + /** + * Value used when enabled is 1. + */ uint32_t value; } R2OptionalU32; /** * Plain configuration data; initialize defaults, edit, then apply. No destructor. + * @ingroup soft_bodies */ typedef struct R2SoftBodyMaterial { + /** + * Spring coefficients for structural edge constraints. + */ struct R2SpringCoefficients edgeSoftness; + /** + * Spring coefficients for bending edges and dihedrals. + */ struct R2SpringCoefficients bendSoftness; + /** + * Spring coefficients for cell and global volume constraints. + */ struct R2SpringCoefficients volumeSoftness; + /** + * Spring coefficients for shape-matching constraints. + */ struct R2SpringCoefficients shapeMatchingSoftness; + /** + * Elastic modulus: force per area in 3D, force per length in 2D; nonnegative. + */ R2Real youngModulus; + /** + * Poisson ratio for elastic cells, in [0, 0.5). + */ R2Real poissonRatio; + /** + * Nonnegative damping ratio of elastic cells. + */ R2Real elasticDampingRatio; + /** + * Cell strain threshold for plastic flow; zero disables plasticity. + */ R2Real plasticYield; + /** + * Nonnegative rate per second at which excess cell strain becomes permanent. + */ R2Real plasticCreep; + /** + * Maximum accumulated cell plastic stretch, measured by the norm of P - I. + */ R2Real plasticMax; + /** + * Rate per second pulling particle velocities toward best-fit rigid motion; zero disables it. + */ R2Real deformationDamping; + /** + * Edge strain threshold for plastic flow; zero disables plasticity. + */ R2Real edgePlasticYield; + /** + * Nonnegative rate per second at which excess edge strain becomes permanent. + */ R2Real edgePlasticCreep; + /** + * Maximum permanent edge-length change as a fraction of its initial length. + */ R2Real edgePlasticMax; + /** + * Plastic flow direction: 0 both, 1 compression only, 2 tension only. + */ uint32_t edgePlasticFlow; + /** + * Optional strain threshold for tearing; disabled means no strain-based tearing. + */ struct R2OptionalReal tearStrain; + /** + * Optional tensile edge-force threshold for tearing. + */ struct R2OptionalReal tearForce; + /** + * Exponential load-smoothing time constant in seconds; zero disables smoothing. + */ R2Real tearSmoothing; + /** + * Tear-threshold multiplier for undamaged interior elements. + */ R2Real interiorStrength; + /** + * Maximum ordinary edge tears per step; edges above twice their threshold bypass the limit. + */ uint32_t maxTearsPerStep; + /** + * Optional minimum particle count of tear pieces. + */ struct R2OptionalU32 minPiece; } R2SoftBodyMaterial; /** * Plain configuration data; initialize defaults, edit, then apply. No destructor. + * @ingroup soft_bodies */ typedef struct R2SoftRecoverySettings { + /** + * Expand speculative contact margins to cover particle velocities set between steps. + */ R2Bool authoredVelocityMargin; + /** + * Enable speculative edge-edge collision constraints. + */ R2Bool edgeSpeculation; + /** + * Detect inverted cells to support self-contact recovery. + */ R2Bool invertedCellDetection; + /** + * Detect surface self-crossings each step. + */ R2Bool selfCrossingDetection; + /** + * Skip self-crossing detection when accumulated motion cannot have created a crossing. + */ R2Bool detectionMotionGating; + /** + * Detect boundary crossings between soft bodies. + */ R2Bool crossBodyDetection; + /** + * Disable contacts on tangled features so elasticity can untangle them. + */ R2Bool selfStandDown; + /** + * Allow contacts at cross-body crossings to expel, but not hold, the intruder. + */ R2Bool crossBodyExpelGate; + /** + * Disable edge constraints touching cross-body crossings. + */ R2Bool edgeStandDown; + /** + * Repel crossing features toward their neighborhood's side of the surface. + */ R2Bool crossingRepulsion; + /** + * Guide cross-body repulsion by overlap-volume normals; closed meshes only. + */ R2Bool crossingRepulsionGuide; + /** + * Guide self-crossing repulsion by self-intersection-volume normals; closed meshes only. + */ R2Bool crossingRepulsionSelfGuide; + /** + * Maximum recovery rate in length units per second, scaled by lengthUnit. + */ R2Real recoveryPace; + /** + * Enable intersection-volume constraints for overlapping closed surfaces. + */ R2Bool overlapConstraints; + /** + * Enable intersection-volume constraints against rigid colliders. + */ R2Bool overlapRigid; + /** + * Skip pair overlap constraints for self-crossed meshes. + */ R2Bool overlapSkipSelfTangled; + /** + * Disable 3D closed-surface edge constraints where overlap constraints take over. + */ R2Bool overlapEdgeStandDown; + /** + * Velocity-change limit per step, as a multiple of recoveryPace. + */ R2Real overlapConstraintPace; + /** + * Per-point constraints inside overlap patches: 0 keep, 1 stand down, 2 align with overlap + * normal. + */ uint32_t overlapPatchConstraints; + /** + * Measure overlap on contact-skin surfaces rather than bare geometry. + */ R2Bool overlapSkinVolume; + /** + * Overlap depth retained by recovery, as a fraction of the pair's contact skins. + */ R2Real overlapKeptDepth; + /** + * Enable recovery of self-intersection regions. + */ R2Bool overlapSelfRegions; + /** + * Use the overlap normal for recovery pushes. + */ R2Bool overlapNormalPush; + /** + * Use spatially split overlap-volume constraints. + */ R2Bool overlapMultiVolume; + /** + * Cells per tangent axis of the multi-volume grid. + */ uint32_t overlapSplit; + /** + * Recovery progress patience in steps. + */ uint32_t overlapPatience; + /** + * Relative overlap-volume decrease that counts as recovery progress. + */ R2Real overlapProgressMargin; } R2SoftRecoverySettings; #if defined(RAPIER_FEM) /** * Plain configuration data; initialize defaults, edit, then apply. No destructor. + * @ingroup soft_bodies */ typedef struct R2SoftFemParameters { + /** + * FEM iterative linear-solver tolerance. + */ R2Real linearTolerance; + /** + * Maximum FEM linear-solver iterations. + */ size_t maxLinearIterations; + /** + * Maximum degrees of freedom solved by the dense FEM solver. + */ size_t maxDenseDofs; } R2SoftFemParameters; #endif /** * Plain configuration data; initialize defaults, edit, then apply. No destructor. + * @ingroup soft_bodies */ typedef struct R2SoftBodiesSettings { + /** + * Soft-body crossing detection and recovery settings. + */ struct R2SoftRecoverySettings recovery; + /** + * Strain threshold for re-solving soft constraints after contacts within a substep. + */ R2Real resweepStrain; + /** + * Maximum additional substeps requested by soft-body motion. + */ size_t maxExtraSubsteps; + /** + * Multiplier on contact natural frequency for soft-body contacts. + */ R2Real contactStiffening; #if defined(RAPIER_FEM) + /** + * FEM linear-solver settings, present only when RAPIER_FEM is enabled. + */ struct R2SoftFemParameters fem; #endif } R2SoftBodiesSettings; /** * Plain configuration data; initialize defaults, edit, then apply. No destructor. + * @ingroup worlds */ typedef struct R2IntegrationParameters { + /** + * Simulation step duration in seconds. + */ R2Real dt; + /** + * Minimum CCD substep duration in seconds. + */ R2Real minCcdDt; + /** + * Spring coefficients for dynamic contact constraints. + */ struct R2SpringCoefficients contactSoftness; + /** + * Spring coefficients for contacts against fixed bodies. + */ struct R2SpringCoefficients staticContactSoftness; + /** + * Scale applied to cached impulses when warmstarting. + */ R2Real warmstartCoefficient; + /** + * Typical world-space length of one meter; scales solver tolerances, not geometry. + */ R2Real lengthUnit; + /** + * Soft-body integration and recovery settings. + */ struct R2SoftBodiesSettings softBodies; + /** + * Allowed penetration divided by lengthUnit. + */ R2Real normalizedAllowedLinearError; + /** + * Maximum penetration-correction speed divided by lengthUnit. + */ R2Real normalizedMaxCorrectiveVelocity; + /** + * Speculative-contact distance divided by lengthUnit. + */ R2Real normalizedPredictionDistance; + /** + * Maximum linear speed divided by lengthUnit. + */ R2Real normalizedMaxLinearVelocity; + /** + * Number of solver substeps/iterations; must be positive. + */ size_t numSolverIterations; + /** + * PGS iterations per solver substep. + */ size_t numInternalPgsIterations; + /** + * Stabilization iterations after velocity solving. + */ size_t numInternalStabilizationIterations; + /** + * Maximum CCD substeps; 0 disables all CCD for the world. + */ size_t maxCcdSubsteps; + /** + * Whether to cluster contacts for solving. + */ R2Bool contactClustering; + /** + * Whether to reuse nearby contacts between steps. + */ R2Bool contactRecycling; + /** + * Contact recycling distance divided by lengthUnit. + */ R2Real normalizedContactRecycleDistance; + /** + * Whether to solve friction in the bias pass. + */ R2Bool frictionInBiasPass; + /** + * Whether to warmstart joint constraints. + */ R2Bool warmstartJoints; #if defined(RAPIER_DIM3) + /** + * Friction model: 0 simplified, 1 Coulomb (3D only). + */ uint32_t frictionModel; #endif } R2IntegrationParameters; /** * Status-returning operations use these integer codes. + * @ingroup errors */ typedef uint32_t R2Status; +/** + * Cartesian vector with two or three components. + * @ingroup math + */ typedef struct R2Vector { + /** + * X component. + */ R2Real x; + /** + * Y component. + */ R2Real y; #if defined(RAPIER_DIM3) + /** + * Z component. + */ R2Real z; #endif } R2Vector; /** * 2D: angle in radians. 3D: unit quaternion in x,y,z,w order (normalized on input). + * @ingroup math */ typedef struct R2Rotation { #if defined(RAPIER_DIM2) + /** + * Rotation angle in radians. + */ R2Real angle; #endif #if defined(RAPIER_DIM3) + /** + * X component. + */ R2Real x; #endif #if defined(RAPIER_DIM3) + /** + * Y component. + */ R2Real y; #endif #if defined(RAPIER_DIM3) + /** + * Z component. + */ R2Real z; #endif #if defined(RAPIER_DIM3) + /** + * Quaternion scalar component. + */ R2Real w; #endif } R2Rotation; +/** + * Rigid transform combining translation and rotation. Use TranslationPose with a zero vector for + * identity. + * @ingroup math + */ typedef struct R2Pose { + /** + * Translation vector. + */ struct R2Vector translation; + /** + * Rotation value. + */ struct R2Rotation rotation; } R2Pose; +/** + * Lower and upper axis limits in length units or radians. + * @ingroup joints + */ typedef struct R2JointLimits { + /** + * Minimum allowed axis displacement (length or radians). + */ R2Real min; + /** + * Maximum allowed axis displacement (length or radians). + */ R2Real max; } R2JointLimits; +/** + * Position/velocity motor settings for one joint axis. + * @ingroup joints + */ typedef struct R2JointMotor { + /** + * Motor target velocity (length per second or radians per second). + */ R2Real targetVel; + /** + * Motor target position (length or radians). + */ R2Real targetPos; + /** + * Nonnegative motor spring stiffness. + */ R2Real stiffness; + /** + * Nonnegative motor damping. + */ R2Real damping; + /** + * Nonnegative maximum force or torque. + */ R2Real maxForce; + /** + * Motor model: 0 acceleration-based, 1 force-based. + */ uint32_t model; } R2JointMotor; +/** + * Application-defined 128-bit value. Contains no owned pointers. + * @ingroup math + */ typedef struct R2UserData { + /** + * Low 64 bits of the application value. + */ uint64_t low; + /** + * High 64 bits of the application value. + */ uint64_t high; } R2UserData; /** * Copyable joint configuration. Limits/motors take effect when their axis mask is enabled. * Solver impulses are deliberately excluded. Applying data resets cached limit and motor impulses. + * @ingroup joints */ typedef struct R2JointDesc { + /** + * Joint frame in body 1 local coordinates. + */ struct R2Pose localFrame1; + /** + * Joint frame in body 2 local coordinates. + */ struct R2Pose localFrame2; + /** + * Locked joint degrees of freedom; translations precede rotations. + */ uint8_t lockedAxes; + /** + * Axis mask enabling corresponding limits entries. + */ uint8_t limitAxes; + /** + * Axis mask enabling corresponding motors entries. + */ uint8_t motorAxes; + /** + * Axis mask sharing a coupled constraint. + */ uint8_t coupledAxes; + /** + * Axis limits in translation-then-rotation order. + */ struct R2JointLimits limits[R2_JOINT_DOF_COUNT]; + /** + * Axis motors in translation-then-rotation order. + */ struct R2JointMotor motors[R2_JOINT_DOF_COUNT]; + /** + * Spring coefficients for constraint correction. + */ struct R2SpringCoefficients softness; + /** + * Whether connected bodies may collide. + */ R2Bool contactsEnabled; + /** + * Whether this setting/object is enabled (0 or 1). + */ R2Bool enabled; + /** + * Application data; Rapier does not own pointers encoded in it. + */ struct R2UserData userData; } R2JointDesc; @@ -624,13 +1534,20 @@ typedef struct R2JointDesc { * Copyable non-owning handle: world pointer plus entity index and generation. * The world must remain alive throughout every use. Copying does not retain it. * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + * @ingroup joints */ typedef struct R2ImpulseJointHandle { /** * Borrowed owning world. Never use this handle after freeing that world. */ struct R2World *world; + /** + * Zero-based element index. + */ uint32_t index; + /** + * Slot generation used to reject stale handles. Do not modify it. + */ uint32_t generation; } R2ImpulseJointHandle; @@ -638,13 +1555,20 @@ typedef struct R2ImpulseJointHandle { * Copyable non-owning handle: world pointer plus entity index and generation. * The world must remain alive throughout every use. Copying does not retain it. * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + * @ingroup rigid_bodies */ typedef struct R2RigidBodyHandle { /** * Borrowed owning world. Never use this handle after freeing that world. */ struct R2World *world; + /** + * Slot index; UINT32_MAX denotes the explicit invalid handle. + */ uint32_t index; + /** + * Slot generation used to reject stale handles. Do not modify it. + */ uint32_t generation; } R2RigidBodyHandle; @@ -652,213 +1576,396 @@ typedef struct R2RigidBodyHandle { * Copyable non-owning handle: world pointer plus entity index and generation. * The world must remain alive throughout every use. Copying does not retain it. * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + * @ingroup joints */ typedef struct R2MultibodyJointHandle { /** * Borrowed owning world. Never use this handle after freeing that world. */ struct R2World *world; + /** + * Zero-based element index. + */ uint32_t index; + /** + * Slot generation used to reject stale handles. Do not modify it. + */ uint32_t generation; } R2MultibodyJointHandle; /** * Parameters for the native volumetric mesher. Enclosure: 0 cover, 1 crust (3D). + * @ingroup shapes */ typedef struct R2VolumeMeshParameters { + /** + * Positive target cell size for volume meshing. + */ R2Real cell_size; #if defined(RAPIER_DIM2) + /** + * Minimum target element angle in radians; above pi/6, refinement may stop early. + */ R2Real min_angle; #endif #if defined(RAPIER_DIM3) + /** + * Enclosure strategy: 0 covers the whole shape, 1 encloses only its surface. + */ uint32_t enclosure; #endif #if defined(RAPIER_DIM3) + /** + * Number of cover-smoothing passes. + */ uint32_t cover_smoothing; #endif #if defined(RAPIER_DIM3) + /** + * Minimum smoothed-cover distance from the shape, as a fraction of local cell size. + */ R2Real cover_guard; #endif #if defined(RAPIER_DIM3) + /** + * Maximum boundary-cell halvings below cell_size before cover smoothing. + */ uint32_t cover_subdivisions; #endif } R2VolumeMeshParameters; /** - * Borrowed array of vector elements. count always counts elements, not scalars. + * Borrowed array of vector elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R2VectorView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R2Vector *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R2VectorView; /** - * Borrowed array of real elements. count always counts elements, not scalars. + * Borrowed array of real elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R2RealView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const R2Real *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R2RealView; /** - * Borrowed array of index elements. count always counts elements, not scalars. + * Borrowed array of index elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R2IndexView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const uint32_t *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R2IndexView; /** * Vertex indices for one edge; contiguous u32 fields with no padding. + * @ingroup math */ typedef struct R2Edge { + /** + * First vertex index. + */ uint32_t a; + /** + * Second vertex index. + */ uint32_t b; } R2Edge; /** - * Borrowed array of edge elements. count always counts elements, not scalars. + * Borrowed array of edge elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R2EdgeView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R2Edge *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R2EdgeView; +/** + * Spring-coefficient override for one soft-body edge. + * @ingroup soft_bodies + */ typedef struct R2SoftEdgeSoftness { + /** + * Zero-based edge index. + */ uint32_t edge; + /** + * Spring coefficients for constraint correction. + */ struct R2SpringCoefficients softness; } R2SoftEdgeSoftness; /** * Borrowed elements; count counts elements. Data must remain live through insertion. + * @ingroup soft_bodies */ typedef struct R2SoftEdgeSoftnessView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R2SoftEdgeSoftness *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R2SoftEdgeSoftnessView; +/** + * Tear-resistance override for one soft-body edge. + * @ingroup soft_bodies + */ typedef struct R2SoftEdgeTear { + /** + * Zero-based edge index. + */ uint32_t edge; + /** + * Nonnegative edge tear-resistance multiplier. + */ R2Real resistance; } R2SoftEdgeTear; /** * Borrowed elements; count counts elements. Data must remain live through insertion. + * @ingroup soft_bodies */ typedef struct R2SoftEdgeTearView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R2SoftEdgeTear *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R2SoftEdgeTearView; /** * Vertex indices for one triangle; contiguous u32 fields with no padding. + * @ingroup math */ typedef struct R2Triangle { + /** + * First vertex index. + */ uint32_t a; + /** + * Second vertex index. + */ uint32_t b; + /** + * Third vertex index. + */ uint32_t c; } R2Triangle; /** - * Borrowed array of triangle elements. count always counts elements, not scalars. + * Borrowed array of triangle elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R2TriangleView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R2Triangle *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R2TriangleView; /** * Vertex indices for one tetrahedron; contiguous u32 fields with no padding. + * @ingroup math */ typedef struct R2Tetrahedron { + /** + * First vertex index. + */ uint32_t a; + /** + * Second vertex index. + */ uint32_t b; + /** + * Third vertex index. + */ uint32_t c; + /** + * Fourth vertex index. + */ uint32_t d; } R2Tetrahedron; /** - * Borrowed array of tetrahedron elements. count always counts elements, not scalars. + * Borrowed array of tetrahedron elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R2TetrahedronView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R2Tetrahedron *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R2TetrahedronView; #if defined(RAPIER_DIM2) +/** + * Borrowed cell array: triangles in 2D, tetrahedra in 3D. + * @ingroup math + */ typedef struct R2TriangleView R2CellView; #endif #if defined(RAPIER_DIM3) +/** + * Borrowed cell array: triangles in 2D, tetrahedra in 3D. + * @ingroup math + */ typedef struct R2TetrahedronView R2CellView; #endif #if defined(RAPIER_DIM2) +/** + * Borrowed surface array: edges in 2D, triangles in 3D. + * @ingroup math + */ typedef struct R2EdgeView R2SurfaceElementView; #endif #if defined(RAPIER_DIM3) +/** + * Borrowed surface array: edges in 2D, triangles in 3D. + * @ingroup math + */ typedef struct R2TriangleView R2SurfaceElementView; #endif /** * Vertex indices for one dihedral; contiguous u32 fields with no padding. + * @ingroup math */ typedef struct R2Dihedral { + /** + * First vertex index. + */ uint32_t a; + /** + * Second vertex index. + */ uint32_t b; + /** + * Third vertex index. + */ uint32_t c; + /** + * Fourth vertex index. + */ uint32_t d; } R2Dihedral; /** - * Borrowed array of dihedral elements. count always counts elements, not scalars. + * Borrowed array of dihedral elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R2DihedralView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R2Dihedral *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R2DihedralView; /** * Optional boolean override. When disabled, retain the recipe's native default. + * @ingroup math */ typedef struct R2OptionalBool { + /** + * Whether this setting/object is enabled (0 or 1). + */ R2Bool enabled; + /** + * Value used when enabled is 1. + */ R2Bool value; } R2OptionalBool; /** * Opaque SharedShape. See the ownership and borrowing contract in README.md. + * @ingroup shapes */ typedef struct R2SharedShape R2SharedShape; /** * Borrowed elements; count counts elements. Data must remain live through insertion. + * @ingroup shapes */ typedef struct R2CompoundShapeView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R2CompoundShapeDesc *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R2CompoundShapeView; @@ -868,82 +1975,240 @@ typedef struct R2CompoundShapeView { * b/c = remaining endpoints/vertices. radius is also the rounded-cuboid border radius. * Mesh views count edges or triangles; heightfields are column-major. * Arrays, compound children, and sharedShape remain borrowed until build/insert returns. + * @ingroup shapes */ typedef struct R2ShapeDesc { + /** + * Discriminant selecting which description fields are read. + */ uint32_t kind; + /** + * Half extents, endpoint/vertex, or halfspace normal, selected by kind. + */ struct R2Vector a; + /** + * Second endpoint or triangle vertex, selected by kind. + */ struct R2Vector b; + /** + * Third triangle vertex. + */ struct R2Vector c; + /** + * Radius for the selected primitive or recipe. + */ R2Real radius; + /** + * Half the height of a cylinder or cone. + */ R2Real halfHeight; + /** + * Rounding radius for a rounded shape. + */ R2Real borderRadius; + /** + * Borrowed vertex positions. + */ struct R2VectorView vertices; + /** + * Borrowed triangle topology; count is triangles. + */ struct R2TriangleView triangles; + /** + * Borrowed edge topology; count is edges. + */ struct R2EdgeView edges; + /** + * TRIMESH_*, POLYLINE_*, or HEIGHTFIELD_* bitmask selected by kind. + */ uint32_t flags; + /** + * Borrowed height samples; 3D uses column-major rows * columns samples. + */ struct R2RealView heights; + /** + * Number of heightfield rows. + */ size_t rows; + /** + * Number of heightfield columns. + */ size_t columns; + /** + * Shape scale along each axis. + */ struct R2Vector scale; + /** + * Borrowed shared geometry; keep its wrapper alive through build/insert. + */ const R2SharedShape *sharedShape; + /** + * Borrowed compound children and their nested geometry views. + */ struct R2CompoundShapeView children; } R2ShapeDesc; +/** + * One compound child with a local pose and borrowed geometry description. + * @ingroup shapes + */ typedef struct R2CompoundShapeDesc { + /** + * Pose relative to the compound parent. + */ struct R2Pose pose; + /** + * Non-owning shape description; build/insert consumes its views synchronously. + */ struct R2ShapeDesc shape; } R2CompoundShapeDesc; #if defined(RAPIER_DIM2) +/** + * Angular scalar in 2D or vector in 3D, in radians for angular displacement. + * @ingroup math + */ typedef R2Real R2AngVector; #endif #if defined(RAPIER_DIM3) +/** + * Angular scalar in 2D or vector in 3D, in radians for angular displacement. + * @ingroup math + */ typedef struct R2Vector R2AngVector; #endif /** - * Explicit mass and principal inertia, matching MassProperties constructors. Zero mass/inertia means infinite. + * Explicit mass and principal inertia, matching MassProperties constructors. Zero mass/inertia + * means infinite. + * @ingroup math */ typedef struct R2MassProperties { + /** + * Center of mass in local coordinates. + */ struct R2Vector local_com; + /** + * Mass; nonnegative when supplied as input. + */ R2Real mass; + /** + * Principal angular inertia; scalar in 2D, three diagonal entries in 3D. + */ R2AngVector principal_inertia; #if defined(RAPIER_DIM3) + /** + * Orientation of principal inertia axes in local coordinates (3D). + */ struct R2Rotation principal_inertia_local_frame; #endif } R2MassProperties; +/** + * Collision membership/filter masks and their pairwise combination rule. + * @ingroup math + */ typedef struct R2InteractionGroups { + /** + * Groups this object belongs to, as a 32-bit mask. + */ uint32_t memberships; + /** + * Membership groups accepted by this object, as a 32-bit mask. + */ uint32_t filter; + /** + * 0 requires both membership/filter tests; 1 accepts either test. + */ uint32_t test_mode; } R2InteractionGroups; /** * Copyable collider construction data. Shape inputs are borrowed, never owned. + * @ingroup colliders */ typedef struct R2ColliderDesc { + /** + * Non-owning shape description; build/insert consumes its views synchronously. + */ struct R2ShapeDesc shape; + /** + * Pose relative to the parent body; world-space for an unparented collider. + */ struct R2Pose position; + /** + * R2_MASS_DENSITY, R2_MASS_TOTAL, or R2_MASS_PROPERTIES. + */ uint32_t massMode; + /** + * Nonnegative mass per unit volume. + */ R2Real density; + /** + * Mass; nonnegative when supplied as input. + */ R2Real mass; + /** + * Explicit local mass and inertia when massMode selects them. + */ struct R2MassProperties massProperties; + /** + * Nonnegative friction coefficient. + */ R2Real friction; + /** + * Nonnegative restitution coefficient. + */ R2Real restitution; + /** + * R2_COMBINE_AVERAGE, MIN, MULTIPLY, or MAX. + */ uint32_t frictionCombineRule; + /** + * R2_COMBINE_AVERAGE, MIN, MULTIPLY, or MAX. + */ uint32_t restitutionCombineRule; + /** + * 1 detects intersections without generating contact forces. + */ R2Bool isSensor; + /** + * Whether this setting/object is enabled (0 or 1). + */ R2Bool enabled; + /** + * Groups controlling collision detection. + */ struct R2InteractionGroups collisionGroups; + /** + * Groups controlling contact-force solving. + */ struct R2InteractionGroups solverGroups; + /** + * Bitmask of body-type pairs allowed to collide. + */ uint16_t activeCollisionTypes; + /** + * Hook flags enabling pair filtering/contact modification. + */ uint32_t activeHooks; + /** + * R2_COLLISION_EVENTS and/or R2_CONTACT_FORCE_EVENTS. + */ uint32_t activeEvents; + /** + * Nonnegative force threshold for force events. + */ R2Real contactForceEventThreshold; + /** + * Nonnegative extra separation distance around the collider. + */ R2Real contactSkin; + /** + * Application data; Rapier does not own pointers encoded in it. + */ struct R2UserData userData; } R2ColliderDesc; @@ -953,63 +2218,196 @@ typedef struct R2ColliderDesc { * for topology arrays are element counts (edges, triangles, or tetrahedra). * Nonempty topology overrides the generator's topology. Zero counts retain it. * Generator inputs: a/b are rope ends or center/half-extents; cloth uses a/du/dv. + * @ingroup soft_bodies */ typedef struct R2SoftBodyDesc { + /** + * Discriminant selecting which description fields are read. + */ uint32_t kind; + /** + * Recipe origin/center or first rope endpoint; use a recipe constructor. + */ struct R2Vector a; + /** + * Recipe half extents, second rope endpoint, or tube axis; use a recipe constructor. + */ struct R2Vector b; + /** + * Cloth basis step along its first parameter axis. + */ struct R2Vector du; + /** + * Cloth basis step along its second parameter axis. + */ struct R2Vector dv; + /** + * First recipe resolution; interpretation depends on kind. + */ size_t nx; + /** + * Second recipe resolution; interpretation depends on kind. + */ size_t ny; + /** + * Third recipe resolution; interpretation depends on kind. + */ size_t nz; + /** + * Radius for the selected primitive or recipe. + */ R2Real radius; + /** + * Radius at the second end of a tapered cloth tube. + */ R2Real radiusEnd; + /** + * Translation vector. + */ struct R2Vector translation; + /** + * Optional total mass override for generated particles. + */ struct R2OptionalReal totalMass; + /** + * Volume-meshing parameters for a volumetric recipe. + */ struct R2VolumeMeshParameters meshing; + /** + * Borrowed initial particle positions. + */ struct R2VectorView positions; + /** + * Borrowed per-particle masses; when empty, particleMass is used. + */ struct R2RealView masses; + /** + * Borrowed indices of pinned particles. + */ struct R2IndexView pinned; + /** + * Borrowed edge topology; count is edges. + */ struct R2EdgeView edges; + /** + * Borrowed bending edge constraints. + */ struct R2EdgeView bendEdges; + /** + * Borrowed indices of edges that resist tension only. + */ struct R2IndexView tensionOnlyEdges; + /** + * Borrowed per-edge softness overrides. + */ struct R2SoftEdgeSoftnessView edgeSoftness; + /** + * Borrowed per-edge tear-resistance overrides. + */ struct R2SoftEdgeTearView edgeTearResistance; + /** + * Borrowed triangles in 2D or tetrahedra in 3D. + */ R2CellView cells; + /** + * Borrowed boundary edges in 2D or triangles in 3D. + */ R2SurfaceElementView surface; #if defined(RAPIER_DIM3) + /** + * Borrowed four-vertex bending constraints. + */ struct R2DihedralView dihedrals; #endif #if defined(RAPIER_DIM3) + /** + * Borrowed wire edges for a surface recipe. + */ struct R2EdgeView wire; #endif + /** + * Borrowed skin vertex positions. + */ struct R2VectorView skinVertices; + /** + * Borrowed skin topology. + */ R2SurfaceElementView skinIndices; + /** + * Soft-body material coefficients. + */ struct R2SoftBodyMaterial material; + /** + * R2_SOFT_CELL_VOLUME, R2_SOFT_CELL_COROTATIONAL, or R2_SOFT_CELL_NEO_HOOKEAN. + */ uint32_t cellModel; /** * 0 = constraints, 1 = FEM (requires a library built with FEM). */ uint32_t solver; + /** + * Default nonnegative particle mass. + */ R2Real particleMass; /** * Disabled by default: retain the radius computed by the generator. */ struct R2OptionalReal particleRadius; + /** + * Whether to preserve volume. + */ R2Bool volumePreservation; + /** + * Target volume multiplier. + */ R2Real volumeFactor; + /** + * Optional shape-matching override; disabled retains recipe defaults. + */ struct R2OptionalBool shapeMatching; + /** + * Whether self-collision is enabled. + */ R2Bool selfContacts; + /** + * Whether skin elements participate in collision detection. + */ R2Bool skinCollision; + /** + * Whether the generated collision geometry is enabled. + */ R2Bool collisionEnabled; + /** + * Collider configuration used by the recipe; shape comes from the generated geometry. + */ struct R2ColliderDesc collider; + /** + * Nonnegative linear damping coefficient. + */ R2Real linearDamping; + /** + * Multiplier applied to world gravity. + */ R2Real gravityScale; + /** + * Extra solver iterations for this body and connected bodies. + */ size_t additionalSolverIterations; + /** + * Extra PGS iterations for this body. + */ size_t additionalPgsIterations; + /** + * Whether automatic sleeping is allowed. + */ R2Bool canSleep; + /** + * Signed dominance group; larger groups dominate smaller groups. + */ int8_t dominanceGroup; + /** + * Application data; Rapier does not own pointers encoded in it. + */ struct R2UserData userData; } R2SoftBodyDesc; @@ -1017,23 +2415,43 @@ typedef struct R2SoftBodyDesc { * Copyable non-owning handle: world pointer plus entity index and generation. * The world must remain alive throughout every use. Copying does not retain it. * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + * @ingroup soft_bodies */ typedef struct R2SoftBodyHandle { /** * Borrowed owning world. Never use this handle after freeing that world. */ struct R2World *world; + /** + * Zero-based element index. + */ uint32_t index; + /** + * Slot generation used to reject stale handles. Do not modify it. + */ uint32_t generation; } R2SoftBodyHandle; /** * Non-owning deformable binding description. Direct particle indices are borrowed. + * @ingroup soft_bodies */ typedef struct R2SoftMeshBindingDesc { + /** + * Discriminant selecting which description fields are read. + */ uint32_t kind; + /** + * Borrowed particle indices used for a direct mesh binding. + */ struct R2IndexView particles; + /** + * Nonnegative positional tolerance for direct-by-position binding. + */ R2Real epsilon; + /** + * Whether self-collision is enabled. + */ R2Bool selfContacts; } R2SoftMeshBindingDesc; @@ -1041,27 +2459,54 @@ typedef struct R2SoftMeshBindingDesc { * Copyable non-owning handle: world pointer plus entity index and generation. * The world must remain alive throughout every use. Copying does not retain it. * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + * @ingroup colliders */ typedef struct R2ColliderHandle { /** * Borrowed owning world. Never use this handle after freeing that world. */ struct R2World *world; + /** + * Slot index; UINT32_MAX denotes the explicit invalid handle. + */ uint32_t index; + /** + * Slot generation used to reject stale handles. Do not modify it. + */ uint32_t generation; } R2ColliderHandle; +/** + * Scene-query flags, groups, and excluded handles. Initialize with r2DefaultQueryFilter. + * @ingroup queries + */ typedef struct R2QueryFilter { + /** + * R2_QUERY_EXCLUDE_* bitmask selecting body types and sensors/solids. + */ uint32_t flags; + /** + * Whether to apply the groups filter. + */ R2Bool use_groups; + /** + * Groups to test when use_groups is 1. + */ struct R2InteractionGroups groups; + /** + * Collider to exclude; use the explicit invalid handle to exclude none. + */ struct R2ColliderHandle exclude_collider; + /** + * Body whose colliders are excluded; use the explicit invalid handle for none. + */ struct R2RigidBodyHandle exclude_rigid_body; } R2QueryFilter; /** * Called with scoped read access and a collider handle. Shared queries may nest; * world mutations are rejected until the outer query returns. Never retain the context. + * @ingroup queries */ typedef R2Bool (RAPIER_CALL *R2QueryPredicate)(void *user_data, const struct R2ReadContext *read, @@ -1069,101 +2514,315 @@ typedef R2Bool (RAPIER_CALL *R2QueryPredicate)(void *user_data, /** * Copyable query settings. They borrow callback data, never world components. + * @ingroup queries */ typedef struct R2QueryOptions { + /** + * Query selection settings. + */ struct R2QueryFilter filter; + /** + * Optional additional query filter; nonzero accepts a collider. + */ R2QueryPredicate predicate; + /** + * Application data; Rapier does not own pointers encoded in it. + */ void *userData; } R2QueryOptions; +/** + * Closest ray intersection, with a world-space normal. + * @ingroup queries + */ typedef struct R2RayHit { + /** + * World-bound collider handle. + */ struct R2ColliderHandle collider; + /** + * Ray/sweep parameter at first impact, bounded by the query options. + */ R2Real time_of_impact; + /** + * World-space contact or surface normal. + */ struct R2Vector normal; + /** + * Shape feature kind: R2_FEATURE_UNKNOWN, R2_FEATURE_VERTEX, R2_FEATURE_EDGE, or + * R2_FEATURE_FACE. + */ uint32_t feature_type; + /** + * Index within the feature kind; zero for unknown. + */ uint32_t feature_id; } R2RayHit; +/** + * Closest projected world-space point and its collider. + * @ingroup queries + */ typedef struct R2PointProjection { + /** + * World-bound collider handle. + */ struct R2ColliderHandle collider; + /** + * Projected world-space point. + */ struct R2Vector point; + /** + * Whether the original point was inside the collider. + */ R2Bool is_inside; } R2PointProjection; +/** + * Shape-cast impact geometry; collider-side data is world-space, moving-shape data is local. + * @ingroup queries + */ typedef struct R2ShapeCastHit { + /** + * World-bound collider handle. + */ struct R2ColliderHandle collider; + /** + * Ray/sweep parameter at first impact, bounded by the query options. + */ R2Real time_of_impact; + /** + * Impact witness on the collider, in world coordinates. + */ struct R2Vector witness1; + /** + * Impact witness on the moving shape, in its local coordinates. + */ struct R2Vector witness2; + /** + * Impact normal on the collider, in world coordinates. + */ struct R2Vector normal1; + /** + * Impact normal on the moving shape, in its local coordinates. + */ struct R2Vector normal2; + /** + * Native result: 0 out of iterations, 1 converged, 2 failed, 3 penetrating or within target + * distance. + */ uint32_t status; } R2ShapeCastHit; +/** + * Sweep termination settings. Initialize with r2DefaultShapeCastOptions. + * @ingroup queries + */ typedef struct R2ShapeCastOptions { + /** + * Maximum sweep parameter; movement is velocity multiplied by this time. + */ R2Real max_time_of_impact; + /** + * Nonnegative separation at which a shape cast counts as a hit. + */ R2Real target_distance; + /** + * Whether to stop at t = 0 for an initial overlap. + */ R2Bool stop_at_penetration; + /** + * Whether to compute witness points/normals for an initial overlap. + */ R2Bool compute_impact_geometry_on_penetration; } R2ShapeCastOptions; +/** + * Axis-aligned bounding box. + * @ingroup math + */ typedef struct R2Aabb { + /** + * Minimum corner in each coordinate. + */ struct R2Vector mins; + /** + * Maximum corner in each coordinate. + */ struct R2Vector maxs; } R2Aabb; +/** + * Optional ray collider/time result. A miss is found = 0 with status OK. + * @ingroup queries + */ typedef struct R2RayToi { + /** + * World-bound collider handle. + */ struct R2ColliderHandle collider; + /** + * Ray parameter t at impact: origin + direction * t. + */ R2Real toi; + /** + * Whether a result exists; other result fields are meaningful only when this is 1. + */ R2Bool found; } R2RayToi; +/** + * Optional full ray result. A miss is found = 0 with status OK. + * @ingroup queries + */ typedef struct R2OptionalRayHit { + /** + * Shape/ray impact details. + */ struct R2RayHit hit; + /** + * Whether a result exists; other result fields are meaningful only when this is 1. + */ R2Bool found; } R2OptionalRayHit; /** - * Stack-allocated rigid-body construction data. Initialize with RigidBodyDescInit. + * Stack-allocated rigid-body construction data. Initialize with r2DynamicRigidBodyDesc, + * r2FixedRigidBodyDesc, or a kinematic description constructor. * Copying this value is safe; it owns no resources and must never be freed by Rapier. + * @ingroup rigid_bodies */ typedef struct R2RigidBodyDesc { + /** + * World-space pose. + */ struct R2Pose position; + /** + * World-space linear velocity. + */ struct R2Vector linvel; + /** + * World-space angular velocity in radians per second. + */ R2AngVector angvel; + /** + * R2_DYNAMIC, R2_FIXED, R2_KINEMATIC_POSITION_BASED, or R2_KINEMATIC_VELOCITY_BASED. + */ uint32_t bodyType; + /** + * Multiplier applied to world gravity. + */ R2Real gravityScale; + /** + * Nonnegative linear damping coefficient. + */ R2Real linearDamping; + /** + * Nonnegative angular damping coefficient. + */ R2Real angularDamping; + /** + * Nonnegative mass added to attached collider contributions. + */ R2Real additionalMass; + /** + * 1 uses additionalMassProperties; 0 uses additionalMass. + */ R2Bool useAdditionalMassProperties; + /** + * Additional body-local mass and inertia when enabled. + */ struct R2MassProperties additionalMassProperties; + /** + * Locked-axis bitmask; translations precede rotations. + */ uint8_t lockedAxes; + /** + * Whether automatic sleeping is allowed. + */ R2Bool canSleep; + /** + * Whether the body starts/is asleep. + */ R2Bool sleeping; + /** + * Whether continuous collision detection is enabled. + */ R2Bool ccdEnabled; + /** + * Nonnegative prediction distance for soft CCD. + */ R2Real softCcdPrediction; + /** + * Whether to allow fast rotations without the native angular-motion clamp. + */ R2Bool allowFastRotation; + /** + * Whether this setting/object is enabled (0 or 1). + */ R2Bool enabled; + /** + * Signed dominance group; larger groups dominate smaller groups. + */ int8_t dominanceGroup; + /** + * Extra solver iterations for this body and connected bodies. + */ size_t additionalSolverIterations; + /** + * Extra PGS iterations for this body. + */ size_t additionalPgsIterations; + /** + * Whether to include gyroscopic forces (3D). + */ R2Bool gyroscopicForcesEnabled; + /** + * Application data; Rapier does not own pointers encoded in it. + */ struct R2UserData userData; } R2RigidBodyDesc; /** * Sizes of the POD types in this library build, for foreign-language layout checks. + * @ingroup math */ typedef struct R2PodLayout { + /** + * Size in bytes of the rigidBodyDesc structure; zero when unavailable. + */ size_t rigidBodyDesc; + /** + * Size in bytes of the colliderDesc structure; zero when unavailable. + */ size_t colliderDesc; + /** + * Size in bytes of the shapeDesc structure; zero when unavailable. + */ size_t shapeDesc; + /** + * Size in bytes of the jointDesc structure; zero when unavailable. + */ size_t jointDesc; + /** + * Size in bytes of the softBodyMaterial structure; zero when unavailable. + */ size_t softBodyMaterial; + /** + * Size in bytes of the integrationParameters structure; zero when unavailable. + */ size_t integrationParameters; + /** + * Size in bytes of the softBodyDesc structure; zero when unavailable. + */ size_t softBodyDesc; + /** + * Size in bytes of the softMeshBindingDesc structure; zero when unavailable. + */ size_t softMeshBindingDesc; + /** + * Size in bytes of the queryOptions structure; zero when unavailable. + */ size_t queryOptions; /** * Zero unless 3D f32 robotics is enabled. @@ -1176,72 +2835,222 @@ typedef struct R2PodLayout { } R2PodLayout; /** - * CharacterLength counterpart: relative=1 scales with character height, relative=0 uses world units. + * CharacterLength counterpart: relative=1 scales with character height, relative=0 uses world + * units. + * @ingroup controllers */ typedef struct R2CharacterLength { + /** + * Value used when enabled is 1. + */ R2Real value; + /** + * 1 scales value by the character shape size; 0 uses an absolute length. + */ R2Bool relative; } R2CharacterLength; +/** + * Allowed character motion and ground-contact state. + * @ingroup controllers + */ typedef struct R2CharacterMovement { + /** + * Allowed world-space displacement; not applied automatically. + */ struct R2Vector translation; + /** + * Whether the character touches the ground after movement. + */ R2Bool grounded; + /** + * Whether motion includes sliding down a non-climbable slope. + */ R2Bool is_sliding_down_slope; } R2CharacterMovement; +/** + * Collision recorded during character movement. + * @ingroup controllers + */ typedef struct R2CharacterCollision { + /** + * World-bound collider handle. + */ struct R2ColliderHandle collider; + /** + * World-space character pose at collision. + */ struct R2Pose character_pos; + /** + * World-space translation already applied before collision. + */ struct R2Vector translation_applied; + /** + * World-space translation remaining at collision. + */ struct R2Vector translation_remaining; + /** + * Shape/ray impact details. + */ struct R2ShapeCastHit hit; } R2CharacterCollision; +/** + * Per-axis proportional, integral, and derivative controller gains. + * @ingroup controllers + */ typedef struct R2PidGains { + /** + * Linear proportional gain per axis. + */ struct R2Vector lin_kp; + /** + * Linear integral gain per axis. + */ struct R2Vector lin_ki; + /** + * Linear derivative gain per axis. + */ struct R2Vector lin_kd; + /** + * Angular proportional gain per axis. + */ R2AngVector ang_kp; + /** + * Angular integral gain per axis. + */ R2AngVector ang_ki; + /** + * Angular derivative gain per axis. + */ R2AngVector ang_kd; } R2PidGains; +/** + * Linear and angular velocity correction computed by a controller. + * @ingroup controllers + */ typedef struct R2VelocityCorrection { + /** + * World-space linear velocity correction. + */ struct R2Vector linear; + /** + * World-space angular velocity correction, in radians per second. + */ R2AngVector angularVelocity; } R2VelocityCorrection; +/** + * Copy of character sliding, slope, and snapping settings. + * @ingroup controllers + */ typedef struct R2CharacterControllerSettings { + /** + * Whether obstacle sliding is enabled. + */ R2Bool slide; + /** + * Maximum climbable slope angle in radians. + */ R2Real max_slope_climb_angle; + /** + * Minimum slope angle for sliding, in radians. + */ R2Real min_slope_slide_angle; + /** + * Whether downward ground snapping is enabled. + */ R2Bool snap_to_ground; + /** + * Maximum downward snapping distance. + */ struct R2CharacterLength snap_distance; } R2CharacterControllerSettings; #if defined(RAPIER_DIM3) +/** + * Wheel suspension/friction parameters. Initialize with r2DefaultWheelTuning. + * @ingroup controllers + */ typedef struct R2WheelTuning { + /** + * Nonnegative suspension spring stiffness. + */ R2Real suspension_stiffness; + /** + * Nonnegative damping coefficient during suspension compression. + */ R2Real suspension_compression; + /** + * Nonnegative suspension relaxation damping. + */ R2Real suspension_damping; + /** + * Maximum suspension travel in length units. + */ R2Real max_suspension_travel; + /** + * Maximum tire friction/slip coefficient. + */ R2Real friction_slip; + /** + * Maximum force exerted by suspension. + */ R2Real max_suspension_force; + /** + * Sideways tire friction stiffness. + */ R2Real side_friction_stiffness; } R2WheelTuning; #endif #if defined(RAPIER_DIM3) +/** + * Copy of the current wheel pose, suspension, and contact state. + * @ingroup controllers + */ typedef struct R2WheelState { + /** + * World-space wheel center. + */ struct R2Vector center; + /** + * World-space suspension direction. + */ struct R2Vector suspension; + /** + * World-space axle direction. + */ struct R2Vector axle; + /** + * Wheel rotation angle in radians. + */ R2Real rotation; + /** + * Current suspension force. + */ R2Real suspension_force; + /** + * Current suspension length. + */ R2Real suspension_length; + /** + * Whether the wheel has ground contact. + */ R2Bool is_in_contact; + /** + * Ground collider handle, invalid when there is no contact. + */ struct R2ColliderHandle ground_object; + /** + * World-space ground contact point. + */ struct R2Vector contact_point; + /** + * World-space ground contact normal. + */ struct R2Vector contact_normal; } R2WheelState; #endif @@ -1251,59 +3060,137 @@ typedef struct R2WheelState { * The diagnostic is borrowed for the duration of the callback. The callback * must return normally or terminate the process: never throw or longjmp across * the Rust/C boundary. Nested failing calls do not invoke the handler recursively. + * @ingroup errors */ typedef void (RAPIER_CALL *R2ErrorCallback)(R2Status, const char*, void*); /** * An optional thread-local error handler. A null callback disables reporting. * Keep the callback and user_data alive until the handler is replaced. + * @ingroup errors */ typedef struct R2ErrorHandler { + /** + * Optional error callback; NULL disables notifications. + */ R2ErrorCallback callback; + /** + * Application data; Rapier does not own pointers encoded in it. + */ void *user_data; } R2ErrorHandler; +/** + * World-bound handles of the two connected bodies. + * @ingroup joints + */ typedef struct R2JointBodies { + /** + * First connected body. + */ struct R2RigidBodyHandle body1; + /** + * Second connected body. + */ struct R2RigidBodyHandle body2; } R2JointBodies; +/** + * Damped least-squares inverse-kinematics parameters. + * @ingroup math + */ typedef struct R2InverseKinematicsOptions { + /** + * Nonnegative motor damping. + */ R2Real damping; + /** + * Maximum inverse-kinematics iterations. + */ size_t max_iters; + /** + * Controlled-axis bitmask; translations precede rotations. + */ uint8_t constrained_axes; + /** + * Linear convergence tolerance. + */ R2Real epsilon_linear; + /** + * Angular convergence tolerance in radians. + */ R2Real epsilon_angular; } R2InverseKinematicsOptions; /** * Optional per-link filter, called synchronously. Must not reenter or retain physics objects. + * @ingroup joints */ typedef R2Bool (RAPIER_CALL *R2IkJointCanMove)(void*, struct R2RigidBodyHandle); /** * Collision start/stop flags match Rapier CollisionEventFlags. + * @ingroup events */ typedef struct R2CollisionEvent { + /** + * First collider in the pair. + */ struct R2ColliderHandle collider1; + /** + * Second collider in the pair. + */ struct R2ColliderHandle collider2; + /** + * 1 for a starting event, 0 for a stopping event. + */ R2Bool started; + /** + * Event flags: bit 0 sensor pair, bit 1 removed collider. + */ uint32_t flags; } R2CollisionEvent; +/** + * Contact-force event, enabled by flags and the collider force threshold. + * @ingroup events + */ typedef struct R2ContactForceEvent { + /** + * First collider in the pair. + */ struct R2ColliderHandle collider1; + /** + * Second collider in the pair. + */ struct R2ColliderHandle collider2; + /** + * Sum of world-space contact forces. + */ struct R2Vector total_force; + /** + * Sum of contact force magnitudes. + */ R2Real total_force_magnitude; + /** + * World-space direction of the strongest contact force. + */ struct R2Vector max_force_direction; + /** + * Magnitude of the strongest contact force. + */ R2Real max_force_magnitude; + /** + * 1 for a starting event, 0 for a stopping event. + */ R2Bool started; } R2ContactForceEvent; /** - * Pair callback: -1 rejects a contact pair; 0 detects contacts without impulses; 1 computes impulses. + * Pair callback: -1 rejects a contact pair; 0 detects contacts without impulses; 1 computes + * impulses. * For sensor intersections only, zero rejects and any positive value accepts. + * @ingroup math */ typedef int32_t (RAPIER_CALL *R2PairFilter)(void *user_data, const struct R2ReadContext *read, @@ -1314,21 +3201,45 @@ typedef int32_t (RAPIER_CALL *R2PairFilter)(void *user_data, /** * Mutable per-manifold properties. Set enabled=0 to discard all its solver contacts. + * @ingroup events */ typedef struct R2ContactModification { + /** + * World-space contact or surface normal. + */ struct R2Vector normal; + /** + * Nonnegative friction coefficient. + */ R2Real friction; + /** + * Nonnegative restitution coefficient. + */ R2Real restitution; + /** + * Application data; Rapier does not own pointers encoded in it. + */ uint32_t user_data; + /** + * Whether this setting/object is enabled (0 or 1). + */ R2Bool enabled; } R2ContactModification; +/** + * Modify aggregate contact properties for one pair; contact is mutable only during this callback. + * @ingroup callbacks + */ typedef void (RAPIER_CALL *R2ModifyContacts)(void *user_data, const struct R2ReadContext *read, struct R2ColliderHandle collider1, struct R2ColliderHandle collider2, struct R2ContactModification *contact); +/** + * Modify individual solver contacts through a borrowed context, valid only during the callback. + * @ingroup callbacks + */ typedef void (RAPIER_CALL *R2ModifyContactContext)(void *user_data, const struct R2ReadContext *read, struct R2ColliderHandle collider1, @@ -1339,12 +3250,26 @@ typedef void (RAPIER_CALL *R2ModifyContactContext)(void *user_data, * Callbacks must not unwind or retain arguments. Use their ReadContext to inspect bodies and * colliders; ordinary access to the stepping world returns WORLD_BUSY. Mutations must be * performed after stepping. With parallel builds - * callbacks and their user_data must be safe for concurrent invocation. NULL callbacks use defaults. + * callbacks and their user_data must be safe for concurrent invocation. NULL callbacks use + * defaults. + * @ingroup callbacks */ typedef struct R2PhysicsHooks { + /** + * Application data; Rapier does not own pointers encoded in it. + */ void *user_data; + /** + * Optional contact-pair filter, called only for colliders enabling the hook. + */ R2PairFilter filter_contact_pair; + /** + * Optional sensor-pair filter, called only for colliders enabling the hook. + */ R2PairFilter filter_intersection_pair; + /** + * Optional legacy aggregate contact-edit callback. + */ R2ModifyContacts modify_solver_contacts; /** * Runs after the legacy property callback. Context accessors may be called here. @@ -1354,52 +3279,144 @@ typedef struct R2PhysicsHooks { /** * Borrowed bytes. Valid while the source Bytes object remains alive; never free data. + * @ingroup math */ typedef struct R2ByteView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const uint8_t *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R2ByteView; +/** + * World-space line segment produced by physics debug rendering. + * @ingroup events + */ typedef struct R2DebugLine { + /** + * World-space start point. + */ struct R2Vector a; + /** + * World-space end point. + */ struct R2Vector b; + /** + * RGBA color, four floats. + */ float color[4]; } R2DebugLine; +/** + * Particle remapping after tearing. + * @ingroup math + */ typedef struct R2ParticleDestination { + /** + * World-bound rigid/soft-body handle, as selected by the field type. + */ struct R2SoftBodyHandle body; + /** + * Zero-based element index. + */ uint32_t index; } R2ParticleDestination; +/** + * Cluster and proxy remapping after a soft-body split. + * @ingroup soft_bodies + */ typedef struct R2SoftClusterSplit { + /** + * Original cluster index before splitting. + */ uint32_t source_cluster; + /** + * Resulting soft-body handle. + */ struct R2SoftBodyHandle soft_body; + /** + * Cluster index within its soft body. + */ uint32_t cluster; + /** + * Cluster rigid-proxy body handle. + */ struct R2RigidBodyHandle proxy; + /** + * Whether this split retains the original proxy. + */ R2Bool keeps_proxy; } R2SoftClusterSplit; +/** + * Impulse-joint movement between rigid proxies after tearing. + * @ingroup soft_bodies + */ typedef struct R2SoftJointMove { + /** + * Impulse joint that moved between proxies. + */ struct R2ImpulseJointHandle joint; + /** + * Original rigid-proxy body. + */ struct R2RigidBodyHandle from; + /** + * Destination rigid-proxy body. + */ struct R2RigidBodyHandle to; } R2SoftJointMove; +/** + * Optional particle remapping; check found before reading the destination. + * @ingroup math + */ typedef struct R2OptionalParticleDestination { + /** + * World-bound rigid/soft-body handle, as selected by the field type. + */ struct R2SoftBodyHandle body; + /** + * Zero-based element index. + */ uint32_t index; + /** + * Whether a result exists; other result fields are meaningful only when this is 1. + */ R2Bool found; } R2OptionalParticleDestination; +/** + * Runtime ABI identity and fundamental type sizes. + * @ingroup errors + */ typedef struct R2BuildInfo { + /** + * Binary ABI revision; compare with the header ABI_VERSION. + */ uint32_t abi_version; + /** + * Spatial dimension, 2 or 3. + */ uint32_t dimension; + /** + * Size of Real in bytes, 4 or 8. + */ uint32_t real_size; + /** + * Pointer size in bytes. + */ uint32_t pointer_size; } R2BuildInfo; /** * Features available through the loaded C library, independent of consumer defines. + * @ingroup errors */ typedef struct R2BuildFeatures { /** @@ -1416,28 +3433,88 @@ typedef struct R2BuildFeatures { R2Bool parallel; } R2BuildFeatures; +/** + * Current narrow-phase pair and accumulated solver impulse summary. + * @ingroup events + */ typedef struct R2ContactPair { + /** + * First collider in the pair. + */ struct R2ColliderHandle collider1; + /** + * Second collider in the pair. + */ struct R2ColliderHandle collider2; + /** + * Whether the pair has an active solver contact. + */ R2Bool has_any_active_contact; + /** + * Sum of world-space contact impulse vectors. + */ struct R2Vector total_impulse; + /** + * Sum of contact impulse magnitudes. + */ R2Real total_impulse_magnitude; + /** + * Largest contact impulse magnitude. + */ R2Real max_impulse; + /** + * World-space direction of the largest contact impulse. + */ struct R2Vector max_impulse_direction; } R2ContactPair; +/** + * Sensor intersection state for a collider pair. + * @ingroup math + */ typedef struct R2IntersectionPair { + /** + * First collider in the pair. + */ struct R2ColliderHandle collider1; + /** + * Second collider in the pair. + */ struct R2ColliderHandle collider2; + /** + * Whether the two sensor/collider shapes intersect. + */ R2Bool intersecting; } R2IntersectionPair; +/** + * One manifold contact and its normal solver impulse. + * @ingroup events + */ typedef struct R2ContactPoint { + /** + * Index of the contact manifold within its pair. + */ size_t manifold_index; + /** + * Contact point in collider 1 local coordinates. + */ struct R2Vector local_p1; + /** + * Contact point in collider 2 local coordinates. + */ struct R2Vector local_p2; + /** + * World-space contact or surface normal. + */ struct R2Vector normal; + /** + * Signed separation; negative means penetration. + */ R2Real distance; + /** + * Normal impulse applied at this contact. + */ R2Real impulse; } R2ContactPoint; @@ -1445,17 +3522,48 @@ typedef struct R2ContactPoint { /** * Loader configuration. Initialize with DefaultUrdfLoaderOptions; no destructor. * Blueprint array views and shared shapes are borrowed through the load call. + * @ingroup robotics */ typedef struct R2UrdfLoaderOptions { + /** + * Whether to build colliders from collision geometry. + */ R2Bool createCollidersFromCollisionShapes; + /** + * Whether to also build colliders from visual geometry. + */ R2Bool createCollidersFromVisualShapes; + /** + * Whether to use imported mass/inertia properties. + */ R2Bool applyImportedMassProps; + /** + * Whether bodies connected by imported joints may collide. + */ R2Bool enableJointCollisions; + /** + * Whether imported root bodies are fixed. + */ R2Bool makeRootsFixed; + /** + * Whether to merge empty fixed URDF links. + */ R2Bool squeezeEmptyFixedLinks; + /** + * Transform applied to the imported model. + */ struct R2Pose shift; + /** + * Shape scale along each axis. + */ R2Real scale; + /** + * Default collider description; nested geometry resources are borrowed through loading. + */ struct R2ColliderDesc colliderBlueprint; + /** + * Default rigid-body description used by the importer. + */ struct R2RigidBodyDesc rigidBodyBlueprint; } R2UrdfLoaderOptions; #endif @@ -1464,18 +3572,52 @@ typedef struct R2UrdfLoaderOptions { /** * Loader configuration. Initialize with DefaultMjcfLoaderOptions; no destructor. * Blueprint array views and shared shapes are borrowed through the load call. + * @ingroup robotics */ typedef struct R2MjcfLoaderOptions { + /** + * Whether to build colliders from collision geometry. + */ R2Bool createCollidersFromCollisionShapes; + /** + * Whether to also build colliders from visual geometry. + */ R2Bool createCollidersFromVisualShapes; + /** + * Whether to use imported mass/inertia properties. + */ R2Bool applyImportedMassProps; + /** + * Whether bodies connected by imported joints may collide. + */ R2Bool enableJointCollisions; + /** + * Whether imported root bodies are fixed. + */ R2Bool makeRootsFixed; + /** + * Whether to omit MJCF plane geometry. + */ R2Bool skipPlaneGeoms; + /** + * Whether imported joint motors are disabled. + */ R2Bool disableJointMotors; + /** + * Transform applied to the imported model. + */ struct R2Pose shift; + /** + * Shape scale along each axis. + */ R2Real scale; + /** + * Default collider description; nested geometry resources are borrowed through loading. + */ struct R2ColliderDesc colliderBlueprint; + /** + * Default rigid-body description used by the importer. + */ struct R2RigidBodyDesc rigidBodyBlueprint; } R2MjcfLoaderOptions; #endif @@ -1483,104 +3625,252 @@ typedef struct R2MjcfLoaderOptions { #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * A borrowed visual declaration, valid until its robot is freed or its body storage changes. + * @ingroup robotics */ typedef struct R2MjcfVisualMesh R2MjcfVisualMesh; #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Imported physically based visual material; no texture ownership. + * @ingroup robotics + */ typedef struct R2RenderMaterial { + /** + * Material metallic factor. + */ float metallic; + /** + * Material roughness factor. + */ float roughness; + /** + * Material reflectance factor. + */ float reflectance; + /** + * RGB emissive color. + */ float emissive[3]; } R2RenderMaterial; #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copied metadata for a borrowed MJCF visual mesh. + * @ingroup robotics + */ typedef struct R2MjcfVisualMeshInfo { + /** + * Visual pose relative to its source body. + */ struct R2Pose local_pose; + /** + * RGBA visual color. + */ float rgba[4]; + /** + * Copied render material; meaningful when has_material is 1. + */ struct R2RenderMaterial material; + /** + * Whether rgba contains an authored color. + */ R2Bool has_color; + /** + * Whether material contains authored material data. + */ R2Bool has_material; + /** + * Whether the visual geometry is a triangle mesh. + */ R2Bool is_trimesh; } R2MjcfVisualMeshInfo; #endif /** * A copied state snapshot, with no pointers or ownership obligations. + * @ingroup rigid_bodies */ typedef struct R2RigidBodyState { + /** + * World-space pose. + */ struct R2Pose position; + /** + * World-space linear velocity. + */ struct R2Vector linvel; + /** + * World-space angular velocity in radians per second. + */ R2AngVector angvel; + /** + * Whether the body starts/is asleep. + */ R2Bool sleeping; + /** + * Whether this setting/object is enabled (0 or 1). + */ R2Bool enabled; + /** + * Application data; Rapier does not own pointers encoded in it. + */ struct R2UserData userData; } R2RigidBodyState; /** * Stable identity of a live mesh within one soft body; matches Rapier's SoftMeshId. + * @ingroup soft_bodies */ typedef struct R2SoftMeshId { + /** + * Cluster index within its soft body. + */ uint32_t cluster; + /** + * Mesh index within the cluster. + */ uint32_t mesh; } R2SoftMeshId; /** * Mesh identity and rendering metadata. A render-only mesh has an invalid collider handle. + * @ingroup soft_bodies */ typedef struct R2SoftMeshInfo { + /** + * Cluster/mesh pair identifying this collision mesh. + */ struct R2SoftMeshId id; + /** + * World-bound collider handle. + */ struct R2ColliderHandle collider; + /** + * Indices per mesh element: 2 for an edge or 3 for a triangle. + */ size_t arity; + /** + * Whether vertex positions are obtained by skinning. + */ R2Bool is_skinned; + /** + * Whether collision detection is enabled for this mesh. + */ R2Bool collision_enabled; } R2SoftMeshInfo; #if defined(RAPIER_F32) /** * Voxel coordinates have DIM signed integer components. + * @ingroup math */ typedef int32_t R2VoxelCoord; #endif #if defined(RAPIER_F64) +/** + * Signed voxel coordinate integer. + * @ingroup math + */ typedef int64_t R2VoxelCoord; #endif +/** + * Integer coordinates of a voxel cell. + * @ingroup math + */ typedef struct R2VoxelKey { + /** + * X component. + */ R2VoxelCoord x; + /** + * Y component. + */ R2VoxelCoord y; #if defined(RAPIER_DIM3) + /** + * Z component. + */ R2VoxelCoord z; #endif } R2VoxelKey; +/** + * Optional voxel lookup result; check found before reading voxel data. + * @ingroup queries + */ typedef struct R2VoxelQuery { + /** + * Integer voxel coordinates. + */ struct R2VoxelKey key; + /** + * World-space wheel center. + */ struct R2Vector center; + /** + * Voxel dimensions along each axis. + */ struct R2Vector size; + /** + * Whether a result exists; other result fields are meaningful only when this is 1. + */ R2Bool found; } R2VoxelQuery; +/** + * @ingroup errors + * Operation succeeded. + */ #define R2_OK 0 +/** + * @ingroup errors + * A required pointer was NULL. + */ #define R2_NULL_POINTER 1 +/** + * @ingroup errors + * An argument failed validation. + */ #define R2_INVALID_ARGUMENT 2 +/** + * @ingroup errors + * The entity handle is stale, invalid, or belongs to another world. + */ #define R2_INVALID_HANDLE 3 +/** + * @ingroup errors + * Output capacity is insufficient; the returned count is the required capacity. + */ #define R2_BUFFER_TOO_SMALL 4 +/** + * @ingroup errors + * This build or object does not support the operation. + */ #define R2_UNSUPPORTED 5 +/** + * @ingroup errors + * Rust panicked; discard objects mutated by the call. + */ #define R2_PANIC 6 +/** + * @ingroup errors + * No matching query result or object was found. + */ #define R2_NOT_FOUND 7 /** + * @ingroup errors * Conflicting or reentrant access to simulation state. No mutation was performed. */ #define R2_WORLD_BUSY 8 @@ -1589,94 +3879,182 @@ typedef struct R2VoxelQuery { extern "C" { #endif // __cplusplus +/** + * Return native default soft body material. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R2SoftBodyMaterial r2DefaultSoftBodyMaterial(void); +/** + * Return native default soft recovery settings. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R2SoftRecoverySettings r2DefaultSoftRecoverySettings(void); #if defined(RAPIER_FEM) +/** + * Return native default soft fem parameters. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R2SoftFemParameters r2DefaultSoftFemParameters(void); #endif +/** + * Return native default soft bodies settings. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R2SoftBodiesSettings r2DefaultSoftBodiesSettings(void); +/** + * Return native default integration parameters. This POD value owns no resources. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R2IntegrationParameters r2DefaultIntegrationParameters(void); +/** + * Return a copy of all world integration settings. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R2IntegrationParameters r2IntegrationParameters(const struct R2World *world); /** * Copies validated values; does not expose a writable alias to Rust memory. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R2Status r2SetIntegrationParameters(struct R2World *world, const struct R2IntegrationParameters *data); +/** + * Return native default joint desc. This POD value owns no resources. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R2JointDesc r2DefaultJointDesc(void); +/** + * Return a fixed joint description with native defaults; no allocation. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R2JointDesc r2FixedJointDesc(void); #if defined(RAPIER_DIM2) +/** + * Return a revolute joint description with native defaults; no allocation. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R2JointDesc r2RevoluteJointDesc(void); #endif #if defined(RAPIER_DIM3) /** * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + * @ingroup joints */ RAPIER_API RAPIER_CALL struct R2JointDesc r2RevoluteJointDesc(struct R2Vector axis_vector); #endif /** * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + * @ingroup joints */ RAPIER_API RAPIER_CALL struct R2JointDesc r2PrismaticJointDesc(struct R2Vector axis_vector); +/** + * Return a rope joint description with native defaults; no allocation. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R2JointDesc r2RopeJointDesc(R2Real length); +/** + * Return a spring joint description with native defaults; no allocation. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R2JointDesc r2SpringJointDesc(R2Real length, R2Real stiffness, R2Real damping); #if defined(RAPIER_DIM3) +/** + * Return a spherical joint description with native defaults; no allocation. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R2JointDesc r2SphericalJointDesc(void); #endif #if defined(RAPIER_DIM2) /** * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + * @ingroup joints */ RAPIER_API RAPIER_CALL struct R2JointDesc r2PinSlotJointDesc(struct R2Vector axis_vector); #endif +/** + * Create an impulse joint connecting two bodies in the same world. The world owns the joint; + * wake_up wakes the connected bodies. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R2ImpulseJointHandle r2InsertImpulseJoint(struct R2RigidBodyHandle body1, struct R2RigidBodyHandle body2, const struct R2JointDesc *joint); +/** + * Create an articulation joint between bodies in the same world. Returns an invalid handle on + * failure; check r2LastStatus. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R2MultibodyJointHandle r2InsertMultibodyJoint(struct R2RigidBodyHandle body1, struct R2RigidBodyHandle body2, const struct R2JointDesc *joint); +/** + * Return native default soft body desc. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R2SoftBodyDesc r2DefaultSoftBodyDesc(void); /** * Consumes no caller-owned resources. All borrowed arrays may be released on return. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyHandle r2InsertSoftBody(struct R2World *world, const struct R2SoftBodyDesc *desc); +/** + * Return native default soft mesh binding desc. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R2SoftMeshBindingDesc r2DefaultSoftMeshBindingDesc(void); +/** + * Create a deformable collider bound to a soft-body cluster. The world owns the collider; binding + * arrays are borrowed only during insertion. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL struct R2ColliderHandle r2InsertDeformableCollider(const struct R2ColliderDesc *collider, const struct R2SoftMeshBindingDesc *binding, struct R2RigidBodyHandle parent); +/** + * Return native default query options. This POD value owns no resources. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R2QueryOptions r2DefaultQueryOptions(void); +/** + * Return the closest ray hit, or report R2_NOT_FOUND on a miss. The ray is origin + direction * t + * for 0 <= t <= max_toi; direction need not be normalized. solid treats an interior origin as a + * hit at t = 0. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R2RayHit r2CastRay(const struct R2World *world, const struct R2QueryOptions *query_options, @@ -1685,6 +4063,13 @@ struct R2RayHit r2CastRay(const struct R2World *world, R2Real max_toi, R2Bool solid); +/** + * Return the closest surface projection within max_distance, or report R2_NOT_FOUND. With solid = + * 1, an interior point projects to itself. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R2PointProjection r2ProjectPoint(const struct R2World *world, const struct R2QueryOptions *query_options, @@ -1692,6 +4077,13 @@ struct R2PointProjection r2ProjectPoint(const struct R2World *world, R2Real max_distance, R2Bool solid); +/** + * Sweep shape from pose along velocity and return the first hit; report R2_NOT_FOUND on a miss. + * Time is bounded by options.max_time_of_impact. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL struct R2ShapeCastHit r2CastShape(const struct R2World *world, const struct R2QueryOptions *query_options, @@ -1700,6 +4092,13 @@ struct R2ShapeCastHit r2CastShape(const struct R2World *world, const R2SharedShape *shape, struct R2ShapeCastOptions options); +/** + * Copy handles of colliders containing the world-space point. + * @see @ref output_buffers + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL size_t r2IntersectPoint(const struct R2World *world, const struct R2QueryOptions *query_options, @@ -1707,6 +4106,14 @@ size_t r2IntersectPoint(const struct R2World *world, struct R2ColliderHandle *buffer, size_t capacity); +/** + * Copy handles of colliders intersecting the shape at its world-space pose. The shape is borrowed + * for this call. + * @see @ref output_buffers + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL size_t r2IntersectShape(const struct R2World *world, const struct R2QueryOptions *query_options, @@ -1715,6 +4122,14 @@ size_t r2IntersectShape(const struct R2World *world, struct R2ColliderHandle *buffer, size_t capacity); +/** + * Copy broad-phase candidates whose bounding boxes overlap the world-space AABB. Results may + * include false positives. + * @see @ref output_buffers + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL size_t r2IntersectAabbConservative(const struct R2World *world, const struct R2QueryOptions *query_options, @@ -1722,6 +4137,13 @@ size_t r2IntersectAabbConservative(const struct R2World *world, struct R2ColliderHandle *buffer, size_t capacity); +/** + * Return the closest ray collider and time, with found = 0 on a miss (R2_OK). The ray is origin + + * direction * t; max_toi bounds t. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R2RayToi r2CastRayToi(const struct R2World *world, const struct R2QueryOptions *query_options, @@ -1730,6 +4152,13 @@ struct R2RayToi r2CastRayToi(const struct R2World *world, R2Real max_toi, R2Bool solid); +/** + * Return the closest ray hit with found = 0 on a miss (R2_OK). The ray is origin + direction * t; + * solid treats an interior origin as a hit at t = 0. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R2OptionalRayHit r2TryCastRay(const struct R2World *world, const struct R2QueryOptions *query_options, @@ -1738,31 +4167,68 @@ struct R2OptionalRayHit r2TryCastRay(const struct R2World *world, R2Real max_toi, R2Bool solid); +/** + * Return a dynamic rigid-body description with native defaults; no allocation. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2DynamicRigidBodyDesc(void); +/** + * Return a fixed rigid-body description with native defaults; no allocation. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2FixedRigidBodyDesc(void); +/** + * Return a kinematic position based rigid-body description with native defaults; no allocation. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2KinematicPositionBasedRigidBodyDesc(void); +/** + * Return a kinematic velocity based rigid-body description with native defaults; no allocation. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2KinematicVelocityBasedRigidBodyDesc(void); +/** + * Return native default shape desc. This POD value owns no resources. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL struct R2ShapeDesc r2DefaultShapeDesc(void); +/** + * Build an owned shared shape from a description; release it with r2FreeSharedShape. Borrowed + * inputs may be released after this call. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2ShapeDesc_Build(const struct R2ShapeDesc *desc); +/** + * Return native default collider desc. This POD value owns no resources. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2DefaultColliderDesc(void); /** + * Return a ball description with the supplied radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2BallColliderDesc(R2Real radius); /** + * Return an axis-aligned box description with the supplied half-extents. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2CuboidColliderDesc(struct R2Vector half_extents); +/** + * Create a body from the description and return its world-bound handle. The world owns the body. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL struct R2RigidBodyHandle r2InsertRigidBody(struct R2World *world, const struct R2RigidBodyDesc *desc); @@ -1771,6 +4237,7 @@ struct R2RigidBodyHandle r2InsertRigidBody(struct R2World *world, * Insert a collider attached to a rigid body, using the world stored in its handle. * The parent handle is copied by value. The description is borrowed through this call. * Invalid or removed parents fail without inserting a collider. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderHandle r2InsertCollider(struct R2RigidBodyHandle parent, @@ -1779,36 +4246,71 @@ struct R2ColliderHandle r2InsertCollider(struct R2RigidBodyHandle parent, /** * Insert a collider without a rigid-body parent. The world owns the collider. * The description is borrowed through this call. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderHandle r2InsertColliderWithoutParent(struct R2World *world, const struct R2ColliderDesc *desc); +/** + * Return POD structure sizes for checking foreign-language layouts against this library. + * @ingroup errors + */ RAPIER_API RAPIER_CALL struct R2PodLayout r2PodLayout(void); +/** + * Allocate a character controller with native defaults; release it with + * r2FreeKinematicCharacterController. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R2KinematicCharacterController *r2NewKinematicCharacterController(void); +/** + * Release an owned kinematic character controller. NULL is allowed. Do not pass borrowed pointers + * or free the object twice. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2FreeKinematicCharacterController(struct R2KinematicCharacterController *controller); +/** + * Set the up direction; it must be finite and nonzero and is normalized on input. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2KinematicCharacterController_SetUp(struct R2KinematicCharacterController *controller, struct R2Vector up); +/** + * Set the collision separation margin; use a positive absolute or relative character length. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2KinematicCharacterController_SetOffset(struct R2KinematicCharacterController *controller, struct R2CharacterLength offset); +/** + * Enable or disable sliding along obstacles. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2KinematicCharacterController_SetSlide(struct R2KinematicCharacterController *controller, R2Bool enabled); +/** + * Set the maximum climb angle and minimum slide angle, in radians. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2KinematicCharacterController_SetSlopes(struct R2KinematicCharacterController *controller, R2Real max_climb_angle, R2Real min_slide_angle); +/** + * Configure automatic stepping over obstacles. enabled = 0 disables it. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2KinematicCharacterController_SetAutostep(struct R2KinematicCharacterController *controller, R2Bool enabled, @@ -1816,13 +4318,21 @@ R2Status r2KinematicCharacterController_SetAutostep(struct R2KinematicCharacterC struct R2CharacterLength min_width, R2Bool include_dynamic_bodies); +/** + * Configure downward ground snapping. enabled = 0 disables it. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2KinematicCharacterController_SetSnapToGround(struct R2KinematicCharacterController *controller, R2Bool enabled, struct R2CharacterLength distance); /** - * Computes movement without moving any collider. Use the returned translation to set the character target. + * Computes movement without moving any collider. Use the returned translation to set the character + * target. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup controllers */ RAPIER_API RAPIER_CALL struct R2CharacterMovement r2KinematicCharacterController_MoveShape(const struct R2World *world, @@ -1833,13 +4343,20 @@ struct R2CharacterMovement r2KinematicCharacterController_MoveShape(const struct struct R2Pose pose, struct R2Vector desired_translation); +/** + * Copy collisions recorded by the most recent MoveShape call. + * @see @ref output_buffers + * @ingroup controllers + */ RAPIER_API RAPIER_CALL size_t r2KinematicCharacterController_Collisions(const struct R2KinematicCharacterController *controller, struct R2CharacterCollision *buffer, size_t capacity); /** - * Applies impulses for the most recent move_shape collisions. Use the same world, shape, dt and filter. + * Applies impulses for the most recent move_shape collisions. Use the same world, shape, dt and + * filter. + * @ingroup controllers */ RAPIER_API RAPIER_CALL R2Status r2KinematicCharacterController_SolveCharacterCollisionImpulses(const struct R2KinematicCharacterController *controller, @@ -1848,19 +4365,38 @@ R2Status r2KinematicCharacterController_SolveCharacterCollisionImpulses(const st R2Real mass, const struct R2QueryFilter *filter); +/** + * Allocate a PID controller with supplied gains and controlled axes. Release with + * r2FreePidController. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R2PidController *r2NewPidController(void); +/** + * Release an owned pid controller. NULL is allowed. Do not pass borrowed pointers or free the + * object twice. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2FreePidController(struct R2PidController *controller); +/** + * Return a copy of the proportional, integral, and derivative gains. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R2PidGains r2PidController_Gains(const struct R2PidController *controller); +/** + * Replace the proportional, integral, and derivative gains. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2PidController_SetGains(struct R2PidController *controller, struct R2PidGains gains); /** * AxesMask bits match Rapier: linear X/Y/Z are 1/2/4, angular X/Y/Z are 8/16/32. + * @ingroup controllers */ RAPIER_API RAPIER_CALL R2Status r2PidController_SetAxes(struct R2PidController *controller, @@ -1868,6 +4404,7 @@ R2Status r2PidController_SetAxes(struct R2PidController *controller, /** * Compute a velocity correction, preserving the body's state and updating PID integrals. + * @ingroup controllers */ RAPIER_API RAPIER_CALL struct R2VelocityCorrection r2PidController_RigidBodyCorrection(struct R2PidController *controller, @@ -1877,24 +4414,47 @@ struct R2VelocityCorrection r2PidController_RigidBodyCorrection(struct R2PidCont struct R2Vector target_linvel, R2AngVector target_angvel); +/** + * Return a copy of slide, slope, and ground-snap settings. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R2CharacterControllerSettings r2KinematicCharacterController_Settings(const struct R2KinematicCharacterController *controller); #if defined(RAPIER_DIM3) +/** + * Return native default wheel tuning. This POD value owns no resources. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R2WheelTuning r2DefaultWheelTuning(void); #endif #if defined(RAPIER_DIM3) +/** + * Allocate a vehicle controller bound to its chassis body. The chassis world must outlive the + * controller. Release with r2FreeDynamicRayCastVehicleController. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R2DynamicRayCastVehicleController *r2NewDynamicRayCastVehicleController(struct R2RigidBodyHandle chassis); #endif #if defined(RAPIER_DIM3) +/** + * Release an owned dynamic ray cast vehicle controller. NULL is allowed. Do not pass borrowed + * pointers or free the object twice. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2FreeDynamicRayCastVehicleController(struct R2DynamicRayCastVehicleController *controller); #endif #if defined(RAPIER_DIM3) +/** + * Append a wheel and return its zero-based index. Connection, suspension direction, and axle + * are in chassis-local coordinates. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL size_t r2DynamicRayCastVehicleController_AddWheel(struct R2DynamicRayCastVehicleController *controller, struct R2Vector connection, @@ -1906,6 +4466,10 @@ size_t r2DynamicRayCastVehicleController_AddWheel(struct R2DynamicRayCastVehicle #endif #if defined(RAPIER_DIM3) +/** + * Set the chassis up/forward axis indices (0 = X, 1 = Y, 2 = Z). + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2DynamicRayCastVehicleController_SetAxes(struct R2DynamicRayCastVehicleController *controller, size_t up, @@ -1913,6 +4477,10 @@ R2Status r2DynamicRayCastVehicleController_SetAxes(struct R2DynamicRayCastVehicl #endif #if defined(RAPIER_DIM3) +/** + * Set a wheel engine force, brake force, and steering angle in radians. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2DynamicRayCastVehicleController_SetWheelControls(struct R2DynamicRayCastVehicleController *controller, size_t index, @@ -1922,6 +4490,10 @@ R2Status r2DynamicRayCastVehicleController_SetWheelControls(struct R2DynamicRayC #endif #if defined(RAPIER_DIM3) +/** + * Ray-cast wheel contacts and apply vehicle forces for dt seconds. Does not step the world. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Status r2DynamicRayCastVehicleController_UpdateVehicle(struct R2DynamicRayCastVehicleController *controller, R2Real dt, @@ -1929,22 +4501,38 @@ R2Status r2DynamicRayCastVehicleController_UpdateVehicle(struct R2DynamicRayCast #endif #if defined(RAPIER_DIM3) +/** + * Return signed chassis speed along its forward direction. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R2Real r2DynamicRayCastVehicleController_CurrentVehicleSpeed(const struct R2DynamicRayCastVehicleController *controller); #endif #if defined(RAPIER_DIM3) +/** + * Copy current wheel state in wheel insertion order. + * @see @ref output_buffers + * @ingroup controllers + */ RAPIER_API RAPIER_CALL size_t r2DynamicRayCastVehicleController_Wheels(const struct R2DynamicRayCastVehicleController *controller, struct R2WheelState *buffer, size_t capacity); #endif +/** + * Propagate all modified body poses to attached colliders. Run collision detection or step before + * querying the broad phase. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RigidBodyPropagateModifiedBodyPositionsToColliders(struct R2World *world); /** * Copies the island manager's active body handles. + * @see @ref output_buffers + * @ingroup worlds */ RAPIER_API RAPIER_CALL size_t r2ActiveRigidBodies(const struct R2World *world, @@ -1953,6 +4541,7 @@ size_t r2ActiveRigidBodies(const struct R2World *world, /** * Wake a body by handle, including a soft-body cluster proxy. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_WakeUp(struct R2RigidBodyHandle handle, @@ -1963,6 +4552,7 @@ R2Status r2RigidBody_WakeUp(struct R2RigidBodyHandle handle, * be restored at the end of a scope. Status returns are unchanged. A handler * that returns lets the caller recover by checking the status; a fail-fast * handler may terminate the process. Includes R2_NOT_FOUND query misses. + * @ingroup errors */ RAPIER_API RAPIER_CALL struct R2ErrorHandler r2SetErrorHandler(struct R2ErrorHandler handler); @@ -1971,83 +4561,161 @@ RAPIER_API RAPIER_CALL struct R2ErrorHandler r2SetErrorHandler(struct R2ErrorHan * LastError does not clear it. Infallible value constructors do not change it. * Check immediately after a fallible value-returning operation when recovering * from errors instead of using a fail-fast error callback. + * @ingroup errors */ RAPIER_API RAPIER_CALL R2Status r2LastStatus(void); /** * Thread-local UTF-8 diagnostic, valid until the next fallible call on this thread. + * @ingroup errors */ RAPIER_API RAPIER_CALL const char *r2LastError(void); +/** + * Create an owned ball shape. Release it with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2BallSharedShape(R2Real radius); +/** + * Create an owned cuboid shape. Release it with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2CuboidSharedShape(struct R2Vector half_extents); +/** + * Create an owned round cuboid shape. Release it with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2RoundCuboidSharedShape(struct R2Vector half_extents, R2Real border_radius); +/** + * Create an owned capsule shape. Release it with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2CapsuleSharedShape(struct R2Vector a, struct R2Vector b, R2Real radius); +/** + * Create an owned segment shape. Release it with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2SegmentSharedShape(struct R2Vector a, struct R2Vector b); +/** + * Create an owned triangle shape. Release it with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2TriangleSharedShape(struct R2Vector a, struct R2Vector b, struct R2Vector c); +/** + * Create an owned halfspace shape. Release it with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2HalfspaceSharedShape(struct R2Vector normal); #if defined(RAPIER_DIM3) +/** + * Create an owned cylinder shape. Release it with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2CylinderSharedShape(R2Real half_height, R2Real radius); #endif #if defined(RAPIER_DIM3) +/** + * Create an owned cone shape. Release it with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2ConeSharedShape(R2Real half_height, R2Real radius); #endif +/** + * Create an owned compound shape; each child pose is relative to the compound. Child shapes are + * shared, not consumed. Release with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2CompoundSharedShape(struct R2CompoundShapeView children); +/** + * Remove the collider and update its parent body mass properties. wake_up wakes the parent. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL R2Status r2RemoveCollider(struct R2ColliderHandle handle, R2Bool wake_up); +/** + * Remove an impulse joint. wake_up wakes its connected bodies. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2RemoveImpulseJoint(struct R2ImpulseJointHandle handle, R2Bool wake_up); +/** + * Copy entity handles. + * @see @ref output_buffers + * @ingroup joints + */ RAPIER_API RAPIER_CALL size_t r2ImpulseJointHandles(const struct R2World *world, struct R2ImpulseJointHandle *buffer, size_t capacity); +/** + * Remove an articulation joint. wake_up wakes affected bodies. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2RemoveMultibodyJoint(struct R2MultibodyJointHandle handle, R2Bool wake_up); +/** + * Copy entity handles. + * @see @ref output_buffers + * @ingroup joints + */ RAPIER_API RAPIER_CALL size_t r2MultibodyJointHandles(const struct R2World *world, struct R2MultibodyJointHandle *buffer, size_t capacity); +/** + * Return the two bodies connected by an impulse joint. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R2JointBodies r2ImpulseJoint_Bodies(struct R2ImpulseJointHandle handle); +/** + * Return native default inverse kinematics options. This POD value owns no resources. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R2InverseKinematicsOptions r2DefaultInverseKinematicsOptions(void); +/** + * Return the articulation degrees of freedom associated with the joint. + * @ingroup joints + */ RAPIER_API RAPIER_CALL size_t r2MultibodyJoint_Ndofs(struct R2MultibodyJointHandle handle); /** * Read/write displacement buffer must contain exactly ndofs entries; zero it for a fresh solve. + * @ingroup joints */ RAPIER_API RAPIER_CALL R2Status r2MultibodyJoint_InverseKinematics(struct R2MultibodyJointHandle handle, @@ -2058,6 +4726,10 @@ R2Status r2MultibodyJoint_InverseKinematics(struct R2MultibodyJointHandle handle R2Real *displacements, size_t count); +/** + * Apply generalized articulation displacements in native degree-of-freedom order. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2MultibodyJoint_ApplyDisplacements(struct R2MultibodyJointHandle handle, const R2Real *displacements, @@ -2065,155 +4737,357 @@ R2Status r2MultibodyJoint_ApplyDisplacements(struct R2MultibodyJointHandle handl /** * Frees an owned object; NULL is allowed. Never free a borrowed pointer. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2Status r2FreeSharedShape(R2SharedShape *object); /** - * Creates an independent owned copy. + * Create an owned wrapper sharing the same immutable geometry. Release it with + * r2FreeSharedShape. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2SharedShape_Clone(const R2SharedShape *object); +/** + * Return the number of rigid body objects in the world. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL size_t r2RigidBodyCount(const struct R2World *world); +/** + * Copy entity handles. + * @see @ref output_buffers + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL size_t r2RigidBodyHandles(const struct R2World *world, struct R2RigidBodyHandle *buffer, size_t capacity); +/** + * Test whether the live world contains this rigid body handle. A removed/stale handle returns + * false. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL R2Bool r2RigidBody_Contains(struct R2RigidBodyHandle handle); +/** + * Return the number of collider objects in the world. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL size_t r2ColliderCount(const struct R2World *world); +/** + * Copy entity handles. + * @see @ref output_buffers + * @ingroup colliders + */ RAPIER_API RAPIER_CALL size_t r2ColliderHandles(const struct R2World *world, struct R2ColliderHandle *buffer, size_t capacity); +/** + * Test whether the live world contains this collider handle. A removed/stale handle returns false. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL R2Bool r2Collider_Contains(struct R2ColliderHandle handle); +/** + * Return the number of soft body objects in the world. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r2SoftBodyCount(const struct R2World *world); +/** + * Copy entity handles. + * @see @ref output_buffers + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r2SoftBodyHandles(const struct R2World *world, struct R2SoftBodyHandle *buffer, size_t capacity); +/** + * Test whether the live world contains this soft body handle. A removed/stale handle returns + * false. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Bool r2SoftBody_Contains(struct R2SoftBodyHandle handle); /** * Remove a body and its joints, optionally keeping colliders as standalone objects. * Returns whether a body was removed; a stale handle returns false without error. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Bool r2RemoveRigidBody(struct R2RigidBodyHandle handle, R2Bool remove_attached_colliders); +/** + * Return the world setting documented by R2IntegrationParameters::dt. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Real r2TimeStep(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::dt. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetTimeStep(struct R2World *world, R2Real value); +/** + * Return the world setting documented by R2IntegrationParameters::minCcdDt. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Real r2MinCcdDt(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::minCcdDt. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetMinCcdDt(struct R2World *world, R2Real value); +/** + * Return the world setting documented by R2IntegrationParameters::lengthUnit. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Real r2LengthUnit(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::lengthUnit. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetLengthUnit(struct R2World *world, R2Real value); +/** + * Return the world setting documented by R2IntegrationParameters::warmstartCoefficient. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Real r2WarmstartCoefficient(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::warmstartCoefficient. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetWarmstartCoefficient(struct R2World *world, R2Real value); +/** + * Return the world setting documented by R2IntegrationParameters::normalizedAllowedLinearError. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Real r2NormalizedAllowedLinearError(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::normalizedAllowedLinearError. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetNormalizedAllowedLinearError(struct R2World *world, R2Real value); +/** + * Return the world setting documented by + * R2IntegrationParameters::normalizedMaxCorrectiveVelocity. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Real r2NormalizedMaxCorrectiveVelocity(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::normalizedMaxCorrectiveVelocity. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetNormalizedMaxCorrectiveVelocity(struct R2World *world, R2Real value); +/** + * Return the world setting documented by R2IntegrationParameters::normalizedPredictionDistance. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Real r2NormalizedPredictionDistance(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::normalizedPredictionDistance. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetNormalizedPredictionDistance(struct R2World *world, R2Real value); +/** + * Return the world setting documented by R2IntegrationParameters::normalizedMaxLinearVelocity. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Real r2NormalizedMaxLinearVelocity(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::normalizedMaxLinearVelocity. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetNormalizedMaxLinearVelocity(struct R2World *world, R2Real value); +/** + * Return the world setting documented by + * R2IntegrationParameters::normalizedContactRecycleDistance. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Real r2NormalizedContactRecycleDistance(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::normalizedContactRecycleDistance. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetNormalizedContactRecycleDistance(struct R2World *world, R2Real value); +/** + * Return the world setting documented by R2IntegrationParameters::numSolverIterations. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL size_t r2NumSolverIterations(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::numSolverIterations. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetNumSolverIterations(struct R2World *world, size_t value); +/** + * Return the world setting documented by R2IntegrationParameters::numInternalPgsIterations. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL size_t r2NumInternalPgsIterations(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::numInternalPgsIterations. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetNumInternalPgsIterations(struct R2World *world, size_t value); +/** + * Return the world setting documented by + * R2IntegrationParameters::numInternalStabilizationIterations. + * @ingroup errors + */ RAPIER_API RAPIER_CALL size_t r2NumInternalStabilizationIterations(const struct R2World *world); +/** + * Set the world setting documented by + * R2IntegrationParameters::numInternalStabilizationIterations. + * @ingroup errors + */ RAPIER_API RAPIER_CALL R2Status r2SetNumInternalStabilizationIterations(struct R2World *world, size_t value); +/** + * Return the world setting documented by R2IntegrationParameters::maxCcdSubsteps. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL size_t r2MaxCcdSubsteps(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::maxCcdSubsteps. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetMaxCcdSubsteps(struct R2World *world, size_t value); +/** + * Return the world setting documented by R2IntegrationParameters::contactClustering. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Bool r2ContactClustering(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::contactClustering. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetContactClustering(struct R2World *world, R2Bool value); +/** + * Return the world setting documented by R2IntegrationParameters::contactRecycling. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Bool r2ContactRecycling(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::contactRecycling. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetContactRecycling(struct R2World *world, R2Bool value); +/** + * Return the world setting documented by R2IntegrationParameters::frictionInBiasPass. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Bool r2FrictionInBiasPass(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::frictionInBiasPass. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetFrictionInBiasPass(struct R2World *world, R2Bool value); +/** + * Return the world setting documented by R2IntegrationParameters::warmstartJoints. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Bool r2WarmstartJoints(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::warmstartJoints. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2SetWarmstartJoints(struct R2World *world, R2Bool value); +/** + * Return the world setting documented by R2IntegrationParameters::contactSoftness. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R2SpringCoefficients r2ContactSoftness(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::contactSoftness. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2SetContactSoftness(struct R2World *world, struct R2SpringCoefficients value); +/** + * Return the world setting documented by R2IntegrationParameters::staticContactSoftness. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R2SpringCoefficients r2StaticContactSoftness(const struct R2World *world); +/** + * Set the world setting documented by R2IntegrationParameters::staticContactSoftness. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2SetStaticContactSoftness(struct R2World *world, struct R2SpringCoefficients value); /** * Applies Rapier's persistent one-way platform logic to the borrowed manifold. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R2Status r2ContactModificationContext_UpdateAsOnewayPlatform(struct R2ContactModificationContext *context, @@ -2222,41 +5096,83 @@ R2Status r2ContactModificationContext_UpdateAsOnewayPlatform(struct R2ContactMod /** * Sets the tangent velocity of every rigid solver contact in this manifold. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R2Status r2ContactModificationContext_SetTangentVelocity(struct R2ContactModificationContext *context, struct R2Vector velocity); +/** + * Allocate an empty event collector; release it with r2FreeEventCollector. + * @ingroup events + */ RAPIER_API RAPIER_CALL struct R2EventCollector *r2NewEventCollector(void); +/** + * Release an owned event collector. NULL is allowed. Do not pass borrowed pointers or free the + * object twice. + * @ingroup events + */ RAPIER_API RAPIER_CALL R2Status r2FreeEventCollector(struct R2EventCollector *events); +/** + * Discard all collected events. Does not change the world. + * @ingroup events + */ RAPIER_API RAPIER_CALL R2Status r2EventCollector_Clear(struct R2EventCollector *events); +/** + * Copy the collected collision start/stop events without removing them. + * @see @ref output_buffers + * @ingroup events + */ RAPIER_API RAPIER_CALL size_t r2EventCollector_CollisionEvents(const struct R2EventCollector *events, struct R2CollisionEvent *buffer, size_t capacity); +/** + * Copy the collected contact-force events without removing them. + * @see @ref output_buffers + * @ingroup events + */ RAPIER_API RAPIER_CALL size_t r2EventCollector_ContactForceEvents(const struct R2EventCollector *events, struct R2ContactForceEvent *buffer, size_t capacity); +/** + * Return the number of queued soft-body tear events. + * @ingroup events + */ RAPIER_API RAPIER_CALL size_t r2EventCollector_TearEventCount(const struct R2EventCollector *events); +/** + * Return an owned copy of a queued tear event; release with r2FreeSoftBodyTearEvent. Does + * not remove the queued event. + * @ingroup events + */ RAPIER_API RAPIER_CALL struct R2SoftBodyTearEvent *r2EventCollector_TearEvent(const struct R2EventCollector *events, size_t index); +/** + * Return the world-space gravitational acceleration. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R2Vector r2Gravity(const struct R2World *world); +/** + * Set the world-space gravitational acceleration. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2SetGravity(struct R2World *world, struct R2Vector value); /** * Hooks and events may be NULL. This call invalidates all borrowed set-element pointers. * Advance simulation by one timestep. Hooks and events may be NULL. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R2Status r2Step(struct R2World *world, @@ -2265,27 +5181,45 @@ R2Status r2Step(struct R2World *world, /** * Refresh collision detection without advancing simulation. Hooks and events may be NULL. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R2Status r2DetectCollisions(struct R2World *world, const struct R2PhysicsHooks *hooks, const struct R2EventCollector *events); +/** + * Borrow snapshot bytes without copying; valid until r2FreeBytes. Never free the returned data + * pointer. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R2ByteView r2Bytes_Data(const struct R2Bytes *bytes); +/** + * Release an owned snapshot byte buffer. NULL is allowed. Do not pass borrowed pointers or free + * the object twice. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R2Status r2FreeBytes(struct R2Bytes *bytes); +/** + * Return owned snapshot bytes; release them with r2FreeBytes. See @ref snapshots for + * restoration and handle lifetimes. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R2Bytes *r2SerializeWorld(const struct R2World *world); /** - * Restore ONLY trusted snapshots produced by the identical Rapier build. Snapshots are not a stable file format. + * Restore ONLY trusted snapshots produced by the identical Rapier build. Snapshots are not a + * stable file format. + * @ingroup worlds */ -RAPIER_API RAPIER_CALL -struct R2World *r2DeserializeWorld(const uint8_t *data, - size_t count); +RAPIER_API RAPIER_CALL struct R2World *r2DeserializeWorld(const uint8_t *data, size_t count); /** * Color is HSLA (hue in degrees), matching Rapier DebugColor. mode uses DebugRenderMode bits. + * @see @ref output_buffers + * @ingroup worlds */ RAPIER_API RAPIER_CALL size_t r2DebugRender(const struct R2World *world, @@ -2293,133 +5227,265 @@ size_t r2DebugRender(const struct R2World *world, struct R2DebugLine *buffer, size_t capacity); +/** + * Set the world setting documented by R2SoftBodiesSettings::resweepStrain. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2SoftBodiesSetResweepStrain(struct R2World *world, R2Real value); +/** + * Return the world setting documented by R2SoftBodiesSettings::resweepStrain. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Real r2SoftBodiesResweepStrain(const struct R2World *world); +/** + * Set the world setting documented by R2SoftBodiesSettings::contactStiffening. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2SoftBodiesSetContactStiffening(struct R2World *world, R2Real value); +/** + * Return the world setting documented by R2SoftBodiesSettings::contactStiffening. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Real r2SoftBodiesContactStiffening(const struct R2World *world); +/** + * Set the world setting documented by R2SoftBodiesSettings::maxExtraSubsteps. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2SoftBodiesSetMaxExtraSubsteps(struct R2World *world, size_t value); +/** + * Return the world setting documented by R2SoftBodiesSettings::maxExtraSubsteps. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r2SoftBodiesMaxExtraSubsteps(const struct R2World *world); +/** + * Set the world setting documented by R2SoftRecoverySettings::authoredVelocityMargin. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetAuthoredVelocityMargin(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::edgeSpeculation. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetEdgeSpeculation(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::invertedCellDetection. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetInvertedCellDetection(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::selfCrossingDetection. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetSelfCrossingDetection(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::detectionMotionGating. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetDetectionMotionGating(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::crossBodyDetection. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetCrossBodyDetection(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::selfStandDown. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetSelfStandDown(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::crossBodyExpelGate. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetCrossBodyExpelGate(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::edgeStandDown. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetEdgeStandDown(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::crossingRepulsion. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetCrossingRepulsion(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::crossingRepulsionGuide. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetCrossingRepulsionGuide(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::crossingRepulsionSelfGuide. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetCrossingRepulsionSelfGuide(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::recoveryPace. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetRecoveryPace(struct R2World *world, R2Real value); +/** + * Set the world setting documented by R2SoftRecoverySettings::overlapConstraints. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetOverlapConstraints(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::overlapRigid. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetOverlapRigid(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::overlapSkipSelfTangled. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetOverlapSkipSelfTangled(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::overlapEdgeStandDown. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetOverlapEdgeStandDown(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::overlapConstraintPace. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetOverlapConstraintPace(struct R2World *world, R2Real value); +/** + * Set the world setting documented by R2SoftRecoverySettings::overlapSkinVolume. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetOverlapSkinVolume(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::overlapKeptDepth. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetOverlapKeptDepth(struct R2World *world, R2Real value); +/** + * Set the world setting documented by R2SoftRecoverySettings::overlapSelfRegions. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetOverlapSelfRegions(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::overlapNormalPush. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetOverlapNormalPush(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::overlapMultiVolume. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetOverlapMultiVolume(struct R2World *world, R2Bool value); +/** + * Set the world setting documented by R2SoftRecoverySettings::overlapProgressMargin. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RecoverySetOverlapProgressMargin(struct R2World *world, R2Real value); #if defined(RAPIER_FEM) +/** + * Set the world setting documented by R2SoftFemParameters::linearTolerance. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2FemSetLinearTolerance(struct R2World *world, R2Real value); #endif #if defined(RAPIER_FEM) +/** + * Set the world setting documented by R2SoftFemParameters::maxLinearIterations. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2FemSetMaxLinearIterations(struct R2World *world, size_t value); #endif #if defined(RAPIER_FEM) +/** + * Set the world setting documented by R2SoftFemParameters::maxDenseDofs. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2FemSetMaxDenseDofs(struct R2World *world, size_t value); #endif @@ -2428,6 +5494,7 @@ RAPIER_API RAPIER_CALL R2Status r2FemSetMaxDenseDofs(struct R2World *world, size * Takes effect on the next step. Reconfiguration must not race with a step or callback. * Returns R2_UNSUPPORTED in builds without the parallel feature; keeps the previous * pool when constructing the new one fails. The pool is not included in snapshots. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R2Status r2SetNumThreads(struct R2World *world, size_t num_threads); @@ -2435,18 +5502,21 @@ RAPIER_API RAPIER_CALL R2Status r2SetNumThreads(struct R2World *world, size_t nu * Removes the world's dedicated pool. A parallel build then uses the calling * context's Rayon pool (normally the global pool), not a single worker. * Returns R2_UNSUPPORTED in a build without the parallel feature. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R2Status r2ClearThreadPool(struct R2World *world); /** * Size of the world's dedicated pool, or zero if a parallel build has no dedicated * pool configured. Returns one for a build without the parallel feature. + * @ingroup worlds */ RAPIER_API RAPIER_CALL size_t r2NumThreads(const struct R2World *world); /** * Enable or disable the native pipeline profiling counters. Enabling returns * R2_UNSUPPORTED if the library was built without the profiler feature. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R2Status r2SetCountersEnabled(struct R2World *world, R2Bool enabled); @@ -2454,41 +5524,81 @@ RAPIER_API RAPIER_CALL R2Status r2SetCountersEnabled(struct R2World *world, R2Bo * Native engine time of the most recent step, in milliseconds, as in the Rust testbed. * Enable counters before stepping. Excludes C callbacks outside the step, rendering, * and dispatch into a dedicated thread pool; remains unchanged while paused. + * @ingroup worlds */ RAPIER_API RAPIER_CALL double r2StepTimeMs(const struct R2World *world); /** * Read ONLY trusted legacy rigid-world snapshots from debug_deserialize3.rs, * produced by the identical Rapier build. This is not a stable interchange format. + * Import trusted legacy Rust testbed rigid-state bytes into a new owned world. Release with + * r2FreeWorld; see @ref snapshots. + * @ingroup worlds */ RAPIER_API RAPIER_CALL struct R2World *r2DeserializeRigidState(const uint8_t *data, size_t count); +/** + * Return native default query filter. This POD value owns no resources. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R2QueryFilter r2DefaultQueryFilter(void); +/** + * Return native default shape cast options. This POD value owns no resources. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R2ShapeCastOptions r2DefaultShapeCastOptions(void); +/** + * Remove a soft body and its associated simulation objects. Invalidates its handle. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RemoveSoftBody(struct R2SoftBodyHandle handle); +/** + * Wake the soft body and its rigid proxies. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_WakeUp(struct R2SoftBodyHandle handle); +/** + * Release an owned soft body tear event. NULL is allowed. Do not pass borrowed pointers or free + * the object twice. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2FreeSoftBodyTearEvent(struct R2SoftBodyTearEvent *event); +/** + * Return the source soft-body handle for this tear event. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R2SoftBodyHandle r2SoftBodyTearEvent_SoftBody(const struct R2SoftBodyTearEvent *event); +/** + * Copy the soft-body handles produced by the tear. + * @see @ref output_buffers + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r2SoftBodyTearEvent_Bodies(const struct R2SoftBodyTearEvent *event, struct R2SoftBodyHandle *buffer, size_t capacity); +/** + * Return the destination body and particle index for an original particle. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R2ParticleDestination r2SoftBodyTearEvent_ParticleDestination(const struct R2SoftBodyTearEvent *event, uint32_t particle); /** * Flat indices; element arity follows the corresponding Rust event field. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBodyTearEvent_TornEdges(const struct R2SoftBodyTearEvent *event, @@ -2497,6 +5607,8 @@ size_t r2SoftBodyTearEvent_TornEdges(const struct R2SoftBodyTearEvent *event, /** * Flat indices; element arity follows the corresponding Rust event field. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBodyTearEvent_TornCells(const struct R2SoftBodyTearEvent *event, @@ -2505,6 +5617,8 @@ size_t r2SoftBodyTearEvent_TornCells(const struct R2SoftBodyTearEvent *event, /** * Flat indices; element arity follows the corresponding Rust event field. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBodyTearEvent_RemovedEdges(const struct R2SoftBodyTearEvent *event, @@ -2513,6 +5627,8 @@ size_t r2SoftBodyTearEvent_RemovedEdges(const struct R2SoftBodyTearEvent *event, /** * Flat indices; element arity follows the corresponding Rust event field. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBodyTearEvent_SplitParticles(const struct R2SoftBodyTearEvent *event, @@ -2521,28 +5637,50 @@ size_t r2SoftBodyTearEvent_SplitParticles(const struct R2SoftBodyTearEvent *even /** * Flat indices; element arity follows the corresponding Rust event field. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBodyTearEvent_InsertedParticles(const struct R2SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); +/** + * Copy original particle indices belonging to a resulting piece. + * @see @ref output_buffers + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r2SoftBodyTearEvent_PieceParticles(const struct R2SoftBodyTearEvent *event, size_t piece_index, uint32_t *buffer, size_t capacity); +/** + * Copy cluster-to-piece and rigid-proxy remapping records. + * @see @ref output_buffers + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r2SoftBodyTearEvent_Clusters(const struct R2SoftBodyTearEvent *event, struct R2SoftClusterSplit *buffer, size_t capacity); +/** + * Copy impulse-joint remapping records produced by the tear. + * @see @ref output_buffers + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r2SoftBodyTearEvent_MovedJoints(const struct R2SoftBodyTearEvent *event, struct R2SoftJointMove *buffer, size_t capacity); +/** + * Tear the selected edges and return an owned remapping event. Release it with + * r2FreeSoftBodyTearEvent. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R2SoftBodyTearEvent *r2SoftBody_Tear(struct R2SoftBodyHandle handle, const uint32_t *edges, @@ -2550,11 +5688,19 @@ struct R2SoftBodyTearEvent *r2SoftBody_Tear(struct R2SoftBodyHandle handle, const uint32_t *cells, size_t cell_count); +/** + * Create a rigid proxy cluster from the supplied particle indices and return its cluster index. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL uint32_t r2SoftBody_AddCluster(struct R2SoftBodyHandle handle, const uint32_t *particles, size_t count); +/** + * Remove the selected cluster and its rigid proxy. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_RemoveCluster(struct R2SoftBodyHandle handle, uint32_t cluster); @@ -2562,6 +5708,7 @@ R2Status r2SoftBody_RemoveCluster(struct R2SoftBodyHandle handle, /** * Optional particle destination after a tear. Missing destinations are normal and set * found to false; body/index are only written when a destination exists. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2OptionalParticleDestination r2SoftBodyTearEvent_TryParticleDestination(const struct R2SoftBodyTearEvent *event, @@ -2570,14 +5717,24 @@ struct R2OptionalParticleDestination r2SoftBodyTearEvent_TryParticleDestination( /** * Cut using DIM points (a segment in 2D, triangle in 3D). A no-op returns a null event. * The optional owned event must be freed with FreeSoftBodyTearEvent. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyTearEvent *r2CutSoftBody(struct R2SoftBodyHandle handle, const struct R2Vector *blade); +/** + * Return volume-meshing settings for the supplied cell size; this POD value requires no + * destructor. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R2VolumeMeshParameters r2NewVolumeMeshParameters(R2Real cell_size); +/** + * Return ABI version, dimension, scalar size, and pointer size of the linked library. + * @ingroup errors + */ RAPIER_API RAPIER_CALL struct R2BuildInfo r2BuildInfo(void); /** @@ -2585,6 +5742,7 @@ RAPIER_API RAPIER_CALL struct R2BuildInfo r2BuildInfo(void); * The suffix identifies the C bindings revision for the Rust crate version. * The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. * This release identifier is independent of the ABI compatibility version. + * @ingroup errors */ RAPIER_API RAPIER_CALL const char *r2Version(void); @@ -2593,47 +5751,86 @@ RAPIER_API RAPIER_CALL const char *r2Version(void); * Custom profiles report the corresponding inherited Cargo profile category. * The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. * This is independent of the consumer's build mode and of per-package optimization overrides. + * @ingroup errors */ RAPIER_API RAPIER_CALL const char *r2BuildProfile(void); +/** + * Return profiling, SIMD width, and parallelism of the linked library. + * @ingroup errors + */ RAPIER_API RAPIER_CALL struct R2BuildFeatures r2BuildFeatures(void); +/** + * Create an owned heightfield shape from copied samples. 3D samples are column-major, with rows * + * columns entries. Release with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2HeightfieldSharedShape(struct R2RealView heights, size_t rows, size_t columns, struct R2Vector scale); +/** + * Compute the shape axis-aligned bounds at the supplied world-space pose. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL struct R2Aabb r2SharedShape_ComputeAabb(const R2SharedShape *shape, struct R2Pose pose); +/** + * Compute local mass properties for the supplied nonnegative density. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL struct R2MassProperties r2SharedShape_MassProperties(const R2SharedShape *shape, R2Real density); +/** + * Test whether the world-space point lies inside the shape at pose. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2Bool r2SharedShape_ContainsPoint(const R2SharedShape *shape, struct R2Pose pose, struct R2Vector point); +/** + * Copy current narrow-phase contact pairs, including pairs without active solver contacts. + * @see @ref output_buffers + * @ingroup events + */ RAPIER_API RAPIER_CALL size_t r2ContactPairs(const struct R2World *world, struct R2ContactPair *buffer, size_t capacity); +/** + * Return the narrow-phase contact pair for two colliders, or report R2_NOT_FOUND. + * @ingroup events + */ RAPIER_API RAPIER_CALL struct R2ContactPair r2ContactPair(struct R2ColliderHandle collider1, struct R2ColliderHandle collider2); +/** + * Copy current sensor intersection pairs from the narrow phase. + * @see @ref output_buffers + * @ingroup events + */ RAPIER_API RAPIER_CALL size_t r2IntersectionPairs(const struct R2World *world, struct R2IntersectionPair *buffer, size_t capacity); /** - * Contact points in collider-local space; normal in world space. Geometric manifolds may be recycled. + * Contact points in collider-local space; normal in world space. Geometric manifolds may be + * recycled. * For clustered solver impulses use contact pair totals. Soft pairs have no rigid manifolds. + * @see @ref output_buffers + * @ingroup worlds */ RAPIER_API RAPIER_CALL size_t r2ContactPoints(struct R2ColliderHandle collider1, @@ -2641,11 +5838,20 @@ size_t r2ContactPoints(struct R2ColliderHandle collider1, struct R2ContactPoint *buffer, size_t capacity); +/** + * Copy the articulation generalized velocities in native degree-of-freedom order. + * @see @ref output_buffers + * @ingroup joints + */ RAPIER_API RAPIER_CALL size_t r2MultibodyJoint_GeneralizedVelocity(struct R2MultibodyJointHandle handle, R2Real *buffer, size_t capacity); +/** + * Replace articulation generalized velocities; the array length must match its degrees of freedom. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2MultibodyJoint_SetGeneralizedVelocity(struct R2MultibodyJointHandle handle, const R2Real *values, @@ -2653,6 +5859,7 @@ R2Status r2MultibodyJoint_SetGeneralizedVelocity(struct R2MultibodyJointHandle h /** * Check this before passing any dimension/precision-dependent structs across the ABI. + * @ingroup errors */ RAPIER_API RAPIER_CALL R2Status r2CheckAbi(uint32_t version, @@ -2661,12 +5868,19 @@ R2Status r2CheckAbi(uint32_t version, size_t vector_size, size_t pose_size); +/** + * Return owned local-space rendering geometry; release it with r2FreeShapeMesh. subdivisions + * controls curved-shape resolution. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL struct R2ShapeMesh *r2SharedShape_Tessellate(const R2SharedShape *shape, uint32_t subdivisions); /** * Flat groups of three vertices. Standard output-buffer convention. + * @see @ref output_buffers + * @ingroup shapes */ RAPIER_API RAPIER_CALL size_t r2ShapeMesh_Triangles(const struct R2ShapeMesh *mesh, @@ -2675,15 +5889,26 @@ size_t r2ShapeMesh_Triangles(const struct R2ShapeMesh *mesh, /** * Flat groups of two vertices. Standard output-buffer convention. + * @see @ref output_buffers + * @ingroup shapes */ RAPIER_API RAPIER_CALL size_t r2ShapeMesh_Lines(const struct R2ShapeMesh *mesh, struct R2Vector *buffer, size_t capacity); +/** + * Release an owned shape mesh. NULL is allowed. Do not pass borrowed pointers or free the object + * twice. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2Status r2FreeShapeMesh(struct R2ShapeMesh *mesh); #if defined(RAPIER_DIM3) +/** + * Create an owned round cylinder shape. Release it with r2FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2SharedShape *r2RoundCylinderSharedShape(R2Real half_height, R2Real radius, @@ -2694,6 +5919,7 @@ R2SharedShape *r2RoundCylinderSharedShape(R2Real half_height, /** * Tessellate a ball or capsule with independent longitude/latitude subdivision counts. * Cuboids, cones, cylinders, convex polyhedra, trimeshes, and heightfields are also supported. + * @ingroup shapes */ RAPIER_API RAPIER_CALL struct R2TriMeshData *r2SharedShape_ToTrimesh(const R2SharedShape *shape, @@ -2702,6 +5928,11 @@ struct R2TriMeshData *r2SharedShape_ToTrimesh(const R2SharedShape *shape, #endif #if defined(RAPIER_DIM3) +/** + * Copy vertices. + * @see @ref output_buffers + * @ingroup shapes + */ RAPIER_API RAPIER_CALL size_t r2TriMeshData_Vertices(const struct R2TriMeshData *mesh, struct R2Vector *buffer, @@ -2711,6 +5942,8 @@ size_t r2TriMeshData_Vertices(const struct R2TriMeshData *mesh, #if defined(RAPIER_DIM3) /** * Flat triangle indices; count and capacity are numbers of u32 entries. + * @see @ref output_buffers + * @ingroup shapes */ RAPIER_API RAPIER_CALL size_t r2TriMeshData_Indices(const struct R2TriMeshData *mesh, @@ -2719,14 +5952,28 @@ size_t r2TriMeshData_Indices(const struct R2TriMeshData *mesh, #endif #if defined(RAPIER_DIM3) +/** + * Release an owned tri mesh data. NULL is allowed. Do not pass borrowed pointers or free the + * object twice. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R2Status r2FreeTriMeshData(struct R2TriMeshData *mesh); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return native default urdf loader options. This POD value owns no resources. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL struct R2UrdfLoaderOptions r2DefaultUrdfLoaderOptions(void); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Release an owned urdf robot. NULL is allowed. Do not pass borrowed pointers or free the object + * twice. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R2Status r2FreeUrdfRobot(struct R2UrdfRobot *object); #endif @@ -2734,6 +5981,7 @@ RAPIER_API RAPIER_CALL R2Status r2FreeUrdfRobot(struct R2UrdfRobot *object); /** * Load from a UTF-8 path. Validates options before reading the file. * Options and their blueprint resources are borrowed through this call; the robot is owned. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R2UrdfRobot *r2UrdfRobotFromFile(const char *path, @@ -2741,18 +5989,28 @@ struct R2UrdfRobot *r2UrdfRobotFromFile(const char *path, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Apply an additional transform to the loaded robot before insertion. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R2Status r2UrdfRobot_AppendTransform(struct R2UrdfRobot *robot, struct R2Pose transform); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Release an owned urdf robot handles. NULL is allowed. Do not pass borrowed pointers or free the + * object twice. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R2Status r2FreeUrdfRobotHandles(struct R2UrdfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R2UrdfRobotHandles *r2UrdfRobot_InsertUsingImpulseJoints(struct R2World *world, @@ -2762,6 +6020,7 @@ struct R2UrdfRobotHandles *r2UrdfRobot_InsertUsingImpulseJoints(struct R2World * #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R2UrdfRobotHandles *r2UrdfRobot_InsertUsingMultibodyJoints(struct R2World *world, @@ -2772,6 +6031,8 @@ struct R2UrdfRobotHandles *r2UrdfRobot_InsertUsingMultibodyJoints(struct R2World #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Body handles in source order; absent MJCF bodies have invalid handles. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r2UrdfRobotHandles_Bodies(const struct R2UrdfRobotHandles *handles, @@ -2780,10 +6041,19 @@ size_t r2UrdfRobotHandles_Bodies(const struct R2UrdfRobotHandles *handles, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return native default mjcf loader options. This POD value owns no resources. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL struct R2MjcfLoaderOptions r2DefaultMjcfLoaderOptions(void); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Release an owned mjcf robot. NULL is allowed. Do not pass borrowed pointers or free the object + * twice. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R2Status r2FreeMjcfRobot(struct R2MjcfRobot *object); #endif @@ -2791,6 +6061,7 @@ RAPIER_API RAPIER_CALL R2Status r2FreeMjcfRobot(struct R2MjcfRobot *object); /** * Load from a UTF-8 path. Validates options before reading the file. * Options and their blueprint resources are borrowed through this call; the robot is owned. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R2MjcfRobot *r2MjcfRobotFromFile(const char *path, @@ -2798,18 +6069,28 @@ struct R2MjcfRobot *r2MjcfRobotFromFile(const char *path, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Apply an additional transform to the loaded robot before insertion. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R2Status r2MjcfRobot_AppendTransform(struct R2MjcfRobot *robot, struct R2Pose transform); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Release an owned mjcf robot handles. NULL is allowed. Do not pass borrowed pointers or free the + * object twice. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R2Status r2FreeMjcfRobotHandles(struct R2MjcfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R2MjcfRobotHandles *r2MjcfRobot_InsertUsingImpulseJoints(struct R2World *world, @@ -2819,6 +6100,7 @@ struct R2MjcfRobotHandles *r2MjcfRobot_InsertUsingImpulseJoints(struct R2World * #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R2MjcfRobotHandles *r2MjcfRobot_InsertUsingMultibodyJoints(struct R2World *world, @@ -2829,6 +6111,8 @@ struct R2MjcfRobotHandles *r2MjcfRobot_InsertUsingMultibodyJoints(struct R2World #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Body handles in source order; absent MJCF bodies have invalid handles. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r2MjcfRobotHandles_Bodies(const struct R2MjcfRobotHandles *handles, @@ -2839,15 +6123,24 @@ size_t r2MjcfRobotHandles_Bodies(const struct R2MjcfRobotHandles *handles, #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Resolved model gravity before the caller chooses a world convention. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R2Vector r2MjcfRobot_Gravity(const struct R2MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return the number of source MJCF bodies. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r2MjcfRobot_BodyCount(const struct R2MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return the collider count for a source body index. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r2MjcfRobot_BodyColliderCount(const struct R2MjcfRobot *robot, size_t body); @@ -2855,7 +6148,8 @@ size_t r2MjcfRobot_BodyColliderCount(const struct R2MjcfRobot *robot, #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** - * Borrowed collider; invalidated by freeing or mutating the robot's storage. + * Set collision groups on a collider in the loaded robot, before insertion. + * @ingroup robotics */ RAPIER_API RAPIER_CALL R2Status r2MjcfRobot_SetBodyColliderCollisionGroups(struct R2MjcfRobot *robot, @@ -2865,12 +6159,18 @@ R2Status r2MjcfRobot_SetBodyColliderCollisionGroups(struct R2MjcfRobot *robot, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return the number of imported keyframes. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r2MjcfRobot_KeyframeCount(const struct R2MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Copies a NUL-terminated UTF-8 name. Count includes NUL; unnamed keys return an empty string. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r2MjcfRobot_KeyframeName(const struct R2MjcfRobot *robot, @@ -2880,6 +6180,10 @@ size_t r2MjcfRobot_KeyframeName(const struct R2MjcfRobot *robot, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Append a keyframe from the source MJCF model to the loaded robot. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R2Status r2MjcfRobot_AppendKeyframe(struct R2MjcfRobot *robot, const struct R2MjcfRobot *source, @@ -2887,6 +6191,11 @@ R2Status r2MjcfRobot_AppendKeyframe(struct R2MjcfRobot *robot, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copy actuator controls for the selected keyframe. + * @see @ref output_buffers + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r2MjcfRobot_KeyframeControls(const struct R2MjcfRobot *robot, size_t key, @@ -2895,11 +6204,19 @@ size_t r2MjcfRobot_KeyframeControls(const struct R2MjcfRobot *robot, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return the number of imported actuators. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r2MjcfRobotHandles_ActuatorCount(const struct R2MjcfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Apply the selected keyframe to the inserted robot. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R2Status r2MjcfRobotHandles_ApplyKeyframe(const struct R2MjcfRobotHandles *handles, const struct R2MjcfRobot *robot, @@ -2907,6 +6224,10 @@ R2Status r2MjcfRobotHandles_ApplyKeyframe(const struct R2MjcfRobotHandles *handl #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Apply actuator controls with per-actuator scaling to the inserted robot. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R2Status r2MjcfRobotHandles_ApplyControlsScaled(const struct R2MjcfRobotHandles *handles, const R2Real *controls, @@ -2915,12 +6236,21 @@ R2Status r2MjcfRobotHandles_ApplyControlsScaled(const struct R2MjcfRobotHandles #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return the number of visual meshes for a source body. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r2MjcfRobot_BodyVisualCount(const struct R2MjcfRobot *robot, size_t body); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Borrow a visual mesh by body/visual index. Valid until the robot is freed or its storage + * changes; never free this pointer. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL const R2MjcfVisualMesh *r2MjcfRobot_BodyVisual(const struct R2MjcfRobot *robot, size_t body, @@ -2928,6 +6258,10 @@ const R2MjcfVisualMesh *r2MjcfRobot_BodyVisual(const struct R2MjcfRobot *robot, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return a copy of visual pose, color, material, and geometry-kind flags. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL struct R2MjcfVisualMeshInfo r2MjcfVisualMesh_Info(const R2MjcfVisualMesh *visual); #endif @@ -2936,6 +6270,7 @@ struct R2MjcfVisualMeshInfo r2MjcfVisualMesh_Info(const R2MjcfVisualMesh *visual /** * Returns an owned shared shape reference. * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + * @ingroup robotics */ RAPIER_API RAPIER_CALL R2SharedShape *r2MjcfVisualMesh_CloneShape(const R2MjcfVisualMesh *visual); @@ -2944,6 +6279,8 @@ R2SharedShape *r2MjcfVisualMesh_CloneShape(const R2MjcfVisualMesh *visual); #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Copies flattened pairs of per-vertex UV coordinates. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r2MjcfVisualMesh_Uvs(const R2MjcfVisualMesh *visual, @@ -2954,6 +6291,8 @@ size_t r2MjcfVisualMesh_Uvs(const R2MjcfVisualMesh *visual, #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Copies flattened triples of per-vertex normals. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r2MjcfVisualMesh_Normals(const R2MjcfVisualMesh *visual, @@ -2964,6 +6303,8 @@ size_t r2MjcfVisualMesh_Normals(const R2MjcfVisualMesh *visual, #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Copies a NUL-terminated texture path, or an empty string for untextured meshes. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r2MjcfVisualMesh_Texture(const R2MjcfVisualMesh *visual, @@ -2972,44 +6313,53 @@ size_t r2MjcfVisualMesh_Texture(const R2MjcfVisualMesh *visual, #endif /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the rigid body world-space pose. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R2Pose r2RigidBody_Position(struct R2RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the rigid body world-space translation. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R2Vector r2RigidBody_Translation(struct R2RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the rigid body world-space linear velocity. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R2Vector r2RigidBody_Linvel(struct R2RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the rigid body world-space angular velocity (radians per second). + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2AngVector r2RigidBody_Angvel(struct R2RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return whether the rigid body is sleeping. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsSleeping(struct R2RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return whether the rigid body is enabled. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsEnabled(struct R2RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the rigid body application-owned 128-bit user value. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R2UserData r2RigidBody_UserData(struct R2RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body world-space pose. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetPosition(struct R2RigidBodyHandle handle, @@ -3017,7 +6367,9 @@ R2Status r2RigidBody_SetPosition(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body world-space translation. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetTranslation(struct R2RigidBodyHandle handle, @@ -3025,7 +6377,9 @@ R2Status r2RigidBody_SetTranslation(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body world-space linear velocity. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetLinvel(struct R2RigidBodyHandle handle, @@ -3033,7 +6387,9 @@ R2Status r2RigidBody_SetLinvel(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body world-space angular velocity (radians per second). + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetAngvel(struct R2RigidBodyHandle handle, @@ -3041,21 +6397,25 @@ R2Status r2RigidBody_SetAngvel(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body next kinematic world-space pose. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetNextKinematicPosition(struct R2RigidBodyHandle handle, struct R2Pose value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body next kinematic world-space translation. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetNextKinematicTranslation(struct R2RigidBodyHandle handle, struct R2Vector value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body gravity multiplier. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetGravityScale(struct R2RigidBodyHandle handle, @@ -3063,35 +6423,41 @@ R2Status r2RigidBody_SetGravityScale(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body linear damping coefficient. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetLinearDamping(struct R2RigidBodyHandle handle, R2Real value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body angular damping coefficient. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetAngularDamping(struct R2RigidBodyHandle handle, R2Real value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Enable or disable the rigid body. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetEnabled(struct R2RigidBodyHandle handle, R2Bool value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body application-owned 128-bit user value. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetUserData(struct R2RigidBodyHandle handle, struct R2UserData value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Apply a world-space linear impulse. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_ApplyImpulse(struct R2RigidBodyHandle handle, @@ -3099,7 +6465,9 @@ R2Status r2RigidBody_ApplyImpulse(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Apply a world-space impulse at a world-space point. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_ApplyImpulseAtPoint(struct R2RigidBodyHandle handle, @@ -3108,7 +6476,9 @@ R2Status r2RigidBody_ApplyImpulseAtPoint(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Accumulate a world-space force; it persists until reset. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_AddForce(struct R2RigidBodyHandle handle, @@ -3116,106 +6486,125 @@ R2Status r2RigidBody_AddForce(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Clear accumulated user forces. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_ResetForces(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Put the body to sleep. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_Sleep(struct R2RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the collider world-space pose. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2Pose r2Collider_Position(struct R2ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the collider world-space translation. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2Vector r2Collider_Translation(struct R2ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the collider friction coefficient. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Real r2Collider_Friction(struct R2ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the collider restitution coefficient. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Real r2Collider_Restitution(struct R2ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return whether the collider is a sensor (detects overlaps without contact forces). + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Bool r2Collider_IsSensor(struct R2ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the parent body handle, or an invalid handle with OK status for a standalone collider. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2RigidBodyHandle r2Collider_Parent(struct R2ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider world-space pose. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetPosition(struct R2ColliderHandle handle, struct R2Pose value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider world-space translation. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetTranslation(struct R2ColliderHandle handle, struct R2Vector value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider friction coefficient. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetFriction(struct R2ColliderHandle handle, R2Real value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider restitution coefficient. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetRestitution(struct R2ColliderHandle handle, R2Real value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Enable or disable a sensor (detects overlaps without contact forces) for the collider. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetSensor(struct R2ColliderHandle handle, R2Bool value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider collision filtering groups. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetCollisionGroups(struct R2ColliderHandle handle, struct R2InteractionGroups value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider application-owned 128-bit user value. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetUserData(struct R2ColliderHandle handle, struct R2UserData value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the world-space position of the indexed particle. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2Vector r2SoftBody_ParticlePosition(struct R2SoftBodyHandle handle, size_t index); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Copy world-space particle positions. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_ParticlePositions(struct R2SoftBodyHandle handle, @@ -3223,13 +6612,15 @@ size_t r2SoftBody_ParticlePositions(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return a copy of the soft body material parameters. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyMaterial r2SoftBody_Material(struct R2SoftBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the world-space position of the indexed particle. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetParticlePosition(struct R2SoftBodyHandle handle, @@ -3237,14 +6628,17 @@ R2Status r2SoftBody_SetParticlePosition(struct R2SoftBodyHandle handle, struct R2Vector value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Copy material parameters into the soft body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetMaterial(struct R2SoftBodyHandle handle, const struct R2SoftBodyMaterial *data); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Add a world-space force to the indexed particle. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_AddParticleForce(struct R2SoftBodyHandle handle, @@ -3255,7 +6649,8 @@ R2Status r2SoftBody_AddParticleForce(struct R2SoftBodyHandle handle, /** * Copies states in the same order as handles, without allocating temporary storage. * All handles are validated before writing. On INVALID_HANDLE outputs are unchanged. - * NULL/0 is a size query. BUFFER_TOO_SMALL updates count but leaves states untouched. + * NULL/0 is a size query. BUFFER_TOO_SMALL returns the required count and leaves states untouched. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL size_t r2RigidBodyReadStates(const struct R2World *world, @@ -3266,12 +6661,15 @@ size_t r2RigidBodyReadStates(const struct R2World *world, /** * Copies joint configuration without returning a borrowed joint pointer. + * @ingroup joints */ RAPIER_API RAPIER_CALL struct R2JointDesc r2ImpulseJoint_Desc(struct R2ImpulseJointHandle handle); /** * Replaces configuration after validation, resetting cached limit/motor impulses. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetDesc(struct R2ImpulseJointHandle handle, @@ -3282,6 +6680,7 @@ R2Status r2ImpulseJoint_SetDesc(struct R2ImpulseJointHandle handle, * Replace the shape geometry with a borrowed tri mesh. Counts are elements. * Copies no arrays. Invalid view metadata leaves the description unchanged. * Geometry and flags are validated when the description is built or inserted. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2Status r2ShapeDesc_SetTrimesh(struct R2ShapeDesc *desc, @@ -3293,6 +6692,7 @@ R2Status r2ShapeDesc_SetTrimesh(struct R2ShapeDesc *desc, * Replace the shape geometry with a borrowed polyline. Counts are elements. * Copies no arrays. Invalid view metadata leaves the description unchanged. * Geometry and flags are validated when the description is built or inserted. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2Status r2ShapeDesc_SetPolyline(struct R2ShapeDesc *desc, @@ -3302,6 +6702,7 @@ R2Status r2ShapeDesc_SetPolyline(struct R2ShapeDesc *desc, /** * Replace the shape geometry with a borrowed convex hull point cloud. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2Status r2ShapeDesc_SetConvexHull(struct R2ShapeDesc *desc, @@ -3309,6 +6710,7 @@ R2Status r2ShapeDesc_SetConvexHull(struct R2ShapeDesc *desc, /** * Select an explicit particle recipe and borrow its positions. Other fields are preserved. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetParticles(struct R2SoftBodyDesc *desc, @@ -3316,6 +6718,7 @@ R2Status r2SoftBodyDesc_SetParticles(struct R2SoftBodyDesc *desc, /** * Select a surface recipe and borrow its vertices and elements. Other fields are preserved. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetSurfaceMesh(struct R2SoftBodyDesc *desc, @@ -3324,6 +6727,7 @@ R2Status r2SoftBodyDesc_SetSurfaceMesh(struct R2SoftBodyDesc *desc, /** * Borrow skin geometry. Other fields, including skinCollision, are preserved. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetSkin(struct R2SoftBodyDesc *desc, @@ -3332,8 +6736,10 @@ R2Status r2SoftBodyDesc_SetSkin(struct R2SoftBodyDesc *desc, /** * Borrow masses; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetMasses(struct R2SoftBodyDesc *desc, @@ -3341,8 +6747,10 @@ R2Status r2SoftBodyDesc_SetMasses(struct R2SoftBodyDesc *desc, /** * Borrow pinned particles; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetPinnedParticles(struct R2SoftBodyDesc *desc, @@ -3350,8 +6758,10 @@ R2Status r2SoftBodyDesc_SetPinnedParticles(struct R2SoftBodyDesc *desc, /** * Borrow edges; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetEdges(struct R2SoftBodyDesc *desc, @@ -3359,8 +6769,10 @@ R2Status r2SoftBodyDesc_SetEdges(struct R2SoftBodyDesc *desc, /** * Borrow bend edges; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetBendEdges(struct R2SoftBodyDesc *desc, @@ -3368,8 +6780,10 @@ R2Status r2SoftBodyDesc_SetBendEdges(struct R2SoftBodyDesc *desc, /** * Borrow cells; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetCells(struct R2SoftBodyDesc *desc, @@ -3377,8 +6791,10 @@ R2Status r2SoftBodyDesc_SetCells(struct R2SoftBodyDesc *desc, /** * Borrow surface; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetSurface(struct R2SoftBodyDesc *desc, @@ -3386,8 +6802,10 @@ R2Status r2SoftBodyDesc_SetSurface(struct R2SoftBodyDesc *desc, /** * Borrow tension only edges; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetTensionOnlyEdges(struct R2SoftBodyDesc *desc, @@ -3396,8 +6814,10 @@ R2Status r2SoftBodyDesc_SetTensionOnlyEdges(struct R2SoftBodyDesc *desc, #if defined(RAPIER_DIM3) /** * Borrow dihedrals; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetDihedrals(struct R2SoftBodyDesc *desc, @@ -3407,8 +6827,10 @@ R2Status r2SoftBodyDesc_SetDihedrals(struct R2SoftBodyDesc *desc, #if defined(RAPIER_DIM3) /** * Borrow wire; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBodyDesc_SetWire(struct R2SoftBodyDesc *desc, @@ -3416,14 +6838,18 @@ R2Status r2SoftBodyDesc_SetWire(struct R2SoftBodyDesc *desc, #endif /** + * Return a rounded box description; half_extents exclude the added border_radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2RoundCuboidColliderDesc(struct R2Vector half_extents, R2Real border_radius); /** + * Return a capsule description with segment endpoints a/b and the supplied radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2CapsuleColliderDesc(struct R2Vector a, @@ -3431,14 +6857,18 @@ struct R2ColliderDesc r2CapsuleColliderDesc(struct R2Vector a, R2Real radius); /** + * Return a segment description with endpoints a and b. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2SegmentColliderDesc(struct R2Vector a, struct R2Vector b); /** + * Return a triangle description with vertices a, b, and c. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2TriangleColliderDesc(struct R2Vector a, @@ -3446,13 +6876,17 @@ struct R2ColliderDesc r2TriangleColliderDesc(struct R2Vector a, struct R2Vector c); /** + * Return a half-space description bounded by a plane through the origin; normal points outward. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2HalfspaceColliderDesc(struct R2Vector normal); #if defined(RAPIER_DIM3) /** + * Return a Y-aligned cylinder description with the supplied half-height and radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2CylinderColliderDesc(R2Real half_height, @@ -3461,7 +6895,9 @@ struct R2ColliderDesc r2CylinderColliderDesc(R2Real half_height, #if defined(RAPIER_DIM3) /** + * Return a Y-aligned cone description with the supplied half-height and base radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2ConeColliderDesc(R2Real half_height, @@ -3470,7 +6906,9 @@ struct R2ColliderDesc r2ConeColliderDesc(R2Real half_height, #if defined(RAPIER_DIM3) /** + * Return a rounded Y-aligned cylinder description; dimensions exclude border_radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2RoundCylinderColliderDesc(R2Real half_height, @@ -3479,14 +6917,18 @@ struct R2ColliderDesc r2RoundCylinderColliderDesc(R2Real half_height, #endif /** + * Return a X-aligned capsule description; half_height is half the segment length, excluding caps. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2CapsuleXColliderDesc(R2Real half_height, R2Real radius); /** + * Return a Y-aligned capsule description; half_height is half the segment length, excluding caps. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2CapsuleYColliderDesc(R2Real half_height, @@ -3494,7 +6936,9 @@ struct R2ColliderDesc r2CapsuleYColliderDesc(R2Real half_height, #if defined(RAPIER_DIM3) /** + * Return a Z-aligned capsule description; half_height is half the segment length, excluding caps. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2CapsuleZColliderDesc(R2Real half_height, @@ -3502,7 +6946,9 @@ struct R2ColliderDesc r2CapsuleZColliderDesc(R2Real half_height, #endif /** + * Return a rope recipe with particles evenly spaced from a to b, including both endpoints. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyDesc r2RopeSoftBodyDesc(struct R2Vector a, @@ -3511,7 +6957,9 @@ struct R2SoftBodyDesc r2RopeSoftBodyDesc(struct R2Vector a, #if defined(RAPIER_DIM2) /** + * Return a solid rectangle recipe on an nx by ny particle grid. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyDesc r2GridSoftBodyDesc(struct R2Vector center, @@ -3522,7 +6970,9 @@ struct R2SoftBodyDesc r2GridSoftBodyDesc(struct R2Vector center, #if defined(RAPIER_DIM3) /** + * Return a solid box recipe on an nx by ny by nz particle grid, subdivided into tetrahedra. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyDesc r2CuboidSoftBodyDesc(struct R2Vector center, @@ -3534,7 +6984,9 @@ struct R2SoftBodyDesc r2CuboidSoftBodyDesc(struct R2Vector center, #if defined(RAPIER_DIM3) /** + * Return a cloth recipe with nu by nv particles at origin + i * du + j * dv. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyDesc r2ClothSoftBodyDesc(struct R2Vector origin, @@ -3546,7 +6998,10 @@ struct R2SoftBodyDesc r2ClothSoftBodyDesc(struct R2Vector origin, #if defined(RAPIER_DIM2) /** + * Return a closed regular polygon recipe with the specified boundary particle count and area + * preservation. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyDesc r2DiskSoftBodyDesc(struct R2Vector center, @@ -3556,7 +7011,9 @@ struct R2SoftBodyDesc r2DiskSoftBodyDesc(struct R2Vector center, #if defined(RAPIER_DIM3) /** + * Return a hollow icosphere recipe with the specified refinement levels and volume preservation. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyDesc r2SphereSoftBodyDesc(struct R2Vector center, @@ -3566,7 +7023,10 @@ struct R2SoftBodyDesc r2SphereSoftBodyDesc(struct R2Vector center, #if defined(RAPIER_DIM3) /** + * Return a cloth tube recipe from origin to origin + axis with num_along rings of num_around + * particles; radius varies linearly between the ends. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyDesc r2ClothTubeSoftBodyDesc(struct R2Vector origin, @@ -3579,6 +7039,7 @@ struct R2SoftBodyDesc r2ClothTubeSoftBodyDesc(struct R2Vector origin, /** * Initializes a borrowed meshing recipe. Mesh generation happens on preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyDesc r2VolumetricSoftBodyDesc(struct R2VectorView vertices, @@ -3587,12 +7048,15 @@ struct R2SoftBodyDesc r2VolumetricSoftBodyDesc(struct R2VectorView vertices, /** * Returns a material with the same softness for each constraint family. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyMaterial r2UniformSoftBodyMaterial(struct R2SpringCoefficients value); /** * Copies generated particle positions into caller-owned storage; no persistent builder. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBodyDesc_ParticlePositions(const struct R2SoftBodyDesc *desc, @@ -3601,6 +7065,8 @@ size_t r2SoftBodyDesc_ParticlePositions(const struct R2SoftBodyDesc *desc, /** * Copies generated cell indices into caller-owned storage. Counts scalar indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBodyDesc_CellIndices(const struct R2SoftBodyDesc *desc, @@ -3608,64 +7074,79 @@ size_t r2SoftBodyDesc_CellIndices(const struct R2SoftBodyDesc *desc, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return a process-local geometry identity for caching, not a serializable ID. Keep a shared-shape + * clone alive while using it as a cache key. + * @ingroup shapes */ RAPIER_API RAPIER_CALL size_t r2Collider_ShapeIdentity(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body particle count. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_NumParticles(struct R2SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return a counter that changes when particle connectivity changes; use it to invalidate mesh + * caches. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL uint32_t r2SoftBody_TopologyVersion(struct R2SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body mass. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Real r2SoftBody_Mass(struct R2SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body current volume. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Real r2SoftBody_Volume(struct R2SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body undeformed volume. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Real r2SoftBody_RestVolume(struct R2SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body target volume multiplier. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Real r2SoftBody_VolumeFactor(struct R2SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body world-space center of mass. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2Vector r2SoftBody_CenterOfMass(struct R2SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body root rigid-proxy handle. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2RigidBodyHandle r2SoftBody_RootBody(struct R2SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the soft body is enabled. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Bool r2SoftBody_IsEnabled(struct R2SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the soft body is sleeping. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Bool r2SoftBody_IsSleeping(struct R2SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy world-space particle velocities. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_ParticleVelocities(struct R2SoftBodyHandle handle, @@ -3673,7 +7154,9 @@ size_t r2SoftBody_ParticleVelocities(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy flattened edge vertex indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_Edges(struct R2SoftBodyHandle handle, @@ -3681,7 +7164,9 @@ size_t r2SoftBody_Edges(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy flattened cell vertex indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_Cells(struct R2SoftBodyHandle handle, @@ -3689,7 +7174,9 @@ size_t r2SoftBody_Cells(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy flattened boundary element indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_Boundary(struct R2SoftBodyHandle handle, @@ -3697,7 +7184,9 @@ size_t r2SoftBody_Boundary(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy piece identifiers. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_Pieces(struct R2SoftBodyHandle handle, @@ -3705,7 +7194,8 @@ size_t r2SoftBody_Pieces(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body particle world-space velocity. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetParticleVelocity(struct R2SoftBodyHandle handle, @@ -3713,7 +7203,8 @@ R2Status r2SoftBody_SetParticleVelocity(struct R2SoftBodyHandle handle, struct R2Vector value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the next world-space target position of a pinned particle. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetParticleKinematicTarget(struct R2SoftBodyHandle handle, @@ -3721,7 +7212,8 @@ R2Status r2SoftBody_SetParticleKinematicTarget(struct R2SoftBodyHandle handle, struct R2Vector value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable pinning the particle for the soft body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetParticlePinned(struct R2SoftBodyHandle handle, @@ -3729,7 +7221,9 @@ R2Status r2SoftBody_SetParticlePinned(struct R2SoftBodyHandle handle, R2Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Apply a world-space impulse to one particle. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_ApplyParticleImpulse(struct R2SoftBodyHandle handle, @@ -3738,7 +7232,9 @@ R2Status r2SoftBody_ApplyParticleImpulse(struct R2SoftBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Accumulate a world-space force; it persists until reset. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_AddForce(struct R2SoftBodyHandle handle, @@ -3746,7 +7242,9 @@ R2Status r2SoftBody_AddForce(struct R2SoftBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Apply a world-space linear impulse. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_ApplyImpulse(struct R2SoftBodyHandle handle, @@ -3754,28 +7252,33 @@ R2Status r2SoftBody_ApplyImpulse(struct R2SoftBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Clear accumulated user forces. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_ResetForces(struct R2SoftBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable the soft body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetEnabled(struct R2SoftBodyHandle handle, R2Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body target volume multiplier. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetVolumeFactor(struct R2SoftBodyHandle handle, R2Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Attach a particle to a rigid body at the supplied body-local anchor. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_AttachParticle(struct R2SoftBodyHandle handle, @@ -3783,14 +7286,17 @@ R2Status r2SoftBody_AttachParticle(struct R2SoftBodyHandle handle, struct R2RigidBodyHandle rigid_body); /** - * Resolves the generational handle for this call; rejects stale handles. + * Remove a particle attachment to a rigid body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_DetachParticle(struct R2SoftBodyHandle handle, size_t index); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy cluster indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_Clusters(struct R2SoftBodyHandle handle, @@ -3798,14 +7304,17 @@ size_t r2SoftBody_Clusters(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid proxy for the selected cluster. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2RigidBodyHandle r2SoftBody_ClusterProxy(struct R2SoftBodyHandle handle, uint32_t cluster); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy particle indices for a cluster. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_ClusterParticles(struct R2SoftBodyHandle handle, @@ -3814,7 +7323,8 @@ size_t r2SoftBody_ClusterParticles(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable pinning the cluster for the soft body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetClusterPinned(struct R2SoftBodyHandle handle, @@ -3822,7 +7332,8 @@ R2Status r2SoftBody_SetClusterPinned(struct R2SoftBodyHandle handle, R2Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the next world-space target pose of a pinned cluster. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetClusterKinematicTarget(struct R2SoftBodyHandle handle, @@ -3830,7 +7341,8 @@ R2Status r2SoftBody_SetClusterKinematicTarget(struct R2SoftBodyHandle handle, struct R2Pose value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable using cluster shape matching for the soft body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetClusterShapeMatchingEnabled(struct R2SoftBodyHandle handle, @@ -3838,7 +7350,8 @@ R2Status r2SoftBody_SetClusterShapeMatchingEnabled(struct R2SoftBodyHandle handl R2Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body cluster shape-matching stiffness multiplier. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetClusterStiffnessScale(struct R2SoftBodyHandle handle, @@ -3846,7 +7359,8 @@ R2Status r2SoftBody_SetClusterStiffnessScale(struct R2SoftBodyHandle handle, R2Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body cluster tear-resistance multiplier. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetClusterTearResistance(struct R2SoftBodyHandle handle, @@ -3854,7 +7368,9 @@ R2Status r2SoftBody_SetClusterTearResistance(struct R2SoftBodyHandle handle, R2Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy collision mesh metadata. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_Meshes(struct R2SoftBodyHandle handle, @@ -3862,7 +7378,9 @@ size_t r2SoftBody_Meshes(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy world-space vertices for a mesh ID. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_MeshVerticesById(struct R2SoftBodyHandle handle, @@ -3871,7 +7389,9 @@ size_t r2SoftBody_MeshVerticesById(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy flattened indices for a mesh ID. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_MeshIndicesById(struct R2SoftBodyHandle handle, @@ -3880,7 +7400,9 @@ size_t r2SoftBody_MeshIndicesById(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy collision mesh collider handles. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_MeshColliders(struct R2SoftBodyHandle handle, @@ -3888,7 +7410,9 @@ size_t r2SoftBody_MeshColliders(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy world-space collision mesh vertices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_MeshVertices(struct R2SoftBodyHandle handle, @@ -3897,7 +7421,9 @@ size_t r2SoftBody_MeshVertices(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy flattened collision mesh indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_MeshIndices(struct R2SoftBodyHandle handle, @@ -3906,14 +7432,16 @@ size_t r2SoftBody_MeshIndices(struct R2SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return indices per collision-mesh element (2 for segments, 3 for triangles). + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r2SoftBody_MeshArity(struct R2SoftBodyHandle handle, struct R2ColliderHandle collider); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the selected collision mesh topology revision for cache invalidation. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL uint32_t r2SoftBody_MeshTopologyVersion(struct R2SoftBodyHandle handle, @@ -3921,7 +7449,8 @@ uint32_t r2SoftBody_MeshTopologyVersion(struct R2SoftBodyHandle handle, #if defined(RAPIER_FEM) /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body soft solver kind (R2_SOFT_SOLVER_*). + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetSolver(struct R2SoftBodyHandle handle, @@ -3929,7 +7458,8 @@ R2Status r2SoftBody_SetSolver(struct R2SoftBodyHandle handle, #endif /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body cluster shape-matching target pose. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetClusterShapeMatchingTarget(struct R2SoftBodyHandle handle, @@ -3937,7 +7467,8 @@ R2Status r2SoftBody_SetClusterShapeMatchingTarget(struct R2SoftBodyHandle handle const struct R2Pose *target); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body edge tear-resistance multiplier. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_SetEdgeTearResistance(struct R2SoftBodyHandle handle, @@ -3945,14 +7476,17 @@ R2Status r2SoftBody_SetEdgeTearResistance(struct R2SoftBodyHandle handle, R2Real resistance); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the selected collision mesh is closed. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Bool r2SoftBody_MeshIsClosed(struct R2SoftBodyHandle handle, struct R2ColliderHandle collider); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body local mass properties added to collider contributions. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetAdditionalMassProperties(struct R2RigidBodyHandle handle, @@ -3960,26 +7494,31 @@ R2Status r2RigidBody_SetAdditionalMassProperties(struct R2RigidBodyHandle handle R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Recompute body mass and inertia from attached colliders and additional mass properties. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_RecomputeMassPropertiesFromColliders(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider local mass properties. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetMassProperties(struct R2ColliderHandle handle, struct R2MassProperties properties); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider local mass properties. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2MassProperties r2Collider_MassProperties(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body translation/rotation lock bitmask. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetLockedAxes(struct R2RigidBodyHandle handle, @@ -3987,24 +7526,28 @@ R2Status r2RigidBody_SetLockedAxes(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body translation/rotation lock bitmask. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL uint8_t r2RigidBody_LockedAxes(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the collider is a voxel shape. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Bool r2Collider_IsVoxels(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return voxel information at a flat index; found = 0 if absent. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2VoxelQuery r2Collider_VoxelAtFlatId(struct R2ColliderHandle handle, uint32_t id); /** - * Resolves the generational handle for this call; rejects stale handles. + * Fill or clear the voxel at key; the collider must have a voxel shape. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetVoxel(struct R2ColliderHandle handle, @@ -4012,116 +7555,139 @@ R2Status r2Collider_SetVoxel(struct R2ColliderHandle handle, R2Bool filled); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body next kinematic world-space pose. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R2Pose r2RigidBody_NextPosition(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body world-space rotation. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R2Rotation r2RigidBody_Rotation(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body world-space center of mass. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R2Vector r2RigidBody_CenterOfMass(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body body-local center of mass. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R2Vector r2RigidBody_LocalCenterOfMass(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body accumulated user-applied world-space force. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R2Vector r2RigidBody_UserForce(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body accumulated user-applied world-space torque. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2AngVector r2RigidBody_UserTorque(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body body type (R2_DYNAMIC, R2_FIXED, or a kinematic kind). + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL uint32_t r2RigidBody_BodyType(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body mass. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Real r2RigidBody_Mass(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body gravity multiplier. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Real r2RigidBody_GravityScale(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body linear damping coefficient. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Real r2RigidBody_LinearDamping(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body angular damping coefficient. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Real r2RigidBody_AngularDamping(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body kinetic energy. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Real r2RigidBody_KineticEnergy(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body soft-CCD prediction distance. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Real r2RigidBody_SoftCcdPrediction(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is using continuous collision detection. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsCcdEnabled(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is dynamic. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsDynamic(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the associated soft-body handle, or an invalid handle if this is not a soft proxy. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R2SoftBodyHandle r2RigidBody_SoftBody(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is a soft-body proxy. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsSoftFrame(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is fixed. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsFixed(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is kinematic. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsKinematic(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is moving. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsMoving(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is currently using CCD for its motion. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsCcdActive(struct R2RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body world-space rotation. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetRotation(struct R2RigidBodyHandle handle, @@ -4129,7 +7695,9 @@ R2Status r2RigidBody_SetRotation(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body body type (R2_DYNAMIC, R2_FIXED, or a kinematic kind). + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetBodyType(struct R2RigidBodyHandle handle, @@ -4137,14 +7705,17 @@ R2Status r2RigidBody_SetBodyType(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body next kinematic world-space rotation. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetNextKinematicRotation(struct R2RigidBodyHandle handle, struct R2Rotation value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body mass added to collider contributions. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetAdditionalMass(struct R2RigidBodyHandle handle, @@ -4152,21 +7723,25 @@ R2Status r2RigidBody_SetAdditionalMass(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body soft-CCD prediction distance. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetSoftCcdPrediction(struct R2RigidBodyHandle handle, R2Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable using continuous collision detection for the rigid body. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetCcdEnabled(struct R2RigidBodyHandle handle, R2Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable locking translation for the rigid body. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetTranslationsLocked(struct R2RigidBodyHandle handle, @@ -4174,7 +7749,9 @@ R2Status r2RigidBody_SetTranslationsLocked(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable locking rotation for the rigid body. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetRotationsLocked(struct R2RigidBodyHandle handle, @@ -4182,28 +7759,33 @@ R2Status r2RigidBody_SetRotationsLocked(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body signed dominance group. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetDominanceGroup(struct R2RigidBodyHandle handle, int8_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body additional solver iterations for connected bodies. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetAdditionalSolverIterations(struct R2RigidBodyHandle handle, size_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body additional PGS iterations. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetAdditionalPgsIterations(struct R2RigidBodyHandle handle, size_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Accumulate a world-space torque; it persists until reset. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_AddTorque(struct R2RigidBodyHandle handle, @@ -4211,7 +7793,9 @@ R2Status r2RigidBody_AddTorque(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Apply a world-space angular impulse. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_ApplyTorqueImpulse(struct R2RigidBodyHandle handle, @@ -4219,7 +7803,9 @@ R2Status r2RigidBody_ApplyTorqueImpulse(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Accumulate a world-space force applied at a world-space point. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_AddForceAtPoint(struct R2RigidBodyHandle handle, @@ -4228,21 +7814,26 @@ R2Status r2RigidBody_AddForceAtPoint(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Clear accumulated user torques. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_ResetTorques(struct R2RigidBodyHandle handle, R2Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return world-space velocity at a world-space point, including angular motion. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R2Vector r2RigidBody_VelocityAtPoint(struct R2RigidBodyHandle handle, struct R2Vector point); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy attached collider handles. + * @see @ref output_buffers + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL size_t r2RigidBody_Colliders(struct R2RigidBodyHandle handle, @@ -4251,7 +7842,8 @@ size_t r2RigidBody_Colliders(struct R2RigidBodyHandle handle, #if defined(RAPIER_DIM3) /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is using gyroscopic forces. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Bool r2RigidBody_GyroscopicForcesEnabled(struct R2RigidBodyHandle handle); @@ -4259,7 +7851,8 @@ R2Bool r2RigidBody_GyroscopicForcesEnabled(struct R2RigidBodyHandle handle); #if defined(RAPIER_DIM3) /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable using gyroscopic forces for the rigid body. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_SetGyroscopicForcesEnabled(struct R2RigidBodyHandle handle, @@ -4267,294 +7860,463 @@ R2Status r2RigidBody_SetGyroscopicForcesEnabled(struct R2RigidBodyHandle handle, #endif /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider mass per unit volume. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetDensity(struct R2ColliderHandle handle, R2Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider mass. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetMass(struct R2ColliderHandle handle, R2Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable the collider. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetEnabled(struct R2ColliderHandle handle, R2Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider contact-force filtering groups. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetSolverGroups(struct R2ColliderHandle handle, struct R2InteractionGroups value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider friction combination rule (R2_COMBINE_*). + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetFrictionCombineRule(struct R2ColliderHandle handle, uint32_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider restitution combination rule (R2_COMBINE_*). + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetRestitutionCombineRule(struct R2ColliderHandle handle, uint32_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider extra separation skin around the shape. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetContactSkin(struct R2ColliderHandle handle, R2Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider force threshold for contact-force events. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetContactForceEventThreshold(struct R2ColliderHandle handle, R2Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider event-generation bitmask (R2_COLLISION_EVENTS and R2_CONTACT_FORCE_EVENTS). + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetActiveEvents(struct R2ColliderHandle handle, uint32_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider physics-hook activation bitmask. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetActiveHooks(struct R2ColliderHandle handle, uint32_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider body-type collision activation bitmask. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetActiveCollisionTypes(struct R2ColliderHandle handle, uint16_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider world-space rotation. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2Rotation r2Collider_Rotation(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider collision filtering groups. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2InteractionGroups r2Collider_CollisionGroups(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider contact-force filtering groups. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2InteractionGroups r2Collider_SolverGroups(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider application-owned 128-bit user value. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2UserData r2Collider_UserData(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider event-generation bitmask (R2_COLLISION_EVENTS and + * R2_CONTACT_FORCE_EVENTS). + * @ingroup colliders */ RAPIER_API RAPIER_CALL uint32_t r2Collider_ActiveEvents(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider mass. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Real r2Collider_Mass(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider mass per unit volume. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Real r2Collider_Density(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider current volume. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Real r2Collider_Volume(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider extra separation skin around the shape. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Real r2Collider_ContactSkin(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider force threshold for contact-force events. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Real r2Collider_ContactForceEventThreshold(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the collider is enabled. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Bool r2Collider_IsEnabled(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the current world-space axis-aligned bounds. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R2Aabb r2Collider_ComputeAabb(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return an owned wrapper sharing the collider geometry. Release with r2FreeSharedShape. * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2Collider_CloneShape(struct R2ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Replace collider geometry by sharing shape; the supplied wrapper is not consumed. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetShape(struct R2ColliderHandle handle, const R2SharedShape *shape); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider pose relative to the parent rigid body. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R2Status r2Collider_SetPositionWrtParent(struct R2ColliderHandle handle, struct R2Pose value); +/** + * Validate the index and generation in the live owning world. Cannot detect a world pointer that + * has already been freed. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL R2Status r2RigidBody_ValidateHandle(struct R2RigidBodyHandle handle); +/** + * Validate the index and generation in the live owning world. Cannot detect a world pointer that + * has already been freed. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL R2Status r2Collider_ValidateHandle(struct R2ColliderHandle handle); +/** + * Validate the index and generation in the live owning world. Cannot detect a world pointer that + * has already been freed. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2SoftBody_ValidateHandle(struct R2SoftBodyHandle handle); +/** + * Set the joint desc joint frame relative to body 1. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetLocalFrame1(struct R2JointDesc *desc, struct R2Pose value); +/** + * Set the impulse joint joint frame relative to body 1. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetLocalFrame1(struct R2ImpulseJointHandle handle, struct R2Pose value, R2Bool wake_up); +/** + * Set the joint desc joint frame relative to body 2. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetLocalFrame2(struct R2JointDesc *desc, struct R2Pose value); +/** + * Set the impulse joint joint frame relative to body 2. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetLocalFrame2(struct R2ImpulseJointHandle handle, struct R2Pose value, R2Bool wake_up); +/** + * Set the joint desc joint anchor relative to body 1. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetLocalAnchor1(struct R2JointDesc *desc, struct R2Vector value); +/** + * Set the impulse joint joint anchor relative to body 1. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetLocalAnchor1(struct R2ImpulseJointHandle handle, struct R2Vector value, R2Bool wake_up); +/** + * Set the joint desc joint anchor relative to body 2. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetLocalAnchor2(struct R2JointDesc *desc, struct R2Vector value); +/** + * Set the impulse joint joint anchor relative to body 2. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetLocalAnchor2(struct R2ImpulseJointHandle handle, struct R2Vector value, R2Bool wake_up); +/** + * Enable or disable allowing contacts between connected bodies for the joint desc. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetContactsEnabled(struct R2JointDesc *desc, R2Bool value); +/** + * Enable or disable allowing contacts between connected bodies for the impulse joint. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetContactsEnabled(struct R2ImpulseJointHandle handle, R2Bool value, R2Bool wake_up); +/** + * Enable or disable the joint desc. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetEnabled(struct R2JointDesc *desc, R2Bool value); +/** + * Enable or disable the impulse joint. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetEnabled(struct R2ImpulseJointHandle handle, R2Bool value, R2Bool wake_up); +/** + * Set the joint desc joint spring coefficients. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetSoftness(struct R2JointDesc *desc, struct R2SpringCoefficients value); +/** + * Set the impulse joint joint spring coefficients. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetSoftness(struct R2ImpulseJointHandle handle, struct R2SpringCoefficients value, R2Bool wake_up); +/** + * Set the joint desc translation/rotation lock bitmask. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetLockedAxes(struct R2JointDesc *desc, uint8_t value); +/** + * Set the impulse joint translation/rotation lock bitmask. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetLockedAxes(struct R2ImpulseJointHandle handle, uint8_t value, R2Bool wake_up); +/** + * Set the joint desc joint axis mask with limits enabled. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetLimitAxes(struct R2JointDesc *desc, uint8_t value); +/** + * Set the impulse joint joint axis mask with limits enabled. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetLimitAxes(struct R2ImpulseJointHandle handle, uint8_t value, R2Bool wake_up); +/** + * Set the joint desc joint axis mask with motors enabled. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetMotorAxes(struct R2JointDesc *desc, uint8_t value); +/** + * Set the impulse joint joint axis mask with motors enabled. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetMotorAxes(struct R2ImpulseJointHandle handle, uint8_t value, R2Bool wake_up); +/** + * Set the joint desc coupled joint axis mask. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetCoupledAxes(struct R2JointDesc *desc, uint8_t value); +/** + * Set the impulse joint coupled joint axis mask. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetCoupledAxes(struct R2ImpulseJointHandle handle, uint8_t value, R2Bool wake_up); +/** + * Set the joint desc joint principal axis in body 1 local coordinates. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetLocalAxis1(struct R2JointDesc *desc, struct R2Vector value); +/** + * Set the impulse joint joint principal axis in body 1 local coordinates. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetLocalAxis1(struct R2ImpulseJointHandle handle, struct R2Vector value, R2Bool wake_up); +/** + * Set the joint desc joint principal axis in body 2 local coordinates. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetLocalAxis2(struct R2JointDesc *desc, struct R2Vector value); +/** + * Set the impulse joint joint principal axis in body 2 local coordinates. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetLocalAxis2(struct R2ImpulseJointHandle handle, struct R2Vector value, R2Bool wake_up); +/** + * Set the joint desc minimum and maximum limits on an axis (linear distance or angular radians). + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetLimits(struct R2JointDesc *desc, uint32_t joint_axis, R2Real min, R2Real max); +/** + * Set the impulse joint minimum and maximum limits on an axis (linear distance or angular + * radians). + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetLimits(struct R2ImpulseJointHandle handle, uint32_t joint_axis, @@ -4562,6 +8324,10 @@ R2Status r2ImpulseJoint_SetLimits(struct R2ImpulseJointHandle handle, R2Real max, R2Bool wake_up); +/** + * Set the joint desc motor position/velocity targets and spring coefficients on an axis. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetMotor(struct R2JointDesc *desc, uint32_t joint_axis, @@ -4570,6 +8336,11 @@ R2Status r2JointDesc_SetMotor(struct R2JointDesc *desc, R2Real stiffness, R2Real damping); +/** + * Set the impulse joint motor position/velocity targets and spring coefficients on an axis. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetMotor(struct R2ImpulseJointHandle handle, uint32_t joint_axis, @@ -4579,37 +8350,68 @@ R2Status r2ImpulseJoint_SetMotor(struct R2ImpulseJointHandle handle, R2Real damping, R2Bool wake_up); +/** + * Set the joint desc maximum motor force or torque on an axis. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetMotorMaxForce(struct R2JointDesc *desc, uint32_t joint_axis, R2Real max_force); +/** + * Set the impulse joint maximum motor force or torque on an axis. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetMotorMaxForce(struct R2ImpulseJointHandle handle, uint32_t joint_axis, R2Real max_force, R2Bool wake_up); +/** + * Set the joint desc motor model on an axis (0 = acceleration-based, 1 = force-based). + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetMotorModel(struct R2JointDesc *desc, uint32_t joint_axis, uint32_t model); +/** + * Set the impulse joint motor model on an axis (0 = acceleration-based, 1 = force-based). + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetMotorModel(struct R2ImpulseJointHandle handle, uint32_t joint_axis, uint32_t model, R2Bool wake_up); +/** + * Set the joint desc application-owned 128-bit user value. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetUserData(struct R2JointDesc *desc, struct R2UserData value); +/** + * Set the impulse joint application-owned 128-bit user value. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetUserData(struct R2ImpulseJointHandle handle, struct R2UserData value, R2Bool wake_up); +/** + * Set the joint desc motor position target and spring coefficients on an axis. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetMotorPosition(struct R2JointDesc *desc, uint32_t joint_axis, @@ -4617,6 +8419,11 @@ R2Status r2JointDesc_SetMotorPosition(struct R2JointDesc *desc, R2Real stiffness, R2Real damping); +/** + * Set the impulse joint motor position target and spring coefficients on an axis. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetMotorPosition(struct R2ImpulseJointHandle handle, uint32_t joint_axis, @@ -4625,12 +8432,21 @@ R2Status r2ImpulseJoint_SetMotorPosition(struct R2ImpulseJointHandle handle, R2Real damping, R2Bool wake_up); +/** + * Set the joint desc motor velocity target and damping factor on an axis. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2JointDesc_SetMotorVelocity(struct R2JointDesc *desc, uint32_t joint_axis, R2Real target_velocity, R2Real factor); +/** + * Set the impulse joint motor velocity target and damping factor on an axis. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R2Status r2ImpulseJoint_SetMotorVelocity(struct R2ImpulseJointHandle handle, uint32_t joint_axis, @@ -4639,21 +8455,30 @@ R2Status r2ImpulseJoint_SetMotorVelocity(struct R2ImpulseJointHandle handle, R2Bool wake_up); /** + * Create an owned compound shape by convex decomposition of the input surface. Release it with + * r2FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2ConvexDecompositionSharedShape(struct R2VectorView vertices, R2SurfaceElementView indices); /** + * Create an owned voxel shape by quantizing points with the supplied per-axis voxel size. Release + * it with r2FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2VoxelsSharedShapeFromPoints(struct R2Vector voxel_size, struct R2VectorView points); /** + * Create an owned voxel shape from a surface mesh with the supplied uniform voxel size. Release it + * with r2FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2VoxelizedMeshSharedShape(struct R2VectorView vertices, @@ -4661,19 +8486,26 @@ R2SharedShape *r2VoxelizedMeshSharedShape(struct R2VectorView vertices, R2Real voxel_size); /** + * Create an owned convex hull of the supplied vertices. Release it with r2FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2ConvexHullSharedShape(struct R2VectorView vertices); /** + * Create an owned triangle mesh from vertices and triangle indices. Release it with + * r2FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2TrimeshSharedShape(struct R2VectorView vertices, struct R2TriangleView indices); /** + * Create an owned polyline from vertices and edge indices. Release it with r2FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2PolylineSharedShape(struct R2VectorView vertices, @@ -4681,7 +8513,10 @@ R2SharedShape *r2PolylineSharedShape(struct R2VectorView vertices, #if defined(RAPIER_DIM2) /** + * Create an owned oriented 2D polyline from vertices and edge indices. Release it with + * r2FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2OrientedPolylineSharedShape(struct R2VectorView vertices, @@ -4690,21 +8525,29 @@ R2SharedShape *r2OrientedPolylineSharedShape(struct R2VectorView vertices, #if defined(RAPIER_DIM2) /** + * Create an owned convex polygon from vertices already ordered along its boundary. Release it with + * r2FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2ConvexPolylineSharedShape(struct R2VectorView vertices); #endif /** + * Create an owned round convex hull shape. Release it with r2FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2RoundConvexHullSharedShape(struct R2VectorView vertices, R2Real border_radius); /** + * Create an owned triangle mesh with the supplied TRIMESH_* processing flags. Release it with + * r2FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R2SharedShape *r2TrimeshSharedShapeWithFlags(struct R2VectorView vertices, @@ -4713,17 +8556,21 @@ R2SharedShape *r2TrimeshSharedShapeWithFlags(struct R2VectorView vertices, /** * Create an owned world. Release it with FreeWorld. + * @ingroup worlds */ RAPIER_API RAPIER_CALL struct R2World *r2NewWorld(void); /** * Free a world. NULL is allowed. Rejects destruction from an active callback. * The caller must prevent other threads from starting calls during destruction. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R2Status r2FreeWorld(struct R2World *world); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Compute a velocity correction from callback-visible body state, updating the PID controller + * history. The context is valid only during its callback. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2VelocityCorrection r2ReadPidController_RigidBodyCorrection(const struct R2ReadContext *context, @@ -4735,12 +8582,16 @@ struct R2VelocityCorrection r2ReadPidController_RigidBodyCorrection(const struct R2AngVector target_angvel); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the number of rigid body objects in the world. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r2ReadRigidBodyCount(const struct R2ReadContext *context); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Copy entity handles. Uses only the callback-scoped read context; never retain the context. + * @see @ref output_buffers + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r2ReadRigidBodyHandles(const struct R2ReadContext *context, @@ -4748,19 +8599,25 @@ size_t r2ReadRigidBodyHandles(const struct R2ReadContext *context, size_t capacity); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Test whether the live world contains this rigid body handle. A removed/stale handle returns + * false. Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadRigidBody_Contains(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the number of collider objects in the world. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r2ReadColliderCount(const struct R2ReadContext *context); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Copy entity handles. Uses only the callback-scoped read context; never retain the context. + * @see @ref output_buffers + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r2ReadColliderHandles(const struct R2ReadContext *context, @@ -4768,42 +8625,55 @@ size_t r2ReadColliderHandles(const struct R2ReadContext *context, size_t capacity); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Test whether the live world contains this collider handle. A removed/stale handle returns false. + * Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadCollider_Contains(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return a process-local geometry identity for caching, not a serializable ID. Keep a shared-shape + * clone alive while using it as a cache key. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r2ReadCollider_ShapeIdentity(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider local mass properties. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2MassProperties r2ReadCollider_MassProperties(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body translation/rotation lock bitmask. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL uint8_t r2ReadRigidBody_LockedAxes(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the collider is a voxel shape. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadCollider_IsVoxels(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return voxel information at a flat index; found = 0 if absent. Uses only the callback-scoped + * read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2VoxelQuery r2ReadCollider_VoxelAtFlatId(const struct R2ReadContext *context, @@ -4811,154 +8681,198 @@ struct R2VoxelQuery r2ReadCollider_VoxelAtFlatId(const struct R2ReadContext *con uint32_t id); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body next kinematic world-space pose. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Pose r2ReadRigidBody_NextPosition(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space rotation. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Rotation r2ReadRigidBody_Rotation(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space center of mass. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Vector r2ReadRigidBody_CenterOfMass(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body body-local center of mass. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Vector r2ReadRigidBody_LocalCenterOfMass(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body accumulated user-applied world-space force. Uses only the callback-scoped + * read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Vector r2ReadRigidBody_UserForce(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body accumulated user-applied world-space torque. Uses only the callback-scoped + * read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2AngVector r2ReadRigidBody_UserTorque(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body body type (R2_DYNAMIC, R2_FIXED, or a kinematic kind). Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL uint32_t r2ReadRigidBody_BodyType(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body mass. Uses only the callback-scoped read context; never retain the + * context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadRigidBody_Mass(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body gravity multiplier. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadRigidBody_GravityScale(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body linear damping coefficient. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadRigidBody_LinearDamping(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body angular damping coefficient. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadRigidBody_AngularDamping(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body kinetic energy. Uses only the callback-scoped read context; never retain + * the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadRigidBody_KineticEnergy(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body soft-CCD prediction distance. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadRigidBody_SoftCcdPrediction(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is using continuous collision detection. Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadRigidBody_IsCcdEnabled(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is dynamic. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadRigidBody_IsDynamic(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the associated soft-body handle, or an invalid handle if this is not a soft proxy. Uses + * only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2SoftBodyHandle r2ReadRigidBody_SoftBody(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is a soft-body proxy. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadRigidBody_IsSoftFrame(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is fixed. Uses only the callback-scoped read context; never retain + * the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadRigidBody_IsFixed(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is kinematic. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadRigidBody_IsKinematic(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is moving. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadRigidBody_IsMoving(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is currently using CCD for its motion. Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadRigidBody_IsCcdActive(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return world-space velocity at a world-space point, including angular motion. Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Vector r2ReadRigidBody_VelocityAtPoint(const struct R2ReadContext *context, @@ -4966,7 +8880,10 @@ struct R2Vector r2ReadRigidBody_VelocityAtPoint(const struct R2ReadContext *cont struct R2Vector point); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Copy attached collider handles. Uses only the callback-scoped read context; never retain the + * context. + * @see @ref output_buffers + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r2ReadRigidBody_Colliders(const struct R2ReadContext *context, @@ -4976,7 +8893,9 @@ size_t r2ReadRigidBody_Colliders(const struct R2ReadContext *context, #if defined(RAPIER_DIM3) /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is using gyroscopic forces. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadRigidBody_GyroscopicForcesEnabled(const struct R2ReadContext *context, @@ -4984,204 +8903,262 @@ R2Bool r2ReadRigidBody_GyroscopicForcesEnabled(const struct R2ReadContext *conte #endif /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider world-space rotation. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Rotation r2ReadCollider_Rotation(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider collision filtering groups. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2InteractionGroups r2ReadCollider_CollisionGroups(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider contact-force filtering groups. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2InteractionGroups r2ReadCollider_SolverGroups(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider application-owned 128-bit user value. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2UserData r2ReadCollider_UserData(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider event-generation bitmask (R2_COLLISION_EVENTS and + * R2_CONTACT_FORCE_EVENTS). Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL uint32_t r2ReadCollider_ActiveEvents(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider mass. Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadCollider_Mass(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider mass per unit volume. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadCollider_Density(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider current volume. Uses only the callback-scoped read context; never retain the + * context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadCollider_Volume(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider extra separation skin around the shape. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadCollider_ContactSkin(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider force threshold for contact-force events. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadCollider_ContactForceEventThreshold(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the collider is enabled. Uses only the callback-scoped read context; never retain + * the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadCollider_IsEnabled(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the current world-space axis-aligned bounds. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Aabb r2ReadCollider_ComputeAabb(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return an owned wrapper sharing the collider geometry. Release with r2FreeSharedShape. Uses + * only the callback-scoped read context; never retain the context. * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2SharedShape *r2ReadCollider_CloneShape(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Validate the index and generation in the live owning world. Cannot detect a world pointer that + * has already been freed. Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Status r2ReadRigidBody_ValidateHandle(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Validate the index and generation in the live owning world. Cannot detect a world pointer that + * has already been freed. Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Status r2ReadCollider_ValidateHandle(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space pose. Uses only the callback-scoped read context; never retain + * the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Pose r2ReadRigidBody_Position(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space translation. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Vector r2ReadRigidBody_Translation(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space linear velocity. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Vector r2ReadRigidBody_Linvel(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space angular velocity (radians per second). Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2AngVector r2ReadRigidBody_Angvel(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is sleeping. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadRigidBody_IsSleeping(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is enabled. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadRigidBody_IsEnabled(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body application-owned 128-bit user value. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2UserData r2ReadRigidBody_UserData(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider world-space pose. Uses only the callback-scoped read context; never retain + * the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Pose r2ReadCollider_Position(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider world-space translation. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2Vector r2ReadCollider_Translation(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider friction coefficient. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadCollider_Friction(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider restitution coefficient. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Real r2ReadCollider_Restitution(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the collider is a sensor (detects overlaps without contact forces). Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R2Bool r2ReadCollider_IsSensor(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Read the parent body handle during a callback; a standalone collider returns an invalid handle + * with OK status. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R2RigidBodyHandle r2ReadCollider_Parent(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Copy callback-visible body states in the supplied handle order. All handles must belong to the + * context world. + * @see @ref output_buffers + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r2ReadRigidBodyReadStates(const struct R2ReadContext *context, @@ -5197,346 +9174,846 @@ size_t r2ReadRigidBodyReadStates(const struct R2ReadContext *context, #else /* RAPIER_DIM3 */ #if defined(RAPIER_DIM2) +/** + * @ingroup joints + * Number of translational and angular joint axes in this dimension. + */ #define R3_JOINT_DOF_COUNT 3 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Number of translational and angular joint axes in this dimension. + */ #define R3_JOINT_DOF_COUNT 6 #endif +/** + * @ingroup soft_bodies + * Soft-body selector: desc particles. + */ #define R3_SOFT_DESC_PARTICLES 0 +/** + * @ingroup soft_bodies + * Soft-body selector: desc rope. + */ #define R3_SOFT_DESC_ROPE 1 +/** + * @ingroup soft_bodies + * Soft-body selector: desc grid. + */ #define R3_SOFT_DESC_GRID 2 +/** + * @ingroup soft_bodies + * Soft-body selector: desc cloth. + */ #define R3_SOFT_DESC_CLOTH 3 +/** + * @ingroup soft_bodies + * Soft-body selector: desc cuboid. + */ #define R3_SOFT_DESC_CUBOID 4 +/** + * @ingroup soft_bodies + * Soft-body selector: desc surface. + */ #define R3_SOFT_DESC_SURFACE 5 +/** + * @ingroup soft_bodies + * Soft-body selector: desc disk. + */ #define R3_SOFT_DESC_DISK 6 +/** + * @ingroup soft_bodies + * Soft-body selector: desc sphere. + */ #define R3_SOFT_DESC_SPHERE 7 +/** + * @ingroup soft_bodies + * Soft-body selector: desc cloth tube. + */ #define R3_SOFT_DESC_CLOTH_TUBE 8 +/** + * @ingroup soft_bodies + * Soft-body selector: desc volumetric. + */ #define R3_SOFT_DESC_VOLUMETRIC 9 +/** + * @ingroup soft_bodies + * Soft-body selector: binding skinned. + */ #define R3_SOFT_BINDING_SKINNED 0 +/** + * @ingroup soft_bodies + * Soft-body selector: binding direct. + */ #define R3_SOFT_BINDING_DIRECT 1 +/** + * @ingroup soft_bodies + * Soft-body selector: binding direct by position. + */ #define R3_SOFT_BINDING_DIRECT_BY_POSITION 2 #if defined(RAPIER_DIM2) +/** + * @ingroup shapes + * Treat the 2D polyline as oriented when generating contact normals. + */ #define R3_POLYLINE_ORIENTED 1 #endif +/** + * @ingroup shapes + * Prepare polyline acceleration data for deformation. + */ #define R3_POLYLINE_DEFORMABLE 2 +/** + * @ingroup shapes + * ShapeDesc kind selecting a ball. + */ #define R3_SHAPE_DESC_BALL 0 +/** + * @ingroup shapes + * ShapeDesc kind selecting a cuboid. + */ #define R3_SHAPE_DESC_CUBOID 1 +/** + * @ingroup shapes + * ShapeDesc kind selecting a round cuboid. + */ #define R3_SHAPE_DESC_ROUND_CUBOID 2 +/** + * @ingroup shapes + * ShapeDesc kind selecting a capsule. + */ #define R3_SHAPE_DESC_CAPSULE 3 +/** + * @ingroup shapes + * ShapeDesc kind selecting a segment. + */ #define R3_SHAPE_DESC_SEGMENT 4 +/** + * @ingroup shapes + * ShapeDesc kind selecting a triangle. + */ #define R3_SHAPE_DESC_TRIANGLE 5 +/** + * @ingroup shapes + * ShapeDesc kind selecting a half-space. + */ #define R3_SHAPE_DESC_HALFSPACE 6 +/** + * @ingroup shapes + * ShapeDesc kind selecting a convex hull. + */ #define R3_SHAPE_DESC_CONVEX_HULL 7 +/** + * @ingroup shapes + * ShapeDesc kind selecting a triangle mesh. + */ #define R3_SHAPE_DESC_TRIMESH 8 +/** + * @ingroup shapes + * ShapeDesc kind selecting a polyline. + */ #define R3_SHAPE_DESC_POLYLINE 9 +/** + * @ingroup shapes + * ShapeDesc kind selecting borrowed shared geometry. + */ #define R3_SHAPE_DESC_SHARED 10 +/** + * @ingroup shapes + * ShapeDesc kind selecting a heightfield. + */ #define R3_SHAPE_DESC_HEIGHTFIELD 11 +/** + * @ingroup shapes + * ShapeDesc kind selecting a cylinder. + */ #define R3_SHAPE_DESC_CYLINDER 12 +/** + * @ingroup shapes + * ShapeDesc kind selecting a cone. + */ #define R3_SHAPE_DESC_CONE 13 +/** + * @ingroup shapes + * ShapeDesc kind selecting compound child shapes. + */ #define R3_SHAPE_DESC_COMPOUND 14 +/** + * @ingroup shapes + * ShapeDesc kind selecting a round cylinder. + */ #define R3_SHAPE_DESC_ROUND_CYLINDER 15 +/** + * @ingroup colliders + * Mass density. + */ #define R3_MASS_DENSITY 0 +/** + * @ingroup colliders + * Mass total. + */ #define R3_MASS_TOTAL 1 +/** + * @ingroup colliders + * Mass properties. + */ #define R3_MASS_PROPERTIES 2 +/** + * @ingroup errors + * C binary ABI revision expected by this header. + */ #define R3_ABI_VERSION 1 +/** + * @ingroup rigid_bodies + * Dynamic body affected by forces and contacts. + */ #define R3_DYNAMIC 0 +/** + * @ingroup rigid_bodies + * Immovable body. + */ #define R3_FIXED 1 +/** + * @ingroup rigid_bodies + * Kinematic body controlled by its next pose. + */ #define R3_KINEMATIC_POSITION_BASED 2 +/** + * @ingroup rigid_bodies + * Kinematic body controlled by its velocity. + */ #define R3_KINEMATIC_VELOCITY_BASED 3 +/** + * @ingroup colliders + * Enable collision-start and collision-stop events for this collider. + */ #define R3_COLLISION_EVENTS 1 +/** + * @ingroup colliders + * Enable contact-force events for this collider, subject to its force threshold. + */ #define R3_CONTACT_FORCE_EVENTS 2 +/** + * @ingroup colliders + * Combine the two material coefficients by their arithmetic mean. + */ #define R3_COMBINE_AVERAGE 0 +/** + * @ingroup colliders + * Use the smaller of the two material coefficients. + */ #define R3_COMBINE_MIN 1 +/** + * @ingroup colliders + * Multiply the two material coefficients. + */ #define R3_COMBINE_MULTIPLY 2 +/** + * @ingroup colliders + * Use the larger of the two material coefficients. + */ #define R3_COMBINE_MAX 3 +/** + * @ingroup colliders + * Invoke the contact-pair filtering hook for this collider. + */ #define R3_FILTER_CONTACT_PAIRS 1 +/** + * @ingroup colliders + * Invoke the sensor-intersection filtering hook for this collider. + */ #define R3_FILTER_INTERSECTION_PAIR 2 +/** + * @ingroup colliders + * Invoke the solver-contact modification hook for this collider. + */ #define R3_MODIFY_SOLVER_CONTACTS 4 +/** + * @ingroup queries + * Query filter: exclude fixed. + */ #define R3_QUERY_EXCLUDE_FIXED 1 +/** + * @ingroup queries + * Query filter: exclude kinematic. + */ #define R3_QUERY_EXCLUDE_KINEMATIC 2 +/** + * @ingroup queries + * Query filter: exclude dynamic. + */ #define R3_QUERY_EXCLUDE_DYNAMIC 4 +/** + * @ingroup queries + * Query filter: exclude sensors. + */ #define R3_QUERY_EXCLUDE_SENSORS 8 +/** + * @ingroup queries + * Query filter: exclude solids. + */ #define R3_QUERY_EXCLUDE_SOLIDS 16 +/** + * @ingroup queries + * Query filter: only dynamic. + */ #define R3_QUERY_ONLY_DYNAMIC 3 +/** + * @ingroup queries + * Query filter: only kinematic. + */ #define R3_QUERY_ONLY_KINEMATIC 5 +/** + * @ingroup queries + * Query filter: only fixed. + */ #define R3_QUERY_ONLY_FIXED 6 +/** + * @ingroup events + * Debug-render flag: collider shapes. + */ #define R3_DEBUG_COLLIDER_SHAPES 1 +/** + * @ingroup events + * Debug-render flag: rigid body axes. + */ #define R3_DEBUG_RIGID_BODY_AXES 2 +/** + * @ingroup events + * Debug-render flag: multibody joints. + */ #define R3_DEBUG_MULTIBODY_JOINTS 4 +/** + * @ingroup events + * Debug-render flag: impulse joints. + */ #define R3_DEBUG_IMPULSE_JOINTS 8 +/** + * @ingroup events + * Debug-render flag: solver contacts. + */ #define R3_DEBUG_SOLVER_CONTACTS 16 +/** + * @ingroup events + * Debug-render flag: contacts. + */ #define R3_DEBUG_CONTACTS 32 +/** + * @ingroup events + * Debug-render flag: collider aabbs. + */ #define R3_DEBUG_COLLIDER_AABBS 64 +/** + * @ingroup events + * Debug-render flag: soft bodies. + */ #define R3_DEBUG_SOFT_BODIES 128 +/** + * @ingroup events + * Debug-render flag: pseudo normals. + */ #define R3_DEBUG_PSEUDO_NORMALS 256 +/** + * @ingroup events + * Debug-render flag: soft volume contacts. + */ #define R3_DEBUG_SOFT_VOLUME_CONTACTS 512 +/** + * @ingroup events + * Debug-render flag: soft body stress. + */ #define R3_DEBUG_SOFT_BODY_STRESS 1024 +/** + * @ingroup colliders + * Require both membership/filter intersections to be nonempty. + */ #define R3_GROUPS_AND 0 +/** + * @ingroup colliders + * Accept either membership/filter intersection if both participants select OR; otherwise use AND. + */ #define R3_GROUPS_OR 1 +/** + * @ingroup joints + * Motor stiffness and damping are acceleration-based, independent of mass. + */ #define R3_MOTOR_ACCELERATION_BASED 0 +/** + * @ingroup joints + * Motor stiffness and damping are force-based, so response depends on mass. + */ #define R3_MOTOR_FORCE_BASED 1 +/** + * @ingroup soft_bodies + * Cell model that constrains volume without elastic shear response. + */ #define R3_SOFT_CELL_VOLUME 0 +/** + * @ingroup soft_bodies + * Corotational elastic cell model. + */ #define R3_SOFT_CELL_COROTATIONAL 1 +/** + * @ingroup soft_bodies + * Neo-Hookean elastic cell model. + */ #define R3_SOFT_CELL_NEO_HOOKEAN 2 +/** + * @ingroup soft_bodies + * Use the constraint-based soft-body solver. + */ #define R3_SOFT_SOLVER_CONSTRAINTS 0 +/** + * @ingroup soft_bodies + * Use the finite-element solver; requires RAPIER_FEM. + */ #define R3_SOFT_SOLVER_FEM 1 +/** + * @ingroup joints + * Joint axis index for translation along local X. + */ #define R3_AXIS_LIN_X 0 +/** + * @ingroup joints + * Joint axis index for translation along local Y. + */ #define R3_AXIS_LIN_Y 1 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: translation x. + */ #define R3_LOCK_TRANSLATION_X 1 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: translation y. + */ #define R3_LOCK_TRANSLATION_Y 2 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: translation z. + */ #define R3_LOCK_TRANSLATION_Z 4 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: rotation x. + */ #define R3_LOCK_ROTATION_X 8 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: rotation y. + */ #define R3_LOCK_ROTATION_Y 16 +/** + * @ingroup rigid_bodies + * Rigid-body lock bit: rotation z. + */ #define R3_LOCK_ROTATION_Z 32 #if defined(RAPIER_DIM2) +/** + * @ingroup joints + * Joint axis index for the first rotation: Z in 2D, local X in 3D. + */ #define R3_AXIS_ANG_X 2 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Joint axis index for the first rotation: Z in 2D, local X in 3D. + */ #define R3_AXIS_ANG_X 3 #endif #if defined(RAPIER_DIM2) +/** + * @ingroup joints + * Locked-axis mask for a fixed joint: all translations and rotations. + */ #define R3_JOINT_FIXED_AXES 7 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Locked-axis mask for a fixed joint: all translations and rotations. + */ #define R3_JOINT_FIXED_AXES 63 #endif #if defined(RAPIER_DIM2) +/** + * @ingroup joints + * Locked-axis mask for a revolute joint: only the first angular axis is free. + */ #define R3_JOINT_REVOLUTE_AXES 3 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Locked-axis mask for a revolute joint: only the first angular axis is free. + */ #define R3_JOINT_REVOLUTE_AXES 55 #endif #if defined(RAPIER_DIM2) +/** + * @ingroup joints + * Locked-axis mask for a prismatic joint: only translation along local X is free. + */ #define R3_JOINT_PRISMATIC_AXES 6 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Locked-axis mask for a prismatic joint: only translation along local X is free. + */ #define R3_JOINT_PRISMATIC_AXES 62 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Joint axis index for translation along local Z. + */ #define R3_AXIS_LIN_Z 2 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Joint axis index for rotation around local Y. + */ #define R3_AXIS_ANG_Y 4 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Joint axis index for rotation around local Z. + */ #define R3_AXIS_ANG_Z 5 #endif #if defined(RAPIER_DIM3) +/** + * @ingroup joints + * Locked-axis mask for a spherical joint: translations locked, rotations free. + */ #define R3_JOINT_SPHERICAL_AXES 7 #endif /** - * Read-only body type used by soft-body cluster proxies; not valid for builder_new. + * @ingroup soft_bodies + * Read-only body type of soft-body cluster proxies; cannot be used to construct a rigid body. */ #define R3_SOFT_FRAME 4 +/** + * @ingroup queries + * Shape-cast iteration limit reached before convergence. + */ #define R3_SHAPE_CAST_OUT_OF_ITERATIONS 0 +/** + * @ingroup queries + * Shape cast converged to the reported impact. + */ #define R3_SHAPE_CAST_CONVERGED 1 +/** + * @ingroup queries + * Shape-cast numerical solver failed to converge. + */ #define R3_SHAPE_CAST_FAILED 2 +/** + * @ingroup queries + * Shapes overlap or are within the target distance at the start of the cast. + */ #define R3_SHAPE_CAST_PENETRATING 3 +/** + * @ingroup queries + * The hit feature is unspecified. + */ #define R3_FEATURE_UNKNOWN 0 +/** + * @ingroup queries + * The feature ID denotes a vertex. + */ #define R3_FEATURE_VERTEX 1 +/** + * @ingroup queries + * The feature ID denotes an edge. + */ #define R3_FEATURE_EDGE 2 +/** + * @ingroup queries + * The feature ID denotes a face. + */ #define R3_FEATURE_FACE 3 /** - * Native URDF/MJCF multibody insertion flags. + * @ingroup joints + * Insert reduced-coordinate joints as kinematic articulations. */ #define R3_MULTIBODY_JOINTS_ARE_KINEMATIC 1 +/** + * @ingroup joints + * Disable contacts between colliders of the inserted articulation. + */ #define R3_MULTIBODY_DISABLE_SELF_CONTACTS 2 +/** + * @ingroup joints + * Skip joints that would close a loop in the articulation. + */ #define R3_MULTIBODY_SKIP_LOOP_CLOSURES 4 +/** + * @ingroup joints + * Do not import joint motors into the articulation. + */ #define R3_MULTIBODY_SKIP_JOINT_MOTORS 8 +/** + * @ingroup joints + * Do not import joint limits into the articulation. + */ #define R3_MULTIBODY_SKIP_JOINT_LIMITS 16 +/** + * @ingroup joints + * Do not import joint springs into the articulation. + */ #define R3_MULTIBODY_SKIP_JOINT_SPRINGS 32 /** - * Parry triangle-mesh and heightfield flags used by the public shape constructors. + * @ingroup shapes + * Merge triangle-mesh vertices with identical positions. */ #define R3_TRIMESH_MERGE_DUPLICATE_VERTICES 16 +/** + * @ingroup shapes + * Correct contact normals at internal mesh edges; includes duplicate-vertex merging. + */ #define R3_TRIMESH_FIX_INTERNAL_EDGES 144 +/** + * @ingroup shapes + * Prepare triangle-mesh acceleration data for deformation. + */ #define R3_TRIMESH_DEFORMABLE 256 +/** + * @ingroup shapes + * Correct internal-edge contacts on both sides of the triangle mesh. + */ #define R3_TRIMESH_FIX_INTERNAL_EDGES_TWO_SIDED 656 +/** + * @ingroup shapes + * Correct contact normals at internal heightfield edges. + */ #define R3_HEIGHTFIELD_FIX_INTERNAL_EDGES 1 /** * Immutable owned byte buffer. Release with the matching FreeBytes function. + * @ingroup worlds */ typedef struct R3Bytes R3Bytes; /** * Borrowed native contact context. Valid only during its callback; never retain or free it. + * @ingroup events */ typedef struct R3ContactModificationContext R3ContactModificationContext; #if defined(RAPIER_DIM3) +/** + * Vehicle controller borrowing its chassis world. Release with the matching Free function. + * @ingroup controllers + */ typedef struct R3DynamicRayCastVehicleController R3DynamicRayCastVehicleController; #endif /** - * Events accumulate until clear. Copying events never drains them, allowing two-call buffer sizing. + * Events accumulate until clear. Copying events never drains them, allowing two-call buffer + * sizing. + * @ingroup events */ typedef struct R3EventCollector R3EventCollector; /** * Controller plus reusable collision output from the last move_shape call. + * Kinematic character controller and its last collision list. Release with the matching Free + * function. + * @ingroup controllers */ typedef struct R3KinematicCharacterController R3KinematicCharacterController; #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Loaded MJCF robot and its visual/keyframe data. Release with the matching Free function. + * @ingroup robotics + */ typedef struct R3MjcfRobot R3MjcfRobot; #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Owned container of borrowed handles and actuators of an inserted MJCF robot. Release with the + * matching Free function. + * @ingroup robotics + */ typedef struct R3MjcfRobotHandles R3MjcfRobotHandles; #endif /** * PID controller with persistent integral state. + * Stateful proportional-integral-derivative controller. Release with the matching Free function. + * @ingroup controllers */ typedef struct R3PidController R3PidController; /** * Callback-scoped read access to bodies and colliders. Never retain or free it. * Only the Read* functions accept this context; it cannot mutate the world. + * @ingroup callbacks */ typedef struct R3ReadContext R3ReadContext; /** * Owned tessellated shape: flat triangle vertices and independent line segments, in local space. - * Rounded 3D shapes use their inner surface (as in the Rust testbed). Halfspaces use a finite patch. + * Rounded 3D shapes use their inner surface (as in the Rust testbed). Halfspaces use a finite + * patch. + * @ingroup shapes */ typedef struct R3ShapeMesh R3ShapeMesh; /** * Owned copy of a tear event. Read particle remapping before rebuilding render meshes. + * @ingroup events */ typedef struct R3SoftBodyTearEvent R3SoftBodyTearEvent; #if defined(RAPIER_DIM3) /** * Owned indexed geometry from Parry's shape tessellation, preserving its vertex order. + * @ingroup shapes */ typedef struct R3TriMeshData R3TriMeshData; #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Loaded URDF robot; insertion clones its simulation objects. Release with the matching Free + * function. + * @ingroup robotics + */ typedef struct R3UrdfRobot R3UrdfRobot; #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Owned container of borrowed handles to an inserted URDF robot. Release with the matching Free + * function. + * @ingroup robotics + */ typedef struct R3UrdfRobotHandles R3UrdfRobotHandles; #endif @@ -5544,224 +10021,621 @@ typedef struct R3UrdfRobotHandles R3UrdfRobotHandles; * Sole owner of simulation state. Handles belong to the world that created them. * Ordinary reads may overlap. A mutation or step requires exclusive access. * Destruction must be externally synchronized with all users of this pointer. + * @ingroup worlds */ typedef struct R3World R3World; #if defined(RAPIER_F32) +/** + * Floating-point scalar: float for f32, double for f64. + * @ingroup math + */ typedef float R3Real; #endif #if defined(RAPIER_F64) +/** + * Floating-point scalar: float for f32, double for f64. + * @ingroup math + */ typedef double R3Real; #endif +/** + * Spring softness expressed as natural frequency and damping ratio. + * @ingroup math + */ typedef struct R3SpringCoefficients { + /** + * Nonnegative spring natural frequency in Hz. + */ R3Real natural_frequency; + /** + * Nonnegative damping ratio; 1 is critical damping. + */ R3Real damping_ratio; } R3SpringCoefficients; /** * ABI booleans are uint32_t: zero is false, one is true. + * @ingroup math */ typedef uint32_t R3Bool; +/** + * Optional scalar override; enabled = 0 selects no override. + * @ingroup math + */ typedef struct R3OptionalReal { + /** + * Whether this setting/object is enabled (0 or 1). + */ R3Bool enabled; + /** + * Value used when enabled is 1. + */ R3Real value; } R3OptionalReal; +/** + * Optional unsigned integer override; enabled = 0 selects no override. + * @ingroup math + */ typedef struct R3OptionalU32 { + /** + * Whether this setting/object is enabled (0 or 1). + */ R3Bool enabled; + /** + * Value used when enabled is 1. + */ uint32_t value; } R3OptionalU32; /** * Plain configuration data; initialize defaults, edit, then apply. No destructor. + * @ingroup soft_bodies */ typedef struct R3SoftBodyMaterial { + /** + * Spring coefficients for structural edge constraints. + */ struct R3SpringCoefficients edgeSoftness; + /** + * Spring coefficients for bending edges and dihedrals. + */ struct R3SpringCoefficients bendSoftness; + /** + * Spring coefficients for cell and global volume constraints. + */ struct R3SpringCoefficients volumeSoftness; + /** + * Spring coefficients for shape-matching constraints. + */ struct R3SpringCoefficients shapeMatchingSoftness; + /** + * Elastic modulus: force per area in 3D, force per length in 2D; nonnegative. + */ R3Real youngModulus; + /** + * Poisson ratio for elastic cells, in [0, 0.5). + */ R3Real poissonRatio; + /** + * Nonnegative damping ratio of elastic cells. + */ R3Real elasticDampingRatio; + /** + * Cell strain threshold for plastic flow; zero disables plasticity. + */ R3Real plasticYield; + /** + * Nonnegative rate per second at which excess cell strain becomes permanent. + */ R3Real plasticCreep; + /** + * Maximum accumulated cell plastic stretch, measured by the norm of P - I. + */ R3Real plasticMax; + /** + * Rate per second pulling particle velocities toward best-fit rigid motion; zero disables it. + */ R3Real deformationDamping; + /** + * Edge strain threshold for plastic flow; zero disables plasticity. + */ R3Real edgePlasticYield; + /** + * Nonnegative rate per second at which excess edge strain becomes permanent. + */ R3Real edgePlasticCreep; + /** + * Maximum permanent edge-length change as a fraction of its initial length. + */ R3Real edgePlasticMax; + /** + * Plastic flow direction: 0 both, 1 compression only, 2 tension only. + */ uint32_t edgePlasticFlow; + /** + * Optional strain threshold for tearing; disabled means no strain-based tearing. + */ struct R3OptionalReal tearStrain; + /** + * Optional tensile edge-force threshold for tearing. + */ struct R3OptionalReal tearForce; + /** + * Exponential load-smoothing time constant in seconds; zero disables smoothing. + */ R3Real tearSmoothing; + /** + * Tear-threshold multiplier for undamaged interior elements. + */ R3Real interiorStrength; + /** + * Maximum ordinary edge tears per step; edges above twice their threshold bypass the limit. + */ uint32_t maxTearsPerStep; + /** + * Optional minimum particle count of tear pieces. + */ struct R3OptionalU32 minPiece; } R3SoftBodyMaterial; /** * Plain configuration data; initialize defaults, edit, then apply. No destructor. + * @ingroup soft_bodies */ typedef struct R3SoftRecoverySettings { + /** + * Expand speculative contact margins to cover particle velocities set between steps. + */ R3Bool authoredVelocityMargin; + /** + * Enable speculative edge-edge collision constraints. + */ R3Bool edgeSpeculation; + /** + * Detect inverted cells to support self-contact recovery. + */ R3Bool invertedCellDetection; + /** + * Detect surface self-crossings each step. + */ R3Bool selfCrossingDetection; + /** + * Skip self-crossing detection when accumulated motion cannot have created a crossing. + */ R3Bool detectionMotionGating; + /** + * Detect boundary crossings between soft bodies. + */ R3Bool crossBodyDetection; + /** + * Disable contacts on tangled features so elasticity can untangle them. + */ R3Bool selfStandDown; + /** + * Allow contacts at cross-body crossings to expel, but not hold, the intruder. + */ R3Bool crossBodyExpelGate; + /** + * Disable edge constraints touching cross-body crossings. + */ R3Bool edgeStandDown; + /** + * Repel crossing features toward their neighborhood's side of the surface. + */ R3Bool crossingRepulsion; + /** + * Guide cross-body repulsion by overlap-volume normals; closed meshes only. + */ R3Bool crossingRepulsionGuide; + /** + * Guide self-crossing repulsion by self-intersection-volume normals; closed meshes only. + */ R3Bool crossingRepulsionSelfGuide; + /** + * Maximum recovery rate in length units per second, scaled by lengthUnit. + */ R3Real recoveryPace; + /** + * Enable intersection-volume constraints for overlapping closed surfaces. + */ R3Bool overlapConstraints; + /** + * Enable intersection-volume constraints against rigid colliders. + */ R3Bool overlapRigid; + /** + * Skip pair overlap constraints for self-crossed meshes. + */ R3Bool overlapSkipSelfTangled; + /** + * Disable 3D closed-surface edge constraints where overlap constraints take over. + */ R3Bool overlapEdgeStandDown; + /** + * Velocity-change limit per step, as a multiple of recoveryPace. + */ R3Real overlapConstraintPace; + /** + * Per-point constraints inside overlap patches: 0 keep, 1 stand down, 2 align with overlap + * normal. + */ uint32_t overlapPatchConstraints; + /** + * Measure overlap on contact-skin surfaces rather than bare geometry. + */ R3Bool overlapSkinVolume; + /** + * Overlap depth retained by recovery, as a fraction of the pair's contact skins. + */ R3Real overlapKeptDepth; + /** + * Enable recovery of self-intersection regions. + */ R3Bool overlapSelfRegions; + /** + * Use the overlap normal for recovery pushes. + */ R3Bool overlapNormalPush; + /** + * Use spatially split overlap-volume constraints. + */ R3Bool overlapMultiVolume; + /** + * Cells per tangent axis of the multi-volume grid. + */ uint32_t overlapSplit; + /** + * Recovery progress patience in steps. + */ uint32_t overlapPatience; + /** + * Relative overlap-volume decrease that counts as recovery progress. + */ R3Real overlapProgressMargin; } R3SoftRecoverySettings; #if defined(RAPIER_FEM) /** * Plain configuration data; initialize defaults, edit, then apply. No destructor. + * @ingroup soft_bodies */ typedef struct R3SoftFemParameters { + /** + * FEM iterative linear-solver tolerance. + */ R3Real linearTolerance; + /** + * Maximum FEM linear-solver iterations. + */ size_t maxLinearIterations; + /** + * Maximum degrees of freedom solved by the dense FEM solver. + */ size_t maxDenseDofs; } R3SoftFemParameters; #endif /** * Plain configuration data; initialize defaults, edit, then apply. No destructor. + * @ingroup soft_bodies */ typedef struct R3SoftBodiesSettings { + /** + * Soft-body crossing detection and recovery settings. + */ struct R3SoftRecoverySettings recovery; + /** + * Strain threshold for re-solving soft constraints after contacts within a substep. + */ R3Real resweepStrain; + /** + * Maximum additional substeps requested by soft-body motion. + */ size_t maxExtraSubsteps; + /** + * Multiplier on contact natural frequency for soft-body contacts. + */ R3Real contactStiffening; #if defined(RAPIER_FEM) + /** + * FEM linear-solver settings, present only when RAPIER_FEM is enabled. + */ struct R3SoftFemParameters fem; #endif } R3SoftBodiesSettings; /** * Plain configuration data; initialize defaults, edit, then apply. No destructor. + * @ingroup worlds */ typedef struct R3IntegrationParameters { + /** + * Simulation step duration in seconds. + */ R3Real dt; + /** + * Minimum CCD substep duration in seconds. + */ R3Real minCcdDt; + /** + * Spring coefficients for dynamic contact constraints. + */ struct R3SpringCoefficients contactSoftness; + /** + * Spring coefficients for contacts against fixed bodies. + */ struct R3SpringCoefficients staticContactSoftness; + /** + * Scale applied to cached impulses when warmstarting. + */ R3Real warmstartCoefficient; + /** + * Typical world-space length of one meter; scales solver tolerances, not geometry. + */ R3Real lengthUnit; + /** + * Soft-body integration and recovery settings. + */ struct R3SoftBodiesSettings softBodies; + /** + * Allowed penetration divided by lengthUnit. + */ R3Real normalizedAllowedLinearError; + /** + * Maximum penetration-correction speed divided by lengthUnit. + */ R3Real normalizedMaxCorrectiveVelocity; + /** + * Speculative-contact distance divided by lengthUnit. + */ R3Real normalizedPredictionDistance; + /** + * Maximum linear speed divided by lengthUnit. + */ R3Real normalizedMaxLinearVelocity; + /** + * Number of solver substeps/iterations; must be positive. + */ size_t numSolverIterations; + /** + * PGS iterations per solver substep. + */ size_t numInternalPgsIterations; + /** + * Stabilization iterations after velocity solving. + */ size_t numInternalStabilizationIterations; + /** + * Maximum CCD substeps; 0 disables all CCD for the world. + */ size_t maxCcdSubsteps; + /** + * Whether to cluster contacts for solving. + */ R3Bool contactClustering; + /** + * Whether to reuse nearby contacts between steps. + */ R3Bool contactRecycling; + /** + * Contact recycling distance divided by lengthUnit. + */ R3Real normalizedContactRecycleDistance; + /** + * Whether to solve friction in the bias pass. + */ R3Bool frictionInBiasPass; + /** + * Whether to warmstart joint constraints. + */ R3Bool warmstartJoints; #if defined(RAPIER_DIM3) + /** + * Friction model: 0 simplified, 1 Coulomb (3D only). + */ uint32_t frictionModel; #endif } R3IntegrationParameters; /** * Status-returning operations use these integer codes. + * @ingroup errors */ typedef uint32_t R3Status; +/** + * Cartesian vector with two or three components. + * @ingroup math + */ typedef struct R3Vector { + /** + * X component. + */ R3Real x; + /** + * Y component. + */ R3Real y; #if defined(RAPIER_DIM3) + /** + * Z component. + */ R3Real z; #endif } R3Vector; /** * 2D: angle in radians. 3D: unit quaternion in x,y,z,w order (normalized on input). + * @ingroup math */ typedef struct R3Rotation { #if defined(RAPIER_DIM2) + /** + * Rotation angle in radians. + */ R3Real angle; #endif #if defined(RAPIER_DIM3) + /** + * X component. + */ R3Real x; #endif #if defined(RAPIER_DIM3) + /** + * Y component. + */ R3Real y; #endif #if defined(RAPIER_DIM3) + /** + * Z component. + */ R3Real z; #endif #if defined(RAPIER_DIM3) + /** + * Quaternion scalar component. + */ R3Real w; #endif } R3Rotation; +/** + * Rigid transform combining translation and rotation. Use TranslationPose with a zero vector for + * identity. + * @ingroup math + */ typedef struct R3Pose { + /** + * Translation vector. + */ struct R3Vector translation; + /** + * Rotation value. + */ struct R3Rotation rotation; } R3Pose; +/** + * Lower and upper axis limits in length units or radians. + * @ingroup joints + */ typedef struct R3JointLimits { + /** + * Minimum allowed axis displacement (length or radians). + */ R3Real min; + /** + * Maximum allowed axis displacement (length or radians). + */ R3Real max; } R3JointLimits; +/** + * Position/velocity motor settings for one joint axis. + * @ingroup joints + */ typedef struct R3JointMotor { + /** + * Motor target velocity (length per second or radians per second). + */ R3Real targetVel; + /** + * Motor target position (length or radians). + */ R3Real targetPos; + /** + * Nonnegative motor spring stiffness. + */ R3Real stiffness; + /** + * Nonnegative motor damping. + */ R3Real damping; + /** + * Nonnegative maximum force or torque. + */ R3Real maxForce; + /** + * Motor model: 0 acceleration-based, 1 force-based. + */ uint32_t model; } R3JointMotor; +/** + * Application-defined 128-bit value. Contains no owned pointers. + * @ingroup math + */ typedef struct R3UserData { + /** + * Low 64 bits of the application value. + */ uint64_t low; + /** + * High 64 bits of the application value. + */ uint64_t high; } R3UserData; /** * Copyable joint configuration. Limits/motors take effect when their axis mask is enabled. * Solver impulses are deliberately excluded. Applying data resets cached limit and motor impulses. + * @ingroup joints */ typedef struct R3JointDesc { + /** + * Joint frame in body 1 local coordinates. + */ struct R3Pose localFrame1; + /** + * Joint frame in body 2 local coordinates. + */ struct R3Pose localFrame2; + /** + * Locked joint degrees of freedom; translations precede rotations. + */ uint8_t lockedAxes; + /** + * Axis mask enabling corresponding limits entries. + */ uint8_t limitAxes; + /** + * Axis mask enabling corresponding motors entries. + */ uint8_t motorAxes; + /** + * Axis mask sharing a coupled constraint. + */ uint8_t coupledAxes; + /** + * Axis limits in translation-then-rotation order. + */ struct R3JointLimits limits[R3_JOINT_DOF_COUNT]; + /** + * Axis motors in translation-then-rotation order. + */ struct R3JointMotor motors[R3_JOINT_DOF_COUNT]; + /** + * Spring coefficients for constraint correction. + */ struct R3SpringCoefficients softness; + /** + * Whether connected bodies may collide. + */ R3Bool contactsEnabled; + /** + * Whether this setting/object is enabled (0 or 1). + */ R3Bool enabled; + /** + * Application data; Rapier does not own pointers encoded in it. + */ struct R3UserData userData; } R3JointDesc; @@ -5769,13 +10643,20 @@ typedef struct R3JointDesc { * Copyable non-owning handle: world pointer plus entity index and generation. * The world must remain alive throughout every use. Copying does not retain it. * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + * @ingroup joints */ typedef struct R3ImpulseJointHandle { /** * Borrowed owning world. Never use this handle after freeing that world. */ struct R3World *world; + /** + * Zero-based element index. + */ uint32_t index; + /** + * Slot generation used to reject stale handles. Do not modify it. + */ uint32_t generation; } R3ImpulseJointHandle; @@ -5783,13 +10664,20 @@ typedef struct R3ImpulseJointHandle { * Copyable non-owning handle: world pointer plus entity index and generation. * The world must remain alive throughout every use. Copying does not retain it. * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + * @ingroup rigid_bodies */ typedef struct R3RigidBodyHandle { /** * Borrowed owning world. Never use this handle after freeing that world. */ struct R3World *world; + /** + * Slot index; UINT32_MAX denotes the explicit invalid handle. + */ uint32_t index; + /** + * Slot generation used to reject stale handles. Do not modify it. + */ uint32_t generation; } R3RigidBodyHandle; @@ -5797,213 +10685,396 @@ typedef struct R3RigidBodyHandle { * Copyable non-owning handle: world pointer plus entity index and generation. * The world must remain alive throughout every use. Copying does not retain it. * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + * @ingroup joints */ typedef struct R3MultibodyJointHandle { /** * Borrowed owning world. Never use this handle after freeing that world. */ struct R3World *world; + /** + * Zero-based element index. + */ uint32_t index; + /** + * Slot generation used to reject stale handles. Do not modify it. + */ uint32_t generation; } R3MultibodyJointHandle; /** * Parameters for the native volumetric mesher. Enclosure: 0 cover, 1 crust (3D). + * @ingroup shapes */ typedef struct R3VolumeMeshParameters { + /** + * Positive target cell size for volume meshing. + */ R3Real cell_size; #if defined(RAPIER_DIM2) + /** + * Minimum target element angle in radians; above pi/6, refinement may stop early. + */ R3Real min_angle; #endif #if defined(RAPIER_DIM3) + /** + * Enclosure strategy: 0 covers the whole shape, 1 encloses only its surface. + */ uint32_t enclosure; #endif #if defined(RAPIER_DIM3) + /** + * Number of cover-smoothing passes. + */ uint32_t cover_smoothing; #endif #if defined(RAPIER_DIM3) + /** + * Minimum smoothed-cover distance from the shape, as a fraction of local cell size. + */ R3Real cover_guard; #endif #if defined(RAPIER_DIM3) + /** + * Maximum boundary-cell halvings below cell_size before cover smoothing. + */ uint32_t cover_subdivisions; #endif } R3VolumeMeshParameters; /** - * Borrowed array of vector elements. count always counts elements, not scalars. + * Borrowed array of vector elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R3VectorView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R3Vector *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R3VectorView; /** - * Borrowed array of real elements. count always counts elements, not scalars. + * Borrowed array of real elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R3RealView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const R3Real *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R3RealView; /** - * Borrowed array of index elements. count always counts elements, not scalars. + * Borrowed array of index elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R3IndexView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const uint32_t *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R3IndexView; /** * Vertex indices for one edge; contiguous u32 fields with no padding. + * @ingroup math */ typedef struct R3Edge { + /** + * First vertex index. + */ uint32_t a; + /** + * Second vertex index. + */ uint32_t b; } R3Edge; /** - * Borrowed array of edge elements. count always counts elements, not scalars. + * Borrowed array of edge elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R3EdgeView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R3Edge *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R3EdgeView; +/** + * Spring-coefficient override for one soft-body edge. + * @ingroup soft_bodies + */ typedef struct R3SoftEdgeSoftness { + /** + * Zero-based edge index. + */ uint32_t edge; + /** + * Spring coefficients for constraint correction. + */ struct R3SpringCoefficients softness; } R3SoftEdgeSoftness; /** * Borrowed elements; count counts elements. Data must remain live through insertion. + * @ingroup soft_bodies */ typedef struct R3SoftEdgeSoftnessView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R3SoftEdgeSoftness *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R3SoftEdgeSoftnessView; +/** + * Tear-resistance override for one soft-body edge. + * @ingroup soft_bodies + */ typedef struct R3SoftEdgeTear { + /** + * Zero-based edge index. + */ uint32_t edge; + /** + * Nonnegative edge tear-resistance multiplier. + */ R3Real resistance; } R3SoftEdgeTear; /** * Borrowed elements; count counts elements. Data must remain live through insertion. + * @ingroup soft_bodies */ typedef struct R3SoftEdgeTearView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R3SoftEdgeTear *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R3SoftEdgeTearView; /** * Vertex indices for one triangle; contiguous u32 fields with no padding. + * @ingroup math */ typedef struct R3Triangle { + /** + * First vertex index. + */ uint32_t a; + /** + * Second vertex index. + */ uint32_t b; + /** + * Third vertex index. + */ uint32_t c; } R3Triangle; /** - * Borrowed array of triangle elements. count always counts elements, not scalars. + * Borrowed array of triangle elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R3TriangleView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R3Triangle *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R3TriangleView; /** * Vertex indices for one tetrahedron; contiguous u32 fields with no padding. + * @ingroup math */ typedef struct R3Tetrahedron { + /** + * First vertex index. + */ uint32_t a; + /** + * Second vertex index. + */ uint32_t b; + /** + * Third vertex index. + */ uint32_t c; + /** + * Fourth vertex index. + */ uint32_t d; } R3Tetrahedron; /** - * Borrowed array of tetrahedron elements. count always counts elements, not scalars. + * Borrowed array of tetrahedron elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R3TetrahedronView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R3Tetrahedron *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R3TetrahedronView; #if defined(RAPIER_DIM2) +/** + * Borrowed cell array: triangles in 2D, tetrahedra in 3D. + * @ingroup math + */ typedef struct R3TriangleView R3CellView; #endif #if defined(RAPIER_DIM3) +/** + * Borrowed cell array: triangles in 2D, tetrahedra in 3D. + * @ingroup math + */ typedef struct R3TetrahedronView R3CellView; #endif #if defined(RAPIER_DIM2) +/** + * Borrowed surface array: edges in 2D, triangles in 3D. + * @ingroup math + */ typedef struct R3EdgeView R3SurfaceElementView; #endif #if defined(RAPIER_DIM3) +/** + * Borrowed surface array: edges in 2D, triangles in 3D. + * @ingroup math + */ typedef struct R3TriangleView R3SurfaceElementView; #endif /** * Vertex indices for one dihedral; contiguous u32 fields with no padding. + * @ingroup math */ typedef struct R3Dihedral { + /** + * First vertex index. + */ uint32_t a; + /** + * Second vertex index. + */ uint32_t b; + /** + * Third vertex index. + */ uint32_t c; + /** + * Fourth vertex index. + */ uint32_t d; } R3Dihedral; /** - * Borrowed array of dihedral elements. count always counts elements, not scalars. + * Borrowed array of dihedral elements. count counts elements of the declared type. * Copying this view does not copy its data or extend its lifetime. No Free is needed. * Data must remain live through the build/insert call that reads the description. * NULL is permitted only when count is zero. + * @ingroup math */ typedef struct R3DihedralView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R3Dihedral *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R3DihedralView; /** * Optional boolean override. When disabled, retain the recipe's native default. + * @ingroup math */ typedef struct R3OptionalBool { + /** + * Whether this setting/object is enabled (0 or 1). + */ R3Bool enabled; + /** + * Value used when enabled is 1. + */ R3Bool value; } R3OptionalBool; /** * Opaque SharedShape. See the ownership and borrowing contract in README.md. + * @ingroup shapes */ typedef struct R3SharedShape R3SharedShape; /** * Borrowed elements; count counts elements. Data must remain live through insertion. + * @ingroup shapes */ typedef struct R3CompoundShapeView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const struct R3CompoundShapeDesc *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R3CompoundShapeView; @@ -6013,82 +11084,240 @@ typedef struct R3CompoundShapeView { * b/c = remaining endpoints/vertices. radius is also the rounded-cuboid border radius. * Mesh views count edges or triangles; heightfields are column-major. * Arrays, compound children, and sharedShape remain borrowed until build/insert returns. + * @ingroup shapes */ typedef struct R3ShapeDesc { + /** + * Discriminant selecting which description fields are read. + */ uint32_t kind; + /** + * Half extents, endpoint/vertex, or halfspace normal, selected by kind. + */ struct R3Vector a; + /** + * Second endpoint or triangle vertex, selected by kind. + */ struct R3Vector b; + /** + * Third triangle vertex. + */ struct R3Vector c; + /** + * Radius for the selected primitive or recipe. + */ R3Real radius; + /** + * Half the height of a cylinder or cone. + */ R3Real halfHeight; + /** + * Rounding radius for a rounded shape. + */ R3Real borderRadius; + /** + * Borrowed vertex positions. + */ struct R3VectorView vertices; + /** + * Borrowed triangle topology; count is triangles. + */ struct R3TriangleView triangles; + /** + * Borrowed edge topology; count is edges. + */ struct R3EdgeView edges; + /** + * TRIMESH_*, POLYLINE_*, or HEIGHTFIELD_* bitmask selected by kind. + */ uint32_t flags; + /** + * Borrowed height samples; 3D uses column-major rows * columns samples. + */ struct R3RealView heights; + /** + * Number of heightfield rows. + */ size_t rows; + /** + * Number of heightfield columns. + */ size_t columns; + /** + * Shape scale along each axis. + */ struct R3Vector scale; + /** + * Borrowed shared geometry; keep its wrapper alive through build/insert. + */ const R3SharedShape *sharedShape; + /** + * Borrowed compound children and their nested geometry views. + */ struct R3CompoundShapeView children; } R3ShapeDesc; +/** + * One compound child with a local pose and borrowed geometry description. + * @ingroup shapes + */ typedef struct R3CompoundShapeDesc { + /** + * Pose relative to the compound parent. + */ struct R3Pose pose; + /** + * Non-owning shape description; build/insert consumes its views synchronously. + */ struct R3ShapeDesc shape; } R3CompoundShapeDesc; #if defined(RAPIER_DIM2) +/** + * Angular scalar in 2D or vector in 3D, in radians for angular displacement. + * @ingroup math + */ typedef R3Real R3AngVector; #endif #if defined(RAPIER_DIM3) +/** + * Angular scalar in 2D or vector in 3D, in radians for angular displacement. + * @ingroup math + */ typedef struct R3Vector R3AngVector; #endif /** - * Explicit mass and principal inertia, matching MassProperties constructors. Zero mass/inertia means infinite. + * Explicit mass and principal inertia, matching MassProperties constructors. Zero mass/inertia + * means infinite. + * @ingroup math */ typedef struct R3MassProperties { + /** + * Center of mass in local coordinates. + */ struct R3Vector local_com; + /** + * Mass; nonnegative when supplied as input. + */ R3Real mass; + /** + * Principal angular inertia; scalar in 2D, three diagonal entries in 3D. + */ R3AngVector principal_inertia; #if defined(RAPIER_DIM3) + /** + * Orientation of principal inertia axes in local coordinates (3D). + */ struct R3Rotation principal_inertia_local_frame; #endif } R3MassProperties; +/** + * Collision membership/filter masks and their pairwise combination rule. + * @ingroup math + */ typedef struct R3InteractionGroups { + /** + * Groups this object belongs to, as a 32-bit mask. + */ uint32_t memberships; + /** + * Membership groups accepted by this object, as a 32-bit mask. + */ uint32_t filter; + /** + * 0 requires both membership/filter tests; 1 accepts either test. + */ uint32_t test_mode; } R3InteractionGroups; /** * Copyable collider construction data. Shape inputs are borrowed, never owned. + * @ingroup colliders */ typedef struct R3ColliderDesc { + /** + * Non-owning shape description; build/insert consumes its views synchronously. + */ struct R3ShapeDesc shape; + /** + * Pose relative to the parent body; world-space for an unparented collider. + */ struct R3Pose position; + /** + * R3_MASS_DENSITY, R3_MASS_TOTAL, or R3_MASS_PROPERTIES. + */ uint32_t massMode; + /** + * Nonnegative mass per unit volume. + */ R3Real density; + /** + * Mass; nonnegative when supplied as input. + */ R3Real mass; + /** + * Explicit local mass and inertia when massMode selects them. + */ struct R3MassProperties massProperties; + /** + * Nonnegative friction coefficient. + */ R3Real friction; + /** + * Nonnegative restitution coefficient. + */ R3Real restitution; + /** + * R3_COMBINE_AVERAGE, MIN, MULTIPLY, or MAX. + */ uint32_t frictionCombineRule; + /** + * R3_COMBINE_AVERAGE, MIN, MULTIPLY, or MAX. + */ uint32_t restitutionCombineRule; + /** + * 1 detects intersections without generating contact forces. + */ R3Bool isSensor; + /** + * Whether this setting/object is enabled (0 or 1). + */ R3Bool enabled; + /** + * Groups controlling collision detection. + */ struct R3InteractionGroups collisionGroups; + /** + * Groups controlling contact-force solving. + */ struct R3InteractionGroups solverGroups; + /** + * Bitmask of body-type pairs allowed to collide. + */ uint16_t activeCollisionTypes; + /** + * Hook flags enabling pair filtering/contact modification. + */ uint32_t activeHooks; + /** + * R3_COLLISION_EVENTS and/or R3_CONTACT_FORCE_EVENTS. + */ uint32_t activeEvents; + /** + * Nonnegative force threshold for force events. + */ R3Real contactForceEventThreshold; + /** + * Nonnegative extra separation distance around the collider. + */ R3Real contactSkin; + /** + * Application data; Rapier does not own pointers encoded in it. + */ struct R3UserData userData; } R3ColliderDesc; @@ -6098,63 +11327,196 @@ typedef struct R3ColliderDesc { * for topology arrays are element counts (edges, triangles, or tetrahedra). * Nonempty topology overrides the generator's topology. Zero counts retain it. * Generator inputs: a/b are rope ends or center/half-extents; cloth uses a/du/dv. + * @ingroup soft_bodies */ typedef struct R3SoftBodyDesc { + /** + * Discriminant selecting which description fields are read. + */ uint32_t kind; + /** + * Recipe origin/center or first rope endpoint; use a recipe constructor. + */ struct R3Vector a; + /** + * Recipe half extents, second rope endpoint, or tube axis; use a recipe constructor. + */ struct R3Vector b; + /** + * Cloth basis step along its first parameter axis. + */ struct R3Vector du; + /** + * Cloth basis step along its second parameter axis. + */ struct R3Vector dv; + /** + * First recipe resolution; interpretation depends on kind. + */ size_t nx; + /** + * Second recipe resolution; interpretation depends on kind. + */ size_t ny; + /** + * Third recipe resolution; interpretation depends on kind. + */ size_t nz; + /** + * Radius for the selected primitive or recipe. + */ R3Real radius; + /** + * Radius at the second end of a tapered cloth tube. + */ R3Real radiusEnd; + /** + * Translation vector. + */ struct R3Vector translation; + /** + * Optional total mass override for generated particles. + */ struct R3OptionalReal totalMass; + /** + * Volume-meshing parameters for a volumetric recipe. + */ struct R3VolumeMeshParameters meshing; + /** + * Borrowed initial particle positions. + */ struct R3VectorView positions; + /** + * Borrowed per-particle masses; when empty, particleMass is used. + */ struct R3RealView masses; + /** + * Borrowed indices of pinned particles. + */ struct R3IndexView pinned; + /** + * Borrowed edge topology; count is edges. + */ struct R3EdgeView edges; + /** + * Borrowed bending edge constraints. + */ struct R3EdgeView bendEdges; + /** + * Borrowed indices of edges that resist tension only. + */ struct R3IndexView tensionOnlyEdges; + /** + * Borrowed per-edge softness overrides. + */ struct R3SoftEdgeSoftnessView edgeSoftness; + /** + * Borrowed per-edge tear-resistance overrides. + */ struct R3SoftEdgeTearView edgeTearResistance; + /** + * Borrowed triangles in 2D or tetrahedra in 3D. + */ R3CellView cells; + /** + * Borrowed boundary edges in 2D or triangles in 3D. + */ R3SurfaceElementView surface; #if defined(RAPIER_DIM3) + /** + * Borrowed four-vertex bending constraints. + */ struct R3DihedralView dihedrals; #endif #if defined(RAPIER_DIM3) + /** + * Borrowed wire edges for a surface recipe. + */ struct R3EdgeView wire; #endif + /** + * Borrowed skin vertex positions. + */ struct R3VectorView skinVertices; + /** + * Borrowed skin topology. + */ R3SurfaceElementView skinIndices; + /** + * Soft-body material coefficients. + */ struct R3SoftBodyMaterial material; + /** + * R3_SOFT_CELL_VOLUME, R3_SOFT_CELL_COROTATIONAL, or R3_SOFT_CELL_NEO_HOOKEAN. + */ uint32_t cellModel; /** * 0 = constraints, 1 = FEM (requires a library built with FEM). */ uint32_t solver; + /** + * Default nonnegative particle mass. + */ R3Real particleMass; /** * Disabled by default: retain the radius computed by the generator. */ struct R3OptionalReal particleRadius; + /** + * Whether to preserve volume. + */ R3Bool volumePreservation; + /** + * Target volume multiplier. + */ R3Real volumeFactor; + /** + * Optional shape-matching override; disabled retains recipe defaults. + */ struct R3OptionalBool shapeMatching; + /** + * Whether self-collision is enabled. + */ R3Bool selfContacts; + /** + * Whether skin elements participate in collision detection. + */ R3Bool skinCollision; + /** + * Whether the generated collision geometry is enabled. + */ R3Bool collisionEnabled; + /** + * Collider configuration used by the recipe; shape comes from the generated geometry. + */ struct R3ColliderDesc collider; + /** + * Nonnegative linear damping coefficient. + */ R3Real linearDamping; + /** + * Multiplier applied to world gravity. + */ R3Real gravityScale; + /** + * Extra solver iterations for this body and connected bodies. + */ size_t additionalSolverIterations; + /** + * Extra PGS iterations for this body. + */ size_t additionalPgsIterations; + /** + * Whether automatic sleeping is allowed. + */ R3Bool canSleep; + /** + * Signed dominance group; larger groups dominate smaller groups. + */ int8_t dominanceGroup; + /** + * Application data; Rapier does not own pointers encoded in it. + */ struct R3UserData userData; } R3SoftBodyDesc; @@ -6162,23 +11524,43 @@ typedef struct R3SoftBodyDesc { * Copyable non-owning handle: world pointer plus entity index and generation. * The world must remain alive throughout every use. Copying does not retain it. * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + * @ingroup soft_bodies */ typedef struct R3SoftBodyHandle { /** * Borrowed owning world. Never use this handle after freeing that world. */ struct R3World *world; + /** + * Zero-based element index. + */ uint32_t index; + /** + * Slot generation used to reject stale handles. Do not modify it. + */ uint32_t generation; } R3SoftBodyHandle; /** * Non-owning deformable binding description. Direct particle indices are borrowed. + * @ingroup soft_bodies */ typedef struct R3SoftMeshBindingDesc { + /** + * Discriminant selecting which description fields are read. + */ uint32_t kind; + /** + * Borrowed particle indices used for a direct mesh binding. + */ struct R3IndexView particles; + /** + * Nonnegative positional tolerance for direct-by-position binding. + */ R3Real epsilon; + /** + * Whether self-collision is enabled. + */ R3Bool selfContacts; } R3SoftMeshBindingDesc; @@ -6186,27 +11568,54 @@ typedef struct R3SoftMeshBindingDesc { * Copyable non-owning handle: world pointer plus entity index and generation. * The world must remain alive throughout every use. Copying does not retain it. * UINT32_MAX/UINT32_MAX with a NULL world is invalid. + * @ingroup colliders */ typedef struct R3ColliderHandle { /** * Borrowed owning world. Never use this handle after freeing that world. */ struct R3World *world; + /** + * Slot index; UINT32_MAX denotes the explicit invalid handle. + */ uint32_t index; + /** + * Slot generation used to reject stale handles. Do not modify it. + */ uint32_t generation; } R3ColliderHandle; +/** + * Scene-query flags, groups, and excluded handles. Initialize with r3DefaultQueryFilter. + * @ingroup queries + */ typedef struct R3QueryFilter { + /** + * R3_QUERY_EXCLUDE_* bitmask selecting body types and sensors/solids. + */ uint32_t flags; + /** + * Whether to apply the groups filter. + */ R3Bool use_groups; + /** + * Groups to test when use_groups is 1. + */ struct R3InteractionGroups groups; + /** + * Collider to exclude; use the explicit invalid handle to exclude none. + */ struct R3ColliderHandle exclude_collider; + /** + * Body whose colliders are excluded; use the explicit invalid handle for none. + */ struct R3RigidBodyHandle exclude_rigid_body; } R3QueryFilter; /** * Called with scoped read access and a collider handle. Shared queries may nest; * world mutations are rejected until the outer query returns. Never retain the context. + * @ingroup queries */ typedef R3Bool (RAPIER_CALL *R3QueryPredicate)(void *user_data, const struct R3ReadContext *read, @@ -6214,101 +11623,315 @@ typedef R3Bool (RAPIER_CALL *R3QueryPredicate)(void *user_data, /** * Copyable query settings. They borrow callback data, never world components. + * @ingroup queries */ typedef struct R3QueryOptions { + /** + * Query selection settings. + */ struct R3QueryFilter filter; + /** + * Optional additional query filter; nonzero accepts a collider. + */ R3QueryPredicate predicate; + /** + * Application data; Rapier does not own pointers encoded in it. + */ void *userData; } R3QueryOptions; +/** + * Closest ray intersection, with a world-space normal. + * @ingroup queries + */ typedef struct R3RayHit { + /** + * World-bound collider handle. + */ struct R3ColliderHandle collider; + /** + * Ray/sweep parameter at first impact, bounded by the query options. + */ R3Real time_of_impact; + /** + * World-space contact or surface normal. + */ struct R3Vector normal; + /** + * Shape feature kind: R3_FEATURE_UNKNOWN, R3_FEATURE_VERTEX, R3_FEATURE_EDGE, or + * R3_FEATURE_FACE. + */ uint32_t feature_type; + /** + * Index within the feature kind; zero for unknown. + */ uint32_t feature_id; } R3RayHit; +/** + * Closest projected world-space point and its collider. + * @ingroup queries + */ typedef struct R3PointProjection { + /** + * World-bound collider handle. + */ struct R3ColliderHandle collider; + /** + * Projected world-space point. + */ struct R3Vector point; + /** + * Whether the original point was inside the collider. + */ R3Bool is_inside; } R3PointProjection; +/** + * Shape-cast impact geometry; collider-side data is world-space, moving-shape data is local. + * @ingroup queries + */ typedef struct R3ShapeCastHit { + /** + * World-bound collider handle. + */ struct R3ColliderHandle collider; + /** + * Ray/sweep parameter at first impact, bounded by the query options. + */ R3Real time_of_impact; + /** + * Impact witness on the collider, in world coordinates. + */ struct R3Vector witness1; + /** + * Impact witness on the moving shape, in its local coordinates. + */ struct R3Vector witness2; + /** + * Impact normal on the collider, in world coordinates. + */ struct R3Vector normal1; + /** + * Impact normal on the moving shape, in its local coordinates. + */ struct R3Vector normal2; + /** + * Native result: 0 out of iterations, 1 converged, 2 failed, 3 penetrating or within target + * distance. + */ uint32_t status; } R3ShapeCastHit; +/** + * Sweep termination settings. Initialize with r3DefaultShapeCastOptions. + * @ingroup queries + */ typedef struct R3ShapeCastOptions { + /** + * Maximum sweep parameter; movement is velocity multiplied by this time. + */ R3Real max_time_of_impact; + /** + * Nonnegative separation at which a shape cast counts as a hit. + */ R3Real target_distance; + /** + * Whether to stop at t = 0 for an initial overlap. + */ R3Bool stop_at_penetration; + /** + * Whether to compute witness points/normals for an initial overlap. + */ R3Bool compute_impact_geometry_on_penetration; } R3ShapeCastOptions; +/** + * Axis-aligned bounding box. + * @ingroup math + */ typedef struct R3Aabb { + /** + * Minimum corner in each coordinate. + */ struct R3Vector mins; + /** + * Maximum corner in each coordinate. + */ struct R3Vector maxs; } R3Aabb; +/** + * Optional ray collider/time result. A miss is found = 0 with status OK. + * @ingroup queries + */ typedef struct R3RayToi { + /** + * World-bound collider handle. + */ struct R3ColliderHandle collider; + /** + * Ray parameter t at impact: origin + direction * t. + */ R3Real toi; + /** + * Whether a result exists; other result fields are meaningful only when this is 1. + */ R3Bool found; } R3RayToi; +/** + * Optional full ray result. A miss is found = 0 with status OK. + * @ingroup queries + */ typedef struct R3OptionalRayHit { + /** + * Shape/ray impact details. + */ struct R3RayHit hit; + /** + * Whether a result exists; other result fields are meaningful only when this is 1. + */ R3Bool found; } R3OptionalRayHit; /** - * Stack-allocated rigid-body construction data. Initialize with RigidBodyDescInit. + * Stack-allocated rigid-body construction data. Initialize with r3DynamicRigidBodyDesc, + * r3FixedRigidBodyDesc, or a kinematic description constructor. * Copying this value is safe; it owns no resources and must never be freed by Rapier. + * @ingroup rigid_bodies */ typedef struct R3RigidBodyDesc { + /** + * World-space pose. + */ struct R3Pose position; + /** + * World-space linear velocity. + */ struct R3Vector linvel; + /** + * World-space angular velocity in radians per second. + */ R3AngVector angvel; + /** + * R3_DYNAMIC, R3_FIXED, R3_KINEMATIC_POSITION_BASED, or R3_KINEMATIC_VELOCITY_BASED. + */ uint32_t bodyType; + /** + * Multiplier applied to world gravity. + */ R3Real gravityScale; + /** + * Nonnegative linear damping coefficient. + */ R3Real linearDamping; + /** + * Nonnegative angular damping coefficient. + */ R3Real angularDamping; + /** + * Nonnegative mass added to attached collider contributions. + */ R3Real additionalMass; + /** + * 1 uses additionalMassProperties; 0 uses additionalMass. + */ R3Bool useAdditionalMassProperties; + /** + * Additional body-local mass and inertia when enabled. + */ struct R3MassProperties additionalMassProperties; + /** + * Locked-axis bitmask; translations precede rotations. + */ uint8_t lockedAxes; + /** + * Whether automatic sleeping is allowed. + */ R3Bool canSleep; + /** + * Whether the body starts/is asleep. + */ R3Bool sleeping; + /** + * Whether continuous collision detection is enabled. + */ R3Bool ccdEnabled; + /** + * Nonnegative prediction distance for soft CCD. + */ R3Real softCcdPrediction; + /** + * Whether to allow fast rotations without the native angular-motion clamp. + */ R3Bool allowFastRotation; + /** + * Whether this setting/object is enabled (0 or 1). + */ R3Bool enabled; + /** + * Signed dominance group; larger groups dominate smaller groups. + */ int8_t dominanceGroup; + /** + * Extra solver iterations for this body and connected bodies. + */ size_t additionalSolverIterations; + /** + * Extra PGS iterations for this body. + */ size_t additionalPgsIterations; + /** + * Whether to include gyroscopic forces (3D). + */ R3Bool gyroscopicForcesEnabled; + /** + * Application data; Rapier does not own pointers encoded in it. + */ struct R3UserData userData; } R3RigidBodyDesc; /** * Sizes of the POD types in this library build, for foreign-language layout checks. + * @ingroup math */ typedef struct R3PodLayout { + /** + * Size in bytes of the rigidBodyDesc structure; zero when unavailable. + */ size_t rigidBodyDesc; + /** + * Size in bytes of the colliderDesc structure; zero when unavailable. + */ size_t colliderDesc; + /** + * Size in bytes of the shapeDesc structure; zero when unavailable. + */ size_t shapeDesc; + /** + * Size in bytes of the jointDesc structure; zero when unavailable. + */ size_t jointDesc; + /** + * Size in bytes of the softBodyMaterial structure; zero when unavailable. + */ size_t softBodyMaterial; + /** + * Size in bytes of the integrationParameters structure; zero when unavailable. + */ size_t integrationParameters; + /** + * Size in bytes of the softBodyDesc structure; zero when unavailable. + */ size_t softBodyDesc; + /** + * Size in bytes of the softMeshBindingDesc structure; zero when unavailable. + */ size_t softMeshBindingDesc; + /** + * Size in bytes of the queryOptions structure; zero when unavailable. + */ size_t queryOptions; /** * Zero unless 3D f32 robotics is enabled. @@ -6321,72 +11944,222 @@ typedef struct R3PodLayout { } R3PodLayout; /** - * CharacterLength counterpart: relative=1 scales with character height, relative=0 uses world units. + * CharacterLength counterpart: relative=1 scales with character height, relative=0 uses world + * units. + * @ingroup controllers */ typedef struct R3CharacterLength { + /** + * Value used when enabled is 1. + */ R3Real value; + /** + * 1 scales value by the character shape size; 0 uses an absolute length. + */ R3Bool relative; } R3CharacterLength; +/** + * Allowed character motion and ground-contact state. + * @ingroup controllers + */ typedef struct R3CharacterMovement { + /** + * Allowed world-space displacement; not applied automatically. + */ struct R3Vector translation; + /** + * Whether the character touches the ground after movement. + */ R3Bool grounded; + /** + * Whether motion includes sliding down a non-climbable slope. + */ R3Bool is_sliding_down_slope; } R3CharacterMovement; +/** + * Collision recorded during character movement. + * @ingroup controllers + */ typedef struct R3CharacterCollision { + /** + * World-bound collider handle. + */ struct R3ColliderHandle collider; + /** + * World-space character pose at collision. + */ struct R3Pose character_pos; + /** + * World-space translation already applied before collision. + */ struct R3Vector translation_applied; + /** + * World-space translation remaining at collision. + */ struct R3Vector translation_remaining; + /** + * Shape/ray impact details. + */ struct R3ShapeCastHit hit; } R3CharacterCollision; +/** + * Per-axis proportional, integral, and derivative controller gains. + * @ingroup controllers + */ typedef struct R3PidGains { + /** + * Linear proportional gain per axis. + */ struct R3Vector lin_kp; + /** + * Linear integral gain per axis. + */ struct R3Vector lin_ki; + /** + * Linear derivative gain per axis. + */ struct R3Vector lin_kd; + /** + * Angular proportional gain per axis. + */ R3AngVector ang_kp; + /** + * Angular integral gain per axis. + */ R3AngVector ang_ki; + /** + * Angular derivative gain per axis. + */ R3AngVector ang_kd; } R3PidGains; +/** + * Linear and angular velocity correction computed by a controller. + * @ingroup controllers + */ typedef struct R3VelocityCorrection { + /** + * World-space linear velocity correction. + */ struct R3Vector linear; + /** + * World-space angular velocity correction, in radians per second. + */ R3AngVector angularVelocity; } R3VelocityCorrection; +/** + * Copy of character sliding, slope, and snapping settings. + * @ingroup controllers + */ typedef struct R3CharacterControllerSettings { + /** + * Whether obstacle sliding is enabled. + */ R3Bool slide; + /** + * Maximum climbable slope angle in radians. + */ R3Real max_slope_climb_angle; + /** + * Minimum slope angle for sliding, in radians. + */ R3Real min_slope_slide_angle; + /** + * Whether downward ground snapping is enabled. + */ R3Bool snap_to_ground; + /** + * Maximum downward snapping distance. + */ struct R3CharacterLength snap_distance; } R3CharacterControllerSettings; #if defined(RAPIER_DIM3) +/** + * Wheel suspension/friction parameters. Initialize with r3DefaultWheelTuning. + * @ingroup controllers + */ typedef struct R3WheelTuning { + /** + * Nonnegative suspension spring stiffness. + */ R3Real suspension_stiffness; + /** + * Nonnegative damping coefficient during suspension compression. + */ R3Real suspension_compression; + /** + * Nonnegative suspension relaxation damping. + */ R3Real suspension_damping; + /** + * Maximum suspension travel in length units. + */ R3Real max_suspension_travel; + /** + * Maximum tire friction/slip coefficient. + */ R3Real friction_slip; + /** + * Maximum force exerted by suspension. + */ R3Real max_suspension_force; + /** + * Sideways tire friction stiffness. + */ R3Real side_friction_stiffness; } R3WheelTuning; #endif #if defined(RAPIER_DIM3) +/** + * Copy of the current wheel pose, suspension, and contact state. + * @ingroup controllers + */ typedef struct R3WheelState { + /** + * World-space wheel center. + */ struct R3Vector center; + /** + * World-space suspension direction. + */ struct R3Vector suspension; + /** + * World-space axle direction. + */ struct R3Vector axle; + /** + * Wheel rotation angle in radians. + */ R3Real rotation; + /** + * Current suspension force. + */ R3Real suspension_force; + /** + * Current suspension length. + */ R3Real suspension_length; + /** + * Whether the wheel has ground contact. + */ R3Bool is_in_contact; + /** + * Ground collider handle, invalid when there is no contact. + */ struct R3ColliderHandle ground_object; + /** + * World-space ground contact point. + */ struct R3Vector contact_point; + /** + * World-space ground contact normal. + */ struct R3Vector contact_normal; } R3WheelState; #endif @@ -6396,59 +12169,137 @@ typedef struct R3WheelState { * The diagnostic is borrowed for the duration of the callback. The callback * must return normally or terminate the process: never throw or longjmp across * the Rust/C boundary. Nested failing calls do not invoke the handler recursively. + * @ingroup errors */ typedef void (RAPIER_CALL *R3ErrorCallback)(R3Status, const char*, void*); /** * An optional thread-local error handler. A null callback disables reporting. * Keep the callback and user_data alive until the handler is replaced. + * @ingroup errors */ typedef struct R3ErrorHandler { + /** + * Optional error callback; NULL disables notifications. + */ R3ErrorCallback callback; + /** + * Application data; Rapier does not own pointers encoded in it. + */ void *user_data; } R3ErrorHandler; +/** + * World-bound handles of the two connected bodies. + * @ingroup joints + */ typedef struct R3JointBodies { + /** + * First connected body. + */ struct R3RigidBodyHandle body1; + /** + * Second connected body. + */ struct R3RigidBodyHandle body2; } R3JointBodies; +/** + * Damped least-squares inverse-kinematics parameters. + * @ingroup math + */ typedef struct R3InverseKinematicsOptions { + /** + * Nonnegative motor damping. + */ R3Real damping; + /** + * Maximum inverse-kinematics iterations. + */ size_t max_iters; + /** + * Controlled-axis bitmask; translations precede rotations. + */ uint8_t constrained_axes; + /** + * Linear convergence tolerance. + */ R3Real epsilon_linear; + /** + * Angular convergence tolerance in radians. + */ R3Real epsilon_angular; } R3InverseKinematicsOptions; /** * Optional per-link filter, called synchronously. Must not reenter or retain physics objects. + * @ingroup joints */ typedef R3Bool (RAPIER_CALL *R3IkJointCanMove)(void*, struct R3RigidBodyHandle); /** * Collision start/stop flags match Rapier CollisionEventFlags. + * @ingroup events */ typedef struct R3CollisionEvent { + /** + * First collider in the pair. + */ struct R3ColliderHandle collider1; + /** + * Second collider in the pair. + */ struct R3ColliderHandle collider2; + /** + * 1 for a starting event, 0 for a stopping event. + */ R3Bool started; + /** + * Event flags: bit 0 sensor pair, bit 1 removed collider. + */ uint32_t flags; } R3CollisionEvent; +/** + * Contact-force event, enabled by flags and the collider force threshold. + * @ingroup events + */ typedef struct R3ContactForceEvent { + /** + * First collider in the pair. + */ struct R3ColliderHandle collider1; + /** + * Second collider in the pair. + */ struct R3ColliderHandle collider2; + /** + * Sum of world-space contact forces. + */ struct R3Vector total_force; + /** + * Sum of contact force magnitudes. + */ R3Real total_force_magnitude; + /** + * World-space direction of the strongest contact force. + */ struct R3Vector max_force_direction; + /** + * Magnitude of the strongest contact force. + */ R3Real max_force_magnitude; + /** + * 1 for a starting event, 0 for a stopping event. + */ R3Bool started; } R3ContactForceEvent; /** - * Pair callback: -1 rejects a contact pair; 0 detects contacts without impulses; 1 computes impulses. + * Pair callback: -1 rejects a contact pair; 0 detects contacts without impulses; 1 computes + * impulses. * For sensor intersections only, zero rejects and any positive value accepts. + * @ingroup math */ typedef int32_t (RAPIER_CALL *R3PairFilter)(void *user_data, const struct R3ReadContext *read, @@ -6459,21 +12310,45 @@ typedef int32_t (RAPIER_CALL *R3PairFilter)(void *user_data, /** * Mutable per-manifold properties. Set enabled=0 to discard all its solver contacts. + * @ingroup events */ typedef struct R3ContactModification { + /** + * World-space contact or surface normal. + */ struct R3Vector normal; + /** + * Nonnegative friction coefficient. + */ R3Real friction; + /** + * Nonnegative restitution coefficient. + */ R3Real restitution; + /** + * Application data; Rapier does not own pointers encoded in it. + */ uint32_t user_data; + /** + * Whether this setting/object is enabled (0 or 1). + */ R3Bool enabled; } R3ContactModification; +/** + * Modify aggregate contact properties for one pair; contact is mutable only during this callback. + * @ingroup callbacks + */ typedef void (RAPIER_CALL *R3ModifyContacts)(void *user_data, const struct R3ReadContext *read, struct R3ColliderHandle collider1, struct R3ColliderHandle collider2, struct R3ContactModification *contact); +/** + * Modify individual solver contacts through a borrowed context, valid only during the callback. + * @ingroup callbacks + */ typedef void (RAPIER_CALL *R3ModifyContactContext)(void *user_data, const struct R3ReadContext *read, struct R3ColliderHandle collider1, @@ -6484,12 +12359,26 @@ typedef void (RAPIER_CALL *R3ModifyContactContext)(void *user_data, * Callbacks must not unwind or retain arguments. Use their ReadContext to inspect bodies and * colliders; ordinary access to the stepping world returns WORLD_BUSY. Mutations must be * performed after stepping. With parallel builds - * callbacks and their user_data must be safe for concurrent invocation. NULL callbacks use defaults. + * callbacks and their user_data must be safe for concurrent invocation. NULL callbacks use + * defaults. + * @ingroup callbacks */ typedef struct R3PhysicsHooks { + /** + * Application data; Rapier does not own pointers encoded in it. + */ void *user_data; + /** + * Optional contact-pair filter, called only for colliders enabling the hook. + */ R3PairFilter filter_contact_pair; + /** + * Optional sensor-pair filter, called only for colliders enabling the hook. + */ R3PairFilter filter_intersection_pair; + /** + * Optional legacy aggregate contact-edit callback. + */ R3ModifyContacts modify_solver_contacts; /** * Runs after the legacy property callback. Context accessors may be called here. @@ -6499,52 +12388,144 @@ typedef struct R3PhysicsHooks { /** * Borrowed bytes. Valid while the source Bytes object remains alive; never free data. + * @ingroup math */ typedef struct R3ByteView { + /** + * Borrowed pointer to contiguous elements; NULL is allowed when count is zero. + */ const uint8_t *data; + /** + * Number of elements, not bytes unless the element type is a byte. + */ size_t count; } R3ByteView; +/** + * World-space line segment produced by physics debug rendering. + * @ingroup events + */ typedef struct R3DebugLine { + /** + * World-space start point. + */ struct R3Vector a; + /** + * World-space end point. + */ struct R3Vector b; + /** + * RGBA color, four floats. + */ float color[4]; } R3DebugLine; +/** + * Particle remapping after tearing. + * @ingroup math + */ typedef struct R3ParticleDestination { + /** + * World-bound rigid/soft-body handle, as selected by the field type. + */ struct R3SoftBodyHandle body; + /** + * Zero-based element index. + */ uint32_t index; } R3ParticleDestination; +/** + * Cluster and proxy remapping after a soft-body split. + * @ingroup soft_bodies + */ typedef struct R3SoftClusterSplit { + /** + * Original cluster index before splitting. + */ uint32_t source_cluster; + /** + * Resulting soft-body handle. + */ struct R3SoftBodyHandle soft_body; + /** + * Cluster index within its soft body. + */ uint32_t cluster; + /** + * Cluster rigid-proxy body handle. + */ struct R3RigidBodyHandle proxy; + /** + * Whether this split retains the original proxy. + */ R3Bool keeps_proxy; } R3SoftClusterSplit; +/** + * Impulse-joint movement between rigid proxies after tearing. + * @ingroup soft_bodies + */ typedef struct R3SoftJointMove { + /** + * Impulse joint that moved between proxies. + */ struct R3ImpulseJointHandle joint; + /** + * Original rigid-proxy body. + */ struct R3RigidBodyHandle from; + /** + * Destination rigid-proxy body. + */ struct R3RigidBodyHandle to; } R3SoftJointMove; +/** + * Optional particle remapping; check found before reading the destination. + * @ingroup math + */ typedef struct R3OptionalParticleDestination { + /** + * World-bound rigid/soft-body handle, as selected by the field type. + */ struct R3SoftBodyHandle body; + /** + * Zero-based element index. + */ uint32_t index; + /** + * Whether a result exists; other result fields are meaningful only when this is 1. + */ R3Bool found; } R3OptionalParticleDestination; +/** + * Runtime ABI identity and fundamental type sizes. + * @ingroup errors + */ typedef struct R3BuildInfo { + /** + * Binary ABI revision; compare with the header ABI_VERSION. + */ uint32_t abi_version; + /** + * Spatial dimension, 2 or 3. + */ uint32_t dimension; + /** + * Size of Real in bytes, 4 or 8. + */ uint32_t real_size; + /** + * Pointer size in bytes. + */ uint32_t pointer_size; } R3BuildInfo; /** * Features available through the loaded C library, independent of consumer defines. + * @ingroup errors */ typedef struct R3BuildFeatures { /** @@ -6561,28 +12542,88 @@ typedef struct R3BuildFeatures { R3Bool parallel; } R3BuildFeatures; +/** + * Current narrow-phase pair and accumulated solver impulse summary. + * @ingroup events + */ typedef struct R3ContactPair { + /** + * First collider in the pair. + */ struct R3ColliderHandle collider1; + /** + * Second collider in the pair. + */ struct R3ColliderHandle collider2; + /** + * Whether the pair has an active solver contact. + */ R3Bool has_any_active_contact; + /** + * Sum of world-space contact impulse vectors. + */ struct R3Vector total_impulse; + /** + * Sum of contact impulse magnitudes. + */ R3Real total_impulse_magnitude; + /** + * Largest contact impulse magnitude. + */ R3Real max_impulse; + /** + * World-space direction of the largest contact impulse. + */ struct R3Vector max_impulse_direction; } R3ContactPair; +/** + * Sensor intersection state for a collider pair. + * @ingroup math + */ typedef struct R3IntersectionPair { + /** + * First collider in the pair. + */ struct R3ColliderHandle collider1; + /** + * Second collider in the pair. + */ struct R3ColliderHandle collider2; + /** + * Whether the two sensor/collider shapes intersect. + */ R3Bool intersecting; } R3IntersectionPair; +/** + * One manifold contact and its normal solver impulse. + * @ingroup events + */ typedef struct R3ContactPoint { + /** + * Index of the contact manifold within its pair. + */ size_t manifold_index; + /** + * Contact point in collider 1 local coordinates. + */ struct R3Vector local_p1; + /** + * Contact point in collider 2 local coordinates. + */ struct R3Vector local_p2; + /** + * World-space contact or surface normal. + */ struct R3Vector normal; + /** + * Signed separation; negative means penetration. + */ R3Real distance; + /** + * Normal impulse applied at this contact. + */ R3Real impulse; } R3ContactPoint; @@ -6590,17 +12631,48 @@ typedef struct R3ContactPoint { /** * Loader configuration. Initialize with DefaultUrdfLoaderOptions; no destructor. * Blueprint array views and shared shapes are borrowed through the load call. + * @ingroup robotics */ typedef struct R3UrdfLoaderOptions { + /** + * Whether to build colliders from collision geometry. + */ R3Bool createCollidersFromCollisionShapes; + /** + * Whether to also build colliders from visual geometry. + */ R3Bool createCollidersFromVisualShapes; + /** + * Whether to use imported mass/inertia properties. + */ R3Bool applyImportedMassProps; + /** + * Whether bodies connected by imported joints may collide. + */ R3Bool enableJointCollisions; + /** + * Whether imported root bodies are fixed. + */ R3Bool makeRootsFixed; + /** + * Whether to merge empty fixed URDF links. + */ R3Bool squeezeEmptyFixedLinks; + /** + * Transform applied to the imported model. + */ struct R3Pose shift; + /** + * Shape scale along each axis. + */ R3Real scale; + /** + * Default collider description; nested geometry resources are borrowed through loading. + */ struct R3ColliderDesc colliderBlueprint; + /** + * Default rigid-body description used by the importer. + */ struct R3RigidBodyDesc rigidBodyBlueprint; } R3UrdfLoaderOptions; #endif @@ -6609,18 +12681,52 @@ typedef struct R3UrdfLoaderOptions { /** * Loader configuration. Initialize with DefaultMjcfLoaderOptions; no destructor. * Blueprint array views and shared shapes are borrowed through the load call. + * @ingroup robotics */ typedef struct R3MjcfLoaderOptions { + /** + * Whether to build colliders from collision geometry. + */ R3Bool createCollidersFromCollisionShapes; + /** + * Whether to also build colliders from visual geometry. + */ R3Bool createCollidersFromVisualShapes; + /** + * Whether to use imported mass/inertia properties. + */ R3Bool applyImportedMassProps; + /** + * Whether bodies connected by imported joints may collide. + */ R3Bool enableJointCollisions; + /** + * Whether imported root bodies are fixed. + */ R3Bool makeRootsFixed; + /** + * Whether to omit MJCF plane geometry. + */ R3Bool skipPlaneGeoms; + /** + * Whether imported joint motors are disabled. + */ R3Bool disableJointMotors; + /** + * Transform applied to the imported model. + */ struct R3Pose shift; + /** + * Shape scale along each axis. + */ R3Real scale; + /** + * Default collider description; nested geometry resources are borrowed through loading. + */ struct R3ColliderDesc colliderBlueprint; + /** + * Default rigid-body description used by the importer. + */ struct R3RigidBodyDesc rigidBodyBlueprint; } R3MjcfLoaderOptions; #endif @@ -6628,104 +12734,252 @@ typedef struct R3MjcfLoaderOptions { #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * A borrowed visual declaration, valid until its robot is freed or its body storage changes. + * @ingroup robotics */ typedef struct R3MjcfVisualMesh R3MjcfVisualMesh; #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Imported physically based visual material; no texture ownership. + * @ingroup robotics + */ typedef struct R3RenderMaterial { + /** + * Material metallic factor. + */ float metallic; + /** + * Material roughness factor. + */ float roughness; + /** + * Material reflectance factor. + */ float reflectance; + /** + * RGB emissive color. + */ float emissive[3]; } R3RenderMaterial; #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copied metadata for a borrowed MJCF visual mesh. + * @ingroup robotics + */ typedef struct R3MjcfVisualMeshInfo { + /** + * Visual pose relative to its source body. + */ struct R3Pose local_pose; + /** + * RGBA visual color. + */ float rgba[4]; + /** + * Copied render material; meaningful when has_material is 1. + */ struct R3RenderMaterial material; + /** + * Whether rgba contains an authored color. + */ R3Bool has_color; + /** + * Whether material contains authored material data. + */ R3Bool has_material; + /** + * Whether the visual geometry is a triangle mesh. + */ R3Bool is_trimesh; } R3MjcfVisualMeshInfo; #endif /** * A copied state snapshot, with no pointers or ownership obligations. + * @ingroup rigid_bodies */ typedef struct R3RigidBodyState { + /** + * World-space pose. + */ struct R3Pose position; + /** + * World-space linear velocity. + */ struct R3Vector linvel; + /** + * World-space angular velocity in radians per second. + */ R3AngVector angvel; + /** + * Whether the body starts/is asleep. + */ R3Bool sleeping; + /** + * Whether this setting/object is enabled (0 or 1). + */ R3Bool enabled; + /** + * Application data; Rapier does not own pointers encoded in it. + */ struct R3UserData userData; } R3RigidBodyState; /** * Stable identity of a live mesh within one soft body; matches Rapier's SoftMeshId. + * @ingroup soft_bodies */ typedef struct R3SoftMeshId { + /** + * Cluster index within its soft body. + */ uint32_t cluster; + /** + * Mesh index within the cluster. + */ uint32_t mesh; } R3SoftMeshId; /** * Mesh identity and rendering metadata. A render-only mesh has an invalid collider handle. + * @ingroup soft_bodies */ typedef struct R3SoftMeshInfo { + /** + * Cluster/mesh pair identifying this collision mesh. + */ struct R3SoftMeshId id; + /** + * World-bound collider handle. + */ struct R3ColliderHandle collider; + /** + * Indices per mesh element: 2 for an edge or 3 for a triangle. + */ size_t arity; + /** + * Whether vertex positions are obtained by skinning. + */ R3Bool is_skinned; + /** + * Whether collision detection is enabled for this mesh. + */ R3Bool collision_enabled; } R3SoftMeshInfo; #if defined(RAPIER_F32) /** * Voxel coordinates have DIM signed integer components. + * @ingroup math */ typedef int32_t R3VoxelCoord; #endif #if defined(RAPIER_F64) +/** + * Signed voxel coordinate integer. + * @ingroup math + */ typedef int64_t R3VoxelCoord; #endif +/** + * Integer coordinates of a voxel cell. + * @ingroup math + */ typedef struct R3VoxelKey { + /** + * X component. + */ R3VoxelCoord x; + /** + * Y component. + */ R3VoxelCoord y; #if defined(RAPIER_DIM3) + /** + * Z component. + */ R3VoxelCoord z; #endif } R3VoxelKey; +/** + * Optional voxel lookup result; check found before reading voxel data. + * @ingroup queries + */ typedef struct R3VoxelQuery { + /** + * Integer voxel coordinates. + */ struct R3VoxelKey key; + /** + * World-space wheel center. + */ struct R3Vector center; + /** + * Voxel dimensions along each axis. + */ struct R3Vector size; + /** + * Whether a result exists; other result fields are meaningful only when this is 1. + */ R3Bool found; } R3VoxelQuery; +/** + * @ingroup errors + * Operation succeeded. + */ #define R3_OK 0 +/** + * @ingroup errors + * A required pointer was NULL. + */ #define R3_NULL_POINTER 1 +/** + * @ingroup errors + * An argument failed validation. + */ #define R3_INVALID_ARGUMENT 2 +/** + * @ingroup errors + * The entity handle is stale, invalid, or belongs to another world. + */ #define R3_INVALID_HANDLE 3 +/** + * @ingroup errors + * Output capacity is insufficient; the returned count is the required capacity. + */ #define R3_BUFFER_TOO_SMALL 4 +/** + * @ingroup errors + * This build or object does not support the operation. + */ #define R3_UNSUPPORTED 5 +/** + * @ingroup errors + * Rust panicked; discard objects mutated by the call. + */ #define R3_PANIC 6 +/** + * @ingroup errors + * No matching query result or object was found. + */ #define R3_NOT_FOUND 7 /** + * @ingroup errors * Conflicting or reentrant access to simulation state. No mutation was performed. */ #define R3_WORLD_BUSY 8 @@ -6734,94 +12988,182 @@ typedef struct R3VoxelQuery { extern "C" { #endif // __cplusplus +/** + * Return native default soft body material. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R3SoftBodyMaterial r3DefaultSoftBodyMaterial(void); +/** + * Return native default soft recovery settings. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R3SoftRecoverySettings r3DefaultSoftRecoverySettings(void); #if defined(RAPIER_FEM) +/** + * Return native default soft fem parameters. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R3SoftFemParameters r3DefaultSoftFemParameters(void); #endif +/** + * Return native default soft bodies settings. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R3SoftBodiesSettings r3DefaultSoftBodiesSettings(void); +/** + * Return native default integration parameters. This POD value owns no resources. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R3IntegrationParameters r3DefaultIntegrationParameters(void); +/** + * Return a copy of all world integration settings. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R3IntegrationParameters r3IntegrationParameters(const struct R3World *world); /** * Copies validated values; does not expose a writable alias to Rust memory. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R3Status r3SetIntegrationParameters(struct R3World *world, const struct R3IntegrationParameters *data); +/** + * Return native default joint desc. This POD value owns no resources. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R3JointDesc r3DefaultJointDesc(void); +/** + * Return a fixed joint description with native defaults; no allocation. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R3JointDesc r3FixedJointDesc(void); #if defined(RAPIER_DIM2) +/** + * Return a revolute joint description with native defaults; no allocation. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R3JointDesc r3RevoluteJointDesc(void); #endif #if defined(RAPIER_DIM3) /** * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + * @ingroup joints */ RAPIER_API RAPIER_CALL struct R3JointDesc r3RevoluteJointDesc(struct R3Vector axis_vector); #endif /** * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + * @ingroup joints */ RAPIER_API RAPIER_CALL struct R3JointDesc r3PrismaticJointDesc(struct R3Vector axis_vector); +/** + * Return a rope joint description with native defaults; no allocation. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R3JointDesc r3RopeJointDesc(R3Real length); +/** + * Return a spring joint description with native defaults; no allocation. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R3JointDesc r3SpringJointDesc(R3Real length, R3Real stiffness, R3Real damping); #if defined(RAPIER_DIM3) +/** + * Return a spherical joint description with native defaults; no allocation. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R3JointDesc r3SphericalJointDesc(void); #endif #if defined(RAPIER_DIM2) /** * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. + * @ingroup joints */ RAPIER_API RAPIER_CALL struct R3JointDesc r3PinSlotJointDesc(struct R3Vector axis_vector); #endif +/** + * Create an impulse joint connecting two bodies in the same world. The world owns the joint; + * wake_up wakes the connected bodies. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R3ImpulseJointHandle r3InsertImpulseJoint(struct R3RigidBodyHandle body1, struct R3RigidBodyHandle body2, const struct R3JointDesc *joint); +/** + * Create an articulation joint between bodies in the same world. Returns an invalid handle on + * failure; check r3LastStatus. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R3MultibodyJointHandle r3InsertMultibodyJoint(struct R3RigidBodyHandle body1, struct R3RigidBodyHandle body2, const struct R3JointDesc *joint); +/** + * Return native default soft body desc. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R3SoftBodyDesc r3DefaultSoftBodyDesc(void); /** * Consumes no caller-owned resources. All borrowed arrays may be released on return. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyHandle r3InsertSoftBody(struct R3World *world, const struct R3SoftBodyDesc *desc); +/** + * Return native default soft mesh binding desc. This POD value owns no resources. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R3SoftMeshBindingDesc r3DefaultSoftMeshBindingDesc(void); +/** + * Create a deformable collider bound to a soft-body cluster. The world owns the collider; binding + * arrays are borrowed only during insertion. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL struct R3ColliderHandle r3InsertDeformableCollider(const struct R3ColliderDesc *collider, const struct R3SoftMeshBindingDesc *binding, struct R3RigidBodyHandle parent); +/** + * Return native default query options. This POD value owns no resources. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R3QueryOptions r3DefaultQueryOptions(void); +/** + * Return the closest ray hit, or report R3_NOT_FOUND on a miss. The ray is origin + direction * t + * for 0 <= t <= max_toi; direction need not be normalized. solid treats an interior origin as a + * hit at t = 0. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R3RayHit r3CastRay(const struct R3World *world, const struct R3QueryOptions *query_options, @@ -6830,6 +13172,13 @@ struct R3RayHit r3CastRay(const struct R3World *world, R3Real max_toi, R3Bool solid); +/** + * Return the closest surface projection within max_distance, or report R3_NOT_FOUND. With solid = + * 1, an interior point projects to itself. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R3PointProjection r3ProjectPoint(const struct R3World *world, const struct R3QueryOptions *query_options, @@ -6837,6 +13186,13 @@ struct R3PointProjection r3ProjectPoint(const struct R3World *world, R3Real max_distance, R3Bool solid); +/** + * Sweep shape from pose along velocity and return the first hit; report R3_NOT_FOUND on a miss. + * Time is bounded by options.max_time_of_impact. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL struct R3ShapeCastHit r3CastShape(const struct R3World *world, const struct R3QueryOptions *query_options, @@ -6845,6 +13201,13 @@ struct R3ShapeCastHit r3CastShape(const struct R3World *world, const R3SharedShape *shape, struct R3ShapeCastOptions options); +/** + * Copy handles of colliders containing the world-space point. + * @see @ref output_buffers + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL size_t r3IntersectPoint(const struct R3World *world, const struct R3QueryOptions *query_options, @@ -6852,6 +13215,14 @@ size_t r3IntersectPoint(const struct R3World *world, struct R3ColliderHandle *buffer, size_t capacity); +/** + * Copy handles of colliders intersecting the shape at its world-space pose. The shape is borrowed + * for this call. + * @see @ref output_buffers + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL size_t r3IntersectShape(const struct R3World *world, const struct R3QueryOptions *query_options, @@ -6860,6 +13231,14 @@ size_t r3IntersectShape(const struct R3World *world, struct R3ColliderHandle *buffer, size_t capacity); +/** + * Copy broad-phase candidates whose bounding boxes overlap the world-space AABB. Results may + * include false positives. + * @see @ref output_buffers + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL size_t r3IntersectAabbConservative(const struct R3World *world, const struct R3QueryOptions *query_options, @@ -6867,6 +13246,13 @@ size_t r3IntersectAabbConservative(const struct R3World *world, struct R3ColliderHandle *buffer, size_t capacity); +/** + * Return the closest ray collider and time, with found = 0 on a miss (R3_OK). The ray is origin + + * direction * t; max_toi bounds t. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R3RayToi r3CastRayToi(const struct R3World *world, const struct R3QueryOptions *query_options, @@ -6875,6 +13261,13 @@ struct R3RayToi r3CastRayToi(const struct R3World *world, R3Real max_toi, R3Bool solid); +/** + * Return the closest ray hit with found = 0 on a miss (R3_OK). The ray is origin + direction * t; + * solid treats an interior origin as a hit at t = 0. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R3OptionalRayHit r3TryCastRay(const struct R3World *world, const struct R3QueryOptions *query_options, @@ -6883,31 +13276,68 @@ struct R3OptionalRayHit r3TryCastRay(const struct R3World *world, R3Real max_toi, R3Bool solid); +/** + * Return a dynamic rigid-body description with native defaults; no allocation. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3DynamicRigidBodyDesc(void); +/** + * Return a fixed rigid-body description with native defaults; no allocation. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3FixedRigidBodyDesc(void); +/** + * Return a kinematic position based rigid-body description with native defaults; no allocation. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3KinematicPositionBasedRigidBodyDesc(void); +/** + * Return a kinematic velocity based rigid-body description with native defaults; no allocation. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3KinematicVelocityBasedRigidBodyDesc(void); +/** + * Return native default shape desc. This POD value owns no resources. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL struct R3ShapeDesc r3DefaultShapeDesc(void); +/** + * Build an owned shared shape from a description; release it with r3FreeSharedShape. Borrowed + * inputs may be released after this call. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3ShapeDesc_Build(const struct R3ShapeDesc *desc); +/** + * Return native default collider desc. This POD value owns no resources. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3DefaultColliderDesc(void); /** + * Return a ball description with the supplied radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3BallColliderDesc(R3Real radius); /** + * Return an axis-aligned box description with the supplied half-extents. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3CuboidColliderDesc(struct R3Vector half_extents); +/** + * Create a body from the description and return its world-bound handle. The world owns the body. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL struct R3RigidBodyHandle r3InsertRigidBody(struct R3World *world, const struct R3RigidBodyDesc *desc); @@ -6916,6 +13346,7 @@ struct R3RigidBodyHandle r3InsertRigidBody(struct R3World *world, * Insert a collider attached to a rigid body, using the world stored in its handle. * The parent handle is copied by value. The description is borrowed through this call. * Invalid or removed parents fail without inserting a collider. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderHandle r3InsertCollider(struct R3RigidBodyHandle parent, @@ -6924,36 +13355,71 @@ struct R3ColliderHandle r3InsertCollider(struct R3RigidBodyHandle parent, /** * Insert a collider without a rigid-body parent. The world owns the collider. * The description is borrowed through this call. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderHandle r3InsertColliderWithoutParent(struct R3World *world, const struct R3ColliderDesc *desc); +/** + * Return POD structure sizes for checking foreign-language layouts against this library. + * @ingroup errors + */ RAPIER_API RAPIER_CALL struct R3PodLayout r3PodLayout(void); +/** + * Allocate a character controller with native defaults; release it with + * r3FreeKinematicCharacterController. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R3KinematicCharacterController *r3NewKinematicCharacterController(void); +/** + * Release an owned kinematic character controller. NULL is allowed. Do not pass borrowed pointers + * or free the object twice. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3FreeKinematicCharacterController(struct R3KinematicCharacterController *controller); +/** + * Set the up direction; it must be finite and nonzero and is normalized on input. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3KinematicCharacterController_SetUp(struct R3KinematicCharacterController *controller, struct R3Vector up); +/** + * Set the collision separation margin; use a positive absolute or relative character length. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3KinematicCharacterController_SetOffset(struct R3KinematicCharacterController *controller, struct R3CharacterLength offset); +/** + * Enable or disable sliding along obstacles. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3KinematicCharacterController_SetSlide(struct R3KinematicCharacterController *controller, R3Bool enabled); +/** + * Set the maximum climb angle and minimum slide angle, in radians. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3KinematicCharacterController_SetSlopes(struct R3KinematicCharacterController *controller, R3Real max_climb_angle, R3Real min_slide_angle); +/** + * Configure automatic stepping over obstacles. enabled = 0 disables it. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3KinematicCharacterController_SetAutostep(struct R3KinematicCharacterController *controller, R3Bool enabled, @@ -6961,13 +13427,21 @@ R3Status r3KinematicCharacterController_SetAutostep(struct R3KinematicCharacterC struct R3CharacterLength min_width, R3Bool include_dynamic_bodies); +/** + * Configure downward ground snapping. enabled = 0 disables it. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3KinematicCharacterController_SetSnapToGround(struct R3KinematicCharacterController *controller, R3Bool enabled, struct R3CharacterLength distance); /** - * Computes movement without moving any collider. Use the returned translation to set the character target. + * Computes movement without moving any collider. Use the returned translation to set the character + * target. + * NULL query options use the default filter. Query state reflects the latest Step or + * DetectCollisions call. + * @ingroup controllers */ RAPIER_API RAPIER_CALL struct R3CharacterMovement r3KinematicCharacterController_MoveShape(const struct R3World *world, @@ -6978,13 +13452,20 @@ struct R3CharacterMovement r3KinematicCharacterController_MoveShape(const struct struct R3Pose pose, struct R3Vector desired_translation); +/** + * Copy collisions recorded by the most recent MoveShape call. + * @see @ref output_buffers + * @ingroup controllers + */ RAPIER_API RAPIER_CALL size_t r3KinematicCharacterController_Collisions(const struct R3KinematicCharacterController *controller, struct R3CharacterCollision *buffer, size_t capacity); /** - * Applies impulses for the most recent move_shape collisions. Use the same world, shape, dt and filter. + * Applies impulses for the most recent move_shape collisions. Use the same world, shape, dt and + * filter. + * @ingroup controllers */ RAPIER_API RAPIER_CALL R3Status r3KinematicCharacterController_SolveCharacterCollisionImpulses(const struct R3KinematicCharacterController *controller, @@ -6993,19 +13474,38 @@ R3Status r3KinematicCharacterController_SolveCharacterCollisionImpulses(const st R3Real mass, const struct R3QueryFilter *filter); +/** + * Allocate a PID controller with supplied gains and controlled axes. Release with + * r3FreePidController. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R3PidController *r3NewPidController(void); +/** + * Release an owned pid controller. NULL is allowed. Do not pass borrowed pointers or free the + * object twice. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3FreePidController(struct R3PidController *controller); +/** + * Return a copy of the proportional, integral, and derivative gains. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R3PidGains r3PidController_Gains(const struct R3PidController *controller); +/** + * Replace the proportional, integral, and derivative gains. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3PidController_SetGains(struct R3PidController *controller, struct R3PidGains gains); /** * AxesMask bits match Rapier: linear X/Y/Z are 1/2/4, angular X/Y/Z are 8/16/32. + * @ingroup controllers */ RAPIER_API RAPIER_CALL R3Status r3PidController_SetAxes(struct R3PidController *controller, @@ -7013,6 +13513,7 @@ R3Status r3PidController_SetAxes(struct R3PidController *controller, /** * Compute a velocity correction, preserving the body's state and updating PID integrals. + * @ingroup controllers */ RAPIER_API RAPIER_CALL struct R3VelocityCorrection r3PidController_RigidBodyCorrection(struct R3PidController *controller, @@ -7022,24 +13523,47 @@ struct R3VelocityCorrection r3PidController_RigidBodyCorrection(struct R3PidCont struct R3Vector target_linvel, R3AngVector target_angvel); +/** + * Return a copy of slide, slope, and ground-snap settings. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R3CharacterControllerSettings r3KinematicCharacterController_Settings(const struct R3KinematicCharacterController *controller); #if defined(RAPIER_DIM3) +/** + * Return native default wheel tuning. This POD value owns no resources. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R3WheelTuning r3DefaultWheelTuning(void); #endif #if defined(RAPIER_DIM3) +/** + * Allocate a vehicle controller bound to its chassis body. The chassis world must outlive the + * controller. Release with r3FreeDynamicRayCastVehicleController. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL struct R3DynamicRayCastVehicleController *r3NewDynamicRayCastVehicleController(struct R3RigidBodyHandle chassis); #endif #if defined(RAPIER_DIM3) +/** + * Release an owned dynamic ray cast vehicle controller. NULL is allowed. Do not pass borrowed + * pointers or free the object twice. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3FreeDynamicRayCastVehicleController(struct R3DynamicRayCastVehicleController *controller); #endif #if defined(RAPIER_DIM3) +/** + * Append a wheel and return its zero-based index. Connection, suspension direction, and axle + * are in chassis-local coordinates. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL size_t r3DynamicRayCastVehicleController_AddWheel(struct R3DynamicRayCastVehicleController *controller, struct R3Vector connection, @@ -7051,6 +13575,10 @@ size_t r3DynamicRayCastVehicleController_AddWheel(struct R3DynamicRayCastVehicle #endif #if defined(RAPIER_DIM3) +/** + * Set the chassis up/forward axis indices (0 = X, 1 = Y, 2 = Z). + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3DynamicRayCastVehicleController_SetAxes(struct R3DynamicRayCastVehicleController *controller, size_t up, @@ -7058,6 +13586,10 @@ R3Status r3DynamicRayCastVehicleController_SetAxes(struct R3DynamicRayCastVehicl #endif #if defined(RAPIER_DIM3) +/** + * Set a wheel engine force, brake force, and steering angle in radians. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3DynamicRayCastVehicleController_SetWheelControls(struct R3DynamicRayCastVehicleController *controller, size_t index, @@ -7067,6 +13599,10 @@ R3Status r3DynamicRayCastVehicleController_SetWheelControls(struct R3DynamicRayC #endif #if defined(RAPIER_DIM3) +/** + * Ray-cast wheel contacts and apply vehicle forces for dt seconds. Does not step the world. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Status r3DynamicRayCastVehicleController_UpdateVehicle(struct R3DynamicRayCastVehicleController *controller, R3Real dt, @@ -7074,22 +13610,38 @@ R3Status r3DynamicRayCastVehicleController_UpdateVehicle(struct R3DynamicRayCast #endif #if defined(RAPIER_DIM3) +/** + * Return signed chassis speed along its forward direction. + * @ingroup controllers + */ RAPIER_API RAPIER_CALL R3Real r3DynamicRayCastVehicleController_CurrentVehicleSpeed(const struct R3DynamicRayCastVehicleController *controller); #endif #if defined(RAPIER_DIM3) +/** + * Copy current wheel state in wheel insertion order. + * @see @ref output_buffers + * @ingroup controllers + */ RAPIER_API RAPIER_CALL size_t r3DynamicRayCastVehicleController_Wheels(const struct R3DynamicRayCastVehicleController *controller, struct R3WheelState *buffer, size_t capacity); #endif +/** + * Propagate all modified body poses to attached colliders. Run collision detection or step before + * querying the broad phase. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RigidBodyPropagateModifiedBodyPositionsToColliders(struct R3World *world); /** * Copies the island manager's active body handles. + * @see @ref output_buffers + * @ingroup worlds */ RAPIER_API RAPIER_CALL size_t r3ActiveRigidBodies(const struct R3World *world, @@ -7098,6 +13650,7 @@ size_t r3ActiveRigidBodies(const struct R3World *world, /** * Wake a body by handle, including a soft-body cluster proxy. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_WakeUp(struct R3RigidBodyHandle handle, @@ -7108,6 +13661,7 @@ R3Status r3RigidBody_WakeUp(struct R3RigidBodyHandle handle, * be restored at the end of a scope. Status returns are unchanged. A handler * that returns lets the caller recover by checking the status; a fail-fast * handler may terminate the process. Includes R3_NOT_FOUND query misses. + * @ingroup errors */ RAPIER_API RAPIER_CALL struct R3ErrorHandler r3SetErrorHandler(struct R3ErrorHandler handler); @@ -7116,83 +13670,161 @@ RAPIER_API RAPIER_CALL struct R3ErrorHandler r3SetErrorHandler(struct R3ErrorHan * LastError does not clear it. Infallible value constructors do not change it. * Check immediately after a fallible value-returning operation when recovering * from errors instead of using a fail-fast error callback. + * @ingroup errors */ RAPIER_API RAPIER_CALL R3Status r3LastStatus(void); /** * Thread-local UTF-8 diagnostic, valid until the next fallible call on this thread. + * @ingroup errors */ RAPIER_API RAPIER_CALL const char *r3LastError(void); +/** + * Create an owned ball shape. Release it with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3BallSharedShape(R3Real radius); +/** + * Create an owned cuboid shape. Release it with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3CuboidSharedShape(struct R3Vector half_extents); +/** + * Create an owned round cuboid shape. Release it with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3RoundCuboidSharedShape(struct R3Vector half_extents, R3Real border_radius); +/** + * Create an owned capsule shape. Release it with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3CapsuleSharedShape(struct R3Vector a, struct R3Vector b, R3Real radius); +/** + * Create an owned segment shape. Release it with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3SegmentSharedShape(struct R3Vector a, struct R3Vector b); +/** + * Create an owned triangle shape. Release it with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3TriangleSharedShape(struct R3Vector a, struct R3Vector b, struct R3Vector c); +/** + * Create an owned halfspace shape. Release it with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3HalfspaceSharedShape(struct R3Vector normal); #if defined(RAPIER_DIM3) +/** + * Create an owned cylinder shape. Release it with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3CylinderSharedShape(R3Real half_height, R3Real radius); #endif #if defined(RAPIER_DIM3) +/** + * Create an owned cone shape. Release it with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3ConeSharedShape(R3Real half_height, R3Real radius); #endif +/** + * Create an owned compound shape; each child pose is relative to the compound. Child shapes are + * shared, not consumed. Release with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3CompoundSharedShape(struct R3CompoundShapeView children); +/** + * Remove the collider and update its parent body mass properties. wake_up wakes the parent. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL R3Status r3RemoveCollider(struct R3ColliderHandle handle, R3Bool wake_up); +/** + * Remove an impulse joint. wake_up wakes its connected bodies. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3RemoveImpulseJoint(struct R3ImpulseJointHandle handle, R3Bool wake_up); +/** + * Copy entity handles. + * @see @ref output_buffers + * @ingroup joints + */ RAPIER_API RAPIER_CALL size_t r3ImpulseJointHandles(const struct R3World *world, struct R3ImpulseJointHandle *buffer, size_t capacity); +/** + * Remove an articulation joint. wake_up wakes affected bodies. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3RemoveMultibodyJoint(struct R3MultibodyJointHandle handle, R3Bool wake_up); +/** + * Copy entity handles. + * @see @ref output_buffers + * @ingroup joints + */ RAPIER_API RAPIER_CALL size_t r3MultibodyJointHandles(const struct R3World *world, struct R3MultibodyJointHandle *buffer, size_t capacity); +/** + * Return the two bodies connected by an impulse joint. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R3JointBodies r3ImpulseJoint_Bodies(struct R3ImpulseJointHandle handle); +/** + * Return native default inverse kinematics options. This POD value owns no resources. + * @ingroup joints + */ RAPIER_API RAPIER_CALL struct R3InverseKinematicsOptions r3DefaultInverseKinematicsOptions(void); +/** + * Return the articulation degrees of freedom associated with the joint. + * @ingroup joints + */ RAPIER_API RAPIER_CALL size_t r3MultibodyJoint_Ndofs(struct R3MultibodyJointHandle handle); /** * Read/write displacement buffer must contain exactly ndofs entries; zero it for a fresh solve. + * @ingroup joints */ RAPIER_API RAPIER_CALL R3Status r3MultibodyJoint_InverseKinematics(struct R3MultibodyJointHandle handle, @@ -7203,6 +13835,10 @@ R3Status r3MultibodyJoint_InverseKinematics(struct R3MultibodyJointHandle handle R3Real *displacements, size_t count); +/** + * Apply generalized articulation displacements in native degree-of-freedom order. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3MultibodyJoint_ApplyDisplacements(struct R3MultibodyJointHandle handle, const R3Real *displacements, @@ -7210,155 +13846,357 @@ R3Status r3MultibodyJoint_ApplyDisplacements(struct R3MultibodyJointHandle handl /** * Frees an owned object; NULL is allowed. Never free a borrowed pointer. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3Status r3FreeSharedShape(R3SharedShape *object); /** - * Creates an independent owned copy. + * Create an owned wrapper sharing the same immutable geometry. Release it with + * r3FreeSharedShape. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3SharedShape_Clone(const R3SharedShape *object); +/** + * Return the number of rigid body objects in the world. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL size_t r3RigidBodyCount(const struct R3World *world); +/** + * Copy entity handles. + * @see @ref output_buffers + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL size_t r3RigidBodyHandles(const struct R3World *world, struct R3RigidBodyHandle *buffer, size_t capacity); +/** + * Test whether the live world contains this rigid body handle. A removed/stale handle returns + * false. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL R3Bool r3RigidBody_Contains(struct R3RigidBodyHandle handle); +/** + * Return the number of collider objects in the world. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL size_t r3ColliderCount(const struct R3World *world); +/** + * Copy entity handles. + * @see @ref output_buffers + * @ingroup colliders + */ RAPIER_API RAPIER_CALL size_t r3ColliderHandles(const struct R3World *world, struct R3ColliderHandle *buffer, size_t capacity); +/** + * Test whether the live world contains this collider handle. A removed/stale handle returns false. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL R3Bool r3Collider_Contains(struct R3ColliderHandle handle); +/** + * Return the number of soft body objects in the world. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r3SoftBodyCount(const struct R3World *world); +/** + * Copy entity handles. + * @see @ref output_buffers + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r3SoftBodyHandles(const struct R3World *world, struct R3SoftBodyHandle *buffer, size_t capacity); +/** + * Test whether the live world contains this soft body handle. A removed/stale handle returns + * false. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Bool r3SoftBody_Contains(struct R3SoftBodyHandle handle); /** * Remove a body and its joints, optionally keeping colliders as standalone objects. * Returns whether a body was removed; a stale handle returns false without error. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Bool r3RemoveRigidBody(struct R3RigidBodyHandle handle, R3Bool remove_attached_colliders); +/** + * Return the world setting documented by R3IntegrationParameters::dt. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Real r3TimeStep(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::dt. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetTimeStep(struct R3World *world, R3Real value); +/** + * Return the world setting documented by R3IntegrationParameters::minCcdDt. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Real r3MinCcdDt(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::minCcdDt. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetMinCcdDt(struct R3World *world, R3Real value); +/** + * Return the world setting documented by R3IntegrationParameters::lengthUnit. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Real r3LengthUnit(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::lengthUnit. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetLengthUnit(struct R3World *world, R3Real value); +/** + * Return the world setting documented by R3IntegrationParameters::warmstartCoefficient. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Real r3WarmstartCoefficient(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::warmstartCoefficient. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetWarmstartCoefficient(struct R3World *world, R3Real value); +/** + * Return the world setting documented by R3IntegrationParameters::normalizedAllowedLinearError. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Real r3NormalizedAllowedLinearError(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::normalizedAllowedLinearError. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetNormalizedAllowedLinearError(struct R3World *world, R3Real value); +/** + * Return the world setting documented by + * R3IntegrationParameters::normalizedMaxCorrectiveVelocity. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Real r3NormalizedMaxCorrectiveVelocity(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::normalizedMaxCorrectiveVelocity. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetNormalizedMaxCorrectiveVelocity(struct R3World *world, R3Real value); +/** + * Return the world setting documented by R3IntegrationParameters::normalizedPredictionDistance. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Real r3NormalizedPredictionDistance(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::normalizedPredictionDistance. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetNormalizedPredictionDistance(struct R3World *world, R3Real value); +/** + * Return the world setting documented by R3IntegrationParameters::normalizedMaxLinearVelocity. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Real r3NormalizedMaxLinearVelocity(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::normalizedMaxLinearVelocity. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetNormalizedMaxLinearVelocity(struct R3World *world, R3Real value); +/** + * Return the world setting documented by + * R3IntegrationParameters::normalizedContactRecycleDistance. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Real r3NormalizedContactRecycleDistance(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::normalizedContactRecycleDistance. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetNormalizedContactRecycleDistance(struct R3World *world, R3Real value); +/** + * Return the world setting documented by R3IntegrationParameters::numSolverIterations. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL size_t r3NumSolverIterations(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::numSolverIterations. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetNumSolverIterations(struct R3World *world, size_t value); +/** + * Return the world setting documented by R3IntegrationParameters::numInternalPgsIterations. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL size_t r3NumInternalPgsIterations(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::numInternalPgsIterations. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetNumInternalPgsIterations(struct R3World *world, size_t value); +/** + * Return the world setting documented by + * R3IntegrationParameters::numInternalStabilizationIterations. + * @ingroup errors + */ RAPIER_API RAPIER_CALL size_t r3NumInternalStabilizationIterations(const struct R3World *world); +/** + * Set the world setting documented by + * R3IntegrationParameters::numInternalStabilizationIterations. + * @ingroup errors + */ RAPIER_API RAPIER_CALL R3Status r3SetNumInternalStabilizationIterations(struct R3World *world, size_t value); +/** + * Return the world setting documented by R3IntegrationParameters::maxCcdSubsteps. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL size_t r3MaxCcdSubsteps(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::maxCcdSubsteps. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetMaxCcdSubsteps(struct R3World *world, size_t value); +/** + * Return the world setting documented by R3IntegrationParameters::contactClustering. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Bool r3ContactClustering(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::contactClustering. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetContactClustering(struct R3World *world, R3Bool value); +/** + * Return the world setting documented by R3IntegrationParameters::contactRecycling. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Bool r3ContactRecycling(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::contactRecycling. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetContactRecycling(struct R3World *world, R3Bool value); +/** + * Return the world setting documented by R3IntegrationParameters::frictionInBiasPass. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Bool r3FrictionInBiasPass(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::frictionInBiasPass. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetFrictionInBiasPass(struct R3World *world, R3Bool value); +/** + * Return the world setting documented by R3IntegrationParameters::warmstartJoints. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Bool r3WarmstartJoints(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::warmstartJoints. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3SetWarmstartJoints(struct R3World *world, R3Bool value); +/** + * Return the world setting documented by R3IntegrationParameters::contactSoftness. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R3SpringCoefficients r3ContactSoftness(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::contactSoftness. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3SetContactSoftness(struct R3World *world, struct R3SpringCoefficients value); +/** + * Return the world setting documented by R3IntegrationParameters::staticContactSoftness. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R3SpringCoefficients r3StaticContactSoftness(const struct R3World *world); +/** + * Set the world setting documented by R3IntegrationParameters::staticContactSoftness. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3SetStaticContactSoftness(struct R3World *world, struct R3SpringCoefficients value); /** * Applies Rapier's persistent one-way platform logic to the borrowed manifold. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R3Status r3ContactModificationContext_UpdateAsOnewayPlatform(struct R3ContactModificationContext *context, @@ -7367,41 +14205,83 @@ R3Status r3ContactModificationContext_UpdateAsOnewayPlatform(struct R3ContactMod /** * Sets the tangent velocity of every rigid solver contact in this manifold. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R3Status r3ContactModificationContext_SetTangentVelocity(struct R3ContactModificationContext *context, struct R3Vector velocity); +/** + * Allocate an empty event collector; release it with r3FreeEventCollector. + * @ingroup events + */ RAPIER_API RAPIER_CALL struct R3EventCollector *r3NewEventCollector(void); +/** + * Release an owned event collector. NULL is allowed. Do not pass borrowed pointers or free the + * object twice. + * @ingroup events + */ RAPIER_API RAPIER_CALL R3Status r3FreeEventCollector(struct R3EventCollector *events); +/** + * Discard all collected events. Does not change the world. + * @ingroup events + */ RAPIER_API RAPIER_CALL R3Status r3EventCollector_Clear(struct R3EventCollector *events); +/** + * Copy the collected collision start/stop events without removing them. + * @see @ref output_buffers + * @ingroup events + */ RAPIER_API RAPIER_CALL size_t r3EventCollector_CollisionEvents(const struct R3EventCollector *events, struct R3CollisionEvent *buffer, size_t capacity); +/** + * Copy the collected contact-force events without removing them. + * @see @ref output_buffers + * @ingroup events + */ RAPIER_API RAPIER_CALL size_t r3EventCollector_ContactForceEvents(const struct R3EventCollector *events, struct R3ContactForceEvent *buffer, size_t capacity); +/** + * Return the number of queued soft-body tear events. + * @ingroup events + */ RAPIER_API RAPIER_CALL size_t r3EventCollector_TearEventCount(const struct R3EventCollector *events); +/** + * Return an owned copy of a queued tear event; release with r3FreeSoftBodyTearEvent. Does + * not remove the queued event. + * @ingroup events + */ RAPIER_API RAPIER_CALL struct R3SoftBodyTearEvent *r3EventCollector_TearEvent(const struct R3EventCollector *events, size_t index); +/** + * Return the world-space gravitational acceleration. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R3Vector r3Gravity(const struct R3World *world); +/** + * Set the world-space gravitational acceleration. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3SetGravity(struct R3World *world, struct R3Vector value); /** * Hooks and events may be NULL. This call invalidates all borrowed set-element pointers. * Advance simulation by one timestep. Hooks and events may be NULL. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R3Status r3Step(struct R3World *world, @@ -7410,27 +14290,45 @@ R3Status r3Step(struct R3World *world, /** * Refresh collision detection without advancing simulation. Hooks and events may be NULL. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R3Status r3DetectCollisions(struct R3World *world, const struct R3PhysicsHooks *hooks, const struct R3EventCollector *events); +/** + * Borrow snapshot bytes without copying; valid until r3FreeBytes. Never free the returned data + * pointer. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R3ByteView r3Bytes_Data(const struct R3Bytes *bytes); +/** + * Release an owned snapshot byte buffer. NULL is allowed. Do not pass borrowed pointers or free + * the object twice. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL R3Status r3FreeBytes(struct R3Bytes *bytes); +/** + * Return owned snapshot bytes; release them with r3FreeBytes. See @ref snapshots for + * restoration and handle lifetimes. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R3Bytes *r3SerializeWorld(const struct R3World *world); /** - * Restore ONLY trusted snapshots produced by the identical Rapier build. Snapshots are not a stable file format. + * Restore ONLY trusted snapshots produced by the identical Rapier build. Snapshots are not a + * stable file format. + * @ingroup worlds */ -RAPIER_API RAPIER_CALL -struct R3World *r3DeserializeWorld(const uint8_t *data, - size_t count); +RAPIER_API RAPIER_CALL struct R3World *r3DeserializeWorld(const uint8_t *data, size_t count); /** * Color is HSLA (hue in degrees), matching Rapier DebugColor. mode uses DebugRenderMode bits. + * @see @ref output_buffers + * @ingroup worlds */ RAPIER_API RAPIER_CALL size_t r3DebugRender(const struct R3World *world, @@ -7438,133 +14336,265 @@ size_t r3DebugRender(const struct R3World *world, struct R3DebugLine *buffer, size_t capacity); +/** + * Set the world setting documented by R3SoftBodiesSettings::resweepStrain. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3SoftBodiesSetResweepStrain(struct R3World *world, R3Real value); +/** + * Return the world setting documented by R3SoftBodiesSettings::resweepStrain. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Real r3SoftBodiesResweepStrain(const struct R3World *world); +/** + * Set the world setting documented by R3SoftBodiesSettings::contactStiffening. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3SoftBodiesSetContactStiffening(struct R3World *world, R3Real value); +/** + * Return the world setting documented by R3SoftBodiesSettings::contactStiffening. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Real r3SoftBodiesContactStiffening(const struct R3World *world); +/** + * Set the world setting documented by R3SoftBodiesSettings::maxExtraSubsteps. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3SoftBodiesSetMaxExtraSubsteps(struct R3World *world, size_t value); +/** + * Return the world setting documented by R3SoftBodiesSettings::maxExtraSubsteps. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r3SoftBodiesMaxExtraSubsteps(const struct R3World *world); +/** + * Set the world setting documented by R3SoftRecoverySettings::authoredVelocityMargin. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetAuthoredVelocityMargin(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::edgeSpeculation. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetEdgeSpeculation(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::invertedCellDetection. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetInvertedCellDetection(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::selfCrossingDetection. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetSelfCrossingDetection(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::detectionMotionGating. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetDetectionMotionGating(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::crossBodyDetection. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetCrossBodyDetection(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::selfStandDown. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetSelfStandDown(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::crossBodyExpelGate. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetCrossBodyExpelGate(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::edgeStandDown. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetEdgeStandDown(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::crossingRepulsion. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetCrossingRepulsion(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::crossingRepulsionGuide. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetCrossingRepulsionGuide(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::crossingRepulsionSelfGuide. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetCrossingRepulsionSelfGuide(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::recoveryPace. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetRecoveryPace(struct R3World *world, R3Real value); +/** + * Set the world setting documented by R3SoftRecoverySettings::overlapConstraints. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetOverlapConstraints(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::overlapRigid. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetOverlapRigid(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::overlapSkipSelfTangled. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetOverlapSkipSelfTangled(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::overlapEdgeStandDown. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetOverlapEdgeStandDown(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::overlapConstraintPace. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetOverlapConstraintPace(struct R3World *world, R3Real value); +/** + * Set the world setting documented by R3SoftRecoverySettings::overlapSkinVolume. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetOverlapSkinVolume(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::overlapKeptDepth. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetOverlapKeptDepth(struct R3World *world, R3Real value); +/** + * Set the world setting documented by R3SoftRecoverySettings::overlapSelfRegions. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetOverlapSelfRegions(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::overlapNormalPush. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetOverlapNormalPush(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::overlapMultiVolume. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetOverlapMultiVolume(struct R3World *world, R3Bool value); +/** + * Set the world setting documented by R3SoftRecoverySettings::overlapProgressMargin. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RecoverySetOverlapProgressMargin(struct R3World *world, R3Real value); #if defined(RAPIER_FEM) +/** + * Set the world setting documented by R3SoftFemParameters::linearTolerance. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3FemSetLinearTolerance(struct R3World *world, R3Real value); #endif #if defined(RAPIER_FEM) +/** + * Set the world setting documented by R3SoftFemParameters::maxLinearIterations. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3FemSetMaxLinearIterations(struct R3World *world, size_t value); #endif #if defined(RAPIER_FEM) +/** + * Set the world setting documented by R3SoftFemParameters::maxDenseDofs. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3FemSetMaxDenseDofs(struct R3World *world, size_t value); #endif @@ -7573,6 +14603,7 @@ RAPIER_API RAPIER_CALL R3Status r3FemSetMaxDenseDofs(struct R3World *world, size * Takes effect on the next step. Reconfiguration must not race with a step or callback. * Returns R3_UNSUPPORTED in builds without the parallel feature; keeps the previous * pool when constructing the new one fails. The pool is not included in snapshots. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R3Status r3SetNumThreads(struct R3World *world, size_t num_threads); @@ -7580,18 +14611,21 @@ RAPIER_API RAPIER_CALL R3Status r3SetNumThreads(struct R3World *world, size_t nu * Removes the world's dedicated pool. A parallel build then uses the calling * context's Rayon pool (normally the global pool), not a single worker. * Returns R3_UNSUPPORTED in a build without the parallel feature. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R3Status r3ClearThreadPool(struct R3World *world); /** * Size of the world's dedicated pool, or zero if a parallel build has no dedicated * pool configured. Returns one for a build without the parallel feature. + * @ingroup worlds */ RAPIER_API RAPIER_CALL size_t r3NumThreads(const struct R3World *world); /** * Enable or disable the native pipeline profiling counters. Enabling returns * R3_UNSUPPORTED if the library was built without the profiler feature. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R3Status r3SetCountersEnabled(struct R3World *world, R3Bool enabled); @@ -7599,41 +14633,81 @@ RAPIER_API RAPIER_CALL R3Status r3SetCountersEnabled(struct R3World *world, R3Bo * Native engine time of the most recent step, in milliseconds, as in the Rust testbed. * Enable counters before stepping. Excludes C callbacks outside the step, rendering, * and dispatch into a dedicated thread pool; remains unchanged while paused. + * @ingroup worlds */ RAPIER_API RAPIER_CALL double r3StepTimeMs(const struct R3World *world); /** * Read ONLY trusted legacy rigid-world snapshots from debug_deserialize3.rs, * produced by the identical Rapier build. This is not a stable interchange format. + * Import trusted legacy Rust testbed rigid-state bytes into a new owned world. Release with + * r3FreeWorld; see @ref snapshots. + * @ingroup worlds */ RAPIER_API RAPIER_CALL struct R3World *r3DeserializeRigidState(const uint8_t *data, size_t count); +/** + * Return native default query filter. This POD value owns no resources. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R3QueryFilter r3DefaultQueryFilter(void); +/** + * Return native default shape cast options. This POD value owns no resources. + * @ingroup queries + */ RAPIER_API RAPIER_CALL struct R3ShapeCastOptions r3DefaultShapeCastOptions(void); +/** + * Remove a soft body and its associated simulation objects. Invalidates its handle. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RemoveSoftBody(struct R3SoftBodyHandle handle); +/** + * Wake the soft body and its rigid proxies. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_WakeUp(struct R3SoftBodyHandle handle); +/** + * Release an owned soft body tear event. NULL is allowed. Do not pass borrowed pointers or free + * the object twice. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3FreeSoftBodyTearEvent(struct R3SoftBodyTearEvent *event); +/** + * Return the source soft-body handle for this tear event. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R3SoftBodyHandle r3SoftBodyTearEvent_SoftBody(const struct R3SoftBodyTearEvent *event); +/** + * Copy the soft-body handles produced by the tear. + * @see @ref output_buffers + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r3SoftBodyTearEvent_Bodies(const struct R3SoftBodyTearEvent *event, struct R3SoftBodyHandle *buffer, size_t capacity); +/** + * Return the destination body and particle index for an original particle. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R3ParticleDestination r3SoftBodyTearEvent_ParticleDestination(const struct R3SoftBodyTearEvent *event, uint32_t particle); /** * Flat indices; element arity follows the corresponding Rust event field. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBodyTearEvent_TornEdges(const struct R3SoftBodyTearEvent *event, @@ -7642,6 +14716,8 @@ size_t r3SoftBodyTearEvent_TornEdges(const struct R3SoftBodyTearEvent *event, /** * Flat indices; element arity follows the corresponding Rust event field. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBodyTearEvent_TornCells(const struct R3SoftBodyTearEvent *event, @@ -7650,6 +14726,8 @@ size_t r3SoftBodyTearEvent_TornCells(const struct R3SoftBodyTearEvent *event, /** * Flat indices; element arity follows the corresponding Rust event field. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBodyTearEvent_RemovedEdges(const struct R3SoftBodyTearEvent *event, @@ -7658,6 +14736,8 @@ size_t r3SoftBodyTearEvent_RemovedEdges(const struct R3SoftBodyTearEvent *event, /** * Flat indices; element arity follows the corresponding Rust event field. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBodyTearEvent_SplitParticles(const struct R3SoftBodyTearEvent *event, @@ -7666,28 +14746,50 @@ size_t r3SoftBodyTearEvent_SplitParticles(const struct R3SoftBodyTearEvent *even /** * Flat indices; element arity follows the corresponding Rust event field. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBodyTearEvent_InsertedParticles(const struct R3SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); +/** + * Copy original particle indices belonging to a resulting piece. + * @see @ref output_buffers + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r3SoftBodyTearEvent_PieceParticles(const struct R3SoftBodyTearEvent *event, size_t piece_index, uint32_t *buffer, size_t capacity); +/** + * Copy cluster-to-piece and rigid-proxy remapping records. + * @see @ref output_buffers + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r3SoftBodyTearEvent_Clusters(const struct R3SoftBodyTearEvent *event, struct R3SoftClusterSplit *buffer, size_t capacity); +/** + * Copy impulse-joint remapping records produced by the tear. + * @see @ref output_buffers + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL size_t r3SoftBodyTearEvent_MovedJoints(const struct R3SoftBodyTearEvent *event, struct R3SoftJointMove *buffer, size_t capacity); +/** + * Tear the selected edges and return an owned remapping event. Release it with + * r3FreeSoftBodyTearEvent. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL struct R3SoftBodyTearEvent *r3SoftBody_Tear(struct R3SoftBodyHandle handle, const uint32_t *edges, @@ -7695,11 +14797,19 @@ struct R3SoftBodyTearEvent *r3SoftBody_Tear(struct R3SoftBodyHandle handle, const uint32_t *cells, size_t cell_count); +/** + * Create a rigid proxy cluster from the supplied particle indices and return its cluster index. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL uint32_t r3SoftBody_AddCluster(struct R3SoftBodyHandle handle, const uint32_t *particles, size_t count); +/** + * Remove the selected cluster and its rigid proxy. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_RemoveCluster(struct R3SoftBodyHandle handle, uint32_t cluster); @@ -7707,6 +14817,7 @@ R3Status r3SoftBody_RemoveCluster(struct R3SoftBodyHandle handle, /** * Optional particle destination after a tear. Missing destinations are normal and set * found to false; body/index are only written when a destination exists. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3OptionalParticleDestination r3SoftBodyTearEvent_TryParticleDestination(const struct R3SoftBodyTearEvent *event, @@ -7715,14 +14826,24 @@ struct R3OptionalParticleDestination r3SoftBodyTearEvent_TryParticleDestination( /** * Cut using DIM points (a segment in 2D, triangle in 3D). A no-op returns a null event. * The optional owned event must be freed with FreeSoftBodyTearEvent. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyTearEvent *r3CutSoftBody(struct R3SoftBodyHandle handle, const struct R3Vector *blade); +/** + * Return volume-meshing settings for the supplied cell size; this POD value requires no + * destructor. + * @ingroup worlds + */ RAPIER_API RAPIER_CALL struct R3VolumeMeshParameters r3NewVolumeMeshParameters(R3Real cell_size); +/** + * Return ABI version, dimension, scalar size, and pointer size of the linked library. + * @ingroup errors + */ RAPIER_API RAPIER_CALL struct R3BuildInfo r3BuildInfo(void); /** @@ -7730,6 +14851,7 @@ RAPIER_API RAPIER_CALL struct R3BuildInfo r3BuildInfo(void); * The suffix identifies the C bindings revision for the Rust crate version. * The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. * This release identifier is independent of the ABI compatibility version. + * @ingroup errors */ RAPIER_API RAPIER_CALL const char *r3Version(void); @@ -7738,47 +14860,86 @@ RAPIER_API RAPIER_CALL const char *r3Version(void); * Custom profiles report the corresponding inherited Cargo profile category. * The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. * This is independent of the consumer's build mode and of per-package optimization overrides. + * @ingroup errors */ RAPIER_API RAPIER_CALL const char *r3BuildProfile(void); +/** + * Return profiling, SIMD width, and parallelism of the linked library. + * @ingroup errors + */ RAPIER_API RAPIER_CALL struct R3BuildFeatures r3BuildFeatures(void); +/** + * Create an owned heightfield shape from copied samples. 3D samples are column-major, with rows * + * columns entries. Release with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3HeightfieldSharedShape(struct R3RealView heights, size_t rows, size_t columns, struct R3Vector scale); +/** + * Compute the shape axis-aligned bounds at the supplied world-space pose. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL struct R3Aabb r3SharedShape_ComputeAabb(const R3SharedShape *shape, struct R3Pose pose); +/** + * Compute local mass properties for the supplied nonnegative density. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL struct R3MassProperties r3SharedShape_MassProperties(const R3SharedShape *shape, R3Real density); +/** + * Test whether the world-space point lies inside the shape at pose. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3Bool r3SharedShape_ContainsPoint(const R3SharedShape *shape, struct R3Pose pose, struct R3Vector point); +/** + * Copy current narrow-phase contact pairs, including pairs without active solver contacts. + * @see @ref output_buffers + * @ingroup events + */ RAPIER_API RAPIER_CALL size_t r3ContactPairs(const struct R3World *world, struct R3ContactPair *buffer, size_t capacity); +/** + * Return the narrow-phase contact pair for two colliders, or report R3_NOT_FOUND. + * @ingroup events + */ RAPIER_API RAPIER_CALL struct R3ContactPair r3ContactPair(struct R3ColliderHandle collider1, struct R3ColliderHandle collider2); +/** + * Copy current sensor intersection pairs from the narrow phase. + * @see @ref output_buffers + * @ingroup events + */ RAPIER_API RAPIER_CALL size_t r3IntersectionPairs(const struct R3World *world, struct R3IntersectionPair *buffer, size_t capacity); /** - * Contact points in collider-local space; normal in world space. Geometric manifolds may be recycled. + * Contact points in collider-local space; normal in world space. Geometric manifolds may be + * recycled. * For clustered solver impulses use contact pair totals. Soft pairs have no rigid manifolds. + * @see @ref output_buffers + * @ingroup worlds */ RAPIER_API RAPIER_CALL size_t r3ContactPoints(struct R3ColliderHandle collider1, @@ -7786,11 +14947,20 @@ size_t r3ContactPoints(struct R3ColliderHandle collider1, struct R3ContactPoint *buffer, size_t capacity); +/** + * Copy the articulation generalized velocities in native degree-of-freedom order. + * @see @ref output_buffers + * @ingroup joints + */ RAPIER_API RAPIER_CALL size_t r3MultibodyJoint_GeneralizedVelocity(struct R3MultibodyJointHandle handle, R3Real *buffer, size_t capacity); +/** + * Replace articulation generalized velocities; the array length must match its degrees of freedom. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3MultibodyJoint_SetGeneralizedVelocity(struct R3MultibodyJointHandle handle, const R3Real *values, @@ -7798,6 +14968,7 @@ R3Status r3MultibodyJoint_SetGeneralizedVelocity(struct R3MultibodyJointHandle h /** * Check this before passing any dimension/precision-dependent structs across the ABI. + * @ingroup errors */ RAPIER_API RAPIER_CALL R3Status r3CheckAbi(uint32_t version, @@ -7806,12 +14977,19 @@ R3Status r3CheckAbi(uint32_t version, size_t vector_size, size_t pose_size); +/** + * Return owned local-space rendering geometry; release it with r3FreeShapeMesh. subdivisions + * controls curved-shape resolution. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL struct R3ShapeMesh *r3SharedShape_Tessellate(const R3SharedShape *shape, uint32_t subdivisions); /** * Flat groups of three vertices. Standard output-buffer convention. + * @see @ref output_buffers + * @ingroup shapes */ RAPIER_API RAPIER_CALL size_t r3ShapeMesh_Triangles(const struct R3ShapeMesh *mesh, @@ -7820,15 +14998,26 @@ size_t r3ShapeMesh_Triangles(const struct R3ShapeMesh *mesh, /** * Flat groups of two vertices. Standard output-buffer convention. + * @see @ref output_buffers + * @ingroup shapes */ RAPIER_API RAPIER_CALL size_t r3ShapeMesh_Lines(const struct R3ShapeMesh *mesh, struct R3Vector *buffer, size_t capacity); +/** + * Release an owned shape mesh. NULL is allowed. Do not pass borrowed pointers or free the object + * twice. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3Status r3FreeShapeMesh(struct R3ShapeMesh *mesh); #if defined(RAPIER_DIM3) +/** + * Create an owned round cylinder shape. Release it with r3FreeSharedShape. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3SharedShape *r3RoundCylinderSharedShape(R3Real half_height, R3Real radius, @@ -7839,6 +15028,7 @@ R3SharedShape *r3RoundCylinderSharedShape(R3Real half_height, /** * Tessellate a ball or capsule with independent longitude/latitude subdivision counts. * Cuboids, cones, cylinders, convex polyhedra, trimeshes, and heightfields are also supported. + * @ingroup shapes */ RAPIER_API RAPIER_CALL struct R3TriMeshData *r3SharedShape_ToTrimesh(const R3SharedShape *shape, @@ -7847,6 +15037,11 @@ struct R3TriMeshData *r3SharedShape_ToTrimesh(const R3SharedShape *shape, #endif #if defined(RAPIER_DIM3) +/** + * Copy vertices. + * @see @ref output_buffers + * @ingroup shapes + */ RAPIER_API RAPIER_CALL size_t r3TriMeshData_Vertices(const struct R3TriMeshData *mesh, struct R3Vector *buffer, @@ -7856,6 +15051,8 @@ size_t r3TriMeshData_Vertices(const struct R3TriMeshData *mesh, #if defined(RAPIER_DIM3) /** * Flat triangle indices; count and capacity are numbers of u32 entries. + * @see @ref output_buffers + * @ingroup shapes */ RAPIER_API RAPIER_CALL size_t r3TriMeshData_Indices(const struct R3TriMeshData *mesh, @@ -7864,14 +15061,28 @@ size_t r3TriMeshData_Indices(const struct R3TriMeshData *mesh, #endif #if defined(RAPIER_DIM3) +/** + * Release an owned tri mesh data. NULL is allowed. Do not pass borrowed pointers or free the + * object twice. + * @ingroup shapes + */ RAPIER_API RAPIER_CALL R3Status r3FreeTriMeshData(struct R3TriMeshData *mesh); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return native default urdf loader options. This POD value owns no resources. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL struct R3UrdfLoaderOptions r3DefaultUrdfLoaderOptions(void); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Release an owned urdf robot. NULL is allowed. Do not pass borrowed pointers or free the object + * twice. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R3Status r3FreeUrdfRobot(struct R3UrdfRobot *object); #endif @@ -7879,6 +15090,7 @@ RAPIER_API RAPIER_CALL R3Status r3FreeUrdfRobot(struct R3UrdfRobot *object); /** * Load from a UTF-8 path. Validates options before reading the file. * Options and their blueprint resources are borrowed through this call; the robot is owned. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R3UrdfRobot *r3UrdfRobotFromFile(const char *path, @@ -7886,18 +15098,28 @@ struct R3UrdfRobot *r3UrdfRobotFromFile(const char *path, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Apply an additional transform to the loaded robot before insertion. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R3Status r3UrdfRobot_AppendTransform(struct R3UrdfRobot *robot, struct R3Pose transform); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Release an owned urdf robot handles. NULL is allowed. Do not pass borrowed pointers or free the + * object twice. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R3Status r3FreeUrdfRobotHandles(struct R3UrdfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R3UrdfRobotHandles *r3UrdfRobot_InsertUsingImpulseJoints(struct R3World *world, @@ -7907,6 +15129,7 @@ struct R3UrdfRobotHandles *r3UrdfRobot_InsertUsingImpulseJoints(struct R3World * #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R3UrdfRobotHandles *r3UrdfRobot_InsertUsingMultibodyJoints(struct R3World *world, @@ -7917,6 +15140,8 @@ struct R3UrdfRobotHandles *r3UrdfRobot_InsertUsingMultibodyJoints(struct R3World #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Body handles in source order; absent MJCF bodies have invalid handles. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r3UrdfRobotHandles_Bodies(const struct R3UrdfRobotHandles *handles, @@ -7925,10 +15150,19 @@ size_t r3UrdfRobotHandles_Bodies(const struct R3UrdfRobotHandles *handles, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return native default mjcf loader options. This POD value owns no resources. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL struct R3MjcfLoaderOptions r3DefaultMjcfLoaderOptions(void); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Release an owned mjcf robot. NULL is allowed. Do not pass borrowed pointers or free the object + * twice. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R3Status r3FreeMjcfRobot(struct R3MjcfRobot *object); #endif @@ -7936,6 +15170,7 @@ RAPIER_API RAPIER_CALL R3Status r3FreeMjcfRobot(struct R3MjcfRobot *object); /** * Load from a UTF-8 path. Validates options before reading the file. * Options and their blueprint resources are borrowed through this call; the robot is owned. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R3MjcfRobot *r3MjcfRobotFromFile(const char *path, @@ -7943,18 +15178,28 @@ struct R3MjcfRobot *r3MjcfRobotFromFile(const char *path, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Apply an additional transform to the loaded robot before insertion. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R3Status r3MjcfRobot_AppendTransform(struct R3MjcfRobot *robot, struct R3Pose transform); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Release an owned mjcf robot handles. NULL is allowed. Do not pass borrowed pointers or free the + * object twice. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R3Status r3FreeMjcfRobotHandles(struct R3MjcfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R3MjcfRobotHandles *r3MjcfRobot_InsertUsingImpulseJoints(struct R3World *world, @@ -7964,6 +15209,7 @@ struct R3MjcfRobotHandles *r3MjcfRobot_InsertUsingImpulseJoints(struct R3World * #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R3MjcfRobotHandles *r3MjcfRobot_InsertUsingMultibodyJoints(struct R3World *world, @@ -7974,6 +15220,8 @@ struct R3MjcfRobotHandles *r3MjcfRobot_InsertUsingMultibodyJoints(struct R3World #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Body handles in source order; absent MJCF bodies have invalid handles. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r3MjcfRobotHandles_Bodies(const struct R3MjcfRobotHandles *handles, @@ -7984,15 +15232,24 @@ size_t r3MjcfRobotHandles_Bodies(const struct R3MjcfRobotHandles *handles, #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Resolved model gravity before the caller chooses a world convention. + * @ingroup robotics */ RAPIER_API RAPIER_CALL struct R3Vector r3MjcfRobot_Gravity(const struct R3MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return the number of source MJCF bodies. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r3MjcfRobot_BodyCount(const struct R3MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return the collider count for a source body index. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r3MjcfRobot_BodyColliderCount(const struct R3MjcfRobot *robot, size_t body); @@ -8000,7 +15257,8 @@ size_t r3MjcfRobot_BodyColliderCount(const struct R3MjcfRobot *robot, #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** - * Borrowed collider; invalidated by freeing or mutating the robot's storage. + * Set collision groups on a collider in the loaded robot, before insertion. + * @ingroup robotics */ RAPIER_API RAPIER_CALL R3Status r3MjcfRobot_SetBodyColliderCollisionGroups(struct R3MjcfRobot *robot, @@ -8010,12 +15268,18 @@ R3Status r3MjcfRobot_SetBodyColliderCollisionGroups(struct R3MjcfRobot *robot, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return the number of imported keyframes. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r3MjcfRobot_KeyframeCount(const struct R3MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Copies a NUL-terminated UTF-8 name. Count includes NUL; unnamed keys return an empty string. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r3MjcfRobot_KeyframeName(const struct R3MjcfRobot *robot, @@ -8025,6 +15289,10 @@ size_t r3MjcfRobot_KeyframeName(const struct R3MjcfRobot *robot, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Append a keyframe from the source MJCF model to the loaded robot. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R3Status r3MjcfRobot_AppendKeyframe(struct R3MjcfRobot *robot, const struct R3MjcfRobot *source, @@ -8032,6 +15300,11 @@ R3Status r3MjcfRobot_AppendKeyframe(struct R3MjcfRobot *robot, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Copy actuator controls for the selected keyframe. + * @see @ref output_buffers + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r3MjcfRobot_KeyframeControls(const struct R3MjcfRobot *robot, size_t key, @@ -8040,11 +15313,19 @@ size_t r3MjcfRobot_KeyframeControls(const struct R3MjcfRobot *robot, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return the number of imported actuators. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r3MjcfRobotHandles_ActuatorCount(const struct R3MjcfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Apply the selected keyframe to the inserted robot. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R3Status r3MjcfRobotHandles_ApplyKeyframe(const struct R3MjcfRobotHandles *handles, const struct R3MjcfRobot *robot, @@ -8052,6 +15333,10 @@ R3Status r3MjcfRobotHandles_ApplyKeyframe(const struct R3MjcfRobotHandles *handl #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Apply actuator controls with per-actuator scaling to the inserted robot. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL R3Status r3MjcfRobotHandles_ApplyControlsScaled(const struct R3MjcfRobotHandles *handles, const R3Real *controls, @@ -8060,12 +15345,21 @@ R3Status r3MjcfRobotHandles_ApplyControlsScaled(const struct R3MjcfRobotHandles #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return the number of visual meshes for a source body. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL size_t r3MjcfRobot_BodyVisualCount(const struct R3MjcfRobot *robot, size_t body); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Borrow a visual mesh by body/visual index. Valid until the robot is freed or its storage + * changes; never free this pointer. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL const R3MjcfVisualMesh *r3MjcfRobot_BodyVisual(const struct R3MjcfRobot *robot, size_t body, @@ -8073,6 +15367,10 @@ const R3MjcfVisualMesh *r3MjcfRobot_BodyVisual(const struct R3MjcfRobot *robot, #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) +/** + * Return a copy of visual pose, color, material, and geometry-kind flags. + * @ingroup robotics + */ RAPIER_API RAPIER_CALL struct R3MjcfVisualMeshInfo r3MjcfVisualMesh_Info(const R3MjcfVisualMesh *visual); #endif @@ -8081,6 +15379,7 @@ struct R3MjcfVisualMeshInfo r3MjcfVisualMesh_Info(const R3MjcfVisualMesh *visual /** * Returns an owned shared shape reference. * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + * @ingroup robotics */ RAPIER_API RAPIER_CALL R3SharedShape *r3MjcfVisualMesh_CloneShape(const R3MjcfVisualMesh *visual); @@ -8089,6 +15388,8 @@ R3SharedShape *r3MjcfVisualMesh_CloneShape(const R3MjcfVisualMesh *visual); #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Copies flattened pairs of per-vertex UV coordinates. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r3MjcfVisualMesh_Uvs(const R3MjcfVisualMesh *visual, @@ -8099,6 +15400,8 @@ size_t r3MjcfVisualMesh_Uvs(const R3MjcfVisualMesh *visual, #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Copies flattened triples of per-vertex normals. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r3MjcfVisualMesh_Normals(const R3MjcfVisualMesh *visual, @@ -8109,6 +15412,8 @@ size_t r3MjcfVisualMesh_Normals(const R3MjcfVisualMesh *visual, #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) /** * Copies a NUL-terminated texture path, or an empty string for untextured meshes. + * @see @ref output_buffers + * @ingroup robotics */ RAPIER_API RAPIER_CALL size_t r3MjcfVisualMesh_Texture(const R3MjcfVisualMesh *visual, @@ -8117,44 +15422,53 @@ size_t r3MjcfVisualMesh_Texture(const R3MjcfVisualMesh *visual, #endif /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the rigid body world-space pose. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R3Pose r3RigidBody_Position(struct R3RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the rigid body world-space translation. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R3Vector r3RigidBody_Translation(struct R3RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the rigid body world-space linear velocity. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R3Vector r3RigidBody_Linvel(struct R3RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the rigid body world-space angular velocity (radians per second). + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3AngVector r3RigidBody_Angvel(struct R3RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return whether the rigid body is sleeping. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsSleeping(struct R3RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return whether the rigid body is enabled. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsEnabled(struct R3RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the rigid body application-owned 128-bit user value. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R3UserData r3RigidBody_UserData(struct R3RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body world-space pose. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetPosition(struct R3RigidBodyHandle handle, @@ -8162,7 +15476,9 @@ R3Status r3RigidBody_SetPosition(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body world-space translation. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetTranslation(struct R3RigidBodyHandle handle, @@ -8170,7 +15486,9 @@ R3Status r3RigidBody_SetTranslation(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body world-space linear velocity. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetLinvel(struct R3RigidBodyHandle handle, @@ -8178,7 +15496,9 @@ R3Status r3RigidBody_SetLinvel(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body world-space angular velocity (radians per second). + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetAngvel(struct R3RigidBodyHandle handle, @@ -8186,21 +15506,25 @@ R3Status r3RigidBody_SetAngvel(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body next kinematic world-space pose. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetNextKinematicPosition(struct R3RigidBodyHandle handle, struct R3Pose value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body next kinematic world-space translation. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetNextKinematicTranslation(struct R3RigidBodyHandle handle, struct R3Vector value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body gravity multiplier. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetGravityScale(struct R3RigidBodyHandle handle, @@ -8208,35 +15532,41 @@ R3Status r3RigidBody_SetGravityScale(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body linear damping coefficient. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetLinearDamping(struct R3RigidBodyHandle handle, R3Real value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body angular damping coefficient. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetAngularDamping(struct R3RigidBodyHandle handle, R3Real value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Enable or disable the rigid body. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetEnabled(struct R3RigidBodyHandle handle, R3Bool value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the rigid body application-owned 128-bit user value. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetUserData(struct R3RigidBodyHandle handle, struct R3UserData value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Apply a world-space linear impulse. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_ApplyImpulse(struct R3RigidBodyHandle handle, @@ -8244,7 +15574,9 @@ R3Status r3RigidBody_ApplyImpulse(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Apply a world-space impulse at a world-space point. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_ApplyImpulseAtPoint(struct R3RigidBodyHandle handle, @@ -8253,7 +15585,9 @@ R3Status r3RigidBody_ApplyImpulseAtPoint(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Accumulate a world-space force; it persists until reset. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_AddForce(struct R3RigidBodyHandle handle, @@ -8261,106 +15595,125 @@ R3Status r3RigidBody_AddForce(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Clear accumulated user forces. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_ResetForces(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Put the body to sleep. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_Sleep(struct R3RigidBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the collider world-space pose. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3Pose r3Collider_Position(struct R3ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the collider world-space translation. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3Vector r3Collider_Translation(struct R3ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the collider friction coefficient. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Real r3Collider_Friction(struct R3ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the collider restitution coefficient. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Real r3Collider_Restitution(struct R3ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return whether the collider is a sensor (detects overlaps without contact forces). + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Bool r3Collider_IsSensor(struct R3ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the parent body handle, or an invalid handle with OK status for a standalone collider. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3RigidBodyHandle r3Collider_Parent(struct R3ColliderHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider world-space pose. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetPosition(struct R3ColliderHandle handle, struct R3Pose value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider world-space translation. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetTranslation(struct R3ColliderHandle handle, struct R3Vector value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider friction coefficient. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetFriction(struct R3ColliderHandle handle, R3Real value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider restitution coefficient. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetRestitution(struct R3ColliderHandle handle, R3Real value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Enable or disable a sensor (detects overlaps without contact forces) for the collider. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetSensor(struct R3ColliderHandle handle, R3Bool value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider collision filtering groups. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetCollisionGroups(struct R3ColliderHandle handle, struct R3InteractionGroups value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the collider application-owned 128-bit user value. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetUserData(struct R3ColliderHandle handle, struct R3UserData value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return the world-space position of the indexed particle. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3Vector r3SoftBody_ParticlePosition(struct R3SoftBodyHandle handle, size_t index); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Copy world-space particle positions. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_ParticlePositions(struct R3SoftBodyHandle handle, @@ -8368,13 +15721,15 @@ size_t r3SoftBody_ParticlePositions(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Return a copy of the soft body material parameters. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyMaterial r3SoftBody_Material(struct R3SoftBodyHandle handle); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Set the world-space position of the indexed particle. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetParticlePosition(struct R3SoftBodyHandle handle, @@ -8382,14 +15737,17 @@ R3Status r3SoftBody_SetParticlePosition(struct R3SoftBodyHandle handle, struct R3Vector value); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Copy material parameters into the soft body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetMaterial(struct R3SoftBodyHandle handle, const struct R3SoftBodyMaterial *data); /** - * Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. + * Add a world-space force to the indexed particle. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_AddParticleForce(struct R3SoftBodyHandle handle, @@ -8400,7 +15758,8 @@ R3Status r3SoftBody_AddParticleForce(struct R3SoftBodyHandle handle, /** * Copies states in the same order as handles, without allocating temporary storage. * All handles are validated before writing. On INVALID_HANDLE outputs are unchanged. - * NULL/0 is a size query. BUFFER_TOO_SMALL updates count but leaves states untouched. + * NULL/0 is a size query. BUFFER_TOO_SMALL returns the required count and leaves states untouched. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL size_t r3RigidBodyReadStates(const struct R3World *world, @@ -8411,12 +15770,15 @@ size_t r3RigidBodyReadStates(const struct R3World *world, /** * Copies joint configuration without returning a borrowed joint pointer. + * @ingroup joints */ RAPIER_API RAPIER_CALL struct R3JointDesc r3ImpulseJoint_Desc(struct R3ImpulseJointHandle handle); /** * Replaces configuration after validation, resetting cached limit/motor impulses. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetDesc(struct R3ImpulseJointHandle handle, @@ -8427,6 +15789,7 @@ R3Status r3ImpulseJoint_SetDesc(struct R3ImpulseJointHandle handle, * Replace the shape geometry with a borrowed tri mesh. Counts are elements. * Copies no arrays. Invalid view metadata leaves the description unchanged. * Geometry and flags are validated when the description is built or inserted. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3Status r3ShapeDesc_SetTrimesh(struct R3ShapeDesc *desc, @@ -8438,6 +15801,7 @@ R3Status r3ShapeDesc_SetTrimesh(struct R3ShapeDesc *desc, * Replace the shape geometry with a borrowed polyline. Counts are elements. * Copies no arrays. Invalid view metadata leaves the description unchanged. * Geometry and flags are validated when the description is built or inserted. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3Status r3ShapeDesc_SetPolyline(struct R3ShapeDesc *desc, @@ -8447,6 +15811,7 @@ R3Status r3ShapeDesc_SetPolyline(struct R3ShapeDesc *desc, /** * Replace the shape geometry with a borrowed convex hull point cloud. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3Status r3ShapeDesc_SetConvexHull(struct R3ShapeDesc *desc, @@ -8454,6 +15819,7 @@ R3Status r3ShapeDesc_SetConvexHull(struct R3ShapeDesc *desc, /** * Select an explicit particle recipe and borrow its positions. Other fields are preserved. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetParticles(struct R3SoftBodyDesc *desc, @@ -8461,6 +15827,7 @@ R3Status r3SoftBodyDesc_SetParticles(struct R3SoftBodyDesc *desc, /** * Select a surface recipe and borrow its vertices and elements. Other fields are preserved. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetSurfaceMesh(struct R3SoftBodyDesc *desc, @@ -8469,6 +15836,7 @@ R3Status r3SoftBodyDesc_SetSurfaceMesh(struct R3SoftBodyDesc *desc, /** * Borrow skin geometry. Other fields, including skinCollision, are preserved. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetSkin(struct R3SoftBodyDesc *desc, @@ -8477,8 +15845,10 @@ R3Status r3SoftBodyDesc_SetSkin(struct R3SoftBodyDesc *desc, /** * Borrow masses; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetMasses(struct R3SoftBodyDesc *desc, @@ -8486,8 +15856,10 @@ R3Status r3SoftBodyDesc_SetMasses(struct R3SoftBodyDesc *desc, /** * Borrow pinned particles; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetPinnedParticles(struct R3SoftBodyDesc *desc, @@ -8495,8 +15867,10 @@ R3Status r3SoftBodyDesc_SetPinnedParticles(struct R3SoftBodyDesc *desc, /** * Borrow edges; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetEdges(struct R3SoftBodyDesc *desc, @@ -8504,8 +15878,10 @@ R3Status r3SoftBodyDesc_SetEdges(struct R3SoftBodyDesc *desc, /** * Borrow bend edges; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetBendEdges(struct R3SoftBodyDesc *desc, @@ -8513,8 +15889,10 @@ R3Status r3SoftBodyDesc_SetBendEdges(struct R3SoftBodyDesc *desc, /** * Borrow cells; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetCells(struct R3SoftBodyDesc *desc, @@ -8522,8 +15900,10 @@ R3Status r3SoftBodyDesc_SetCells(struct R3SoftBodyDesc *desc, /** * Borrow surface; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetSurface(struct R3SoftBodyDesc *desc, @@ -8531,8 +15911,10 @@ R3Status r3SoftBodyDesc_SetSurface(struct R3SoftBodyDesc *desc, /** * Borrow tension only edges; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetTensionOnlyEdges(struct R3SoftBodyDesc *desc, @@ -8541,8 +15923,10 @@ R3Status r3SoftBodyDesc_SetTensionOnlyEdges(struct R3SoftBodyDesc *desc, #if defined(RAPIER_DIM3) /** * Borrow dihedrals; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetDihedrals(struct R3SoftBodyDesc *desc, @@ -8552,8 +15936,10 @@ R3Status r3SoftBodyDesc_SetDihedrals(struct R3SoftBodyDesc *desc, #if defined(RAPIER_DIM3) /** * Borrow wire; preserve all other fields. No allocation or element reads. - * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. + * Zero counts retain the recipe's generated defaults at insertion, as with directly assigned + * views. * Invalid view metadata leaves the description unchanged. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBodyDesc_SetWire(struct R3SoftBodyDesc *desc, @@ -8561,14 +15947,18 @@ R3Status r3SoftBodyDesc_SetWire(struct R3SoftBodyDesc *desc, #endif /** + * Return a rounded box description; half_extents exclude the added border_radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3RoundCuboidColliderDesc(struct R3Vector half_extents, R3Real border_radius); /** + * Return a capsule description with segment endpoints a/b and the supplied radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3CapsuleColliderDesc(struct R3Vector a, @@ -8576,14 +15966,18 @@ struct R3ColliderDesc r3CapsuleColliderDesc(struct R3Vector a, R3Real radius); /** + * Return a segment description with endpoints a and b. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3SegmentColliderDesc(struct R3Vector a, struct R3Vector b); /** + * Return a triangle description with vertices a, b, and c. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3TriangleColliderDesc(struct R3Vector a, @@ -8591,13 +15985,17 @@ struct R3ColliderDesc r3TriangleColliderDesc(struct R3Vector a, struct R3Vector c); /** + * Return a half-space description bounded by a plane through the origin; normal points outward. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3HalfspaceColliderDesc(struct R3Vector normal); #if defined(RAPIER_DIM3) /** + * Return a Y-aligned cylinder description with the supplied half-height and radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3CylinderColliderDesc(R3Real half_height, @@ -8606,7 +16004,9 @@ struct R3ColliderDesc r3CylinderColliderDesc(R3Real half_height, #if defined(RAPIER_DIM3) /** + * Return a Y-aligned cone description with the supplied half-height and base radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3ConeColliderDesc(R3Real half_height, @@ -8615,7 +16015,9 @@ struct R3ColliderDesc r3ConeColliderDesc(R3Real half_height, #if defined(RAPIER_DIM3) /** + * Return a rounded Y-aligned cylinder description; dimensions exclude border_radius. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3RoundCylinderColliderDesc(R3Real half_height, @@ -8624,14 +16026,18 @@ struct R3ColliderDesc r3RoundCylinderColliderDesc(R3Real half_height, #endif /** + * Return a X-aligned capsule description; half_height is half the segment length, excluding caps. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3CapsuleXColliderDesc(R3Real half_height, R3Real radius); /** + * Return a Y-aligned capsule description; half_height is half the segment length, excluding caps. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3CapsuleYColliderDesc(R3Real half_height, @@ -8639,7 +16045,9 @@ struct R3ColliderDesc r3CapsuleYColliderDesc(R3Real half_height, #if defined(RAPIER_DIM3) /** + * Return a Z-aligned capsule description; half_height is half the segment length, excluding caps. * Returns a description without allocating or validating. Build/insert validates its fields. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3CapsuleZColliderDesc(R3Real half_height, @@ -8647,7 +16055,9 @@ struct R3ColliderDesc r3CapsuleZColliderDesc(R3Real half_height, #endif /** + * Return a rope recipe with particles evenly spaced from a to b, including both endpoints. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyDesc r3RopeSoftBodyDesc(struct R3Vector a, @@ -8656,7 +16066,9 @@ struct R3SoftBodyDesc r3RopeSoftBodyDesc(struct R3Vector a, #if defined(RAPIER_DIM2) /** + * Return a solid rectangle recipe on an nx by ny particle grid. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyDesc r3GridSoftBodyDesc(struct R3Vector center, @@ -8667,7 +16079,9 @@ struct R3SoftBodyDesc r3GridSoftBodyDesc(struct R3Vector center, #if defined(RAPIER_DIM3) /** + * Return a solid box recipe on an nx by ny by nz particle grid, subdivided into tetrahedra. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyDesc r3CuboidSoftBodyDesc(struct R3Vector center, @@ -8679,7 +16093,9 @@ struct R3SoftBodyDesc r3CuboidSoftBodyDesc(struct R3Vector center, #if defined(RAPIER_DIM3) /** + * Return a cloth recipe with nu by nv particles at origin + i * du + j * dv. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyDesc r3ClothSoftBodyDesc(struct R3Vector origin, @@ -8691,7 +16107,10 @@ struct R3SoftBodyDesc r3ClothSoftBodyDesc(struct R3Vector origin, #if defined(RAPIER_DIM2) /** + * Return a closed regular polygon recipe with the specified boundary particle count and area + * preservation. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyDesc r3DiskSoftBodyDesc(struct R3Vector center, @@ -8701,7 +16120,9 @@ struct R3SoftBodyDesc r3DiskSoftBodyDesc(struct R3Vector center, #if defined(RAPIER_DIM3) /** + * Return a hollow icosphere recipe with the specified refinement levels and volume preservation. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyDesc r3SphereSoftBodyDesc(struct R3Vector center, @@ -8711,7 +16132,10 @@ struct R3SoftBodyDesc r3SphereSoftBodyDesc(struct R3Vector center, #if defined(RAPIER_DIM3) /** + * Return a cloth tube recipe from origin to origin + axis with num_along rings of num_around + * particles; radius varies linearly between the ends. * Initializes a recipe without allocating. Geometry is validated during preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyDesc r3ClothTubeSoftBodyDesc(struct R3Vector origin, @@ -8724,6 +16148,7 @@ struct R3SoftBodyDesc r3ClothTubeSoftBodyDesc(struct R3Vector origin, /** * Initializes a borrowed meshing recipe. Mesh generation happens on preview/insertion. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyDesc r3VolumetricSoftBodyDesc(struct R3VectorView vertices, @@ -8732,12 +16157,15 @@ struct R3SoftBodyDesc r3VolumetricSoftBodyDesc(struct R3VectorView vertices, /** * Returns a material with the same softness for each constraint family. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyMaterial r3UniformSoftBodyMaterial(struct R3SpringCoefficients value); /** * Copies generated particle positions into caller-owned storage; no persistent builder. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBodyDesc_ParticlePositions(const struct R3SoftBodyDesc *desc, @@ -8746,6 +16174,8 @@ size_t r3SoftBodyDesc_ParticlePositions(const struct R3SoftBodyDesc *desc, /** * Copies generated cell indices into caller-owned storage. Counts scalar indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBodyDesc_CellIndices(const struct R3SoftBodyDesc *desc, @@ -8753,64 +16183,79 @@ size_t r3SoftBodyDesc_CellIndices(const struct R3SoftBodyDesc *desc, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return a process-local geometry identity for caching, not a serializable ID. Keep a shared-shape + * clone alive while using it as a cache key. + * @ingroup shapes */ RAPIER_API RAPIER_CALL size_t r3Collider_ShapeIdentity(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body particle count. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_NumParticles(struct R3SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return a counter that changes when particle connectivity changes; use it to invalidate mesh + * caches. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL uint32_t r3SoftBody_TopologyVersion(struct R3SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body mass. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Real r3SoftBody_Mass(struct R3SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body current volume. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Real r3SoftBody_Volume(struct R3SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body undeformed volume. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Real r3SoftBody_RestVolume(struct R3SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body target volume multiplier. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Real r3SoftBody_VolumeFactor(struct R3SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body world-space center of mass. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3Vector r3SoftBody_CenterOfMass(struct R3SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the soft body root rigid-proxy handle. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3RigidBodyHandle r3SoftBody_RootBody(struct R3SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the soft body is enabled. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Bool r3SoftBody_IsEnabled(struct R3SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the soft body is sleeping. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Bool r3SoftBody_IsSleeping(struct R3SoftBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy world-space particle velocities. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_ParticleVelocities(struct R3SoftBodyHandle handle, @@ -8818,7 +16263,9 @@ size_t r3SoftBody_ParticleVelocities(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy flattened edge vertex indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_Edges(struct R3SoftBodyHandle handle, @@ -8826,7 +16273,9 @@ size_t r3SoftBody_Edges(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy flattened cell vertex indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_Cells(struct R3SoftBodyHandle handle, @@ -8834,7 +16283,9 @@ size_t r3SoftBody_Cells(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy flattened boundary element indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_Boundary(struct R3SoftBodyHandle handle, @@ -8842,7 +16293,9 @@ size_t r3SoftBody_Boundary(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy piece identifiers. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_Pieces(struct R3SoftBodyHandle handle, @@ -8850,7 +16303,8 @@ size_t r3SoftBody_Pieces(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body particle world-space velocity. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetParticleVelocity(struct R3SoftBodyHandle handle, @@ -8858,7 +16312,8 @@ R3Status r3SoftBody_SetParticleVelocity(struct R3SoftBodyHandle handle, struct R3Vector value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the next world-space target position of a pinned particle. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetParticleKinematicTarget(struct R3SoftBodyHandle handle, @@ -8866,7 +16321,8 @@ R3Status r3SoftBody_SetParticleKinematicTarget(struct R3SoftBodyHandle handle, struct R3Vector value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable pinning the particle for the soft body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetParticlePinned(struct R3SoftBodyHandle handle, @@ -8874,7 +16330,9 @@ R3Status r3SoftBody_SetParticlePinned(struct R3SoftBodyHandle handle, R3Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Apply a world-space impulse to one particle. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_ApplyParticleImpulse(struct R3SoftBodyHandle handle, @@ -8883,7 +16341,9 @@ R3Status r3SoftBody_ApplyParticleImpulse(struct R3SoftBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Accumulate a world-space force; it persists until reset. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_AddForce(struct R3SoftBodyHandle handle, @@ -8891,7 +16351,9 @@ R3Status r3SoftBody_AddForce(struct R3SoftBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Apply a world-space linear impulse. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_ApplyImpulse(struct R3SoftBodyHandle handle, @@ -8899,28 +16361,33 @@ R3Status r3SoftBody_ApplyImpulse(struct R3SoftBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Clear accumulated user forces. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_ResetForces(struct R3SoftBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable the soft body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetEnabled(struct R3SoftBodyHandle handle, R3Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body target volume multiplier. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetVolumeFactor(struct R3SoftBodyHandle handle, R3Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Attach a particle to a rigid body at the supplied body-local anchor. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_AttachParticle(struct R3SoftBodyHandle handle, @@ -8928,14 +16395,17 @@ R3Status r3SoftBody_AttachParticle(struct R3SoftBodyHandle handle, struct R3RigidBodyHandle rigid_body); /** - * Resolves the generational handle for this call; rejects stale handles. + * Remove a particle attachment to a rigid body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_DetachParticle(struct R3SoftBodyHandle handle, size_t index); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy cluster indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_Clusters(struct R3SoftBodyHandle handle, @@ -8943,14 +16413,17 @@ size_t r3SoftBody_Clusters(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid proxy for the selected cluster. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3RigidBodyHandle r3SoftBody_ClusterProxy(struct R3SoftBodyHandle handle, uint32_t cluster); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy particle indices for a cluster. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_ClusterParticles(struct R3SoftBodyHandle handle, @@ -8959,7 +16432,8 @@ size_t r3SoftBody_ClusterParticles(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable pinning the cluster for the soft body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetClusterPinned(struct R3SoftBodyHandle handle, @@ -8967,7 +16441,8 @@ R3Status r3SoftBody_SetClusterPinned(struct R3SoftBodyHandle handle, R3Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the next world-space target pose of a pinned cluster. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetClusterKinematicTarget(struct R3SoftBodyHandle handle, @@ -8975,7 +16450,8 @@ R3Status r3SoftBody_SetClusterKinematicTarget(struct R3SoftBodyHandle handle, struct R3Pose value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable using cluster shape matching for the soft body. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetClusterShapeMatchingEnabled(struct R3SoftBodyHandle handle, @@ -8983,7 +16459,8 @@ R3Status r3SoftBody_SetClusterShapeMatchingEnabled(struct R3SoftBodyHandle handl R3Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body cluster shape-matching stiffness multiplier. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetClusterStiffnessScale(struct R3SoftBodyHandle handle, @@ -8991,7 +16468,8 @@ R3Status r3SoftBody_SetClusterStiffnessScale(struct R3SoftBodyHandle handle, R3Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body cluster tear-resistance multiplier. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetClusterTearResistance(struct R3SoftBodyHandle handle, @@ -8999,7 +16477,9 @@ R3Status r3SoftBody_SetClusterTearResistance(struct R3SoftBodyHandle handle, R3Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy collision mesh metadata. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_Meshes(struct R3SoftBodyHandle handle, @@ -9007,7 +16487,9 @@ size_t r3SoftBody_Meshes(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy world-space vertices for a mesh ID. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_MeshVerticesById(struct R3SoftBodyHandle handle, @@ -9016,7 +16498,9 @@ size_t r3SoftBody_MeshVerticesById(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy flattened indices for a mesh ID. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_MeshIndicesById(struct R3SoftBodyHandle handle, @@ -9025,7 +16509,9 @@ size_t r3SoftBody_MeshIndicesById(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy collision mesh collider handles. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_MeshColliders(struct R3SoftBodyHandle handle, @@ -9033,7 +16519,9 @@ size_t r3SoftBody_MeshColliders(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy world-space collision mesh vertices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_MeshVertices(struct R3SoftBodyHandle handle, @@ -9042,7 +16530,9 @@ size_t r3SoftBody_MeshVertices(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy flattened collision mesh indices. + * @see @ref output_buffers + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_MeshIndices(struct R3SoftBodyHandle handle, @@ -9051,14 +16541,16 @@ size_t r3SoftBody_MeshIndices(struct R3SoftBodyHandle handle, size_t capacity); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return indices per collision-mesh element (2 for segments, 3 for triangles). + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL size_t r3SoftBody_MeshArity(struct R3SoftBodyHandle handle, struct R3ColliderHandle collider); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the selected collision mesh topology revision for cache invalidation. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL uint32_t r3SoftBody_MeshTopologyVersion(struct R3SoftBodyHandle handle, @@ -9066,7 +16558,8 @@ uint32_t r3SoftBody_MeshTopologyVersion(struct R3SoftBodyHandle handle, #if defined(RAPIER_FEM) /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body soft solver kind (R3_SOFT_SOLVER_*). + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetSolver(struct R3SoftBodyHandle handle, @@ -9074,7 +16567,8 @@ R3Status r3SoftBody_SetSolver(struct R3SoftBodyHandle handle, #endif /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body cluster shape-matching target pose. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetClusterShapeMatchingTarget(struct R3SoftBodyHandle handle, @@ -9082,7 +16576,8 @@ R3Status r3SoftBody_SetClusterShapeMatchingTarget(struct R3SoftBodyHandle handle const struct R3Pose *target); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the soft body edge tear-resistance multiplier. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_SetEdgeTearResistance(struct R3SoftBodyHandle handle, @@ -9090,14 +16585,17 @@ R3Status r3SoftBody_SetEdgeTearResistance(struct R3SoftBodyHandle handle, R3Real resistance); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the selected collision mesh is closed. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Bool r3SoftBody_MeshIsClosed(struct R3SoftBodyHandle handle, struct R3ColliderHandle collider); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body local mass properties added to collider contributions. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetAdditionalMassProperties(struct R3RigidBodyHandle handle, @@ -9105,26 +16603,31 @@ R3Status r3RigidBody_SetAdditionalMassProperties(struct R3RigidBodyHandle handle R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Recompute body mass and inertia from attached colliders and additional mass properties. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_RecomputeMassPropertiesFromColliders(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider local mass properties. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetMassProperties(struct R3ColliderHandle handle, struct R3MassProperties properties); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider local mass properties. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3MassProperties r3Collider_MassProperties(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body translation/rotation lock bitmask. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetLockedAxes(struct R3RigidBodyHandle handle, @@ -9132,24 +16635,28 @@ R3Status r3RigidBody_SetLockedAxes(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body translation/rotation lock bitmask. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL uint8_t r3RigidBody_LockedAxes(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the collider is a voxel shape. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Bool r3Collider_IsVoxels(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return voxel information at a flat index; found = 0 if absent. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3VoxelQuery r3Collider_VoxelAtFlatId(struct R3ColliderHandle handle, uint32_t id); /** - * Resolves the generational handle for this call; rejects stale handles. + * Fill or clear the voxel at key; the collider must have a voxel shape. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetVoxel(struct R3ColliderHandle handle, @@ -9157,116 +16664,139 @@ R3Status r3Collider_SetVoxel(struct R3ColliderHandle handle, R3Bool filled); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body next kinematic world-space pose. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R3Pose r3RigidBody_NextPosition(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body world-space rotation. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R3Rotation r3RigidBody_Rotation(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body world-space center of mass. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R3Vector r3RigidBody_CenterOfMass(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body body-local center of mass. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R3Vector r3RigidBody_LocalCenterOfMass(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body accumulated user-applied world-space force. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R3Vector r3RigidBody_UserForce(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body accumulated user-applied world-space torque. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3AngVector r3RigidBody_UserTorque(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body body type (R3_DYNAMIC, R3_FIXED, or a kinematic kind). + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL uint32_t r3RigidBody_BodyType(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body mass. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Real r3RigidBody_Mass(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body gravity multiplier. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Real r3RigidBody_GravityScale(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body linear damping coefficient. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Real r3RigidBody_LinearDamping(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body angular damping coefficient. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Real r3RigidBody_AngularDamping(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body kinetic energy. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Real r3RigidBody_KineticEnergy(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the rigid body soft-CCD prediction distance. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Real r3RigidBody_SoftCcdPrediction(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is using continuous collision detection. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsCcdEnabled(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is dynamic. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsDynamic(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the associated soft-body handle, or an invalid handle if this is not a soft proxy. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL struct R3SoftBodyHandle r3RigidBody_SoftBody(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is a soft-body proxy. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsSoftFrame(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is fixed. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsFixed(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is kinematic. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsKinematic(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is moving. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsMoving(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is currently using CCD for its motion. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsCcdActive(struct R3RigidBodyHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body world-space rotation. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetRotation(struct R3RigidBodyHandle handle, @@ -9274,7 +16804,9 @@ R3Status r3RigidBody_SetRotation(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body body type (R3_DYNAMIC, R3_FIXED, or a kinematic kind). + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetBodyType(struct R3RigidBodyHandle handle, @@ -9282,14 +16814,17 @@ R3Status r3RigidBody_SetBodyType(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body next kinematic world-space rotation. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetNextKinematicRotation(struct R3RigidBodyHandle handle, struct R3Rotation value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body mass added to collider contributions. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetAdditionalMass(struct R3RigidBodyHandle handle, @@ -9297,21 +16832,25 @@ R3Status r3RigidBody_SetAdditionalMass(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body soft-CCD prediction distance. + * @ingroup soft_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetSoftCcdPrediction(struct R3RigidBodyHandle handle, R3Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable using continuous collision detection for the rigid body. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetCcdEnabled(struct R3RigidBodyHandle handle, R3Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable locking translation for the rigid body. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetTranslationsLocked(struct R3RigidBodyHandle handle, @@ -9319,7 +16858,9 @@ R3Status r3RigidBody_SetTranslationsLocked(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable locking rotation for the rigid body. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetRotationsLocked(struct R3RigidBodyHandle handle, @@ -9327,28 +16868,33 @@ R3Status r3RigidBody_SetRotationsLocked(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body signed dominance group. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetDominanceGroup(struct R3RigidBodyHandle handle, int8_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body additional solver iterations for connected bodies. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetAdditionalSolverIterations(struct R3RigidBodyHandle handle, size_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the rigid body additional PGS iterations. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetAdditionalPgsIterations(struct R3RigidBodyHandle handle, size_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Accumulate a world-space torque; it persists until reset. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_AddTorque(struct R3RigidBodyHandle handle, @@ -9356,7 +16902,9 @@ R3Status r3RigidBody_AddTorque(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Apply a world-space angular impulse. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_ApplyTorqueImpulse(struct R3RigidBodyHandle handle, @@ -9364,7 +16912,9 @@ R3Status r3RigidBody_ApplyTorqueImpulse(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Accumulate a world-space force applied at a world-space point. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_AddForceAtPoint(struct R3RigidBodyHandle handle, @@ -9373,21 +16923,26 @@ R3Status r3RigidBody_AddForceAtPoint(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Clear accumulated user torques. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_ResetTorques(struct R3RigidBodyHandle handle, R3Bool wake_up); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return world-space velocity at a world-space point, including angular motion. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL struct R3Vector r3RigidBody_VelocityAtPoint(struct R3RigidBodyHandle handle, struct R3Vector point); /** - * Resolves the generational handle for this call; rejects stale handles. + * Copy attached collider handles. + * @see @ref output_buffers + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL size_t r3RigidBody_Colliders(struct R3RigidBodyHandle handle, @@ -9396,7 +16951,8 @@ size_t r3RigidBody_Colliders(struct R3RigidBodyHandle handle, #if defined(RAPIER_DIM3) /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the rigid body is using gyroscopic forces. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Bool r3RigidBody_GyroscopicForcesEnabled(struct R3RigidBodyHandle handle); @@ -9404,7 +16960,8 @@ R3Bool r3RigidBody_GyroscopicForcesEnabled(struct R3RigidBodyHandle handle); #if defined(RAPIER_DIM3) /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable using gyroscopic forces for the rigid body. + * @ingroup rigid_bodies */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_SetGyroscopicForcesEnabled(struct R3RigidBodyHandle handle, @@ -9412,294 +16969,463 @@ R3Status r3RigidBody_SetGyroscopicForcesEnabled(struct R3RigidBodyHandle handle, #endif /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider mass per unit volume. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetDensity(struct R3ColliderHandle handle, R3Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider mass. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetMass(struct R3ColliderHandle handle, R3Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Enable or disable the collider. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetEnabled(struct R3ColliderHandle handle, R3Bool value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider contact-force filtering groups. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetSolverGroups(struct R3ColliderHandle handle, struct R3InteractionGroups value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider friction combination rule (R3_COMBINE_*). + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetFrictionCombineRule(struct R3ColliderHandle handle, uint32_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider restitution combination rule (R3_COMBINE_*). + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetRestitutionCombineRule(struct R3ColliderHandle handle, uint32_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider extra separation skin around the shape. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetContactSkin(struct R3ColliderHandle handle, R3Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider force threshold for contact-force events. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetContactForceEventThreshold(struct R3ColliderHandle handle, R3Real value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider event-generation bitmask (R3_COLLISION_EVENTS and R3_CONTACT_FORCE_EVENTS). + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetActiveEvents(struct R3ColliderHandle handle, uint32_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider physics-hook activation bitmask. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetActiveHooks(struct R3ColliderHandle handle, uint32_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider body-type collision activation bitmask. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetActiveCollisionTypes(struct R3ColliderHandle handle, uint16_t value); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider world-space rotation. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3Rotation r3Collider_Rotation(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider collision filtering groups. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3InteractionGroups r3Collider_CollisionGroups(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider contact-force filtering groups. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3InteractionGroups r3Collider_SolverGroups(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider application-owned 128-bit user value. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3UserData r3Collider_UserData(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider event-generation bitmask (R3_COLLISION_EVENTS and + * R3_CONTACT_FORCE_EVENTS). + * @ingroup colliders */ RAPIER_API RAPIER_CALL uint32_t r3Collider_ActiveEvents(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider mass. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Real r3Collider_Mass(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider mass per unit volume. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Real r3Collider_Density(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider current volume. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Real r3Collider_Volume(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider extra separation skin around the shape. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Real r3Collider_ContactSkin(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the collider force threshold for contact-force events. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Real r3Collider_ContactForceEventThreshold(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return whether the collider is enabled. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Bool r3Collider_IsEnabled(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return the current world-space axis-aligned bounds. + * @ingroup colliders */ RAPIER_API RAPIER_CALL struct R3Aabb r3Collider_ComputeAabb(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Return an owned wrapper sharing the collider geometry. Release with r3FreeSharedShape. * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3Collider_CloneShape(struct R3ColliderHandle handle); /** - * Resolves the generational handle for this call; rejects stale handles. + * Replace collider geometry by sharing shape; the supplied wrapper is not consumed. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetShape(struct R3ColliderHandle handle, const R3SharedShape *shape); /** - * Resolves the generational handle for this call; rejects stale handles. + * Set the collider pose relative to the parent rigid body. + * @ingroup colliders */ RAPIER_API RAPIER_CALL R3Status r3Collider_SetPositionWrtParent(struct R3ColliderHandle handle, struct R3Pose value); +/** + * Validate the index and generation in the live owning world. Cannot detect a world pointer that + * has already been freed. + * @ingroup rigid_bodies + */ RAPIER_API RAPIER_CALL R3Status r3RigidBody_ValidateHandle(struct R3RigidBodyHandle handle); +/** + * Validate the index and generation in the live owning world. Cannot detect a world pointer that + * has already been freed. + * @ingroup colliders + */ RAPIER_API RAPIER_CALL R3Status r3Collider_ValidateHandle(struct R3ColliderHandle handle); +/** + * Validate the index and generation in the live owning world. Cannot detect a world pointer that + * has already been freed. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3SoftBody_ValidateHandle(struct R3SoftBodyHandle handle); +/** + * Set the joint desc joint frame relative to body 1. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetLocalFrame1(struct R3JointDesc *desc, struct R3Pose value); +/** + * Set the impulse joint joint frame relative to body 1. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetLocalFrame1(struct R3ImpulseJointHandle handle, struct R3Pose value, R3Bool wake_up); +/** + * Set the joint desc joint frame relative to body 2. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetLocalFrame2(struct R3JointDesc *desc, struct R3Pose value); +/** + * Set the impulse joint joint frame relative to body 2. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetLocalFrame2(struct R3ImpulseJointHandle handle, struct R3Pose value, R3Bool wake_up); +/** + * Set the joint desc joint anchor relative to body 1. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetLocalAnchor1(struct R3JointDesc *desc, struct R3Vector value); +/** + * Set the impulse joint joint anchor relative to body 1. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetLocalAnchor1(struct R3ImpulseJointHandle handle, struct R3Vector value, R3Bool wake_up); +/** + * Set the joint desc joint anchor relative to body 2. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetLocalAnchor2(struct R3JointDesc *desc, struct R3Vector value); +/** + * Set the impulse joint joint anchor relative to body 2. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetLocalAnchor2(struct R3ImpulseJointHandle handle, struct R3Vector value, R3Bool wake_up); +/** + * Enable or disable allowing contacts between connected bodies for the joint desc. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetContactsEnabled(struct R3JointDesc *desc, R3Bool value); +/** + * Enable or disable allowing contacts between connected bodies for the impulse joint. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetContactsEnabled(struct R3ImpulseJointHandle handle, R3Bool value, R3Bool wake_up); +/** + * Enable or disable the joint desc. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetEnabled(struct R3JointDesc *desc, R3Bool value); +/** + * Enable or disable the impulse joint. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetEnabled(struct R3ImpulseJointHandle handle, R3Bool value, R3Bool wake_up); +/** + * Set the joint desc joint spring coefficients. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetSoftness(struct R3JointDesc *desc, struct R3SpringCoefficients value); +/** + * Set the impulse joint joint spring coefficients. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup soft_bodies + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetSoftness(struct R3ImpulseJointHandle handle, struct R3SpringCoefficients value, R3Bool wake_up); +/** + * Set the joint desc translation/rotation lock bitmask. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetLockedAxes(struct R3JointDesc *desc, uint8_t value); +/** + * Set the impulse joint translation/rotation lock bitmask. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetLockedAxes(struct R3ImpulseJointHandle handle, uint8_t value, R3Bool wake_up); +/** + * Set the joint desc joint axis mask with limits enabled. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetLimitAxes(struct R3JointDesc *desc, uint8_t value); +/** + * Set the impulse joint joint axis mask with limits enabled. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetLimitAxes(struct R3ImpulseJointHandle handle, uint8_t value, R3Bool wake_up); +/** + * Set the joint desc joint axis mask with motors enabled. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetMotorAxes(struct R3JointDesc *desc, uint8_t value); +/** + * Set the impulse joint joint axis mask with motors enabled. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetMotorAxes(struct R3ImpulseJointHandle handle, uint8_t value, R3Bool wake_up); +/** + * Set the joint desc coupled joint axis mask. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetCoupledAxes(struct R3JointDesc *desc, uint8_t value); +/** + * Set the impulse joint coupled joint axis mask. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetCoupledAxes(struct R3ImpulseJointHandle handle, uint8_t value, R3Bool wake_up); +/** + * Set the joint desc joint principal axis in body 1 local coordinates. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetLocalAxis1(struct R3JointDesc *desc, struct R3Vector value); +/** + * Set the impulse joint joint principal axis in body 1 local coordinates. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetLocalAxis1(struct R3ImpulseJointHandle handle, struct R3Vector value, R3Bool wake_up); +/** + * Set the joint desc joint principal axis in body 2 local coordinates. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetLocalAxis2(struct R3JointDesc *desc, struct R3Vector value); +/** + * Set the impulse joint joint principal axis in body 2 local coordinates. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetLocalAxis2(struct R3ImpulseJointHandle handle, struct R3Vector value, R3Bool wake_up); +/** + * Set the joint desc minimum and maximum limits on an axis (linear distance or angular radians). + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetLimits(struct R3JointDesc *desc, uint32_t joint_axis, R3Real min, R3Real max); +/** + * Set the impulse joint minimum and maximum limits on an axis (linear distance or angular + * radians). + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetLimits(struct R3ImpulseJointHandle handle, uint32_t joint_axis, @@ -9707,6 +17433,10 @@ R3Status r3ImpulseJoint_SetLimits(struct R3ImpulseJointHandle handle, R3Real max, R3Bool wake_up); +/** + * Set the joint desc motor position/velocity targets and spring coefficients on an axis. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetMotor(struct R3JointDesc *desc, uint32_t joint_axis, @@ -9715,6 +17445,11 @@ R3Status r3JointDesc_SetMotor(struct R3JointDesc *desc, R3Real stiffness, R3Real damping); +/** + * Set the impulse joint motor position/velocity targets and spring coefficients on an axis. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetMotor(struct R3ImpulseJointHandle handle, uint32_t joint_axis, @@ -9724,37 +17459,68 @@ R3Status r3ImpulseJoint_SetMotor(struct R3ImpulseJointHandle handle, R3Real damping, R3Bool wake_up); +/** + * Set the joint desc maximum motor force or torque on an axis. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetMotorMaxForce(struct R3JointDesc *desc, uint32_t joint_axis, R3Real max_force); +/** + * Set the impulse joint maximum motor force or torque on an axis. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetMotorMaxForce(struct R3ImpulseJointHandle handle, uint32_t joint_axis, R3Real max_force, R3Bool wake_up); +/** + * Set the joint desc motor model on an axis (0 = acceleration-based, 1 = force-based). + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetMotorModel(struct R3JointDesc *desc, uint32_t joint_axis, uint32_t model); +/** + * Set the impulse joint motor model on an axis (0 = acceleration-based, 1 = force-based). + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetMotorModel(struct R3ImpulseJointHandle handle, uint32_t joint_axis, uint32_t model, R3Bool wake_up); +/** + * Set the joint desc application-owned 128-bit user value. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetUserData(struct R3JointDesc *desc, struct R3UserData value); +/** + * Set the impulse joint application-owned 128-bit user value. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetUserData(struct R3ImpulseJointHandle handle, struct R3UserData value, R3Bool wake_up); +/** + * Set the joint desc motor position target and spring coefficients on an axis. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetMotorPosition(struct R3JointDesc *desc, uint32_t joint_axis, @@ -9762,6 +17528,11 @@ R3Status r3JointDesc_SetMotorPosition(struct R3JointDesc *desc, R3Real stiffness, R3Real damping); +/** + * Set the impulse joint motor position target and spring coefficients on an axis. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetMotorPosition(struct R3ImpulseJointHandle handle, uint32_t joint_axis, @@ -9770,12 +17541,21 @@ R3Status r3ImpulseJoint_SetMotorPosition(struct R3ImpulseJointHandle handle, R3Real damping, R3Bool wake_up); +/** + * Set the joint desc motor velocity target and damping factor on an axis. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3JointDesc_SetMotorVelocity(struct R3JointDesc *desc, uint32_t joint_axis, R3Real target_velocity, R3Real factor); +/** + * Set the impulse joint motor velocity target and damping factor on an axis. + * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. + * @ingroup joints + */ RAPIER_API RAPIER_CALL R3Status r3ImpulseJoint_SetMotorVelocity(struct R3ImpulseJointHandle handle, uint32_t joint_axis, @@ -9784,21 +17564,30 @@ R3Status r3ImpulseJoint_SetMotorVelocity(struct R3ImpulseJointHandle handle, R3Bool wake_up); /** + * Create an owned compound shape by convex decomposition of the input surface. Release it with + * r3FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3ConvexDecompositionSharedShape(struct R3VectorView vertices, R3SurfaceElementView indices); /** + * Create an owned voxel shape by quantizing points with the supplied per-axis voxel size. Release + * it with r3FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3VoxelsSharedShapeFromPoints(struct R3Vector voxel_size, struct R3VectorView points); /** + * Create an owned voxel shape from a surface mesh with the supplied uniform voxel size. Release it + * with r3FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3VoxelizedMeshSharedShape(struct R3VectorView vertices, @@ -9806,19 +17595,26 @@ R3SharedShape *r3VoxelizedMeshSharedShape(struct R3VectorView vertices, R3Real voxel_size); /** + * Create an owned convex hull of the supplied vertices. Release it with r3FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3ConvexHullSharedShape(struct R3VectorView vertices); /** + * Create an owned triangle mesh from vertices and triangle indices. Release it with + * r3FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3TrimeshSharedShape(struct R3VectorView vertices, struct R3TriangleView indices); /** + * Create an owned polyline from vertices and edge indices. Release it with r3FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3PolylineSharedShape(struct R3VectorView vertices, @@ -9826,7 +17622,10 @@ R3SharedShape *r3PolylineSharedShape(struct R3VectorView vertices, #if defined(RAPIER_DIM2) /** + * Create an owned oriented 2D polyline from vertices and edge indices. Release it with + * r3FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3OrientedPolylineSharedShape(struct R3VectorView vertices, @@ -9835,21 +17634,29 @@ R3SharedShape *r3OrientedPolylineSharedShape(struct R3VectorView vertices, #if defined(RAPIER_DIM2) /** + * Create an owned convex polygon from vertices already ordered along its boundary. Release it with + * r3FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3ConvexPolylineSharedShape(struct R3VectorView vertices); #endif /** + * Create an owned round convex hull shape. Release it with r3FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3RoundConvexHullSharedShape(struct R3VectorView vertices, R3Real border_radius); /** + * Create an owned triangle mesh with the supplied TRIMESH_* processing flags. Release it with + * r3FreeSharedShape. * Copies typed input geometry into an owned shared shape; arrays may be released on return. + * @ingroup shapes */ RAPIER_API RAPIER_CALL R3SharedShape *r3TrimeshSharedShapeWithFlags(struct R3VectorView vertices, @@ -9858,17 +17665,21 @@ R3SharedShape *r3TrimeshSharedShapeWithFlags(struct R3VectorView vertices, /** * Create an owned world. Release it with FreeWorld. + * @ingroup worlds */ RAPIER_API RAPIER_CALL struct R3World *r3NewWorld(void); /** * Free a world. NULL is allowed. Rejects destruction from an active callback. * The caller must prevent other threads from starting calls during destruction. + * @ingroup worlds */ RAPIER_API RAPIER_CALL R3Status r3FreeWorld(struct R3World *world); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Compute a velocity correction from callback-visible body state, updating the PID controller + * history. The context is valid only during its callback. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3VelocityCorrection r3ReadPidController_RigidBodyCorrection(const struct R3ReadContext *context, @@ -9880,12 +17691,16 @@ struct R3VelocityCorrection r3ReadPidController_RigidBodyCorrection(const struct R3AngVector target_angvel); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the number of rigid body objects in the world. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r3ReadRigidBodyCount(const struct R3ReadContext *context); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Copy entity handles. Uses only the callback-scoped read context; never retain the context. + * @see @ref output_buffers + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r3ReadRigidBodyHandles(const struct R3ReadContext *context, @@ -9893,19 +17708,25 @@ size_t r3ReadRigidBodyHandles(const struct R3ReadContext *context, size_t capacity); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Test whether the live world contains this rigid body handle. A removed/stale handle returns + * false. Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadRigidBody_Contains(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the number of collider objects in the world. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r3ReadColliderCount(const struct R3ReadContext *context); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Copy entity handles. Uses only the callback-scoped read context; never retain the context. + * @see @ref output_buffers + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r3ReadColliderHandles(const struct R3ReadContext *context, @@ -9913,42 +17734,55 @@ size_t r3ReadColliderHandles(const struct R3ReadContext *context, size_t capacity); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Test whether the live world contains this collider handle. A removed/stale handle returns false. + * Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadCollider_Contains(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return a process-local geometry identity for caching, not a serializable ID. Keep a shared-shape + * clone alive while using it as a cache key. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r3ReadCollider_ShapeIdentity(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider local mass properties. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3MassProperties r3ReadCollider_MassProperties(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body translation/rotation lock bitmask. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL uint8_t r3ReadRigidBody_LockedAxes(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the collider is a voxel shape. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadCollider_IsVoxels(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return voxel information at a flat index; found = 0 if absent. Uses only the callback-scoped + * read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3VoxelQuery r3ReadCollider_VoxelAtFlatId(const struct R3ReadContext *context, @@ -9956,154 +17790,198 @@ struct R3VoxelQuery r3ReadCollider_VoxelAtFlatId(const struct R3ReadContext *con uint32_t id); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body next kinematic world-space pose. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Pose r3ReadRigidBody_NextPosition(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space rotation. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Rotation r3ReadRigidBody_Rotation(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space center of mass. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Vector r3ReadRigidBody_CenterOfMass(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body body-local center of mass. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Vector r3ReadRigidBody_LocalCenterOfMass(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body accumulated user-applied world-space force. Uses only the callback-scoped + * read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Vector r3ReadRigidBody_UserForce(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body accumulated user-applied world-space torque. Uses only the callback-scoped + * read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3AngVector r3ReadRigidBody_UserTorque(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body body type (R3_DYNAMIC, R3_FIXED, or a kinematic kind). Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL uint32_t r3ReadRigidBody_BodyType(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body mass. Uses only the callback-scoped read context; never retain the + * context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadRigidBody_Mass(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body gravity multiplier. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadRigidBody_GravityScale(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body linear damping coefficient. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadRigidBody_LinearDamping(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body angular damping coefficient. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadRigidBody_AngularDamping(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body kinetic energy. Uses only the callback-scoped read context; never retain + * the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadRigidBody_KineticEnergy(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body soft-CCD prediction distance. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadRigidBody_SoftCcdPrediction(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is using continuous collision detection. Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadRigidBody_IsCcdEnabled(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is dynamic. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadRigidBody_IsDynamic(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the associated soft-body handle, or an invalid handle if this is not a soft proxy. Uses + * only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3SoftBodyHandle r3ReadRigidBody_SoftBody(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is a soft-body proxy. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadRigidBody_IsSoftFrame(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is fixed. Uses only the callback-scoped read context; never retain + * the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadRigidBody_IsFixed(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is kinematic. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadRigidBody_IsKinematic(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is moving. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadRigidBody_IsMoving(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is currently using CCD for its motion. Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadRigidBody_IsCcdActive(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return world-space velocity at a world-space point, including angular motion. Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Vector r3ReadRigidBody_VelocityAtPoint(const struct R3ReadContext *context, @@ -10111,7 +17989,10 @@ struct R3Vector r3ReadRigidBody_VelocityAtPoint(const struct R3ReadContext *cont struct R3Vector point); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Copy attached collider handles. Uses only the callback-scoped read context; never retain the + * context. + * @see @ref output_buffers + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r3ReadRigidBody_Colliders(const struct R3ReadContext *context, @@ -10121,7 +18002,9 @@ size_t r3ReadRigidBody_Colliders(const struct R3ReadContext *context, #if defined(RAPIER_DIM3) /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is using gyroscopic forces. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadRigidBody_GyroscopicForcesEnabled(const struct R3ReadContext *context, @@ -10129,204 +18012,262 @@ R3Bool r3ReadRigidBody_GyroscopicForcesEnabled(const struct R3ReadContext *conte #endif /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider world-space rotation. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Rotation r3ReadCollider_Rotation(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider collision filtering groups. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3InteractionGroups r3ReadCollider_CollisionGroups(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider contact-force filtering groups. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3InteractionGroups r3ReadCollider_SolverGroups(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider application-owned 128-bit user value. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3UserData r3ReadCollider_UserData(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider event-generation bitmask (R3_COLLISION_EVENTS and + * R3_CONTACT_FORCE_EVENTS). Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL uint32_t r3ReadCollider_ActiveEvents(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider mass. Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadCollider_Mass(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider mass per unit volume. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadCollider_Density(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider current volume. Uses only the callback-scoped read context; never retain the + * context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadCollider_Volume(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider extra separation skin around the shape. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadCollider_ContactSkin(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider force threshold for contact-force events. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadCollider_ContactForceEventThreshold(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the collider is enabled. Uses only the callback-scoped read context; never retain + * the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadCollider_IsEnabled(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the current world-space axis-aligned bounds. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Aabb r3ReadCollider_ComputeAabb(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return an owned wrapper sharing the collider geometry. Release with r3FreeSharedShape. Uses + * only the callback-scoped read context; never retain the context. * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3SharedShape *r3ReadCollider_CloneShape(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Validate the index and generation in the live owning world. Cannot detect a world pointer that + * has already been freed. Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Status r3ReadRigidBody_ValidateHandle(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Validate the index and generation in the live owning world. Cannot detect a world pointer that + * has already been freed. Uses only the callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Status r3ReadCollider_ValidateHandle(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space pose. Uses only the callback-scoped read context; never retain + * the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Pose r3ReadRigidBody_Position(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space translation. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Vector r3ReadRigidBody_Translation(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space linear velocity. Uses only the callback-scoped read context; + * never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Vector r3ReadRigidBody_Linvel(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body world-space angular velocity (radians per second). Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3AngVector r3ReadRigidBody_Angvel(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is sleeping. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadRigidBody_IsSleeping(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the rigid body is enabled. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadRigidBody_IsEnabled(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the rigid body application-owned 128-bit user value. Uses only the callback-scoped read + * context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3UserData r3ReadRigidBody_UserData(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider world-space pose. Uses only the callback-scoped read context; never retain + * the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Pose r3ReadCollider_Position(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider world-space translation. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3Vector r3ReadCollider_Translation(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider friction coefficient. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadCollider_Friction(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return the collider restitution coefficient. Uses only the callback-scoped read context; never + * retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Real r3ReadCollider_Restitution(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Return whether the collider is a sensor (detects overlaps without contact forces). Uses only the + * callback-scoped read context; never retain the context. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL R3Bool r3ReadCollider_IsSensor(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Read the parent body handle during a callback; a standalone collider returns an invalid handle + * with OK status. + * @ingroup callbacks */ RAPIER_API RAPIER_CALL struct R3RigidBodyHandle r3ReadCollider_Parent(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** - * Read callback-visible state. The context is valid only until its callback returns. + * Copy callback-visible body states in the supplied handle order. All handles must belong to the + * context world. + * @see @ref output_buffers + * @ingroup callbacks */ RAPIER_API RAPIER_CALL size_t r3ReadRigidBodyReadStates(const struct R3ReadContext *context, diff --git a/c/include/rapier.hpp b/c/include/rapier.hpp index 4ca6740b1..002ddc350 100644 --- a/c/include/rapier.hpp +++ b/c/include/rapier.hpp @@ -1,3 +1,7 @@ +/** @file + * Optional C++ RAII wrappers. + * @ingroup cpp + */ #ifndef RAPIER_HPP #define RAPIER_HPP #include "rapier_helpers.h" @@ -5,7 +9,9 @@ #include #include +/** Optional C++ ownership helpers. @ingroup cpp */ namespace rapier { +/** Throw std::runtime_error with LastError when status is not OK. */ inline void check(RAPIER_TYPE(Status) status) { if (status != RAPIER_CONST(OK)) { throw std::runtime_error(std::string(RAPIER_FN(LastError)())); @@ -13,65 +19,89 @@ inline void check(RAPIER_TYPE(Status) status) { } // Use Owner only for owned pointers, never for a borrowed callback context. +/** Deleter for an owned pointer; Free must succeed at destruction time. */ template struct Deleter { + /** Release the pointer through its matching C API function. */ void operator()(T *value) const noexcept { (void)Free(value); } }; +/** Unique owner of a native pointer; never wrap a borrowed pointer. */ template using Owner = std::unique_ptr>; +/** Own a native World using its matching Free function. */ using World = Owner; +/** Own a native Shape using its matching Free function. */ using Shape = Owner; +/** Own a native EventCollector using its matching Free function. */ using EventCollector = Owner; +/** Own a native Snapshot using its matching Free function. */ using Snapshot = Owner; +/** Own a native SoftBodyTearEvent using its matching Free function. */ using SoftBodyTearEvent = Owner; +/** Own a native ShapeMesh using its matching Free function. */ using ShapeMesh = Owner; +/** Own a native KinematicCharacterController using its matching Free function. */ using KinematicCharacterController = Owner; +/** Own a native PidController using its matching Free function. */ using PidController = Owner; #if defined(RAPIER_DIM3) +/** Own a native DynamicRayCastVehicleController using its matching Free function. */ using DynamicRayCastVehicleController = Owner; +/** Own a native TriMeshData using its matching Free function. */ using TriMeshData = Owner; #endif #if defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32) +/** Own a native UrdfRobot using its matching Free function. */ using UrdfRobot = Owner; +/** Own a native UrdfRobotHandles using its matching Free function. */ using UrdfRobotHandles = Owner; +/** Own a native MjcfRobot using its matching Free function. */ using MjcfRobot = Owner; +/** Own a native MjcfRobotHandles using its matching Free function. */ using MjcfRobotHandles = Owner; #endif // These factories return ordinary values. No allocation or deleter is needed. +/** Return a POD rigid-body description with the selected body type. */ inline RAPIER_TYPE(RigidBodyDesc) rigid_body(uint32_t kind = RAPIER_CONST(DYNAMIC)) { RAPIER_TYPE(RigidBodyDesc) value = RAPIER_FN(DynamicRigidBodyDesc)(); value.bodyType = kind; return value; } +/** Return a POD ball collider description. */ inline RAPIER_TYPE(ColliderDesc) ball(RAPIER_TYPE(Real) radius) { return RAPIER_FN(BallColliderDesc)(radius); } +/** Return a POD cuboid collider description. */ inline RAPIER_TYPE(ColliderDesc) cuboid(RAPIER_TYPE(Vector) half_extents) { return RAPIER_FN(CuboidColliderDesc)(half_extents); } +/** Return native soft-body description defaults. */ inline RAPIER_TYPE(SoftBodyDesc) soft_body() { return RAPIER_FN(DefaultSoftBodyDesc)(); } +/** Return a joint description with the selected locked-axis mask. */ inline RAPIER_TYPE(JointDesc) joint(uint8_t locked_axes = 0) { RAPIER_TYPE(JointDesc) value = RAPIER_FN(DefaultJointDesc)(); value.lockedAxes = locked_axes; return value; } +/** Return query defaults with no callback or exclusions. */ inline RAPIER_TYPE(QueryOptions) queryOptions() { return RAPIER_FN(DefaultQueryOptions)(); } +/** Check the ABI and create an owned world; throw on failure. */ inline World make_world() { check(RAPIER_FN(CheckAbi)(RAPIER_CONST(ABI_VERSION), RAPIER_CONST(DIMENSION), sizeof(RAPIER_TYPE(Real)), sizeof(RAPIER_TYPE(Vector)), diff --git a/c/include/rapier_helpers.h b/c/include/rapier_helpers.h index 740ef7cce..129663033 100644 --- a/c/include/rapier_helpers.h +++ b/c/include/rapier_helpers.h @@ -1,3 +1,7 @@ +/** @file + * Explicit invalid handle values; no owned resources. + * @ingroup math + */ #ifndef RAPIER_HELPERS_H #define RAPIER_HELPERS_H #include "rapier.h" @@ -6,10 +10,15 @@ /* Explicit invalid values; zero-initialized Rapier handles are not invalid. * These constants have internal linkage and own no resources. */ +/** Invalid rigid body handle. */ static const RAPIER_TYPE(RigidBodyHandle) RAPIER_CONST(INVALID_RIGID_BODY_HANDLE) = {NULL, UINT32_MAX, UINT32_MAX}; +/** Invalid collider handle. */ static const RAPIER_TYPE(ColliderHandle) RAPIER_CONST(INVALID_COLLIDER_HANDLE) = {NULL, UINT32_MAX, UINT32_MAX}; +/** Invalid impulse joint handle. */ static const RAPIER_TYPE(ImpulseJointHandle) RAPIER_CONST(INVALID_IMPULSE_JOINT_HANDLE) = {NULL, UINT32_MAX, UINT32_MAX}; +/** Invalid multibody joint handle. */ static const RAPIER_TYPE(MultibodyJointHandle) RAPIER_CONST(INVALID_MULTIBODY_JOINT_HANDLE) = {NULL, UINT32_MAX, UINT32_MAX}; +/** Invalid soft body handle. */ static const RAPIER_TYPE(SoftBodyHandle) RAPIER_CONST(INVALID_SOFT_BODY_HANDLE) = {NULL, UINT32_MAX, UINT32_MAX}; #endif diff --git a/c/include/rapier_math.h b/c/include/rapier_math.h index 4e2a2ca93..fb2eebfea 100644 --- a/c/include/rapier_math.h +++ b/c/include/rapier_math.h @@ -1,31 +1,43 @@ +/** @file + * Inline value constructors and arithmetic; no allocation or error-state changes. + * @defgroup inline_math Inline math + * @ingroup math + * @{ + */ #ifndef RAPIER_MATH_H #define RAPIER_MATH_H #include "rapier.h" #include #if defined(RAPIER_DIM2) +/** Pi in the selected scalar precision. */ #define R2_PI ((R2Real)3.14159265358979323846) #else +/** Pi in the selected scalar precision. */ #define R3_PI ((R3Real)3.14159265358979323846) #endif /* Value constructors and arithmetic for Rapier's public C math types. */ #if defined(RAPIER_DIM2) +/** Construct a vector from its components. */ static inline RAPIER_TYPE(Vector) RAPIER_FN(Vector)(RAPIER_TYPE(Real) x, RAPIER_TYPE(Real) y) { RAPIER_TYPE(Vector) result = {x, y}; return result; } +/** Construct a 2D rotation from an angle in radians. */ static inline RAPIER_TYPE(Rotation) RAPIER_FN(Rotation)(RAPIER_TYPE(Real) angle) { RAPIER_TYPE(Rotation) result = {angle}; return result; } #else +/** Construct a vector from its components. */ static inline RAPIER_TYPE(Vector) RAPIER_FN(Vector)(RAPIER_TYPE(Real) x, RAPIER_TYPE(Real) y, RAPIER_TYPE(Real) z) { RAPIER_TYPE(Vector) result = {x, y, z}; return result; } +/** Construct a 3D unit quaternion; normalizes axis, and returns identity for a zero axis. Angle is in radians. */ static inline RAPIER_TYPE(Rotation) RAPIER_FN(RotationFromAxisAngle)(RAPIER_TYPE(Vector) axis, RAPIER_TYPE(Real) angle) { RAPIER_TYPE(Real) length = @@ -41,6 +53,7 @@ static inline RAPIER_TYPE(Rotation) RAPIER_FN(RotationFromAxisAngle)(RAPIER_TYPE } #endif +/** Return a + b. */ static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorAdd)(RAPIER_TYPE(Vector) a, RAPIER_TYPE(Vector) b) { #if defined(RAPIER_DIM2) return RAPIER_FN(Vector)(a.x + b.x, a.y + b.y); @@ -49,6 +62,7 @@ static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorAdd)(RAPIER_TYPE(Vector) a, RA #endif } +/** Return a - b. */ static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorSub)(RAPIER_TYPE(Vector) a, RAPIER_TYPE(Vector) b) { #if defined(RAPIER_DIM2) return RAPIER_FN(Vector)(a.x - b.x, a.y - b.y); @@ -57,6 +71,7 @@ static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorSub)(RAPIER_TYPE(Vector) a, RA #endif } +/** Multiply each component by scale. */ static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorScale)(RAPIER_TYPE(Vector) vector, RAPIER_TYPE(Real) scale) { #if defined(RAPIER_DIM2) return RAPIER_FN(Vector)(vector.x * scale, vector.y * scale); @@ -65,6 +80,7 @@ static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorScale)(RAPIER_TYPE(Vector) vec #endif } +/** Return the dot product. */ static inline RAPIER_TYPE(Real) RAPIER_FN(VectorDot)(RAPIER_TYPE(Vector) a, RAPIER_TYPE(Vector) b) { #if defined(RAPIER_DIM2) return a.x * b.x + a.y * b.y; @@ -72,20 +88,24 @@ static inline RAPIER_TYPE(Real) RAPIER_FN(VectorDot)(RAPIER_TYPE(Vector) a, RAPI return a.x * b.x + a.y * b.y + a.z * b.z; #endif } +/** Return the Euclidean length. */ static inline RAPIER_TYPE(Real) RAPIER_FN(VectorLength)(RAPIER_TYPE(Vector) vector) { return (RAPIER_TYPE(Real))sqrt(RAPIER_FN(VectorDot)(vector, vector)); } +/** Normalize a nonzero vector; a zero vector is returned unchanged. */ static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorNormalize)(RAPIER_TYPE(Vector) vector) { RAPIER_TYPE(Real) length = RAPIER_FN(VectorLength)(vector); return length > 0 ? RAPIER_FN(VectorScale)(vector, 1 / length) : vector; } #if defined(RAPIER_DIM3) +/** Return the 3D cross product a x b. */ static inline RAPIER_TYPE(Vector) RAPIER_FN(VectorCross)(RAPIER_TYPE(Vector) a, RAPIER_TYPE(Vector) b) { return RAPIER_FN(Vector)(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); } #endif +/** Compose rotations, applying b then a. Inputs must be normalized. */ static inline RAPIER_TYPE(Rotation) RAPIER_FN(RotationMul)(RAPIER_TYPE(Rotation) a, RAPIER_TYPE(Rotation) b) { #if defined(RAPIER_DIM2) RAPIER_TYPE(Rotation) result = {a.angle + b.angle}; @@ -101,6 +121,7 @@ static inline RAPIER_TYPE(Rotation) RAPIER_FN(RotationMul)(RAPIER_TYPE(Rotation) /* Rotate a vector without changing its length. The rotation must be normalized. */ +/** Rotate a vector; rotation must be normalized. */ static inline RAPIER_TYPE(Vector) RAPIER_FN(RotationTransformVector)(RAPIER_TYPE(Rotation) rotation, RAPIER_TYPE(Vector) vector) { #if defined(RAPIER_DIM2) @@ -119,12 +140,14 @@ static inline RAPIER_TYPE(Vector) RAPIER_FN(RotationTransformVector)(RAPIER_TYPE #endif } +/** Construct a pose from translation and rotation without validation. */ static inline RAPIER_TYPE(Pose) RAPIER_FN(Pose)(RAPIER_TYPE(Vector) translation, RAPIER_TYPE(Rotation) rotation) { RAPIER_TYPE(Pose) result = {translation, rotation}; return result; } +/** Construct a pose with the supplied translation and identity rotation. */ static inline RAPIER_TYPE(Pose) RAPIER_FN(TranslationPose)(RAPIER_TYPE(Vector) translation) { #if defined(RAPIER_DIM2) RAPIER_TYPE(Rotation) rotation = {0}; @@ -133,6 +156,7 @@ static inline RAPIER_TYPE(Pose) RAPIER_FN(TranslationPose)(RAPIER_TYPE(Vector) t #endif return RAPIER_FN(Pose)(translation, rotation); } +/** Return the inverse of a normalized rotation. */ static inline RAPIER_TYPE(Rotation) RAPIER_FN(RotationInverse)(RAPIER_TYPE(Rotation) rotation) { #if defined(RAPIER_DIM2) return RAPIER_FN(Rotation)(-rotation.angle); @@ -141,11 +165,13 @@ static inline RAPIER_TYPE(Rotation) RAPIER_FN(RotationInverse)(RAPIER_TYPE(Rotat return result; #endif } +/** Transform a point by rotation then translation. Rotation must be normalized. */ static inline RAPIER_TYPE(Vector) RAPIER_FN(PoseTransformPoint)(RAPIER_TYPE(Pose) pose, RAPIER_TYPE(Vector) point) { return RAPIER_FN(VectorAdd)( pose.translation, RAPIER_FN(RotationTransformVector)(pose.rotation, point)); } +/** Return the inverse rigid transform. Rotation must be normalized. */ static inline RAPIER_TYPE(Pose) RAPIER_FN(PoseInverse)(RAPIER_TYPE(Pose) pose) { RAPIER_TYPE(Rotation) rotation = RAPIER_FN(RotationInverse)(pose.rotation); return RAPIER_FN(Pose)(RAPIER_FN(RotationTransformVector)( @@ -153,3 +179,5 @@ static inline RAPIER_TYPE(Pose) RAPIER_FN(PoseInverse)(RAPIER_TYPE(Pose) pose) { rotation); } #endif + +/** @} */ diff --git a/c/src/array_views.rs b/c/src/array_views.rs index 64757b87f..80a7cb1f2 100644 --- a/c/src/array_views.rs +++ b/c/src/array_views.rs @@ -3,118 +3,164 @@ use crate::*; /// Vertex indices for one edge; contiguous u32 fields with no padding. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprEdge { + /// First vertex index. pub a: u32, + /// Second vertex index. pub b: u32, } const _: () = assert!(size_of::() == 2 * size_of::()); /// Vertex indices for one triangle; contiguous u32 fields with no padding. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprTriangle { + /// First vertex index. pub a: u32, + /// Second vertex index. pub b: u32, + /// Third vertex index. pub c: u32, } const _: () = assert!(size_of::() == 3 * size_of::()); /// Vertex indices for one tetrahedron; contiguous u32 fields with no padding. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprTetrahedron { + /// First vertex index. pub a: u32, + /// Second vertex index. pub b: u32, + /// Third vertex index. pub c: u32, + /// Fourth vertex index. pub d: u32, } const _: () = assert!(size_of::() == 4 * size_of::()); /// Vertex indices for one dihedral; contiguous u32 fields with no padding. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprDihedral { + /// First vertex index. pub a: u32, + /// Second vertex index. pub b: u32, + /// Third vertex index. pub c: u32, + /// Fourth vertex index. pub d: u32, } const _: () = assert!(size_of::() == 4 * size_of::()); -/// Borrowed array of vector elements. count always counts elements, not scalars. +/// Borrowed array of vector elements. count counts elements of the declared type. /// Copying this view does not copy its data or extend its lifetime. No Free is needed. /// Data must remain live through the build/insert call that reads the description. /// NULL is permitted only when count is zero. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprVectorView { + /// Borrowed pointer to contiguous elements; NULL is allowed when count is zero. pub data: *const RprVector, + /// Number of elements, not bytes unless the element type is a byte. pub count: usize, } -/// Borrowed array of real elements. count always counts elements, not scalars. +/// Borrowed array of real elements. count counts elements of the declared type. /// Copying this view does not copy its data or extend its lifetime. No Free is needed. /// Data must remain live through the build/insert call that reads the description. /// NULL is permitted only when count is zero. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprRealView { + /// Borrowed pointer to contiguous elements; NULL is allowed when count is zero. pub data: *const RprReal, + /// Number of elements, not bytes unless the element type is a byte. pub count: usize, } -/// Borrowed array of index elements. count always counts elements, not scalars. +/// Borrowed array of index elements. count counts elements of the declared type. /// Copying this view does not copy its data or extend its lifetime. No Free is needed. /// Data must remain live through the build/insert call that reads the description. /// NULL is permitted only when count is zero. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprIndexView { + /// Borrowed pointer to contiguous elements; NULL is allowed when count is zero. pub data: *const u32, + /// Number of elements, not bytes unless the element type is a byte. pub count: usize, } -/// Borrowed array of edge elements. count always counts elements, not scalars. +/// Borrowed array of edge elements. count counts elements of the declared type. /// Copying this view does not copy its data or extend its lifetime. No Free is needed. /// Data must remain live through the build/insert call that reads the description. /// NULL is permitted only when count is zero. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprEdgeView { + /// Borrowed pointer to contiguous elements; NULL is allowed when count is zero. pub data: *const RprEdge, + /// Number of elements, not bytes unless the element type is a byte. pub count: usize, } -/// Borrowed array of triangle elements. count always counts elements, not scalars. +/// Borrowed array of triangle elements. count counts elements of the declared type. /// Copying this view does not copy its data or extend its lifetime. No Free is needed. /// Data must remain live through the build/insert call that reads the description. /// NULL is permitted only when count is zero. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprTriangleView { + /// Borrowed pointer to contiguous elements; NULL is allowed when count is zero. pub data: *const RprTriangle, + /// Number of elements, not bytes unless the element type is a byte. pub count: usize, } -/// Borrowed array of tetrahedron elements. count always counts elements, not scalars. +/// Borrowed array of tetrahedron elements. count counts elements of the declared type. /// Copying this view does not copy its data or extend its lifetime. No Free is needed. /// Data must remain live through the build/insert call that reads the description. /// NULL is permitted only when count is zero. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprTetrahedronView { + /// Borrowed pointer to contiguous elements; NULL is allowed when count is zero. pub data: *const RprTetrahedron, + /// Number of elements, not bytes unless the element type is a byte. pub count: usize, } -/// Borrowed array of dihedral elements. count always counts elements, not scalars. +/// Borrowed array of dihedral elements. count counts elements of the declared type. /// Copying this view does not copy its data or extend its lifetime. No Free is needed. /// Data must remain live through the build/insert call that reads the description. /// NULL is permitted only when count is zero. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprDihedralView { + /// Borrowed pointer to contiguous elements; NULL is allowed when count is zero. pub data: *const RprDihedral, + /// Number of elements, not bytes unless the element type is a byte. pub count: usize, } +/// Borrowed cell array: triangles in 2D, tetrahedra in 3D. +/// @ingroup math #[cfg(feature = "dim2")] pub type RprCellView = RprTriangleView; +/// Borrowed cell array: triangles in 2D, tetrahedra in 3D. +/// @ingroup math #[cfg(feature = "dim3")] pub type RprCellView = RprTetrahedronView; +/// Borrowed surface array: edges in 2D, triangles in 3D. +/// @ingroup math #[cfg(feature = "dim2")] pub type RprSurfaceElementView = RprEdgeView; +/// Borrowed surface array: edges in 2D, triangles in 3D. +/// @ingroup math #[cfg(feature = "dim3")] pub type RprSurfaceElementView = RprTriangleView; @@ -137,6 +183,7 @@ pub(crate) fn validate_view(data: *const T, count: usize) -> Result { /// Replace the shape geometry with a borrowed tri mesh. Counts are elements. /// Copies no arrays. Invalid view metadata leaves the description unchanged. /// Geometry and flags are validated when the description is built or inserted. +/// @ingroup shapes #[rapier_export(shape_desc)] pub unsafe extern "C" fn rpr_shape_desc_set_trimesh( desc: *mut RprShapeDesc, @@ -160,6 +207,7 @@ pub unsafe extern "C" fn rpr_shape_desc_set_trimesh( /// Replace the shape geometry with a borrowed polyline. Counts are elements. /// Copies no arrays. Invalid view metadata leaves the description unchanged. /// Geometry and flags are validated when the description is built or inserted. +/// @ingroup shapes #[rapier_export(shape_desc)] pub unsafe extern "C" fn rpr_shape_desc_set_polyline( desc: *mut RprShapeDesc, @@ -181,6 +229,7 @@ pub unsafe extern "C" fn rpr_shape_desc_set_polyline( }) } /// Replace the shape geometry with a borrowed convex hull point cloud. +/// @ingroup shapes #[rapier_export(shape_desc)] pub unsafe extern "C" fn rpr_shape_desc_set_convex_hull( desc: *mut RprShapeDesc, @@ -199,6 +248,7 @@ pub unsafe extern "C" fn rpr_shape_desc_set_convex_hull( }) } /// Select an explicit particle recipe and borrow its positions. Other fields are preserved. +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_particles( desc: *mut RprSoftBodyDesc, @@ -213,6 +263,7 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_particles( }) } /// Select a surface recipe and borrow its vertices and elements. Other fields are preserved. +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_surface_mesh( desc: *mut RprSoftBodyDesc, @@ -230,6 +281,7 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_surface_mesh( }) } /// Borrow skin geometry. Other fields, including skinCollision, are preserved. +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_skin( desc: *mut RprSoftBodyDesc, @@ -246,8 +298,10 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_skin( }) } /// Borrow masses; preserve all other fields. No allocation or element reads. -/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned +/// views. /// Invalid view metadata leaves the description unchanged. +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_masses( desc: *mut RprSoftBodyDesc, @@ -261,8 +315,10 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_masses( }) } /// Borrow pinned particles; preserve all other fields. No allocation or element reads. -/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned +/// views. /// Invalid view metadata leaves the description unchanged. +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_pinned_particles( desc: *mut RprSoftBodyDesc, @@ -276,8 +332,10 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_pinned_particles( }) } /// Borrow edges; preserve all other fields. No allocation or element reads. -/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned +/// views. /// Invalid view metadata leaves the description unchanged. +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_edges( desc: *mut RprSoftBodyDesc, @@ -291,8 +349,10 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_edges( }) } /// Borrow bend edges; preserve all other fields. No allocation or element reads. -/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned +/// views. /// Invalid view metadata leaves the description unchanged. +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_bend_edges( desc: *mut RprSoftBodyDesc, @@ -306,8 +366,10 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_bend_edges( }) } /// Borrow cells; preserve all other fields. No allocation or element reads. -/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned +/// views. /// Invalid view metadata leaves the description unchanged. +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_cells( desc: *mut RprSoftBodyDesc, @@ -321,8 +383,10 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_cells( }) } /// Borrow surface; preserve all other fields. No allocation or element reads. -/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned +/// views. /// Invalid view metadata leaves the description unchanged. +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_surface( desc: *mut RprSoftBodyDesc, @@ -336,8 +400,10 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_surface( }) } /// Borrow tension only edges; preserve all other fields. No allocation or element reads. -/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned +/// views. /// Invalid view metadata leaves the description unchanged. +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_tension_only_edges( desc: *mut RprSoftBodyDesc, @@ -351,8 +417,10 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_tension_only_edges( }) } /// Borrow dihedrals; preserve all other fields. No allocation or element reads. -/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned +/// views. /// Invalid view metadata leaves the description unchanged. +/// @ingroup soft_bodies #[cfg(feature = "dim3")] #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_dihedrals( @@ -367,8 +435,10 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_dihedrals( }) } /// Borrow wire; preserve all other fields. No allocation or element reads. -/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned views. +/// Zero counts retain the recipe's generated defaults at insertion, as with directly assigned +/// views. /// Invalid view metadata leaves the description unchanged. +/// @ingroup soft_bodies #[cfg(feature = "dim3")] #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_set_wire( @@ -383,35 +453,52 @@ pub unsafe extern "C" fn rpr_soft_body_desc_set_wire( }) } +/// Triangle in 2D or tetrahedron in 3D. +/// @ingroup math #[cfg(feature = "dim2")] pub type RprCell = RprTriangle; +/// Triangle in 2D or tetrahedron in 3D. +/// @ingroup math #[cfg(feature = "dim3")] pub type RprCell = RprTetrahedron; +/// Edge in 2D or triangle in 3D. +/// @ingroup math #[cfg(feature = "dim2")] pub type RprSurfaceElement = RprEdge; +/// Edge in 2D or triangle in 3D. +/// @ingroup math #[cfg(feature = "dim3")] pub type RprSurfaceElement = RprTriangle; /// Borrowed elements; count counts elements. Data must remain live through insertion. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprSoftEdgeSoftnessView { + /// Borrowed pointer to contiguous elements; NULL is allowed when count is zero. pub data: *const RprSoftEdgeSoftness, + /// Number of elements, not bytes unless the element type is a byte. pub count: usize, } /// Borrowed elements; count counts elements. Data must remain live through insertion. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprSoftEdgeTearView { + /// Borrowed pointer to contiguous elements; NULL is allowed when count is zero. pub data: *const RprSoftEdgeTear, + /// Number of elements, not bytes unless the element type is a byte. pub count: usize, } /// Borrowed elements; count counts elements. Data must remain live through insertion. +/// @ingroup shapes #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprCompoundShapeView { + /// Borrowed pointer to contiguous elements; NULL is allowed when count is zero. pub data: *const RprCompoundShapeDesc, + /// Number of elements, not bytes unless the element type is a byte. pub count: usize, } diff --git a/c/src/config_data.rs b/c/src/config_data.rs index c65188880..dfdae9f3b 100644 --- a/c/src/config_data.rs +++ b/c/src/config_data.rs @@ -9,49 +9,82 @@ use rapier::dynamics::{ SoftBodiesSettings, SoftEdgePlasticFlow, SoftPatchConstraints, SoftRecoverySettings, }; +/// Optional scalar override; enabled = 0 selects no override. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprOptionalReal { + /// Whether this setting/object is enabled (0 or 1). pub enabled: RprBool, + /// Value used when enabled is 1. pub value: RprReal, } +/// Optional unsigned integer override; enabled = 0 selects no override. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprOptionalU32 { + /// Whether this setting/object is enabled (0 or 1). pub enabled: RprBool, + /// Value used when enabled is 1. pub value: u32, } /// Optional boolean override. When disabled, retain the recipe's native default. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprOptionalBool { + /// Whether this setting/object is enabled (0 or 1). pub enabled: RprBool, + /// Value used when enabled is 1. pub value: RprBool, } /// Plain configuration data; initialize defaults, edit, then apply. No destructor. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprSoftBodyMaterial { + /// Spring coefficients for structural edge constraints. pub edgeSoftness: RprSpringCoefficients, + /// Spring coefficients for bending edges and dihedrals. pub bendSoftness: RprSpringCoefficients, + /// Spring coefficients for cell and global volume constraints. pub volumeSoftness: RprSpringCoefficients, + /// Spring coefficients for shape-matching constraints. pub shapeMatchingSoftness: RprSpringCoefficients, + /// Elastic modulus: force per area in 3D, force per length in 2D; nonnegative. pub youngModulus: RprReal, + /// Poisson ratio for elastic cells, in [0, 0.5). pub poissonRatio: RprReal, + /// Nonnegative damping ratio of elastic cells. pub elasticDampingRatio: RprReal, + /// Cell strain threshold for plastic flow; zero disables plasticity. pub plasticYield: RprReal, + /// Nonnegative rate per second at which excess cell strain becomes permanent. pub plasticCreep: RprReal, + /// Maximum accumulated cell plastic stretch, measured by the norm of P - I. pub plasticMax: RprReal, + /// Rate per second pulling particle velocities toward best-fit rigid motion; zero disables it. pub deformationDamping: RprReal, + /// Edge strain threshold for plastic flow; zero disables plasticity. pub edgePlasticYield: RprReal, + /// Nonnegative rate per second at which excess edge strain becomes permanent. pub edgePlasticCreep: RprReal, + /// Maximum permanent edge-length change as a fraction of its initial length. pub edgePlasticMax: RprReal, + /// Plastic flow direction: 0 both, 1 compression only, 2 tension only. pub edgePlasticFlow: u32, + /// Optional strain threshold for tearing; disabled means no strain-based tearing. pub tearStrain: RprOptionalReal, + /// Optional tensile edge-force threshold for tearing. pub tearForce: RprOptionalReal, + /// Exponential load-smoothing time constant in seconds; zero disables smoothing. pub tearSmoothing: RprReal, + /// Tear-threshold multiplier for undamaged interior elements. pub interiorStrength: RprReal, + /// Maximum ordinary edge tears per step; edges above twice their threshold bypass the limit. pub maxTearsPerStep: u32, + /// Optional minimum particle count of tear pieces. pub minPiece: RprOptionalU32, } impl From for RprSoftBodyMaterial { @@ -147,40 +180,71 @@ impl RprSoftBodyMaterial { }) } } +/// Return native default soft body material. This POD value owns no resources. +/// @ingroup soft_bodies #[rapier_export] pub extern "C" fn rpr_default_soft_body_material() -> RprSoftBodyMaterial { SoftBodyMaterial::default().into() } /// Plain configuration data; initialize defaults, edit, then apply. No destructor. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprSoftRecoverySettings { + /// Expand speculative contact margins to cover particle velocities set between steps. pub authoredVelocityMargin: RprBool, + /// Enable speculative edge-edge collision constraints. pub edgeSpeculation: RprBool, + /// Detect inverted cells to support self-contact recovery. pub invertedCellDetection: RprBool, + /// Detect surface self-crossings each step. pub selfCrossingDetection: RprBool, + /// Skip self-crossing detection when accumulated motion cannot have created a crossing. pub detectionMotionGating: RprBool, + /// Detect boundary crossings between soft bodies. pub crossBodyDetection: RprBool, + /// Disable contacts on tangled features so elasticity can untangle them. pub selfStandDown: RprBool, + /// Allow contacts at cross-body crossings to expel, but not hold, the intruder. pub crossBodyExpelGate: RprBool, + /// Disable edge constraints touching cross-body crossings. pub edgeStandDown: RprBool, + /// Repel crossing features toward their neighborhood's side of the surface. pub crossingRepulsion: RprBool, + /// Guide cross-body repulsion by overlap-volume normals; closed meshes only. pub crossingRepulsionGuide: RprBool, + /// Guide self-crossing repulsion by self-intersection-volume normals; closed meshes only. pub crossingRepulsionSelfGuide: RprBool, + /// Maximum recovery rate in length units per second, scaled by lengthUnit. pub recoveryPace: RprReal, + /// Enable intersection-volume constraints for overlapping closed surfaces. pub overlapConstraints: RprBool, + /// Enable intersection-volume constraints against rigid colliders. pub overlapRigid: RprBool, + /// Skip pair overlap constraints for self-crossed meshes. pub overlapSkipSelfTangled: RprBool, + /// Disable 3D closed-surface edge constraints where overlap constraints take over. pub overlapEdgeStandDown: RprBool, + /// Velocity-change limit per step, as a multiple of recoveryPace. pub overlapConstraintPace: RprReal, + /// Per-point constraints inside overlap patches: 0 keep, 1 stand down, 2 align with overlap + /// normal. pub overlapPatchConstraints: u32, + /// Measure overlap on contact-skin surfaces rather than bare geometry. pub overlapSkinVolume: RprBool, + /// Overlap depth retained by recovery, as a fraction of the pair's contact skins. pub overlapKeptDepth: RprReal, + /// Enable recovery of self-intersection regions. pub overlapSelfRegions: RprBool, + /// Use the overlap normal for recovery pushes. pub overlapNormalPush: RprBool, + /// Use spatially split overlap-volume constraints. pub overlapMultiVolume: RprBool, + /// Cells per tangent axis of the multi-volume grid. pub overlapSplit: u32, + /// Recovery progress patience in steps. pub overlapPatience: u32, + /// Relative overlap-volume decrease that counts as recovery progress. pub overlapProgressMargin: RprReal, } impl From for RprSoftRecoverySettings { @@ -258,17 +322,23 @@ impl RprSoftRecoverySettings { }) } } +/// Return native default soft recovery settings. This POD value owns no resources. +/// @ingroup soft_bodies #[rapier_export] pub extern "C" fn rpr_default_soft_recovery_settings() -> RprSoftRecoverySettings { SoftRecoverySettings::default().into() } #[cfg(feature = "fem")] /// Plain configuration data; initialize defaults, edit, then apply. No destructor. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprSoftFemParameters { + /// FEM iterative linear-solver tolerance. pub linearTolerance: RprReal, + /// Maximum FEM linear-solver iterations. pub maxLinearIterations: usize, + /// Maximum degrees of freedom solved by the dense FEM solver. pub maxDenseDofs: usize, } #[cfg(feature = "fem")] @@ -291,20 +361,28 @@ impl RprSoftFemParameters { }) } } +/// Return native default soft fem parameters. This POD value owns no resources. +/// @ingroup soft_bodies #[cfg(feature = "fem")] #[rapier_export] pub extern "C" fn rpr_default_soft_fem_parameters() -> RprSoftFemParameters { SoftFemParameters::default().into() } /// Plain configuration data; initialize defaults, edit, then apply. No destructor. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprSoftBodiesSettings { + /// Soft-body crossing detection and recovery settings. pub recovery: RprSoftRecoverySettings, + /// Strain threshold for re-solving soft constraints after contacts within a substep. pub resweepStrain: RprReal, + /// Maximum additional substeps requested by soft-body motion. pub maxExtraSubsteps: usize, + /// Multiplier on contact natural frequency for soft-body contacts. pub contactStiffening: RprReal, #[cfg(feature = "fem")] + /// FEM linear-solver settings, present only when RAPIER_FEM is enabled. pub fem: RprSoftFemParameters, } impl From for RprSoftBodiesSettings { @@ -331,35 +409,59 @@ impl RprSoftBodiesSettings { }) } } +/// Return native default soft bodies settings. This POD value owns no resources. +/// @ingroup soft_bodies #[rapier_export] pub extern "C" fn rpr_default_soft_bodies_settings() -> RprSoftBodiesSettings { SoftBodiesSettings::default().into() } /// Plain configuration data; initialize defaults, edit, then apply. No destructor. +/// @ingroup worlds #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprIntegrationParameters { + /// Simulation step duration in seconds. pub dt: RprReal, + /// Minimum CCD substep duration in seconds. pub minCcdDt: RprReal, + /// Spring coefficients for dynamic contact constraints. pub contactSoftness: RprSpringCoefficients, + /// Spring coefficients for contacts against fixed bodies. pub staticContactSoftness: RprSpringCoefficients, + /// Scale applied to cached impulses when warmstarting. pub warmstartCoefficient: RprReal, + /// Typical world-space length of one meter; scales solver tolerances, not geometry. pub lengthUnit: RprReal, + /// Soft-body integration and recovery settings. pub softBodies: RprSoftBodiesSettings, + /// Allowed penetration divided by lengthUnit. pub normalizedAllowedLinearError: RprReal, + /// Maximum penetration-correction speed divided by lengthUnit. pub normalizedMaxCorrectiveVelocity: RprReal, + /// Speculative-contact distance divided by lengthUnit. pub normalizedPredictionDistance: RprReal, + /// Maximum linear speed divided by lengthUnit. pub normalizedMaxLinearVelocity: RprReal, + /// Number of solver substeps/iterations; must be positive. pub numSolverIterations: usize, + /// PGS iterations per solver substep. pub numInternalPgsIterations: usize, + /// Stabilization iterations after velocity solving. pub numInternalStabilizationIterations: usize, + /// Maximum CCD substeps; 0 disables all CCD for the world. pub maxCcdSubsteps: usize, + /// Whether to cluster contacts for solving. pub contactClustering: RprBool, + /// Whether to reuse nearby contacts between steps. pub contactRecycling: RprBool, + /// Contact recycling distance divided by lengthUnit. pub normalizedContactRecycleDistance: RprReal, + /// Whether to solve friction in the bias pass. pub frictionInBiasPass: RprBool, + /// Whether to warmstart joint constraints. pub warmstartJoints: RprBool, #[cfg(feature = "dim3")] + /// Friction model: 0 simplified, 1 Coulomb (3D only). pub frictionModel: u32, } impl From for RprIntegrationParameters { @@ -427,11 +529,15 @@ impl RprIntegrationParameters { }) } } +/// Return native default integration parameters. This POD value owns no resources. +/// @ingroup worlds #[rapier_export] pub extern "C" fn rpr_default_integration_parameters() -> RprIntegrationParameters { IntegrationParameters::default().into() } +/// Return a copy of all world integration settings. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_integration_parameters( world: *const RprWorld, @@ -449,6 +555,7 @@ pub unsafe extern "C" fn rpr_integration_parameters( } /// Copies validated values; does not expose a writable alias to Rust memory. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_integration_parameters( world: *mut RprWorld, diff --git a/c/src/control.rs b/c/src/control.rs index ef733e531..96a8a9149 100644 --- a/c/src/control.rs +++ b/c/src/control.rs @@ -3,16 +3,23 @@ use rapier::control::{ CharacterAutostep, CharacterCollision, CharacterLength, KinematicCharacterController, }; /// Controller plus reusable collision output from the last move_shape call. +/// Kinematic character controller and its last collision list. Release with the matching Free +/// function. +/// @ingroup controllers pub struct RprKinematicCharacterController { world: *mut RprWorld, inner: KinematicCharacterController, collisions: Vec, } -/// CharacterLength counterpart: relative=1 scales with character height, relative=0 uses world units. +/// CharacterLength counterpart: relative=1 scales with character height, relative=0 uses world +/// units. +/// @ingroup controllers #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprCharacterLength { + /// Value used when enabled is 1. pub value: RprReal, + /// 1 scales value by the character shape size; 0 uses an absolute length. pub relative: RprBool, } impl RprCharacterLength { @@ -25,22 +32,37 @@ impl RprCharacterLength { }) } } +/// Allowed character motion and ground-contact state. +/// @ingroup controllers #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprCharacterMovement { + /// Allowed world-space displacement; not applied automatically. pub translation: RprVector, + /// Whether the character touches the ground after movement. pub grounded: RprBool, + /// Whether motion includes sliding down a non-climbable slope. pub is_sliding_down_slope: RprBool, } +/// Collision recorded during character movement. +/// @ingroup controllers #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprCharacterCollision { + /// World-bound collider handle. pub collider: RprColliderHandle, + /// World-space character pose at collision. pub character_pos: RprPose, + /// World-space translation already applied before collision. pub translation_applied: RprVector, + /// World-space translation remaining at collision. pub translation_remaining: RprVector, + /// Shape/ray impact details. pub hit: RprShapeCastHit, } +/// Allocate a character controller with native defaults; release it with +/// rpr_free_kinematic_character_controller. +/// @ingroup controllers #[rapier_export] pub unsafe extern "C" fn rpr_new_kinematic_character_controller() -> *mut RprKinematicCharacterController { @@ -58,6 +80,9 @@ pub unsafe extern "C" fn rpr_new_kinematic_character_controller() }) }) } +/// Release an owned kinematic character controller. NULL is allowed. Do not pass borrowed pointers +/// or free the object twice. +/// @ingroup controllers #[rapier_export] pub unsafe extern "C" fn rpr_free_kinematic_character_controller( controller: *mut RprKinematicCharacterController, @@ -70,6 +95,8 @@ pub unsafe extern "C" fn rpr_free_kinematic_character_controller( Ok(()) }) } +/// Set the up direction; it must be finite and nonzero and is normalized on input. +/// @ingroup controllers #[rapier_export(kinematic_character_controller)] pub unsafe extern "C" fn rpr_kinematic_character_controller_set_up( controller: *mut RprKinematicCharacterController, @@ -82,6 +109,8 @@ pub unsafe extern "C" fn rpr_kinematic_character_controller_set_up( Ok(()) }) } +/// Set the collision separation margin; use a positive absolute or relative character length. +/// @ingroup controllers #[rapier_export(kinematic_character_controller)] pub unsafe extern "C" fn rpr_kinematic_character_controller_set_offset( controller: *mut RprKinematicCharacterController, @@ -94,6 +123,8 @@ pub unsafe extern "C" fn rpr_kinematic_character_controller_set_offset( Ok(()) }) } +/// Enable or disable sliding along obstacles. +/// @ingroup controllers #[rapier_export(kinematic_character_controller)] pub unsafe extern "C" fn rpr_kinematic_character_controller_set_slide( controller: *mut RprKinematicCharacterController, @@ -105,6 +136,8 @@ pub unsafe extern "C" fn rpr_kinematic_character_controller_set_slide( Ok(()) }) } +/// Set the maximum climb angle and minimum slide angle, in radians. +/// @ingroup controllers #[rapier_export(kinematic_character_controller)] pub unsafe extern "C" fn rpr_kinematic_character_controller_set_slopes( controller: *mut RprKinematicCharacterController, @@ -122,6 +155,8 @@ pub unsafe extern "C" fn rpr_kinematic_character_controller_set_slopes( Ok(()) }) } +/// Configure automatic stepping over obstacles. enabled = 0 disables it. +/// @ingroup controllers #[rapier_export(kinematic_character_controller)] pub unsafe extern "C" fn rpr_kinematic_character_controller_set_autostep( controller: *mut RprKinematicCharacterController, @@ -141,6 +176,8 @@ pub unsafe extern "C" fn rpr_kinematic_character_controller_set_autostep( Ok(()) }) } +/// Configure downward ground snapping. enabled = 0 disables it. +/// @ingroup controllers #[rapier_export(kinematic_character_controller)] pub unsafe extern "C" fn rpr_kinematic_character_controller_set_snap_to_ground( controller: *mut RprKinematicCharacterController, @@ -154,7 +191,11 @@ pub unsafe extern "C" fn rpr_kinematic_character_controller_set_snap_to_ground( Ok(()) }) } -/// Computes movement without moving any collider. Use the returned translation to set the character target. +/// Computes movement without moving any collider. Use the returned translation to set the character +/// target. +/// NULL query options use the default filter. Query state reflects the latest Step or +/// DetectCollisions call. +/// @ingroup controllers #[rapier_export(kinematic_character_controller)] pub unsafe extern "C" fn rpr_kinematic_character_controller_move_shape( world: *const RprWorld, @@ -201,6 +242,9 @@ pub unsafe extern "C" fn rpr_kinematic_character_controller_move_shape( }) } +/// Copy collisions recorded by the most recent MoveShape call. +/// @see @ref output_buffers +/// @ingroup controllers #[rapier_export(kinematic_character_controller)] pub unsafe extern "C" fn rpr_kinematic_character_controller_collisions( controller: *const RprKinematicCharacterController, @@ -239,7 +283,9 @@ pub unsafe extern "C" fn rpr_kinematic_character_controller_collisions( ) } } -/// Applies impulses for the most recent move_shape collisions. Use the same world, shape, dt and filter. +/// Applies impulses for the most recent move_shape collisions. Use the same world, shape, dt and +/// filter. +/// @ingroup controllers #[rapier_export(kinematic_character_controller)] pub unsafe extern "C" fn rpr_kinematic_character_controller_solve_character_collision_impulses( controller: *const RprKinematicCharacterController, @@ -285,16 +331,27 @@ pub unsafe extern "C" fn rpr_kinematic_character_controller_solve_character_coll mod vehicle { use super::*; use rapier::control::{DynamicRayCastVehicleController, WheelTuning}; + /// Vehicle controller borrowing its chassis world. Release with the matching Free function. + /// @ingroup controllers pub struct RprDynamicRayCastVehicleController(DynamicRayCastVehicleController, *mut RprWorld); #[repr(C)] #[derive(Copy, Clone, Default)] + /// Wheel suspension/friction parameters. Initialize with rpr_default_wheel_tuning. + /// @ingroup controllers pub struct RprWheelTuning { + /// Nonnegative suspension spring stiffness. pub suspension_stiffness: RprReal, + /// Nonnegative damping coefficient during suspension compression. pub suspension_compression: RprReal, + /// Nonnegative suspension relaxation damping. pub suspension_damping: RprReal, + /// Maximum suspension travel in length units. pub max_suspension_travel: RprReal, + /// Maximum tire friction/slip coefficient. pub friction_slip: RprReal, + /// Maximum force exerted by suspension. pub max_suspension_force: RprReal, + /// Sideways tire friction stiffness. pub side_friction_stiffness: RprReal, } impl RprWheelTuning { @@ -312,18 +369,32 @@ mod vehicle { } #[repr(C)] #[derive(Copy, Clone, Default)] + /// Copy of the current wheel pose, suspension, and contact state. + /// @ingroup controllers pub struct RprWheelState { + /// World-space wheel center. pub center: RprVector, + /// World-space suspension direction. pub suspension: RprVector, + /// World-space axle direction. pub axle: RprVector, + /// Wheel rotation angle in radians. pub rotation: RprReal, + /// Current suspension force. pub suspension_force: RprReal, + /// Current suspension length. pub suspension_length: RprReal, + /// Whether the wheel has ground contact. pub is_in_contact: RprBool, + /// Ground collider handle, invalid when there is no contact. pub ground_object: RprColliderHandle, + /// World-space ground contact point. pub contact_point: RprVector, + /// World-space ground contact normal. pub contact_normal: RprVector, } + /// Return native default wheel tuning. This POD value owns no resources. + /// @ingroup controllers #[rapier_export] pub extern "C" fn rpr_default_wheel_tuning() -> RprWheelTuning { let t = WheelTuning::default(); @@ -337,6 +408,9 @@ mod vehicle { side_friction_stiffness: t.side_friction_stiffness, } } + /// Allocate a vehicle controller bound to its chassis body. The chassis world must outlive the + /// controller. Release with rpr_free_dynamic_ray_cast_vehicle_controller. + /// @ingroup controllers #[rapier_export] pub unsafe extern "C" fn rpr_new_dynamic_ray_cast_vehicle_controller( chassis: RprRigidBodyHandle, @@ -367,6 +441,9 @@ mod vehicle { }) } + /// Release an owned dynamic ray cast vehicle controller. NULL is allowed. Do not pass borrowed + /// pointers or free the object twice. + /// @ingroup controllers #[rapier_export] pub unsafe extern "C" fn rpr_free_dynamic_ray_cast_vehicle_controller( controller: *mut RprDynamicRayCastVehicleController, @@ -379,6 +456,9 @@ mod vehicle { Ok(()) }) } + /// Append a wheel and return its zero-based index. Connection, suspension direction, and axle + /// are in chassis-local coordinates. + /// @ingroup controllers #[rapier_export(dynamic_ray_cast_vehicle_controller)] pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_add_wheel( controller: *mut RprDynamicRayCastVehicleController, @@ -411,6 +491,8 @@ mod vehicle { }) }) } + /// Set the chassis up/forward axis indices (0 = X, 1 = Y, 2 = Z). + /// @ingroup controllers #[rapier_export(dynamic_ray_cast_vehicle_controller)] pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_set_axes( controller: *mut RprDynamicRayCastVehicleController, @@ -428,6 +510,8 @@ mod vehicle { Ok(()) }) } + /// Set a wheel engine force, brake force, and steering angle in radians. + /// @ingroup controllers #[rapier_export(dynamic_ray_cast_vehicle_controller)] pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_set_wheel_controls( controller: *mut RprDynamicRayCastVehicleController, @@ -451,6 +535,8 @@ mod vehicle { Ok(()) }) } + /// Ray-cast wheel contacts and apply vehicle forces for dt seconds. Does not step the world. + /// @ingroup controllers #[rapier_export(dynamic_ray_cast_vehicle_controller)] pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_update_vehicle( controller: *mut RprDynamicRayCastVehicleController, @@ -493,6 +579,8 @@ mod vehicle { }) } + /// Return signed chassis speed along its forward direction. + /// @ingroup controllers #[rapier_export(dynamic_ray_cast_vehicle_controller)] pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_current_vehicle_speed( controller: *const RprDynamicRayCastVehicleController, @@ -501,6 +589,9 @@ mod vehicle { ffi(|| unsafe { output(out, get(controller)?.0.current_vehicle_speed) }) }) } + /// Copy current wheel state in wheel insertion order. + /// @see @ref output_buffers + /// @ingroup controllers #[rapier_export(dynamic_ray_cast_vehicle_controller)] pub unsafe extern "C" fn rpr_dynamic_ray_cast_vehicle_controller_wheels( controller: *const RprDynamicRayCastVehicleController, @@ -548,19 +639,32 @@ mod vehicle { pub use vehicle::*; /// PID controller with persistent integral state. +/// Stateful proportional-integral-derivative controller. Release with the matching Free function. +/// @ingroup controllers pub struct RprPidController(rapier::control::PidController); +/// Per-axis proportional, integral, and derivative controller gains. +/// @ingroup controllers #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprPidGains { + /// Linear proportional gain per axis. pub lin_kp: RprVector, + /// Linear integral gain per axis. pub lin_ki: RprVector, + /// Linear derivative gain per axis. pub lin_kd: RprVector, + /// Angular proportional gain per axis. pub ang_kp: RprAngVector, + /// Angular integral gain per axis. pub ang_ki: RprAngVector, + /// Angular derivative gain per axis. pub ang_kd: RprAngVector, } +/// Allocate a PID controller with supplied gains and controlled axes. Release with +/// rpr_free_pid_controller. +/// @ingroup controllers #[rapier_export] pub unsafe extern "C" fn rpr_new_pid_controller() -> *mut RprPidController { ffi_value(|out: *mut *mut RprPidController| { @@ -573,6 +677,9 @@ pub unsafe extern "C" fn rpr_new_pid_controller() -> *mut RprPidController { }) }) } +/// Release an owned pid controller. NULL is allowed. Do not pass borrowed pointers or free the +/// object twice. +/// @ingroup controllers #[rapier_export] pub unsafe extern "C" fn rpr_free_pid_controller(controller: *mut RprPidController) -> RprStatus { ffi(|| unsafe { @@ -582,6 +689,8 @@ pub unsafe extern "C" fn rpr_free_pid_controller(controller: *mut RprPidControll Ok(()) }) } +/// Return a copy of the proportional, integral, and derivative gains. +/// @ingroup controllers #[rapier_export(pid_controller)] pub unsafe extern "C" fn rpr_pid_controller_gains( controller: *const RprPidController, @@ -603,6 +712,8 @@ pub unsafe extern "C" fn rpr_pid_controller_gains( }) }) } +/// Replace the proportional, integral, and derivative gains. +/// @ingroup controllers #[rapier_export(pid_controller)] pub unsafe extern "C" fn rpr_pid_controller_set_gains( controller: *mut RprPidController, @@ -626,6 +737,7 @@ pub unsafe extern "C" fn rpr_pid_controller_set_gains( }) } /// AxesMask bits match Rapier: linear X/Y/Z are 1/2/4, angular X/Y/Z are 8/16/32. +/// @ingroup controllers #[rapier_export(pid_controller)] pub unsafe extern "C" fn rpr_pid_controller_set_axes( controller: *mut RprPidController, @@ -641,6 +753,7 @@ pub unsafe extern "C" fn rpr_pid_controller_set_axes( }) } /// Compute a velocity correction, preserving the body's state and updating PID integrals. +/// @ingroup controllers #[rapier_export(pid_controller)] pub unsafe extern "C" fn rpr_pid_controller_rigid_body_correction( controller: *mut RprPidController, @@ -706,15 +819,24 @@ pub(crate) unsafe fn native_pid_controller_rigid_body_correction( }) } +/// Copy of character sliding, slope, and snapping settings. +/// @ingroup controllers #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprCharacterControllerSettings { + /// Whether obstacle sliding is enabled. pub slide: RprBool, + /// Maximum climbable slope angle in radians. pub max_slope_climb_angle: RprReal, + /// Minimum slope angle for sliding, in radians. pub min_slope_slide_angle: RprReal, + /// Whether downward ground snapping is enabled. pub snap_to_ground: RprBool, + /// Maximum downward snapping distance. pub snap_distance: RprCharacterLength, } +/// Return a copy of slide, slope, and ground-snap settings. +/// @ingroup controllers #[rapier_export(kinematic_character_controller)] pub unsafe extern "C" fn rpr_kinematic_character_controller_settings( controller: *const RprKinematicCharacterController, diff --git a/c/src/descriptors.rs b/c/src/descriptors.rs index a41842529..4346d0b1a 100644 --- a/c/src/descriptors.rs +++ b/c/src/descriptors.rs @@ -2,32 +2,56 @@ #![allow(non_snake_case)] use crate::*; -/// Stack-allocated rigid-body construction data. Initialize with RigidBodyDescInit. +/// Stack-allocated rigid-body construction data. Initialize with rpr_dynamic_rigid_body_desc, +/// rpr_fixed_rigid_body_desc, or a kinematic description constructor. /// Copying this value is safe; it owns no resources and must never be freed by Rapier. +/// @ingroup rigid_bodies #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprRigidBodyDesc { + /// World-space pose. pub position: RprPose, + /// World-space linear velocity. pub linvel: RprVector, + /// World-space angular velocity in radians per second. pub angvel: RprAngVector, + /// RPR_DYNAMIC, RPR_FIXED, RPR_KINEMATIC_POSITION_BASED, or RPR_KINEMATIC_VELOCITY_BASED. pub bodyType: u32, + /// Multiplier applied to world gravity. pub gravityScale: RprReal, + /// Nonnegative linear damping coefficient. pub linearDamping: RprReal, + /// Nonnegative angular damping coefficient. pub angularDamping: RprReal, + /// Nonnegative mass added to attached collider contributions. pub additionalMass: RprReal, + /// 1 uses additionalMassProperties; 0 uses additionalMass. pub useAdditionalMassProperties: RprBool, + /// Additional body-local mass and inertia when enabled. pub additionalMassProperties: RprMassProperties, + /// Locked-axis bitmask; translations precede rotations. pub lockedAxes: u8, + /// Whether automatic sleeping is allowed. pub canSleep: RprBool, + /// Whether the body starts/is asleep. pub sleeping: RprBool, + /// Whether continuous collision detection is enabled. pub ccdEnabled: RprBool, + /// Nonnegative prediction distance for soft CCD. pub softCcdPrediction: RprReal, + /// Whether to allow fast rotations without the native angular-motion clamp. pub allowFastRotation: RprBool, + /// Whether this setting/object is enabled (0 or 1). pub enabled: RprBool, + /// Signed dominance group; larger groups dominate smaller groups. pub dominanceGroup: i8, + /// Extra solver iterations for this body and connected bodies. pub additionalSolverIterations: usize, + /// Extra PGS iterations for this body. pub additionalPgsIterations: usize, + /// Whether to include gyroscopic forces (3D). pub gyroscopicForcesEnabled: RprBool, + /// Application data; Rapier does not own pointers encoded in it. pub userData: RprUserData, } impl RprRigidBodyDesc { @@ -94,45 +118,89 @@ impl RprRigidBodyDesc { Ok(b) } } +/// Return a dynamic rigid-body description with native defaults; no allocation. +/// @ingroup rigid_bodies #[rapier_export] pub extern "C" fn rpr_dynamic_rigid_body_desc() -> RprRigidBodyDesc { RprRigidBodyDesc::new(RPR_DYNAMIC).expect("valid body kind") } +/// Return a fixed rigid-body description with native defaults; no allocation. +/// @ingroup rigid_bodies #[rapier_export] pub extern "C" fn rpr_fixed_rigid_body_desc() -> RprRigidBodyDesc { RprRigidBodyDesc::new(RPR_FIXED).expect("valid body kind") } +/// Return a kinematic position based rigid-body description with native defaults; no allocation. +/// @ingroup rigid_bodies #[rapier_export] pub extern "C" fn rpr_kinematic_position_based_rigid_body_desc() -> RprRigidBodyDesc { RprRigidBodyDesc::new(RPR_KINEMATIC_POSITION_BASED).expect("valid body kind") } +/// Return a kinematic velocity based rigid-body description with native defaults; no allocation. +/// @ingroup rigid_bodies #[rapier_export] pub extern "C" fn rpr_kinematic_velocity_based_rigid_body_desc() -> RprRigidBodyDesc { RprRigidBodyDesc::new(RPR_KINEMATIC_VELOCITY_BASED).expect("valid body kind") } +/// @ingroup shapes +/// Treat the 2D polyline as oriented when generating contact normals. #[cfg(feature = "dim2")] pub const RPR_POLYLINE_ORIENTED: u32 = 1; +/// @ingroup shapes +/// Prepare polyline acceleration data for deformation. pub const RPR_POLYLINE_DEFORMABLE: u32 = 2; +/// @ingroup shapes +/// ShapeDesc kind selecting a ball. pub const RPR_SHAPE_DESC_BALL: u32 = 0; +/// @ingroup shapes +/// ShapeDesc kind selecting a cuboid. pub const RPR_SHAPE_DESC_CUBOID: u32 = 1; +/// @ingroup shapes +/// ShapeDesc kind selecting a round cuboid. pub const RPR_SHAPE_DESC_ROUND_CUBOID: u32 = 2; +/// @ingroup shapes +/// ShapeDesc kind selecting a capsule. pub const RPR_SHAPE_DESC_CAPSULE: u32 = 3; +/// @ingroup shapes +/// ShapeDesc kind selecting a segment. pub const RPR_SHAPE_DESC_SEGMENT: u32 = 4; +/// @ingroup shapes +/// ShapeDesc kind selecting a triangle. pub const RPR_SHAPE_DESC_TRIANGLE: u32 = 5; +/// @ingroup shapes +/// ShapeDesc kind selecting a half-space. pub const RPR_SHAPE_DESC_HALFSPACE: u32 = 6; +/// @ingroup shapes +/// ShapeDesc kind selecting a convex hull. pub const RPR_SHAPE_DESC_CONVEX_HULL: u32 = 7; +/// @ingroup shapes +/// ShapeDesc kind selecting a triangle mesh. pub const RPR_SHAPE_DESC_TRIMESH: u32 = 8; +/// @ingroup shapes +/// ShapeDesc kind selecting a polyline. pub const RPR_SHAPE_DESC_POLYLINE: u32 = 9; +/// @ingroup shapes +/// ShapeDesc kind selecting borrowed shared geometry. pub const RPR_SHAPE_DESC_SHARED: u32 = 10; +/// @ingroup shapes +/// ShapeDesc kind selecting a heightfield. pub const RPR_SHAPE_DESC_HEIGHTFIELD: u32 = 11; +/// @ingroup shapes +/// ShapeDesc kind selecting a cylinder. pub const RPR_SHAPE_DESC_CYLINDER: u32 = 12; +/// @ingroup shapes +/// ShapeDesc kind selecting a cone. pub const RPR_SHAPE_DESC_CONE: u32 = 13; +/// @ingroup shapes +/// ShapeDesc kind selecting compound child shapes. pub const RPR_SHAPE_DESC_COMPOUND: u32 = 14; +/// @ingroup shapes +/// ShapeDesc kind selecting a round cylinder. pub const RPR_SHAPE_DESC_ROUND_CYLINDER: u32 = 15; /// Non-owning shape description. Only fields selected by kind are read. @@ -140,31 +208,53 @@ pub const RPR_SHAPE_DESC_ROUND_CYLINDER: u32 = 15; /// b/c = remaining endpoints/vertices. radius is also the rounded-cuboid border radius. /// Mesh views count edges or triangles; heightfields are column-major. /// Arrays, compound children, and sharedShape remain borrowed until build/insert returns. +/// @ingroup shapes #[repr(C)] #[derive(Clone, Copy)] pub struct RprShapeDesc { + /// Discriminant selecting which description fields are read. pub kind: u32, + /// Half extents, endpoint/vertex, or halfspace normal, selected by kind. pub a: RprVector, + /// Second endpoint or triangle vertex, selected by kind. pub b: RprVector, + /// Third triangle vertex. pub c: RprVector, + /// Radius for the selected primitive or recipe. pub radius: RprReal, + /// Half the height of a cylinder or cone. pub halfHeight: RprReal, + /// Rounding radius for a rounded shape. pub borderRadius: RprReal, + /// Borrowed vertex positions. pub vertices: RprVectorView, + /// Borrowed triangle topology; count is triangles. pub triangles: RprTriangleView, + /// Borrowed edge topology; count is edges. pub edges: RprEdgeView, + /// TRIMESH_*, POLYLINE_*, or HEIGHTFIELD_* bitmask selected by kind. pub flags: u32, + /// Borrowed height samples; 3D uses column-major rows * columns samples. pub heights: RprRealView, + /// Number of heightfield rows. pub rows: usize, + /// Number of heightfield columns. pub columns: usize, + /// Shape scale along each axis. pub scale: RprVector, + /// Borrowed shared geometry; keep its wrapper alive through build/insert. pub sharedShape: *const RprSharedShape, + /// Borrowed compound children and their nested geometry views. pub children: RprCompoundShapeView, } +/// One compound child with a local pose and borrowed geometry description. +/// @ingroup shapes #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprCompoundShapeDesc { + /// Pose relative to the compound parent. pub pose: RprPose, + /// Non-owning shape description; build/insert consumes its views synchronously. pub shape: RprShapeDesc, } impl Default for RprShapeDesc { @@ -366,10 +456,15 @@ impl RprShapeDesc { }) } } +/// Return native default shape desc. This POD value owns no resources. +/// @ingroup shapes #[rapier_export] pub extern "C" fn rpr_default_shape_desc() -> RprShapeDesc { RprShapeDesc::default() } +/// Build an owned shared shape from a description; release it with rpr_free_shared_shape. Borrowed +/// inputs may be released after this call. +/// @ingroup shapes #[rapier_export(shape_desc)] pub unsafe extern "C" fn rpr_shape_desc_build(desc: *const RprShapeDesc) -> *mut RprSharedShape { ffi_value(|out: *mut *mut RprSharedShape| { @@ -381,32 +476,59 @@ pub unsafe extern "C" fn rpr_shape_desc_build(desc: *const RprShapeDesc) -> *mut }) } +/// @ingroup colliders +/// Mass density. pub const RPR_MASS_DENSITY: u32 = 0; +/// @ingroup colliders +/// Mass total. pub const RPR_MASS_TOTAL: u32 = 1; +/// @ingroup colliders +/// Mass properties. pub const RPR_MASS_PROPERTIES: u32 = 2; /// Copyable collider construction data. Shape inputs are borrowed, never owned. +/// @ingroup colliders #[repr(C)] #[derive(Clone, Copy)] pub struct RprColliderDesc { + /// Non-owning shape description; build/insert consumes its views synchronously. pub shape: RprShapeDesc, + /// Pose relative to the parent body; world-space for an unparented collider. pub position: RprPose, + /// RPR_MASS_DENSITY, RPR_MASS_TOTAL, or RPR_MASS_PROPERTIES. pub massMode: u32, + /// Nonnegative mass per unit volume. pub density: RprReal, + /// Mass; nonnegative when supplied as input. pub mass: RprReal, + /// Explicit local mass and inertia when massMode selects them. pub massProperties: RprMassProperties, + /// Nonnegative friction coefficient. pub friction: RprReal, + /// Nonnegative restitution coefficient. pub restitution: RprReal, + /// RPR_COMBINE_AVERAGE, MIN, MULTIPLY, or MAX. pub frictionCombineRule: u32, + /// RPR_COMBINE_AVERAGE, MIN, MULTIPLY, or MAX. pub restitutionCombineRule: u32, + /// 1 detects intersections without generating contact forces. pub isSensor: RprBool, + /// Whether this setting/object is enabled (0 or 1). pub enabled: RprBool, + /// Groups controlling collision detection. pub collisionGroups: RprInteractionGroups, + /// Groups controlling contact-force solving. pub solverGroups: RprInteractionGroups, + /// Bitmask of body-type pairs allowed to collide. pub activeCollisionTypes: u16, + /// Hook flags enabling pair filtering/contact modification. pub activeHooks: u32, + /// RPR_COLLISION_EVENTS and/or RPR_CONTACT_FORCE_EVENTS. pub activeEvents: u32, + /// Nonnegative force threshold for force events. pub contactForceEventThreshold: RprReal, + /// Nonnegative extra separation distance around the collider. pub contactSkin: RprReal, + /// Application data; Rapier does not own pointers encoded in it. pub userData: RprUserData, } impl Default for RprColliderDesc { @@ -470,18 +592,24 @@ impl RprColliderDesc { Ok(b) } } +/// Return native default collider desc. This POD value owns no resources. +/// @ingroup colliders #[rapier_export] pub extern "C" fn rpr_default_collider_desc() -> RprColliderDesc { RprColliderDesc::default() } +/// Return a ball description with the supplied radius. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders #[rapier_export] pub extern "C" fn rpr_ball_collider_desc(radius: RprReal) -> RprColliderDesc { let mut d = RprColliderDesc::default(); d.shape.radius = radius; d } +/// Return an axis-aligned box description with the supplied half-extents. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders #[rapier_export] pub extern "C" fn rpr_cuboid_collider_desc(half_extents: RprVector) -> RprColliderDesc { let mut d = RprColliderDesc::default(); @@ -489,6 +617,8 @@ pub extern "C" fn rpr_cuboid_collider_desc(half_extents: RprVector) -> RprCollid d.shape.a = half_extents; d } +/// Create a body from the description and return its world-bound handle. The world owns the body. +/// @ingroup rigid_bodies #[rapier_export] pub unsafe extern "C" fn rpr_insert_rigid_body( world: *mut RprWorld, @@ -517,6 +647,7 @@ pub unsafe extern "C" fn rpr_insert_rigid_body( /// Insert a collider attached to a rigid body, using the world stored in its handle. /// The parent handle is copied by value. The description is borrowed through this call. /// Invalid or removed parents fail without inserting a collider. +/// @ingroup colliders #[rapier_export] pub unsafe extern "C" fn rpr_insert_collider( parent: RprRigidBodyHandle, @@ -540,6 +671,7 @@ pub unsafe extern "C" fn rpr_insert_collider( /// Insert a collider without a rigid-body parent. The world owns the collider. /// The description is borrowed through this call. +/// @ingroup colliders #[rapier_export] pub unsafe extern "C" fn rpr_insert_collider_without_parent( world: *mut RprWorld, @@ -556,23 +688,35 @@ pub unsafe extern "C" fn rpr_insert_collider_without_parent( } /// Sizes of the POD types in this library build, for foreign-language layout checks. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprPodLayout { + /// Size in bytes of the rigidBodyDesc structure; zero when unavailable. pub rigidBodyDesc: usize, + /// Size in bytes of the colliderDesc structure; zero when unavailable. pub colliderDesc: usize, + /// Size in bytes of the shapeDesc structure; zero when unavailable. pub shapeDesc: usize, + /// Size in bytes of the jointDesc structure; zero when unavailable. pub jointDesc: usize, + /// Size in bytes of the softBodyMaterial structure; zero when unavailable. pub softBodyMaterial: usize, + /// Size in bytes of the integrationParameters structure; zero when unavailable. pub integrationParameters: usize, + /// Size in bytes of the softBodyDesc structure; zero when unavailable. pub softBodyDesc: usize, + /// Size in bytes of the softMeshBindingDesc structure; zero when unavailable. pub softMeshBindingDesc: usize, + /// Size in bytes of the queryOptions structure; zero when unavailable. pub queryOptions: usize, /// Zero unless 3D f32 robotics is enabled. pub urdfLoaderOptions: usize, /// Zero unless 3D f32 robotics is enabled. pub mjcfLoaderOptions: usize, } +/// Return POD structure sizes for checking foreign-language layouts against this library. +/// @ingroup errors #[rapier_export] pub extern "C" fn rpr_pod_layout() -> RprPodLayout { RprPodLayout { diff --git a/c/src/dynamics.rs b/c/src/dynamics.rs index 14c522f3d..3770cd3cc 100644 --- a/c/src/dynamics.rs +++ b/c/src/dynamics.rs @@ -706,6 +706,9 @@ pub(crate) unsafe fn native_rigid_body_colliders( }) } +/// Propagate all modified body poses to attached colliders. Run collision detection or step before +/// querying the broad phase. +/// @ingroup rigid_bodies #[rapier_export] pub unsafe extern "C" fn rpr_rigid_body_propagate_modified_body_positions_to_colliders( world: *mut RprWorld, @@ -745,6 +748,8 @@ pub(crate) unsafe fn native_rigid_body_set_gyroscopic_forces_enabled( } /// Copies the island manager's active body handles. +/// @see @ref output_buffers +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_active_rigid_bodies( world: *const RprWorld, @@ -768,6 +773,7 @@ pub unsafe extern "C" fn rpr_active_rigid_bodies( } /// Wake a body by handle, including a soft-body cluster proxy. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_wake_up( handle: RprRigidBodyHandle, diff --git a/c/src/error.rs b/c/src/error.rs index 6cae46d93..65437fcae 100644 --- a/c/src/error.rs +++ b/c/src/error.rs @@ -6,15 +6,33 @@ use std::{ }; /// Status-returning operations use these integer codes. +/// @ingroup errors pub type RprStatus = u32; +/// @ingroup errors +/// Operation succeeded. pub const RPR_OK: RprStatus = 0; +/// @ingroup errors +/// A required pointer was NULL. pub const RPR_NULL_POINTER: RprStatus = 1; +/// @ingroup errors +/// An argument failed validation. pub const RPR_INVALID_ARGUMENT: RprStatus = 2; +/// @ingroup errors +/// The entity handle is stale, invalid, or belongs to another world. pub const RPR_INVALID_HANDLE: RprStatus = 3; +/// @ingroup errors +/// Output capacity is insufficient; the returned count is the required capacity. pub const RPR_BUFFER_TOO_SMALL: RprStatus = 4; +/// @ingroup errors +/// This build or object does not support the operation. pub const RPR_UNSUPPORTED: RprStatus = 5; +/// @ingroup errors +/// Rust panicked; discard objects mutated by the call. pub const RPR_PANIC: RprStatus = 6; +/// @ingroup errors +/// No matching query result or object was found. pub const RPR_NOT_FOUND: RprStatus = 7; +/// @ingroup errors /// Conflicting or reentrant access to simulation state. No mutation was performed. pub const RPR_WORLD_BUSY: RprStatus = 8; pub(crate) type Result = std::result::Result; @@ -24,14 +42,18 @@ thread_local! { static LAST_ERROR: RefCell = RefCell::new(CString::defa /// The diagnostic is borrowed for the duration of the callback. The callback /// must return normally or terminate the process: never throw or longjmp across /// the Rust/C boundary. Nested failing calls do not invoke the handler recursively. +/// @ingroup errors pub type RprErrorCallback = Option; /// An optional thread-local error handler. A null callback disables reporting. /// Keep the callback and user_data alive until the handler is replaced. +/// @ingroup errors #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprErrorHandler { + /// Optional error callback; NULL disables notifications. pub callback: RprErrorCallback, + /// Application data; Rapier does not own pointers encoded in it. pub user_data: *mut c_void, } @@ -47,6 +69,7 @@ thread_local! { /// be restored at the end of a scope. Status returns are unchanged. A handler /// that returns lets the caller recover by checking the status; a fail-fast /// handler may terminate the process. Includes RPR_NOT_FOUND query misses. +/// @ingroup errors #[rapier_export] pub unsafe extern "C" fn rpr_set_error_handler(handler: RprErrorHandler) -> RprErrorHandler { ERROR_HANDLER.with(|current| current.replace(handler)) @@ -136,6 +159,7 @@ pub(crate) fn ffi(f: impl FnOnce() -> Result) -> RprStatus { /// LastError does not clear it. Infallible value constructors do not change it. /// Check immediately after a fallible value-returning operation when recovering /// from errors instead of using a fail-fast error callback. +/// @ingroup errors #[rapier_export] pub extern "C" fn rpr_last_status() -> RprStatus { LAST_STATUS.with(Cell::get) @@ -154,6 +178,7 @@ pub(crate) fn ffi_value(f: impl FnOnce(*mut T) -> RprStatus) -> T { } /// Thread-local UTF-8 diagnostic, valid until the next fallible call on this thread. +/// @ingroup errors #[rapier_export] pub extern "C" fn rpr_last_error() -> *const c_char { LAST_ERROR.with(|e| e.borrow().as_ptr()) diff --git a/c/src/extra.rs b/c/src/extra.rs index 4b4e5669c..7505b75e5 100644 --- a/c/src/extra.rs +++ b/c/src/extra.rs @@ -1,13 +1,19 @@ use crate::*; use rapier::geometry::ContactPair; -/// Explicit mass and principal inertia, matching MassProperties constructors. Zero mass/inertia means infinite. +/// Explicit mass and principal inertia, matching MassProperties constructors. Zero mass/inertia +/// means infinite. +/// @ingroup math #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprMassProperties { + /// Center of mass in local coordinates. pub local_com: RprVector, + /// Mass; nonnegative when supplied as input. pub mass: RprReal, + /// Principal angular inertia; scalar in 2D, three diagonal entries in 3D. pub principal_inertia: RprAngVector, #[cfg(feature = "dim3")] + /// Orientation of principal inertia axes in local coordinates (3D). pub principal_inertia_local_frame: RprRotation, } impl RprMassProperties { @@ -104,6 +110,9 @@ pub(crate) unsafe fn native_rigid_body_locked_axes( ffi(|| unsafe { output(out, get(body)?.0.locked_axes().bits()) }) } +/// Create an owned heightfield shape from copied samples. 3D samples are column-major, with rows * +/// columns entries. Release with rpr_free_shared_shape. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_heightfield_shared_shape( heights: RprRealView, @@ -202,6 +211,8 @@ pub(crate) unsafe fn impl_rpr_shared_shape_voxels_from_points( ) }) } +/// Compute the shape axis-aligned bounds at the supplied world-space pose. +/// @ingroup shapes #[rapier_export(shared_shape)] pub unsafe extern "C" fn rpr_shared_shape_compute_aabb( shape: *const RprSharedShape, @@ -220,6 +231,8 @@ pub unsafe extern "C" fn rpr_shared_shape_compute_aabb( }) }) } +/// Compute local mass properties for the supplied nonnegative density. +/// @ingroup shapes #[rapier_export(shared_shape)] pub unsafe extern "C" fn rpr_shared_shape_mass_properties( shape: *const RprSharedShape, @@ -232,6 +245,8 @@ pub unsafe extern "C" fn rpr_shared_shape_mass_properties( }) }) } +/// Test whether the world-space point lies inside the shape at pose. +/// @ingroup shapes #[rapier_export(shared_shape)] pub unsafe extern "C" fn rpr_shared_shape_contains_point( shape: *const RprSharedShape, @@ -246,15 +261,24 @@ pub unsafe extern "C" fn rpr_shared_shape_contains_point( }) }) } +/// Current narrow-phase pair and accumulated solver impulse summary. +/// @ingroup events #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprContactPair { + /// First collider in the pair. pub collider1: RprColliderHandle, + /// Second collider in the pair. pub collider2: RprColliderHandle, + /// Whether the pair has an active solver contact. pub has_any_active_contact: RprBool, + /// Sum of world-space contact impulse vectors. pub total_impulse: RprVector, + /// Sum of contact impulse magnitudes. pub total_impulse_magnitude: RprReal, + /// Largest contact impulse magnitude. pub max_impulse: RprReal, + /// World-space direction of the largest contact impulse. pub max_impulse_direction: RprVector, } impl From<&ContactPair> for RprContactPair { @@ -271,13 +295,21 @@ impl From<&ContactPair> for RprContactPair { } } } +/// Sensor intersection state for a collider pair. +/// @ingroup math #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprIntersectionPair { + /// First collider in the pair. pub collider1: RprColliderHandle, + /// Second collider in the pair. pub collider2: RprColliderHandle, + /// Whether the two sensor/collider shapes intersect. pub intersecting: RprBool, } +/// Copy current narrow-phase contact pairs, including pairs without active solver contacts. +/// @see @ref output_buffers +/// @ingroup events #[rapier_export] pub unsafe extern "C" fn rpr_contact_pairs( world: *const RprWorld, @@ -300,6 +332,8 @@ pub unsafe extern "C" fn rpr_contact_pairs( } } +/// Return the narrow-phase contact pair for two colliders, or report RPR_NOT_FOUND. +/// @ingroup events #[rapier_export] pub unsafe extern "C" fn rpr_contact_pair( collider1: RprColliderHandle, @@ -324,6 +358,9 @@ pub unsafe extern "C" fn rpr_contact_pair( }) } +/// Copy current sensor intersection pairs from the narrow phase. +/// @see @ref output_buffers +/// @ingroup events #[rapier_export] pub unsafe extern "C" fn rpr_intersection_pairs( world: *const RprWorld, @@ -354,18 +391,29 @@ pub unsafe extern "C" fn rpr_intersection_pairs( } } +/// One manifold contact and its normal solver impulse. +/// @ingroup events #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprContactPoint { + /// Index of the contact manifold within its pair. pub manifold_index: usize, + /// Contact point in collider 1 local coordinates. pub local_p1: RprVector, + /// Contact point in collider 2 local coordinates. pub local_p2: RprVector, + /// World-space contact or surface normal. pub normal: RprVector, + /// Signed separation; negative means penetration. pub distance: RprReal, + /// Normal impulse applied at this contact. pub impulse: RprReal, } -/// Contact points in collider-local space; normal in world space. Geometric manifolds may be recycled. +/// Contact points in collider-local space; normal in world space. Geometric manifolds may be +/// recycled. /// For clustered solver impulses use contact pair totals. Soft pairs have no rigid manifolds. +/// @see @ref output_buffers +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_contact_points( collider1: RprColliderHandle, @@ -407,6 +455,9 @@ pub unsafe extern "C" fn rpr_contact_points( }) } +/// Copy the articulation generalized velocities in native degree-of-freedom order. +/// @see @ref output_buffers +/// @ingroup joints #[rapier_export(multibody_joint)] pub unsafe extern "C" fn rpr_multibody_joint_generalized_velocity( handle: RprMultibodyJointHandle, @@ -429,6 +480,8 @@ pub unsafe extern "C" fn rpr_multibody_joint_generalized_velocity( }) } +/// Replace articulation generalized velocities; the array length must match its degrees of freedom. +/// @ingroup joints #[rapier_export(multibody_joint)] pub unsafe extern "C" fn rpr_multibody_joint_set_generalized_velocity( handle: RprMultibodyJointHandle, @@ -461,6 +514,7 @@ pub unsafe extern "C" fn rpr_multibody_joint_set_generalized_velocity( } /// Check this before passing any dimension/precision-dependent structs across the ABI. +/// @ingroup errors #[rapier_export] pub unsafe extern "C" fn rpr_check_abi( version: u32, @@ -529,16 +583,24 @@ pub(crate) unsafe fn native_collider_is_voxels( }) } /// Voxel coordinates have DIM signed integer components. +/// @ingroup math #[cfg(feature = "f32")] pub type RprVoxelCoord = i32; +/// Signed voxel coordinate integer. +/// @ingroup math #[cfg(feature = "f64")] pub type RprVoxelCoord = i64; +/// Integer coordinates of a voxel cell. +/// @ingroup math #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprVoxelKey { + /// X component. pub x: RprVoxelCoord, + /// Y component. pub y: RprVoxelCoord, #[cfg(feature = "dim3")] + /// Z component. pub z: RprVoxelCoord, } impl RprVoxelKey { diff --git a/c/src/geometry.rs b/c/src/geometry.rs index a80301755..0d49c9615 100644 --- a/c/src/geometry.rs +++ b/c/src/geometry.rs @@ -1,4 +1,6 @@ use crate::*; +/// Create an owned ball shape. Release it with rpr_free_shared_shape. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_ball_shared_shape(radius: RprReal) -> *mut RprSharedShape { ffi_value(|out: *mut *mut RprSharedShape| { @@ -10,6 +12,8 @@ pub unsafe extern "C" fn rpr_ball_shared_shape(radius: RprReal) -> *mut RprShare }) } +/// Create an owned cuboid shape. Release it with rpr_free_shared_shape. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_cuboid_shared_shape(half_extents: RprVector) -> *mut RprSharedShape { ffi_value(|out: *mut *mut RprSharedShape| { @@ -32,6 +36,8 @@ pub unsafe extern "C" fn rpr_cuboid_shared_shape(half_extents: RprVector) -> *mu }) } +/// Create an owned round cuboid shape. Release it with rpr_free_shared_shape. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_round_cuboid_shared_shape( half_extents: RprVector, @@ -58,6 +64,8 @@ pub unsafe extern "C" fn rpr_round_cuboid_shared_shape( }) } +/// Create an owned capsule shape. Release it with rpr_free_shared_shape. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_capsule_shared_shape( a: RprVector, @@ -73,6 +81,8 @@ pub unsafe extern "C" fn rpr_capsule_shared_shape( }) } +/// Create an owned segment shape. Release it with rpr_free_shared_shape. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_segment_shared_shape( a: RprVector, @@ -87,6 +97,8 @@ pub unsafe extern "C" fn rpr_segment_shared_shape( }) } +/// Create an owned triangle shape. Release it with rpr_free_shared_shape. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_triangle_shared_shape( a: RprVector, @@ -102,6 +114,8 @@ pub unsafe extern "C" fn rpr_triangle_shared_shape( }) } +/// Create an owned halfspace shape. Release it with rpr_free_shared_shape. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_halfspace_shared_shape(normal: RprVector) -> *mut RprSharedShape { ffi_value(|out: *mut *mut RprSharedShape| { @@ -115,6 +129,8 @@ pub unsafe extern "C" fn rpr_halfspace_shared_shape(normal: RprVector) -> *mut R }) } +/// Create an owned cylinder shape. Release it with rpr_free_shared_shape. +/// @ingroup shapes #[cfg(feature = "dim3")] #[rapier_export] pub unsafe extern "C" fn rpr_cylinder_shared_shape( @@ -130,6 +146,8 @@ pub unsafe extern "C" fn rpr_cylinder_shared_shape( }) } +/// Create an owned cone shape. Release it with rpr_free_shared_shape. +/// @ingroup shapes #[cfg(feature = "dim3")] #[rapier_export] pub unsafe extern "C" fn rpr_cone_shared_shape( @@ -224,6 +242,9 @@ pub(crate) unsafe fn indices_array( .map(|c| c.try_into().unwrap()) .collect()) } +/// Create an owned compound shape; each child pose is relative to the compound. Child shapes are +/// shared, not consumed. Release with rpr_free_shared_shape. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_compound_shared_shape( children: RprCompoundShapeView, @@ -677,6 +698,8 @@ pub(crate) unsafe fn native_collider_set_position_wrt_parent( }) } +/// Remove the collider and update its parent body mass properties. wake_up wakes the parent. +/// @ingroup colliders #[rapier_export] pub unsafe extern "C" fn rpr_remove_collider( handle: RprColliderHandle, diff --git a/c/src/geometry_views.rs b/c/src/geometry_views.rs index 64996e4cb..9a08652f7 100644 --- a/c/src/geometry_views.rs +++ b/c/src/geometry_views.rs @@ -1,7 +1,10 @@ //! Typed geometry input boundaries; view counts always count elements. use crate::handle_access::forward; use crate::*; +/// Create an owned compound shape by convex decomposition of the input surface. Release it with +/// rpr_free_shared_shape. /// Copies typed input geometry into an owned shared shape; arrays may be released on return. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_convex_decomposition_shared_shape( vertices: RprVectorView, @@ -21,7 +24,10 @@ pub unsafe extern "C" fn rpr_convex_decomposition_shared_shape( }) }) } +/// Create an owned voxel shape by quantizing points with the supplied per-axis voxel size. Release +/// it with rpr_free_shared_shape. /// Copies typed input geometry into an owned shared shape; arrays may be released on return. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_voxels_shared_shape_from_points( voxel_size: RprVector, @@ -39,7 +45,10 @@ pub unsafe extern "C" fn rpr_voxels_shared_shape_from_points( }) }) } +/// Create an owned voxel shape from a surface mesh with the supplied uniform voxel size. Release it +/// with rpr_free_shared_shape. /// Copies typed input geometry into an owned shared shape; arrays may be released on return. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_voxelized_mesh_shared_shape( vertices: RprVectorView, @@ -61,7 +70,9 @@ pub unsafe extern "C" fn rpr_voxelized_mesh_shared_shape( }) }) } +/// Create an owned convex hull of the supplied vertices. Release it with rpr_free_shared_shape. /// Copies typed input geometry into an owned shared shape; arrays may be released on return. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_convex_hull_shared_shape( vertices: RprVectorView, @@ -77,7 +88,10 @@ pub unsafe extern "C" fn rpr_convex_hull_shared_shape( }) }) } +/// Create an owned triangle mesh from vertices and triangle indices. Release it with +/// rpr_free_shared_shape. /// Copies typed input geometry into an owned shared shape; arrays may be released on return. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_trimesh_shared_shape( vertices: RprVectorView, @@ -97,7 +111,9 @@ pub unsafe extern "C" fn rpr_trimesh_shared_shape( }) }) } +/// Create an owned polyline from vertices and edge indices. Release it with rpr_free_shared_shape. /// Copies typed input geometry into an owned shared shape; arrays may be released on return. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_polyline_shared_shape( vertices: RprVectorView, @@ -117,8 +133,11 @@ pub unsafe extern "C" fn rpr_polyline_shared_shape( }) }) } -#[cfg(feature = "dim2")] +/// Create an owned oriented 2D polyline from vertices and edge indices. Release it with +/// rpr_free_shared_shape. /// Copies typed input geometry into an owned shared shape; arrays may be released on return. +/// @ingroup shapes +#[cfg(feature = "dim2")] #[rapier_export] pub unsafe extern "C" fn rpr_oriented_polyline_shared_shape( vertices: RprVectorView, @@ -138,8 +157,11 @@ pub unsafe extern "C" fn rpr_oriented_polyline_shared_shape( }) }) } -#[cfg(feature = "dim2")] +/// Create an owned convex polygon from vertices already ordered along its boundary. Release it with +/// rpr_free_shared_shape. /// Copies typed input geometry into an owned shared shape; arrays may be released on return. +/// @ingroup shapes +#[cfg(feature = "dim2")] #[rapier_export] pub unsafe extern "C" fn rpr_convex_polyline_shared_shape( vertices: RprVectorView, @@ -155,7 +177,9 @@ pub unsafe extern "C" fn rpr_convex_polyline_shared_shape( }) }) } +/// Create an owned round convex hull shape. Release it with rpr_free_shared_shape. /// Copies typed input geometry into an owned shared shape; arrays may be released on return. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_round_convex_hull_shared_shape( vertices: RprVectorView, @@ -173,7 +197,10 @@ pub unsafe extern "C" fn rpr_round_convex_hull_shared_shape( }) }) } +/// Create an owned triangle mesh with the supplied TRIMESH_* processing flags. Release it with +/// rpr_free_shared_shape. /// Copies typed input geometry into an owned shared shape; arrays may be released on return. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_trimesh_shared_shape_with_flags( vertices: RprVectorView, diff --git a/c/src/handle_access.rs b/c/src/handle_access.rs index 1763221ef..6e6b8ab78 100644 --- a/c/src/handle_access.rs +++ b/c/src/handle_access.rs @@ -17,7 +17,8 @@ pub(crate) unsafe fn forward(status: RprStatus) -> Result { } } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return the rigid body world-space pose. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_position(handle: RprRigidBodyHandle) -> RprPose { let world = handle.world; @@ -50,7 +51,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_position( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return the rigid body world-space translation. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_translation(handle: RprRigidBodyHandle) -> RprVector { let world = handle.world; @@ -83,7 +85,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_translation( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return the rigid body world-space linear velocity. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_linvel(handle: RprRigidBodyHandle) -> RprVector { let world = handle.world; @@ -116,7 +119,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_linvel( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return the rigid body world-space angular velocity (radians per second). +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_angvel(handle: RprRigidBodyHandle) -> RprAngVector { let world = handle.world; @@ -149,7 +153,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_angvel( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return whether the rigid body is sleeping. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_is_sleeping(handle: RprRigidBodyHandle) -> RprBool { let world = handle.world; @@ -182,7 +187,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_is_sleeping( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return whether the rigid body is enabled. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_is_enabled(handle: RprRigidBodyHandle) -> RprBool { let world = handle.world; @@ -215,7 +221,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_is_enabled( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return the rigid body application-owned 128-bit user value. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_user_data(handle: RprRigidBodyHandle) -> RprUserData { let world = handle.world; @@ -248,7 +255,9 @@ pub(crate) unsafe fn native_rigid_body_set_get_user_data( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the rigid body world-space pose. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_position( handle: RprRigidBodyHandle, @@ -276,7 +285,9 @@ pub unsafe extern "C" fn rpr_rigid_body_set_position( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the rigid body world-space translation. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_translation( handle: RprRigidBodyHandle, @@ -304,7 +315,9 @@ pub unsafe extern "C" fn rpr_rigid_body_set_translation( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the rigid body world-space linear velocity. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_linvel( handle: RprRigidBodyHandle, @@ -332,7 +345,9 @@ pub unsafe extern "C" fn rpr_rigid_body_set_linvel( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the rigid body world-space angular velocity (radians per second). +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_angvel( handle: RprRigidBodyHandle, @@ -360,7 +375,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_angvel( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the rigid body next kinematic world-space pose. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_next_kinematic_position( handle: RprRigidBodyHandle, @@ -386,7 +402,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_next_kinematic_position( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the rigid body next kinematic world-space translation. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_next_kinematic_translation( handle: RprRigidBodyHandle, @@ -412,7 +429,9 @@ pub unsafe extern "C" fn rpr_rigid_body_set_next_kinematic_translation( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the rigid body gravity multiplier. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_gravity_scale( handle: RprRigidBodyHandle, @@ -440,7 +459,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_gravity_scale( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the rigid body linear damping coefficient. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_linear_damping( handle: RprRigidBodyHandle, @@ -478,7 +498,8 @@ pub(crate) unsafe fn native_rigid_body_set_set_linear_damping( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the rigid body angular damping coefficient. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_angular_damping( handle: RprRigidBodyHandle, @@ -504,7 +525,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_angular_damping( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Enable or disable the rigid body. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_enabled( handle: RprRigidBodyHandle, @@ -542,7 +564,8 @@ pub(crate) unsafe fn native_rigid_body_set_set_enabled( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the rigid body application-owned 128-bit user value. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_user_data( handle: RprRigidBodyHandle, @@ -568,7 +591,9 @@ pub unsafe extern "C" fn rpr_rigid_body_set_user_data( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Apply a world-space linear impulse. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_apply_impulse( handle: RprRigidBodyHandle, @@ -596,7 +621,9 @@ pub unsafe extern "C" fn rpr_rigid_body_apply_impulse( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Apply a world-space impulse at a world-space point. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_apply_impulse_at_point( handle: RprRigidBodyHandle, @@ -626,7 +653,9 @@ pub unsafe extern "C" fn rpr_rigid_body_apply_impulse_at_point( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Accumulate a world-space force; it persists until reset. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_add_force( handle: RprRigidBodyHandle, @@ -654,7 +683,9 @@ pub unsafe extern "C" fn rpr_rigid_body_add_force( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Clear accumulated user forces. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_reset_forces( handle: RprRigidBodyHandle, @@ -680,7 +711,8 @@ pub unsafe extern "C" fn rpr_rigid_body_reset_forces( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Put the body to sleep. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_sleep(handle: RprRigidBodyHandle) -> RprStatus { let world = handle.world; @@ -700,7 +732,8 @@ pub unsafe extern "C" fn rpr_rigid_body_sleep(handle: RprRigidBodyHandle) -> Rpr }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return the collider world-space pose. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_position(handle: RprColliderHandle) -> RprPose { let world = handle.world; @@ -733,7 +766,8 @@ pub(crate) unsafe fn native_collider_set_get_position( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return the collider world-space translation. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_translation(handle: RprColliderHandle) -> RprVector { let world = handle.world; @@ -766,7 +800,8 @@ pub(crate) unsafe fn native_collider_set_get_translation( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return the collider friction coefficient. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_friction(handle: RprColliderHandle) -> RprReal { let world = handle.world; @@ -799,7 +834,8 @@ pub(crate) unsafe fn native_collider_set_get_friction( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return the collider restitution coefficient. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_restitution(handle: RprColliderHandle) -> RprReal { let world = handle.world; @@ -832,7 +868,8 @@ pub(crate) unsafe fn native_collider_set_get_restitution( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return whether the collider is a sensor (detects overlaps without contact forces). +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_is_sensor(handle: RprColliderHandle) -> RprBool { let world = handle.world; @@ -865,7 +902,8 @@ pub(crate) unsafe fn native_collider_set_get_is_sensor( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return the parent body handle, or an invalid handle with OK status for a standalone collider. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_parent(handle: RprColliderHandle) -> RprRigidBodyHandle { let world = handle.world; @@ -898,7 +936,8 @@ pub(crate) unsafe fn native_collider_set_get_parent( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the collider world-space pose. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_position( handle: RprColliderHandle, @@ -920,7 +959,8 @@ pub unsafe extern "C" fn rpr_collider_set_position( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the collider world-space translation. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_translation( handle: RprColliderHandle, @@ -942,7 +982,8 @@ pub unsafe extern "C" fn rpr_collider_set_translation( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the collider friction coefficient. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_friction( handle: RprColliderHandle, @@ -964,7 +1005,8 @@ pub unsafe extern "C" fn rpr_collider_set_friction( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the collider restitution coefficient. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_restitution( handle: RprColliderHandle, @@ -986,7 +1028,8 @@ pub unsafe extern "C" fn rpr_collider_set_restitution( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Enable or disable a sensor (detects overlaps without contact forces) for the collider. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_sensor( handle: RprColliderHandle, @@ -1008,7 +1051,8 @@ pub unsafe extern "C" fn rpr_collider_set_sensor( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the collider collision filtering groups. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_collision_groups( handle: RprColliderHandle, @@ -1030,7 +1074,8 @@ pub unsafe extern "C" fn rpr_collider_set_collision_groups( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the collider application-owned 128-bit user value. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_user_data( handle: RprColliderHandle, @@ -1052,7 +1097,8 @@ pub unsafe extern "C" fn rpr_collider_set_user_data( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return the world-space position of the indexed particle. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_particle_position( handle: RprSoftBodyHandle, @@ -1077,7 +1123,9 @@ pub unsafe extern "C" fn rpr_soft_body_particle_position( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Copy world-space particle positions. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_particle_positions( handle: RprSoftBodyHandle, @@ -1104,7 +1152,8 @@ pub unsafe extern "C" fn rpr_soft_body_particle_positions( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Return a copy of the soft body material parameters. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_material(handle: RprSoftBodyHandle) -> RprSoftBodyMaterial { let world = handle.world; @@ -1125,7 +1174,8 @@ pub unsafe extern "C" fn rpr_soft_body_material(handle: RprSoftBodyHandle) -> Rp }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Set the world-space position of the indexed particle. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_particle_position( handle: RprSoftBodyHandle, @@ -1149,7 +1199,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_particle_position( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Copy material parameters into the soft body. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_material( handle: RprSoftBodyHandle, @@ -1171,7 +1222,9 @@ pub unsafe extern "C" fn rpr_soft_body_set_material( }) } -/// Resolves the handle for this call only. Reports INVALID_HANDLE for a removed/stale element. +/// Add a world-space force to the indexed particle. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_add_particle_force( handle: RprSoftBodyHandle, @@ -1198,15 +1251,22 @@ pub unsafe extern "C" fn rpr_soft_body_add_particle_force( } /// A copied state snapshot, with no pointers or ownership obligations. +/// @ingroup rigid_bodies #[repr(C)] #[derive(Clone, Copy)] #[allow(non_snake_case)] pub struct RprRigidBodyState { + /// World-space pose. pub position: RprPose, + /// World-space linear velocity. pub linvel: RprVector, + /// World-space angular velocity in radians per second. pub angvel: RprAngVector, + /// Whether the body starts/is asleep. pub sleeping: RprBool, + /// Whether this setting/object is enabled (0 or 1). pub enabled: RprBool, + /// Application data; Rapier does not own pointers encoded in it. pub userData: RprUserData, } impl From<&RigidBody> for RprRigidBodyState { @@ -1224,7 +1284,8 @@ impl From<&RigidBody> for RprRigidBodyState { /// Copies states in the same order as handles, without allocating temporary storage. /// All handles are validated before writing. On INVALID_HANDLE outputs are unchanged. -/// NULL/0 is a size query. BUFFER_TOO_SMALL updates count but leaves states untouched. +/// NULL/0 is a size query. BUFFER_TOO_SMALL returns the required count and leaves states untouched. +/// @ingroup rigid_bodies #[rapier_export] pub unsafe extern "C" fn rpr_rigid_body_read_states( world: *const RprWorld, @@ -1290,6 +1351,7 @@ pub(crate) unsafe fn native_rigid_body_set_read_states( } /// Copies joint configuration without returning a borrowed joint pointer. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_desc(handle: RprImpulseJointHandle) -> RprJointDesc { let world = handle.world; @@ -1315,6 +1377,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_desc(handle: RprImpulseJointHandle) - } /// Replaces configuration after validation, resetting cached limit/motor impulses. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_desc( handle: RprImpulseJointHandle, diff --git a/c/src/joint_access.rs b/c/src/joint_access.rs index 5214ec538..416bce0ab 100644 --- a/c/src/joint_access.rs +++ b/c/src/joint_access.rs @@ -1,6 +1,8 @@ //! Joint configuration setters and handle-scoped live-joint edits. use crate::handle_access::forward; use crate::*; +/// Set the joint desc joint frame relative to body 1. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_local_frame1( desc: *mut RprJointDesc, @@ -12,6 +14,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_local_frame1( output(desc, joint.0.into()) }) } +/// Set the impulse joint joint frame relative to body 1. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_local_frame1( handle: RprImpulseJointHandle, @@ -38,6 +43,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_local_frame1( }) } +/// Set the joint desc joint frame relative to body 2. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_local_frame2( desc: *mut RprJointDesc, @@ -49,6 +56,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_local_frame2( output(desc, joint.0.into()) }) } +/// Set the impulse joint joint frame relative to body 2. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_local_frame2( handle: RprImpulseJointHandle, @@ -75,6 +85,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_local_frame2( }) } +/// Set the joint desc joint anchor relative to body 1. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_local_anchor1( desc: *mut RprJointDesc, @@ -86,6 +98,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_local_anchor1( output(desc, joint.0.into()) }) } +/// Set the impulse joint joint anchor relative to body 1. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_local_anchor1( handle: RprImpulseJointHandle, @@ -112,6 +127,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_local_anchor1( }) } +/// Set the joint desc joint anchor relative to body 2. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_local_anchor2( desc: *mut RprJointDesc, @@ -123,6 +140,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_local_anchor2( output(desc, joint.0.into()) }) } +/// Set the impulse joint joint anchor relative to body 2. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_local_anchor2( handle: RprImpulseJointHandle, @@ -149,6 +169,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_local_anchor2( }) } +/// Enable or disable allowing contacts between connected bodies for the joint desc. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_contacts_enabled( desc: *mut RprJointDesc, @@ -160,6 +182,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_contacts_enabled( output(desc, joint.0.into()) }) } +/// Enable or disable allowing contacts between connected bodies for the impulse joint. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_contacts_enabled( handle: RprImpulseJointHandle, @@ -186,6 +211,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_contacts_enabled( }) } +/// Enable or disable the joint desc. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_enabled( desc: *mut RprJointDesc, @@ -197,6 +224,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_enabled( output(desc, joint.0.into()) }) } +/// Enable or disable the impulse joint. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_enabled( handle: RprImpulseJointHandle, @@ -223,6 +253,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_enabled( }) } +/// Set the joint desc joint spring coefficients. +/// @ingroup soft_bodies #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_softness( desc: *mut RprJointDesc, @@ -234,6 +266,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_softness( output(desc, joint.0.into()) }) } +/// Set the impulse joint joint spring coefficients. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup soft_bodies #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_softness( handle: RprImpulseJointHandle, @@ -260,6 +295,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_softness( }) } +/// Set the joint desc translation/rotation lock bitmask. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_locked_axes( desc: *mut RprJointDesc, @@ -271,6 +308,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_locked_axes( output(desc, joint.0.into()) }) } +/// Set the impulse joint translation/rotation lock bitmask. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_locked_axes( handle: RprImpulseJointHandle, @@ -297,6 +337,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_locked_axes( }) } +/// Set the joint desc joint axis mask with limits enabled. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_limit_axes( desc: *mut RprJointDesc, @@ -308,6 +350,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_limit_axes( output(desc, joint.0.into()) }) } +/// Set the impulse joint joint axis mask with limits enabled. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_limit_axes( handle: RprImpulseJointHandle, @@ -334,6 +379,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_limit_axes( }) } +/// Set the joint desc joint axis mask with motors enabled. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_motor_axes( desc: *mut RprJointDesc, @@ -345,6 +392,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_motor_axes( output(desc, joint.0.into()) }) } +/// Set the impulse joint joint axis mask with motors enabled. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_motor_axes( handle: RprImpulseJointHandle, @@ -371,6 +421,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_motor_axes( }) } +/// Set the joint desc coupled joint axis mask. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_coupled_axes( desc: *mut RprJointDesc, @@ -382,6 +434,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_coupled_axes( output(desc, joint.0.into()) }) } +/// Set the impulse joint coupled joint axis mask. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_coupled_axes( handle: RprImpulseJointHandle, @@ -408,6 +463,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_coupled_axes( }) } +/// Set the joint desc joint principal axis in body 1 local coordinates. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_local_axis1( desc: *mut RprJointDesc, @@ -419,6 +476,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_local_axis1( output(desc, joint.0.into()) }) } +/// Set the impulse joint joint principal axis in body 1 local coordinates. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_local_axis1( handle: RprImpulseJointHandle, @@ -445,6 +505,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_local_axis1( }) } +/// Set the joint desc joint principal axis in body 2 local coordinates. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_local_axis2( desc: *mut RprJointDesc, @@ -456,6 +518,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_local_axis2( output(desc, joint.0.into()) }) } +/// Set the impulse joint joint principal axis in body 2 local coordinates. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_local_axis2( handle: RprImpulseJointHandle, @@ -482,6 +547,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_local_axis2( }) } +/// Set the joint desc minimum and maximum limits on an axis (linear distance or angular radians). +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_limits( desc: *mut RprJointDesc, @@ -497,6 +564,10 @@ pub unsafe extern "C" fn rpr_joint_desc_set_limits( output(desc, joint.0.into()) }) } +/// Set the impulse joint minimum and maximum limits on an axis (linear distance or angular +/// radians). +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_limits( handle: RprImpulseJointHandle, @@ -527,6 +598,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_limits( }) } +/// Set the joint desc motor position/velocity targets and spring coefficients on an axis. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_motor( desc: *mut RprJointDesc, @@ -549,6 +622,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_motor( output(desc, joint.0.into()) }) } +/// Set the impulse joint motor position/velocity targets and spring coefficients on an axis. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_motor( handle: RprImpulseJointHandle, @@ -583,6 +659,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_motor( }) } +/// Set the joint desc maximum motor force or torque on an axis. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_motor_max_force( desc: *mut RprJointDesc, @@ -597,6 +675,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_motor_max_force( output(desc, joint.0.into()) }) } +/// Set the impulse joint maximum motor force or torque on an axis. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_motor_max_force( handle: RprImpulseJointHandle, @@ -625,6 +706,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_motor_max_force( }) } +/// Set the joint desc motor model on an axis (0 = acceleration-based, 1 = force-based). +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_motor_model( desc: *mut RprJointDesc, @@ -639,6 +722,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_motor_model( output(desc, joint.0.into()) }) } +/// Set the impulse joint motor model on an axis (0 = acceleration-based, 1 = force-based). +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_motor_model( handle: RprImpulseJointHandle, @@ -667,6 +753,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_motor_model( }) } +/// Set the joint desc application-owned 128-bit user value. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_user_data( desc: *mut RprJointDesc, @@ -678,6 +766,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_user_data( output(desc, joint.0.into()) }) } +/// Set the impulse joint application-owned 128-bit user value. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_user_data( handle: RprImpulseJointHandle, @@ -704,6 +795,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_user_data( }) } +/// Set the joint desc motor position target and spring coefficients on an axis. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_motor_position( desc: *mut RprJointDesc, @@ -724,6 +817,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_motor_position( output(desc, joint.0.into()) }) } +/// Set the impulse joint motor position target and spring coefficients on an axis. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_motor_position( handle: RprImpulseJointHandle, @@ -756,6 +852,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_set_motor_position( }) } +/// Set the joint desc motor velocity target and damping factor on an axis. +/// @ingroup joints #[rapier_export(joint_desc)] pub unsafe extern "C" fn rpr_joint_desc_set_motor_velocity( desc: *mut RprJointDesc, @@ -774,6 +872,9 @@ pub unsafe extern "C" fn rpr_joint_desc_set_motor_velocity( output(desc, joint.0.into()) }) } +/// Set the impulse joint motor velocity target and damping factor on an axis. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_set_motor_velocity( handle: RprImpulseJointHandle, diff --git a/c/src/joint_desc.rs b/c/src/joint_desc.rs index 9f35aa212..60e858ebb 100644 --- a/c/src/joint_desc.rs +++ b/c/src/joint_desc.rs @@ -1,42 +1,71 @@ //! Plain joint configuration, distinct from a borrowed live joint. #![allow(non_snake_case)] use crate::*; +/// @ingroup joints +/// Number of translational and angular joint axes in this dimension. #[cfg(feature = "dim2")] pub const RPR_JOINT_DOF_COUNT: usize = 3; +/// @ingroup joints +/// Number of translational and angular joint axes in this dimension. #[cfg(feature = "dim3")] pub const RPR_JOINT_DOF_COUNT: usize = 6; +/// Lower and upper axis limits in length units or radians. +/// @ingroup joints #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprJointLimits { + /// Minimum allowed axis displacement (length or radians). pub min: RprReal, + /// Maximum allowed axis displacement (length or radians). pub max: RprReal, } +/// Position/velocity motor settings for one joint axis. +/// @ingroup joints #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprJointMotor { + /// Motor target velocity (length per second or radians per second). pub targetVel: RprReal, + /// Motor target position (length or radians). pub targetPos: RprReal, + /// Nonnegative motor spring stiffness. pub stiffness: RprReal, + /// Nonnegative motor damping. pub damping: RprReal, + /// Nonnegative maximum force or torque. pub maxForce: RprReal, + /// Motor model: 0 acceleration-based, 1 force-based. pub model: u32, } /// Copyable joint configuration. Limits/motors take effect when their axis mask is enabled. /// Solver impulses are deliberately excluded. Applying data resets cached limit and motor impulses. +/// @ingroup joints #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprJointDesc { + /// Joint frame in body 1 local coordinates. pub localFrame1: RprPose, + /// Joint frame in body 2 local coordinates. pub localFrame2: RprPose, + /// Locked joint degrees of freedom; translations precede rotations. pub lockedAxes: u8, + /// Axis mask enabling corresponding limits entries. pub limitAxes: u8, + /// Axis mask enabling corresponding motors entries. pub motorAxes: u8, + /// Axis mask sharing a coupled constraint. pub coupledAxes: u8, + /// Axis limits in translation-then-rotation order. pub limits: [RprJointLimits; RPR_JOINT_DOF_COUNT], + /// Axis motors in translation-then-rotation order. pub motors: [RprJointMotor; RPR_JOINT_DOF_COUNT], + /// Spring coefficients for constraint correction. pub softness: RprSpringCoefficients, + /// Whether connected bodies may collide. pub contactsEnabled: RprBool, + /// Whether this setting/object is enabled (0 or 1). pub enabled: RprBool, + /// Application data; Rapier does not own pointers encoded in it. pub userData: RprUserData, } impl From for RprJointDesc { @@ -107,6 +136,8 @@ impl RprJointDesc { Ok(j) } } +/// Return native default joint desc. This POD value owns no resources. +/// @ingroup joints #[rapier_export] pub extern "C" fn rpr_default_joint_desc() -> RprJointDesc { GenericJoint::new(JointAxesMask::empty()).into() @@ -139,30 +170,40 @@ fn joint_desc_with_axis(locked_axes: JointAxesMask, axis: RprVector) -> RprJoint desc } +/// Return a fixed joint description with native defaults; no allocation. +/// @ingroup joints #[rapier_export] pub extern "C" fn rpr_fixed_joint_desc() -> RprJointDesc { GenericJoint::from(FixedJointBuilder::new().build()).into() } +/// Return a revolute joint description with native defaults; no allocation. +/// @ingroup joints #[cfg(feature = "dim2")] #[rapier_export] pub extern "C" fn rpr_revolute_joint_desc() -> RprJointDesc { GenericJoint::from(RevoluteJointBuilder::new().build()).into() } /// Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. +/// @ingroup joints #[cfg(feature = "dim3")] #[rapier_export] pub extern "C" fn rpr_revolute_joint_desc(axis_vector: RprVector) -> RprJointDesc { joint_desc_with_axis(JointAxesMask::LOCKED_REVOLUTE_AXES, axis_vector) } /// Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. +/// @ingroup joints #[rapier_export] pub extern "C" fn rpr_prismatic_joint_desc(axis_vector: RprVector) -> RprJointDesc { joint_desc_with_axis(JointAxesMask::LOCKED_PRISMATIC_AXES, axis_vector) } +/// Return a rope joint description with native defaults; no allocation. +/// @ingroup joints #[rapier_export] pub extern "C" fn rpr_rope_joint_desc(length: RprReal) -> RprJointDesc { GenericJoint::from(RopeJointBuilder::new(length).build()).into() } +/// Return a spring joint description with native defaults; no allocation. +/// @ingroup joints #[rapier_export] pub extern "C" fn rpr_spring_joint_desc( length: RprReal, @@ -171,17 +212,23 @@ pub extern "C" fn rpr_spring_joint_desc( ) -> RprJointDesc { GenericJoint::from(SpringJointBuilder::new(length, stiffness, damping).build()).into() } +/// Return a spherical joint description with native defaults; no allocation. +/// @ingroup joints #[cfg(feature = "dim3")] #[rapier_export] pub extern "C" fn rpr_spherical_joint_desc() -> RprJointDesc { GenericJoint::from(SphericalJointBuilder::new().build()).into() } -#[cfg(feature = "dim2")] /// Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. +/// @ingroup joints +#[cfg(feature = "dim2")] #[rapier_export] pub extern "C" fn rpr_pin_slot_joint_desc(axis_vector: RprVector) -> RprJointDesc { joint_desc_with_axis(JointAxesMask::LOCKED_PIN_SLOT_AXES, axis_vector) } +/// Create an impulse joint connecting two bodies in the same world. The world owns the joint; +/// wake_up wakes the connected bodies. +/// @ingroup joints #[rapier_export] pub unsafe extern "C" fn rpr_insert_impulse_joint( body1: RprRigidBodyHandle, @@ -215,6 +262,9 @@ pub unsafe extern "C" fn rpr_insert_impulse_joint( }) } +/// Create an articulation joint between bodies in the same world. Returns an invalid handle on +/// failure; check rpr_last_status. +/// @ingroup joints #[rapier_export] pub unsafe extern "C" fn rpr_insert_multibody_joint( body1: RprRigidBodyHandle, diff --git a/c/src/joints.rs b/c/src/joints.rs index 53bea5d73..c3f86c3f1 100644 --- a/c/src/joints.rs +++ b/c/src/joints.rs @@ -231,6 +231,8 @@ pub(crate) unsafe fn rpr_generic_joint_set_user_data( }) } +/// Remove an impulse joint. wake_up wakes its connected bodies. +/// @ingroup joints #[rapier_export] pub unsafe extern "C" fn rpr_remove_impulse_joint( handle: RprImpulseJointHandle, @@ -252,6 +254,9 @@ pub unsafe extern "C" fn rpr_remove_impulse_joint( }) } +/// Copy entity handles. +/// @see @ref output_buffers +/// @ingroup joints #[rapier_export] pub unsafe extern "C" fn rpr_impulse_joint_handles( world: *const RprWorld, @@ -274,6 +279,8 @@ pub unsafe extern "C" fn rpr_impulse_joint_handles( } } +/// Remove an articulation joint. wake_up wakes affected bodies. +/// @ingroup joints #[rapier_export] pub unsafe extern "C" fn rpr_remove_multibody_joint( handle: RprMultibodyJointHandle, @@ -296,6 +303,9 @@ pub unsafe extern "C" fn rpr_remove_multibody_joint( }) } +/// Copy entity handles. +/// @see @ref output_buffers +/// @ingroup joints #[rapier_export] pub unsafe extern "C" fn rpr_multibody_joint_handles( world: *const RprWorld, @@ -318,6 +328,8 @@ pub unsafe extern "C" fn rpr_multibody_joint_handles( } } +/// Return the two bodies connected by an impulse joint. +/// @ingroup joints #[rapier_export(impulse_joint)] pub unsafe extern "C" fn rpr_impulse_joint_bodies(handle: RprImpulseJointHandle) -> RprJointBodies { let world = handle.world; @@ -341,15 +353,24 @@ pub unsafe extern "C" fn rpr_impulse_joint_bodies(handle: RprImpulseJointHandle) }) } +/// Damped least-squares inverse-kinematics parameters. +/// @ingroup math #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprInverseKinematicsOptions { + /// Nonnegative motor damping. pub damping: RprReal, + /// Maximum inverse-kinematics iterations. pub max_iters: usize, + /// Controlled-axis bitmask; translations precede rotations. pub constrained_axes: u8, + /// Linear convergence tolerance. pub epsilon_linear: RprReal, + /// Angular convergence tolerance in radians. pub epsilon_angular: RprReal, } +/// Return native default inverse kinematics options. This POD value owns no resources. +/// @ingroup joints #[rapier_export] pub extern "C" fn rpr_default_inverse_kinematics_options() -> RprInverseKinematicsOptions { let options = InverseKinematicsOption::default(); @@ -362,6 +383,8 @@ pub extern "C" fn rpr_default_inverse_kinematics_options() -> RprInverseKinemati } } +/// Return the articulation degrees of freedom associated with the joint. +/// @ingroup joints #[rapier_export(multibody_joint)] pub unsafe extern "C" fn rpr_multibody_joint_ndofs(handle: RprMultibodyJointHandle) -> usize { let world = handle.world; @@ -383,9 +406,11 @@ pub unsafe extern "C" fn rpr_multibody_joint_ndofs(handle: RprMultibodyJointHand } /// Optional per-link filter, called synchronously. Must not reenter or retain physics objects. +/// @ingroup joints pub type RprIkJointCanMove = Option RprBool>; /// Read/write displacement buffer must contain exactly ndofs entries; zero it for a fresh solve. +/// @ingroup joints #[rapier_export(multibody_joint)] pub unsafe extern "C" fn rpr_multibody_joint_inverse_kinematics( handle: RprMultibodyJointHandle, @@ -447,6 +472,8 @@ pub unsafe extern "C" fn rpr_multibody_joint_inverse_kinematics( }) } +/// Apply generalized articulation displacements in native degree-of-freedom order. +/// @ingroup joints #[rapier_export(multibody_joint)] pub unsafe extern "C" fn rpr_multibody_joint_apply_displacements( handle: RprMultibodyJointHandle, diff --git a/c/src/objects.rs b/c/src/objects.rs index 9f25cc9d1..948f477a6 100644 --- a/c/src/objects.rs +++ b/c/src/objects.rs @@ -56,9 +56,11 @@ pub(crate) struct RprCollider(pub(crate) Collider); pub(crate) struct RprGenericJoint(pub(crate) GenericJoint); /// Opaque SharedShape. See the ownership and borrowing contract in README.md. +/// @ingroup shapes #[repr(transparent)] pub struct RprSharedShape(pub(crate) SharedShape); /// Frees an owned object; NULL is allowed. Never free a borrowed pointer. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_free_shared_shape(object: *mut RprSharedShape) -> RprStatus { ffi(|| unsafe { @@ -70,7 +72,9 @@ pub unsafe extern "C" fn rpr_free_shared_shape(object: *mut RprSharedShape) -> R }) } -/// Creates an independent owned copy. +/// Create an owned wrapper sharing the same immutable geometry. Release it with +/// rpr_free_shared_shape. +/// @ingroup shapes #[rapier_export(shared_shape)] pub unsafe extern "C" fn rpr_shared_shape_clone( object: *const RprSharedShape, @@ -88,6 +92,8 @@ pub unsafe extern "C" fn rpr_shared_shape_clone( #[repr(transparent)] pub(crate) struct RprSoftBody(pub(crate) SoftBody); +/// Return the number of rigid body objects in the world. +/// @ingroup rigid_bodies #[rapier_export] pub unsafe extern "C" fn rpr_rigid_body_count(world: *const RprWorld) -> usize { ffi_value(|out: *mut usize| { @@ -113,6 +119,9 @@ pub(crate) unsafe fn native_rigid_body_set_len( }) } +/// Copy entity handles. +/// @see @ref output_buffers +/// @ingroup rigid_bodies #[rapier_export] pub unsafe extern "C" fn rpr_rigid_body_handles( world: *const RprWorld, @@ -148,6 +157,9 @@ pub(crate) unsafe fn native_rigid_body_set_handles( }) } +/// Test whether the live world contains this rigid body handle. A removed/stale handle returns +/// false. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_contains(handle: RprRigidBodyHandle) -> RprBool { let world = handle.world; @@ -174,6 +186,8 @@ pub(crate) unsafe fn native_rigid_body_set_contains( ffi(|| unsafe { output(out, get(set)?.0.get(handle.raw()).is_some() as u32) }) } +/// Return the number of collider objects in the world. +/// @ingroup colliders #[rapier_export] pub unsafe extern "C" fn rpr_collider_count(world: *const RprWorld) -> usize { ffi_value(|out: *mut usize| { @@ -199,6 +213,9 @@ pub(crate) unsafe fn native_collider_set_len( }) } +/// Copy entity handles. +/// @see @ref output_buffers +/// @ingroup colliders #[rapier_export] pub unsafe extern "C" fn rpr_collider_handles( world: *const RprWorld, @@ -234,6 +251,8 @@ pub(crate) unsafe fn native_collider_set_handles( }) } +/// Test whether the live world contains this collider handle. A removed/stale handle returns false. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_contains(handle: RprColliderHandle) -> RprBool { let world = handle.world; @@ -260,6 +279,8 @@ pub(crate) unsafe fn native_collider_set_contains( ffi(|| unsafe { output(out, get(set)?.0.get(handle.raw()).is_some() as u32) }) } +/// Return the number of soft body objects in the world. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_soft_body_count(world: *const RprWorld) -> usize { ffi_value(|out: *mut usize| { @@ -275,6 +296,9 @@ pub unsafe extern "C" fn rpr_soft_body_count(world: *const RprWorld) -> usize { }) } +/// Copy entity handles. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_soft_body_handles( world: *const RprWorld, @@ -296,6 +320,9 @@ pub unsafe extern "C" fn rpr_soft_body_handles( } } +/// Test whether the live world contains this soft body handle. A removed/stale handle returns +/// false. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_contains(handle: RprSoftBodyHandle) -> RprBool { let world = handle.world; @@ -313,6 +340,7 @@ pub unsafe extern "C" fn rpr_soft_body_contains(handle: RprSoftBodyHandle) -> Rp /// Remove a body and its joints, optionally keeping colliders as standalone objects. /// Returns whether a body was removed; a stale handle returns false without error. +/// @ingroup rigid_bodies #[rapier_export] pub unsafe extern "C" fn rpr_remove_rigid_body( handle: RprRigidBodyHandle, diff --git a/c/src/pipeline.rs b/c/src/pipeline.rs index a960defe6..473b09cc4 100644 --- a/c/src/pipeline.rs +++ b/c/src/pipeline.rs @@ -1,4 +1,6 @@ use crate::*; +/// Return the world setting documented by RprIntegrationParameters::dt. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_time_step(world: *const RprWorld) -> RprReal { ffi_value(|out: *mut RprReal| { @@ -15,6 +17,8 @@ pub unsafe extern "C" fn rpr_time_step(world: *const RprWorld) -> RprReal { }) } +/// Set the world setting documented by RprIntegrationParameters::dt. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_time_step(world: *mut RprWorld, value: RprReal) -> RprStatus { ffi(|| unsafe { @@ -30,6 +34,8 @@ pub unsafe extern "C" fn rpr_set_time_step(world: *mut RprWorld, value: RprReal) }) } +/// Return the world setting documented by RprIntegrationParameters::minCcdDt. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_min_ccd_dt(world: *const RprWorld) -> RprReal { ffi_value(|out: *mut RprReal| { @@ -46,6 +52,8 @@ pub unsafe extern "C" fn rpr_min_ccd_dt(world: *const RprWorld) -> RprReal { }) } +/// Set the world setting documented by RprIntegrationParameters::minCcdDt. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_min_ccd_dt(world: *mut RprWorld, value: RprReal) -> RprStatus { ffi(|| unsafe { @@ -61,6 +69,8 @@ pub unsafe extern "C" fn rpr_set_min_ccd_dt(world: *mut RprWorld, value: RprReal }) } +/// Return the world setting documented by RprIntegrationParameters::lengthUnit. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_length_unit(world: *const RprWorld) -> RprReal { ffi_value(|out: *mut RprReal| { @@ -77,6 +87,8 @@ pub unsafe extern "C" fn rpr_length_unit(world: *const RprWorld) -> RprReal { }) } +/// Set the world setting documented by RprIntegrationParameters::lengthUnit. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_length_unit(world: *mut RprWorld, value: RprReal) -> RprStatus { ffi(|| unsafe { @@ -92,6 +104,8 @@ pub unsafe extern "C" fn rpr_set_length_unit(world: *mut RprWorld, value: RprRea }) } +/// Return the world setting documented by RprIntegrationParameters::warmstartCoefficient. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_warmstart_coefficient(world: *const RprWorld) -> RprReal { ffi_value(|out: *mut RprReal| { @@ -108,6 +122,8 @@ pub unsafe extern "C" fn rpr_warmstart_coefficient(world: *const RprWorld) -> Rp }) } +/// Set the world setting documented by RprIntegrationParameters::warmstartCoefficient. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_warmstart_coefficient( world: *mut RprWorld, @@ -127,6 +143,8 @@ pub unsafe extern "C" fn rpr_set_warmstart_coefficient( }) } +/// Return the world setting documented by RprIntegrationParameters::normalizedAllowedLinearError. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_normalized_allowed_linear_error(world: *const RprWorld) -> RprReal { ffi_value(|out: *mut RprReal| { @@ -143,6 +161,8 @@ pub unsafe extern "C" fn rpr_normalized_allowed_linear_error(world: *const RprWo }) } +/// Set the world setting documented by RprIntegrationParameters::normalizedAllowedLinearError. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_normalized_allowed_linear_error( world: *mut RprWorld, @@ -161,6 +181,9 @@ pub unsafe extern "C" fn rpr_set_normalized_allowed_linear_error( }) } +/// Return the world setting documented by +/// RprIntegrationParameters::normalizedMaxCorrectiveVelocity. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_normalized_max_corrective_velocity(world: *const RprWorld) -> RprReal { ffi_value(|out: *mut RprReal| { @@ -177,6 +200,8 @@ pub unsafe extern "C" fn rpr_normalized_max_corrective_velocity(world: *const Rp }) } +/// Set the world setting documented by RprIntegrationParameters::normalizedMaxCorrectiveVelocity. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_normalized_max_corrective_velocity( world: *mut RprWorld, @@ -195,6 +220,8 @@ pub unsafe extern "C" fn rpr_set_normalized_max_corrective_velocity( }) } +/// Return the world setting documented by RprIntegrationParameters::normalizedPredictionDistance. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_normalized_prediction_distance(world: *const RprWorld) -> RprReal { ffi_value(|out: *mut RprReal| { @@ -211,6 +238,8 @@ pub unsafe extern "C" fn rpr_normalized_prediction_distance(world: *const RprWor }) } +/// Set the world setting documented by RprIntegrationParameters::normalizedPredictionDistance. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_normalized_prediction_distance( world: *mut RprWorld, @@ -229,6 +258,8 @@ pub unsafe extern "C" fn rpr_set_normalized_prediction_distance( }) } +/// Return the world setting documented by RprIntegrationParameters::normalizedMaxLinearVelocity. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_normalized_max_linear_velocity(world: *const RprWorld) -> RprReal { ffi_value(|out: *mut RprReal| { @@ -245,6 +276,8 @@ pub unsafe extern "C" fn rpr_normalized_max_linear_velocity(world: *const RprWor }) } +/// Set the world setting documented by RprIntegrationParameters::normalizedMaxLinearVelocity. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_normalized_max_linear_velocity( world: *mut RprWorld, @@ -263,6 +296,9 @@ pub unsafe extern "C" fn rpr_set_normalized_max_linear_velocity( }) } +/// Return the world setting documented by +/// RprIntegrationParameters::normalizedContactRecycleDistance. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_normalized_contact_recycle_distance( world: *const RprWorld, @@ -281,6 +317,8 @@ pub unsafe extern "C" fn rpr_normalized_contact_recycle_distance( }) } +/// Set the world setting documented by RprIntegrationParameters::normalizedContactRecycleDistance. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_normalized_contact_recycle_distance( world: *mut RprWorld, @@ -299,6 +337,8 @@ pub unsafe extern "C" fn rpr_set_normalized_contact_recycle_distance( }) } +/// Return the world setting documented by RprIntegrationParameters::numSolverIterations. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_num_solver_iterations(world: *const RprWorld) -> usize { ffi_value(|out: *mut usize| { @@ -315,6 +355,8 @@ pub unsafe extern "C" fn rpr_num_solver_iterations(world: *const RprWorld) -> us }) } +/// Set the world setting documented by RprIntegrationParameters::numSolverIterations. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_num_solver_iterations( world: *mut RprWorld, @@ -333,6 +375,8 @@ pub unsafe extern "C" fn rpr_set_num_solver_iterations( }) } +/// Return the world setting documented by RprIntegrationParameters::numInternalPgsIterations. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_num_internal_pgs_iterations(world: *const RprWorld) -> usize { ffi_value(|out: *mut usize| { @@ -349,6 +393,8 @@ pub unsafe extern "C" fn rpr_num_internal_pgs_iterations(world: *const RprWorld) }) } +/// Set the world setting documented by RprIntegrationParameters::numInternalPgsIterations. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_num_internal_pgs_iterations( world: *mut RprWorld, @@ -367,6 +413,9 @@ pub unsafe extern "C" fn rpr_set_num_internal_pgs_iterations( }) } +/// Return the world setting documented by +/// RprIntegrationParameters::numInternalStabilizationIterations. +/// @ingroup errors #[rapier_export] pub unsafe extern "C" fn rpr_num_internal_stabilization_iterations( world: *const RprWorld, @@ -385,6 +434,9 @@ pub unsafe extern "C" fn rpr_num_internal_stabilization_iterations( }) } +/// Set the world setting documented by +/// RprIntegrationParameters::numInternalStabilizationIterations. +/// @ingroup errors #[rapier_export] pub unsafe extern "C" fn rpr_set_num_internal_stabilization_iterations( world: *mut RprWorld, @@ -402,6 +454,8 @@ pub unsafe extern "C" fn rpr_set_num_internal_stabilization_iterations( }) } +/// Return the world setting documented by RprIntegrationParameters::maxCcdSubsteps. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_max_ccd_substeps(world: *const RprWorld) -> usize { ffi_value(|out: *mut usize| { @@ -418,6 +472,8 @@ pub unsafe extern "C" fn rpr_max_ccd_substeps(world: *const RprWorld) -> usize { }) } +/// Set the world setting documented by RprIntegrationParameters::maxCcdSubsteps. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_max_ccd_substeps(world: *mut RprWorld, value: usize) -> RprStatus { ffi(|| unsafe { @@ -432,6 +488,8 @@ pub unsafe extern "C" fn rpr_set_max_ccd_substeps(world: *mut RprWorld, value: u }) } +/// Return the world setting documented by RprIntegrationParameters::contactClustering. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_contact_clustering(world: *const RprWorld) -> RprBool { ffi_value(|out: *mut RprBool| { @@ -448,6 +506,8 @@ pub unsafe extern "C" fn rpr_contact_clustering(world: *const RprWorld) -> RprBo }) } +/// Set the world setting documented by RprIntegrationParameters::contactClustering. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_contact_clustering( world: *mut RprWorld, @@ -466,6 +526,8 @@ pub unsafe extern "C" fn rpr_set_contact_clustering( }) } +/// Return the world setting documented by RprIntegrationParameters::contactRecycling. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_contact_recycling(world: *const RprWorld) -> RprBool { ffi_value(|out: *mut RprBool| { @@ -482,6 +544,8 @@ pub unsafe extern "C" fn rpr_contact_recycling(world: *const RprWorld) -> RprBoo }) } +/// Set the world setting documented by RprIntegrationParameters::contactRecycling. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_contact_recycling( world: *mut RprWorld, @@ -500,6 +564,8 @@ pub unsafe extern "C" fn rpr_set_contact_recycling( }) } +/// Return the world setting documented by RprIntegrationParameters::frictionInBiasPass. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_friction_in_bias_pass(world: *const RprWorld) -> RprBool { ffi_value(|out: *mut RprBool| { @@ -516,6 +582,8 @@ pub unsafe extern "C" fn rpr_friction_in_bias_pass(world: *const RprWorld) -> Rp }) } +/// Set the world setting documented by RprIntegrationParameters::frictionInBiasPass. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_friction_in_bias_pass( world: *mut RprWorld, @@ -534,6 +602,8 @@ pub unsafe extern "C" fn rpr_set_friction_in_bias_pass( }) } +/// Return the world setting documented by RprIntegrationParameters::warmstartJoints. +/// @ingroup joints #[rapier_export] pub unsafe extern "C" fn rpr_warmstart_joints(world: *const RprWorld) -> RprBool { ffi_value(|out: *mut RprBool| { @@ -550,6 +620,8 @@ pub unsafe extern "C" fn rpr_warmstart_joints(world: *const RprWorld) -> RprBool }) } +/// Set the world setting documented by RprIntegrationParameters::warmstartJoints. +/// @ingroup joints #[rapier_export] pub unsafe extern "C" fn rpr_set_warmstart_joints( world: *mut RprWorld, @@ -568,6 +640,8 @@ pub unsafe extern "C" fn rpr_set_warmstart_joints( }) } +/// Return the world setting documented by RprIntegrationParameters::contactSoftness. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_contact_softness(world: *const RprWorld) -> RprSpringCoefficients { ffi_value(|out: *mut RprSpringCoefficients| { @@ -584,6 +658,8 @@ pub unsafe extern "C" fn rpr_contact_softness(world: *const RprWorld) -> RprSpri }) } +/// Set the world setting documented by RprIntegrationParameters::contactSoftness. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_set_contact_softness( world: *mut RprWorld, @@ -602,6 +678,8 @@ pub unsafe extern "C" fn rpr_set_contact_softness( }) } +/// Return the world setting documented by RprIntegrationParameters::staticContactSoftness. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_static_contact_softness( world: *const RprWorld, @@ -620,6 +698,8 @@ pub unsafe extern "C" fn rpr_static_contact_softness( }) } +/// Set the world setting documented by RprIntegrationParameters::staticContactSoftness. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_set_static_contact_softness( world: *mut RprWorld, @@ -644,26 +724,42 @@ use rapier::pipeline::{ContactModificationContext, PairFilterContext}; use std::{ffi::c_void, sync::Mutex}; /// Collision start/stop flags match Rapier CollisionEventFlags. +/// @ingroup events #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprCollisionEvent { + /// First collider in the pair. pub collider1: RprColliderHandle, + /// Second collider in the pair. pub collider2: RprColliderHandle, + /// 1 for a starting event, 0 for a stopping event. pub started: RprBool, + /// Event flags: bit 0 sensor pair, bit 1 removed collider. pub flags: u32, } +/// Contact-force event, enabled by flags and the collider force threshold. +/// @ingroup events #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprContactForceEvent { + /// First collider in the pair. pub collider1: RprColliderHandle, + /// Second collider in the pair. pub collider2: RprColliderHandle, + /// Sum of world-space contact forces. pub total_force: RprVector, + /// Sum of contact force magnitudes. pub total_force_magnitude: RprReal, + /// World-space direction of the strongest contact force. pub max_force_direction: RprVector, + /// Magnitude of the strongest contact force. pub max_force_magnitude: RprReal, + /// 1 for a starting event, 0 for a stopping event. pub started: RprBool, } -/// Events accumulate until clear. Copying events never drains them, allowing two-call buffer sizing. +/// Events accumulate until clear. Copying events never drains them, allowing two-call buffer +/// sizing. +/// @ingroup events #[derive(Default)] pub struct RprEventCollector { collisions: Mutex>, @@ -730,8 +826,10 @@ impl EventHandler for WorldEvents<'_> { .push(RprSoftBodyTearEvent(event.clone(), self.world)); } } -/// Pair callback: -1 rejects a contact pair; 0 detects contacts without impulses; 1 computes impulses. +/// Pair callback: -1 rejects a contact pair; 0 detects contacts without impulses; 1 computes +/// impulses. /// For sensor intersections only, zero rejects and any positive value accepts. +/// @ingroup math pub type RprPairFilter = Option< unsafe extern "C" fn( user_data: *mut c_void, @@ -743,15 +841,23 @@ pub type RprPairFilter = Option< ) -> i32, >; /// Mutable per-manifold properties. Set enabled=0 to discard all its solver contacts. +/// @ingroup events #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprContactModification { + /// World-space contact or surface normal. pub normal: RprVector, + /// Nonnegative friction coefficient. pub friction: RprReal, + /// Nonnegative restitution coefficient. pub restitution: RprReal, + /// Application data; Rapier does not own pointers encoded in it. pub user_data: u32, + /// Whether this setting/object is enabled (0 or 1). pub enabled: RprBool, } +/// Modify aggregate contact properties for one pair; contact is mutable only during this callback. +/// @ingroup callbacks pub type RprModifyContacts = Option< unsafe extern "C" fn( user_data: *mut c_void, @@ -762,9 +868,12 @@ pub type RprModifyContacts = Option< ), >; /// Borrowed native contact context. Valid only during its callback; never retain or free it. +/// @ingroup events pub struct RprContactModificationContext { raw: *mut c_void, } +/// Modify individual solver contacts through a borrowed context, valid only during the callback. +/// @ingroup callbacks pub type RprModifyContactContext = Option< unsafe extern "C" fn( user_data: *mut c_void, @@ -778,13 +887,19 @@ pub type RprModifyContactContext = Option< /// Callbacks must not unwind or retain arguments. Use their ReadContext to inspect bodies and /// colliders; ordinary access to the stepping world returns WORLD_BUSY. Mutations must be /// performed after stepping. With parallel builds -/// callbacks and their user_data must be safe for concurrent invocation. NULL callbacks use defaults. +/// callbacks and their user_data must be safe for concurrent invocation. NULL callbacks use +/// defaults. +/// @ingroup callbacks #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprPhysicsHooks { + /// Application data; Rapier does not own pointers encoded in it. pub user_data: *mut c_void, + /// Optional contact-pair filter, called only for colliders enabling the hook. pub filter_contact_pair: RprPairFilter, + /// Optional sensor-pair filter, called only for colliders enabling the hook. pub filter_intersection_pair: RprPairFilter, + /// Optional legacy aggregate contact-edit callback. pub modify_solver_contacts: RprModifyContacts, /// Runs after the legacy property callback. Context accessors may be called here. pub modify_solver_contacts_context: RprModifyContactContext, @@ -889,6 +1004,7 @@ impl PhysicsHooks for WorldHooks { } /// Applies Rapier's persistent one-way platform logic to the borrowed manifold. +/// @ingroup worlds #[rapier_export(contact_modification_context)] pub unsafe extern "C" fn rpr_contact_modification_context_update_as_oneway_platform( context: *mut RprContactModificationContext, @@ -907,6 +1023,7 @@ pub unsafe extern "C" fn rpr_contact_modification_context_update_as_oneway_platf } /// Sets the tangent velocity of every rigid solver contact in this manifold. +/// @ingroup worlds #[rapier_export(contact_modification_context)] pub unsafe extern "C" fn rpr_contact_modification_context_set_tangent_velocity( context: *mut RprContactModificationContext, @@ -926,6 +1043,8 @@ pub unsafe extern "C" fn rpr_contact_modification_context_set_tangent_velocity( }) } +/// Allocate an empty event collector; release it with rpr_free_event_collector. +/// @ingroup events #[rapier_export] pub unsafe extern "C" fn rpr_new_event_collector() -> *mut RprEventCollector { ffi_value(|out: *mut *mut RprEventCollector| { @@ -935,6 +1054,9 @@ pub unsafe extern "C" fn rpr_new_event_collector() -> *mut RprEventCollector { }) }) } +/// Release an owned event collector. NULL is allowed. Do not pass borrowed pointers or free the +/// object twice. +/// @ingroup events #[rapier_export] pub unsafe extern "C" fn rpr_free_event_collector(events: *mut RprEventCollector) -> RprStatus { ffi(|| unsafe { @@ -945,6 +1067,8 @@ pub unsafe extern "C" fn rpr_free_event_collector(events: *mut RprEventCollector Ok(()) }) } +/// Discard all collected events. Does not change the world. +/// @ingroup events #[rapier_export(event_collector)] pub unsafe extern "C" fn rpr_event_collector_clear(events: *mut RprEventCollector) -> RprStatus { ffi(|| unsafe { @@ -955,6 +1079,9 @@ pub unsafe extern "C" fn rpr_event_collector_clear(events: *mut RprEventCollecto Ok(()) }) } +/// Copy the collected collision start/stop events without removing them. +/// @see @ref output_buffers +/// @ingroup events #[rapier_export(event_collector)] pub unsafe extern "C" fn rpr_event_collector_collision_events( events: *const RprEventCollector, @@ -972,6 +1099,9 @@ pub unsafe extern "C" fn rpr_event_collector_collision_events( }) }) } +/// Copy the collected contact-force events without removing them. +/// @see @ref output_buffers +/// @ingroup events #[rapier_export(event_collector)] pub unsafe extern "C" fn rpr_event_collector_contact_force_events( events: *const RprEventCollector, @@ -989,6 +1119,8 @@ pub unsafe extern "C" fn rpr_event_collector_contact_force_events( }) }) } +/// Return the number of queued soft-body tear events. +/// @ingroup events #[rapier_export(event_collector)] pub unsafe extern "C" fn rpr_event_collector_tear_event_count( events: *const RprEventCollector, @@ -998,11 +1130,15 @@ pub unsafe extern "C" fn rpr_event_collector_tear_event_count( }) } /// Owned copy of a tear event. Read particle remapping before rebuilding render meshes. +/// @ingroup events #[derive(Clone)] pub struct RprSoftBodyTearEvent(pub(crate) SoftBodyTearEvent, pub(crate) *mut RprWorld); // Owned event data plus a non-owning world address, never dereferenced by the event. unsafe impl Send for RprSoftBodyTearEvent {} unsafe impl Sync for RprSoftBodyTearEvent {} +/// Return an owned copy of a queued tear event; release with rpr_free_soft_body_tear_event. Does +/// not remove the queued event. +/// @ingroup events #[rapier_export(event_collector)] pub unsafe extern "C" fn rpr_event_collector_tear_event( events: *const RprEventCollector, @@ -1022,6 +1158,8 @@ pub unsafe extern "C" fn rpr_event_collector_tear_event( }) }) } +/// Return the world-space gravitational acceleration. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_gravity(world: *const RprWorld) -> RprVector { ffi_value(|out: *mut RprVector| { @@ -1035,6 +1173,8 @@ pub unsafe extern "C" fn rpr_gravity(world: *const RprWorld) -> RprVector { }) } +/// Set the world-space gravitational acceleration. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_gravity(world: *mut RprWorld, value: RprVector) -> RprStatus { ffi(|| unsafe { @@ -1051,6 +1191,7 @@ pub unsafe extern "C" fn rpr_set_gravity(world: *mut RprWorld, value: RprVector) /// Hooks and events may be NULL. This call invalidates all borrowed set-element pointers. /// Advance simulation by one timestep. Hooks and events may be NULL. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_step( world: *mut RprWorld, @@ -1080,6 +1221,7 @@ pub unsafe extern "C" fn rpr_step( } /// Refresh collision detection without advancing simulation. Hooks and events may be NULL. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_detect_collisions( world: *mut RprWorld, @@ -1109,7 +1251,11 @@ pub unsafe extern "C" fn rpr_detect_collisions( } /// Immutable owned byte buffer. Release with the matching FreeBytes function. +/// @ingroup worlds pub struct RprBytes(Vec); +/// Borrow snapshot bytes without copying; valid until rpr_free_bytes. Never free the returned data +/// pointer. +/// @ingroup worlds #[rapier_export(bytes)] pub unsafe extern "C" fn rpr_bytes_data(bytes: *const RprBytes) -> RprByteView { ffi_value(|result: *mut RprByteView| { @@ -1125,6 +1271,9 @@ pub unsafe extern "C" fn rpr_bytes_data(bytes: *const RprBytes) -> RprByteView { }) }) } +/// Release an owned snapshot byte buffer. NULL is allowed. Do not pass borrowed pointers or free +/// the object twice. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_free_bytes(bytes: *mut RprBytes) -> RprStatus { ffi(|| unsafe { @@ -1155,6 +1304,9 @@ fn snapshot_options() -> impl Options { .with_limit(256 * 1024 * 1024) .reject_trailing_bytes() } +/// Return owned snapshot bytes; release them with rpr_free_bytes. See @ref snapshots for +/// restoration and handle lifetimes. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_serialize_world(world: *const RprWorld) -> *mut RprBytes { ffi_value(|out: *mut *mut RprBytes| { @@ -1176,7 +1328,9 @@ pub unsafe extern "C" fn rpr_serialize_world(world: *const RprWorld) -> *mut Rpr }) } -/// Restore ONLY trusted snapshots produced by the identical Rapier build. Snapshots are not a stable file format. +/// Restore ONLY trusted snapshots produced by the identical Rapier build. Snapshots are not a +/// stable file format. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_deserialize_world(data: *const u8, count: usize) -> *mut RprWorld { ffi_value(|out: *mut *mut RprWorld| { @@ -1199,11 +1353,16 @@ pub unsafe extern "C" fn rpr_deserialize_world(data: *const u8, count: usize) -> }) } +/// World-space line segment produced by physics debug rendering. +/// @ingroup events #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprDebugLine { + /// World-space start point. pub a: RprVector, + /// World-space end point. pub b: RprVector, + /// RGBA color, four floats. pub color: [f32; 4], } struct Lines(Vec); @@ -1223,6 +1382,8 @@ impl rapier::pipeline::DebugRenderBackend for Lines { } } /// Color is HSLA (hue in degrees), matching Rapier DebugColor. mode uses DebugRenderMode bits. +/// @see @ref output_buffers +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_debug_render( world: *const RprWorld, @@ -1247,6 +1408,8 @@ pub unsafe extern "C" fn rpr_debug_render( }) } +/// Set the world setting documented by RprSoftBodiesSettings::resweepStrain. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_soft_bodies_set_resweep_strain( world: *mut RprWorld, @@ -1265,6 +1428,8 @@ pub unsafe extern "C" fn rpr_soft_bodies_set_resweep_strain( }) } +/// Return the world setting documented by RprSoftBodiesSettings::resweepStrain. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_soft_bodies_resweep_strain(world: *const RprWorld) -> RprReal { ffi_value(|out: *mut RprReal| { @@ -1279,6 +1444,8 @@ pub unsafe extern "C" fn rpr_soft_bodies_resweep_strain(world: *const RprWorld) }) } +/// Set the world setting documented by RprSoftBodiesSettings::contactStiffening. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_soft_bodies_set_contact_stiffening( world: *mut RprWorld, @@ -1297,6 +1464,8 @@ pub unsafe extern "C" fn rpr_soft_bodies_set_contact_stiffening( }) } +/// Return the world setting documented by RprSoftBodiesSettings::contactStiffening. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_soft_bodies_contact_stiffening(world: *const RprWorld) -> RprReal { ffi_value(|out: *mut RprReal| { @@ -1311,6 +1480,8 @@ pub unsafe extern "C" fn rpr_soft_bodies_contact_stiffening(world: *const RprWor }) } +/// Set the world setting documented by RprSoftBodiesSettings::maxExtraSubsteps. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_soft_bodies_set_max_extra_substeps( world: *mut RprWorld, @@ -1328,6 +1499,8 @@ pub unsafe extern "C" fn rpr_soft_bodies_set_max_extra_substeps( }) } +/// Return the world setting documented by RprSoftBodiesSettings::maxExtraSubsteps. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_soft_bodies_max_extra_substeps(world: *const RprWorld) -> usize { ffi_value(|out: *mut usize| { @@ -1342,6 +1515,8 @@ pub unsafe extern "C" fn rpr_soft_bodies_max_extra_substeps(world: *const RprWor }) } +/// Set the world setting documented by RprSoftRecoverySettings::authoredVelocityMargin. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_authored_velocity_margin( world: *mut RprWorld, @@ -1364,6 +1539,8 @@ pub unsafe extern "C" fn rpr_recovery_set_authored_velocity_margin( }) } +/// Set the world setting documented by RprSoftRecoverySettings::edgeSpeculation. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_edge_speculation( world: *mut RprWorld, @@ -1382,6 +1559,8 @@ pub unsafe extern "C" fn rpr_recovery_set_edge_speculation( }) } +/// Set the world setting documented by RprSoftRecoverySettings::invertedCellDetection. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_inverted_cell_detection( world: *mut RprWorld, @@ -1404,6 +1583,8 @@ pub unsafe extern "C" fn rpr_recovery_set_inverted_cell_detection( }) } +/// Set the world setting documented by RprSoftRecoverySettings::selfCrossingDetection. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_self_crossing_detection( world: *mut RprWorld, @@ -1426,6 +1607,8 @@ pub unsafe extern "C" fn rpr_recovery_set_self_crossing_detection( }) } +/// Set the world setting documented by RprSoftRecoverySettings::detectionMotionGating. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_detection_motion_gating( world: *mut RprWorld, @@ -1448,6 +1631,8 @@ pub unsafe extern "C" fn rpr_recovery_set_detection_motion_gating( }) } +/// Set the world setting documented by RprSoftRecoverySettings::crossBodyDetection. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_cross_body_detection( world: *mut RprWorld, @@ -1470,6 +1655,8 @@ pub unsafe extern "C" fn rpr_recovery_set_cross_body_detection( }) } +/// Set the world setting documented by RprSoftRecoverySettings::selfStandDown. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_self_stand_down( world: *mut RprWorld, @@ -1488,6 +1675,8 @@ pub unsafe extern "C" fn rpr_recovery_set_self_stand_down( }) } +/// Set the world setting documented by RprSoftRecoverySettings::crossBodyExpelGate. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_cross_body_expel_gate( world: *mut RprWorld, @@ -1510,6 +1699,8 @@ pub unsafe extern "C" fn rpr_recovery_set_cross_body_expel_gate( }) } +/// Set the world setting documented by RprSoftRecoverySettings::edgeStandDown. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_edge_stand_down( world: *mut RprWorld, @@ -1528,6 +1719,8 @@ pub unsafe extern "C" fn rpr_recovery_set_edge_stand_down( }) } +/// Set the world setting documented by RprSoftRecoverySettings::crossingRepulsion. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_crossing_repulsion( world: *mut RprWorld, @@ -1550,6 +1743,8 @@ pub unsafe extern "C" fn rpr_recovery_set_crossing_repulsion( }) } +/// Set the world setting documented by RprSoftRecoverySettings::crossingRepulsionGuide. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_crossing_repulsion_guide( world: *mut RprWorld, @@ -1572,6 +1767,8 @@ pub unsafe extern "C" fn rpr_recovery_set_crossing_repulsion_guide( }) } +/// Set the world setting documented by RprSoftRecoverySettings::crossingRepulsionSelfGuide. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_crossing_repulsion_self_guide( world: *mut RprWorld, @@ -1594,6 +1791,8 @@ pub unsafe extern "C" fn rpr_recovery_set_crossing_repulsion_self_guide( }) } +/// Set the world setting documented by RprSoftRecoverySettings::recoveryPace. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_recovery_pace( world: *mut RprWorld, @@ -1612,6 +1811,8 @@ pub unsafe extern "C" fn rpr_recovery_set_recovery_pace( }) } +/// Set the world setting documented by RprSoftRecoverySettings::overlapConstraints. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_overlap_constraints( world: *mut RprWorld, @@ -1634,6 +1835,8 @@ pub unsafe extern "C" fn rpr_recovery_set_overlap_constraints( }) } +/// Set the world setting documented by RprSoftRecoverySettings::overlapRigid. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_overlap_rigid( world: *mut RprWorld, @@ -1652,6 +1855,8 @@ pub unsafe extern "C" fn rpr_recovery_set_overlap_rigid( }) } +/// Set the world setting documented by RprSoftRecoverySettings::overlapSkipSelfTangled. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_overlap_skip_self_tangled( world: *mut RprWorld, @@ -1674,6 +1879,8 @@ pub unsafe extern "C" fn rpr_recovery_set_overlap_skip_self_tangled( }) } +/// Set the world setting documented by RprSoftRecoverySettings::overlapEdgeStandDown. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_overlap_edge_stand_down( world: *mut RprWorld, @@ -1696,6 +1903,8 @@ pub unsafe extern "C" fn rpr_recovery_set_overlap_edge_stand_down( }) } +/// Set the world setting documented by RprSoftRecoverySettings::overlapConstraintPace. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_overlap_constraint_pace( world: *mut RprWorld, @@ -1718,6 +1927,8 @@ pub unsafe extern "C" fn rpr_recovery_set_overlap_constraint_pace( }) } +/// Set the world setting documented by RprSoftRecoverySettings::overlapSkinVolume. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_overlap_skin_volume( world: *mut RprWorld, @@ -1740,6 +1951,8 @@ pub unsafe extern "C" fn rpr_recovery_set_overlap_skin_volume( }) } +/// Set the world setting documented by RprSoftRecoverySettings::overlapKeptDepth. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_overlap_kept_depth( world: *mut RprWorld, @@ -1762,6 +1975,8 @@ pub unsafe extern "C" fn rpr_recovery_set_overlap_kept_depth( }) } +/// Set the world setting documented by RprSoftRecoverySettings::overlapSelfRegions. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_overlap_self_regions( world: *mut RprWorld, @@ -1784,6 +1999,8 @@ pub unsafe extern "C" fn rpr_recovery_set_overlap_self_regions( }) } +/// Set the world setting documented by RprSoftRecoverySettings::overlapNormalPush. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_overlap_normal_push( world: *mut RprWorld, @@ -1806,6 +2023,8 @@ pub unsafe extern "C" fn rpr_recovery_set_overlap_normal_push( }) } +/// Set the world setting documented by RprSoftRecoverySettings::overlapMultiVolume. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_overlap_multi_volume( world: *mut RprWorld, @@ -1828,6 +2047,8 @@ pub unsafe extern "C" fn rpr_recovery_set_overlap_multi_volume( }) } +/// Set the world setting documented by RprSoftRecoverySettings::overlapProgressMargin. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_recovery_set_overlap_progress_margin( world: *mut RprWorld, @@ -1850,6 +2071,8 @@ pub unsafe extern "C" fn rpr_recovery_set_overlap_progress_margin( }) } +/// Set the world setting documented by RprSoftFemParameters::linearTolerance. +/// @ingroup soft_bodies #[cfg(feature = "fem")] #[rapier_export] pub unsafe extern "C" fn rpr_fem_set_linear_tolerance( @@ -1869,6 +2092,8 @@ pub unsafe extern "C" fn rpr_fem_set_linear_tolerance( }) } +/// Set the world setting documented by RprSoftFemParameters::maxLinearIterations. +/// @ingroup soft_bodies #[cfg(feature = "fem")] #[rapier_export] pub unsafe extern "C" fn rpr_fem_set_max_linear_iterations( @@ -1888,6 +2113,8 @@ pub unsafe extern "C" fn rpr_fem_set_max_linear_iterations( }) } +/// Set the world setting documented by RprSoftFemParameters::maxDenseDofs. +/// @ingroup soft_bodies #[cfg(feature = "fem")] #[rapier_export] pub unsafe extern "C" fn rpr_fem_set_max_dense_dofs( @@ -1910,6 +2137,7 @@ pub unsafe extern "C" fn rpr_fem_set_max_dense_dofs( /// Takes effect on the next step. Reconfiguration must not race with a step or callback. /// Returns RPR_UNSUPPORTED in builds without the parallel feature; keeps the previous /// pool when constructing the new one fails. The pool is not included in snapshots. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_num_threads( world: *mut RprWorld, @@ -1943,6 +2171,7 @@ pub unsafe extern "C" fn rpr_set_num_threads( /// Removes the world's dedicated pool. A parallel build then uses the calling /// context's Rayon pool (normally the global pool), not a single worker. /// Returns RPR_UNSUPPORTED in a build without the parallel feature. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_clear_thread_pool(world: *mut RprWorld) -> RprStatus { ffi(|| unsafe { @@ -1970,6 +2199,7 @@ pub unsafe extern "C" fn rpr_clear_thread_pool(world: *mut RprWorld) -> RprStatu /// Size of the world's dedicated pool, or zero if a parallel build has no dedicated /// pool configured. Returns one for a build without the parallel feature. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_num_threads(world: *const RprWorld) -> usize { ffi_value(|out: *mut usize| { @@ -1994,6 +2224,7 @@ pub unsafe extern "C" fn rpr_num_threads(world: *const RprWorld) -> usize { /// Enable or disable the native pipeline profiling counters. Enabling returns /// RPR_UNSUPPORTED if the library was built without the profiler feature. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_set_counters_enabled( world: *mut RprWorld, @@ -2026,6 +2257,7 @@ pub unsafe extern "C" fn rpr_set_counters_enabled( /// Native engine time of the most recent step, in milliseconds, as in the Rust testbed. /// Enable counters before stepping. Excludes C callbacks outside the step, rendering, /// and dispatch into a dedicated thread pool; remains unchanged while paused. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_step_time_ms(world: *const RprWorld) -> f64 { ffi_value(|out: *mut f64| { @@ -2043,6 +2275,9 @@ pub unsafe extern "C" fn rpr_step_time_ms(world: *const RprWorld) -> f64 { /// Read ONLY trusted legacy rigid-world snapshots from debug_deserialize3.rs, /// produced by the identical Rapier build. This is not a stable interchange format. +/// Import trusted legacy Rust testbed rigid-state bytes into a new owned world. Release with +/// rpr_free_world; see @ref snapshots. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_deserialize_rigid_state( data: *const u8, diff --git a/c/src/queries.rs b/c/src/queries.rs index 2af5756bd..374a496ba 100644 --- a/c/src/queries.rs +++ b/c/src/queries.rs @@ -1,12 +1,19 @@ use crate::*; use rapier::parry::query::ShapeCastOptions; +/// Scene-query flags, groups, and excluded handles. Initialize with rpr_default_query_filter. +/// @ingroup queries #[repr(C)] #[derive(Copy, Clone)] pub struct RprQueryFilter { + /// RPR_QUERY_EXCLUDE_* bitmask selecting body types and sensors/solids. pub flags: u32, + /// Whether to apply the groups filter. pub use_groups: RprBool, + /// Groups to test when use_groups is 1. pub groups: RprInteractionGroups, + /// Collider to exclude; use the explicit invalid handle to exclude none. pub exclude_collider: RprColliderHandle, + /// Body whose colliders are excluded; use the explicit invalid handle for none. pub exclude_rigid_body: RprRigidBodyHandle, } impl Default for RprQueryFilter { @@ -42,18 +49,28 @@ impl RprQueryFilter { }) } } +/// Return native default query filter. This POD value owns no resources. +/// @ingroup queries #[rapier_export] pub extern "C" fn rpr_default_query_filter() -> RprQueryFilter { RprQueryFilter::default() } +/// Closest ray intersection, with a world-space normal. +/// @ingroup queries #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprRayHit { + /// World-bound collider handle. pub collider: RprColliderHandle, + /// Ray/sweep parameter at first impact, bounded by the query options. pub time_of_impact: RprReal, + /// World-space contact or surface normal. pub normal: RprVector, + /// Shape feature kind: RPR_FEATURE_UNKNOWN, RPR_FEATURE_VERTEX, RPR_FEATURE_EDGE, or + /// RPR_FEATURE_FACE. pub feature_type: u32, + /// Index within the feature kind; zero for unknown. pub feature_id: u32, } pub(crate) fn feature(f: rapier::parry::shape::FeatureId) -> (u32, u32) { @@ -67,20 +84,31 @@ pub(crate) fn feature(f: rapier::parry::shape::FeatureId) -> (u32, u32) { } } +/// Closest projected world-space point and its collider. +/// @ingroup queries #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprPointProjection { + /// World-bound collider handle. pub collider: RprColliderHandle, + /// Projected world-space point. pub point: RprVector, + /// Whether the original point was inside the collider. pub is_inside: RprBool, } +/// Sweep termination settings. Initialize with rpr_default_shape_cast_options. +/// @ingroup queries #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprShapeCastOptions { + /// Maximum sweep parameter; movement is velocity multiplied by this time. pub max_time_of_impact: RprReal, + /// Nonnegative separation at which a shape cast counts as a hit. pub target_distance: RprReal, + /// Whether to stop at t = 0 for an initial overlap. pub stop_at_penetration: RprBool, + /// Whether to compute witness points/normals for an initial overlap. pub compute_impact_geometry_on_penetration: RprBool, } impl RprShapeCastOptions { @@ -95,18 +123,30 @@ impl RprShapeCastOptions { }) } } +/// Shape-cast impact geometry; collider-side data is world-space, moving-shape data is local. +/// @ingroup queries #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprShapeCastHit { + /// World-bound collider handle. pub collider: RprColliderHandle, + /// Ray/sweep parameter at first impact, bounded by the query options. pub time_of_impact: RprReal, + /// Impact witness on the collider, in world coordinates. pub witness1: RprVector, + /// Impact witness on the moving shape, in its local coordinates. pub witness2: RprVector, + /// Impact normal on the collider, in world coordinates. pub normal1: RprVector, + /// Impact normal on the moving shape, in its local coordinates. pub normal2: RprVector, + /// Native result: 0 out of iterations, 1 converged, 2 failed, 3 penetrating or within target + /// distance. pub status: u32, } +/// Return native default shape cast options. This POD value owns no resources. +/// @ingroup queries #[rapier_export] pub extern "C" fn rpr_default_shape_cast_options() -> RprShapeCastOptions { let o = ShapeCastOptions::default(); @@ -120,6 +160,7 @@ pub extern "C" fn rpr_default_shape_cast_options() -> RprShapeCastOptions { /// Called with scoped read access and a collider handle. Shared queries may nest; /// world mutations are rejected until the outer query returns. Never retain the context. +/// @ingroup queries pub type RprQueryPredicate = Option< unsafe extern "C" fn( user_data: *mut std::ffi::c_void, diff --git a/c/src/read_access.rs b/c/src/read_access.rs index d60469058..bc34257f5 100644 --- a/c/src/read_access.rs +++ b/c/src/read_access.rs @@ -1,6 +1,8 @@ //! Scoped read access derived from the borrows Rapier supplies to callbacks. use crate::*; -/// Read callback-visible state. The context is valid only until its callback returns. +/// Compute a velocity correction from callback-visible body state, updating the PID controller +/// history. The context is valid only during its callback. +/// @ingroup callbacks #[rapier_export(read_pid_controller)] pub unsafe extern "C" fn rpr_read_pid_controller_rigid_body_correction( context: *const RprReadContext, @@ -31,7 +33,9 @@ pub unsafe extern "C" fn rpr_read_pid_controller_rigid_body_correction( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the number of rigid body objects in the world. Uses only the callback-scoped read +/// context; never retain the context. +/// @ingroup callbacks #[rapier_export] pub unsafe extern "C" fn rpr_read_rigid_body_count(context: *const RprReadContext) -> usize { ffi_value(|out: *mut usize| { @@ -40,7 +44,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_count(context: *const RprReadContex }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Copy entity handles. Uses only the callback-scoped read context; never retain the context. +/// @see @ref output_buffers +/// @ingroup callbacks #[rapier_export] pub unsafe extern "C" fn rpr_read_rigid_body_handles( context: *const RprReadContext, @@ -65,7 +71,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_handles( ) } } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Test whether the live world contains this rigid body handle. A removed/stale handle returns +/// false. Uses only the callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_contains( context: *const RprReadContext, @@ -82,7 +90,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_contains( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the number of collider objects in the world. Uses only the callback-scoped read context; +/// never retain the context. +/// @ingroup callbacks #[rapier_export] pub unsafe extern "C" fn rpr_read_collider_count(context: *const RprReadContext) -> usize { ffi_value(|out: *mut usize| { @@ -91,7 +101,9 @@ pub unsafe extern "C" fn rpr_read_collider_count(context: *const RprReadContext) }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Copy entity handles. Uses only the callback-scoped read context; never retain the context. +/// @see @ref output_buffers +/// @ingroup callbacks #[rapier_export] pub unsafe extern "C" fn rpr_read_collider_handles( context: *const RprReadContext, @@ -116,7 +128,9 @@ pub unsafe extern "C" fn rpr_read_collider_handles( ) } } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Test whether the live world contains this collider handle. A removed/stale handle returns false. +/// Uses only the callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_contains( context: *const RprReadContext, @@ -133,7 +147,10 @@ pub unsafe extern "C" fn rpr_read_collider_contains( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return a process-local geometry identity for caching, not a serializable ID. Keep a shared-shape +/// clone alive while using it as a cache key. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_shape_identity( context: *const RprReadContext, @@ -150,7 +167,9 @@ pub unsafe extern "C" fn rpr_read_collider_shape_identity( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider local mass properties. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_mass_properties( context: *const RprReadContext, @@ -167,7 +186,9 @@ pub unsafe extern "C" fn rpr_read_collider_mass_properties( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body translation/rotation lock bitmask. Uses only the callback-scoped read +/// context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_locked_axes( context: *const RprReadContext, @@ -184,7 +205,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_locked_axes( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the collider is a voxel shape. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_is_voxels( context: *const RprReadContext, @@ -201,7 +224,9 @@ pub unsafe extern "C" fn rpr_read_collider_is_voxels( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return voxel information at a flat index; found = 0 if absent. Uses only the callback-scoped +/// read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_voxel_at_flat_id( context: *const RprReadContext, @@ -228,7 +253,9 @@ pub unsafe extern "C" fn rpr_read_collider_voxel_at_flat_id( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body next kinematic world-space pose. Uses only the callback-scoped read +/// context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_next_position( context: *const RprReadContext, @@ -245,7 +272,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_next_position( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body world-space rotation. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_rotation( context: *const RprReadContext, @@ -262,7 +291,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_rotation( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body world-space center of mass. Uses only the callback-scoped read context; +/// never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_center_of_mass( context: *const RprReadContext, @@ -279,7 +310,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_center_of_mass( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body body-local center of mass. Uses only the callback-scoped read context; +/// never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_local_center_of_mass( context: *const RprReadContext, @@ -296,7 +329,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_local_center_of_mass( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body accumulated user-applied world-space force. Uses only the callback-scoped +/// read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_user_force( context: *const RprReadContext, @@ -313,7 +348,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_user_force( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body accumulated user-applied world-space torque. Uses only the callback-scoped +/// read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_user_torque( context: *const RprReadContext, @@ -330,7 +367,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_user_torque( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body body type (RPR_DYNAMIC, RPR_FIXED, or a kinematic kind). Uses only the +/// callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_body_type( context: *const RprReadContext, @@ -347,7 +386,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_body_type( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body mass. Uses only the callback-scoped read context; never retain the +/// context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_mass( context: *const RprReadContext, @@ -364,7 +405,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_mass( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body gravity multiplier. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_gravity_scale( context: *const RprReadContext, @@ -381,7 +424,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_gravity_scale( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body linear damping coefficient. Uses only the callback-scoped read context; +/// never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_linear_damping( context: *const RprReadContext, @@ -398,7 +443,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_linear_damping( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body angular damping coefficient. Uses only the callback-scoped read context; +/// never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_angular_damping( context: *const RprReadContext, @@ -415,7 +462,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_angular_damping( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body kinetic energy. Uses only the callback-scoped read context; never retain +/// the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_kinetic_energy( context: *const RprReadContext, @@ -432,7 +481,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_kinetic_energy( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body soft-CCD prediction distance. Uses only the callback-scoped read context; +/// never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_soft_ccd_prediction( context: *const RprReadContext, @@ -449,7 +500,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_soft_ccd_prediction( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the rigid body is using continuous collision detection. Uses only the +/// callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_is_ccd_enabled( context: *const RprReadContext, @@ -466,7 +519,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_is_ccd_enabled( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the rigid body is dynamic. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_is_dynamic( context: *const RprReadContext, @@ -483,7 +538,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_is_dynamic( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the associated soft-body handle, or an invalid handle if this is not a soft proxy. Uses +/// only the callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_soft_body( context: *const RprReadContext, @@ -503,7 +560,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_soft_body( }, ) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the rigid body is a soft-body proxy. Uses only the callback-scoped read context; +/// never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_is_soft_frame( context: *const RprReadContext, @@ -520,7 +579,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_is_soft_frame( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the rigid body is fixed. Uses only the callback-scoped read context; never retain +/// the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_is_fixed( context: *const RprReadContext, @@ -537,7 +598,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_is_fixed( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the rigid body is kinematic. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_is_kinematic( context: *const RprReadContext, @@ -554,7 +617,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_is_kinematic( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the rigid body is moving. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_is_moving( context: *const RprReadContext, @@ -571,7 +636,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_is_moving( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the rigid body is currently using CCD for its motion. Uses only the +/// callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_is_ccd_active( context: *const RprReadContext, @@ -588,7 +655,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_is_ccd_active( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return world-space velocity at a world-space point, including angular motion. Uses only the +/// callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_velocity_at_point( context: *const RprReadContext, @@ -607,7 +676,10 @@ pub unsafe extern "C" fn rpr_read_rigid_body_velocity_at_point( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Copy attached collider handles. Uses only the callback-scoped read context; never retain the +/// context. +/// @see @ref output_buffers +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_colliders( context: *const RprReadContext, @@ -635,8 +707,10 @@ pub unsafe extern "C" fn rpr_read_rigid_body_colliders( ) } } +/// Return whether the rigid body is using gyroscopic forces. Uses only the callback-scoped read +/// context; never retain the context. +/// @ingroup callbacks #[cfg(feature = "dim3")] -/// Read callback-visible state. The context is valid only until its callback returns. #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_gyroscopic_forces_enabled( context: *const RprReadContext, @@ -653,7 +727,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_gyroscopic_forces_enabled( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider world-space rotation. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_rotation( context: *const RprReadContext, @@ -670,7 +746,9 @@ pub unsafe extern "C" fn rpr_read_collider_rotation( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider collision filtering groups. Uses only the callback-scoped read context; +/// never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_collision_groups( context: *const RprReadContext, @@ -687,7 +765,9 @@ pub unsafe extern "C" fn rpr_read_collider_collision_groups( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider contact-force filtering groups. Uses only the callback-scoped read context; +/// never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_solver_groups( context: *const RprReadContext, @@ -704,7 +784,9 @@ pub unsafe extern "C" fn rpr_read_collider_solver_groups( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider application-owned 128-bit user value. Uses only the callback-scoped read +/// context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_user_data( context: *const RprReadContext, @@ -721,7 +803,9 @@ pub unsafe extern "C" fn rpr_read_collider_user_data( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider event-generation bitmask (RPR_COLLISION_EVENTS and +/// RPR_CONTACT_FORCE_EVENTS). Uses only the callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_active_events( context: *const RprReadContext, @@ -738,7 +822,8 @@ pub unsafe extern "C" fn rpr_read_collider_active_events( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider mass. Uses only the callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_mass( context: *const RprReadContext, @@ -755,7 +840,9 @@ pub unsafe extern "C" fn rpr_read_collider_mass( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider mass per unit volume. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_density( context: *const RprReadContext, @@ -772,7 +859,9 @@ pub unsafe extern "C" fn rpr_read_collider_density( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider current volume. Uses only the callback-scoped read context; never retain the +/// context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_volume( context: *const RprReadContext, @@ -789,7 +878,9 @@ pub unsafe extern "C" fn rpr_read_collider_volume( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider extra separation skin around the shape. Uses only the callback-scoped read +/// context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_contact_skin( context: *const RprReadContext, @@ -806,7 +897,9 @@ pub unsafe extern "C" fn rpr_read_collider_contact_skin( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider force threshold for contact-force events. Uses only the callback-scoped read +/// context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_contact_force_event_threshold( context: *const RprReadContext, @@ -823,7 +916,9 @@ pub unsafe extern "C" fn rpr_read_collider_contact_force_event_threshold( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the collider is enabled. Uses only the callback-scoped read context; never retain +/// the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_is_enabled( context: *const RprReadContext, @@ -840,7 +935,9 @@ pub unsafe extern "C" fn rpr_read_collider_is_enabled( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the current world-space axis-aligned bounds. Uses only the callback-scoped read context; +/// never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_compute_aabb( context: *const RprReadContext, @@ -857,8 +954,10 @@ pub unsafe extern "C" fn rpr_read_collider_compute_aabb( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return an owned wrapper sharing the collider geometry. Release with rpr_free_shared_shape. Uses +/// only the callback-scoped read context; never retain the context. /// Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_clone_shape( context: *const RprReadContext, @@ -875,7 +974,9 @@ pub unsafe extern "C" fn rpr_read_collider_clone_shape( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Validate the index and generation in the live owning world. Cannot detect a world pointer that +/// has already been freed. Uses only the callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_validate_handle( context: *const RprReadContext, @@ -889,7 +990,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_validate_handle( )) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Validate the index and generation in the live owning world. Cannot detect a world pointer that +/// has already been freed. Uses only the callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_validate_handle( context: *const RprReadContext, @@ -903,7 +1006,9 @@ pub unsafe extern "C" fn rpr_read_collider_validate_handle( )) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body world-space pose. Uses only the callback-scoped read context; never retain +/// the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_position( context: *const RprReadContext, @@ -920,7 +1025,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_position( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body world-space translation. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_translation( context: *const RprReadContext, @@ -937,7 +1044,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_translation( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body world-space linear velocity. Uses only the callback-scoped read context; +/// never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_linvel( context: *const RprReadContext, @@ -954,7 +1063,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_linvel( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body world-space angular velocity (radians per second). Uses only the +/// callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_angvel( context: *const RprReadContext, @@ -971,7 +1082,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_angvel( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the rigid body is sleeping. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_is_sleeping( context: *const RprReadContext, @@ -988,7 +1101,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_is_sleeping( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the rigid body is enabled. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_is_enabled( context: *const RprReadContext, @@ -1005,7 +1120,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_is_enabled( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the rigid body application-owned 128-bit user value. Uses only the callback-scoped read +/// context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_rigid_body)] pub unsafe extern "C" fn rpr_read_rigid_body_user_data( context: *const RprReadContext, @@ -1022,7 +1139,9 @@ pub unsafe extern "C" fn rpr_read_rigid_body_user_data( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider world-space pose. Uses only the callback-scoped read context; never retain +/// the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_position( context: *const RprReadContext, @@ -1039,7 +1158,9 @@ pub unsafe extern "C" fn rpr_read_collider_position( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider world-space translation. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_translation( context: *const RprReadContext, @@ -1056,7 +1177,9 @@ pub unsafe extern "C" fn rpr_read_collider_translation( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider friction coefficient. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_friction( context: *const RprReadContext, @@ -1073,7 +1196,9 @@ pub unsafe extern "C" fn rpr_read_collider_friction( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return the collider restitution coefficient. Uses only the callback-scoped read context; never +/// retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_restitution( context: *const RprReadContext, @@ -1090,7 +1215,9 @@ pub unsafe extern "C" fn rpr_read_collider_restitution( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Return whether the collider is a sensor (detects overlaps without contact forces). Uses only the +/// callback-scoped read context; never retain the context. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_is_sensor( context: *const RprReadContext, @@ -1107,7 +1234,9 @@ pub unsafe extern "C" fn rpr_read_collider_is_sensor( }) }) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Read the parent body handle during a callback; a standalone collider returns an invalid handle +/// with OK status. +/// @ingroup callbacks #[rapier_export(read_collider)] pub unsafe extern "C" fn rpr_read_collider_parent( context: *const RprReadContext, @@ -1127,7 +1256,10 @@ pub unsafe extern "C" fn rpr_read_collider_parent( }, ) } -/// Read callback-visible state. The context is valid only until its callback returns. +/// Copy callback-visible body states in the supplied handle order. All handles must belong to the +/// context world. +/// @see @ref output_buffers +/// @ingroup callbacks #[rapier_export] pub unsafe extern "C" fn rpr_read_rigid_body_read_states( context: *const RprReadContext, diff --git a/c/src/render.rs b/c/src/render.rs index ab5a2d04f..c7a5c2e8c 100644 --- a/c/src/render.rs +++ b/c/src/render.rs @@ -3,7 +3,9 @@ use crate::*; use rapier::parry::shape::{Shape, TypedShape}; /// Owned tessellated shape: flat triangle vertices and independent line segments, in local space. -/// Rounded 3D shapes use their inner surface (as in the Rust testbed). Halfspaces use a finite patch. +/// Rounded 3D shapes use their inner surface (as in the Rust testbed). Halfspaces use a finite +/// patch. +/// @ingroup shapes pub struct RprShapeMesh { triangles: Vec, lines: Vec, @@ -154,7 +156,8 @@ impl RprShapeMesh { } } /// Process-local identity of the immutable shape allocation, for render caches. Keep an owned -/// SharedShape clone alive while caching this value. Not serializable; does not identify equal geometry. +/// SharedShape clone alive while caching this value. Not serializable; does not identify equal +/// geometry. pub(crate) unsafe fn native_collider_shape_identity( collider: *const RprCollider, out: *mut usize, @@ -166,6 +169,9 @@ pub(crate) unsafe fn native_collider_shape_identity( ) }) } +/// Return owned local-space rendering geometry; release it with rpr_free_shape_mesh. subdivisions +/// controls curved-shape resolution. +/// @ingroup shapes #[rapier_export(shared_shape)] pub unsafe extern "C" fn rpr_shared_shape_tessellate( shape: *const RprSharedShape, @@ -188,6 +194,8 @@ pub unsafe extern "C" fn rpr_shared_shape_tessellate( }) } /// Flat groups of three vertices. Standard output-buffer convention. +/// @see @ref output_buffers +/// @ingroup shapes #[rapier_export(shape_mesh)] pub unsafe extern "C" fn rpr_shape_mesh_triangles( mesh: *const RprShapeMesh, @@ -199,6 +207,8 @@ pub unsafe extern "C" fn rpr_shape_mesh_triangles( }) } /// Flat groups of two vertices. Standard output-buffer convention. +/// @see @ref output_buffers +/// @ingroup shapes #[rapier_export(shape_mesh)] pub unsafe extern "C" fn rpr_shape_mesh_lines( mesh: *const RprShapeMesh, @@ -209,6 +219,9 @@ pub unsafe extern "C" fn rpr_shape_mesh_lines( ffi(|| unsafe { copy_out(&get(mesh)?.lines, buffer, capacity, count) }) }) } +/// Release an owned shape mesh. NULL is allowed. Do not pass borrowed pointers or free the object +/// twice. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_free_shape_mesh(mesh: *mut RprShapeMesh) -> RprStatus { ffi(|| unsafe { @@ -219,6 +232,8 @@ pub unsafe extern "C" fn rpr_free_shape_mesh(mesh: *mut RprShapeMesh) -> RprStat Ok(()) }) } +/// Create an owned round cylinder shape. Release it with rpr_free_shared_shape. +/// @ingroup shapes #[cfg(feature = "dim3")] #[rapier_export] pub unsafe extern "C" fn rpr_round_cylinder_shared_shape( @@ -242,6 +257,7 @@ pub unsafe extern "C" fn rpr_round_cylinder_shared_shape( } /// Owned indexed geometry from Parry's shape tessellation, preserving its vertex order. +/// @ingroup shapes #[cfg(feature = "dim3")] pub struct RprTriMeshData { vertices: Vec, @@ -250,6 +266,7 @@ pub struct RprTriMeshData { /// Tessellate a ball or capsule with independent longitude/latitude subdivision counts. /// Cuboids, cones, cylinders, convex polyhedra, trimeshes, and heightfields are also supported. +/// @ingroup shapes #[cfg(feature = "dim3")] #[rapier_export(shared_shape)] pub unsafe extern "C" fn rpr_shared_shape_to_trimesh( @@ -284,6 +301,9 @@ pub unsafe extern "C" fn rpr_shared_shape_to_trimesh( }) } +/// Copy vertices. +/// @see @ref output_buffers +/// @ingroup shapes #[cfg(feature = "dim3")] #[rapier_export(tri_mesh_data)] pub unsafe extern "C" fn rpr_tri_mesh_data_vertices( @@ -297,6 +317,8 @@ pub unsafe extern "C" fn rpr_tri_mesh_data_vertices( } /// Flat triangle indices; count and capacity are numbers of u32 entries. +/// @see @ref output_buffers +/// @ingroup shapes #[cfg(feature = "dim3")] #[rapier_export(tri_mesh_data)] pub unsafe extern "C" fn rpr_tri_mesh_data_indices( @@ -309,6 +331,9 @@ pub unsafe extern "C" fn rpr_tri_mesh_data_indices( }) } +/// Release an owned tri mesh data. NULL is allowed. Do not pass borrowed pointers or free the +/// object twice. +/// @ingroup shapes #[cfg(feature = "dim3")] #[rapier_export] pub unsafe extern "C" fn rpr_free_tri_mesh_data(mesh: *mut RprTriMeshData) -> RprStatus { diff --git a/c/src/return_values.rs b/c/src/return_values.rs index 3734823aa..af2f4cb1e 100644 --- a/c/src/return_values.rs +++ b/c/src/return_values.rs @@ -2,63 +2,98 @@ #![allow(non_snake_case)] use crate::*; +/// Optional ray collider/time result. A miss is found = 0 with status OK. +/// @ingroup queries #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprRayToi { + /// World-bound collider handle. pub collider: RprColliderHandle, + /// Ray parameter t at impact: origin + direction * t. pub toi: RprReal, + /// Whether a result exists; other result fields are meaningful only when this is 1. pub found: RprBool, } +/// Optional full ray result. A miss is found = 0 with status OK. +/// @ingroup queries #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprOptionalRayHit { + /// Shape/ray impact details. pub hit: RprRayHit, + /// Whether a result exists; other result fields are meaningful only when this is 1. pub found: RprBool, } +/// Linear and angular velocity correction computed by a controller. +/// @ingroup controllers #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprVelocityCorrection { + /// World-space linear velocity correction. pub linear: RprVector, + /// World-space angular velocity correction, in radians per second. pub angularVelocity: RprAngVector, } +/// Particle remapping after tearing. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprParticleDestination { + /// World-bound rigid/soft-body handle, as selected by the field type. pub body: RprSoftBodyHandle, + /// Zero-based element index. pub index: u32, } +/// Optional particle remapping; check found before reading the destination. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprOptionalParticleDestination { + /// World-bound rigid/soft-body handle, as selected by the field type. pub body: RprSoftBodyHandle, + /// Zero-based element index. pub index: u32, + /// Whether a result exists; other result fields are meaningful only when this is 1. pub found: RprBool, } /// Borrowed bytes. Valid while the source Bytes object remains alive; never free data. +/// @ingroup math #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprByteView { + /// Borrowed pointer to contiguous elements; NULL is allowed when count is zero. pub data: *const u8, + /// Number of elements, not bytes unless the element type is a byte. pub count: usize, } +/// Optional voxel lookup result; check found before reading voxel data. +/// @ingroup queries #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprVoxelQuery { + /// Integer voxel coordinates. pub key: RprVoxelKey, + /// World-space wheel center. pub center: RprVector, + /// Voxel dimensions along each axis. pub size: RprVector, + /// Whether a result exists; other result fields are meaningful only when this is 1. pub found: RprBool, } +/// World-bound handles of the two connected bodies. +/// @ingroup joints #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprJointBodies { + /// First connected body. pub body1: RprRigidBodyHandle, + /// Second connected body. pub body2: RprRigidBodyHandle, } diff --git a/c/src/robotics.rs b/c/src/robotics.rs index b2c819b19..7c795f50f 100644 --- a/c/src/robotics.rs +++ b/c/src/robotics.rs @@ -14,18 +14,29 @@ unsafe fn path_string<'a>(path: *const c_char) -> Result<&'a str> { } /// Loader configuration. Initialize with DefaultUrdfLoaderOptions; no destructor. /// Blueprint array views and shared shapes are borrowed through the load call. +/// @ingroup robotics #[repr(C)] #[derive(Clone, Copy)] pub struct RprUrdfLoaderOptions { + /// Whether to build colliders from collision geometry. pub createCollidersFromCollisionShapes: RprBool, + /// Whether to also build colliders from visual geometry. pub createCollidersFromVisualShapes: RprBool, + /// Whether to use imported mass/inertia properties. pub applyImportedMassProps: RprBool, + /// Whether bodies connected by imported joints may collide. pub enableJointCollisions: RprBool, + /// Whether imported root bodies are fixed. pub makeRootsFixed: RprBool, + /// Whether to merge empty fixed URDF links. pub squeezeEmptyFixedLinks: RprBool, + /// Transform applied to the imported model. pub shift: RprPose, + /// Shape scale along each axis. pub scale: RprReal, + /// Default collider description; nested geometry resources are borrowed through loading. pub colliderBlueprint: RprColliderDesc, + /// Default rigid-body description used by the importer. pub rigidBodyBlueprint: RprRigidBodyDesc, } impl Default for RprUrdfLoaderOptions { @@ -69,11 +80,19 @@ impl RprUrdfLoaderOptions { }) } } +/// Return native default urdf loader options. This POD value owns no resources. +/// @ingroup robotics #[rapier_export] pub extern "C" fn rpr_default_urdf_loader_options() -> RprUrdfLoaderOptions { RprUrdfLoaderOptions::default() } +/// Loaded URDF robot; insertion clones its simulation objects. Release with the matching Free +/// function. +/// @ingroup robotics pub struct RprUrdfRobot(pub(crate) UrdfRobot); +/// Release an owned urdf robot. NULL is allowed. Do not pass borrowed pointers or free the object +/// twice. +/// @ingroup robotics #[rapier_export] pub unsafe extern "C" fn rpr_free_urdf_robot(object: *mut RprUrdfRobot) -> RprStatus { ffi(|| unsafe { @@ -86,6 +105,7 @@ pub unsafe extern "C" fn rpr_free_urdf_robot(object: *mut RprUrdfRobot) -> RprSt } /// Load from a UTF-8 path. Validates options before reading the file. /// Options and their blueprint resources are borrowed through this call; the robot is owned. +/// @ingroup robotics #[rapier_export] pub unsafe extern "C" fn rpr_urdf_robot_from_file( path: *const c_char, @@ -101,6 +121,8 @@ pub unsafe extern "C" fn rpr_urdf_robot_from_file( }) }) } +/// Apply an additional transform to the loaded robot before insertion. +/// @ingroup robotics #[rapier_export(urdf_robot)] pub unsafe extern "C" fn rpr_urdf_robot_append_transform( robot: *mut RprUrdfRobot, @@ -111,6 +133,9 @@ pub unsafe extern "C" fn rpr_urdf_robot_append_transform( Ok(()) }) } +/// Owned container of borrowed handles to an inserted URDF robot. Release with the matching Free +/// function. +/// @ingroup robotics pub struct RprUrdfRobotHandles { world: *mut RprWorld, handles: UrdfHandles, @@ -119,6 +144,9 @@ enum UrdfHandles { Impulse(UrdfRobotHandles), Multibody(UrdfRobotHandles>), } +/// Release an owned urdf robot handles. NULL is allowed. Do not pass borrowed pointers or free the +/// object twice. +/// @ingroup robotics #[rapier_export] pub unsafe extern "C" fn rpr_free_urdf_robot_handles( handles: *mut RprUrdfRobotHandles, @@ -132,6 +160,7 @@ pub unsafe extern "C" fn rpr_free_urdf_robot_handles( }) } /// Inserts a clone; the source robot remains owned by the caller. Returns owned handles. +/// @ingroup robotics #[rapier_export(urdf_robot)] pub unsafe extern "C" fn rpr_urdf_robot_insert_using_impulse_joints( world: *mut RprWorld, @@ -165,6 +194,7 @@ pub unsafe extern "C" fn rpr_urdf_robot_insert_using_impulse_joints( } /// Inserts a clone; the source robot remains owned by the caller. Returns owned handles. +/// @ingroup robotics #[rapier_export(urdf_robot)] pub unsafe extern "C" fn rpr_urdf_robot_insert_using_multibody_joints( world: *mut RprWorld, @@ -201,6 +231,8 @@ pub unsafe extern "C" fn rpr_urdf_robot_insert_using_multibody_joints( } /// Body handles in source order; absent MJCF bodies have invalid handles. +/// @see @ref output_buffers +/// @ingroup robotics #[rapier_export(urdf_robot_handles)] pub unsafe extern "C" fn rpr_urdf_robot_handles_bodies( handles: *const RprUrdfRobotHandles, @@ -228,19 +260,31 @@ pub unsafe extern "C" fn rpr_urdf_robot_handles_bodies( } /// Loader configuration. Initialize with DefaultMjcfLoaderOptions; no destructor. /// Blueprint array views and shared shapes are borrowed through the load call. +/// @ingroup robotics #[repr(C)] #[derive(Clone, Copy)] pub struct RprMjcfLoaderOptions { + /// Whether to build colliders from collision geometry. pub createCollidersFromCollisionShapes: RprBool, + /// Whether to also build colliders from visual geometry. pub createCollidersFromVisualShapes: RprBool, + /// Whether to use imported mass/inertia properties. pub applyImportedMassProps: RprBool, + /// Whether bodies connected by imported joints may collide. pub enableJointCollisions: RprBool, + /// Whether imported root bodies are fixed. pub makeRootsFixed: RprBool, + /// Whether to omit MJCF plane geometry. pub skipPlaneGeoms: RprBool, + /// Whether imported joint motors are disabled. pub disableJointMotors: RprBool, + /// Transform applied to the imported model. pub shift: RprPose, + /// Shape scale along each axis. pub scale: RprReal, + /// Default collider description; nested geometry resources are borrowed through loading. pub colliderBlueprint: RprColliderDesc, + /// Default rigid-body description used by the importer. pub rigidBodyBlueprint: RprRigidBodyDesc, } impl Default for RprMjcfLoaderOptions { @@ -286,11 +330,18 @@ impl RprMjcfLoaderOptions { }) } } +/// Return native default mjcf loader options. This POD value owns no resources. +/// @ingroup robotics #[rapier_export] pub extern "C" fn rpr_default_mjcf_loader_options() -> RprMjcfLoaderOptions { RprMjcfLoaderOptions::default() } +/// Loaded MJCF robot and its visual/keyframe data. Release with the matching Free function. +/// @ingroup robotics pub struct RprMjcfRobot(pub(crate) MjcfRobot); +/// Release an owned mjcf robot. NULL is allowed. Do not pass borrowed pointers or free the object +/// twice. +/// @ingroup robotics #[rapier_export] pub unsafe extern "C" fn rpr_free_mjcf_robot(object: *mut RprMjcfRobot) -> RprStatus { ffi(|| unsafe { @@ -303,6 +354,7 @@ pub unsafe extern "C" fn rpr_free_mjcf_robot(object: *mut RprMjcfRobot) -> RprSt } /// Load from a UTF-8 path. Validates options before reading the file. /// Options and their blueprint resources are borrowed through this call; the robot is owned. +/// @ingroup robotics #[rapier_export] pub unsafe extern "C" fn rpr_mjcf_robot_from_file( path: *const c_char, @@ -318,6 +370,8 @@ pub unsafe extern "C" fn rpr_mjcf_robot_from_file( }) }) } +/// Apply an additional transform to the loaded robot before insertion. +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_append_transform( robot: *mut RprMjcfRobot, @@ -328,6 +382,9 @@ pub unsafe extern "C" fn rpr_mjcf_robot_append_transform( Ok(()) }) } +/// Owned container of borrowed handles and actuators of an inserted MJCF robot. Release with the +/// matching Free function. +/// @ingroup robotics pub struct RprMjcfRobotHandles { world: *mut RprWorld, handles: MjcfHandles, @@ -336,6 +393,9 @@ enum MjcfHandles { Impulse(MjcfRobotHandles), Multibody(MjcfRobotHandles>), } +/// Release an owned mjcf robot handles. NULL is allowed. Do not pass borrowed pointers or free the +/// object twice. +/// @ingroup robotics #[rapier_export] pub unsafe extern "C" fn rpr_free_mjcf_robot_handles( handles: *mut RprMjcfRobotHandles, @@ -349,6 +409,7 @@ pub unsafe extern "C" fn rpr_free_mjcf_robot_handles( }) } /// Inserts a clone; the source robot remains owned by the caller. Returns owned handles. +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_insert_using_impulse_joints( world: *mut RprWorld, @@ -382,6 +443,7 @@ pub unsafe extern "C" fn rpr_mjcf_robot_insert_using_impulse_joints( } /// Inserts a clone; the source robot remains owned by the caller. Returns owned handles. +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_insert_using_multibody_joints( world: *mut RprWorld, @@ -429,6 +491,8 @@ pub(crate) unsafe fn native_mjcf_robot_insert_using_multibody_joints( }) } /// Body handles in source order; absent MJCF bodies have invalid handles. +/// @see @ref output_buffers +/// @ingroup robotics #[rapier_export(mjcf_robot_handles)] pub unsafe extern "C" fn rpr_mjcf_robot_handles_bodies( handles: *const RprMjcfRobotHandles, @@ -461,14 +525,19 @@ pub unsafe extern "C" fn rpr_mjcf_robot_handles_bodies( } } /// Resolved model gravity before the caller chooses a world convention. +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_gravity(robot: *const RprMjcfRobot) -> RprVector { ffi_value(|out: *mut RprVector| ffi(|| unsafe { output(out, get(robot)?.0.gravity.into()) })) } +/// Return the number of source MJCF bodies. +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_body_count(robot: *const RprMjcfRobot) -> usize { ffi_value(|out: *mut usize| ffi(|| unsafe { output(out, get(robot)?.0.bodies.len()) })) } +/// Return the collider count for a source body index. +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_body_collider_count( robot: *const RprMjcfRobot, @@ -489,7 +558,8 @@ pub unsafe extern "C" fn rpr_mjcf_robot_body_collider_count( }) }) } -/// Borrowed collider; invalidated by freeing or mutating the robot's storage. +/// Set collision groups on a collider in the loaded robot, before insertion. +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_set_body_collider_collision_groups( robot: *mut RprMjcfRobot, @@ -509,11 +579,15 @@ pub unsafe extern "C" fn rpr_mjcf_robot_set_body_collider_collision_groups( Ok(()) }) } +/// Return the number of imported keyframes. +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_keyframe_count(robot: *const RprMjcfRobot) -> usize { ffi_value(|out: *mut usize| ffi(|| unsafe { output(out, get(robot)?.0.keyframes.len()) })) } /// Copies a NUL-terminated UTF-8 name. Count includes NUL; unnamed keys return an empty string. +/// @see @ref output_buffers +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_keyframe_name( robot: *const RprMjcfRobot, @@ -534,6 +608,8 @@ pub unsafe extern "C" fn rpr_mjcf_robot_keyframe_name( }) }) } +/// Append a keyframe from the source MJCF model to the loaded robot. +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_append_keyframe( robot: *mut RprMjcfRobot, @@ -551,6 +627,9 @@ pub unsafe extern "C" fn rpr_mjcf_robot_append_keyframe( Ok(()) }) } +/// Copy actuator controls for the selected keyframe. +/// @see @ref output_buffers +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_keyframe_controls( robot: *const RprMjcfRobot, @@ -569,6 +648,8 @@ pub unsafe extern "C" fn rpr_mjcf_robot_keyframe_controls( }) }) } +/// Return the number of imported actuators. +/// @ingroup robotics #[rapier_export(mjcf_robot_handles)] pub unsafe extern "C" fn rpr_mjcf_robot_handles_actuator_count( handles: *const RprMjcfRobotHandles, @@ -585,6 +666,8 @@ pub unsafe extern "C" fn rpr_mjcf_robot_handles_actuator_count( }) }) } +/// Apply the selected keyframe to the inserted robot. +/// @ingroup robotics #[rapier_export(mjcf_robot_handles)] pub unsafe extern "C" fn rpr_mjcf_robot_handles_apply_keyframe( handles: *const RprMjcfRobotHandles, @@ -624,6 +707,8 @@ pub(crate) unsafe fn native_mjcf_robot_handles_apply_keyframe( Ok(()) }) } +/// Apply actuator controls with per-actuator scaling to the inserted robot. +/// @ingroup robotics #[rapier_export(mjcf_robot_handles)] pub unsafe extern "C" fn rpr_mjcf_robot_handles_apply_controls_scaled( handles: *const RprMjcfRobotHandles, @@ -671,26 +756,43 @@ pub(crate) unsafe fn native_mjcf_robot_handles_apply_controls_scaled( }) } /// A borrowed visual declaration, valid until its robot is freed or its body storage changes. +/// @ingroup robotics #[repr(transparent)] pub struct RprMjcfVisualMesh(pub(crate) MjcfVisualMesh); +/// Imported physically based visual material; no texture ownership. +/// @ingroup robotics #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprRenderMaterial { + /// Material metallic factor. pub metallic: f32, + /// Material roughness factor. pub roughness: f32, + /// Material reflectance factor. pub reflectance: f32, + /// RGB emissive color. pub emissive: [f32; 3], } +/// Copied metadata for a borrowed MJCF visual mesh. +/// @ingroup robotics #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprMjcfVisualMeshInfo { + /// Visual pose relative to its source body. pub local_pose: RprPose, + /// RGBA visual color. pub rgba: [f32; 4], + /// Copied render material; meaningful when has_material is 1. pub material: RprRenderMaterial, + /// Whether rgba contains an authored color. pub has_color: RprBool, + /// Whether material contains authored material data. pub has_material: RprBool, + /// Whether the visual geometry is a triangle mesh. pub is_trimesh: RprBool, } +/// Return the number of visual meshes for a source body. +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_body_visual_count( robot: *const RprMjcfRobot, @@ -711,6 +813,9 @@ pub unsafe extern "C" fn rpr_mjcf_robot_body_visual_count( }) }) } +/// Borrow a visual mesh by body/visual index. Valid until the robot is freed or its storage +/// changes; never free this pointer. +/// @ingroup robotics #[rapier_export(mjcf_robot)] pub unsafe extern "C" fn rpr_mjcf_robot_body_visual( robot: *const RprMjcfRobot, @@ -732,6 +837,8 @@ pub unsafe extern "C" fn rpr_mjcf_robot_body_visual( }) }) } +/// Return a copy of visual pose, color, material, and geometry-kind flags. +/// @ingroup robotics #[rapier_export(mjcf_visual_mesh)] pub unsafe extern "C" fn rpr_mjcf_visual_mesh_info( visual: *const RprMjcfVisualMesh, @@ -761,6 +868,7 @@ pub unsafe extern "C" fn rpr_mjcf_visual_mesh_info( } /// Returns an owned shared shape reference. /// Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. +/// @ingroup robotics #[rapier_export(mjcf_visual_mesh)] pub unsafe extern "C" fn rpr_mjcf_visual_mesh_clone_shape( visual: *const RprMjcfVisualMesh, @@ -776,6 +884,8 @@ pub unsafe extern "C" fn rpr_mjcf_visual_mesh_clone_shape( }) } /// Copies flattened pairs of per-vertex UV coordinates. +/// @see @ref output_buffers +/// @ingroup robotics #[rapier_export(mjcf_visual_mesh)] pub unsafe extern "C" fn rpr_mjcf_visual_mesh_uvs( visual: *const RprMjcfVisualMesh, @@ -794,6 +904,8 @@ pub unsafe extern "C" fn rpr_mjcf_visual_mesh_uvs( }) } /// Copies flattened triples of per-vertex normals. +/// @see @ref output_buffers +/// @ingroup robotics #[rapier_export(mjcf_visual_mesh)] pub unsafe extern "C" fn rpr_mjcf_visual_mesh_normals( visual: *const RprMjcfVisualMesh, @@ -817,6 +929,8 @@ pub unsafe extern "C" fn rpr_mjcf_visual_mesh_normals( }) } /// Copies a NUL-terminated texture path, or an empty string for untextured meshes. +/// @see @ref output_buffers +/// @ingroup robotics #[rapier_export(mjcf_visual_mesh)] pub unsafe extern "C" fn rpr_mjcf_visual_mesh_texture( visual: *const RprMjcfVisualMesh, diff --git a/c/src/scoped_access.rs b/c/src/scoped_access.rs index 459654293..10410c62c 100644 --- a/c/src/scoped_access.rs +++ b/c/src/scoped_access.rs @@ -1,7 +1,9 @@ //! Complete set-and-handle element access. No borrowed element pointer escapes. use crate::handle_access::forward; use crate::*; -/// Resolves the generational handle for this call; rejects stale handles. +/// Return a process-local geometry identity for caching, not a serializable ID. Keep a shared-shape +/// clone alive while using it as a cache key. +/// @ingroup shapes #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_shape_identity(handle: RprColliderHandle) -> usize { let world = handle.world; @@ -33,7 +35,8 @@ pub(crate) unsafe fn native_collider_set_get_shape_identity( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the soft body particle count. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_num_particles(handle: RprSoftBodyHandle) -> usize { let world = handle.world; @@ -54,7 +57,9 @@ pub unsafe extern "C" fn rpr_soft_body_num_particles(handle: RprSoftBodyHandle) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return a counter that changes when particle connectivity changes; use it to invalidate mesh +/// caches. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_topology_version(handle: RprSoftBodyHandle) -> u32 { let world = handle.world; @@ -75,7 +80,8 @@ pub unsafe extern "C" fn rpr_soft_body_topology_version(handle: RprSoftBodyHandl }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the soft body mass. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_mass(handle: RprSoftBodyHandle) -> RprReal { let world = handle.world; @@ -96,7 +102,8 @@ pub unsafe extern "C" fn rpr_soft_body_mass(handle: RprSoftBodyHandle) -> RprRea }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the soft body current volume. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_volume(handle: RprSoftBodyHandle) -> RprReal { let world = handle.world; @@ -117,7 +124,8 @@ pub unsafe extern "C" fn rpr_soft_body_volume(handle: RprSoftBodyHandle) -> RprR }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the soft body undeformed volume. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_rest_volume(handle: RprSoftBodyHandle) -> RprReal { let world = handle.world; @@ -138,7 +146,8 @@ pub unsafe extern "C" fn rpr_soft_body_rest_volume(handle: RprSoftBodyHandle) -> }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the soft body target volume multiplier. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_volume_factor(handle: RprSoftBodyHandle) -> RprReal { let world = handle.world; @@ -159,7 +168,8 @@ pub unsafe extern "C" fn rpr_soft_body_volume_factor(handle: RprSoftBodyHandle) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the soft body world-space center of mass. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_center_of_mass(handle: RprSoftBodyHandle) -> RprVector { let world = handle.world; @@ -180,7 +190,8 @@ pub unsafe extern "C" fn rpr_soft_body_center_of_mass(handle: RprSoftBodyHandle) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the soft body root rigid-proxy handle. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_root_body(handle: RprSoftBodyHandle) -> RprRigidBodyHandle { let world = handle.world; @@ -201,7 +212,8 @@ pub unsafe extern "C" fn rpr_soft_body_root_body(handle: RprSoftBodyHandle) -> R }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the soft body is enabled. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_is_enabled(handle: RprSoftBodyHandle) -> RprBool { let world = handle.world; @@ -222,7 +234,8 @@ pub unsafe extern "C" fn rpr_soft_body_is_enabled(handle: RprSoftBodyHandle) -> }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the soft body is sleeping. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_is_sleeping(handle: RprSoftBodyHandle) -> RprBool { let world = handle.world; @@ -243,7 +256,9 @@ pub unsafe extern "C" fn rpr_soft_body_is_sleeping(handle: RprSoftBodyHandle) -> }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy world-space particle velocities. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_particle_velocities( handle: RprSoftBodyHandle, @@ -270,7 +285,9 @@ pub unsafe extern "C" fn rpr_soft_body_particle_velocities( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy flattened edge vertex indices. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_edges( handle: RprSoftBodyHandle, @@ -297,7 +314,9 @@ pub unsafe extern "C" fn rpr_soft_body_edges( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy flattened cell vertex indices. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_cells( handle: RprSoftBodyHandle, @@ -324,7 +343,9 @@ pub unsafe extern "C" fn rpr_soft_body_cells( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy flattened boundary element indices. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_boundary( handle: RprSoftBodyHandle, @@ -351,7 +372,9 @@ pub unsafe extern "C" fn rpr_soft_body_boundary( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy piece identifiers. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_pieces( handle: RprSoftBodyHandle, @@ -380,7 +403,8 @@ pub unsafe extern "C" fn rpr_soft_body_pieces( } } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the soft body particle world-space velocity. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_particle_velocity( handle: RprSoftBodyHandle, @@ -404,7 +428,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_particle_velocity( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the next world-space target position of a pinned particle. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_particle_kinematic_target( handle: RprSoftBodyHandle, @@ -428,7 +453,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_particle_kinematic_target( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Enable or disable pinning the particle for the soft body. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_particle_pinned( handle: RprSoftBodyHandle, @@ -452,7 +478,9 @@ pub unsafe extern "C" fn rpr_soft_body_set_particle_pinned( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Apply a world-space impulse to one particle. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_apply_particle_impulse( handle: RprSoftBodyHandle, @@ -478,7 +506,9 @@ pub unsafe extern "C" fn rpr_soft_body_apply_particle_impulse( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Accumulate a world-space force; it persists until reset. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_add_force( handle: RprSoftBodyHandle, @@ -502,7 +532,9 @@ pub unsafe extern "C" fn rpr_soft_body_add_force( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Apply a world-space linear impulse. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_apply_impulse( handle: RprSoftBodyHandle, @@ -526,7 +558,9 @@ pub unsafe extern "C" fn rpr_soft_body_apply_impulse( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Clear accumulated user forces. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_reset_forces( handle: RprSoftBodyHandle, @@ -548,7 +582,8 @@ pub unsafe extern "C" fn rpr_soft_body_reset_forces( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Enable or disable the soft body. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_enabled( handle: RprSoftBodyHandle, @@ -570,7 +605,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_enabled( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the soft body target volume multiplier. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_volume_factor( handle: RprSoftBodyHandle, @@ -592,7 +628,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_volume_factor( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Attach a particle to a rigid body at the supplied body-local anchor. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_attach_particle( handle: RprSoftBodyHandle, @@ -619,7 +656,8 @@ pub unsafe extern "C" fn rpr_soft_body_attach_particle( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Remove a particle attachment to a rigid body. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_detach_particle( handle: RprSoftBodyHandle, @@ -641,7 +679,9 @@ pub unsafe extern "C" fn rpr_soft_body_detach_particle( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy cluster indices. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_clusters( handle: RprSoftBodyHandle, @@ -668,7 +708,8 @@ pub unsafe extern "C" fn rpr_soft_body_clusters( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid proxy for the selected cluster. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_cluster_proxy( handle: RprSoftBodyHandle, @@ -693,7 +734,9 @@ pub unsafe extern "C" fn rpr_soft_body_cluster_proxy( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy particle indices for a cluster. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_cluster_particles( handle: RprSoftBodyHandle, @@ -722,7 +765,8 @@ pub unsafe extern "C" fn rpr_soft_body_cluster_particles( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Enable or disable pinning the cluster for the soft body. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_cluster_pinned( handle: RprSoftBodyHandle, @@ -746,7 +790,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_cluster_pinned( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the next world-space target pose of a pinned cluster. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_cluster_kinematic_target( handle: RprSoftBodyHandle, @@ -770,7 +815,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_cluster_kinematic_target( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Enable or disable using cluster shape matching for the soft body. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_cluster_shape_matching_enabled( handle: RprSoftBodyHandle, @@ -794,7 +840,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_cluster_shape_matching_enabled( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the soft body cluster shape-matching stiffness multiplier. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_cluster_stiffness_scale( handle: RprSoftBodyHandle, @@ -818,7 +865,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_cluster_stiffness_scale( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the soft body cluster tear-resistance multiplier. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_cluster_tear_resistance( handle: RprSoftBodyHandle, @@ -842,7 +890,9 @@ pub unsafe extern "C" fn rpr_soft_body_set_cluster_tear_resistance( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy collision mesh metadata. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_meshes( handle: RprSoftBodyHandle, @@ -871,7 +921,9 @@ pub unsafe extern "C" fn rpr_soft_body_meshes( } } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy world-space vertices for a mesh ID. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_mesh_vertices_by_id( handle: RprSoftBodyHandle, @@ -900,7 +952,9 @@ pub unsafe extern "C" fn rpr_soft_body_mesh_vertices_by_id( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy flattened indices for a mesh ID. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_mesh_indices_by_id( handle: RprSoftBodyHandle, @@ -929,7 +983,9 @@ pub unsafe extern "C" fn rpr_soft_body_mesh_indices_by_id( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy collision mesh collider handles. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_mesh_colliders( handle: RprSoftBodyHandle, @@ -958,7 +1014,9 @@ pub unsafe extern "C" fn rpr_soft_body_mesh_colliders( } } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy world-space collision mesh vertices. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_mesh_vertices( handle: RprSoftBodyHandle, @@ -988,7 +1046,9 @@ pub unsafe extern "C" fn rpr_soft_body_mesh_vertices( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy flattened collision mesh indices. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_mesh_indices( handle: RprSoftBodyHandle, @@ -1018,7 +1078,8 @@ pub unsafe extern "C" fn rpr_soft_body_mesh_indices( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return indices per collision-mesh element (2 for segments, 3 for triangles). +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_mesh_arity( handle: RprSoftBodyHandle, @@ -1044,7 +1105,8 @@ pub unsafe extern "C" fn rpr_soft_body_mesh_arity( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the selected collision mesh topology revision for cache invalidation. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_mesh_topology_version( handle: RprSoftBodyHandle, @@ -1070,7 +1132,8 @@ pub unsafe extern "C" fn rpr_soft_body_mesh_topology_version( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the soft body soft solver kind (RPR_SOFT_SOLVER_*). +/// @ingroup soft_bodies #[cfg(feature = "fem")] #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_solver( @@ -1093,7 +1156,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_solver( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the soft body cluster shape-matching target pose. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_cluster_shape_matching_target( handle: RprSoftBodyHandle, @@ -1117,7 +1181,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_cluster_shape_matching_target( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the soft body edge tear-resistance multiplier. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_set_edge_tear_resistance( handle: RprSoftBodyHandle, @@ -1141,7 +1206,8 @@ pub unsafe extern "C" fn rpr_soft_body_set_edge_tear_resistance( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the selected collision mesh is closed. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_mesh_is_closed( handle: RprSoftBodyHandle, @@ -1167,7 +1233,9 @@ pub unsafe extern "C" fn rpr_soft_body_mesh_is_closed( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the rigid body local mass properties added to collider contributions. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_additional_mass_properties( handle: RprRigidBodyHandle, @@ -1195,7 +1263,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_additional_mass_properties( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Recompute body mass and inertia from attached colliders and additional mass properties. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_recompute_mass_properties_from_colliders( handle: RprRigidBodyHandle, @@ -1221,7 +1290,8 @@ pub unsafe extern "C" fn rpr_rigid_body_recompute_mass_properties_from_colliders }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider local mass properties. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_mass_properties( handle: RprColliderHandle, @@ -1243,7 +1313,8 @@ pub unsafe extern "C" fn rpr_collider_set_mass_properties( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the collider local mass properties. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_mass_properties( handle: RprColliderHandle, @@ -1277,7 +1348,9 @@ pub(crate) unsafe fn native_collider_set_get_mass_properties( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the rigid body translation/rotation lock bitmask. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_locked_axes( handle: RprRigidBodyHandle, @@ -1305,7 +1378,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_locked_axes( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body translation/rotation lock bitmask. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_locked_axes(handle: RprRigidBodyHandle) -> u8 { let world = handle.world; @@ -1337,7 +1411,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_locked_axes( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the collider is a voxel shape. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_is_voxels(handle: RprColliderHandle) -> RprBool { let world = handle.world; @@ -1369,7 +1444,8 @@ pub(crate) unsafe fn native_collider_set_get_is_voxels( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return voxel information at a flat index; found = 0 if absent. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_voxel_at_flat_id( handle: RprColliderHandle, @@ -1421,7 +1497,8 @@ pub(crate) unsafe fn native_collider_set_get_voxel_at_flat_id( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Fill or clear the voxel at key; the collider must have a voxel shape. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_voxel( handle: RprColliderHandle, @@ -1445,7 +1522,8 @@ pub unsafe extern "C" fn rpr_collider_set_voxel( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body next kinematic world-space pose. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_next_position(handle: RprRigidBodyHandle) -> RprPose { let world = handle.world; @@ -1477,7 +1555,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_next_position( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body world-space rotation. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_rotation(handle: RprRigidBodyHandle) -> RprRotation { let world = handle.world; @@ -1509,7 +1588,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_rotation( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body world-space center of mass. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_center_of_mass(handle: RprRigidBodyHandle) -> RprVector { let world = handle.world; @@ -1541,7 +1621,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_center_of_mass( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body body-local center of mass. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_local_center_of_mass( handle: RprRigidBodyHandle, @@ -1575,7 +1656,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_local_center_of_mass( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body accumulated user-applied world-space force. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_user_force(handle: RprRigidBodyHandle) -> RprVector { let world = handle.world; @@ -1607,7 +1689,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_user_force( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body accumulated user-applied world-space torque. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_user_torque(handle: RprRigidBodyHandle) -> RprAngVector { let world = handle.world; @@ -1639,7 +1722,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_user_torque( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body body type (RPR_DYNAMIC, RPR_FIXED, or a kinematic kind). +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_body_type(handle: RprRigidBodyHandle) -> u32 { let world = handle.world; @@ -1671,7 +1755,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_body_type( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body mass. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_mass(handle: RprRigidBodyHandle) -> RprReal { let world = handle.world; @@ -1703,7 +1788,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_mass( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body gravity multiplier. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_gravity_scale(handle: RprRigidBodyHandle) -> RprReal { let world = handle.world; @@ -1735,7 +1821,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_gravity_scale( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body linear damping coefficient. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_linear_damping(handle: RprRigidBodyHandle) -> RprReal { let world = handle.world; @@ -1767,7 +1854,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_linear_damping( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body angular damping coefficient. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_angular_damping(handle: RprRigidBodyHandle) -> RprReal { let world = handle.world; @@ -1799,7 +1887,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_angular_damping( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body kinetic energy. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_kinetic_energy(handle: RprRigidBodyHandle) -> RprReal { let world = handle.world; @@ -1831,7 +1920,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_kinetic_energy( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the rigid body soft-CCD prediction distance. +/// @ingroup soft_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_soft_ccd_prediction(handle: RprRigidBodyHandle) -> RprReal { let world = handle.world; @@ -1863,7 +1953,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_soft_ccd_prediction( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the rigid body is using continuous collision detection. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_is_ccd_enabled(handle: RprRigidBodyHandle) -> RprBool { let world = handle.world; @@ -1895,7 +1986,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_is_ccd_enabled( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the rigid body is dynamic. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_is_dynamic(handle: RprRigidBodyHandle) -> RprBool { let world = handle.world; @@ -1927,7 +2019,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_is_dynamic( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the associated soft-body handle, or an invalid handle if this is not a soft proxy. +/// @ingroup soft_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_soft_body(handle: RprRigidBodyHandle) -> RprSoftBodyHandle { let world = handle.world; @@ -1959,7 +2052,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_soft_body( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the rigid body is a soft-body proxy. +/// @ingroup soft_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_is_soft_frame(handle: RprRigidBodyHandle) -> RprBool { let world = handle.world; @@ -1991,7 +2085,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_is_soft_frame( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the rigid body is fixed. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_is_fixed(handle: RprRigidBodyHandle) -> RprBool { let world = handle.world; @@ -2023,7 +2118,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_is_fixed( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the rigid body is kinematic. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_is_kinematic(handle: RprRigidBodyHandle) -> RprBool { let world = handle.world; @@ -2055,7 +2151,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_is_kinematic( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the rigid body is moving. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_is_moving(handle: RprRigidBodyHandle) -> RprBool { let world = handle.world; @@ -2087,7 +2184,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_is_moving( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the rigid body is currently using CCD for its motion. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_is_ccd_active(handle: RprRigidBodyHandle) -> RprBool { let world = handle.world; @@ -2119,7 +2217,9 @@ pub(crate) unsafe fn native_rigid_body_set_get_is_ccd_active( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the rigid body world-space rotation. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_rotation( handle: RprRigidBodyHandle, @@ -2147,7 +2247,9 @@ pub unsafe extern "C" fn rpr_rigid_body_set_rotation( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the rigid body body type (RPR_DYNAMIC, RPR_FIXED, or a kinematic kind). +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_body_type( handle: RprRigidBodyHandle, @@ -2175,7 +2277,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_body_type( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the rigid body next kinematic world-space rotation. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_next_kinematic_rotation( handle: RprRigidBodyHandle, @@ -2201,7 +2304,9 @@ pub unsafe extern "C" fn rpr_rigid_body_set_next_kinematic_rotation( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the rigid body mass added to collider contributions. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_additional_mass( handle: RprRigidBodyHandle, @@ -2229,7 +2334,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_additional_mass( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the rigid body soft-CCD prediction distance. +/// @ingroup soft_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_soft_ccd_prediction( handle: RprRigidBodyHandle, @@ -2255,7 +2361,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_soft_ccd_prediction( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Enable or disable using continuous collision detection for the rigid body. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_ccd_enabled( handle: RprRigidBodyHandle, @@ -2281,7 +2388,9 @@ pub unsafe extern "C" fn rpr_rigid_body_set_ccd_enabled( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Enable or disable locking translation for the rigid body. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_translations_locked( handle: RprRigidBodyHandle, @@ -2309,7 +2418,9 @@ pub unsafe extern "C" fn rpr_rigid_body_set_translations_locked( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Enable or disable locking rotation for the rigid body. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_rotations_locked( handle: RprRigidBodyHandle, @@ -2337,7 +2448,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_rotations_locked( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the rigid body signed dominance group. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_dominance_group( handle: RprRigidBodyHandle, @@ -2363,7 +2475,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_dominance_group( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the rigid body additional solver iterations for connected bodies. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_additional_solver_iterations( handle: RprRigidBodyHandle, @@ -2389,7 +2502,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_additional_solver_iterations( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the rigid body additional PGS iterations. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_additional_pgs_iterations( handle: RprRigidBodyHandle, @@ -2415,7 +2529,9 @@ pub unsafe extern "C" fn rpr_rigid_body_set_additional_pgs_iterations( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Accumulate a world-space torque; it persists until reset. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_add_torque( handle: RprRigidBodyHandle, @@ -2443,7 +2559,9 @@ pub unsafe extern "C" fn rpr_rigid_body_add_torque( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Apply a world-space angular impulse. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_apply_torque_impulse( handle: RprRigidBodyHandle, @@ -2471,7 +2589,9 @@ pub unsafe extern "C" fn rpr_rigid_body_apply_torque_impulse( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Accumulate a world-space force applied at a world-space point. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_add_force_at_point( handle: RprRigidBodyHandle, @@ -2501,7 +2621,9 @@ pub unsafe extern "C" fn rpr_rigid_body_add_force_at_point( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Clear accumulated user torques. +/// wake_up = 1 wakes affected bodies; 0 preserves their sleep state. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_reset_torques( handle: RprRigidBodyHandle, @@ -2527,7 +2649,8 @@ pub unsafe extern "C" fn rpr_rigid_body_reset_torques( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return world-space velocity at a world-space point, including angular motion. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_velocity_at_point( handle: RprRigidBodyHandle, @@ -2565,7 +2688,9 @@ pub(crate) unsafe fn native_rigid_body_set_get_velocity_at_point( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Copy attached collider handles. +/// @see @ref output_buffers +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_colliders( handle: RprRigidBodyHandle, @@ -2609,7 +2734,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_colliders( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the rigid body is using gyroscopic forces. +/// @ingroup rigid_bodies #[cfg(feature = "dim3")] #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_gyroscopic_forces_enabled( @@ -2644,7 +2770,8 @@ pub(crate) unsafe fn native_rigid_body_set_get_gyroscopic_forces_enabled( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Enable or disable using gyroscopic forces for the rigid body. +/// @ingroup rigid_bodies #[cfg(feature = "dim3")] #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_set_gyroscopic_forces_enabled( @@ -2671,7 +2798,8 @@ pub unsafe extern "C" fn rpr_rigid_body_set_gyroscopic_forces_enabled( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider mass per unit volume. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_density( handle: RprColliderHandle, @@ -2693,7 +2821,8 @@ pub unsafe extern "C" fn rpr_collider_set_density( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider mass. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_mass( handle: RprColliderHandle, @@ -2715,7 +2844,8 @@ pub unsafe extern "C" fn rpr_collider_set_mass( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Enable or disable the collider. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_enabled( handle: RprColliderHandle, @@ -2737,7 +2867,8 @@ pub unsafe extern "C" fn rpr_collider_set_enabled( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider contact-force filtering groups. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_solver_groups( handle: RprColliderHandle, @@ -2759,7 +2890,8 @@ pub unsafe extern "C" fn rpr_collider_set_solver_groups( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider friction combination rule (RPR_COMBINE_*). +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_friction_combine_rule( handle: RprColliderHandle, @@ -2781,7 +2913,8 @@ pub unsafe extern "C" fn rpr_collider_set_friction_combine_rule( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider restitution combination rule (RPR_COMBINE_*). +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_restitution_combine_rule( handle: RprColliderHandle, @@ -2803,7 +2936,8 @@ pub unsafe extern "C" fn rpr_collider_set_restitution_combine_rule( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider extra separation skin around the shape. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_contact_skin( handle: RprColliderHandle, @@ -2825,7 +2959,8 @@ pub unsafe extern "C" fn rpr_collider_set_contact_skin( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider force threshold for contact-force events. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_contact_force_event_threshold( handle: RprColliderHandle, @@ -2847,7 +2982,8 @@ pub unsafe extern "C" fn rpr_collider_set_contact_force_event_threshold( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider event-generation bitmask (RPR_COLLISION_EVENTS and RPR_CONTACT_FORCE_EVENTS). +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_active_events( handle: RprColliderHandle, @@ -2869,7 +3005,8 @@ pub unsafe extern "C" fn rpr_collider_set_active_events( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider physics-hook activation bitmask. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_active_hooks( handle: RprColliderHandle, @@ -2891,7 +3028,8 @@ pub unsafe extern "C" fn rpr_collider_set_active_hooks( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider body-type collision activation bitmask. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_active_collision_types( handle: RprColliderHandle, @@ -2913,7 +3051,8 @@ pub unsafe extern "C" fn rpr_collider_set_active_collision_types( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the collider world-space rotation. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_rotation(handle: RprColliderHandle) -> RprRotation { let world = handle.world; @@ -2945,7 +3084,8 @@ pub(crate) unsafe fn native_collider_set_get_rotation( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the collider collision filtering groups. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_collision_groups( handle: RprColliderHandle, @@ -2979,7 +3119,8 @@ pub(crate) unsafe fn native_collider_set_get_collision_groups( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the collider contact-force filtering groups. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_solver_groups( handle: RprColliderHandle, @@ -3013,7 +3154,8 @@ pub(crate) unsafe fn native_collider_set_get_solver_groups( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the collider application-owned 128-bit user value. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_user_data(handle: RprColliderHandle) -> RprUserData { let world = handle.world; @@ -3045,7 +3187,9 @@ pub(crate) unsafe fn native_collider_set_get_user_data( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the collider event-generation bitmask (RPR_COLLISION_EVENTS and +/// RPR_CONTACT_FORCE_EVENTS). +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_active_events(handle: RprColliderHandle) -> u32 { let world = handle.world; @@ -3077,7 +3221,8 @@ pub(crate) unsafe fn native_collider_set_get_active_events( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the collider mass. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_mass(handle: RprColliderHandle) -> RprReal { let world = handle.world; @@ -3109,7 +3254,8 @@ pub(crate) unsafe fn native_collider_set_get_mass( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the collider mass per unit volume. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_density(handle: RprColliderHandle) -> RprReal { let world = handle.world; @@ -3141,7 +3287,8 @@ pub(crate) unsafe fn native_collider_set_get_density( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the collider current volume. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_volume(handle: RprColliderHandle) -> RprReal { let world = handle.world; @@ -3173,7 +3320,8 @@ pub(crate) unsafe fn native_collider_set_get_volume( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the collider extra separation skin around the shape. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_contact_skin(handle: RprColliderHandle) -> RprReal { let world = handle.world; @@ -3205,7 +3353,8 @@ pub(crate) unsafe fn native_collider_set_get_contact_skin( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the collider force threshold for contact-force events. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_contact_force_event_threshold( handle: RprColliderHandle, @@ -3239,7 +3388,8 @@ pub(crate) unsafe fn native_collider_set_get_contact_force_event_threshold( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return whether the collider is enabled. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_is_enabled(handle: RprColliderHandle) -> RprBool { let world = handle.world; @@ -3271,7 +3421,8 @@ pub(crate) unsafe fn native_collider_set_get_is_enabled( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return the current world-space axis-aligned bounds. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_compute_aabb(handle: RprColliderHandle) -> RprAabb { let world = handle.world; @@ -3303,8 +3454,9 @@ pub(crate) unsafe fn native_collider_set_get_compute_aabb( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Return an owned wrapper sharing the collider geometry. Release with rpr_free_shared_shape. /// Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. +/// @ingroup shapes #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_clone_shape( handle: RprColliderHandle, @@ -3338,7 +3490,8 @@ pub(crate) unsafe fn native_collider_set_get_shared_shape( )) }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Replace collider geometry by sharing shape; the supplied wrapper is not consumed. +/// @ingroup shapes #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_shape( handle: RprColliderHandle, @@ -3360,7 +3513,8 @@ pub unsafe extern "C" fn rpr_collider_set_shape( }) } -/// Resolves the generational handle for this call; rejects stale handles. +/// Set the collider pose relative to the parent rigid body. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_set_position_wrt_parent( handle: RprColliderHandle, @@ -3382,6 +3536,9 @@ pub unsafe extern "C" fn rpr_collider_set_position_wrt_parent( }) } +/// Validate the index and generation in the live owning world. Cannot detect a world pointer that +/// has already been freed. +/// @ingroup rigid_bodies #[rapier_export(rigid_body)] pub unsafe extern "C" fn rpr_rigid_body_validate_handle(handle: RprRigidBodyHandle) -> RprStatus { let world = handle.world; @@ -3406,6 +3563,9 @@ pub(crate) unsafe fn native_rigid_body_set_validate_handle( Ok(()) }) } +/// Validate the index and generation in the live owning world. Cannot detect a world pointer that +/// has already been freed. +/// @ingroup colliders #[rapier_export(collider)] pub unsafe extern "C" fn rpr_collider_validate_handle(handle: RprColliderHandle) -> RprStatus { let world = handle.world; @@ -3430,6 +3590,9 @@ pub(crate) unsafe fn native_collider_set_validate_handle( Ok(()) }) } +/// Validate the index and generation in the live owning world. Cannot detect a world pointer that +/// has already been freed. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_validate_handle(handle: RprSoftBodyHandle) -> RprStatus { let world = handle.world; diff --git a/c/src/shape_desc.rs b/c/src/shape_desc.rs index 3b2f7c73e..5081c1996 100644 --- a/c/src/shape_desc.rs +++ b/c/src/shape_desc.rs @@ -1,6 +1,8 @@ //! POD shape constructors; input geometry is borrowed until insertion. use crate::*; +/// Return a rounded box description; half_extents exclude the added border_radius. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders #[rapier_export] pub extern "C" fn rpr_round_cuboid_collider_desc( half_extents: RprVector, @@ -16,7 +18,9 @@ pub extern "C" fn rpr_round_cuboid_collider_desc( ..RprColliderDesc::default() } } +/// Return a capsule description with segment endpoints a/b and the supplied radius. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders #[rapier_export] pub extern "C" fn rpr_capsule_collider_desc( a: RprVector, @@ -34,7 +38,9 @@ pub extern "C" fn rpr_capsule_collider_desc( ..RprColliderDesc::default() } } +/// Return a segment description with endpoints a and b. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders #[rapier_export] pub extern "C" fn rpr_segment_collider_desc(a: RprVector, b: RprVector) -> RprColliderDesc { RprColliderDesc { @@ -47,7 +53,9 @@ pub extern "C" fn rpr_segment_collider_desc(a: RprVector, b: RprVector) -> RprCo ..RprColliderDesc::default() } } +/// Return a triangle description with vertices a, b, and c. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders #[rapier_export] pub extern "C" fn rpr_triangle_collider_desc( a: RprVector, @@ -65,7 +73,9 @@ pub extern "C" fn rpr_triangle_collider_desc( ..RprColliderDesc::default() } } +/// Return a half-space description bounded by a plane through the origin; normal points outward. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders #[rapier_export] pub extern "C" fn rpr_halfspace_collider_desc(normal: RprVector) -> RprColliderDesc { RprColliderDesc { @@ -77,8 +87,10 @@ pub extern "C" fn rpr_halfspace_collider_desc(normal: RprVector) -> RprColliderD ..RprColliderDesc::default() } } -#[cfg(feature = "dim3")] +/// Return a Y-aligned cylinder description with the supplied half-height and radius. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders +#[cfg(feature = "dim3")] #[rapier_export] pub extern "C" fn rpr_cylinder_collider_desc( half_height: RprReal, @@ -94,8 +106,10 @@ pub extern "C" fn rpr_cylinder_collider_desc( ..RprColliderDesc::default() } } -#[cfg(feature = "dim3")] +/// Return a Y-aligned cone description with the supplied half-height and base radius. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders +#[cfg(feature = "dim3")] #[rapier_export] pub extern "C" fn rpr_cone_collider_desc(half_height: RprReal, radius: RprReal) -> RprColliderDesc { RprColliderDesc { @@ -108,8 +122,10 @@ pub extern "C" fn rpr_cone_collider_desc(half_height: RprReal, radius: RprReal) ..RprColliderDesc::default() } } -#[cfg(feature = "dim3")] +/// Return a rounded Y-aligned cylinder description; dimensions exclude border_radius. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders +#[cfg(feature = "dim3")] #[rapier_export] pub extern "C" fn rpr_round_cylinder_collider_desc( half_height: RprReal, @@ -127,7 +143,9 @@ pub extern "C" fn rpr_round_cylinder_collider_desc( ..RprColliderDesc::default() } } +/// Return a X-aligned capsule description; half_height is half the segment length, excluding caps. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders #[rapier_export] pub extern "C" fn rpr_capsule_x_collider_desc( half_height: RprReal, @@ -145,7 +163,9 @@ pub extern "C" fn rpr_capsule_x_collider_desc( ..RprColliderDesc::default() } } +/// Return a Y-aligned capsule description; half_height is half the segment length, excluding caps. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders #[rapier_export] pub extern "C" fn rpr_capsule_y_collider_desc( half_height: RprReal, @@ -163,8 +183,10 @@ pub extern "C" fn rpr_capsule_y_collider_desc( ..RprColliderDesc::default() } } -#[cfg(feature = "dim3")] +/// Return a Z-aligned capsule description; half_height is half the segment length, excluding caps. /// Returns a description without allocating or validating. Build/insert validates its fields. +/// @ingroup colliders +#[cfg(feature = "dim3")] #[rapier_export] pub extern "C" fn rpr_capsule_z_collider_desc( half_height: RprReal, diff --git a/c/src/soft_body.rs b/c/src/soft_body.rs index 916e4f384..648f70b9a 100644 --- a/c/src/soft_body.rs +++ b/c/src/soft_body.rs @@ -1,5 +1,7 @@ use crate::*; +/// Remove a soft body and its associated simulation objects. Invalidates its handle. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_remove_soft_body(handle: RprSoftBodyHandle) -> RprStatus { let world = handle.world; @@ -316,6 +318,8 @@ pub(crate) unsafe fn native_soft_body_reset_forces( Ok(()) }) } +/// Wake the soft body and its rigid proxies. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_wake_up(handle: RprSoftBodyHandle) -> RprStatus { let world = handle.world; @@ -387,6 +391,9 @@ pub(crate) unsafe fn native_soft_body_detach_particle( }) } +/// Release an owned soft body tear event. NULL is allowed. Do not pass borrowed pointers or free +/// the object twice. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_free_soft_body_tear_event( event: *mut RprSoftBodyTearEvent, @@ -399,6 +406,8 @@ pub unsafe extern "C" fn rpr_free_soft_body_tear_event( Ok(()) }) } +/// Return the source soft-body handle for this tear event. +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_soft_body( event: *const RprSoftBodyTearEvent, @@ -410,6 +419,9 @@ pub unsafe extern "C" fn rpr_soft_body_tear_event_soft_body( }, ) } +/// Copy the soft-body handles produced by the tear. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_bodies( event: *const RprSoftBodyTearEvent, @@ -430,6 +442,8 @@ pub unsafe extern "C" fn rpr_soft_body_tear_event_bodies( ) } } +/// Return the destination body and particle index for an original particle. +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_particle_destination( event: *const RprSoftBodyTearEvent, @@ -455,6 +469,8 @@ pub unsafe extern "C" fn rpr_soft_body_tear_event_particle_destination( ) } /// Flat indices; element arity follows the corresponding Rust event field. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_torn_edges( event: *const RprSoftBodyTearEvent, @@ -470,6 +486,8 @@ pub unsafe extern "C" fn rpr_soft_body_tear_event_torn_edges( }) } /// Flat indices; element arity follows the corresponding Rust event field. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_torn_cells( event: *const RprSoftBodyTearEvent, @@ -485,6 +503,8 @@ pub unsafe extern "C" fn rpr_soft_body_tear_event_torn_cells( }) } /// Flat indices; element arity follows the corresponding Rust event field. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_removed_edges( event: *const RprSoftBodyTearEvent, @@ -500,6 +520,8 @@ pub unsafe extern "C" fn rpr_soft_body_tear_event_removed_edges( }) } /// Flat indices; element arity follows the corresponding Rust event field. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_split_particles( event: *const RprSoftBodyTearEvent, @@ -519,6 +541,8 @@ pub unsafe extern "C" fn rpr_soft_body_tear_event_split_particles( }) } /// Flat indices; element arity follows the corresponding Rust event field. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_inserted_particles( event: *const RprSoftBodyTearEvent, @@ -533,6 +557,9 @@ pub unsafe extern "C" fn rpr_soft_body_tear_event_inserted_particles( }) }) } +/// Copy original particle indices belonging to a resulting piece. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_piece_particles( event: *const RprSoftBodyTearEvent, @@ -551,22 +578,37 @@ pub unsafe extern "C" fn rpr_soft_body_tear_event_piece_particles( }) }) } +/// Cluster and proxy remapping after a soft-body split. +/// @ingroup soft_bodies #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprSoftClusterSplit { + /// Original cluster index before splitting. pub source_cluster: u32, + /// Resulting soft-body handle. pub soft_body: RprSoftBodyHandle, + /// Cluster index within its soft body. pub cluster: u32, + /// Cluster rigid-proxy body handle. pub proxy: RprRigidBodyHandle, + /// Whether this split retains the original proxy. pub keeps_proxy: RprBool, } +/// Impulse-joint movement between rigid proxies after tearing. +/// @ingroup soft_bodies #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprSoftJointMove { + /// Impulse joint that moved between proxies. pub joint: RprImpulseJointHandle, + /// Original rigid-proxy body. pub from: RprRigidBodyHandle, + /// Destination rigid-proxy body. pub to: RprRigidBodyHandle, } +/// Copy cluster-to-piece and rigid-proxy remapping records. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_clusters( event: *const RprSoftBodyTearEvent, @@ -598,6 +640,9 @@ pub unsafe extern "C" fn rpr_soft_body_tear_event_clusters( ) } } +/// Copy impulse-joint remapping records produced by the tear. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_moved_joints( event: *const RprSoftBodyTearEvent, @@ -628,6 +673,9 @@ pub unsafe extern "C" fn rpr_soft_body_tear_event_moved_joints( } } +/// Tear the selected edges and return an owned remapping event. Release it with +/// rpr_free_soft_body_tear_event. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_tear( handle: RprSoftBodyHandle, @@ -681,6 +729,8 @@ pub unsafe extern "C" fn rpr_soft_body_tear( }) } +/// Create a rigid proxy cluster from the supplied particle indices and return its cluster index. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_add_cluster( handle: RprSoftBodyHandle, @@ -719,6 +769,8 @@ pub unsafe extern "C" fn rpr_soft_body_add_cluster( }) } +/// Remove the selected cluster and its rigid proxy. +/// @ingroup soft_bodies #[rapier_export(soft_body)] pub unsafe extern "C" fn rpr_soft_body_remove_cluster( handle: RprSoftBodyHandle, @@ -872,10 +924,13 @@ pub(crate) unsafe fn native_soft_body_set_cluster_tear_resistance( } /// Stable identity of a live mesh within one soft body; matches Rapier's SoftMeshId. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy, Default, PartialEq, Eq)] pub struct RprSoftMeshId { + /// Cluster index within its soft body. pub cluster: u32, + /// Mesh index within the cluster. pub mesh: u32, } impl RprSoftMeshId { @@ -887,13 +942,19 @@ impl RprSoftMeshId { } } /// Mesh identity and rendering metadata. A render-only mesh has an invalid collider handle. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprSoftMeshInfo { + /// Cluster/mesh pair identifying this collision mesh. pub id: RprSoftMeshId, + /// World-bound collider handle. pub collider: RprColliderHandle, + /// Indices per mesh element: 2 for an edge or 3 for a triangle. pub arity: usize, + /// Whether vertex positions are obtained by skinning. pub is_skinned: RprBool, + /// Whether collision detection is enabled for this mesh. pub collision_enabled: RprBool, } /// Enumerates all live meshes, including skins without a physics collider. @@ -1088,6 +1149,7 @@ pub(crate) unsafe fn native_soft_body_particle_position( /// Optional particle destination after a tear. Missing destinations are normal and set /// found to false; body/index are only written when a destination exists. +/// @ingroup soft_bodies #[rapier_export(soft_body_tear_event)] pub unsafe extern "C" fn rpr_soft_body_tear_event_try_particle_destination( event: *const RprSoftBodyTearEvent, @@ -1134,6 +1196,7 @@ pub(crate) unsafe fn native_soft_body_set_edge_tear_resistance( /// Cut using DIM points (a segment in 2D, triangle in 3D). A no-op returns a null event. /// The optional owned event must be freed with FreeSoftBodyTearEvent. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_cut_soft_body( handle: RprSoftBodyHandle, @@ -1167,21 +1230,31 @@ pub unsafe extern "C" fn rpr_cut_soft_body( } /// Parameters for the native volumetric mesher. Enclosure: 0 cover, 1 crust (3D). +/// @ingroup shapes #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprVolumeMeshParameters { + /// Positive target cell size for volume meshing. pub cell_size: RprReal, #[cfg(feature = "dim2")] + /// Minimum target element angle in radians; above pi/6, refinement may stop early. pub min_angle: RprReal, #[cfg(feature = "dim3")] + /// Enclosure strategy: 0 covers the whole shape, 1 encloses only its surface. pub enclosure: u32, #[cfg(feature = "dim3")] + /// Number of cover-smoothing passes. pub cover_smoothing: u32, #[cfg(feature = "dim3")] + /// Minimum smoothed-cover distance from the shape, as a fraction of local cell size. pub cover_guard: RprReal, #[cfg(feature = "dim3")] + /// Maximum boundary-cell halvings below cell_size before cover smoothing. pub cover_subdivisions: u32, } +/// Return volume-meshing settings for the supplied cell size; this POD value requires no +/// destructor. +/// @ingroup worlds #[rapier_export] pub extern "C" fn rpr_new_volume_mesh_parameters(cell_size: RprReal) -> RprVolumeMeshParameters { let p = rapier::parry::transformation::VolumeMeshParameters::new(cell_size); diff --git a/c/src/soft_desc.rs b/c/src/soft_desc.rs index eb3c8cad9..242362f2c 100644 --- a/c/src/soft_desc.rs +++ b/c/src/soft_desc.rs @@ -1,26 +1,54 @@ //! Soft-body recipes and borrowed topology. All arrays are copied during insertion. #![allow(non_snake_case)] use crate::*; +/// @ingroup soft_bodies +/// Soft-body selector: desc particles. pub const RPR_SOFT_DESC_PARTICLES: u32 = 0; +/// @ingroup soft_bodies +/// Soft-body selector: desc rope. pub const RPR_SOFT_DESC_ROPE: u32 = 1; +/// @ingroup soft_bodies +/// Soft-body selector: desc grid. pub const RPR_SOFT_DESC_GRID: u32 = 2; +/// @ingroup soft_bodies +/// Soft-body selector: desc cloth. pub const RPR_SOFT_DESC_CLOTH: u32 = 3; +/// @ingroup soft_bodies +/// Soft-body selector: desc cuboid. pub const RPR_SOFT_DESC_CUBOID: u32 = 4; +/// @ingroup soft_bodies +/// Soft-body selector: desc surface. pub const RPR_SOFT_DESC_SURFACE: u32 = 5; +/// @ingroup soft_bodies +/// Soft-body selector: desc disk. pub const RPR_SOFT_DESC_DISK: u32 = 6; +/// @ingroup soft_bodies +/// Soft-body selector: desc sphere. pub const RPR_SOFT_DESC_SPHERE: u32 = 7; +/// @ingroup soft_bodies +/// Soft-body selector: desc cloth tube. pub const RPR_SOFT_DESC_CLOTH_TUBE: u32 = 8; +/// @ingroup soft_bodies +/// Soft-body selector: desc volumetric. pub const RPR_SOFT_DESC_VOLUMETRIC: u32 = 9; +/// Spring-coefficient override for one soft-body edge. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprSoftEdgeSoftness { + /// Zero-based edge index. pub edge: u32, + /// Spring coefficients for constraint correction. pub softness: RprSpringCoefficients, } +/// Tear-resistance override for one soft-body edge. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprSoftEdgeTear { + /// Zero-based edge index. pub edge: u32, + /// Nonnegative edge tear-resistance multiplier. pub resistance: RprReal, } /// Copyable recipe, not an owned procedural builder. Initialize before editing. @@ -28,58 +56,103 @@ pub struct RprSoftEdgeTear { /// for topology arrays are element counts (edges, triangles, or tetrahedra). /// Nonempty topology overrides the generator's topology. Zero counts retain it. /// Generator inputs: a/b are rope ends or center/half-extents; cloth uses a/du/dv. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy)] pub struct RprSoftBodyDesc { + /// Discriminant selecting which description fields are read. pub kind: u32, + /// Recipe origin/center or first rope endpoint; use a recipe constructor. pub a: RprVector, + /// Recipe half extents, second rope endpoint, or tube axis; use a recipe constructor. pub b: RprVector, + /// Cloth basis step along its first parameter axis. pub du: RprVector, + /// Cloth basis step along its second parameter axis. pub dv: RprVector, + /// First recipe resolution; interpretation depends on kind. pub nx: usize, + /// Second recipe resolution; interpretation depends on kind. pub ny: usize, + /// Third recipe resolution; interpretation depends on kind. pub nz: usize, + /// Radius for the selected primitive or recipe. pub radius: RprReal, + /// Radius at the second end of a tapered cloth tube. pub radiusEnd: RprReal, + /// Translation vector. pub translation: RprVector, + /// Optional total mass override for generated particles. pub totalMass: RprOptionalReal, + /// Volume-meshing parameters for a volumetric recipe. pub meshing: RprVolumeMeshParameters, + /// Borrowed initial particle positions. pub positions: RprVectorView, + /// Borrowed per-particle masses; when empty, particleMass is used. pub masses: RprRealView, + /// Borrowed indices of pinned particles. pub pinned: RprIndexView, + /// Borrowed edge topology; count is edges. pub edges: RprEdgeView, + /// Borrowed bending edge constraints. pub bendEdges: RprEdgeView, + /// Borrowed indices of edges that resist tension only. pub tensionOnlyEdges: RprIndexView, + /// Borrowed per-edge softness overrides. pub edgeSoftness: RprSoftEdgeSoftnessView, + /// Borrowed per-edge tear-resistance overrides. pub edgeTearResistance: RprSoftEdgeTearView, + /// Borrowed triangles in 2D or tetrahedra in 3D. pub cells: RprCellView, + /// Borrowed boundary edges in 2D or triangles in 3D. pub surface: RprSurfaceElementView, #[cfg(feature = "dim3")] + /// Borrowed four-vertex bending constraints. pub dihedrals: RprDihedralView, #[cfg(feature = "dim3")] + /// Borrowed wire edges for a surface recipe. pub wire: RprEdgeView, + /// Borrowed skin vertex positions. pub skinVertices: RprVectorView, + /// Borrowed skin topology. pub skinIndices: RprSurfaceElementView, + /// Soft-body material coefficients. pub material: RprSoftBodyMaterial, + /// RPR_SOFT_CELL_VOLUME, RPR_SOFT_CELL_COROTATIONAL, or RPR_SOFT_CELL_NEO_HOOKEAN. pub cellModel: u32, /// 0 = constraints, 1 = FEM (requires a library built with FEM). pub solver: u32, + /// Default nonnegative particle mass. pub particleMass: RprReal, /// Disabled by default: retain the radius computed by the generator. pub particleRadius: RprOptionalReal, + /// Whether to preserve volume. pub volumePreservation: RprBool, + /// Target volume multiplier. pub volumeFactor: RprReal, + /// Optional shape-matching override; disabled retains recipe defaults. pub shapeMatching: RprOptionalBool, + /// Whether self-collision is enabled. pub selfContacts: RprBool, + /// Whether skin elements participate in collision detection. pub skinCollision: RprBool, + /// Whether the generated collision geometry is enabled. pub collisionEnabled: RprBool, + /// Collider configuration used by the recipe; shape comes from the generated geometry. pub collider: RprColliderDesc, + /// Nonnegative linear damping coefficient. pub linearDamping: RprReal, + /// Multiplier applied to world gravity. pub gravityScale: RprReal, + /// Extra solver iterations for this body and connected bodies. pub additionalSolverIterations: usize, + /// Extra PGS iterations for this body. pub additionalPgsIterations: usize, + /// Whether automatic sleeping is allowed. pub canSleep: RprBool, + /// Signed dominance group; larger groups dominate smaller groups. pub dominanceGroup: i8, + /// Application data; Rapier does not own pointers encoded in it. pub userData: RprUserData, } impl Default for RprSoftBodyDesc { @@ -451,11 +524,14 @@ impl RprSoftBodyDesc { ) } } +/// Return native default soft body desc. This POD value owns no resources. +/// @ingroup soft_bodies #[rapier_export] pub extern "C" fn rpr_default_soft_body_desc() -> RprSoftBodyDesc { RprSoftBodyDesc::default() } /// Consumes no caller-owned resources. All borrowed arrays may be released on return. +/// @ingroup soft_bodies #[rapier_export] pub unsafe extern "C" fn rpr_insert_soft_body( world: *mut RprWorld, @@ -481,16 +557,27 @@ pub unsafe extern "C" fn rpr_insert_soft_body( }) } +/// @ingroup soft_bodies +/// Soft-body selector: binding skinned. pub const RPR_SOFT_BINDING_SKINNED: u32 = 0; +/// @ingroup soft_bodies +/// Soft-body selector: binding direct. pub const RPR_SOFT_BINDING_DIRECT: u32 = 1; +/// @ingroup soft_bodies +/// Soft-body selector: binding direct by position. pub const RPR_SOFT_BINDING_DIRECT_BY_POSITION: u32 = 2; /// Non-owning deformable binding description. Direct particle indices are borrowed. +/// @ingroup soft_bodies #[repr(C)] #[derive(Clone, Copy, Default)] pub struct RprSoftMeshBindingDesc { + /// Discriminant selecting which description fields are read. pub kind: u32, + /// Borrowed particle indices used for a direct mesh binding. pub particles: RprIndexView, + /// Nonnegative positional tolerance for direct-by-position binding. pub epsilon: RprReal, + /// Whether self-collision is enabled. pub selfContacts: RprBool, } impl RprSoftMeshBindingDesc { @@ -508,6 +595,8 @@ impl RprSoftMeshBindingDesc { Ok(b.self_contacts(boolean(self.selfContacts)?)) } } +/// Return native default soft mesh binding desc. This POD value owns no resources. +/// @ingroup soft_bodies #[rapier_export] pub extern "C" fn rpr_default_soft_mesh_binding_desc() -> RprSoftMeshBindingDesc { RprSoftMeshBindingDesc { @@ -517,6 +606,9 @@ pub extern "C" fn rpr_default_soft_mesh_binding_desc() -> RprSoftMeshBindingDesc selfContacts: 0, } } +/// Create a deformable collider bound to a soft-body cluster. The world owns the collider; binding +/// arrays are borrowed only during insertion. +/// @ingroup colliders #[rapier_export] pub unsafe extern "C" fn rpr_insert_deformable_collider( collider: *const RprColliderDesc, diff --git a/c/src/soft_recipes.rs b/c/src/soft_recipes.rs index 584bf95e1..5c64519a1 100644 --- a/c/src/soft_recipes.rs +++ b/c/src/soft_recipes.rs @@ -1,6 +1,8 @@ //! POD soft-body recipes and copied previews of procedural geometry. use crate::*; +/// Return a rope recipe with particles evenly spaced from a to b, including both endpoints. /// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +/// @ingroup soft_bodies #[rapier_export] pub extern "C" fn rpr_rope_soft_body_desc( a: RprVector, @@ -15,8 +17,10 @@ pub extern "C" fn rpr_rope_soft_body_desc( ..RprSoftBodyDesc::default() } } -#[cfg(feature = "dim2")] +/// Return a solid rectangle recipe on an nx by ny particle grid. /// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +/// @ingroup soft_bodies +#[cfg(feature = "dim2")] #[rapier_export] pub extern "C" fn rpr_grid_soft_body_desc( center: RprVector, @@ -33,8 +37,10 @@ pub extern "C" fn rpr_grid_soft_body_desc( ..RprSoftBodyDesc::default() } } -#[cfg(feature = "dim3")] +/// Return a solid box recipe on an nx by ny by nz particle grid, subdivided into tetrahedra. /// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +/// @ingroup soft_bodies +#[cfg(feature = "dim3")] #[rapier_export] pub extern "C" fn rpr_cuboid_soft_body_desc( center: RprVector, @@ -53,8 +59,10 @@ pub extern "C" fn rpr_cuboid_soft_body_desc( ..RprSoftBodyDesc::default() } } -#[cfg(feature = "dim3")] +/// Return a cloth recipe with nu by nv particles at origin + i * du + j * dv. /// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +/// @ingroup soft_bodies +#[cfg(feature = "dim3")] #[rapier_export] pub extern "C" fn rpr_cloth_soft_body_desc( origin: RprVector, @@ -73,8 +81,11 @@ pub extern "C" fn rpr_cloth_soft_body_desc( ..RprSoftBodyDesc::default() } } -#[cfg(feature = "dim2")] +/// Return a closed regular polygon recipe with the specified boundary particle count and area +/// preservation. /// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +/// @ingroup soft_bodies +#[cfg(feature = "dim2")] #[rapier_export] pub extern "C" fn rpr_disk_soft_body_desc( center: RprVector, @@ -90,8 +101,10 @@ pub extern "C" fn rpr_disk_soft_body_desc( ..RprSoftBodyDesc::default() } } -#[cfg(feature = "dim3")] +/// Return a hollow icosphere recipe with the specified refinement levels and volume preservation. /// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +/// @ingroup soft_bodies +#[cfg(feature = "dim3")] #[rapier_export] pub extern "C" fn rpr_sphere_soft_body_desc( center: RprVector, @@ -107,8 +120,11 @@ pub extern "C" fn rpr_sphere_soft_body_desc( ..RprSoftBodyDesc::default() } } -#[cfg(feature = "dim3")] +/// Return a cloth tube recipe from origin to origin + axis with num_along rings of num_around +/// particles; radius varies linearly between the ends. /// Initializes a recipe without allocating. Geometry is validated during preview/insertion. +/// @ingroup soft_bodies +#[cfg(feature = "dim3")] #[rapier_export] pub extern "C" fn rpr_cloth_tube_soft_body_desc( origin: RprVector, @@ -130,6 +146,7 @@ pub extern "C" fn rpr_cloth_tube_soft_body_desc( } } /// Initializes a borrowed meshing recipe. Mesh generation happens on preview/insertion. +/// @ingroup soft_bodies #[rapier_export] pub extern "C" fn rpr_volumetric_soft_body_desc( vertices: RprVectorView, @@ -145,6 +162,7 @@ pub extern "C" fn rpr_volumetric_soft_body_desc( } } /// Returns a material with the same softness for each constraint family. +/// @ingroup soft_bodies #[rapier_export] pub extern "C" fn rpr_uniform_soft_body_material( value: RprSpringCoefficients, @@ -157,6 +175,8 @@ pub extern "C" fn rpr_uniform_soft_body_material( material } /// Copies generated particle positions into caller-owned storage; no persistent builder. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_particle_positions( desc: *const RprSoftBodyDesc, @@ -172,6 +192,8 @@ pub unsafe extern "C" fn rpr_soft_body_desc_particle_positions( }) } /// Copies generated cell indices into caller-owned storage. Counts scalar indices. +/// @see @ref output_buffers +/// @ingroup soft_bodies #[rapier_export(soft_body_desc)] pub unsafe extern "C" fn rpr_soft_body_desc_cell_indices( desc: *const RprSoftBodyDesc, diff --git a/c/src/types.rs b/c/src/types.rs index 876da0409..c337f1fcd 100644 --- a/c/src/types.rs +++ b/c/src/types.rs @@ -1,29 +1,61 @@ use crate::*; +/// Floating-point scalar: float for f32, double for f64. +/// @ingroup math #[cfg(feature = "f32")] pub type RprReal = f32; +/// Floating-point scalar: float for f32, double for f64. +/// @ingroup math #[cfg(feature = "f64")] pub type RprReal = f64; /// ABI booleans are uint32_t: zero is false, one is true. +/// @ingroup math pub type RprBool = u32; +/// @ingroup errors +/// C binary ABI revision expected by this header. pub const RPR_ABI_VERSION: u32 = 1; +/// @ingroup rigid_bodies +/// Dynamic body affected by forces and contacts. pub const RPR_DYNAMIC: u32 = 0; +/// @ingroup rigid_bodies +/// Immovable body. pub const RPR_FIXED: u32 = 1; +/// @ingroup rigid_bodies +/// Kinematic body controlled by its next pose. pub const RPR_KINEMATIC_POSITION_BASED: u32 = 2; +/// @ingroup rigid_bodies +/// Kinematic body controlled by its velocity. pub const RPR_KINEMATIC_VELOCITY_BASED: u32 = 3; +/// @ingroup colliders +/// Enable collision-start and collision-stop events for this collider. pub const RPR_COLLISION_EVENTS: u32 = 1; +/// @ingroup colliders +/// Enable contact-force events for this collider, subject to its force threshold. pub const RPR_CONTACT_FORCE_EVENTS: u32 = 2; +/// @ingroup colliders +/// Combine the two material coefficients by their arithmetic mean. pub const RPR_COMBINE_AVERAGE: u32 = 0; +/// @ingroup colliders +/// Use the smaller of the two material coefficients. pub const RPR_COMBINE_MIN: u32 = 1; +/// @ingroup colliders +/// Multiply the two material coefficients. pub const RPR_COMBINE_MULTIPLY: u32 = 2; +/// @ingroup colliders +/// Use the larger of the two material coefficients. pub const RPR_COMBINE_MAX: u32 = 3; +/// Cartesian vector with two or three components. +/// @ingroup math #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprVector { + /// X component. pub x: RprReal, + /// Y component. pub y: RprReal, #[cfg(feature = "dim3")] + /// Z component. pub z: RprReal, } impl RprVector { @@ -51,8 +83,12 @@ impl From for RprVector { } } } +/// Angular scalar in 2D or vector in 3D, in radians for angular displacement. +/// @ingroup math #[cfg(feature = "dim2")] pub type RprAngVector = RprReal; +/// Angular scalar in 2D or vector in 3D, in radians for angular displacement. +/// @ingroup math #[cfg(feature = "dim3")] pub type RprAngVector = RprVector; pub(crate) fn angular(v: RprAngVector) -> Result { @@ -66,18 +102,24 @@ pub(crate) fn angular(v: RprAngVector) -> Result { } } /// 2D: angle in radians. 3D: unit quaternion in x,y,z,w order (normalized on input). +/// @ingroup math #[repr(C)] #[derive(Copy, Clone)] pub struct RprRotation { #[cfg(feature = "dim2")] + /// Rotation angle in radians. pub angle: RprReal, #[cfg(feature = "dim3")] + /// X component. pub x: RprReal, #[cfg(feature = "dim3")] + /// Y component. pub y: RprReal, #[cfg(feature = "dim3")] + /// Z component. pub z: RprReal, #[cfg(feature = "dim3")] + /// Quaternion scalar component. pub w: RprReal, } impl Default for RprRotation { @@ -123,10 +165,15 @@ impl From for RprRotation { } } } +/// Rigid transform combining translation and rotation. Use TranslationPose with a zero vector for +/// identity. +/// @ingroup math #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprPose { + /// Translation vector. pub translation: RprVector, + /// Rotation value. pub rotation: RprRotation, } impl RprPose { @@ -145,11 +192,16 @@ impl From for RprPose { } } } +/// Collision membership/filter masks and their pairwise combination rule. +/// @ingroup math #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprInteractionGroups { + /// Groups this object belongs to, as a 32-bit mask. pub memberships: u32, + /// Membership groups accepted by this object, as a 32-bit mask. pub filter: u32, + /// 0 requires both membership/filter tests; 1 accepts either test. pub test_mode: u32, } impl RprInteractionGroups { @@ -175,10 +227,14 @@ impl From for RprInteractionGroups { } } } +/// Application-defined 128-bit value. Contains no owned pointers. +/// @ingroup math #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprUserData { + /// Low 64 bits of the application value. pub low: u64, + /// High 64 bits of the application value. pub high: u64, } impl RprUserData { @@ -194,16 +250,24 @@ impl From for RprUserData { } } } +/// Axis-aligned bounding box. +/// @ingroup math #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprAabb { + /// Minimum corner in each coordinate. pub mins: RprVector, + /// Maximum corner in each coordinate. pub maxs: RprVector, } +/// Spring softness expressed as natural frequency and damping ratio. +/// @ingroup math #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprSpringCoefficients { + /// Nonnegative spring natural frequency in Hz. pub natural_frequency: RprReal, + /// Nonnegative damping ratio; 1 is critical damping. pub damping_ratio: RprReal, } impl RprSpringCoefficients { @@ -222,14 +286,22 @@ impl From> for RprSpringCoefficients { } } } +/// Runtime ABI identity and fundamental type sizes. +/// @ingroup errors #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprBuildInfo { + /// Binary ABI revision; compare with the header ABI_VERSION. pub abi_version: u32, + /// Spatial dimension, 2 or 3. pub dimension: u32, + /// Size of Real in bytes, 4 or 8. pub real_size: u32, + /// Pointer size in bytes. pub pointer_size: u32, } +/// Return ABI version, dimension, scalar size, and pointer size of the linked library. +/// @ingroup errors #[rapier_export] pub extern "C" fn rpr_build_info() -> RprBuildInfo { RprBuildInfo { @@ -243,6 +315,7 @@ pub extern "C" fn rpr_build_info() -> RprBuildInfo { /// The suffix identifies the C bindings revision for the Rust crate version. /// The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. /// This release identifier is independent of the ABI compatibility version. +/// @ingroup errors #[rapier_export] pub extern "C" fn rpr_version() -> *const std::ffi::c_char { static VERSION: &[u8] = concat!(env!("RAPIER_C_VERSION"), "\0").as_bytes(); @@ -253,6 +326,7 @@ pub extern "C" fn rpr_version() -> *const std::ffi::c_char { /// Custom profiles report the corresponding inherited Cargo profile category. /// The UTF-8, NUL-terminated string is borrowed for the library's lifetime; do not free it. /// This is independent of the consumer's build mode and of per-package optimization overrides. +/// @ingroup errors #[rapier_export] pub extern "C" fn rpr_build_profile() -> *const std::ffi::c_char { static PROFILE: &[u8] = concat!(env!("RAPIER_CARGO_PROFILE"), "\0").as_bytes(); @@ -260,6 +334,7 @@ pub extern "C" fn rpr_build_profile() -> *const std::ffi::c_char { } /// Features available through the loaded C library, independent of consumer defines. +/// @ingroup errors #[repr(C)] #[derive(Copy, Clone, Default)] pub struct RprBuildFeatures { @@ -270,6 +345,8 @@ pub struct RprBuildFeatures { /// Whether this library exposes Rapier's parallel execution and thread-pool APIs. pub parallel: RprBool, } +/// Return profiling, SIMD width, and parallelism of the linked library. +/// @ingroup errors #[rapier_export] pub extern "C" fn rpr_build_features() -> RprBuildFeatures { RprBuildFeatures { @@ -301,12 +378,15 @@ pub(crate) fn combine(value: u32) -> Result { /// Copyable non-owning handle: world pointer plus entity index and generation. /// The world must remain alive throughout every use. Copying does not retain it. /// UINT32_MAX/UINT32_MAX with a NULL world is invalid. +/// @ingroup rigid_bodies #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct RprRigidBodyHandle { /// Borrowed owning world. Never use this handle after freeing that world. pub world: *mut RprWorld, + /// Slot index; UINT32_MAX denotes the explicit invalid handle. pub index: u32, + /// Slot generation used to reject stale handles. Do not modify it. pub generation: u32, } impl Default for RprRigidBodyHandle { @@ -336,12 +416,15 @@ impl From for RprRigidBodyHandle { /// Copyable non-owning handle: world pointer plus entity index and generation. /// The world must remain alive throughout every use. Copying does not retain it. /// UINT32_MAX/UINT32_MAX with a NULL world is invalid. +/// @ingroup colliders #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct RprColliderHandle { /// Borrowed owning world. Never use this handle after freeing that world. pub world: *mut RprWorld, + /// Slot index; UINT32_MAX denotes the explicit invalid handle. pub index: u32, + /// Slot generation used to reject stale handles. Do not modify it. pub generation: u32, } impl Default for RprColliderHandle { @@ -371,12 +454,15 @@ impl From for RprColliderHandle { /// Copyable non-owning handle: world pointer plus entity index and generation. /// The world must remain alive throughout every use. Copying does not retain it. /// UINT32_MAX/UINT32_MAX with a NULL world is invalid. +/// @ingroup joints #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct RprImpulseJointHandle { /// Borrowed owning world. Never use this handle after freeing that world. pub world: *mut RprWorld, + /// Zero-based element index. pub index: u32, + /// Slot generation used to reject stale handles. Do not modify it. pub generation: u32, } impl Default for RprImpulseJointHandle { @@ -406,12 +492,15 @@ impl From for RprImpulseJointHandle { /// Copyable non-owning handle: world pointer plus entity index and generation. /// The world must remain alive throughout every use. Copying does not retain it. /// UINT32_MAX/UINT32_MAX with a NULL world is invalid. +/// @ingroup joints #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct RprMultibodyJointHandle { /// Borrowed owning world. Never use this handle after freeing that world. pub world: *mut RprWorld, + /// Zero-based element index. pub index: u32, + /// Slot generation used to reject stale handles. Do not modify it. pub generation: u32, } impl Default for RprMultibodyJointHandle { @@ -441,12 +530,15 @@ impl From for RprMultibodyJointHandle { /// Copyable non-owning handle: world pointer plus entity index and generation. /// The world must remain alive throughout every use. Copying does not retain it. /// UINT32_MAX/UINT32_MAX with a NULL world is invalid. +/// @ingroup soft_bodies #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct RprSoftBodyHandle { /// Borrowed owning world. Never use this handle after freeing that world. pub world: *mut RprWorld, + /// Zero-based element index. pub index: u32, + /// Slot generation used to reject stale handles. Do not modify it. pub generation: u32, } impl Default for RprSoftBodyHandle { @@ -474,67 +566,169 @@ impl From for RprSoftBodyHandle { } } +/// @ingroup colliders +/// Invoke the contact-pair filtering hook for this collider. pub const RPR_FILTER_CONTACT_PAIRS: u32 = 1; +/// @ingroup colliders +/// Invoke the sensor-intersection filtering hook for this collider. pub const RPR_FILTER_INTERSECTION_PAIR: u32 = 2; +/// @ingroup colliders +/// Invoke the solver-contact modification hook for this collider. pub const RPR_MODIFY_SOLVER_CONTACTS: u32 = 4; +/// @ingroup queries +/// Query filter: exclude fixed. pub const RPR_QUERY_EXCLUDE_FIXED: u32 = 1; +/// @ingroup queries +/// Query filter: exclude kinematic. pub const RPR_QUERY_EXCLUDE_KINEMATIC: u32 = 2; +/// @ingroup queries +/// Query filter: exclude dynamic. pub const RPR_QUERY_EXCLUDE_DYNAMIC: u32 = 4; +/// @ingroup queries +/// Query filter: exclude sensors. pub const RPR_QUERY_EXCLUDE_SENSORS: u32 = 8; +/// @ingroup queries +/// Query filter: exclude solids. pub const RPR_QUERY_EXCLUDE_SOLIDS: u32 = 16; +/// @ingroup queries +/// Query filter: only dynamic. pub const RPR_QUERY_ONLY_DYNAMIC: u32 = 3; +/// @ingroup queries +/// Query filter: only kinematic. pub const RPR_QUERY_ONLY_KINEMATIC: u32 = 5; +/// @ingroup queries +/// Query filter: only fixed. pub const RPR_QUERY_ONLY_FIXED: u32 = 6; +/// @ingroup events +/// Debug-render flag: collider shapes. pub const RPR_DEBUG_COLLIDER_SHAPES: u32 = 1; +/// @ingroup events +/// Debug-render flag: rigid body axes. pub const RPR_DEBUG_RIGID_BODY_AXES: u32 = 2; +/// @ingroup events +/// Debug-render flag: multibody joints. pub const RPR_DEBUG_MULTIBODY_JOINTS: u32 = 4; +/// @ingroup events +/// Debug-render flag: impulse joints. pub const RPR_DEBUG_IMPULSE_JOINTS: u32 = 8; +/// @ingroup events +/// Debug-render flag: solver contacts. pub const RPR_DEBUG_SOLVER_CONTACTS: u32 = 16; +/// @ingroup events +/// Debug-render flag: contacts. pub const RPR_DEBUG_CONTACTS: u32 = 32; +/// @ingroup events +/// Debug-render flag: collider aabbs. pub const RPR_DEBUG_COLLIDER_AABBS: u32 = 64; +/// @ingroup events +/// Debug-render flag: soft bodies. pub const RPR_DEBUG_SOFT_BODIES: u32 = 128; +/// @ingroup events +/// Debug-render flag: pseudo normals. pub const RPR_DEBUG_PSEUDO_NORMALS: u32 = 256; +/// @ingroup events +/// Debug-render flag: soft volume contacts. pub const RPR_DEBUG_SOFT_VOLUME_CONTACTS: u32 = 512; +/// @ingroup events +/// Debug-render flag: soft body stress. pub const RPR_DEBUG_SOFT_BODY_STRESS: u32 = 1024; +/// @ingroup colliders +/// Require both membership/filter intersections to be nonempty. pub const RPR_GROUPS_AND: u32 = 0; +/// @ingroup colliders +/// Accept either membership/filter intersection if both participants select OR; otherwise use AND. pub const RPR_GROUPS_OR: u32 = 1; +/// @ingroup joints +/// Motor stiffness and damping are acceleration-based, independent of mass. pub const RPR_MOTOR_ACCELERATION_BASED: u32 = 0; +/// @ingroup joints +/// Motor stiffness and damping are force-based, so response depends on mass. pub const RPR_MOTOR_FORCE_BASED: u32 = 1; +/// @ingroup soft_bodies +/// Cell model that constrains volume without elastic shear response. pub const RPR_SOFT_CELL_VOLUME: u32 = 0; +/// @ingroup soft_bodies +/// Corotational elastic cell model. pub const RPR_SOFT_CELL_COROTATIONAL: u32 = 1; +/// @ingroup soft_bodies +/// Neo-Hookean elastic cell model. pub const RPR_SOFT_CELL_NEO_HOOKEAN: u32 = 2; +/// @ingroup soft_bodies +/// Use the constraint-based soft-body solver. pub const RPR_SOFT_SOLVER_CONSTRAINTS: u32 = 0; +/// @ingroup soft_bodies +/// Use the finite-element solver; requires RAPIER_FEM. pub const RPR_SOFT_SOLVER_FEM: u32 = 1; +/// @ingroup joints +/// Joint axis index for translation along local X. pub const RPR_AXIS_LIN_X: u32 = 0; +/// @ingroup joints +/// Joint axis index for translation along local Y. pub const RPR_AXIS_LIN_Y: u32 = 1; +/// @ingroup rigid_bodies +/// Rigid-body lock bit: translation x. pub const RPR_LOCK_TRANSLATION_X: u32 = 1; +/// @ingroup rigid_bodies +/// Rigid-body lock bit: translation y. pub const RPR_LOCK_TRANSLATION_Y: u32 = 2; +/// @ingroup rigid_bodies +/// Rigid-body lock bit: translation z. pub const RPR_LOCK_TRANSLATION_Z: u32 = 4; +/// @ingroup rigid_bodies +/// Rigid-body lock bit: rotation x. pub const RPR_LOCK_ROTATION_X: u32 = 8; +/// @ingroup rigid_bodies +/// Rigid-body lock bit: rotation y. pub const RPR_LOCK_ROTATION_Y: u32 = 16; +/// @ingroup rigid_bodies +/// Rigid-body lock bit: rotation z. pub const RPR_LOCK_ROTATION_Z: u32 = 32; +/// @ingroup joints +/// Joint axis index for the first rotation: Z in 2D, local X in 3D. #[cfg(feature = "dim2")] pub const RPR_AXIS_ANG_X: u32 = 2; +/// @ingroup joints +/// Locked-axis mask for a fixed joint: all translations and rotations. #[cfg(feature = "dim2")] pub const RPR_JOINT_FIXED_AXES: u32 = 7; +/// @ingroup joints +/// Locked-axis mask for a revolute joint: only the first angular axis is free. #[cfg(feature = "dim2")] pub const RPR_JOINT_REVOLUTE_AXES: u32 = 3; +/// @ingroup joints +/// Locked-axis mask for a prismatic joint: only translation along local X is free. #[cfg(feature = "dim2")] pub const RPR_JOINT_PRISMATIC_AXES: u32 = 6; +/// @ingroup joints +/// Joint axis index for translation along local Z. #[cfg(feature = "dim3")] pub const RPR_AXIS_LIN_Z: u32 = 2; +/// @ingroup joints +/// Joint axis index for the first rotation: Z in 2D, local X in 3D. #[cfg(feature = "dim3")] pub const RPR_AXIS_ANG_X: u32 = 3; +/// @ingroup joints +/// Joint axis index for rotation around local Y. #[cfg(feature = "dim3")] pub const RPR_AXIS_ANG_Y: u32 = 4; +/// @ingroup joints +/// Joint axis index for rotation around local Z. #[cfg(feature = "dim3")] pub const RPR_AXIS_ANG_Z: u32 = 5; +/// @ingroup joints +/// Locked-axis mask for a fixed joint: all translations and rotations. #[cfg(feature = "dim3")] pub const RPR_JOINT_FIXED_AXES: u32 = 63; +/// @ingroup joints +/// Locked-axis mask for a revolute joint: only the first angular axis is free. #[cfg(feature = "dim3")] pub const RPR_JOINT_REVOLUTE_AXES: u32 = 55; +/// @ingroup joints +/// Locked-axis mask for a prismatic joint: only translation along local X is free. #[cfg(feature = "dim3")] pub const RPR_JOINT_PRISMATIC_AXES: u32 = 62; +/// @ingroup joints +/// Locked-axis mask for a spherical joint: translations locked, rotations free. #[cfg(feature = "dim3")] pub const RPR_JOINT_SPHERICAL_AXES: u32 = 7; @@ -559,28 +753,65 @@ pub(crate) fn real_pi() -> Real { } } -/// Read-only body type used by soft-body cluster proxies; not valid for builder_new. +/// @ingroup soft_bodies +/// Read-only body type of soft-body cluster proxies; cannot be used to construct a rigid body. pub const RPR_SOFT_FRAME: u32 = 4; +/// @ingroup queries +/// Shape-cast iteration limit reached before convergence. pub const RPR_SHAPE_CAST_OUT_OF_ITERATIONS: u32 = 0; +/// @ingroup queries +/// Shape cast converged to the reported impact. pub const RPR_SHAPE_CAST_CONVERGED: u32 = 1; +/// @ingroup queries +/// Shape-cast numerical solver failed to converge. pub const RPR_SHAPE_CAST_FAILED: u32 = 2; +/// @ingroup queries +/// Shapes overlap or are within the target distance at the start of the cast. pub const RPR_SHAPE_CAST_PENETRATING: u32 = 3; +/// @ingroup queries +/// The hit feature is unspecified. pub const RPR_FEATURE_UNKNOWN: u32 = 0; +/// @ingroup queries +/// The feature ID denotes a vertex. pub const RPR_FEATURE_VERTEX: u32 = 1; +/// @ingroup queries +/// The feature ID denotes an edge. pub const RPR_FEATURE_EDGE: u32 = 2; +/// @ingroup queries +/// The feature ID denotes a face. pub const RPR_FEATURE_FACE: u32 = 3; -/// Native URDF/MJCF multibody insertion flags. +/// @ingroup joints +/// Insert reduced-coordinate joints as kinematic articulations. pub const RPR_MULTIBODY_JOINTS_ARE_KINEMATIC: u8 = 1; +/// @ingroup joints +/// Disable contacts between colliders of the inserted articulation. pub const RPR_MULTIBODY_DISABLE_SELF_CONTACTS: u8 = 2; +/// @ingroup joints +/// Skip joints that would close a loop in the articulation. pub const RPR_MULTIBODY_SKIP_LOOP_CLOSURES: u8 = 4; +/// @ingroup joints +/// Do not import joint motors into the articulation. pub const RPR_MULTIBODY_SKIP_JOINT_MOTORS: u8 = 8; +/// @ingroup joints +/// Do not import joint limits into the articulation. pub const RPR_MULTIBODY_SKIP_JOINT_LIMITS: u8 = 16; +/// @ingroup joints +/// Do not import joint springs into the articulation. pub const RPR_MULTIBODY_SKIP_JOINT_SPRINGS: u8 = 32; -/// Parry triangle-mesh and heightfield flags used by the public shape constructors. +/// @ingroup shapes +/// Merge triangle-mesh vertices with identical positions. pub const RPR_TRIMESH_MERGE_DUPLICATE_VERTICES: u32 = 16; +/// @ingroup shapes +/// Correct contact normals at internal mesh edges; includes duplicate-vertex merging. pub const RPR_TRIMESH_FIX_INTERNAL_EDGES: u32 = 144; +/// @ingroup shapes +/// Prepare triangle-mesh acceleration data for deformation. pub const RPR_TRIMESH_DEFORMABLE: u32 = 256; +/// @ingroup shapes +/// Correct internal-edge contacts on both sides of the triangle mesh. pub const RPR_TRIMESH_FIX_INTERNAL_EDGES_TWO_SIDED: u32 = 656; +/// @ingroup shapes +/// Correct contact normals at internal heightfield edges. pub const RPR_HEIGHTFIELD_FIX_INTERNAL_EDGES: u32 = 1; diff --git a/c/src/world.rs b/c/src/world.rs index a427face2..e029c3821 100644 --- a/c/src/world.rs +++ b/c/src/world.rs @@ -9,6 +9,7 @@ const WRITER: usize = usize::MAX; /// Sole owner of simulation state. Handles belong to the world that created them. /// Ordinary reads may overlap. A mutation or step requires exclusive access. /// Destruction must be externally synchronized with all users of this pointer. +/// @ingroup worlds pub struct RprWorld { access: AtomicUsize, data: UnsafeCell, @@ -64,6 +65,7 @@ impl Drop for WorldWrite<'_> { } } /// Create an owned world. Release it with FreeWorld. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_new_world() -> *mut RprWorld { ffi_value(|out: *mut *mut RprWorld| { @@ -80,6 +82,7 @@ pub unsafe extern "C" fn rpr_new_world() -> *mut RprWorld { } /// Free a world. NULL is allowed. Rejects destruction from an active callback. /// The caller must prevent other threads from starting calls during destruction. +/// @ingroup worlds #[rapier_export] pub unsafe extern "C" fn rpr_free_world(world: *mut RprWorld) -> RprStatus { ffi(|| unsafe { @@ -96,6 +99,7 @@ pub unsafe extern "C" fn rpr_free_world(world: *mut RprWorld) -> RprStatus { /// Callback-scoped read access to bodies and colliders. Never retain or free it. /// Only the Read* functions accept this context; it cannot mutate the world. +/// @ingroup callbacks pub struct RprReadContext { pub(crate) world: *mut RprWorld, pub(crate) bodies: *const RprRigidBodySet, diff --git a/c/src/world_queries.rs b/c/src/world_queries.rs index d8412ce61..896d8d7ef 100644 --- a/c/src/world_queries.rs +++ b/c/src/world_queries.rs @@ -14,11 +14,15 @@ pub(crate) struct QueryAccess { pub userData: *mut std::ffi::c_void, } /// Copyable query settings. They borrow callback data, never world components. +/// @ingroup queries #[repr(C)] #[derive(Clone, Copy)] pub struct RprQueryOptions { + /// Query selection settings. pub filter: RprQueryFilter, + /// Optional additional query filter; nonzero accepts a collider. pub predicate: RprQueryPredicate, + /// Application data; Rapier does not own pointers encoded in it. pub userData: *mut std::ffi::c_void, } impl Default for RprQueryOptions { @@ -30,6 +34,8 @@ impl Default for RprQueryOptions { } } } +/// Return native default query options. This POD value owns no resources. +/// @ingroup queries #[rapier_export] pub extern "C" fn rpr_default_query_options() -> RprQueryOptions { RprQueryOptions::default() @@ -93,6 +99,12 @@ impl QueryAccess { } } +/// Return the closest ray hit, or report RPR_NOT_FOUND on a miss. The ray is origin + direction * t +/// for 0 <= t <= max_toi; direction need not be normalized. solid treats an interior origin as a +/// hit at t = 0. +/// NULL query options use the default filter. Query state reflects the latest Step or +/// DetectCollisions call. +/// @ingroup queries #[rapier_export] pub unsafe extern "C" fn rpr_cast_ray( world: *const RprWorld, @@ -137,6 +149,11 @@ pub unsafe extern "C" fn rpr_cast_ray( }) } +/// Return the closest surface projection within max_distance, or report RPR_NOT_FOUND. With solid = +/// 1, an interior point projects to itself. +/// NULL query options use the default filter. Query state reflects the latest Step or +/// DetectCollisions call. +/// @ingroup queries #[rapier_export] pub unsafe extern "C" fn rpr_project_point( world: *const RprWorld, @@ -175,6 +192,11 @@ pub unsafe extern "C" fn rpr_project_point( }) } +/// Sweep shape from pose along velocity and return the first hit; report RPR_NOT_FOUND on a miss. +/// Time is bounded by options.max_time_of_impact. +/// NULL query options use the default filter. Query state reflects the latest Step or +/// DetectCollisions call. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_cast_shape( world: *const RprWorld, @@ -218,6 +240,11 @@ pub unsafe extern "C" fn rpr_cast_shape( }) } +/// Copy handles of colliders containing the world-space point. +/// @see @ref output_buffers +/// NULL query options use the default filter. Query state reflects the latest Step or +/// DetectCollisions call. +/// @ingroup queries #[rapier_export] pub unsafe extern "C" fn rpr_intersect_point( world: *const RprWorld, @@ -247,6 +274,12 @@ pub unsafe extern "C" fn rpr_intersect_point( } } +/// Copy handles of colliders intersecting the shape at its world-space pose. The shape is borrowed +/// for this call. +/// @see @ref output_buffers +/// NULL query options use the default filter. Query state reflects the latest Step or +/// DetectCollisions call. +/// @ingroup shapes #[rapier_export] pub unsafe extern "C" fn rpr_intersect_shape( world: *const RprWorld, @@ -280,6 +313,12 @@ pub unsafe extern "C" fn rpr_intersect_shape( } } +/// Copy broad-phase candidates whose bounding boxes overlap the world-space AABB. Results may +/// include false positives. +/// @see @ref output_buffers +/// NULL query options use the default filter. Query state reflects the latest Step or +/// DetectCollisions call. +/// @ingroup queries #[rapier_export] pub unsafe extern "C" fn rpr_intersect_aabb_conservative( world: *const RprWorld, @@ -314,6 +353,11 @@ pub unsafe extern "C" fn rpr_intersect_aabb_conservative( } } +/// Return the closest ray collider and time, with found = 0 on a miss (RPR_OK). The ray is origin + +/// direction * t; max_toi bounds t. +/// NULL query options use the default filter. Query state reflects the latest Step or +/// DetectCollisions call. +/// @ingroup queries #[rapier_export] pub unsafe extern "C" fn rpr_cast_ray_toi( world: *const RprWorld, @@ -361,6 +405,11 @@ pub unsafe extern "C" fn rpr_cast_ray_toi( }) } +/// Return the closest ray hit with found = 0 on a miss (RPR_OK). The ray is origin + direction * t; +/// solid treats an interior origin as a hit at t = 0. +/// NULL query options use the default filter. Query state reflects the latest Step or +/// DetectCollisions call. +/// @ingroup queries #[rapier_export] pub unsafe extern "C" fn rpr_try_cast_ray( world: *const RprWorld, diff --git a/c/tools/generate-header.py b/c/tools/generate-header.py index 1fd35d5e0..48c20b1fa 100644 --- a/c/tools/generate-header.py +++ b/c/tools/generate-header.py @@ -54,7 +54,7 @@ # cbindgen emits this mutually recursive pair in pointer-dependency order. # C requires the by-value ShapeDesc field to be complete first. - compound = re.search(r"typedef struct RprCompoundShapeDesc \{.*?\} RprCompoundShapeDesc;\n", text, re.S) + compound = re.search(r"(?:/\*\*(?:(?!\*/).)*\*/\s*)?typedef struct RprCompoundShapeDesc \{.*?\} RprCompoundShapeDesc;\n", text, re.S) if compound: declaration = compound[0] text = text[:compound.start()] + text[compound.end():] From 29e38f79a4116e0158609e893e11934edec86e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 24 Sep 2026 16:36:38 +0200 Subject: [PATCH 5/8] chore: simplify C bindings readme --- c/README.md | 634 +++++--------------------------------------- c/testbed/README.md | 2 +- 2 files changed, 62 insertions(+), 574 deletions(-) diff --git a/c/README.md b/c/README.md index f2be1f1e6..8efdd7140 100644 --- a/c/README.md +++ b/c/README.md @@ -1,614 +1,102 @@ # Rapier C bindings -C11 ABI for Rapier, with optional C++17 ownership helpers. A world owns all -simulation components. Live bodies, colliders, joints, and soft bodies are accessed -through generational handles that contain their owning world pointer. Construction descriptions and query -options are plain C values; no builders or component pointers need cleanup. +C11 bindings for Rapier, with optional C++17 helpers, 2D/3D support, and single or +double precision. -This implementation targets Rapier’s `master` branch. ABI version **1** uses -explicit operation names, collection counts, and `TimeStep` accessors. Entity -handles embed a borrowed world pointer, avoiding redundant world parameters. Newly -produced values return directly. A single world owns the simulation; -callback-scoped read access and runtime borrow checks protect access to it. -There are no compatibility aliases. Rebuild consumers using the matching header, -library, dimension, precision, and feature configuration. +You need Rust/Cargo, a C/C++ compiler, and CMake 3.25+. Run the commands below from +the repository root. -See [API reference](#api-reference) for searchable Doxygen documentation and build instructions. - -## C names - -Functions use a dimension prefix and camelCase: `r2NewWorld` in 2D and -`r3NewWorld` in 3D. Instance methods separate the receiver type and method with -an underscore: `r3RigidBody_Position`, `r3Collider_SetFriction`, and `r3JointDesc_SetMotorPosition`. -Creation and destruction are lifecycle exceptions: `r3NewWorld()` and -`r3FreeWorld(world)`. Named constructors put the qualifier before the type, such as -`r3DynamicRigidBodyDesc()`, `r3CuboidColliderDesc(halfExtents)`, and -`r3DefaultQueryOptions()`. Loading and conversion constructors retain their -`TypeFromSource` spelling, for example `r3UrdfRobotFromFile(path, &options)`. -Static math helpers and whole-world operations omit the receiver separator, -for example `r3VectorAdd`, `r3Step`, and `r3InsertRigidBody`. Documentation below -uses 3D names unless noted; -2D uses the same suffix with `r2`. Types use PascalCase with the corresponding -`R2` or `R3` prefix, for example `R2Vector` and `R3ColliderDesc`. Constants use -`R2_` or `R3_`, for example `R2_OK` and `R3_DYNAMIC`. There are no legacy type -or constant aliases. New construction/configuration POD fields -use camelCase. Existing ABI fields retain their original Rust spelling. - -Property setters include `Set`, for example `r3RigidBody_SetTranslation` and -`r3Collider_SetFriction`. Entity operations take a handle that identifies its world. Whole-world operations -and standalone insertion still take an explicit world. Operations combining entities -reject handles from different worlds. `World` appears only in lifecycle operations: -`r3NewWorld`, `r3FreeWorld`. Simulation uses `r3Step`, queries use `r3CastRay`, and -insertion uses `r3InsertRigidBody` followed by `r3InsertCollider` to attach a collider. Descriptions expose fields directly and use setters for compound operations, -such as `JointDesc_SetMotorPosition` and `ShapeDesc_SetTrimesh`. - -Dimension-specific examples call the concrete functions directly. Shared C/C++ -code compiled for either dimension can use `RAPIER_FN(NewWorld)`, -`RAPIER_FN(RigidBody_SetTranslation)`, -`RAPIER_TYPE(World)`, and `RAPIER_CONST(OK)`. These select the corresponding -function, type, and constant using `RAPIER_DIM2`/`RAPIER_DIM3`, with no wrapper -function or extra call. Dimension-neutral import and calling-convention macros -are named `RAPIER_API` and `RAPIER_CALL`. Inline math functions use -the same convention, for example `r2Vector`, `r3Vector`, and `r3TranslationPose`. - -Rust implementations use snake_case identifiers. The -`#[rapier_export]` attribute in `rapier-c-macros` chooses the exported C symbol. -Instance methods specify the receiver, for example `#[rapier_export(rigid_body)]`; -constructors, destructors, and static helpers leave the attribute empty. Callback read methods -retain their `Read` prefix, for example `r3ReadRigidBody_Position`. The macro -has no third-party dependencies. The header generator translates the same -function names and receiver annotations, and maps the shared Rust `Rpr`/`RPR_` type and constant names to -each C dimension. This keeps the Rust implementation shared. ABI tests compare -every generated declaration with the binary exports. The dimension-specific type and constant prefixes do not affect binary layouts -or function symbols. - -## Build - -From the repository root: +## Build the library ```sh -cargo build --release -p rapier3d-ffi -# Also available: rapier2d-ffi, rapier3d-f64-ffi, rapier2d-f64-ffi. +cmake -S c -B build/c -DCMAKE_BUILD_TYPE=Release +cmake --build build/c --config Release --parallel ``` -Each crate produces a shared library and static library in `target/release`. -Library names replace hyphens with underscores, e.g. `librapier3d_ffi.so`, -`librapier3d_ffi.dylib`, or `rapier3d_ffi.dll`. On Windows use the MSVC Rust target -with MSVC C/C++ consumers. The dynamic import library is `rapier3d_ffi.dll.lib`; -the static library is `rapier3d_ffi.lib`. - -The checked-in `include/rapier.h` needs no generator when consumed. Define one -of `RAPIER_DIM2` / `RAPIER_DIM3` and one of `RAPIER_F32` / `RAPIER_F64` before -including it. Defaults are 3D and f32. For static linkage define `RAPIER_STATIC`. -2D and 3D can be linked together, with each dimension's header configuration in -separate translation units. Select one scalar precision per dimension: f32 and -f64 of the same dimension share symbol names. Objects must only be passed to -the dimension and precision that created them. - -The bindings are unreleased and use ABI version 1 throughout initial development. -Headers and libraries must come from the same revision; the ABI number does not -distinguish development revisions. After the first release, incompatible releases -will increment the ABI version. +This builds the 3D, single-precision shared library, a small C example, and tests. +Both the Rust library and C/C++ code use release mode. CMake runs Cargo for you; +the library is under `build/c/cargo/release/`. -Before any other call that passes vectors or poses, check the build: +Add these options to the configuration command as needed: -```c -r3CheckAbi(R3_ABI_VERSION, R3_DIMENSION, - sizeof(R3Real), sizeof(R3Vector), sizeof(R3Pose)); -``` - -Check its return status. `r3Version()` returns the loaded C library's release -version, currently `"0.35.3+c.2"` (`r2Version()` in 2D). The returned string is -borrowed and must not be freed. `c/VERSION` is the source of this version for Rust -and CMake builds: keep the Rust crate version as the base and increment `c.N` for -C bindings releases against that version. The build rejects mismatched Rust versions. -This release identifier is separate from ABI version 1. SemVer treats `+c.N` as -build metadata, so it does not establish dependency upgrade ordering. CMake uses -the numeric base for `find_package` comparisons and exposes the full string as -`Rapier_BINDINGS_VERSION` in the installed package. +| Option | Purpose | +| --- | --- | +| `-DRAPIER_DIMENSION=2` | Build 2D instead of 3D. | +| `-DRAPIER_PRECISION=64` | Use double precision instead of single precision. | +| `-DRAPIER_SHARED=OFF` | Link the static library. | +| `-DRAPIER_ENABLE_PARALLEL=ON` | Enable multithreaded physics; on by default for the testbed. | +| `-DRAPIER_SIMD_LANES=8` | Use eight SIMD lanes instead of four; requires single precision and excludes `enhanced-determinism`. | +| `-DRAPIER_FEATURES=fem,robotics` | Enable optional features; robotics requires 3D/single precision. | -`r3BuildInfo` reports dimension, scalar width, pointer -width, and ABI version without using dimension-dependent arguments. -`r3BuildProfile()` returns a borrowed, static string containing the loaded -library's Cargo profile category (`"release"` or `"debug"`). It is independent of the -C/C++ consumer's build mode. Custom Cargo profiles report their inherited category; -per-package optimization overrides do not change that profile name. -`r3BuildFeatures` reports the solver SIMD lane count and whether parallel -execution and profiling are available through the loaded C library. +Use separate build directories for different configurations. For debug builds, +set both `-DRAPIER_PROFILE=debug` and `-DCMAKE_BUILD_TYPE=Debug`, then build with +`--config Debug`. -### CMake and installation +## Install and use from CMake ```sh -cmake -S c -B build/c -DRAPIER_DIMENSION=3 -DRAPIER_PRECISION=32 -cmake --build build/c --config Release -ctest --test-dir build/c -C Release --output-on-failure -cmake --install build/c --prefix /your/sdk/rapier +cmake --install build/c --config Release --prefix /path/to/rapier-sdk ``` -Use `-DRAPIER_SHARED=OFF` for static linkage, `-DRAPIER_PROFILE=debug` for a debug -Cargo build, and `-DRAPIER_FEATURES=fem,enhanced-determinism` for additional Rust -features. Select execution features explicitly: - -- `-DRAPIER_ENABLE_PARALLEL=ON` or `OFF`: enables Rayon and thread-pool control. - Defaults to ON when building the testbed, OFF for bindings alone. -- `-DRAPIER_SIMD_LANES=4` (default) or `8`: this branch always uses SIMD and has no - scalar solver build. Eight lanes require f32 and exclude `enhanced-determinism`. - Hardware instruction width depends on the target CPU; eight lanes do not imply - native eight-lane instructions or better performance on every machine. - -For direct Cargo builds, use `--features parallel` and optionally `simd8` on the -f32 crates. The older `RAPIER_FEATURES=parallel,simd8` spelling initializes the -explicit options on the first CMake configuration; the explicit cache options -control subsequent configurations. - -`r3SetNumThreads(world, count)` sets a dedicated pool per -world: 0 selects Rayon's automatic count, 1 selects one worker. Changes take effect -on the next step and must be made between steps. `r3NumThreads` -reports its actual size (0 means no dedicated pool; 1 in a build without parallel -support). `r3ClearThreadPool` returns to the calling/global Rayon -pool; it does not force serial execution. Configuration APIs return -`R3_UNSUPPORTED` when parallel support is compiled out. Pools are not serialized; -configure them again after restoring a snapshot. - -`r3SetCountersEnabled` enables native profiling, and -`r3StepTimeMs` reports the last engine step using the same -counter as the Rust testbed. This requires `--features profiler` (or -`RAPIER_FEATURES=profiler`); CMake enables it automatically for the testbed. -Counters are disabled by default in newly created worlds. The timer excludes -rendering, callbacks outside the physics step, and dispatch into the dedicated pool. - -`RAPIER_TARGET` accepts a Rust target -triple for cross builds; install that target and configure CMake's matching -compiler/toolchain. Rust's linker must also be configured for the target. Cross -builds are supported by the build configuration, not locally verified for every -target. Disable tests/examples for targets that cannot run on the build host. - -CMake builds Cargo automatically and provides `Rapier::rapier`, with the selected -ABI definitions and native system libraries. It supports `add_subdirectory(c)` -or an installed package: +Configure your application with `-DCMAKE_PREFIX_PATH=/path/to/rapier-sdk`, then link: ```cmake find_package(Rapier CONFIG REQUIRED) -target_link_libraries(your_game PRIVATE Rapier::rapier) +target_link_libraries(your_app PRIVATE Rapier::rapier) ``` -Install each dimension/precision/configuration to a separate prefix. CMake builds -into its own `cargo` directory by default; `RAPIER_CARGO_TARGET_DIR` can reuse an -existing Cargo target directory. On macOS the shared library has an `@rpath` -install name; configure your application's runtime library search path for -redistribution. On Windows deploy the DLL beside the executable or plugin. - -## Construction with caller-owned descriptions - -Prefer caller-owned descriptions for construction. Initialize them using the API, -edit their fields, then insert directly into a world: - -```c -R3RigidBodyDesc body = r3DynamicRigidBodyDesc(); -R3ColliderDesc collider = r3BallColliderDesc(0.5f); -body.position.translation.y = 5.0f; -body.canSleep = 0; -R3RigidBodyHandle handle = r3InsertRigidBody(world, &body); -// Check r3LastStatus() before using handle unless a fail-fast error handler is installed. -R3ColliderHandle colliderHandle = r3InsertCollider(handle, &collider); -// Check r3LastStatus() again. -// No builder, body, or collider temporary needs freeing. -``` +The target supplies the matching headers, compile definitions, and libraries. +Install each dimension/precision configuration to a separate prefix. When shipping +a shared-library build, include the library and configure its runtime search path +(on Windows, place the DLL beside your executable). -Insert the rigid body first, then pass its handle by value to -`r3InsertCollider(body, &desc)`. The function uses the world stored in that handle. -Repeat collider insertion to attach multiple colliders to the same body. -For a collider without a parent, use `r3InsertColliderWithoutParent(world, &desc)`. Each call validates its own input; -if collider insertion fails, the rigid body remains in the world and may be -reused or removed with `r3RemoveRigidBody`. - -Description constructors and copying descriptions need no heap allocation. -Constructors return plain values and defer validation to build/insert (or a -soft-geometry preview). They do not report errors or substitute valid defaults -for invalid arguments. You can edit a description before passing it to a -fallible operation. Invalid joint axes produce invalid frames that are -rejected during description validation. Insertion still -allocates the native simulation objects and geometry it needs. - -These are ordinary C values. Assignment copies them; they can live on the stack, -in an engine component, or in a language's blittable struct. Always call the -matching initializer: `{0}` does not produce Rapier defaults (in particular, -rotations, collision groups, and query exclusions need initialization). - -| Value | Initialization and use | -| --- | --- | -| `R3RigidBodyDesc` | `DynamicRigidBodyDesc`, `FixedRigidBodyDesc`, `KinematicPositionBasedRigidBodyDesc`, `KinematicVelocityBasedRigidBodyDesc`; `InsertRigidBody` | -| `R3ColliderDesc` | `DefaultColliderDesc`, `BallColliderDesc`, `CuboidColliderDesc`; world insertion | -| `R3ShapeDesc` | Inline in a collider; primitives, borrowed mesh/heightfield arrays, compound children, or a borrowed shared-shape reference | -| `R3JointDesc` | `DefaultJointDesc` or `FixedJointDesc`, `RevoluteJointDesc`, `PrismaticJointDesc`, `RopeJointDesc`, `SpringJointDesc` (plus dimension-specific joints); world insertion | -| `R3SoftBodyDesc` | `DefaultSoftBodyDesc`; particles, procedural generators, or borrowed surface/volume meshes; world insertion | -| `R3SoftBodyMaterial` | `DefaultSoftBodyMaterial`; inline in a soft recipe, or live `SoftBody_Material` / `SoftBody_SetMaterial` | -| `R3SoftMeshBindingDesc` | `DefaultSoftMeshBindingDesc`; `InsertDeformableCollider` | -| `R3IntegrationParameters` | `DefaultIntegrationParameters` or `IntegrationParameters`; edit then `SetIntegrationParameters` | -| `R3QueryOptions` | `DefaultQueryOptions`; reusable filter/predicate settings passed alongside the world to ray, point, shape, and intersection queries | - -`R3ShapeDesc` and `R3SoftBodyDesc` borrow their array views until the -build/insertion call returns. Insertion copies arrays and retains shared geometry; -you can then release or reuse input buffers. Copying a description alone does -**not** extend the lifetime of its arrays or shared-shape handle. Geometry view counts always count elements: vectors, edges, triangles, or -tetrahedra. `R3ShapeDesc.triangles` and `.edges` replace flattened indices. - -Soft recipes retain generator-specific radius and shape-matching defaults. -Override them explicitly, for example `soft.particleRadius = (R3OptionalReal){1, 0.05f}` -or `soft.shapeMatching = (R3OptionalBool){1, 0}`. Other material options use the -same `{enabled, value}` representation. Nonempty topology arrays override generated -topology. Procedural recipes include ropes, grids, disks, cloth, cloth tubes, -cuboids, spheres, and volumetric meshes. `SoftBodyDesc_ParticlePositions` and -`SoftBodyDesc_CellIndices` copy generated geometry into caller storage for editing; -each preview regenerates the recipe. For counts after insertion, query the soft -body by handle instead of generating it again. - -Query options hold a filter, predicate, and user pointer. They have no fixed world owner -and need no destructor. Filters that exclude specific bodies or colliders must use -handles from the queried world; rebind those after snapshot restoration. Keep predicate data -alive during each query. Pass NULL options to use the defaults. Queries observe the -broad phase from the latest `Step` or `DetectCollisions`; the latter updates -collision detection without advancing time. Some queries allocate internal scratch -buffers. - -`ImpulseJoint_Desc` / `ImpulseJoint_SetDesc` copy and apply joint configuration. They exclude -solver impulses; applying a description resets cached limit and motor impulses. Configuration -apply functions validate the complete value before replacing live settings. - -POD layouts depend on dimension/precision and, for integration settings, FEM. -`PodLayout` reports sizes for language-wrapper checks; use matching headers and -feature definitions. C++ value factories live alongside the RAII owners in -`rapier.hpp`. The C# example uses a blittable body description and only disposes -the world. - -## Typed array views - -Use typed views to keep pointers and counts together and express mesh topology -with named element types. A view's count always means **elements**: two triangles -have a count of two, irrespective of their six vertex indices. - -```c -#include "rapier_helpers.h" - -R3Vector vertices[] = {{0, 0, 0}, {1, 0, 0}, {0, 0, 1}, {1, 0, 1}}; -R3Triangle triangles[] = {{0, 2, 1}, {1, 2, 3}}; -R3ColliderDesc collider = r3DefaultColliderDesc(); -R3VectorView points = {vertices, 4}; -R3TriangleView faces = {triangles, 2}; -R3Status status = r3ShapeDesc_SetTrimesh(&collider.shape, points, faces, 0); -if (status == R3_OK) { - R3ColliderHandle handle = r3InsertColliderWithoutParent(world, &collider); - status = r3LastStatus(); -} -// After insertion returns, the arrays may be freed or leave scope. -``` - -`ShapeDesc_SetPolyline` accepts `R3EdgeView`; `ShapeDesc_SetConvexHull` accepts -`R3VectorView`. Shape setters replace the complete shape description with the -selected geometry and defaults; the surrounding collider configuration is preserved. - -Soft descriptions have setters for particles, surface meshes, edges, bend edges, -cells, surface elements, skin, masses, pinned particles, and tension-only edge -indices; 3D also exposes dihedrals and wire edges. `R3CellView` means triangle -cells in 2D and tetrahedra in 3D. `R3SurfaceElementView` means edges in 2D and -triangles in 3D. `R3RealView` and `R3IndexView` represent scalar arrays. -Soft setters preserve other fields. `SetParticles` and `SetSurfaceMesh` also select -the corresponding recipe kind. Zero topology counts retain generated topology, -just as the underlying description fields do. - -Views and descriptors own no arrays and need no destructor. Setters store pointers -without allocating or copying elements. **Keep the arrays alive and unmodified -until build/insert returns.** Copying a view or description does not extend that -lifetime. Insertion copies the required data into Rapier-owned storage. Setters -reject null nonempty views, misalignment, and unrepresentable lengths without -modifying the description. Element values, topology bounds, and geometry flags -are validated during build/insert; check `LastStatus()` too. As with all C pointer -inputs, the caller must supply valid storage for the declared extent. - -Description geometry fields are typed views too. Shared-shape mesh, compound, and heightfield constructors -also accept views; the former pointer/count overloads have been removed. - -## Value initialization helpers - -Include `rapier_helpers.h` for single-expression initialization in C11 or C++17: - -```c -R3RigidBodyDesc body = r3DynamicRigidBodyDesc(); -R3ColliderDesc collider = r3DefaultColliderDesc(); -R3SoftBodyDesc soft = r3DefaultSoftBodyDesc(); -R3QueryFilter filter = r3DefaultQueryFilter(); -R3RigidBodyHandle handle = R3_INVALID_RIGID_BODY_HANDLE; -``` +## Run the testbed -Body helpers cover dynamic, fixed, and both kinematic types. Other descriptions -and configuration data have `Default...()` helpers; an unconstrained joint uses -`DefaultJointDesc()`. `DefaultShapeCastOptions()` initializes cast options. They -are exported native value-returning functions, usable from C and other language -bindings without compiling an inline shim. The same applies to parameterized -constructors such as `CuboidColliderDesc`, `SpringJointDesc`, and `RopeSoftBodyDesc`. -They require no cleanup; build/insert validates their contents and reports failures through `LastStatus()`. -`r2RevoluteJointDesc()` takes no axis, while `r3RevoluteJointDesc(axis)` does. -The replaced output-pointer constructors are removed. - -Explicit invalid constants are provided for body, collider, impulse-joint, -multibody-joint, and soft-body handles. Use them for local initialization and -assignment; none retains an owner. Check the ABI and match FEM/dimension/precision -configuration as with other calls. The helpers are installed with the SDK and -included by `rapier.hpp`. - -## Handle-based access - -For runtime element access, pass the generational handle; it includes the world pointer. -Each call resolves the element internally; no borrowed element pointer escapes. -For example, `r3RigidBody_SetTranslation(body, position, 1)` and -`position = r3RigidBody_Translation(body)` work across steps and storage growth. -`RigidBodyReadStates` copies an ordered batch into caller-owned storage, validates -all handles before writing, and leaves the buffer untouched on failure. - -Removed or stale handles return `R3_INVALID_HANDLE`, including after slot reuse. -Handles identify their original world but do not retain it. Each contains a `world` -pointer, `index`, and `generation` (16 bytes on 64-bit targets). Handle equality -compares all three fields. Joint creation and other operations involving multiple -entities reject mixed-world handles with `R3_INVALID_HANDLE` before mutation. The invalid -sentinel is `{NULL, UINT32_MAX, UINT32_MAX}`, not a zero-initialized handle. - -The world pointer is process-local and must not be persisted as part of a handle. -Do not fabricate or change it except when deliberately rebinding indices from a matching -snapshot. Stale entity generations are checked while the world is alive; a freed world -cannot be detected safely. Language wrappers must keep their owning world object alive -through every handle-based call (for example, `GC.KeepAlive(world)` for a C# SafeHandle). - -## Loader options - -URDF and MJCF loading uses copyable configuration values. Initialize defaults and -edit fields directly; options own no resources and need no setters or destructor: - -```c -R3UrdfLoaderOptions options = r3DefaultUrdfLoaderOptions(); -options.makeRootsFixed = 1; -options.rigidBodyBlueprint.canSleep = 0; -R3UrdfRobot *robot = r3UrdfRobotFromFile(path, &options); -// Check r3LastStatus(), use robot, then r3FreeUrdfRobot(robot). -``` - -`r3DefaultMjcfLoaderOptions()` works the same way. Blueprints are embedded -`RigidBodyDesc` and `ColliderDesc` values; geometry referenced by a collider -blueprint is borrowed until loading returns. Copying options does not retain -that geometry. Invalid fields fail during loading, before file I/O. -The defaults preserve native behavior, including zero collider density and -dynamic body blueprints. `PodLayout` reports both option sizes when robotics is -enabled, and zero otherwise. Robotics requires 3D f32. - -## Ownership and borrowing - -- Descriptions, configuration data, and `R3QueryOptions` are caller-owned values - and need no destructor. Description arrays must remain valid through insertion; - insertion copies them and retains any shared geometry it needs. -- `Collider_CloneShape`, `ReadCollider_CloneShape`, and `MjcfVisualMesh_CloneShape` - return owned wrappers sharing geometry. Release them with `FreeSharedShape`. - Ordinary value getters such as `SoftBody_Material` and `ImpulseJoint_Desc` - return POD copies that need no destructor. -- Worlds, controllers, shared shapes, mesh assets, event collectors, and snapshots - are owned resources. Use the matching `Free`, never C `free` or C++ `delete`. - `Free(NULL)` succeeds. Each owned pointer must be freed exactly once. -- A world owns its sets, pipelines, and integration settings. There are no public - component pointers, independent component constructors, or component destructors. - Configuration is accessed through world functions or copied POD snapshots. -- Ordinary world reads may overlap, including nested reads from a query predicate. - Mutation and stepping require exclusive access. Conflicting calls report - `R3_WORLD_BUSY` before borrowing native simulation state; they do not block. -- A physics hook receives a borrowed `R3ReadContext`. Use `ReadRigidBody*` and - `ReadCollider*` to inspect the callback-visible state. Ordinary calls on the - stepping world report `R3_WORLD_BUSY`. Contact context setters remain available. - Never retain either context after the callback returns. -- Perform additions, removals, and body changes after the active step/query returns. - The event collector supports this workflow. Applications may also record their - own commands during callbacks and apply them afterward; there is no implicit queue. -- Synchronize world destruction externally: no other thread may start an operation - during or after `FreeWorld`. Every entity handle becomes dangling when its world - is freed; even a `Contains` call is then invalid. Copying handles never retains a world. The access gate rejects freeing from an active - callback, but cannot make a dangling pointer safe. Controllers and other separately - owned objects still require caller synchronization. -- Mutating or removing a soft body's hidden rigid root/proxy through ordinary body - APIs is rejected. Use soft-body and cluster operations instead. -- Output buffers belong to the caller. Passing `(NULL, 0)` returns the element count. - Insufficient capacity reports `R3_BUFFER_TOO_SMALL`, returns the required count, - and leaves the buffer untouched. Counts are elements unless specified as bytes. -- Snapshot bytes belong to `R3Bytes`. Serialization copies state; restoration - creates an independent world with the serialized entity indices and generations. - Enumerate handles from the restored world to obtain its new pointers. If preserving - application references to a matching snapshot, rebind their `world` field explicitly - to the restored world; do not reuse pointers from the source world. Handles returned - by queries, events, callbacks, and controller results already carry their owner. - -C pointers must refer to live, aligned allocations of the documented type and -extent. Output buffers must not alias inputs or one another. Null/alignment checks -cannot establish allocation validity. `rapier.hpp` provides `unique_ptr` aliases -for owned objects and a `check` helper that converts statuses to C++ exceptions. - -## Errors, threads, and callbacks - -Operations that produce values return them directly: pointers for owned resources, -handles for inserted objects, PODs for getters, and counts for buffer fills. Related -outputs are grouped into structs such as `R3OptionalRayHit`, -`R3VelocityCorrection`, and `R3ByteView`. Returned structs need no cleanup, but -owned pointers inside or returned separately retain their documented ownership. -Setters, stepping, and operations without a produced value return `R3Status`. - -Every fallible call records its status and diagnostic on the calling thread. -Read `r3LastStatus()` immediately after the call when recovering from errors; -`R3_OK` means success. Infallible constructors and reads of `LastStatus`/`LastError` -do not change the recorded status. A successful fallible call clears it. - -On failure, value-returning calls return a default value: null owned pointers, -invalid handles, zero scalars/vectors, or the POD type's default configuration. -These are placeholders, not a substitute for checking the status. Array APIs keep -caller-provided buffers and return the count directly. A size query uses a null -buffer and zero capacity; `BUFFER_TOO_SMALL` returns the required count without -partially writing the buffer. Other errors return zero and preserve the buffer. -`NOT_FOUND` reports a query miss; `TryCastRay` instead returns a successful value -with `found == 0` for misses. - -```c -R3Vector position = r3RigidBody_Translation(body); -if (r3LastStatus() != R3_OK) { - fprintf(stderr, "%s\n", r3LastError()); -} +```sh +cmake -S c -B build/c3 -DRAPIER_BUILD_TESTBED=ON -DRAPIER_DIMENSION=3 \ + -DRAPIER_PROFILE=release -DCMAKE_BUILD_TYPE=Release +cmake --build build/c3 --target rapier_testbed --config Release --parallel +./build/c3/testbed/rapier_testbed ``` -`r3LastError()` returns a thread-local UTF-8 message valid until the next fallible -call on the same thread. Copy it before another call. After an error handler makes -nested calls, the original operation's status and diagnostic are restored. -Rust panics are caught at the boundary when built with unwinding and reported as -`R3_PANIC`; discard objects mutated by that call because rollback is not promised. -Do not build these bindings with `panic=abort` if you depend on panic containment. -Allocation failure and invalid/dangling C pointers are not recoverable statuses. - -`r3SetErrorHandler` optionally installs a handler on the calling thread and -returns the previous handler for scoped restoration. The default handler is null; -both status-returning and value-returning calls report failures to the handler. A reporting handler may return normally, -or a fail-fast application may print the diagnostic and terminate the process. -It must never throw or `longjmp` through Rust frames. The testbed installs a -fail-fast handler around each example so its physics calls need no checking -macros. Handlers also receive `R3_NOT_FOUND` query misses, so applications using -expected misses should handle that status accordingly. The diagnostic is borrowed -only for the callback; the callback and its user data must remain alive until the -handler is replaced. Handler state is thread-local, not inherited by workers. - -Independent worlds may run on independent threads. Shared reads on one world -are allowed; conflicting access reports `R3_WORLD_BUSY`. Callbacks use the C -calling convention and must never throw, unwind, or longjmp across Rust frames. -With `parallel`, callbacks and their user data must support concurrent invocation. -Keep callbacks and their data alive until the synchronous operation returns. - -Pass NULL hooks/events for default behavior. Enable `activeEvents` and -`activeHooks` on colliders to request the corresponding callbacks/collections. -The event collector accumulates collision, force, and tear events until `clear`; -copying events does not consume them. Tear-event getters return owned copies. -`ContactForceEvent.started` preserves Rapier's threshold-crossing semantics. -The contact modification callback currently edits rigid-manifold material, -normal, user data, and enabled status; soft-contact candidate editing is not -exposed. Invalid callback material/normal values leave the manifold unchanged. +For 2D, use `build/c2` and `-DRAPIER_DIMENSION=2`. With a multi-configuration +generator such as Visual Studio, the executable is under `testbed/Release/`. -`modify_solver_contacts_context` additionally receives a borrowed native contact -context. Its accessors support `update_as_oneway_platform` and tangent velocity; -the context is only valid during that callback. Query predicates also receive a scoped read context. They may -perform nested world reads/queries but cannot mutate the world during traversal. +CMake downloads the graphics dependencies on the first configuration. macOS uses +system frameworks; Linux needs X11/OpenGL development packages. +See the [testbed guide](testbed/README.md) for controls, scene selection, and +headless runs, and [dependencies](testbed/dependencies.md) for offline builds. -## Math and configuration +## Documentation and examples -`R3Real` is float or double; vectors contain two or three packed scalar fields, -without Rust SIMD alignment. A 2D rotation is one angle in radians. A 3D rotation -is an `(x,y,z,w)` quaternion, normalized on input; initialize identity with w=1. -All boolean inputs/outputs are `uint32_t` values 0 or 1. Enum inputs and flags are -integers validated before conversion into Rust values. User data preserves all -128 bits as `{low, high}`. `size_t` is pointer-sized, including in language bindings. -There are no C varargs, C++ types, Rust references, slices, strings, or enums in -the binary interface. Callback function pointers are explicitly `cdecl` on Windows. - -Defaults come from the native Rust constructors. Gravity, integration parameters, -soft-body recovery settings, joint motor models, collision/solver groups, and -material combine rules retain their Rust semantics. Generic joints cover the -standard fixed, revolute, prismatic, rope, spring, 2D pin-slot, and 3D spherical families. -3D heightfield samples are **column-major**, matching Parry `Array2`. - -## Coverage and examples - -- [testbed/README.md](testbed/README.md): raylib/Dear ImGui viewer and headless C scenes. -- [examples/falling_ball.c](examples/falling_ball.c): complete C simulation. -- [include/rapier.hpp](include/rapier.hpp): optional C++ ownership helpers. -- [examples/RapierNative.cs](examples/RapierNative.cs): executable P/Invoke - example using a SafeHandle for the world, also suitable for a Unity wrapper. -- [tests/integration.c](tests/integration.c): world ownership and collision-only updates, - events/hooks, queries, joints, snapshots, controllers, soft-body meshes and cuts. - -The engine examples are integration starting points, not complete Unity or Unreal -plugins. Physics, game-object synchronization, editor tooling, and deployment -policies belong in those engine-specific layers. - -## Development and verification +Generate the searchable API reference and usage guides: ```sh -cargo test -p rapier-c-macros -p rapier2d-ffi -p rapier3d-ffi -p rapier2d-f64-ffi -p rapier3d-f64-ffi -python3 c/tools/test-native.py # Unix: all four variants, shared and static, C and C++ -# Header generation requires Python 3 and cbindgen 0.29.4: -cargo install cbindgen --version 0.29.4 --locked -sh c/tools/generate-header.sh +cmake -S c/doxygen -B build/c-docs +cmake --build build/c-docs --target rapier_docs --parallel ``` -The generator uses Rust declarations and makes the transparent Rust wrapper types -opaque to C. The symbol test links every function declared for each selected ABI. -A CI workflow additionally builds the CMake tests on Linux, macOS, and Windows and -checks the generated header. The shared source is in `src/`; the four tiny crates -select Rapier's matching dimension and scalar precision. - -The optional `robotics` feature (3D/f32 only, matching the native importer crates) -adds URDF/MJCF loading, insertion, keyframes, actuator controls, and visual mesh -access. Enable it with `-DRAPIER_FEATURES=robotics` or Cargo `--features robotics`. -It uses the workspace Rust importers and their mesh readers; no additional C -libraries are required. See the [robot examples](testbed/README.md#optional-examples). - -Snapshots are limited to 256 MiB. Their 16-byte header contains `RPRS` followed -by the ABI version, dimension, and scalar byte size as little-endian `uint32_t` -values. Older six-byte headers are rejected. Load -**trusted snapshots from the identical Rapier build only**. The Rust serialized -world representation is neither an untrusted asset format nor a versioned storage -format; the tag cannot establish source-revision compatibility or data integrity. - -### Direct builder and world APIs +Open `build/c-docs/html/index.html`. This requires Doxygen 1.9.4+, Python 3, and a +C compiler. It generates all four dimension/precision variants without building +the library or downloading testbed dependencies. Normal builds do not need Doxygen. -`r3BallColliderDesc`, `r3CuboidColliderDesc`, and other shape constructors return -ordinary collider descriptions by value. `r3InsertRigidBody`, -`r3InsertCollider`, and `r3InsertSoftBody` insert descriptions into the world -and return handles directly. `r3InsertCollider` takes a parent handle by value; -`r3InsertColliderWithoutParent` takes the world explicitly for standalone colliders. +The reference covers API usage, ownership, errors, callbacks, and snapshots. +The CI workflow also provides a `rapier-c-api-docs` HTML artifact. -The optional `rapier_math.h` header supplies C11/C++17 value constructors and -arithmetic for vectors, rotations, and poses. It has no third-party dependencies; -on Unix, manual consumers using its trigonometric operations should link `libm`. -The CMake target supplies that system library automatically. +- [C example](examples/falling_ball.c) +- [C++ helpers](include/rapier.hpp) +- [C# interop example](examples/RapierNative.cs) -### C++ ownership helpers +## Tests and header generation -`rapier.hpp` provides movable `std::unique_ptr` owners for all 14 owned resource -types: `World`, `Shape`, `EventCollector`, `Snapshot`, `SoftBodyTearEvent`, -`ShapeMesh`, `KinematicCharacterController`, `PidController`, and, in 3D, -`DynamicRayCastVehicleController` and `TriMeshData`. Robotics adds `UrdfRobot`, -`UrdfRobotHandles`, `MjcfRobot`, and `MjcfRobotHandles`. - -```cpp -rapier::Shape shape(r3Collider_CloneShape(collider)); -rapier::check(r3LastStatus()); -rapier::ShapeMesh mesh(r3SharedShape_Tessellate(shape.get(), 16)); -rapier::check(r3LastStatus()); -// Each owner frees its resource when it leaves scope, including during exceptions. +```sh +ctest --test-dir build/c -C Release --output-on-failure ``` -Check errors after each fallible call. These owners wrap only owned pointers; -borrowed callback contexts and MJCF visual meshes must not be wrapped or freed. -Owners of resources referencing a world must be destroyed before that world. - -## API reference - -Generate searchable Doxygen documentation for 2D/3D and f32/f64: +The headers are checked in; users do not need to generate them. When changing the +bindings or API comments in `c/src/`, regenerate the header with: ```sh -cmake -S c/doxygen -B build/c-docs -cmake --build build/c-docs --target rapier_docs --parallel +cargo install cbindgen --version 0.29.4 --locked +python3 c/tools/generate-header.py ``` - -Open `build/c-docs/html/index.html`. This needs Doxygen 1.9.4+, Python 3, CMake, -and a C compiler; it does not build physics or fetch testbed dependencies. -Alternatively, configure the normal C build with `-DRAPIER_BUILD_DOCS=ON` and build -`rapier_docs`; the entry page is then under `doxygen/html/index.html` in that build. -Normal builds do not require documentation tools. - -The reference includes all optional APIs, with robotics limited to 3D/f32. -Descriptions of ownership, errors, arrays, callbacks, and snapshots accompany the -API groups. CI checks all four variants for Doxygen warnings and missing functions, -then uploads the HTML as the `rapier-c-api-docs` artifact. - -Edit API comments in `c/src/*.rs` and regenerate `include/rapier.h`; edit inline -math/C++ helper comments in their headers. Keep contracts specific: units, -coordinate frames, ownership, callback lifetime, and special failure cases. -The concise usage pages live in `doxygen/reference.dox`. diff --git a/c/testbed/README.md b/c/testbed/README.md index ddb7eb7b1..554a5dabb 100644 --- a/c/testbed/README.md +++ b/c/testbed/README.md @@ -28,7 +28,7 @@ The header also reports **SIMD lanes**, **parallel support**, and the actual wor count from the loaded library. Build options are `-DRAPIER_ENABLE_PARALLEL=ON|OFF` (default ON for the testbed) and `-DRAPIER_SIMD_LANES=4|8` (default 4). This branch always uses SIMD; eight lanes require f32 and cannot be combined with -`enhanced-determinism`. See [binding build options](../README.md#cmake-and-installation). +`enhanced-determinism`. See [binding build options](../README.md#build-the-library). Choose workers under **Settings > Execution**, then Apply, or pass `--threads N` to either executable. `0` selects automatic sizing, `1` uses one worker, and the From 3654a81ecdec88ddf015a171c80e0e778f441527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 24 Sep 2026 16:40:18 +0200 Subject: [PATCH 6/8] chore: restrict C bindings CI to master and pull requests --- .github/workflows/c-bindings.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/c-bindings.yml b/.github/workflows/c-bindings.yml index 268c3fe1d..df4ebcc5b 100644 --- a/.github/workflows/c-bindings.yml +++ b/.github/workflows/c-bindings.yml @@ -1,8 +1,10 @@ name: C bindings on: push: + branches: [master] paths: ['c/**', 'src/**', 'crates/rapier*/**', 'Cargo.toml', '.github/workflows/c-bindings.yml'] pull_request: + branches: [master] paths: ['c/**', 'src/**', 'crates/rapier*/**', 'Cargo.toml', '.github/workflows/c-bindings.yml'] workflow_dispatch: jobs: From 22e8ec8b70bd3ba0ff526a1eaa77448ed3bcb023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 24 Sep 2026 17:02:27 +0200 Subject: [PATCH 7/8] fix: resolve C bindings CI failures --- c/cbindgen.toml | 2 +- c/include/rapier.h | 4154 +++++++++++++++++------------------- c/src/pipeline.rs | 2 +- c/tests/initializers.c | 4 +- c/tests/pod.c | 4 +- c/tools/generate-header.py | 5 + src_testbed/physics/mod.rs | 1 + 7 files changed, 1937 insertions(+), 2235 deletions(-) diff --git a/c/cbindgen.toml b/c/cbindgen.toml index 30871b5e6..06bc33169 100644 --- a/c/cbindgen.toml +++ b/c/cbindgen.toml @@ -67,6 +67,6 @@ header = """ "feature = parallel" = "RAPIER_PARALLEL" "feature = robotics" = "RAPIER_ROBOTICS" [fn] -prefix = "RAPIER_API RAPIER_CALL" +prefix = "RAPIER_API" [parse] parse_deps = false diff --git a/c/include/rapier.h b/c/include/rapier.h index 0e5dfc89b..8a8c476bf 100644 --- a/c/include/rapier.h +++ b/c/include/rapier.h @@ -3883,67 +3883,66 @@ extern "C" { * Return native default soft body material. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R2SoftBodyMaterial r2DefaultSoftBodyMaterial(void); +RAPIER_API struct R2SoftBodyMaterial RAPIER_CALL r2DefaultSoftBodyMaterial(void); /** * Return native default soft recovery settings. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R2SoftRecoverySettings r2DefaultSoftRecoverySettings(void); +RAPIER_API struct R2SoftRecoverySettings RAPIER_CALL r2DefaultSoftRecoverySettings(void); #if defined(RAPIER_FEM) /** * Return native default soft fem parameters. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R2SoftFemParameters r2DefaultSoftFemParameters(void); +RAPIER_API struct R2SoftFemParameters RAPIER_CALL r2DefaultSoftFemParameters(void); #endif /** * Return native default soft bodies settings. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R2SoftBodiesSettings r2DefaultSoftBodiesSettings(void); +RAPIER_API struct R2SoftBodiesSettings RAPIER_CALL r2DefaultSoftBodiesSettings(void); /** * Return native default integration parameters. This POD value owns no resources. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R2IntegrationParameters r2DefaultIntegrationParameters(void); +RAPIER_API struct R2IntegrationParameters RAPIER_CALL r2DefaultIntegrationParameters(void); /** * Return a copy of all world integration settings. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -struct R2IntegrationParameters r2IntegrationParameters(const struct R2World *world); +RAPIER_API struct R2IntegrationParameters RAPIER_CALL r2IntegrationParameters(const struct R2World *world); /** * Copies validated values; does not expose a writable alias to Rust memory. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2SetIntegrationParameters(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2SetIntegrationParameters(struct R2World *world, const struct R2IntegrationParameters *data); /** * Return native default joint desc. This POD value owns no resources. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R2JointDesc r2DefaultJointDesc(void); +RAPIER_API struct R2JointDesc RAPIER_CALL r2DefaultJointDesc(void); /** * Return a fixed joint description with native defaults; no allocation. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R2JointDesc r2FixedJointDesc(void); +RAPIER_API struct R2JointDesc RAPIER_CALL r2FixedJointDesc(void); #if defined(RAPIER_DIM2) /** * Return a revolute joint description with native defaults; no allocation. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R2JointDesc r2RevoluteJointDesc(void); +RAPIER_API struct R2JointDesc RAPIER_CALL r2RevoluteJointDesc(void); #endif #if defined(RAPIER_DIM3) @@ -3951,27 +3950,27 @@ RAPIER_API RAPIER_CALL struct R2JointDesc r2RevoluteJointDesc(void); * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R2JointDesc r2RevoluteJointDesc(struct R2Vector axis_vector); +RAPIER_API struct R2JointDesc RAPIER_CALL r2RevoluteJointDesc(struct R2Vector axis_vector); #endif /** * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R2JointDesc r2PrismaticJointDesc(struct R2Vector axis_vector); +RAPIER_API struct R2JointDesc RAPIER_CALL r2PrismaticJointDesc(struct R2Vector axis_vector); /** * Return a rope joint description with native defaults; no allocation. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R2JointDesc r2RopeJointDesc(R2Real length); +RAPIER_API struct R2JointDesc RAPIER_CALL r2RopeJointDesc(R2Real length); /** * Return a spring joint description with native defaults; no allocation. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R2JointDesc r2SpringJointDesc(R2Real length, +RAPIER_API +struct R2JointDesc RAPIER_CALL r2SpringJointDesc(R2Real length, R2Real stiffness, R2Real damping); @@ -3980,7 +3979,7 @@ struct R2JointDesc r2SpringJointDesc(R2Real length, * Return a spherical joint description with native defaults; no allocation. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R2JointDesc r2SphericalJointDesc(void); +RAPIER_API struct R2JointDesc RAPIER_CALL r2SphericalJointDesc(void); #endif #if defined(RAPIER_DIM2) @@ -3988,7 +3987,7 @@ RAPIER_API RAPIER_CALL struct R2JointDesc r2SphericalJointDesc(void); * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R2JointDesc r2PinSlotJointDesc(struct R2Vector axis_vector); +RAPIER_API struct R2JointDesc RAPIER_CALL r2PinSlotJointDesc(struct R2Vector axis_vector); #endif /** @@ -3996,8 +3995,8 @@ RAPIER_API RAPIER_CALL struct R2JointDesc r2PinSlotJointDesc(struct R2Vector axi * wake_up wakes the connected bodies. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R2ImpulseJointHandle r2InsertImpulseJoint(struct R2RigidBodyHandle body1, +RAPIER_API +struct R2ImpulseJointHandle RAPIER_CALL r2InsertImpulseJoint(struct R2RigidBodyHandle body1, struct R2RigidBodyHandle body2, const struct R2JointDesc *joint); @@ -4006,8 +4005,8 @@ struct R2ImpulseJointHandle r2InsertImpulseJoint(struct R2RigidBodyHandle body1, * failure; check r2LastStatus. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R2MultibodyJointHandle r2InsertMultibodyJoint(struct R2RigidBodyHandle body1, +RAPIER_API +struct R2MultibodyJointHandle RAPIER_CALL r2InsertMultibodyJoint(struct R2RigidBodyHandle body1, struct R2RigidBodyHandle body2, const struct R2JointDesc *joint); @@ -4015,29 +4014,29 @@ struct R2MultibodyJointHandle r2InsertMultibodyJoint(struct R2RigidBodyHandle bo * Return native default soft body desc. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R2SoftBodyDesc r2DefaultSoftBodyDesc(void); +RAPIER_API struct R2SoftBodyDesc RAPIER_CALL r2DefaultSoftBodyDesc(void); /** * Consumes no caller-owned resources. All borrowed arrays may be released on return. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyHandle r2InsertSoftBody(struct R2World *world, +RAPIER_API +struct R2SoftBodyHandle RAPIER_CALL r2InsertSoftBody(struct R2World *world, const struct R2SoftBodyDesc *desc); /** * Return native default soft mesh binding desc. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R2SoftMeshBindingDesc r2DefaultSoftMeshBindingDesc(void); +RAPIER_API struct R2SoftMeshBindingDesc RAPIER_CALL r2DefaultSoftMeshBindingDesc(void); /** * Create a deformable collider bound to a soft-body cluster. The world owns the collider; binding * arrays are borrowed only during insertion. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderHandle r2InsertDeformableCollider(const struct R2ColliderDesc *collider, +RAPIER_API +struct R2ColliderHandle RAPIER_CALL r2InsertDeformableCollider(const struct R2ColliderDesc *collider, const struct R2SoftMeshBindingDesc *binding, struct R2RigidBodyHandle parent); @@ -4045,7 +4044,7 @@ struct R2ColliderHandle r2InsertDeformableCollider(const struct R2ColliderDesc * * Return native default query options. This POD value owns no resources. * @ingroup queries */ -RAPIER_API RAPIER_CALL struct R2QueryOptions r2DefaultQueryOptions(void); +RAPIER_API struct R2QueryOptions RAPIER_CALL r2DefaultQueryOptions(void); /** * Return the closest ray hit, or report R2_NOT_FOUND on a miss. The ray is origin + direction * t @@ -4055,8 +4054,8 @@ RAPIER_API RAPIER_CALL struct R2QueryOptions r2DefaultQueryOptions(void); * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -struct R2RayHit r2CastRay(const struct R2World *world, +RAPIER_API +struct R2RayHit RAPIER_CALL r2CastRay(const struct R2World *world, const struct R2QueryOptions *query_options, struct R2Vector origin, struct R2Vector direction, @@ -4070,8 +4069,8 @@ struct R2RayHit r2CastRay(const struct R2World *world, * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -struct R2PointProjection r2ProjectPoint(const struct R2World *world, +RAPIER_API +struct R2PointProjection RAPIER_CALL r2ProjectPoint(const struct R2World *world, const struct R2QueryOptions *query_options, struct R2Vector point, R2Real max_distance, @@ -4084,8 +4083,8 @@ struct R2PointProjection r2ProjectPoint(const struct R2World *world, * DetectCollisions call. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -struct R2ShapeCastHit r2CastShape(const struct R2World *world, +RAPIER_API +struct R2ShapeCastHit RAPIER_CALL r2CastShape(const struct R2World *world, const struct R2QueryOptions *query_options, struct R2Pose pose, struct R2Vector velocity, @@ -4099,8 +4098,8 @@ struct R2ShapeCastHit r2CastShape(const struct R2World *world, * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -size_t r2IntersectPoint(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2IntersectPoint(const struct R2World *world, const struct R2QueryOptions *query_options, struct R2Vector point, struct R2ColliderHandle *buffer, @@ -4114,8 +4113,8 @@ size_t r2IntersectPoint(const struct R2World *world, * DetectCollisions call. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -size_t r2IntersectShape(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2IntersectShape(const struct R2World *world, const struct R2QueryOptions *query_options, struct R2Pose pose, const R2SharedShape *shape, @@ -4130,8 +4129,8 @@ size_t r2IntersectShape(const struct R2World *world, * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -size_t r2IntersectAabbConservative(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2IntersectAabbConservative(const struct R2World *world, const struct R2QueryOptions *query_options, struct R2Aabb aabb, struct R2ColliderHandle *buffer, @@ -4144,8 +4143,8 @@ size_t r2IntersectAabbConservative(const struct R2World *world, * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -struct R2RayToi r2CastRayToi(const struct R2World *world, +RAPIER_API +struct R2RayToi RAPIER_CALL r2CastRayToi(const struct R2World *world, const struct R2QueryOptions *query_options, struct R2Vector origin, struct R2Vector direction, @@ -4159,8 +4158,8 @@ struct R2RayToi r2CastRayToi(const struct R2World *world, * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -struct R2OptionalRayHit r2TryCastRay(const struct R2World *world, +RAPIER_API +struct R2OptionalRayHit RAPIER_CALL r2TryCastRay(const struct R2World *world, const struct R2QueryOptions *query_options, struct R2Vector origin, struct R2Vector direction, @@ -4171,66 +4170,65 @@ struct R2OptionalRayHit r2TryCastRay(const struct R2World *world, * Return a dynamic rigid-body description with native defaults; no allocation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2DynamicRigidBodyDesc(void); +RAPIER_API struct R2RigidBodyDesc RAPIER_CALL r2DynamicRigidBodyDesc(void); /** * Return a fixed rigid-body description with native defaults; no allocation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2FixedRigidBodyDesc(void); +RAPIER_API struct R2RigidBodyDesc RAPIER_CALL r2FixedRigidBodyDesc(void); /** * Return a kinematic position based rigid-body description with native defaults; no allocation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2KinematicPositionBasedRigidBodyDesc(void); +RAPIER_API struct R2RigidBodyDesc RAPIER_CALL r2KinematicPositionBasedRigidBodyDesc(void); /** * Return a kinematic velocity based rigid-body description with native defaults; no allocation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R2RigidBodyDesc r2KinematicVelocityBasedRigidBodyDesc(void); +RAPIER_API struct R2RigidBodyDesc RAPIER_CALL r2KinematicVelocityBasedRigidBodyDesc(void); /** * Return native default shape desc. This POD value owns no resources. * @ingroup shapes */ -RAPIER_API RAPIER_CALL struct R2ShapeDesc r2DefaultShapeDesc(void); +RAPIER_API struct R2ShapeDesc RAPIER_CALL r2DefaultShapeDesc(void); /** * Build an owned shared shape from a description; release it with r2FreeSharedShape. Borrowed * inputs may be released after this call. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R2SharedShape *r2ShapeDesc_Build(const struct R2ShapeDesc *desc); +RAPIER_API R2SharedShape *RAPIER_CALL r2ShapeDesc_Build(const struct R2ShapeDesc *desc); /** * Return native default collider desc. This POD value owns no resources. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R2ColliderDesc r2DefaultColliderDesc(void); +RAPIER_API struct R2ColliderDesc RAPIER_CALL r2DefaultColliderDesc(void); /** * Return a ball description with the supplied radius. * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R2ColliderDesc r2BallColliderDesc(R2Real radius); +RAPIER_API struct R2ColliderDesc RAPIER_CALL r2BallColliderDesc(R2Real radius); /** * Return an axis-aligned box description with the supplied half-extents. * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderDesc r2CuboidColliderDesc(struct R2Vector half_extents); +RAPIER_API struct R2ColliderDesc RAPIER_CALL r2CuboidColliderDesc(struct R2Vector half_extents); /** * Create a body from the description and return its world-bound handle. The world owns the body. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R2RigidBodyHandle r2InsertRigidBody(struct R2World *world, +RAPIER_API +struct R2RigidBodyHandle RAPIER_CALL r2InsertRigidBody(struct R2World *world, const struct R2RigidBodyDesc *desc); /** @@ -4239,8 +4237,8 @@ struct R2RigidBodyHandle r2InsertRigidBody(struct R2World *world, * Invalid or removed parents fail without inserting a collider. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderHandle r2InsertCollider(struct R2RigidBodyHandle parent, +RAPIER_API +struct R2ColliderHandle RAPIER_CALL r2InsertCollider(struct R2RigidBodyHandle parent, const struct R2ColliderDesc *desc); /** @@ -4248,62 +4246,61 @@ struct R2ColliderHandle r2InsertCollider(struct R2RigidBodyHandle parent, * The description is borrowed through this call. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderHandle r2InsertColliderWithoutParent(struct R2World *world, +RAPIER_API +struct R2ColliderHandle RAPIER_CALL r2InsertColliderWithoutParent(struct R2World *world, const struct R2ColliderDesc *desc); /** * Return POD structure sizes for checking foreign-language layouts against this library. * @ingroup errors */ -RAPIER_API RAPIER_CALL struct R2PodLayout r2PodLayout(void); +RAPIER_API struct R2PodLayout RAPIER_CALL r2PodLayout(void); /** * Allocate a character controller with native defaults; release it with * r2FreeKinematicCharacterController. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R2KinematicCharacterController *r2NewKinematicCharacterController(void); +RAPIER_API struct R2KinematicCharacterController *RAPIER_CALL r2NewKinematicCharacterController(void); /** * Release an owned kinematic character controller. NULL is allowed. Do not pass borrowed pointers * or free the object twice. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2FreeKinematicCharacterController(struct R2KinematicCharacterController *controller); +RAPIER_API +R2Status RAPIER_CALL r2FreeKinematicCharacterController(struct R2KinematicCharacterController *controller); /** * Set the up direction; it must be finite and nonzero and is normalized on input. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2KinematicCharacterController_SetUp(struct R2KinematicCharacterController *controller, +RAPIER_API +R2Status RAPIER_CALL r2KinematicCharacterController_SetUp(struct R2KinematicCharacterController *controller, struct R2Vector up); /** * Set the collision separation margin; use a positive absolute or relative character length. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2KinematicCharacterController_SetOffset(struct R2KinematicCharacterController *controller, +RAPIER_API +R2Status RAPIER_CALL r2KinematicCharacterController_SetOffset(struct R2KinematicCharacterController *controller, struct R2CharacterLength offset); /** * Enable or disable sliding along obstacles. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2KinematicCharacterController_SetSlide(struct R2KinematicCharacterController *controller, +RAPIER_API +R2Status RAPIER_CALL r2KinematicCharacterController_SetSlide(struct R2KinematicCharacterController *controller, R2Bool enabled); /** * Set the maximum climb angle and minimum slide angle, in radians. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2KinematicCharacterController_SetSlopes(struct R2KinematicCharacterController *controller, +RAPIER_API +R2Status RAPIER_CALL r2KinematicCharacterController_SetSlopes(struct R2KinematicCharacterController *controller, R2Real max_climb_angle, R2Real min_slide_angle); @@ -4311,8 +4308,8 @@ R2Status r2KinematicCharacterController_SetSlopes(struct R2KinematicCharacterCon * Configure automatic stepping over obstacles. enabled = 0 disables it. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2KinematicCharacterController_SetAutostep(struct R2KinematicCharacterController *controller, +RAPIER_API +R2Status RAPIER_CALL r2KinematicCharacterController_SetAutostep(struct R2KinematicCharacterController *controller, R2Bool enabled, struct R2CharacterLength max_height, struct R2CharacterLength min_width, @@ -4322,8 +4319,8 @@ R2Status r2KinematicCharacterController_SetAutostep(struct R2KinematicCharacterC * Configure downward ground snapping. enabled = 0 disables it. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2KinematicCharacterController_SetSnapToGround(struct R2KinematicCharacterController *controller, +RAPIER_API +R2Status RAPIER_CALL r2KinematicCharacterController_SetSnapToGround(struct R2KinematicCharacterController *controller, R2Bool enabled, struct R2CharacterLength distance); @@ -4334,8 +4331,8 @@ R2Status r2KinematicCharacterController_SetSnapToGround(struct R2KinematicCharac * DetectCollisions call. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R2CharacterMovement r2KinematicCharacterController_MoveShape(const struct R2World *world, +RAPIER_API +struct R2CharacterMovement RAPIER_CALL r2KinematicCharacterController_MoveShape(const struct R2World *world, const struct R2QueryOptions *options, struct R2KinematicCharacterController *controller, R2Real dt, @@ -4348,8 +4345,8 @@ struct R2CharacterMovement r2KinematicCharacterController_MoveShape(const struct * @see @ref output_buffers * @ingroup controllers */ -RAPIER_API RAPIER_CALL -size_t r2KinematicCharacterController_Collisions(const struct R2KinematicCharacterController *controller, +RAPIER_API +size_t RAPIER_CALL r2KinematicCharacterController_Collisions(const struct R2KinematicCharacterController *controller, struct R2CharacterCollision *buffer, size_t capacity); @@ -4358,8 +4355,8 @@ size_t r2KinematicCharacterController_Collisions(const struct R2KinematicCharact * filter. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2KinematicCharacterController_SolveCharacterCollisionImpulses(const struct R2KinematicCharacterController *controller, +RAPIER_API +R2Status RAPIER_CALL r2KinematicCharacterController_SolveCharacterCollisionImpulses(const struct R2KinematicCharacterController *controller, const R2SharedShape *shape, R2Real dt, R2Real mass, @@ -4370,44 +4367,43 @@ R2Status r2KinematicCharacterController_SolveCharacterCollisionImpulses(const st * r2FreePidController. * @ingroup controllers */ -RAPIER_API RAPIER_CALL struct R2PidController *r2NewPidController(void); +RAPIER_API struct R2PidController *RAPIER_CALL r2NewPidController(void); /** * Release an owned pid controller. NULL is allowed. Do not pass borrowed pointers or free the * object twice. * @ingroup controllers */ -RAPIER_API RAPIER_CALL R2Status r2FreePidController(struct R2PidController *controller); +RAPIER_API R2Status RAPIER_CALL r2FreePidController(struct R2PidController *controller); /** * Return a copy of the proportional, integral, and derivative gains. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R2PidGains r2PidController_Gains(const struct R2PidController *controller); +RAPIER_API struct R2PidGains RAPIER_CALL r2PidController_Gains(const struct R2PidController *controller); /** * Replace the proportional, integral, and derivative gains. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2PidController_SetGains(struct R2PidController *controller, +RAPIER_API +R2Status RAPIER_CALL r2PidController_SetGains(struct R2PidController *controller, struct R2PidGains gains); /** * AxesMask bits match Rapier: linear X/Y/Z are 1/2/4, angular X/Y/Z are 8/16/32. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2PidController_SetAxes(struct R2PidController *controller, +RAPIER_API +R2Status RAPIER_CALL r2PidController_SetAxes(struct R2PidController *controller, uint32_t axes); /** * Compute a velocity correction, preserving the body's state and updating PID integrals. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R2VelocityCorrection r2PidController_RigidBodyCorrection(struct R2PidController *controller, +RAPIER_API +struct R2VelocityCorrection RAPIER_CALL r2PidController_RigidBodyCorrection(struct R2PidController *controller, R2Real dt, struct R2RigidBodyHandle body, struct R2Pose target_pose, @@ -4418,15 +4414,15 @@ struct R2VelocityCorrection r2PidController_RigidBodyCorrection(struct R2PidCont * Return a copy of slide, slope, and ground-snap settings. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R2CharacterControllerSettings r2KinematicCharacterController_Settings(const struct R2KinematicCharacterController *controller); +RAPIER_API +struct R2CharacterControllerSettings RAPIER_CALL r2KinematicCharacterController_Settings(const struct R2KinematicCharacterController *controller); #if defined(RAPIER_DIM3) /** * Return native default wheel tuning. This POD value owns no resources. * @ingroup controllers */ -RAPIER_API RAPIER_CALL struct R2WheelTuning r2DefaultWheelTuning(void); +RAPIER_API struct R2WheelTuning RAPIER_CALL r2DefaultWheelTuning(void); #endif #if defined(RAPIER_DIM3) @@ -4435,8 +4431,8 @@ RAPIER_API RAPIER_CALL struct R2WheelTuning r2DefaultWheelTuning(void); * controller. Release with r2FreeDynamicRayCastVehicleController. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R2DynamicRayCastVehicleController *r2NewDynamicRayCastVehicleController(struct R2RigidBodyHandle chassis); +RAPIER_API +struct R2DynamicRayCastVehicleController *RAPIER_CALL r2NewDynamicRayCastVehicleController(struct R2RigidBodyHandle chassis); #endif #if defined(RAPIER_DIM3) @@ -4445,8 +4441,8 @@ struct R2DynamicRayCastVehicleController *r2NewDynamicRayCastVehicleController(s * pointers or free the object twice. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2FreeDynamicRayCastVehicleController(struct R2DynamicRayCastVehicleController *controller); +RAPIER_API +R2Status RAPIER_CALL r2FreeDynamicRayCastVehicleController(struct R2DynamicRayCastVehicleController *controller); #endif #if defined(RAPIER_DIM3) @@ -4455,8 +4451,8 @@ R2Status r2FreeDynamicRayCastVehicleController(struct R2DynamicRayCastVehicleCon * are in chassis-local coordinates. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -size_t r2DynamicRayCastVehicleController_AddWheel(struct R2DynamicRayCastVehicleController *controller, +RAPIER_API +size_t RAPIER_CALL r2DynamicRayCastVehicleController_AddWheel(struct R2DynamicRayCastVehicleController *controller, struct R2Vector connection, struct R2Vector direction, struct R2Vector axle, @@ -4470,8 +4466,8 @@ size_t r2DynamicRayCastVehicleController_AddWheel(struct R2DynamicRayCastVehicle * Set the chassis up/forward axis indices (0 = X, 1 = Y, 2 = Z). * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2DynamicRayCastVehicleController_SetAxes(struct R2DynamicRayCastVehicleController *controller, +RAPIER_API +R2Status RAPIER_CALL r2DynamicRayCastVehicleController_SetAxes(struct R2DynamicRayCastVehicleController *controller, size_t up, size_t forward); #endif @@ -4481,8 +4477,8 @@ R2Status r2DynamicRayCastVehicleController_SetAxes(struct R2DynamicRayCastVehicl * Set a wheel engine force, brake force, and steering angle in radians. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2DynamicRayCastVehicleController_SetWheelControls(struct R2DynamicRayCastVehicleController *controller, +RAPIER_API +R2Status RAPIER_CALL r2DynamicRayCastVehicleController_SetWheelControls(struct R2DynamicRayCastVehicleController *controller, size_t index, R2Real steering, R2Real engine_force, @@ -4494,8 +4490,8 @@ R2Status r2DynamicRayCastVehicleController_SetWheelControls(struct R2DynamicRayC * Ray-cast wheel contacts and apply vehicle forces for dt seconds. Does not step the world. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Status r2DynamicRayCastVehicleController_UpdateVehicle(struct R2DynamicRayCastVehicleController *controller, +RAPIER_API +R2Status RAPIER_CALL r2DynamicRayCastVehicleController_UpdateVehicle(struct R2DynamicRayCastVehicleController *controller, R2Real dt, const struct R2QueryFilter *filter); #endif @@ -4505,8 +4501,8 @@ R2Status r2DynamicRayCastVehicleController_UpdateVehicle(struct R2DynamicRayCast * Return signed chassis speed along its forward direction. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R2Real r2DynamicRayCastVehicleController_CurrentVehicleSpeed(const struct R2DynamicRayCastVehicleController *controller); +RAPIER_API +R2Real RAPIER_CALL r2DynamicRayCastVehicleController_CurrentVehicleSpeed(const struct R2DynamicRayCastVehicleController *controller); #endif #if defined(RAPIER_DIM3) @@ -4515,8 +4511,8 @@ R2Real r2DynamicRayCastVehicleController_CurrentVehicleSpeed(const struct R2Dyna * @see @ref output_buffers * @ingroup controllers */ -RAPIER_API RAPIER_CALL -size_t r2DynamicRayCastVehicleController_Wheels(const struct R2DynamicRayCastVehicleController *controller, +RAPIER_API +size_t RAPIER_CALL r2DynamicRayCastVehicleController_Wheels(const struct R2DynamicRayCastVehicleController *controller, struct R2WheelState *buffer, size_t capacity); #endif @@ -4526,16 +4522,16 @@ size_t r2DynamicRayCastVehicleController_Wheels(const struct R2DynamicRayCastVeh * querying the broad phase. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBodyPropagateModifiedBodyPositionsToColliders(struct R2World *world); +RAPIER_API +R2Status RAPIER_CALL r2RigidBodyPropagateModifiedBodyPositionsToColliders(struct R2World *world); /** * Copies the island manager's active body handles. * @see @ref output_buffers * @ingroup worlds */ -RAPIER_API RAPIER_CALL -size_t r2ActiveRigidBodies(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2ActiveRigidBodies(const struct R2World *world, struct R2RigidBodyHandle *buffer, size_t capacity); @@ -4543,9 +4539,7 @@ size_t r2ActiveRigidBodies(const struct R2World *world, * Wake a body by handle, including a soft-body cluster proxy. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_WakeUp(struct R2RigidBodyHandle handle, - R2Bool strong); +RAPIER_API R2Status RAPIER_CALL r2RigidBody_WakeUp(struct R2RigidBodyHandle handle, R2Bool strong); /** * Replace this thread's error handler and return the previous handler so it can @@ -4554,7 +4548,7 @@ R2Status r2RigidBody_WakeUp(struct R2RigidBodyHandle handle, * handler may terminate the process. Includes R2_NOT_FOUND query misses. * @ingroup errors */ -RAPIER_API RAPIER_CALL struct R2ErrorHandler r2SetErrorHandler(struct R2ErrorHandler handler); +RAPIER_API struct R2ErrorHandler RAPIER_CALL r2SetErrorHandler(struct R2ErrorHandler handler); /** * Status of the most recent fallible operation on this thread. Reading this or @@ -4563,40 +4557,40 @@ RAPIER_API RAPIER_CALL struct R2ErrorHandler r2SetErrorHandler(struct R2ErrorHan * from errors instead of using a fail-fast error callback. * @ingroup errors */ -RAPIER_API RAPIER_CALL R2Status r2LastStatus(void); +RAPIER_API R2Status RAPIER_CALL r2LastStatus(void); /** * Thread-local UTF-8 diagnostic, valid until the next fallible call on this thread. * @ingroup errors */ -RAPIER_API RAPIER_CALL const char *r2LastError(void); +RAPIER_API const char *RAPIER_CALL r2LastError(void); /** * Create an owned ball shape. Release it with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R2SharedShape *r2BallSharedShape(R2Real radius); +RAPIER_API R2SharedShape *RAPIER_CALL r2BallSharedShape(R2Real radius); /** * Create an owned cuboid shape. Release it with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R2SharedShape *r2CuboidSharedShape(struct R2Vector half_extents); +RAPIER_API R2SharedShape *RAPIER_CALL r2CuboidSharedShape(struct R2Vector half_extents); /** * Create an owned round cuboid shape. Release it with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2RoundCuboidSharedShape(struct R2Vector half_extents, +RAPIER_API +R2SharedShape *RAPIER_CALL r2RoundCuboidSharedShape(struct R2Vector half_extents, R2Real border_radius); /** * Create an owned capsule shape. Release it with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2CapsuleSharedShape(struct R2Vector a, +RAPIER_API +R2SharedShape *RAPIER_CALL r2CapsuleSharedShape(struct R2Vector a, struct R2Vector b, R2Real radius); @@ -4604,16 +4598,14 @@ R2SharedShape *r2CapsuleSharedShape(struct R2Vector a, * Create an owned segment shape. Release it with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2SegmentSharedShape(struct R2Vector a, - struct R2Vector b); +RAPIER_API R2SharedShape *RAPIER_CALL r2SegmentSharedShape(struct R2Vector a, struct R2Vector b); /** * Create an owned triangle shape. Release it with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2TriangleSharedShape(struct R2Vector a, +RAPIER_API +R2SharedShape *RAPIER_CALL r2TriangleSharedShape(struct R2Vector a, struct R2Vector b, struct R2Vector c); @@ -4621,16 +4613,14 @@ R2SharedShape *r2TriangleSharedShape(struct R2Vector a, * Create an owned halfspace shape. Release it with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R2SharedShape *r2HalfspaceSharedShape(struct R2Vector normal); +RAPIER_API R2SharedShape *RAPIER_CALL r2HalfspaceSharedShape(struct R2Vector normal); #if defined(RAPIER_DIM3) /** * Create an owned cylinder shape. Release it with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2CylinderSharedShape(R2Real half_height, - R2Real radius); +RAPIER_API R2SharedShape *RAPIER_CALL r2CylinderSharedShape(R2Real half_height, R2Real radius); #endif #if defined(RAPIER_DIM3) @@ -4638,7 +4628,7 @@ R2SharedShape *r2CylinderSharedShape(R2Real half_height, * Create an owned cone shape. Release it with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R2SharedShape *r2ConeSharedShape(R2Real half_height, R2Real radius); +RAPIER_API R2SharedShape *RAPIER_CALL r2ConeSharedShape(R2Real half_height, R2Real radius); #endif /** @@ -4646,32 +4636,27 @@ RAPIER_API RAPIER_CALL R2SharedShape *r2ConeSharedShape(R2Real half_height, R2Re * shared, not consumed. Release with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2CompoundSharedShape(struct R2CompoundShapeView children); +RAPIER_API R2SharedShape *RAPIER_CALL r2CompoundSharedShape(struct R2CompoundShapeView children); /** * Remove the collider and update its parent body mass properties. wake_up wakes the parent. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2RemoveCollider(struct R2ColliderHandle handle, - R2Bool wake_up); +RAPIER_API R2Status RAPIER_CALL r2RemoveCollider(struct R2ColliderHandle handle, R2Bool wake_up); /** * Remove an impulse joint. wake_up wakes its connected bodies. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2RemoveImpulseJoint(struct R2ImpulseJointHandle handle, - R2Bool wake_up); +RAPIER_API R2Status RAPIER_CALL r2RemoveImpulseJoint(struct R2ImpulseJointHandle handle, R2Bool wake_up); /** * Copy entity handles. * @see @ref output_buffers * @ingroup joints */ -RAPIER_API RAPIER_CALL -size_t r2ImpulseJointHandles(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2ImpulseJointHandles(const struct R2World *world, struct R2ImpulseJointHandle *buffer, size_t capacity); @@ -4679,8 +4664,8 @@ size_t r2ImpulseJointHandles(const struct R2World *world, * Remove an articulation joint. wake_up wakes affected bodies. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2RemoveMultibodyJoint(struct R2MultibodyJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RemoveMultibodyJoint(struct R2MultibodyJointHandle handle, R2Bool wake_up); /** @@ -4688,8 +4673,8 @@ R2Status r2RemoveMultibodyJoint(struct R2MultibodyJointHandle handle, * @see @ref output_buffers * @ingroup joints */ -RAPIER_API RAPIER_CALL -size_t r2MultibodyJointHandles(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2MultibodyJointHandles(const struct R2World *world, struct R2MultibodyJointHandle *buffer, size_t capacity); @@ -4697,28 +4682,26 @@ size_t r2MultibodyJointHandles(const struct R2World *world, * Return the two bodies connected by an impulse joint. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R2JointBodies r2ImpulseJoint_Bodies(struct R2ImpulseJointHandle handle); +RAPIER_API struct R2JointBodies RAPIER_CALL r2ImpulseJoint_Bodies(struct R2ImpulseJointHandle handle); /** * Return native default inverse kinematics options. This POD value owns no resources. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R2InverseKinematicsOptions r2DefaultInverseKinematicsOptions(void); +RAPIER_API struct R2InverseKinematicsOptions RAPIER_CALL r2DefaultInverseKinematicsOptions(void); /** * Return the articulation degrees of freedom associated with the joint. * @ingroup joints */ -RAPIER_API RAPIER_CALL size_t r2MultibodyJoint_Ndofs(struct R2MultibodyJointHandle handle); +RAPIER_API size_t RAPIER_CALL r2MultibodyJoint_Ndofs(struct R2MultibodyJointHandle handle); /** * Read/write displacement buffer must contain exactly ndofs entries; zero it for a fresh solve. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2MultibodyJoint_InverseKinematics(struct R2MultibodyJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2MultibodyJoint_InverseKinematics(struct R2MultibodyJointHandle handle, const struct R2InverseKinematicsOptions *options, struct R2Pose target, R2IkJointCanMove can_move, @@ -4730,8 +4713,8 @@ R2Status r2MultibodyJoint_InverseKinematics(struct R2MultibodyJointHandle handle * Apply generalized articulation displacements in native degree-of-freedom order. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2MultibodyJoint_ApplyDisplacements(struct R2MultibodyJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2MultibodyJoint_ApplyDisplacements(struct R2MultibodyJointHandle handle, const R2Real *displacements, size_t count); @@ -4739,28 +4722,28 @@ R2Status r2MultibodyJoint_ApplyDisplacements(struct R2MultibodyJointHandle handl * Frees an owned object; NULL is allowed. Never free a borrowed pointer. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R2Status r2FreeSharedShape(R2SharedShape *object); +RAPIER_API R2Status RAPIER_CALL r2FreeSharedShape(R2SharedShape *object); /** * Create an owned wrapper sharing the same immutable geometry. Release it with * r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R2SharedShape *r2SharedShape_Clone(const R2SharedShape *object); +RAPIER_API R2SharedShape *RAPIER_CALL r2SharedShape_Clone(const R2SharedShape *object); /** * Return the number of rigid body objects in the world. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL size_t r2RigidBodyCount(const struct R2World *world); +RAPIER_API size_t RAPIER_CALL r2RigidBodyCount(const struct R2World *world); /** * Copy entity handles. * @see @ref output_buffers * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -size_t r2RigidBodyHandles(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2RigidBodyHandles(const struct R2World *world, struct R2RigidBodyHandle *buffer, size_t capacity); @@ -4769,21 +4752,21 @@ size_t r2RigidBodyHandles(const struct R2World *world, * false. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2RigidBody_Contains(struct R2RigidBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2RigidBody_Contains(struct R2RigidBodyHandle handle); /** * Return the number of collider objects in the world. * @ingroup colliders */ -RAPIER_API RAPIER_CALL size_t r2ColliderCount(const struct R2World *world); +RAPIER_API size_t RAPIER_CALL r2ColliderCount(const struct R2World *world); /** * Copy entity handles. * @see @ref output_buffers * @ingroup colliders */ -RAPIER_API RAPIER_CALL -size_t r2ColliderHandles(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2ColliderHandles(const struct R2World *world, struct R2ColliderHandle *buffer, size_t capacity); @@ -4791,21 +4774,21 @@ size_t r2ColliderHandles(const struct R2World *world, * Test whether the live world contains this collider handle. A removed/stale handle returns false. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R2Bool r2Collider_Contains(struct R2ColliderHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2Collider_Contains(struct R2ColliderHandle handle); /** * Return the number of soft body objects in the world. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL size_t r2SoftBodyCount(const struct R2World *world); +RAPIER_API size_t RAPIER_CALL r2SoftBodyCount(const struct R2World *world); /** * Copy entity handles. * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyHandles(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyHandles(const struct R2World *world, struct R2SoftBodyHandle *buffer, size_t capacity); @@ -4814,283 +4797,265 @@ size_t r2SoftBodyHandles(const struct R2World *world, * false. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2SoftBody_Contains(struct R2SoftBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2SoftBody_Contains(struct R2SoftBodyHandle handle); /** * Remove a body and its joints, optionally keeping colliders as standalone objects. * Returns whether a body was removed; a stale handle returns false without error. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Bool r2RemoveRigidBody(struct R2RigidBodyHandle handle, +RAPIER_API +R2Bool RAPIER_CALL r2RemoveRigidBody(struct R2RigidBodyHandle handle, R2Bool remove_attached_colliders); /** * Return the world setting documented by R2IntegrationParameters::dt. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Real r2TimeStep(const struct R2World *world); +RAPIER_API R2Real RAPIER_CALL r2TimeStep(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::dt. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2SetTimeStep(struct R2World *world, R2Real value); +RAPIER_API R2Status RAPIER_CALL r2SetTimeStep(struct R2World *world, R2Real value); /** * Return the world setting documented by R2IntegrationParameters::minCcdDt. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Real r2MinCcdDt(const struct R2World *world); +RAPIER_API R2Real RAPIER_CALL r2MinCcdDt(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::minCcdDt. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2SetMinCcdDt(struct R2World *world, R2Real value); +RAPIER_API R2Status RAPIER_CALL r2SetMinCcdDt(struct R2World *world, R2Real value); /** * Return the world setting documented by R2IntegrationParameters::lengthUnit. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Real r2LengthUnit(const struct R2World *world); +RAPIER_API R2Real RAPIER_CALL r2LengthUnit(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::lengthUnit. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2SetLengthUnit(struct R2World *world, R2Real value); +RAPIER_API R2Status RAPIER_CALL r2SetLengthUnit(struct R2World *world, R2Real value); /** * Return the world setting documented by R2IntegrationParameters::warmstartCoefficient. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Real r2WarmstartCoefficient(const struct R2World *world); +RAPIER_API R2Real RAPIER_CALL r2WarmstartCoefficient(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::warmstartCoefficient. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2SetWarmstartCoefficient(struct R2World *world, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2SetWarmstartCoefficient(struct R2World *world, R2Real value); /** * Return the world setting documented by R2IntegrationParameters::normalizedAllowedLinearError. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Real r2NormalizedAllowedLinearError(const struct R2World *world); +RAPIER_API R2Real RAPIER_CALL r2NormalizedAllowedLinearError(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::normalizedAllowedLinearError. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2SetNormalizedAllowedLinearError(struct R2World *world, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2SetNormalizedAllowedLinearError(struct R2World *world, R2Real value); /** * Return the world setting documented by * R2IntegrationParameters::normalizedMaxCorrectiveVelocity. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Real r2NormalizedMaxCorrectiveVelocity(const struct R2World *world); +RAPIER_API R2Real RAPIER_CALL r2NormalizedMaxCorrectiveVelocity(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::normalizedMaxCorrectiveVelocity. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2SetNormalizedMaxCorrectiveVelocity(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2SetNormalizedMaxCorrectiveVelocity(struct R2World *world, R2Real value); /** * Return the world setting documented by R2IntegrationParameters::normalizedPredictionDistance. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Real r2NormalizedPredictionDistance(const struct R2World *world); +RAPIER_API R2Real RAPIER_CALL r2NormalizedPredictionDistance(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::normalizedPredictionDistance. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2SetNormalizedPredictionDistance(struct R2World *world, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2SetNormalizedPredictionDistance(struct R2World *world, R2Real value); /** * Return the world setting documented by R2IntegrationParameters::normalizedMaxLinearVelocity. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Real r2NormalizedMaxLinearVelocity(const struct R2World *world); +RAPIER_API R2Real RAPIER_CALL r2NormalizedMaxLinearVelocity(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::normalizedMaxLinearVelocity. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2SetNormalizedMaxLinearVelocity(struct R2World *world, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2SetNormalizedMaxLinearVelocity(struct R2World *world, R2Real value); /** * Return the world setting documented by * R2IntegrationParameters::normalizedContactRecycleDistance. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Real r2NormalizedContactRecycleDistance(const struct R2World *world); +RAPIER_API R2Real RAPIER_CALL r2NormalizedContactRecycleDistance(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::normalizedContactRecycleDistance. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2SetNormalizedContactRecycleDistance(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2SetNormalizedContactRecycleDistance(struct R2World *world, R2Real value); /** * Return the world setting documented by R2IntegrationParameters::numSolverIterations. * @ingroup worlds */ -RAPIER_API RAPIER_CALL size_t r2NumSolverIterations(const struct R2World *world); +RAPIER_API size_t RAPIER_CALL r2NumSolverIterations(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::numSolverIterations. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2SetNumSolverIterations(struct R2World *world, - size_t value); +RAPIER_API R2Status RAPIER_CALL r2SetNumSolverIterations(struct R2World *world, size_t value); /** * Return the world setting documented by R2IntegrationParameters::numInternalPgsIterations. * @ingroup worlds */ -RAPIER_API RAPIER_CALL size_t r2NumInternalPgsIterations(const struct R2World *world); +RAPIER_API size_t RAPIER_CALL r2NumInternalPgsIterations(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::numInternalPgsIterations. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2SetNumInternalPgsIterations(struct R2World *world, - size_t value); +RAPIER_API R2Status RAPIER_CALL r2SetNumInternalPgsIterations(struct R2World *world, size_t value); /** * Return the world setting documented by * R2IntegrationParameters::numInternalStabilizationIterations. * @ingroup errors */ -RAPIER_API RAPIER_CALL -size_t r2NumInternalStabilizationIterations(const struct R2World *world); +RAPIER_API size_t RAPIER_CALL r2NumInternalStabilizationIterations(const struct R2World *world); /** * Set the world setting documented by * R2IntegrationParameters::numInternalStabilizationIterations. * @ingroup errors */ -RAPIER_API RAPIER_CALL -R2Status r2SetNumInternalStabilizationIterations(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2SetNumInternalStabilizationIterations(struct R2World *world, size_t value); /** * Return the world setting documented by R2IntegrationParameters::maxCcdSubsteps. * @ingroup worlds */ -RAPIER_API RAPIER_CALL size_t r2MaxCcdSubsteps(const struct R2World *world); +RAPIER_API size_t RAPIER_CALL r2MaxCcdSubsteps(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::maxCcdSubsteps. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2SetMaxCcdSubsteps(struct R2World *world, size_t value); +RAPIER_API R2Status RAPIER_CALL r2SetMaxCcdSubsteps(struct R2World *world, size_t value); /** * Return the world setting documented by R2IntegrationParameters::contactClustering. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Bool r2ContactClustering(const struct R2World *world); +RAPIER_API R2Bool RAPIER_CALL r2ContactClustering(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::contactClustering. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2SetContactClustering(struct R2World *world, R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2SetContactClustering(struct R2World *world, R2Bool value); /** * Return the world setting documented by R2IntegrationParameters::contactRecycling. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Bool r2ContactRecycling(const struct R2World *world); +RAPIER_API R2Bool RAPIER_CALL r2ContactRecycling(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::contactRecycling. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2SetContactRecycling(struct R2World *world, R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2SetContactRecycling(struct R2World *world, R2Bool value); /** * Return the world setting documented by R2IntegrationParameters::frictionInBiasPass. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Bool r2FrictionInBiasPass(const struct R2World *world); +RAPIER_API R2Bool RAPIER_CALL r2FrictionInBiasPass(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::frictionInBiasPass. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2SetFrictionInBiasPass(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2SetFrictionInBiasPass(struct R2World *world, R2Bool value); /** * Return the world setting documented by R2IntegrationParameters::warmstartJoints. * @ingroup joints */ -RAPIER_API RAPIER_CALL R2Bool r2WarmstartJoints(const struct R2World *world); +RAPIER_API R2Bool RAPIER_CALL r2WarmstartJoints(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::warmstartJoints. * @ingroup joints */ -RAPIER_API RAPIER_CALL R2Status r2SetWarmstartJoints(struct R2World *world, R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2SetWarmstartJoints(struct R2World *world, R2Bool value); /** * Return the world setting documented by R2IntegrationParameters::contactSoftness. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SpringCoefficients r2ContactSoftness(const struct R2World *world); +RAPIER_API struct R2SpringCoefficients RAPIER_CALL r2ContactSoftness(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::contactSoftness. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SetContactSoftness(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2SetContactSoftness(struct R2World *world, struct R2SpringCoefficients value); /** * Return the world setting documented by R2IntegrationParameters::staticContactSoftness. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SpringCoefficients r2StaticContactSoftness(const struct R2World *world); +RAPIER_API struct R2SpringCoefficients RAPIER_CALL r2StaticContactSoftness(const struct R2World *world); /** * Set the world setting documented by R2IntegrationParameters::staticContactSoftness. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SetStaticContactSoftness(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2SetStaticContactSoftness(struct R2World *world, struct R2SpringCoefficients value); /** * Applies Rapier's persistent one-way platform logic to the borrowed manifold. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2ContactModificationContext_UpdateAsOnewayPlatform(struct R2ContactModificationContext *context, +RAPIER_API +R2Status RAPIER_CALL r2ContactModificationContext_UpdateAsOnewayPlatform(struct R2ContactModificationContext *context, struct R2Vector allowed_local_n1, R2Real allowed_angle); @@ -5098,36 +5063,36 @@ R2Status r2ContactModificationContext_UpdateAsOnewayPlatform(struct R2ContactMod * Sets the tangent velocity of every rigid solver contact in this manifold. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2ContactModificationContext_SetTangentVelocity(struct R2ContactModificationContext *context, +RAPIER_API +R2Status RAPIER_CALL r2ContactModificationContext_SetTangentVelocity(struct R2ContactModificationContext *context, struct R2Vector velocity); /** * Allocate an empty event collector; release it with r2FreeEventCollector. * @ingroup events */ -RAPIER_API RAPIER_CALL struct R2EventCollector *r2NewEventCollector(void); +RAPIER_API struct R2EventCollector *RAPIER_CALL r2NewEventCollector(void); /** * Release an owned event collector. NULL is allowed. Do not pass borrowed pointers or free the * object twice. * @ingroup events */ -RAPIER_API RAPIER_CALL R2Status r2FreeEventCollector(struct R2EventCollector *events); +RAPIER_API R2Status RAPIER_CALL r2FreeEventCollector(struct R2EventCollector *events); /** * Discard all collected events. Does not change the world. * @ingroup events */ -RAPIER_API RAPIER_CALL R2Status r2EventCollector_Clear(struct R2EventCollector *events); +RAPIER_API R2Status RAPIER_CALL r2EventCollector_Clear(struct R2EventCollector *events); /** * Copy the collected collision start/stop events without removing them. * @see @ref output_buffers * @ingroup events */ -RAPIER_API RAPIER_CALL -size_t r2EventCollector_CollisionEvents(const struct R2EventCollector *events, +RAPIER_API +size_t RAPIER_CALL r2EventCollector_CollisionEvents(const struct R2EventCollector *events, struct R2CollisionEvent *buffer, size_t capacity); @@ -5136,8 +5101,8 @@ size_t r2EventCollector_CollisionEvents(const struct R2EventCollector *events, * @see @ref output_buffers * @ingroup events */ -RAPIER_API RAPIER_CALL -size_t r2EventCollector_ContactForceEvents(const struct R2EventCollector *events, +RAPIER_API +size_t RAPIER_CALL r2EventCollector_ContactForceEvents(const struct R2EventCollector *events, struct R2ContactForceEvent *buffer, size_t capacity); @@ -5145,37 +5110,36 @@ size_t r2EventCollector_ContactForceEvents(const struct R2EventCollector *events * Return the number of queued soft-body tear events. * @ingroup events */ -RAPIER_API RAPIER_CALL -size_t r2EventCollector_TearEventCount(const struct R2EventCollector *events); +RAPIER_API size_t RAPIER_CALL r2EventCollector_TearEventCount(const struct R2EventCollector *events); /** * Return an owned copy of a queued tear event; release with r2FreeSoftBodyTearEvent. Does * not remove the queued event. * @ingroup events */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyTearEvent *r2EventCollector_TearEvent(const struct R2EventCollector *events, +RAPIER_API +struct R2SoftBodyTearEvent *RAPIER_CALL r2EventCollector_TearEvent(const struct R2EventCollector *events, size_t index); /** * Return the world-space gravitational acceleration. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R2Vector r2Gravity(const struct R2World *world); +RAPIER_API struct R2Vector RAPIER_CALL r2Gravity(const struct R2World *world); /** * Set the world-space gravitational acceleration. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2SetGravity(struct R2World *world, struct R2Vector value); +RAPIER_API R2Status RAPIER_CALL r2SetGravity(struct R2World *world, struct R2Vector value); /** * Hooks and events may be NULL. This call invalidates all borrowed set-element pointers. * Advance simulation by one timestep. Hooks and events may be NULL. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2Step(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2Step(struct R2World *world, const struct R2PhysicsHooks *hooks, const struct R2EventCollector *events); @@ -5183,8 +5147,8 @@ R2Status r2Step(struct R2World *world, * Refresh collision detection without advancing simulation. Hooks and events may be NULL. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R2Status r2DetectCollisions(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2DetectCollisions(struct R2World *world, const struct R2PhysicsHooks *hooks, const struct R2EventCollector *events); @@ -5193,36 +5157,36 @@ R2Status r2DetectCollisions(struct R2World *world, * pointer. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R2ByteView r2Bytes_Data(const struct R2Bytes *bytes); +RAPIER_API struct R2ByteView RAPIER_CALL r2Bytes_Data(const struct R2Bytes *bytes); /** * Release an owned snapshot byte buffer. NULL is allowed. Do not pass borrowed pointers or free * the object twice. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2FreeBytes(struct R2Bytes *bytes); +RAPIER_API R2Status RAPIER_CALL r2FreeBytes(struct R2Bytes *bytes); /** * Return owned snapshot bytes; release them with r2FreeBytes. See @ref snapshots for * restoration and handle lifetimes. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R2Bytes *r2SerializeWorld(const struct R2World *world); +RAPIER_API struct R2Bytes *RAPIER_CALL r2SerializeWorld(const struct R2World *world); /** * Restore ONLY trusted snapshots produced by the identical Rapier build. Snapshots are not a * stable file format. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R2World *r2DeserializeWorld(const uint8_t *data, size_t count); +RAPIER_API struct R2World *RAPIER_CALL r2DeserializeWorld(const uint8_t *data, size_t count); /** * Color is HSLA (hue in degrees), matching Rapier DebugColor. mode uses DebugRenderMode bits. * @see @ref output_buffers * @ingroup worlds */ -RAPIER_API RAPIER_CALL -size_t r2DebugRender(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2DebugRender(const struct R2World *world, uint32_t mode, struct R2DebugLine *buffer, size_t capacity); @@ -5231,234 +5195,200 @@ size_t r2DebugRender(const struct R2World *world, * Set the world setting documented by R2SoftBodiesSettings::resweepStrain. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodiesSetResweepStrain(struct R2World *world, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2SoftBodiesSetResweepStrain(struct R2World *world, R2Real value); /** * Return the world setting documented by R2SoftBodiesSettings::resweepStrain. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Real r2SoftBodiesResweepStrain(const struct R2World *world); +RAPIER_API R2Real RAPIER_CALL r2SoftBodiesResweepStrain(const struct R2World *world); /** * Set the world setting documented by R2SoftBodiesSettings::contactStiffening. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodiesSetContactStiffening(struct R2World *world, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2SoftBodiesSetContactStiffening(struct R2World *world, R2Real value); /** * Return the world setting documented by R2SoftBodiesSettings::contactStiffening. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Real r2SoftBodiesContactStiffening(const struct R2World *world); +RAPIER_API R2Real RAPIER_CALL r2SoftBodiesContactStiffening(const struct R2World *world); /** * Set the world setting documented by R2SoftBodiesSettings::maxExtraSubsteps. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodiesSetMaxExtraSubsteps(struct R2World *world, - size_t value); +RAPIER_API R2Status RAPIER_CALL r2SoftBodiesSetMaxExtraSubsteps(struct R2World *world, size_t value); /** * Return the world setting documented by R2SoftBodiesSettings::maxExtraSubsteps. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL size_t r2SoftBodiesMaxExtraSubsteps(const struct R2World *world); +RAPIER_API size_t RAPIER_CALL r2SoftBodiesMaxExtraSubsteps(const struct R2World *world); /** * Set the world setting documented by R2SoftRecoverySettings::authoredVelocityMargin. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetAuthoredVelocityMargin(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2RecoverySetAuthoredVelocityMargin(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::edgeSpeculation. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetEdgeSpeculation(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetEdgeSpeculation(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::invertedCellDetection. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetInvertedCellDetection(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2RecoverySetInvertedCellDetection(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::selfCrossingDetection. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetSelfCrossingDetection(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2RecoverySetSelfCrossingDetection(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::detectionMotionGating. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetDetectionMotionGating(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2RecoverySetDetectionMotionGating(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::crossBodyDetection. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetCrossBodyDetection(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetCrossBodyDetection(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::selfStandDown. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetSelfStandDown(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetSelfStandDown(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::crossBodyExpelGate. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetCrossBodyExpelGate(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetCrossBodyExpelGate(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::edgeStandDown. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetEdgeStandDown(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetEdgeStandDown(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::crossingRepulsion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetCrossingRepulsion(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetCrossingRepulsion(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::crossingRepulsionGuide. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetCrossingRepulsionGuide(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2RecoverySetCrossingRepulsionGuide(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::crossingRepulsionSelfGuide. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetCrossingRepulsionSelfGuide(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2RecoverySetCrossingRepulsionSelfGuide(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::recoveryPace. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetRecoveryPace(struct R2World *world, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetRecoveryPace(struct R2World *world, R2Real value); /** * Set the world setting documented by R2SoftRecoverySettings::overlapConstraints. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetOverlapConstraints(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetOverlapConstraints(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::overlapRigid. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetOverlapRigid(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetOverlapRigid(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::overlapSkipSelfTangled. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetOverlapSkipSelfTangled(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2RecoverySetOverlapSkipSelfTangled(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::overlapEdgeStandDown. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetOverlapEdgeStandDown(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2RecoverySetOverlapEdgeStandDown(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::overlapConstraintPace. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetOverlapConstraintPace(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2RecoverySetOverlapConstraintPace(struct R2World *world, R2Real value); /** * Set the world setting documented by R2SoftRecoverySettings::overlapSkinVolume. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetOverlapSkinVolume(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetOverlapSkinVolume(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::overlapKeptDepth. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetOverlapKeptDepth(struct R2World *world, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetOverlapKeptDepth(struct R2World *world, R2Real value); /** * Set the world setting documented by R2SoftRecoverySettings::overlapSelfRegions. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetOverlapSelfRegions(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetOverlapSelfRegions(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::overlapNormalPush. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetOverlapNormalPush(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetOverlapNormalPush(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::overlapMultiVolume. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetOverlapMultiVolume(struct R2World *world, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RecoverySetOverlapMultiVolume(struct R2World *world, R2Bool value); /** * Set the world setting documented by R2SoftRecoverySettings::overlapProgressMargin. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RecoverySetOverlapProgressMargin(struct R2World *world, +RAPIER_API +R2Status RAPIER_CALL r2RecoverySetOverlapProgressMargin(struct R2World *world, R2Real value); #if defined(RAPIER_FEM) @@ -5466,9 +5396,7 @@ R2Status r2RecoverySetOverlapProgressMargin(struct R2World *world, * Set the world setting documented by R2SoftFemParameters::linearTolerance. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2FemSetLinearTolerance(struct R2World *world, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2FemSetLinearTolerance(struct R2World *world, R2Real value); #endif #if defined(RAPIER_FEM) @@ -5476,9 +5404,7 @@ R2Status r2FemSetLinearTolerance(struct R2World *world, * Set the world setting documented by R2SoftFemParameters::maxLinearIterations. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2FemSetMaxLinearIterations(struct R2World *world, - size_t value); +RAPIER_API R2Status RAPIER_CALL r2FemSetMaxLinearIterations(struct R2World *world, size_t value); #endif #if defined(RAPIER_FEM) @@ -5486,7 +5412,7 @@ R2Status r2FemSetMaxLinearIterations(struct R2World *world, * Set the world setting documented by R2SoftFemParameters::maxDenseDofs. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Status r2FemSetMaxDenseDofs(struct R2World *world, size_t value); +RAPIER_API R2Status RAPIER_CALL r2FemSetMaxDenseDofs(struct R2World *world, size_t value); #endif /** @@ -5496,7 +5422,7 @@ RAPIER_API RAPIER_CALL R2Status r2FemSetMaxDenseDofs(struct R2World *world, size * pool when constructing the new one fails. The pool is not included in snapshots. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2SetNumThreads(struct R2World *world, size_t num_threads); +RAPIER_API R2Status RAPIER_CALL r2SetNumThreads(struct R2World *world, size_t num_threads); /** * Removes the world's dedicated pool. A parallel build then uses the calling @@ -5504,21 +5430,21 @@ RAPIER_API RAPIER_CALL R2Status r2SetNumThreads(struct R2World *world, size_t nu * Returns R2_UNSUPPORTED in a build without the parallel feature. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2ClearThreadPool(struct R2World *world); +RAPIER_API R2Status RAPIER_CALL r2ClearThreadPool(struct R2World *world); /** * Size of the world's dedicated pool, or zero if a parallel build has no dedicated * pool configured. Returns one for a build without the parallel feature. * @ingroup worlds */ -RAPIER_API RAPIER_CALL size_t r2NumThreads(const struct R2World *world); +RAPIER_API size_t RAPIER_CALL r2NumThreads(const struct R2World *world); /** * Enable or disable the native pipeline profiling counters. Enabling returns * R2_UNSUPPORTED if the library was built without the profiler feature. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2SetCountersEnabled(struct R2World *world, R2Bool enabled); +RAPIER_API R2Status RAPIER_CALL r2SetCountersEnabled(struct R2World *world, R2Bool enabled); /** * Native engine time of the most recent step, in milliseconds, as in the Rust testbed. @@ -5526,64 +5452,63 @@ RAPIER_API RAPIER_CALL R2Status r2SetCountersEnabled(struct R2World *world, R2Bo * and dispatch into a dedicated thread pool; remains unchanged while paused. * @ingroup worlds */ -RAPIER_API RAPIER_CALL double r2StepTimeMs(const struct R2World *world); +RAPIER_API double RAPIER_CALL r2StepTimeMs(const struct R2World *world); /** * Read ONLY trusted legacy rigid-world snapshots from debug_deserialize3.rs, * produced by the identical Rapier build. This is not a stable interchange format. + * * Import trusted legacy Rust testbed rigid-state bytes into a new owned world. Release with * r2FreeWorld; see @ref snapshots. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -struct R2World *r2DeserializeRigidState(const uint8_t *data, - size_t count); +RAPIER_API struct R2World *RAPIER_CALL r2DeserializeRigidState(const uint8_t *data, size_t count); /** * Return native default query filter. This POD value owns no resources. * @ingroup queries */ -RAPIER_API RAPIER_CALL struct R2QueryFilter r2DefaultQueryFilter(void); +RAPIER_API struct R2QueryFilter RAPIER_CALL r2DefaultQueryFilter(void); /** * Return native default shape cast options. This POD value owns no resources. * @ingroup queries */ -RAPIER_API RAPIER_CALL struct R2ShapeCastOptions r2DefaultShapeCastOptions(void); +RAPIER_API struct R2ShapeCastOptions RAPIER_CALL r2DefaultShapeCastOptions(void); /** * Remove a soft body and its associated simulation objects. Invalidates its handle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Status r2RemoveSoftBody(struct R2SoftBodyHandle handle); +RAPIER_API R2Status RAPIER_CALL r2RemoveSoftBody(struct R2SoftBodyHandle handle); /** * Wake the soft body and its rigid proxies. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Status r2SoftBody_WakeUp(struct R2SoftBodyHandle handle); +RAPIER_API R2Status RAPIER_CALL r2SoftBody_WakeUp(struct R2SoftBodyHandle handle); /** * Release an owned soft body tear event. NULL is allowed. Do not pass borrowed pointers or free * the object twice. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Status r2FreeSoftBodyTearEvent(struct R2SoftBodyTearEvent *event); +RAPIER_API R2Status RAPIER_CALL r2FreeSoftBodyTearEvent(struct R2SoftBodyTearEvent *event); /** * Return the source soft-body handle for this tear event. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyHandle r2SoftBodyTearEvent_SoftBody(const struct R2SoftBodyTearEvent *event); +RAPIER_API +struct R2SoftBodyHandle RAPIER_CALL r2SoftBodyTearEvent_SoftBody(const struct R2SoftBodyTearEvent *event); /** * Copy the soft-body handles produced by the tear. * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyTearEvent_Bodies(const struct R2SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyTearEvent_Bodies(const struct R2SoftBodyTearEvent *event, struct R2SoftBodyHandle *buffer, size_t capacity); @@ -5591,8 +5516,8 @@ size_t r2SoftBodyTearEvent_Bodies(const struct R2SoftBodyTearEvent *event, * Return the destination body and particle index for an original particle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2ParticleDestination r2SoftBodyTearEvent_ParticleDestination(const struct R2SoftBodyTearEvent *event, +RAPIER_API +struct R2ParticleDestination RAPIER_CALL r2SoftBodyTearEvent_ParticleDestination(const struct R2SoftBodyTearEvent *event, uint32_t particle); /** @@ -5600,8 +5525,8 @@ struct R2ParticleDestination r2SoftBodyTearEvent_ParticleDestination(const struc * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyTearEvent_TornEdges(const struct R2SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyTearEvent_TornEdges(const struct R2SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); @@ -5610,8 +5535,8 @@ size_t r2SoftBodyTearEvent_TornEdges(const struct R2SoftBodyTearEvent *event, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyTearEvent_TornCells(const struct R2SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyTearEvent_TornCells(const struct R2SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); @@ -5620,8 +5545,8 @@ size_t r2SoftBodyTearEvent_TornCells(const struct R2SoftBodyTearEvent *event, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyTearEvent_RemovedEdges(const struct R2SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyTearEvent_RemovedEdges(const struct R2SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); @@ -5630,8 +5555,8 @@ size_t r2SoftBodyTearEvent_RemovedEdges(const struct R2SoftBodyTearEvent *event, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyTearEvent_SplitParticles(const struct R2SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyTearEvent_SplitParticles(const struct R2SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); @@ -5640,8 +5565,8 @@ size_t r2SoftBodyTearEvent_SplitParticles(const struct R2SoftBodyTearEvent *even * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyTearEvent_InsertedParticles(const struct R2SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyTearEvent_InsertedParticles(const struct R2SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); @@ -5650,8 +5575,8 @@ size_t r2SoftBodyTearEvent_InsertedParticles(const struct R2SoftBodyTearEvent *e * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyTearEvent_PieceParticles(const struct R2SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyTearEvent_PieceParticles(const struct R2SoftBodyTearEvent *event, size_t piece_index, uint32_t *buffer, size_t capacity); @@ -5661,8 +5586,8 @@ size_t r2SoftBodyTearEvent_PieceParticles(const struct R2SoftBodyTearEvent *even * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyTearEvent_Clusters(const struct R2SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyTearEvent_Clusters(const struct R2SoftBodyTearEvent *event, struct R2SoftClusterSplit *buffer, size_t capacity); @@ -5671,8 +5596,8 @@ size_t r2SoftBodyTearEvent_Clusters(const struct R2SoftBodyTearEvent *event, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyTearEvent_MovedJoints(const struct R2SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyTearEvent_MovedJoints(const struct R2SoftBodyTearEvent *event, struct R2SoftJointMove *buffer, size_t capacity); @@ -5681,8 +5606,8 @@ size_t r2SoftBodyTearEvent_MovedJoints(const struct R2SoftBodyTearEvent *event, * r2FreeSoftBodyTearEvent. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyTearEvent *r2SoftBody_Tear(struct R2SoftBodyHandle handle, +RAPIER_API +struct R2SoftBodyTearEvent *RAPIER_CALL r2SoftBody_Tear(struct R2SoftBodyHandle handle, const uint32_t *edges, size_t edge_count, const uint32_t *cells, @@ -5692,8 +5617,8 @@ struct R2SoftBodyTearEvent *r2SoftBody_Tear(struct R2SoftBodyHandle handle, * Create a rigid proxy cluster from the supplied particle indices and return its cluster index. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -uint32_t r2SoftBody_AddCluster(struct R2SoftBodyHandle handle, +RAPIER_API +uint32_t RAPIER_CALL r2SoftBody_AddCluster(struct R2SoftBodyHandle handle, const uint32_t *particles, size_t count); @@ -5701,8 +5626,8 @@ uint32_t r2SoftBody_AddCluster(struct R2SoftBodyHandle handle, * Remove the selected cluster and its rigid proxy. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_RemoveCluster(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_RemoveCluster(struct R2SoftBodyHandle handle, uint32_t cluster); /** @@ -5710,8 +5635,8 @@ R2Status r2SoftBody_RemoveCluster(struct R2SoftBodyHandle handle, * found to false; body/index are only written when a destination exists. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2OptionalParticleDestination r2SoftBodyTearEvent_TryParticleDestination(const struct R2SoftBodyTearEvent *event, +RAPIER_API +struct R2OptionalParticleDestination RAPIER_CALL r2SoftBodyTearEvent_TryParticleDestination(const struct R2SoftBodyTearEvent *event, uint32_t particle); /** @@ -5719,8 +5644,8 @@ struct R2OptionalParticleDestination r2SoftBodyTearEvent_TryParticleDestination( * The optional owned event must be freed with FreeSoftBodyTearEvent. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyTearEvent *r2CutSoftBody(struct R2SoftBodyHandle handle, +RAPIER_API +struct R2SoftBodyTearEvent *RAPIER_CALL r2CutSoftBody(struct R2SoftBodyHandle handle, const struct R2Vector *blade); /** @@ -5728,14 +5653,13 @@ struct R2SoftBodyTearEvent *r2CutSoftBody(struct R2SoftBodyHandle handle, * destructor. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -struct R2VolumeMeshParameters r2NewVolumeMeshParameters(R2Real cell_size); +RAPIER_API struct R2VolumeMeshParameters RAPIER_CALL r2NewVolumeMeshParameters(R2Real cell_size); /** * Return ABI version, dimension, scalar size, and pointer size of the linked library. * @ingroup errors */ -RAPIER_API RAPIER_CALL struct R2BuildInfo r2BuildInfo(void); +RAPIER_API struct R2BuildInfo RAPIER_CALL r2BuildInfo(void); /** * Release version of the loaded C bindings, e.g. "0.35.3+c.2". @@ -5744,7 +5668,7 @@ RAPIER_API RAPIER_CALL struct R2BuildInfo r2BuildInfo(void); * This release identifier is independent of the ABI compatibility version. * @ingroup errors */ -RAPIER_API RAPIER_CALL const char *r2Version(void); +RAPIER_API const char *RAPIER_CALL r2Version(void); /** * Cargo profile of the loaded physics library: "debug" or "release". @@ -5753,21 +5677,21 @@ RAPIER_API RAPIER_CALL const char *r2Version(void); * This is independent of the consumer's build mode and of per-package optimization overrides. * @ingroup errors */ -RAPIER_API RAPIER_CALL const char *r2BuildProfile(void); +RAPIER_API const char *RAPIER_CALL r2BuildProfile(void); /** * Return profiling, SIMD width, and parallelism of the linked library. * @ingroup errors */ -RAPIER_API RAPIER_CALL struct R2BuildFeatures r2BuildFeatures(void); +RAPIER_API struct R2BuildFeatures RAPIER_CALL r2BuildFeatures(void); /** * Create an owned heightfield shape from copied samples. 3D samples are column-major, with rows * * columns entries. Release with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2HeightfieldSharedShape(struct R2RealView heights, +RAPIER_API +R2SharedShape *RAPIER_CALL r2HeightfieldSharedShape(struct R2RealView heights, size_t rows, size_t columns, struct R2Vector scale); @@ -5776,24 +5700,24 @@ R2SharedShape *r2HeightfieldSharedShape(struct R2RealView heights, * Compute the shape axis-aligned bounds at the supplied world-space pose. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -struct R2Aabb r2SharedShape_ComputeAabb(const R2SharedShape *shape, +RAPIER_API +struct R2Aabb RAPIER_CALL r2SharedShape_ComputeAabb(const R2SharedShape *shape, struct R2Pose pose); /** * Compute local mass properties for the supplied nonnegative density. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -struct R2MassProperties r2SharedShape_MassProperties(const R2SharedShape *shape, +RAPIER_API +struct R2MassProperties RAPIER_CALL r2SharedShape_MassProperties(const R2SharedShape *shape, R2Real density); /** * Test whether the world-space point lies inside the shape at pose. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2Bool r2SharedShape_ContainsPoint(const R2SharedShape *shape, +RAPIER_API +R2Bool RAPIER_CALL r2SharedShape_ContainsPoint(const R2SharedShape *shape, struct R2Pose pose, struct R2Vector point); @@ -5802,8 +5726,8 @@ R2Bool r2SharedShape_ContainsPoint(const R2SharedShape *shape, * @see @ref output_buffers * @ingroup events */ -RAPIER_API RAPIER_CALL -size_t r2ContactPairs(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2ContactPairs(const struct R2World *world, struct R2ContactPair *buffer, size_t capacity); @@ -5811,8 +5735,8 @@ size_t r2ContactPairs(const struct R2World *world, * Return the narrow-phase contact pair for two colliders, or report R2_NOT_FOUND. * @ingroup events */ -RAPIER_API RAPIER_CALL -struct R2ContactPair r2ContactPair(struct R2ColliderHandle collider1, +RAPIER_API +struct R2ContactPair RAPIER_CALL r2ContactPair(struct R2ColliderHandle collider1, struct R2ColliderHandle collider2); /** @@ -5820,8 +5744,8 @@ struct R2ContactPair r2ContactPair(struct R2ColliderHandle collider1, * @see @ref output_buffers * @ingroup events */ -RAPIER_API RAPIER_CALL -size_t r2IntersectionPairs(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2IntersectionPairs(const struct R2World *world, struct R2IntersectionPair *buffer, size_t capacity); @@ -5832,8 +5756,8 @@ size_t r2IntersectionPairs(const struct R2World *world, * @see @ref output_buffers * @ingroup worlds */ -RAPIER_API RAPIER_CALL -size_t r2ContactPoints(struct R2ColliderHandle collider1, +RAPIER_API +size_t RAPIER_CALL r2ContactPoints(struct R2ColliderHandle collider1, struct R2ColliderHandle collider2, struct R2ContactPoint *buffer, size_t capacity); @@ -5843,8 +5767,8 @@ size_t r2ContactPoints(struct R2ColliderHandle collider1, * @see @ref output_buffers * @ingroup joints */ -RAPIER_API RAPIER_CALL -size_t r2MultibodyJoint_GeneralizedVelocity(struct R2MultibodyJointHandle handle, +RAPIER_API +size_t RAPIER_CALL r2MultibodyJoint_GeneralizedVelocity(struct R2MultibodyJointHandle handle, R2Real *buffer, size_t capacity); @@ -5852,8 +5776,8 @@ size_t r2MultibodyJoint_GeneralizedVelocity(struct R2MultibodyJointHandle handle * Replace articulation generalized velocities; the array length must match its degrees of freedom. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2MultibodyJoint_SetGeneralizedVelocity(struct R2MultibodyJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2MultibodyJoint_SetGeneralizedVelocity(struct R2MultibodyJointHandle handle, const R2Real *values, size_t count); @@ -5861,8 +5785,8 @@ R2Status r2MultibodyJoint_SetGeneralizedVelocity(struct R2MultibodyJointHandle h * Check this before passing any dimension/precision-dependent structs across the ABI. * @ingroup errors */ -RAPIER_API RAPIER_CALL -R2Status r2CheckAbi(uint32_t version, +RAPIER_API +R2Status RAPIER_CALL r2CheckAbi(uint32_t version, uint32_t dimension, size_t real_size, size_t vector_size, @@ -5873,8 +5797,8 @@ R2Status r2CheckAbi(uint32_t version, * controls curved-shape resolution. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -struct R2ShapeMesh *r2SharedShape_Tessellate(const R2SharedShape *shape, +RAPIER_API +struct R2ShapeMesh *RAPIER_CALL r2SharedShape_Tessellate(const R2SharedShape *shape, uint32_t subdivisions); /** @@ -5882,8 +5806,8 @@ struct R2ShapeMesh *r2SharedShape_Tessellate(const R2SharedShape *shape, * @see @ref output_buffers * @ingroup shapes */ -RAPIER_API RAPIER_CALL -size_t r2ShapeMesh_Triangles(const struct R2ShapeMesh *mesh, +RAPIER_API +size_t RAPIER_CALL r2ShapeMesh_Triangles(const struct R2ShapeMesh *mesh, struct R2Vector *buffer, size_t capacity); @@ -5892,8 +5816,8 @@ size_t r2ShapeMesh_Triangles(const struct R2ShapeMesh *mesh, * @see @ref output_buffers * @ingroup shapes */ -RAPIER_API RAPIER_CALL -size_t r2ShapeMesh_Lines(const struct R2ShapeMesh *mesh, +RAPIER_API +size_t RAPIER_CALL r2ShapeMesh_Lines(const struct R2ShapeMesh *mesh, struct R2Vector *buffer, size_t capacity); @@ -5902,15 +5826,15 @@ size_t r2ShapeMesh_Lines(const struct R2ShapeMesh *mesh, * twice. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R2Status r2FreeShapeMesh(struct R2ShapeMesh *mesh); +RAPIER_API R2Status RAPIER_CALL r2FreeShapeMesh(struct R2ShapeMesh *mesh); #if defined(RAPIER_DIM3) /** * Create an owned round cylinder shape. Release it with r2FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2RoundCylinderSharedShape(R2Real half_height, +RAPIER_API +R2SharedShape *RAPIER_CALL r2RoundCylinderSharedShape(R2Real half_height, R2Real radius, R2Real border_radius); #endif @@ -5921,8 +5845,8 @@ R2SharedShape *r2RoundCylinderSharedShape(R2Real half_height, * Cuboids, cones, cylinders, convex polyhedra, trimeshes, and heightfields are also supported. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -struct R2TriMeshData *r2SharedShape_ToTrimesh(const R2SharedShape *shape, +RAPIER_API +struct R2TriMeshData *RAPIER_CALL r2SharedShape_ToTrimesh(const R2SharedShape *shape, uint32_t ntheta, uint32_t nphi); #endif @@ -5933,8 +5857,8 @@ struct R2TriMeshData *r2SharedShape_ToTrimesh(const R2SharedShape *shape, * @see @ref output_buffers * @ingroup shapes */ -RAPIER_API RAPIER_CALL -size_t r2TriMeshData_Vertices(const struct R2TriMeshData *mesh, +RAPIER_API +size_t RAPIER_CALL r2TriMeshData_Vertices(const struct R2TriMeshData *mesh, struct R2Vector *buffer, size_t capacity); #endif @@ -5945,8 +5869,8 @@ size_t r2TriMeshData_Vertices(const struct R2TriMeshData *mesh, * @see @ref output_buffers * @ingroup shapes */ -RAPIER_API RAPIER_CALL -size_t r2TriMeshData_Indices(const struct R2TriMeshData *mesh, +RAPIER_API +size_t RAPIER_CALL r2TriMeshData_Indices(const struct R2TriMeshData *mesh, uint32_t *buffer, size_t capacity); #endif @@ -5957,7 +5881,7 @@ size_t r2TriMeshData_Indices(const struct R2TriMeshData *mesh, * object twice. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R2Status r2FreeTriMeshData(struct R2TriMeshData *mesh); +RAPIER_API R2Status RAPIER_CALL r2FreeTriMeshData(struct R2TriMeshData *mesh); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -5965,7 +5889,7 @@ RAPIER_API RAPIER_CALL R2Status r2FreeTriMeshData(struct R2TriMeshData *mesh); * Return native default urdf loader options. This POD value owns no resources. * @ingroup robotics */ -RAPIER_API RAPIER_CALL struct R2UrdfLoaderOptions r2DefaultUrdfLoaderOptions(void); +RAPIER_API struct R2UrdfLoaderOptions RAPIER_CALL r2DefaultUrdfLoaderOptions(void); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -5974,7 +5898,7 @@ RAPIER_API RAPIER_CALL struct R2UrdfLoaderOptions r2DefaultUrdfLoaderOptions(voi * twice. * @ingroup robotics */ -RAPIER_API RAPIER_CALL R2Status r2FreeUrdfRobot(struct R2UrdfRobot *object); +RAPIER_API R2Status RAPIER_CALL r2FreeUrdfRobot(struct R2UrdfRobot *object); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -5983,8 +5907,8 @@ RAPIER_API RAPIER_CALL R2Status r2FreeUrdfRobot(struct R2UrdfRobot *object); * Options and their blueprint resources are borrowed through this call; the robot is owned. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R2UrdfRobot *r2UrdfRobotFromFile(const char *path, +RAPIER_API +struct R2UrdfRobot *RAPIER_CALL r2UrdfRobotFromFile(const char *path, const struct R2UrdfLoaderOptions *options); #endif @@ -5993,8 +5917,8 @@ struct R2UrdfRobot *r2UrdfRobotFromFile(const char *path, * Apply an additional transform to the loaded robot before insertion. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R2Status r2UrdfRobot_AppendTransform(struct R2UrdfRobot *robot, +RAPIER_API +R2Status RAPIER_CALL r2UrdfRobot_AppendTransform(struct R2UrdfRobot *robot, struct R2Pose transform); #endif @@ -6004,7 +5928,7 @@ R2Status r2UrdfRobot_AppendTransform(struct R2UrdfRobot *robot, * object twice. * @ingroup robotics */ -RAPIER_API RAPIER_CALL R2Status r2FreeUrdfRobotHandles(struct R2UrdfRobotHandles *handles); +RAPIER_API R2Status RAPIER_CALL r2FreeUrdfRobotHandles(struct R2UrdfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6012,8 +5936,8 @@ RAPIER_API RAPIER_CALL R2Status r2FreeUrdfRobotHandles(struct R2UrdfRobotHandles * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R2UrdfRobotHandles *r2UrdfRobot_InsertUsingImpulseJoints(struct R2World *world, +RAPIER_API +struct R2UrdfRobotHandles *RAPIER_CALL r2UrdfRobot_InsertUsingImpulseJoints(struct R2World *world, const struct R2UrdfRobot *robot); #endif @@ -6022,8 +5946,8 @@ struct R2UrdfRobotHandles *r2UrdfRobot_InsertUsingImpulseJoints(struct R2World * * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R2UrdfRobotHandles *r2UrdfRobot_InsertUsingMultibodyJoints(struct R2World *world, +RAPIER_API +struct R2UrdfRobotHandles *RAPIER_CALL r2UrdfRobot_InsertUsingMultibodyJoints(struct R2World *world, const struct R2UrdfRobot *robot, uint8_t options); #endif @@ -6034,8 +5958,8 @@ struct R2UrdfRobotHandles *r2UrdfRobot_InsertUsingMultibodyJoints(struct R2World * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r2UrdfRobotHandles_Bodies(const struct R2UrdfRobotHandles *handles, +RAPIER_API +size_t RAPIER_CALL r2UrdfRobotHandles_Bodies(const struct R2UrdfRobotHandles *handles, struct R2RigidBodyHandle *buffer, size_t capacity); #endif @@ -6045,7 +5969,7 @@ size_t r2UrdfRobotHandles_Bodies(const struct R2UrdfRobotHandles *handles, * Return native default mjcf loader options. This POD value owns no resources. * @ingroup robotics */ -RAPIER_API RAPIER_CALL struct R2MjcfLoaderOptions r2DefaultMjcfLoaderOptions(void); +RAPIER_API struct R2MjcfLoaderOptions RAPIER_CALL r2DefaultMjcfLoaderOptions(void); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6054,7 +5978,7 @@ RAPIER_API RAPIER_CALL struct R2MjcfLoaderOptions r2DefaultMjcfLoaderOptions(voi * twice. * @ingroup robotics */ -RAPIER_API RAPIER_CALL R2Status r2FreeMjcfRobot(struct R2MjcfRobot *object); +RAPIER_API R2Status RAPIER_CALL r2FreeMjcfRobot(struct R2MjcfRobot *object); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6063,8 +5987,8 @@ RAPIER_API RAPIER_CALL R2Status r2FreeMjcfRobot(struct R2MjcfRobot *object); * Options and their blueprint resources are borrowed through this call; the robot is owned. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R2MjcfRobot *r2MjcfRobotFromFile(const char *path, +RAPIER_API +struct R2MjcfRobot *RAPIER_CALL r2MjcfRobotFromFile(const char *path, const struct R2MjcfLoaderOptions *options); #endif @@ -6073,8 +5997,8 @@ struct R2MjcfRobot *r2MjcfRobotFromFile(const char *path, * Apply an additional transform to the loaded robot before insertion. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R2Status r2MjcfRobot_AppendTransform(struct R2MjcfRobot *robot, +RAPIER_API +R2Status RAPIER_CALL r2MjcfRobot_AppendTransform(struct R2MjcfRobot *robot, struct R2Pose transform); #endif @@ -6084,7 +6008,7 @@ R2Status r2MjcfRobot_AppendTransform(struct R2MjcfRobot *robot, * object twice. * @ingroup robotics */ -RAPIER_API RAPIER_CALL R2Status r2FreeMjcfRobotHandles(struct R2MjcfRobotHandles *handles); +RAPIER_API R2Status RAPIER_CALL r2FreeMjcfRobotHandles(struct R2MjcfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6092,8 +6016,8 @@ RAPIER_API RAPIER_CALL R2Status r2FreeMjcfRobotHandles(struct R2MjcfRobotHandles * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R2MjcfRobotHandles *r2MjcfRobot_InsertUsingImpulseJoints(struct R2World *world, +RAPIER_API +struct R2MjcfRobotHandles *RAPIER_CALL r2MjcfRobot_InsertUsingImpulseJoints(struct R2World *world, const struct R2MjcfRobot *robot); #endif @@ -6102,8 +6026,8 @@ struct R2MjcfRobotHandles *r2MjcfRobot_InsertUsingImpulseJoints(struct R2World * * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R2MjcfRobotHandles *r2MjcfRobot_InsertUsingMultibodyJoints(struct R2World *world, +RAPIER_API +struct R2MjcfRobotHandles *RAPIER_CALL r2MjcfRobot_InsertUsingMultibodyJoints(struct R2World *world, const struct R2MjcfRobot *robot, uint8_t options); #endif @@ -6114,8 +6038,8 @@ struct R2MjcfRobotHandles *r2MjcfRobot_InsertUsingMultibodyJoints(struct R2World * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r2MjcfRobotHandles_Bodies(const struct R2MjcfRobotHandles *handles, +RAPIER_API +size_t RAPIER_CALL r2MjcfRobotHandles_Bodies(const struct R2MjcfRobotHandles *handles, struct R2RigidBodyHandle *buffer, size_t capacity); #endif @@ -6125,7 +6049,7 @@ size_t r2MjcfRobotHandles_Bodies(const struct R2MjcfRobotHandles *handles, * Resolved model gravity before the caller chooses a world convention. * @ingroup robotics */ -RAPIER_API RAPIER_CALL struct R2Vector r2MjcfRobot_Gravity(const struct R2MjcfRobot *robot); +RAPIER_API struct R2Vector RAPIER_CALL r2MjcfRobot_Gravity(const struct R2MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6133,7 +6057,7 @@ RAPIER_API RAPIER_CALL struct R2Vector r2MjcfRobot_Gravity(const struct R2MjcfRo * Return the number of source MJCF bodies. * @ingroup robotics */ -RAPIER_API RAPIER_CALL size_t r2MjcfRobot_BodyCount(const struct R2MjcfRobot *robot); +RAPIER_API size_t RAPIER_CALL r2MjcfRobot_BodyCount(const struct R2MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6141,9 +6065,7 @@ RAPIER_API RAPIER_CALL size_t r2MjcfRobot_BodyCount(const struct R2MjcfRobot *ro * Return the collider count for a source body index. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r2MjcfRobot_BodyColliderCount(const struct R2MjcfRobot *robot, - size_t body); +RAPIER_API size_t RAPIER_CALL r2MjcfRobot_BodyColliderCount(const struct R2MjcfRobot *robot, size_t body); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6151,8 +6073,8 @@ size_t r2MjcfRobot_BodyColliderCount(const struct R2MjcfRobot *robot, * Set collision groups on a collider in the loaded robot, before insertion. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R2Status r2MjcfRobot_SetBodyColliderCollisionGroups(struct R2MjcfRobot *robot, +RAPIER_API +R2Status RAPIER_CALL r2MjcfRobot_SetBodyColliderCollisionGroups(struct R2MjcfRobot *robot, size_t body, size_t collider, struct R2InteractionGroups groups); @@ -6163,7 +6085,7 @@ R2Status r2MjcfRobot_SetBodyColliderCollisionGroups(struct R2MjcfRobot *robot, * Return the number of imported keyframes. * @ingroup robotics */ -RAPIER_API RAPIER_CALL size_t r2MjcfRobot_KeyframeCount(const struct R2MjcfRobot *robot); +RAPIER_API size_t RAPIER_CALL r2MjcfRobot_KeyframeCount(const struct R2MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6172,8 +6094,8 @@ RAPIER_API RAPIER_CALL size_t r2MjcfRobot_KeyframeCount(const struct R2MjcfRobot * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r2MjcfRobot_KeyframeName(const struct R2MjcfRobot *robot, +RAPIER_API +size_t RAPIER_CALL r2MjcfRobot_KeyframeName(const struct R2MjcfRobot *robot, size_t key, char *buffer, size_t capacity); @@ -6184,8 +6106,8 @@ size_t r2MjcfRobot_KeyframeName(const struct R2MjcfRobot *robot, * Append a keyframe from the source MJCF model to the loaded robot. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R2Status r2MjcfRobot_AppendKeyframe(struct R2MjcfRobot *robot, +RAPIER_API +R2Status RAPIER_CALL r2MjcfRobot_AppendKeyframe(struct R2MjcfRobot *robot, const struct R2MjcfRobot *source, size_t key); #endif @@ -6196,8 +6118,8 @@ R2Status r2MjcfRobot_AppendKeyframe(struct R2MjcfRobot *robot, * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r2MjcfRobot_KeyframeControls(const struct R2MjcfRobot *robot, +RAPIER_API +size_t RAPIER_CALL r2MjcfRobot_KeyframeControls(const struct R2MjcfRobot *robot, size_t key, R2Real *buffer, size_t capacity); @@ -6208,8 +6130,7 @@ size_t r2MjcfRobot_KeyframeControls(const struct R2MjcfRobot *robot, * Return the number of imported actuators. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r2MjcfRobotHandles_ActuatorCount(const struct R2MjcfRobotHandles *handles); +RAPIER_API size_t RAPIER_CALL r2MjcfRobotHandles_ActuatorCount(const struct R2MjcfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6217,8 +6138,8 @@ size_t r2MjcfRobotHandles_ActuatorCount(const struct R2MjcfRobotHandles *handles * Apply the selected keyframe to the inserted robot. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R2Status r2MjcfRobotHandles_ApplyKeyframe(const struct R2MjcfRobotHandles *handles, +RAPIER_API +R2Status RAPIER_CALL r2MjcfRobotHandles_ApplyKeyframe(const struct R2MjcfRobotHandles *handles, const struct R2MjcfRobot *robot, size_t key); #endif @@ -6228,8 +6149,8 @@ R2Status r2MjcfRobotHandles_ApplyKeyframe(const struct R2MjcfRobotHandles *handl * Apply actuator controls with per-actuator scaling to the inserted robot. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R2Status r2MjcfRobotHandles_ApplyControlsScaled(const struct R2MjcfRobotHandles *handles, +RAPIER_API +R2Status RAPIER_CALL r2MjcfRobotHandles_ApplyControlsScaled(const struct R2MjcfRobotHandles *handles, const R2Real *controls, size_t count, R2Real gain); @@ -6240,9 +6161,7 @@ R2Status r2MjcfRobotHandles_ApplyControlsScaled(const struct R2MjcfRobotHandles * Return the number of visual meshes for a source body. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r2MjcfRobot_BodyVisualCount(const struct R2MjcfRobot *robot, - size_t body); +RAPIER_API size_t RAPIER_CALL r2MjcfRobot_BodyVisualCount(const struct R2MjcfRobot *robot, size_t body); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6251,8 +6170,8 @@ size_t r2MjcfRobot_BodyVisualCount(const struct R2MjcfRobot *robot, * changes; never free this pointer. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -const R2MjcfVisualMesh *r2MjcfRobot_BodyVisual(const struct R2MjcfRobot *robot, +RAPIER_API +const R2MjcfVisualMesh *RAPIER_CALL r2MjcfRobot_BodyVisual(const struct R2MjcfRobot *robot, size_t body, size_t visual); #endif @@ -6262,8 +6181,7 @@ const R2MjcfVisualMesh *r2MjcfRobot_BodyVisual(const struct R2MjcfRobot *robot, * Return a copy of visual pose, color, material, and geometry-kind flags. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R2MjcfVisualMeshInfo r2MjcfVisualMesh_Info(const R2MjcfVisualMesh *visual); +RAPIER_API struct R2MjcfVisualMeshInfo RAPIER_CALL r2MjcfVisualMesh_Info(const R2MjcfVisualMesh *visual); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6272,8 +6190,7 @@ struct R2MjcfVisualMeshInfo r2MjcfVisualMesh_Info(const R2MjcfVisualMesh *visual * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2MjcfVisualMesh_CloneShape(const R2MjcfVisualMesh *visual); +RAPIER_API R2SharedShape *RAPIER_CALL r2MjcfVisualMesh_CloneShape(const R2MjcfVisualMesh *visual); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -6282,8 +6199,8 @@ R2SharedShape *r2MjcfVisualMesh_CloneShape(const R2MjcfVisualMesh *visual); * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r2MjcfVisualMesh_Uvs(const R2MjcfVisualMesh *visual, +RAPIER_API +size_t RAPIER_CALL r2MjcfVisualMesh_Uvs(const R2MjcfVisualMesh *visual, float *buffer, size_t capacity); #endif @@ -6294,8 +6211,8 @@ size_t r2MjcfVisualMesh_Uvs(const R2MjcfVisualMesh *visual, * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r2MjcfVisualMesh_Normals(const R2MjcfVisualMesh *visual, +RAPIER_API +size_t RAPIER_CALL r2MjcfVisualMesh_Normals(const R2MjcfVisualMesh *visual, float *buffer, size_t capacity); #endif @@ -6306,8 +6223,8 @@ size_t r2MjcfVisualMesh_Normals(const R2MjcfVisualMesh *visual, * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r2MjcfVisualMesh_Texture(const R2MjcfVisualMesh *visual, +RAPIER_API +size_t RAPIER_CALL r2MjcfVisualMesh_Texture(const R2MjcfVisualMesh *visual, char *buffer, size_t capacity); #endif @@ -6316,53 +6233,51 @@ size_t r2MjcfVisualMesh_Texture(const R2MjcfVisualMesh *visual, * Return the rigid body world-space pose. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R2Pose r2RigidBody_Position(struct R2RigidBodyHandle handle); +RAPIER_API struct R2Pose RAPIER_CALL r2RigidBody_Position(struct R2RigidBodyHandle handle); /** * Return the rigid body world-space translation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R2Vector r2RigidBody_Translation(struct R2RigidBodyHandle handle); +RAPIER_API struct R2Vector RAPIER_CALL r2RigidBody_Translation(struct R2RigidBodyHandle handle); /** * Return the rigid body world-space linear velocity. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R2Vector r2RigidBody_Linvel(struct R2RigidBodyHandle handle); +RAPIER_API struct R2Vector RAPIER_CALL r2RigidBody_Linvel(struct R2RigidBodyHandle handle); /** * Return the rigid body world-space angular velocity (radians per second). * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2AngVector r2RigidBody_Angvel(struct R2RigidBodyHandle handle); +RAPIER_API R2AngVector RAPIER_CALL r2RigidBody_Angvel(struct R2RigidBodyHandle handle); /** * Return whether the rigid body is sleeping. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsSleeping(struct R2RigidBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2RigidBody_IsSleeping(struct R2RigidBodyHandle handle); /** * Return whether the rigid body is enabled. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsEnabled(struct R2RigidBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2RigidBody_IsEnabled(struct R2RigidBodyHandle handle); /** * Return the rigid body application-owned 128-bit user value. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R2UserData r2RigidBody_UserData(struct R2RigidBodyHandle handle); +RAPIER_API struct R2UserData RAPIER_CALL r2RigidBody_UserData(struct R2RigidBodyHandle handle); /** * Set the rigid body world-space pose. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetPosition(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetPosition(struct R2RigidBodyHandle handle, struct R2Pose value, R2Bool wake_up); @@ -6371,8 +6286,8 @@ R2Status r2RigidBody_SetPosition(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetTranslation(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetTranslation(struct R2RigidBodyHandle handle, struct R2Vector value, R2Bool wake_up); @@ -6381,8 +6296,8 @@ R2Status r2RigidBody_SetTranslation(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetLinvel(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetLinvel(struct R2RigidBodyHandle handle, struct R2Vector value, R2Bool wake_up); @@ -6391,8 +6306,8 @@ R2Status r2RigidBody_SetLinvel(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetAngvel(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetAngvel(struct R2RigidBodyHandle handle, R2AngVector value, R2Bool wake_up); @@ -6400,16 +6315,16 @@ R2Status r2RigidBody_SetAngvel(struct R2RigidBodyHandle handle, * Set the rigid body next kinematic world-space pose. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetNextKinematicPosition(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetNextKinematicPosition(struct R2RigidBodyHandle handle, struct R2Pose value); /** * Set the rigid body next kinematic world-space translation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetNextKinematicTranslation(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetNextKinematicTranslation(struct R2RigidBodyHandle handle, struct R2Vector value); /** @@ -6417,8 +6332,8 @@ R2Status r2RigidBody_SetNextKinematicTranslation(struct R2RigidBodyHandle handle * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetGravityScale(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetGravityScale(struct R2RigidBodyHandle handle, R2Real value, R2Bool wake_up); @@ -6426,32 +6341,30 @@ R2Status r2RigidBody_SetGravityScale(struct R2RigidBodyHandle handle, * Set the rigid body linear damping coefficient. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetLinearDamping(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetLinearDamping(struct R2RigidBodyHandle handle, R2Real value); /** * Set the rigid body angular damping coefficient. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetAngularDamping(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetAngularDamping(struct R2RigidBodyHandle handle, R2Real value); /** * Enable or disable the rigid body. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetEnabled(struct R2RigidBodyHandle handle, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2RigidBody_SetEnabled(struct R2RigidBodyHandle handle, R2Bool value); /** * Set the rigid body application-owned 128-bit user value. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetUserData(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetUserData(struct R2RigidBodyHandle handle, struct R2UserData value); /** @@ -6459,8 +6372,8 @@ R2Status r2RigidBody_SetUserData(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_ApplyImpulse(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_ApplyImpulse(struct R2RigidBodyHandle handle, struct R2Vector value, R2Bool wake_up); @@ -6469,8 +6382,8 @@ R2Status r2RigidBody_ApplyImpulse(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_ApplyImpulseAtPoint(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_ApplyImpulseAtPoint(struct R2RigidBodyHandle handle, struct R2Vector value, struct R2Vector point, R2Bool wake_up); @@ -6480,8 +6393,8 @@ R2Status r2RigidBody_ApplyImpulseAtPoint(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_AddForce(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_AddForce(struct R2RigidBodyHandle handle, struct R2Vector value, R2Bool wake_up); @@ -6490,115 +6403,106 @@ R2Status r2RigidBody_AddForce(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_ResetForces(struct R2RigidBodyHandle handle, - R2Bool wake_up); +RAPIER_API R2Status RAPIER_CALL r2RigidBody_ResetForces(struct R2RigidBodyHandle handle, R2Bool wake_up); /** * Put the body to sleep. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Status r2RigidBody_Sleep(struct R2RigidBodyHandle handle); +RAPIER_API R2Status RAPIER_CALL r2RigidBody_Sleep(struct R2RigidBodyHandle handle); /** * Return the collider world-space pose. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R2Pose r2Collider_Position(struct R2ColliderHandle handle); +RAPIER_API struct R2Pose RAPIER_CALL r2Collider_Position(struct R2ColliderHandle handle); /** * Return the collider world-space translation. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R2Vector r2Collider_Translation(struct R2ColliderHandle handle); +RAPIER_API struct R2Vector RAPIER_CALL r2Collider_Translation(struct R2ColliderHandle handle); /** * Return the collider friction coefficient. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R2Real r2Collider_Friction(struct R2ColliderHandle handle); +RAPIER_API R2Real RAPIER_CALL r2Collider_Friction(struct R2ColliderHandle handle); /** * Return the collider restitution coefficient. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R2Real r2Collider_Restitution(struct R2ColliderHandle handle); +RAPIER_API R2Real RAPIER_CALL r2Collider_Restitution(struct R2ColliderHandle handle); /** * Return whether the collider is a sensor (detects overlaps without contact forces). * @ingroup colliders */ -RAPIER_API RAPIER_CALL R2Bool r2Collider_IsSensor(struct R2ColliderHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2Collider_IsSensor(struct R2ColliderHandle handle); /** * Return the parent body handle, or an invalid handle with OK status for a standalone collider. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2RigidBodyHandle r2Collider_Parent(struct R2ColliderHandle handle); +RAPIER_API struct R2RigidBodyHandle RAPIER_CALL r2Collider_Parent(struct R2ColliderHandle handle); /** * Set the collider world-space pose. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetPosition(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetPosition(struct R2ColliderHandle handle, struct R2Pose value); /** * Set the collider world-space translation. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetTranslation(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetTranslation(struct R2ColliderHandle handle, struct R2Vector value); /** * Set the collider friction coefficient. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetFriction(struct R2ColliderHandle handle, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2Collider_SetFriction(struct R2ColliderHandle handle, R2Real value); /** * Set the collider restitution coefficient. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetRestitution(struct R2ColliderHandle handle, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2Collider_SetRestitution(struct R2ColliderHandle handle, R2Real value); /** * Enable or disable a sensor (detects overlaps without contact forces) for the collider. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetSensor(struct R2ColliderHandle handle, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2Collider_SetSensor(struct R2ColliderHandle handle, R2Bool value); /** * Set the collider collision filtering groups. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetCollisionGroups(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetCollisionGroups(struct R2ColliderHandle handle, struct R2InteractionGroups value); /** * Set the collider application-owned 128-bit user value. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetUserData(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetUserData(struct R2ColliderHandle handle, struct R2UserData value); /** * Return the world-space position of the indexed particle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2Vector r2SoftBody_ParticlePosition(struct R2SoftBodyHandle handle, +RAPIER_API +struct R2Vector RAPIER_CALL r2SoftBody_ParticlePosition(struct R2SoftBodyHandle handle, size_t index); /** @@ -6606,8 +6510,8 @@ struct R2Vector r2SoftBody_ParticlePosition(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_ParticlePositions(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_ParticlePositions(struct R2SoftBodyHandle handle, struct R2Vector *buffer, size_t capacity); @@ -6615,15 +6519,14 @@ size_t r2SoftBody_ParticlePositions(struct R2SoftBodyHandle handle, * Return a copy of the soft body material parameters. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyMaterial r2SoftBody_Material(struct R2SoftBodyHandle handle); +RAPIER_API struct R2SoftBodyMaterial RAPIER_CALL r2SoftBody_Material(struct R2SoftBodyHandle handle); /** * Set the world-space position of the indexed particle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetParticlePosition(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetParticlePosition(struct R2SoftBodyHandle handle, size_t index, struct R2Vector value); @@ -6631,8 +6534,8 @@ R2Status r2SoftBody_SetParticlePosition(struct R2SoftBodyHandle handle, * Copy material parameters into the soft body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetMaterial(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetMaterial(struct R2SoftBodyHandle handle, const struct R2SoftBodyMaterial *data); /** @@ -6640,8 +6543,8 @@ R2Status r2SoftBody_SetMaterial(struct R2SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_AddParticleForce(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_AddParticleForce(struct R2SoftBodyHandle handle, size_t index, struct R2Vector value, R2Bool wake_up); @@ -6652,8 +6555,8 @@ R2Status r2SoftBody_AddParticleForce(struct R2SoftBodyHandle handle, * NULL/0 is a size query. BUFFER_TOO_SMALL returns the required count and leaves states untouched. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -size_t r2RigidBodyReadStates(const struct R2World *world, +RAPIER_API +size_t RAPIER_CALL r2RigidBodyReadStates(const struct R2World *world, const struct R2RigidBodyHandle *handles, size_t handle_count, struct R2RigidBodyState *states, @@ -6663,16 +6566,15 @@ size_t r2RigidBodyReadStates(const struct R2World *world, * Copies joint configuration without returning a borrowed joint pointer. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R2JointDesc r2ImpulseJoint_Desc(struct R2ImpulseJointHandle handle); +RAPIER_API struct R2JointDesc RAPIER_CALL r2ImpulseJoint_Desc(struct R2ImpulseJointHandle handle); /** * Replaces configuration after validation, resetting cached limit/motor impulses. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetDesc(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetDesc(struct R2ImpulseJointHandle handle, const struct R2JointDesc *desc, R2Bool wake_up); @@ -6682,8 +6584,8 @@ R2Status r2ImpulseJoint_SetDesc(struct R2ImpulseJointHandle handle, * Geometry and flags are validated when the description is built or inserted. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2Status r2ShapeDesc_SetTrimesh(struct R2ShapeDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2ShapeDesc_SetTrimesh(struct R2ShapeDesc *desc, struct R2VectorView vertices, struct R2TriangleView indices, uint32_t flags); @@ -6694,8 +6596,8 @@ R2Status r2ShapeDesc_SetTrimesh(struct R2ShapeDesc *desc, * Geometry and flags are validated when the description is built or inserted. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2Status r2ShapeDesc_SetPolyline(struct R2ShapeDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2ShapeDesc_SetPolyline(struct R2ShapeDesc *desc, struct R2VectorView vertices, struct R2EdgeView indices, uint32_t flags); @@ -6704,24 +6606,24 @@ R2Status r2ShapeDesc_SetPolyline(struct R2ShapeDesc *desc, * Replace the shape geometry with a borrowed convex hull point cloud. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2Status r2ShapeDesc_SetConvexHull(struct R2ShapeDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2ShapeDesc_SetConvexHull(struct R2ShapeDesc *desc, struct R2VectorView vertices); /** * Select an explicit particle recipe and borrow its positions. Other fields are preserved. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetParticles(struct R2SoftBodyDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2SoftBodyDesc_SetParticles(struct R2SoftBodyDesc *desc, struct R2VectorView positions); /** * Select a surface recipe and borrow its vertices and elements. Other fields are preserved. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetSurfaceMesh(struct R2SoftBodyDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2SoftBodyDesc_SetSurfaceMesh(struct R2SoftBodyDesc *desc, struct R2VectorView vertices, R2SurfaceElementView elements); @@ -6729,8 +6631,8 @@ R2Status r2SoftBodyDesc_SetSurfaceMesh(struct R2SoftBodyDesc *desc, * Borrow skin geometry. Other fields, including skinCollision, are preserved. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetSkin(struct R2SoftBodyDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2SoftBodyDesc_SetSkin(struct R2SoftBodyDesc *desc, struct R2VectorView vertices, R2SurfaceElementView elements); @@ -6741,8 +6643,8 @@ R2Status r2SoftBodyDesc_SetSkin(struct R2SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetMasses(struct R2SoftBodyDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2SoftBodyDesc_SetMasses(struct R2SoftBodyDesc *desc, struct R2RealView view); /** @@ -6752,8 +6654,8 @@ R2Status r2SoftBodyDesc_SetMasses(struct R2SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetPinnedParticles(struct R2SoftBodyDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2SoftBodyDesc_SetPinnedParticles(struct R2SoftBodyDesc *desc, struct R2IndexView view); /** @@ -6763,8 +6665,8 @@ R2Status r2SoftBodyDesc_SetPinnedParticles(struct R2SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetEdges(struct R2SoftBodyDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2SoftBodyDesc_SetEdges(struct R2SoftBodyDesc *desc, struct R2EdgeView view); /** @@ -6774,8 +6676,8 @@ R2Status r2SoftBodyDesc_SetEdges(struct R2SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetBendEdges(struct R2SoftBodyDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2SoftBodyDesc_SetBendEdges(struct R2SoftBodyDesc *desc, struct R2EdgeView view); /** @@ -6785,9 +6687,7 @@ R2Status r2SoftBodyDesc_SetBendEdges(struct R2SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetCells(struct R2SoftBodyDesc *desc, - R2CellView view); +RAPIER_API R2Status RAPIER_CALL r2SoftBodyDesc_SetCells(struct R2SoftBodyDesc *desc, R2CellView view); /** * Borrow surface; preserve all other fields. No allocation or element reads. @@ -6796,8 +6696,8 @@ R2Status r2SoftBodyDesc_SetCells(struct R2SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetSurface(struct R2SoftBodyDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2SoftBodyDesc_SetSurface(struct R2SoftBodyDesc *desc, R2SurfaceElementView view); /** @@ -6807,8 +6707,8 @@ R2Status r2SoftBodyDesc_SetSurface(struct R2SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetTensionOnlyEdges(struct R2SoftBodyDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2SoftBodyDesc_SetTensionOnlyEdges(struct R2SoftBodyDesc *desc, struct R2IndexView view); #if defined(RAPIER_DIM3) @@ -6819,8 +6719,8 @@ R2Status r2SoftBodyDesc_SetTensionOnlyEdges(struct R2SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetDihedrals(struct R2SoftBodyDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2SoftBodyDesc_SetDihedrals(struct R2SoftBodyDesc *desc, struct R2DihedralView view); #endif @@ -6832,8 +6732,8 @@ R2Status r2SoftBodyDesc_SetDihedrals(struct R2SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBodyDesc_SetWire(struct R2SoftBodyDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2SoftBodyDesc_SetWire(struct R2SoftBodyDesc *desc, struct R2EdgeView view); #endif @@ -6842,8 +6742,8 @@ R2Status r2SoftBodyDesc_SetWire(struct R2SoftBodyDesc *desc, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderDesc r2RoundCuboidColliderDesc(struct R2Vector half_extents, +RAPIER_API +struct R2ColliderDesc RAPIER_CALL r2RoundCuboidColliderDesc(struct R2Vector half_extents, R2Real border_radius); /** @@ -6851,8 +6751,8 @@ struct R2ColliderDesc r2RoundCuboidColliderDesc(struct R2Vector half_extents, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderDesc r2CapsuleColliderDesc(struct R2Vector a, +RAPIER_API +struct R2ColliderDesc RAPIER_CALL r2CapsuleColliderDesc(struct R2Vector a, struct R2Vector b, R2Real radius); @@ -6861,17 +6761,15 @@ struct R2ColliderDesc r2CapsuleColliderDesc(struct R2Vector a, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderDesc r2SegmentColliderDesc(struct R2Vector a, - struct R2Vector b); +RAPIER_API struct R2ColliderDesc RAPIER_CALL r2SegmentColliderDesc(struct R2Vector a, struct R2Vector b); /** * Return a triangle description with vertices a, b, and c. * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderDesc r2TriangleColliderDesc(struct R2Vector a, +RAPIER_API +struct R2ColliderDesc RAPIER_CALL r2TriangleColliderDesc(struct R2Vector a, struct R2Vector b, struct R2Vector c); @@ -6880,7 +6778,7 @@ struct R2ColliderDesc r2TriangleColliderDesc(struct R2Vector a, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R2ColliderDesc r2HalfspaceColliderDesc(struct R2Vector normal); +RAPIER_API struct R2ColliderDesc RAPIER_CALL r2HalfspaceColliderDesc(struct R2Vector normal); #if defined(RAPIER_DIM3) /** @@ -6888,9 +6786,7 @@ RAPIER_API RAPIER_CALL struct R2ColliderDesc r2HalfspaceColliderDesc(struct R2Ve * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderDesc r2CylinderColliderDesc(R2Real half_height, - R2Real radius); +RAPIER_API struct R2ColliderDesc RAPIER_CALL r2CylinderColliderDesc(R2Real half_height, R2Real radius); #endif #if defined(RAPIER_DIM3) @@ -6899,9 +6795,7 @@ struct R2ColliderDesc r2CylinderColliderDesc(R2Real half_height, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderDesc r2ConeColliderDesc(R2Real half_height, - R2Real radius); +RAPIER_API struct R2ColliderDesc RAPIER_CALL r2ConeColliderDesc(R2Real half_height, R2Real radius); #endif #if defined(RAPIER_DIM3) @@ -6910,8 +6804,8 @@ struct R2ColliderDesc r2ConeColliderDesc(R2Real half_height, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderDesc r2RoundCylinderColliderDesc(R2Real half_height, +RAPIER_API +struct R2ColliderDesc RAPIER_CALL r2RoundCylinderColliderDesc(R2Real half_height, R2Real radius, R2Real border_radius); #endif @@ -6921,18 +6815,14 @@ struct R2ColliderDesc r2RoundCylinderColliderDesc(R2Real half_height, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderDesc r2CapsuleXColliderDesc(R2Real half_height, - R2Real radius); +RAPIER_API struct R2ColliderDesc RAPIER_CALL r2CapsuleXColliderDesc(R2Real half_height, R2Real radius); /** * Return a Y-aligned capsule description; half_height is half the segment length, excluding caps. * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderDesc r2CapsuleYColliderDesc(R2Real half_height, - R2Real radius); +RAPIER_API struct R2ColliderDesc RAPIER_CALL r2CapsuleYColliderDesc(R2Real half_height, R2Real radius); #if defined(RAPIER_DIM3) /** @@ -6940,9 +6830,7 @@ struct R2ColliderDesc r2CapsuleYColliderDesc(R2Real half_height, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2ColliderDesc r2CapsuleZColliderDesc(R2Real half_height, - R2Real radius); +RAPIER_API struct R2ColliderDesc RAPIER_CALL r2CapsuleZColliderDesc(R2Real half_height, R2Real radius); #endif /** @@ -6950,8 +6838,8 @@ struct R2ColliderDesc r2CapsuleZColliderDesc(R2Real half_height, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyDesc r2RopeSoftBodyDesc(struct R2Vector a, +RAPIER_API +struct R2SoftBodyDesc RAPIER_CALL r2RopeSoftBodyDesc(struct R2Vector a, struct R2Vector b, size_t particles); @@ -6961,8 +6849,8 @@ struct R2SoftBodyDesc r2RopeSoftBodyDesc(struct R2Vector a, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyDesc r2GridSoftBodyDesc(struct R2Vector center, +RAPIER_API +struct R2SoftBodyDesc RAPIER_CALL r2GridSoftBodyDesc(struct R2Vector center, struct R2Vector half_extents, size_t nx, size_t ny); @@ -6974,8 +6862,8 @@ struct R2SoftBodyDesc r2GridSoftBodyDesc(struct R2Vector center, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyDesc r2CuboidSoftBodyDesc(struct R2Vector center, +RAPIER_API +struct R2SoftBodyDesc RAPIER_CALL r2CuboidSoftBodyDesc(struct R2Vector center, struct R2Vector half_extents, size_t nx, size_t ny, @@ -6988,8 +6876,8 @@ struct R2SoftBodyDesc r2CuboidSoftBodyDesc(struct R2Vector center, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyDesc r2ClothSoftBodyDesc(struct R2Vector origin, +RAPIER_API +struct R2SoftBodyDesc RAPIER_CALL r2ClothSoftBodyDesc(struct R2Vector origin, struct R2Vector du, struct R2Vector dv, size_t nu, @@ -7003,8 +6891,8 @@ struct R2SoftBodyDesc r2ClothSoftBodyDesc(struct R2Vector origin, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyDesc r2DiskSoftBodyDesc(struct R2Vector center, +RAPIER_API +struct R2SoftBodyDesc RAPIER_CALL r2DiskSoftBodyDesc(struct R2Vector center, R2Real radius, size_t particles); #endif @@ -7015,8 +6903,8 @@ struct R2SoftBodyDesc r2DiskSoftBodyDesc(struct R2Vector center, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyDesc r2SphereSoftBodyDesc(struct R2Vector center, +RAPIER_API +struct R2SoftBodyDesc RAPIER_CALL r2SphereSoftBodyDesc(struct R2Vector center, R2Real radius, uint32_t subdivisions); #endif @@ -7028,8 +6916,8 @@ struct R2SoftBodyDesc r2SphereSoftBodyDesc(struct R2Vector center, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyDesc r2ClothTubeSoftBodyDesc(struct R2Vector origin, +RAPIER_API +struct R2SoftBodyDesc RAPIER_CALL r2ClothTubeSoftBodyDesc(struct R2Vector origin, struct R2Vector axis, R2Real radius_start, R2Real radius_end, @@ -7041,8 +6929,8 @@ struct R2SoftBodyDesc r2ClothTubeSoftBodyDesc(struct R2Vector origin, * Initializes a borrowed meshing recipe. Mesh generation happens on preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyDesc r2VolumetricSoftBodyDesc(struct R2VectorView vertices, +RAPIER_API +struct R2SoftBodyDesc RAPIER_CALL r2VolumetricSoftBodyDesc(struct R2VectorView vertices, R2SurfaceElementView surface, struct R2VolumeMeshParameters parameters); @@ -7050,16 +6938,16 @@ struct R2SoftBodyDesc r2VolumetricSoftBodyDesc(struct R2VectorView vertices, * Returns a material with the same softness for each constraint family. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyMaterial r2UniformSoftBodyMaterial(struct R2SpringCoefficients value); +RAPIER_API +struct R2SoftBodyMaterial RAPIER_CALL r2UniformSoftBodyMaterial(struct R2SpringCoefficients value); /** * Copies generated particle positions into caller-owned storage; no persistent builder. * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyDesc_ParticlePositions(const struct R2SoftBodyDesc *desc, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyDesc_ParticlePositions(const struct R2SoftBodyDesc *desc, struct R2Vector *buffer, size_t capacity); @@ -7068,8 +6956,8 @@ size_t r2SoftBodyDesc_ParticlePositions(const struct R2SoftBodyDesc *desc, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBodyDesc_CellIndices(const struct R2SoftBodyDesc *desc, +RAPIER_API +size_t RAPIER_CALL r2SoftBodyDesc_CellIndices(const struct R2SoftBodyDesc *desc, uint32_t *buffer, size_t capacity); @@ -7078,78 +6966,76 @@ size_t r2SoftBodyDesc_CellIndices(const struct R2SoftBodyDesc *desc, * clone alive while using it as a cache key. * @ingroup shapes */ -RAPIER_API RAPIER_CALL size_t r2Collider_ShapeIdentity(struct R2ColliderHandle handle); +RAPIER_API size_t RAPIER_CALL r2Collider_ShapeIdentity(struct R2ColliderHandle handle); /** * Return the soft body particle count. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL size_t r2SoftBody_NumParticles(struct R2SoftBodyHandle handle); +RAPIER_API size_t RAPIER_CALL r2SoftBody_NumParticles(struct R2SoftBodyHandle handle); /** * Return a counter that changes when particle connectivity changes; use it to invalidate mesh * caches. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL uint32_t r2SoftBody_TopologyVersion(struct R2SoftBodyHandle handle); +RAPIER_API uint32_t RAPIER_CALL r2SoftBody_TopologyVersion(struct R2SoftBodyHandle handle); /** * Return the soft body mass. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Real r2SoftBody_Mass(struct R2SoftBodyHandle handle); +RAPIER_API R2Real RAPIER_CALL r2SoftBody_Mass(struct R2SoftBodyHandle handle); /** * Return the soft body current volume. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Real r2SoftBody_Volume(struct R2SoftBodyHandle handle); +RAPIER_API R2Real RAPIER_CALL r2SoftBody_Volume(struct R2SoftBodyHandle handle); /** * Return the soft body undeformed volume. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Real r2SoftBody_RestVolume(struct R2SoftBodyHandle handle); +RAPIER_API R2Real RAPIER_CALL r2SoftBody_RestVolume(struct R2SoftBodyHandle handle); /** * Return the soft body target volume multiplier. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Real r2SoftBody_VolumeFactor(struct R2SoftBodyHandle handle); +RAPIER_API R2Real RAPIER_CALL r2SoftBody_VolumeFactor(struct R2SoftBodyHandle handle); /** * Return the soft body world-space center of mass. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2Vector r2SoftBody_CenterOfMass(struct R2SoftBodyHandle handle); +RAPIER_API struct R2Vector RAPIER_CALL r2SoftBody_CenterOfMass(struct R2SoftBodyHandle handle); /** * Return the soft body root rigid-proxy handle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2RigidBodyHandle r2SoftBody_RootBody(struct R2SoftBodyHandle handle); +RAPIER_API struct R2RigidBodyHandle RAPIER_CALL r2SoftBody_RootBody(struct R2SoftBodyHandle handle); /** * Return whether the soft body is enabled. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2SoftBody_IsEnabled(struct R2SoftBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2SoftBody_IsEnabled(struct R2SoftBodyHandle handle); /** * Return whether the soft body is sleeping. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2SoftBody_IsSleeping(struct R2SoftBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2SoftBody_IsSleeping(struct R2SoftBodyHandle handle); /** * Copy world-space particle velocities. * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_ParticleVelocities(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_ParticleVelocities(struct R2SoftBodyHandle handle, struct R2Vector *buffer, size_t capacity); @@ -7158,8 +7044,8 @@ size_t r2SoftBody_ParticleVelocities(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_Edges(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_Edges(struct R2SoftBodyHandle handle, uint32_t *buffer, size_t capacity); @@ -7168,8 +7054,8 @@ size_t r2SoftBody_Edges(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_Cells(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_Cells(struct R2SoftBodyHandle handle, uint32_t *buffer, size_t capacity); @@ -7178,8 +7064,8 @@ size_t r2SoftBody_Cells(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_Boundary(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_Boundary(struct R2SoftBodyHandle handle, uint32_t *buffer, size_t capacity); @@ -7188,8 +7074,8 @@ size_t r2SoftBody_Boundary(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_Pieces(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_Pieces(struct R2SoftBodyHandle handle, struct R2SoftBodyHandle *buffer, size_t capacity); @@ -7197,8 +7083,8 @@ size_t r2SoftBody_Pieces(struct R2SoftBodyHandle handle, * Set the soft body particle world-space velocity. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetParticleVelocity(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetParticleVelocity(struct R2SoftBodyHandle handle, size_t index, struct R2Vector value); @@ -7206,8 +7092,8 @@ R2Status r2SoftBody_SetParticleVelocity(struct R2SoftBodyHandle handle, * Set the next world-space target position of a pinned particle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetParticleKinematicTarget(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetParticleKinematicTarget(struct R2SoftBodyHandle handle, size_t index, struct R2Vector value); @@ -7215,8 +7101,8 @@ R2Status r2SoftBody_SetParticleKinematicTarget(struct R2SoftBodyHandle handle, * Enable or disable pinning the particle for the soft body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetParticlePinned(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetParticlePinned(struct R2SoftBodyHandle handle, size_t index, R2Bool value); @@ -7225,8 +7111,8 @@ R2Status r2SoftBody_SetParticlePinned(struct R2SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_ApplyParticleImpulse(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_ApplyParticleImpulse(struct R2SoftBodyHandle handle, size_t index, struct R2Vector value, R2Bool wake_up); @@ -7236,8 +7122,8 @@ R2Status r2SoftBody_ApplyParticleImpulse(struct R2SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_AddForce(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_AddForce(struct R2SoftBodyHandle handle, struct R2Vector value, R2Bool wake_up); @@ -7246,8 +7132,8 @@ R2Status r2SoftBody_AddForce(struct R2SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_ApplyImpulse(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_ApplyImpulse(struct R2SoftBodyHandle handle, struct R2Vector value, R2Bool wake_up); @@ -7256,32 +7142,28 @@ R2Status r2SoftBody_ApplyImpulse(struct R2SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_ResetForces(struct R2SoftBodyHandle handle, - R2Bool wake_up); +RAPIER_API R2Status RAPIER_CALL r2SoftBody_ResetForces(struct R2SoftBodyHandle handle, R2Bool wake_up); /** * Enable or disable the soft body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetEnabled(struct R2SoftBodyHandle handle, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2SoftBody_SetEnabled(struct R2SoftBodyHandle handle, R2Bool value); /** * Set the soft body target volume multiplier. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetVolumeFactor(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetVolumeFactor(struct R2SoftBodyHandle handle, R2Real value); /** * Attach a particle to a rigid body at the supplied body-local anchor. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_AttachParticle(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_AttachParticle(struct R2SoftBodyHandle handle, size_t index, struct R2RigidBodyHandle rigid_body); @@ -7289,17 +7171,15 @@ R2Status r2SoftBody_AttachParticle(struct R2SoftBodyHandle handle, * Remove a particle attachment to a rigid body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_DetachParticle(struct R2SoftBodyHandle handle, - size_t index); +RAPIER_API R2Status RAPIER_CALL r2SoftBody_DetachParticle(struct R2SoftBodyHandle handle, size_t index); /** * Copy cluster indices. * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_Clusters(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_Clusters(struct R2SoftBodyHandle handle, uint32_t *buffer, size_t capacity); @@ -7307,8 +7187,8 @@ size_t r2SoftBody_Clusters(struct R2SoftBodyHandle handle, * Return the rigid proxy for the selected cluster. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2RigidBodyHandle r2SoftBody_ClusterProxy(struct R2SoftBodyHandle handle, +RAPIER_API +struct R2RigidBodyHandle RAPIER_CALL r2SoftBody_ClusterProxy(struct R2SoftBodyHandle handle, uint32_t cluster); /** @@ -7316,8 +7196,8 @@ struct R2RigidBodyHandle r2SoftBody_ClusterProxy(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_ClusterParticles(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_ClusterParticles(struct R2SoftBodyHandle handle, uint32_t cluster, uint32_t *buffer, size_t capacity); @@ -7326,8 +7206,8 @@ size_t r2SoftBody_ClusterParticles(struct R2SoftBodyHandle handle, * Enable or disable pinning the cluster for the soft body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetClusterPinned(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetClusterPinned(struct R2SoftBodyHandle handle, uint32_t cluster, R2Bool value); @@ -7335,8 +7215,8 @@ R2Status r2SoftBody_SetClusterPinned(struct R2SoftBodyHandle handle, * Set the next world-space target pose of a pinned cluster. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetClusterKinematicTarget(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetClusterKinematicTarget(struct R2SoftBodyHandle handle, uint32_t cluster, struct R2Pose value); @@ -7344,8 +7224,8 @@ R2Status r2SoftBody_SetClusterKinematicTarget(struct R2SoftBodyHandle handle, * Enable or disable using cluster shape matching for the soft body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetClusterShapeMatchingEnabled(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetClusterShapeMatchingEnabled(struct R2SoftBodyHandle handle, uint32_t cluster, R2Bool value); @@ -7353,8 +7233,8 @@ R2Status r2SoftBody_SetClusterShapeMatchingEnabled(struct R2SoftBodyHandle handl * Set the soft body cluster shape-matching stiffness multiplier. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetClusterStiffnessScale(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetClusterStiffnessScale(struct R2SoftBodyHandle handle, uint32_t cluster, R2Real value); @@ -7362,8 +7242,8 @@ R2Status r2SoftBody_SetClusterStiffnessScale(struct R2SoftBodyHandle handle, * Set the soft body cluster tear-resistance multiplier. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetClusterTearResistance(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetClusterTearResistance(struct R2SoftBodyHandle handle, uint32_t cluster, R2Real value); @@ -7372,8 +7252,8 @@ R2Status r2SoftBody_SetClusterTearResistance(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_Meshes(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_Meshes(struct R2SoftBodyHandle handle, struct R2SoftMeshInfo *buffer, size_t capacity); @@ -7382,8 +7262,8 @@ size_t r2SoftBody_Meshes(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_MeshVerticesById(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_MeshVerticesById(struct R2SoftBodyHandle handle, struct R2SoftMeshId id, struct R2Vector *buffer, size_t capacity); @@ -7393,8 +7273,8 @@ size_t r2SoftBody_MeshVerticesById(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_MeshIndicesById(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_MeshIndicesById(struct R2SoftBodyHandle handle, struct R2SoftMeshId id, uint32_t *buffer, size_t capacity); @@ -7404,8 +7284,8 @@ size_t r2SoftBody_MeshIndicesById(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_MeshColliders(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_MeshColliders(struct R2SoftBodyHandle handle, struct R2ColliderHandle *buffer, size_t capacity); @@ -7414,8 +7294,8 @@ size_t r2SoftBody_MeshColliders(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_MeshVertices(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_MeshVertices(struct R2SoftBodyHandle handle, struct R2ColliderHandle collider, struct R2Vector *buffer, size_t capacity); @@ -7425,8 +7305,8 @@ size_t r2SoftBody_MeshVertices(struct R2SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_MeshIndices(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_MeshIndices(struct R2SoftBodyHandle handle, struct R2ColliderHandle collider, uint32_t *buffer, size_t capacity); @@ -7435,16 +7315,16 @@ size_t r2SoftBody_MeshIndices(struct R2SoftBodyHandle handle, * Return indices per collision-mesh element (2 for segments, 3 for triangles). * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r2SoftBody_MeshArity(struct R2SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2SoftBody_MeshArity(struct R2SoftBodyHandle handle, struct R2ColliderHandle collider); /** * Return the selected collision mesh topology revision for cache invalidation. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -uint32_t r2SoftBody_MeshTopologyVersion(struct R2SoftBodyHandle handle, +RAPIER_API +uint32_t RAPIER_CALL r2SoftBody_MeshTopologyVersion(struct R2SoftBodyHandle handle, struct R2ColliderHandle collider); #if defined(RAPIER_FEM) @@ -7452,17 +7332,15 @@ uint32_t r2SoftBody_MeshTopologyVersion(struct R2SoftBodyHandle handle, * Set the soft body soft solver kind (R2_SOFT_SOLVER_*). * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetSolver(struct R2SoftBodyHandle handle, - uint32_t solver); +RAPIER_API R2Status RAPIER_CALL r2SoftBody_SetSolver(struct R2SoftBodyHandle handle, uint32_t solver); #endif /** * Set the soft body cluster shape-matching target pose. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetClusterShapeMatchingTarget(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetClusterShapeMatchingTarget(struct R2SoftBodyHandle handle, uint32_t cluster, const struct R2Pose *target); @@ -7470,8 +7348,8 @@ R2Status r2SoftBody_SetClusterShapeMatchingTarget(struct R2SoftBodyHandle handle * Set the soft body edge tear-resistance multiplier. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2SoftBody_SetEdgeTearResistance(struct R2SoftBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2SoftBody_SetEdgeTearResistance(struct R2SoftBodyHandle handle, size_t index, R2Real resistance); @@ -7479,8 +7357,8 @@ R2Status r2SoftBody_SetEdgeTearResistance(struct R2SoftBodyHandle handle, * Return whether the selected collision mesh is closed. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Bool r2SoftBody_MeshIsClosed(struct R2SoftBodyHandle handle, +RAPIER_API +R2Bool RAPIER_CALL r2SoftBody_MeshIsClosed(struct R2SoftBodyHandle handle, struct R2ColliderHandle collider); /** @@ -7488,8 +7366,8 @@ R2Bool r2SoftBody_MeshIsClosed(struct R2SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetAdditionalMassProperties(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetAdditionalMassProperties(struct R2RigidBodyHandle handle, struct R2MassProperties properties, R2Bool wake_up); @@ -7497,31 +7375,30 @@ R2Status r2RigidBody_SetAdditionalMassProperties(struct R2RigidBodyHandle handle * Recompute body mass and inertia from attached colliders and additional mass properties. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_RecomputeMassPropertiesFromColliders(struct R2RigidBodyHandle handle); +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_RecomputeMassPropertiesFromColliders(struct R2RigidBodyHandle handle); /** * Set the collider local mass properties. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetMassProperties(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetMassProperties(struct R2ColliderHandle handle, struct R2MassProperties properties); /** * Return the collider local mass properties. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2MassProperties r2Collider_MassProperties(struct R2ColliderHandle handle); +RAPIER_API struct R2MassProperties RAPIER_CALL r2Collider_MassProperties(struct R2ColliderHandle handle); /** * Set the rigid body translation/rotation lock bitmask. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetLockedAxes(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetLockedAxes(struct R2RigidBodyHandle handle, uint8_t axes, R2Bool wake_up); @@ -7529,28 +7406,28 @@ R2Status r2RigidBody_SetLockedAxes(struct R2RigidBodyHandle handle, * Return the rigid body translation/rotation lock bitmask. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL uint8_t r2RigidBody_LockedAxes(struct R2RigidBodyHandle handle); +RAPIER_API uint8_t RAPIER_CALL r2RigidBody_LockedAxes(struct R2RigidBodyHandle handle); /** * Return whether the collider is a voxel shape. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R2Bool r2Collider_IsVoxels(struct R2ColliderHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2Collider_IsVoxels(struct R2ColliderHandle handle); /** * Return voxel information at a flat index; found = 0 if absent. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2VoxelQuery r2Collider_VoxelAtFlatId(struct R2ColliderHandle handle, +RAPIER_API +struct R2VoxelQuery RAPIER_CALL r2Collider_VoxelAtFlatId(struct R2ColliderHandle handle, uint32_t id); /** * Fill or clear the voxel at key; the collider must have a voxel shape. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetVoxel(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetVoxel(struct R2ColliderHandle handle, struct R2VoxelKey key, R2Bool filled); @@ -7558,139 +7435,135 @@ R2Status r2Collider_SetVoxel(struct R2ColliderHandle handle, * Return the rigid body next kinematic world-space pose. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R2Pose r2RigidBody_NextPosition(struct R2RigidBodyHandle handle); +RAPIER_API struct R2Pose RAPIER_CALL r2RigidBody_NextPosition(struct R2RigidBodyHandle handle); /** * Return the rigid body world-space rotation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R2Rotation r2RigidBody_Rotation(struct R2RigidBodyHandle handle); +RAPIER_API struct R2Rotation RAPIER_CALL r2RigidBody_Rotation(struct R2RigidBodyHandle handle); /** * Return the rigid body world-space center of mass. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R2Vector r2RigidBody_CenterOfMass(struct R2RigidBodyHandle handle); +RAPIER_API struct R2Vector RAPIER_CALL r2RigidBody_CenterOfMass(struct R2RigidBodyHandle handle); /** * Return the rigid body body-local center of mass. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R2Vector r2RigidBody_LocalCenterOfMass(struct R2RigidBodyHandle handle); +RAPIER_API struct R2Vector RAPIER_CALL r2RigidBody_LocalCenterOfMass(struct R2RigidBodyHandle handle); /** * Return the rigid body accumulated user-applied world-space force. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R2Vector r2RigidBody_UserForce(struct R2RigidBodyHandle handle); +RAPIER_API struct R2Vector RAPIER_CALL r2RigidBody_UserForce(struct R2RigidBodyHandle handle); /** * Return the rigid body accumulated user-applied world-space torque. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2AngVector r2RigidBody_UserTorque(struct R2RigidBodyHandle handle); +RAPIER_API R2AngVector RAPIER_CALL r2RigidBody_UserTorque(struct R2RigidBodyHandle handle); /** * Return the rigid body body type (R2_DYNAMIC, R2_FIXED, or a kinematic kind). * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL uint32_t r2RigidBody_BodyType(struct R2RigidBodyHandle handle); +RAPIER_API uint32_t RAPIER_CALL r2RigidBody_BodyType(struct R2RigidBodyHandle handle); /** * Return the rigid body mass. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Real r2RigidBody_Mass(struct R2RigidBodyHandle handle); +RAPIER_API R2Real RAPIER_CALL r2RigidBody_Mass(struct R2RigidBodyHandle handle); /** * Return the rigid body gravity multiplier. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Real r2RigidBody_GravityScale(struct R2RigidBodyHandle handle); +RAPIER_API R2Real RAPIER_CALL r2RigidBody_GravityScale(struct R2RigidBodyHandle handle); /** * Return the rigid body linear damping coefficient. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Real r2RigidBody_LinearDamping(struct R2RigidBodyHandle handle); +RAPIER_API R2Real RAPIER_CALL r2RigidBody_LinearDamping(struct R2RigidBodyHandle handle); /** * Return the rigid body angular damping coefficient. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Real r2RigidBody_AngularDamping(struct R2RigidBodyHandle handle); +RAPIER_API R2Real RAPIER_CALL r2RigidBody_AngularDamping(struct R2RigidBodyHandle handle); /** * Return the rigid body kinetic energy. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Real r2RigidBody_KineticEnergy(struct R2RigidBodyHandle handle); +RAPIER_API R2Real RAPIER_CALL r2RigidBody_KineticEnergy(struct R2RigidBodyHandle handle); /** * Return the rigid body soft-CCD prediction distance. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Real r2RigidBody_SoftCcdPrediction(struct R2RigidBodyHandle handle); +RAPIER_API R2Real RAPIER_CALL r2RigidBody_SoftCcdPrediction(struct R2RigidBodyHandle handle); /** * Return whether the rigid body is using continuous collision detection. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsCcdEnabled(struct R2RigidBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2RigidBody_IsCcdEnabled(struct R2RigidBodyHandle handle); /** * Return whether the rigid body is dynamic. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsDynamic(struct R2RigidBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2RigidBody_IsDynamic(struct R2RigidBodyHandle handle); /** * Return the associated soft-body handle, or an invalid handle if this is not a soft proxy. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyHandle r2RigidBody_SoftBody(struct R2RigidBodyHandle handle); +RAPIER_API struct R2SoftBodyHandle RAPIER_CALL r2RigidBody_SoftBody(struct R2RigidBodyHandle handle); /** * Return whether the rigid body is a soft-body proxy. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsSoftFrame(struct R2RigidBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2RigidBody_IsSoftFrame(struct R2RigidBodyHandle handle); /** * Return whether the rigid body is fixed. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsFixed(struct R2RigidBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2RigidBody_IsFixed(struct R2RigidBodyHandle handle); /** * Return whether the rigid body is kinematic. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsKinematic(struct R2RigidBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2RigidBody_IsKinematic(struct R2RigidBodyHandle handle); /** * Return whether the rigid body is moving. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsMoving(struct R2RigidBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2RigidBody_IsMoving(struct R2RigidBodyHandle handle); /** * Return whether the rigid body is currently using CCD for its motion. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Bool r2RigidBody_IsCcdActive(struct R2RigidBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2RigidBody_IsCcdActive(struct R2RigidBodyHandle handle); /** * Set the rigid body world-space rotation. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetRotation(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetRotation(struct R2RigidBodyHandle handle, struct R2Rotation value, R2Bool wake_up); @@ -7699,8 +7572,8 @@ R2Status r2RigidBody_SetRotation(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetBodyType(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetBodyType(struct R2RigidBodyHandle handle, uint32_t value, R2Bool wake_up); @@ -7708,8 +7581,8 @@ R2Status r2RigidBody_SetBodyType(struct R2RigidBodyHandle handle, * Set the rigid body next kinematic world-space rotation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetNextKinematicRotation(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetNextKinematicRotation(struct R2RigidBodyHandle handle, struct R2Rotation value); /** @@ -7717,8 +7590,8 @@ R2Status r2RigidBody_SetNextKinematicRotation(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetAdditionalMass(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetAdditionalMass(struct R2RigidBodyHandle handle, R2Real value, R2Bool wake_up); @@ -7726,16 +7599,16 @@ R2Status r2RigidBody_SetAdditionalMass(struct R2RigidBodyHandle handle, * Set the rigid body soft-CCD prediction distance. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetSoftCcdPrediction(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetSoftCcdPrediction(struct R2RigidBodyHandle handle, R2Real value); /** * Enable or disable using continuous collision detection for the rigid body. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetCcdEnabled(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetCcdEnabled(struct R2RigidBodyHandle handle, R2Bool value); /** @@ -7743,8 +7616,8 @@ R2Status r2RigidBody_SetCcdEnabled(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetTranslationsLocked(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetTranslationsLocked(struct R2RigidBodyHandle handle, R2Bool value, R2Bool wake_up); @@ -7753,8 +7626,8 @@ R2Status r2RigidBody_SetTranslationsLocked(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetRotationsLocked(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetRotationsLocked(struct R2RigidBodyHandle handle, R2Bool value, R2Bool wake_up); @@ -7762,24 +7635,24 @@ R2Status r2RigidBody_SetRotationsLocked(struct R2RigidBodyHandle handle, * Set the rigid body signed dominance group. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetDominanceGroup(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetDominanceGroup(struct R2RigidBodyHandle handle, int8_t value); /** * Set the rigid body additional solver iterations for connected bodies. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetAdditionalSolverIterations(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetAdditionalSolverIterations(struct R2RigidBodyHandle handle, size_t value); /** * Set the rigid body additional PGS iterations. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetAdditionalPgsIterations(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetAdditionalPgsIterations(struct R2RigidBodyHandle handle, size_t value); /** @@ -7787,8 +7660,8 @@ R2Status r2RigidBody_SetAdditionalPgsIterations(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_AddTorque(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_AddTorque(struct R2RigidBodyHandle handle, R2AngVector value, R2Bool wake_up); @@ -7797,8 +7670,8 @@ R2Status r2RigidBody_AddTorque(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_ApplyTorqueImpulse(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_ApplyTorqueImpulse(struct R2RigidBodyHandle handle, R2AngVector value, R2Bool wake_up); @@ -7807,8 +7680,8 @@ R2Status r2RigidBody_ApplyTorqueImpulse(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_AddForceAtPoint(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_AddForceAtPoint(struct R2RigidBodyHandle handle, struct R2Vector value, struct R2Vector point, R2Bool wake_up); @@ -7818,16 +7691,16 @@ R2Status r2RigidBody_AddForceAtPoint(struct R2RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_ResetTorques(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_ResetTorques(struct R2RigidBodyHandle handle, R2Bool wake_up); /** * Return world-space velocity at a world-space point, including angular motion. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R2Vector r2RigidBody_VelocityAtPoint(struct R2RigidBodyHandle handle, +RAPIER_API +struct R2Vector RAPIER_CALL r2RigidBody_VelocityAtPoint(struct R2RigidBodyHandle handle, struct R2Vector point); /** @@ -7835,8 +7708,8 @@ struct R2Vector r2RigidBody_VelocityAtPoint(struct R2RigidBodyHandle handle, * @see @ref output_buffers * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -size_t r2RigidBody_Colliders(struct R2RigidBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r2RigidBody_Colliders(struct R2RigidBodyHandle handle, struct R2ColliderHandle *buffer, size_t capacity); @@ -7845,8 +7718,7 @@ size_t r2RigidBody_Colliders(struct R2RigidBodyHandle handle, * Return whether the rigid body is using gyroscopic forces. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Bool r2RigidBody_GyroscopicForcesEnabled(struct R2RigidBodyHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2RigidBody_GyroscopicForcesEnabled(struct R2RigidBodyHandle handle); #endif #if defined(RAPIER_DIM3) @@ -7854,8 +7726,8 @@ R2Bool r2RigidBody_GyroscopicForcesEnabled(struct R2RigidBodyHandle handle); * Enable or disable using gyroscopic forces for the rigid body. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2RigidBody_SetGyroscopicForcesEnabled(struct R2RigidBodyHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2RigidBody_SetGyroscopicForcesEnabled(struct R2RigidBodyHandle handle, R2Bool enabled); #endif @@ -7863,187 +7735,175 @@ R2Status r2RigidBody_SetGyroscopicForcesEnabled(struct R2RigidBodyHandle handle, * Set the collider mass per unit volume. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetDensity(struct R2ColliderHandle handle, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2Collider_SetDensity(struct R2ColliderHandle handle, R2Real value); /** * Set the collider mass. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetMass(struct R2ColliderHandle handle, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2Collider_SetMass(struct R2ColliderHandle handle, R2Real value); /** * Enable or disable the collider. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetEnabled(struct R2ColliderHandle handle, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2Collider_SetEnabled(struct R2ColliderHandle handle, R2Bool value); /** * Set the collider contact-force filtering groups. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetSolverGroups(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetSolverGroups(struct R2ColliderHandle handle, struct R2InteractionGroups value); /** * Set the collider friction combination rule (R2_COMBINE_*). * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetFrictionCombineRule(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetFrictionCombineRule(struct R2ColliderHandle handle, uint32_t value); /** * Set the collider restitution combination rule (R2_COMBINE_*). * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetRestitutionCombineRule(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetRestitutionCombineRule(struct R2ColliderHandle handle, uint32_t value); /** * Set the collider extra separation skin around the shape. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetContactSkin(struct R2ColliderHandle handle, - R2Real value); +RAPIER_API R2Status RAPIER_CALL r2Collider_SetContactSkin(struct R2ColliderHandle handle, R2Real value); /** * Set the collider force threshold for contact-force events. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetContactForceEventThreshold(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetContactForceEventThreshold(struct R2ColliderHandle handle, R2Real value); /** * Set the collider event-generation bitmask (R2_COLLISION_EVENTS and R2_CONTACT_FORCE_EVENTS). * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetActiveEvents(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetActiveEvents(struct R2ColliderHandle handle, uint32_t value); /** * Set the collider physics-hook activation bitmask. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetActiveHooks(struct R2ColliderHandle handle, - uint32_t value); +RAPIER_API R2Status RAPIER_CALL r2Collider_SetActiveHooks(struct R2ColliderHandle handle, uint32_t value); /** * Set the collider body-type collision activation bitmask. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetActiveCollisionTypes(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetActiveCollisionTypes(struct R2ColliderHandle handle, uint16_t value); /** * Return the collider world-space rotation. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R2Rotation r2Collider_Rotation(struct R2ColliderHandle handle); +RAPIER_API struct R2Rotation RAPIER_CALL r2Collider_Rotation(struct R2ColliderHandle handle); /** * Return the collider collision filtering groups. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2InteractionGroups r2Collider_CollisionGroups(struct R2ColliderHandle handle); +RAPIER_API +struct R2InteractionGroups RAPIER_CALL r2Collider_CollisionGroups(struct R2ColliderHandle handle); /** * Return the collider contact-force filtering groups. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R2InteractionGroups r2Collider_SolverGroups(struct R2ColliderHandle handle); +RAPIER_API struct R2InteractionGroups RAPIER_CALL r2Collider_SolverGroups(struct R2ColliderHandle handle); /** * Return the collider application-owned 128-bit user value. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R2UserData r2Collider_UserData(struct R2ColliderHandle handle); +RAPIER_API struct R2UserData RAPIER_CALL r2Collider_UserData(struct R2ColliderHandle handle); /** * Return the collider event-generation bitmask (R2_COLLISION_EVENTS and * R2_CONTACT_FORCE_EVENTS). * @ingroup colliders */ -RAPIER_API RAPIER_CALL uint32_t r2Collider_ActiveEvents(struct R2ColliderHandle handle); +RAPIER_API uint32_t RAPIER_CALL r2Collider_ActiveEvents(struct R2ColliderHandle handle); /** * Return the collider mass. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R2Real r2Collider_Mass(struct R2ColliderHandle handle); +RAPIER_API R2Real RAPIER_CALL r2Collider_Mass(struct R2ColliderHandle handle); /** * Return the collider mass per unit volume. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R2Real r2Collider_Density(struct R2ColliderHandle handle); +RAPIER_API R2Real RAPIER_CALL r2Collider_Density(struct R2ColliderHandle handle); /** * Return the collider current volume. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R2Real r2Collider_Volume(struct R2ColliderHandle handle); +RAPIER_API R2Real RAPIER_CALL r2Collider_Volume(struct R2ColliderHandle handle); /** * Return the collider extra separation skin around the shape. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R2Real r2Collider_ContactSkin(struct R2ColliderHandle handle); +RAPIER_API R2Real RAPIER_CALL r2Collider_ContactSkin(struct R2ColliderHandle handle); /** * Return the collider force threshold for contact-force events. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Real r2Collider_ContactForceEventThreshold(struct R2ColliderHandle handle); +RAPIER_API R2Real RAPIER_CALL r2Collider_ContactForceEventThreshold(struct R2ColliderHandle handle); /** * Return whether the collider is enabled. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R2Bool r2Collider_IsEnabled(struct R2ColliderHandle handle); +RAPIER_API R2Bool RAPIER_CALL r2Collider_IsEnabled(struct R2ColliderHandle handle); /** * Return the current world-space axis-aligned bounds. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R2Aabb r2Collider_ComputeAabb(struct R2ColliderHandle handle); +RAPIER_API struct R2Aabb RAPIER_CALL r2Collider_ComputeAabb(struct R2ColliderHandle handle); /** * Return an owned wrapper sharing the collider geometry. Release with r2FreeSharedShape. * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R2SharedShape *r2Collider_CloneShape(struct R2ColliderHandle handle); +RAPIER_API R2SharedShape *RAPIER_CALL r2Collider_CloneShape(struct R2ColliderHandle handle); /** * Replace collider geometry by sharing shape; the supplied wrapper is not consumed. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetShape(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetShape(struct R2ColliderHandle handle, const R2SharedShape *shape); /** * Set the collider pose relative to the parent rigid body. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R2Status r2Collider_SetPositionWrtParent(struct R2ColliderHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2Collider_SetPositionWrtParent(struct R2ColliderHandle handle, struct R2Pose value); /** @@ -8051,28 +7911,28 @@ R2Status r2Collider_SetPositionWrtParent(struct R2ColliderHandle handle, * has already been freed. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R2Status r2RigidBody_ValidateHandle(struct R2RigidBodyHandle handle); +RAPIER_API R2Status RAPIER_CALL r2RigidBody_ValidateHandle(struct R2RigidBodyHandle handle); /** * Validate the index and generation in the live owning world. Cannot detect a world pointer that * has already been freed. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R2Status r2Collider_ValidateHandle(struct R2ColliderHandle handle); +RAPIER_API R2Status RAPIER_CALL r2Collider_ValidateHandle(struct R2ColliderHandle handle); /** * Validate the index and generation in the live owning world. Cannot detect a world pointer that * has already been freed. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R2Status r2SoftBody_ValidateHandle(struct R2SoftBodyHandle handle); +RAPIER_API R2Status RAPIER_CALL r2SoftBody_ValidateHandle(struct R2SoftBodyHandle handle); /** * Set the joint desc joint frame relative to body 1. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetLocalFrame1(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetLocalFrame1(struct R2JointDesc *desc, struct R2Pose value); /** @@ -8080,8 +7940,8 @@ R2Status r2JointDesc_SetLocalFrame1(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetLocalFrame1(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetLocalFrame1(struct R2ImpulseJointHandle handle, struct R2Pose value, R2Bool wake_up); @@ -8089,8 +7949,8 @@ R2Status r2ImpulseJoint_SetLocalFrame1(struct R2ImpulseJointHandle handle, * Set the joint desc joint frame relative to body 2. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetLocalFrame2(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetLocalFrame2(struct R2JointDesc *desc, struct R2Pose value); /** @@ -8098,8 +7958,8 @@ R2Status r2JointDesc_SetLocalFrame2(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetLocalFrame2(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetLocalFrame2(struct R2ImpulseJointHandle handle, struct R2Pose value, R2Bool wake_up); @@ -8107,8 +7967,8 @@ R2Status r2ImpulseJoint_SetLocalFrame2(struct R2ImpulseJointHandle handle, * Set the joint desc joint anchor relative to body 1. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetLocalAnchor1(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetLocalAnchor1(struct R2JointDesc *desc, struct R2Vector value); /** @@ -8116,8 +7976,8 @@ R2Status r2JointDesc_SetLocalAnchor1(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetLocalAnchor1(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetLocalAnchor1(struct R2ImpulseJointHandle handle, struct R2Vector value, R2Bool wake_up); @@ -8125,8 +7985,8 @@ R2Status r2ImpulseJoint_SetLocalAnchor1(struct R2ImpulseJointHandle handle, * Set the joint desc joint anchor relative to body 2. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetLocalAnchor2(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetLocalAnchor2(struct R2JointDesc *desc, struct R2Vector value); /** @@ -8134,8 +7994,8 @@ R2Status r2JointDesc_SetLocalAnchor2(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetLocalAnchor2(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetLocalAnchor2(struct R2ImpulseJointHandle handle, struct R2Vector value, R2Bool wake_up); @@ -8143,17 +8003,15 @@ R2Status r2ImpulseJoint_SetLocalAnchor2(struct R2ImpulseJointHandle handle, * Enable or disable allowing contacts between connected bodies for the joint desc. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetContactsEnabled(struct R2JointDesc *desc, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2JointDesc_SetContactsEnabled(struct R2JointDesc *desc, R2Bool value); /** * Enable or disable allowing contacts between connected bodies for the impulse joint. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetContactsEnabled(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetContactsEnabled(struct R2ImpulseJointHandle handle, R2Bool value, R2Bool wake_up); @@ -8161,17 +8019,15 @@ R2Status r2ImpulseJoint_SetContactsEnabled(struct R2ImpulseJointHandle handle, * Enable or disable the joint desc. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetEnabled(struct R2JointDesc *desc, - R2Bool value); +RAPIER_API R2Status RAPIER_CALL r2JointDesc_SetEnabled(struct R2JointDesc *desc, R2Bool value); /** * Enable or disable the impulse joint. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetEnabled(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetEnabled(struct R2ImpulseJointHandle handle, R2Bool value, R2Bool wake_up); @@ -8179,8 +8035,8 @@ R2Status r2ImpulseJoint_SetEnabled(struct R2ImpulseJointHandle handle, * Set the joint desc joint spring coefficients. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetSoftness(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetSoftness(struct R2JointDesc *desc, struct R2SpringCoefficients value); /** @@ -8188,8 +8044,8 @@ R2Status r2JointDesc_SetSoftness(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetSoftness(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetSoftness(struct R2ImpulseJointHandle handle, struct R2SpringCoefficients value, R2Bool wake_up); @@ -8197,17 +8053,15 @@ R2Status r2ImpulseJoint_SetSoftness(struct R2ImpulseJointHandle handle, * Set the joint desc translation/rotation lock bitmask. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetLockedAxes(struct R2JointDesc *desc, - uint8_t value); +RAPIER_API R2Status RAPIER_CALL r2JointDesc_SetLockedAxes(struct R2JointDesc *desc, uint8_t value); /** * Set the impulse joint translation/rotation lock bitmask. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetLockedAxes(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetLockedAxes(struct R2ImpulseJointHandle handle, uint8_t value, R2Bool wake_up); @@ -8215,17 +8069,15 @@ R2Status r2ImpulseJoint_SetLockedAxes(struct R2ImpulseJointHandle handle, * Set the joint desc joint axis mask with limits enabled. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetLimitAxes(struct R2JointDesc *desc, - uint8_t value); +RAPIER_API R2Status RAPIER_CALL r2JointDesc_SetLimitAxes(struct R2JointDesc *desc, uint8_t value); /** * Set the impulse joint joint axis mask with limits enabled. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetLimitAxes(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetLimitAxes(struct R2ImpulseJointHandle handle, uint8_t value, R2Bool wake_up); @@ -8233,17 +8085,15 @@ R2Status r2ImpulseJoint_SetLimitAxes(struct R2ImpulseJointHandle handle, * Set the joint desc joint axis mask with motors enabled. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetMotorAxes(struct R2JointDesc *desc, - uint8_t value); +RAPIER_API R2Status RAPIER_CALL r2JointDesc_SetMotorAxes(struct R2JointDesc *desc, uint8_t value); /** * Set the impulse joint joint axis mask with motors enabled. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetMotorAxes(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetMotorAxes(struct R2ImpulseJointHandle handle, uint8_t value, R2Bool wake_up); @@ -8251,17 +8101,15 @@ R2Status r2ImpulseJoint_SetMotorAxes(struct R2ImpulseJointHandle handle, * Set the joint desc coupled joint axis mask. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetCoupledAxes(struct R2JointDesc *desc, - uint8_t value); +RAPIER_API R2Status RAPIER_CALL r2JointDesc_SetCoupledAxes(struct R2JointDesc *desc, uint8_t value); /** * Set the impulse joint coupled joint axis mask. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetCoupledAxes(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetCoupledAxes(struct R2ImpulseJointHandle handle, uint8_t value, R2Bool wake_up); @@ -8269,8 +8117,8 @@ R2Status r2ImpulseJoint_SetCoupledAxes(struct R2ImpulseJointHandle handle, * Set the joint desc joint principal axis in body 1 local coordinates. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetLocalAxis1(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetLocalAxis1(struct R2JointDesc *desc, struct R2Vector value); /** @@ -8278,8 +8126,8 @@ R2Status r2JointDesc_SetLocalAxis1(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetLocalAxis1(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetLocalAxis1(struct R2ImpulseJointHandle handle, struct R2Vector value, R2Bool wake_up); @@ -8287,8 +8135,8 @@ R2Status r2ImpulseJoint_SetLocalAxis1(struct R2ImpulseJointHandle handle, * Set the joint desc joint principal axis in body 2 local coordinates. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetLocalAxis2(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetLocalAxis2(struct R2JointDesc *desc, struct R2Vector value); /** @@ -8296,8 +8144,8 @@ R2Status r2JointDesc_SetLocalAxis2(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetLocalAxis2(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetLocalAxis2(struct R2ImpulseJointHandle handle, struct R2Vector value, R2Bool wake_up); @@ -8305,8 +8153,8 @@ R2Status r2ImpulseJoint_SetLocalAxis2(struct R2ImpulseJointHandle handle, * Set the joint desc minimum and maximum limits on an axis (linear distance or angular radians). * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetLimits(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetLimits(struct R2JointDesc *desc, uint32_t joint_axis, R2Real min, R2Real max); @@ -8317,8 +8165,8 @@ R2Status r2JointDesc_SetLimits(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetLimits(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetLimits(struct R2ImpulseJointHandle handle, uint32_t joint_axis, R2Real min, R2Real max, @@ -8328,8 +8176,8 @@ R2Status r2ImpulseJoint_SetLimits(struct R2ImpulseJointHandle handle, * Set the joint desc motor position/velocity targets and spring coefficients on an axis. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetMotor(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetMotor(struct R2JointDesc *desc, uint32_t joint_axis, R2Real target_position, R2Real target_velocity, @@ -8341,8 +8189,8 @@ R2Status r2JointDesc_SetMotor(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetMotor(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetMotor(struct R2ImpulseJointHandle handle, uint32_t joint_axis, R2Real target_position, R2Real target_velocity, @@ -8354,8 +8202,8 @@ R2Status r2ImpulseJoint_SetMotor(struct R2ImpulseJointHandle handle, * Set the joint desc maximum motor force or torque on an axis. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetMotorMaxForce(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetMotorMaxForce(struct R2JointDesc *desc, uint32_t joint_axis, R2Real max_force); @@ -8364,8 +8212,8 @@ R2Status r2JointDesc_SetMotorMaxForce(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetMotorMaxForce(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetMotorMaxForce(struct R2ImpulseJointHandle handle, uint32_t joint_axis, R2Real max_force, R2Bool wake_up); @@ -8374,8 +8222,8 @@ R2Status r2ImpulseJoint_SetMotorMaxForce(struct R2ImpulseJointHandle handle, * Set the joint desc motor model on an axis (0 = acceleration-based, 1 = force-based). * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetMotorModel(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetMotorModel(struct R2JointDesc *desc, uint32_t joint_axis, uint32_t model); @@ -8384,8 +8232,8 @@ R2Status r2JointDesc_SetMotorModel(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetMotorModel(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetMotorModel(struct R2ImpulseJointHandle handle, uint32_t joint_axis, uint32_t model, R2Bool wake_up); @@ -8394,8 +8242,8 @@ R2Status r2ImpulseJoint_SetMotorModel(struct R2ImpulseJointHandle handle, * Set the joint desc application-owned 128-bit user value. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetUserData(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetUserData(struct R2JointDesc *desc, struct R2UserData value); /** @@ -8403,8 +8251,8 @@ R2Status r2JointDesc_SetUserData(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetUserData(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetUserData(struct R2ImpulseJointHandle handle, struct R2UserData value, R2Bool wake_up); @@ -8412,8 +8260,8 @@ R2Status r2ImpulseJoint_SetUserData(struct R2ImpulseJointHandle handle, * Set the joint desc motor position target and spring coefficients on an axis. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetMotorPosition(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetMotorPosition(struct R2JointDesc *desc, uint32_t joint_axis, R2Real target_position, R2Real stiffness, @@ -8424,8 +8272,8 @@ R2Status r2JointDesc_SetMotorPosition(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetMotorPosition(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetMotorPosition(struct R2ImpulseJointHandle handle, uint32_t joint_axis, R2Real target_position, R2Real stiffness, @@ -8436,8 +8284,8 @@ R2Status r2ImpulseJoint_SetMotorPosition(struct R2ImpulseJointHandle handle, * Set the joint desc motor velocity target and damping factor on an axis. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2JointDesc_SetMotorVelocity(struct R2JointDesc *desc, +RAPIER_API +R2Status RAPIER_CALL r2JointDesc_SetMotorVelocity(struct R2JointDesc *desc, uint32_t joint_axis, R2Real target_velocity, R2Real factor); @@ -8447,8 +8295,8 @@ R2Status r2JointDesc_SetMotorVelocity(struct R2JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R2Status r2ImpulseJoint_SetMotorVelocity(struct R2ImpulseJointHandle handle, +RAPIER_API +R2Status RAPIER_CALL r2ImpulseJoint_SetMotorVelocity(struct R2ImpulseJointHandle handle, uint32_t joint_axis, R2Real target_velocity, R2Real factor, @@ -8460,8 +8308,8 @@ R2Status r2ImpulseJoint_SetMotorVelocity(struct R2ImpulseJointHandle handle, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2ConvexDecompositionSharedShape(struct R2VectorView vertices, +RAPIER_API +R2SharedShape *RAPIER_CALL r2ConvexDecompositionSharedShape(struct R2VectorView vertices, R2SurfaceElementView indices); /** @@ -8470,8 +8318,8 @@ R2SharedShape *r2ConvexDecompositionSharedShape(struct R2VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2VoxelsSharedShapeFromPoints(struct R2Vector voxel_size, +RAPIER_API +R2SharedShape *RAPIER_CALL r2VoxelsSharedShapeFromPoints(struct R2Vector voxel_size, struct R2VectorView points); /** @@ -8480,8 +8328,8 @@ R2SharedShape *r2VoxelsSharedShapeFromPoints(struct R2Vector voxel_size, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2VoxelizedMeshSharedShape(struct R2VectorView vertices, +RAPIER_API +R2SharedShape *RAPIER_CALL r2VoxelizedMeshSharedShape(struct R2VectorView vertices, R2SurfaceElementView indices, R2Real voxel_size); @@ -8490,7 +8338,7 @@ R2SharedShape *r2VoxelizedMeshSharedShape(struct R2VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R2SharedShape *r2ConvexHullSharedShape(struct R2VectorView vertices); +RAPIER_API R2SharedShape *RAPIER_CALL r2ConvexHullSharedShape(struct R2VectorView vertices); /** * Create an owned triangle mesh from vertices and triangle indices. Release it with @@ -8498,8 +8346,8 @@ RAPIER_API RAPIER_CALL R2SharedShape *r2ConvexHullSharedShape(struct R2VectorVie * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2TrimeshSharedShape(struct R2VectorView vertices, +RAPIER_API +R2SharedShape *RAPIER_CALL r2TrimeshSharedShape(struct R2VectorView vertices, struct R2TriangleView indices); /** @@ -8507,8 +8355,8 @@ R2SharedShape *r2TrimeshSharedShape(struct R2VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2PolylineSharedShape(struct R2VectorView vertices, +RAPIER_API +R2SharedShape *RAPIER_CALL r2PolylineSharedShape(struct R2VectorView vertices, struct R2EdgeView indices); #if defined(RAPIER_DIM2) @@ -8518,8 +8366,8 @@ R2SharedShape *r2PolylineSharedShape(struct R2VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2OrientedPolylineSharedShape(struct R2VectorView vertices, +RAPIER_API +R2SharedShape *RAPIER_CALL r2OrientedPolylineSharedShape(struct R2VectorView vertices, struct R2EdgeView indices); #endif @@ -8530,8 +8378,7 @@ R2SharedShape *r2OrientedPolylineSharedShape(struct R2VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2ConvexPolylineSharedShape(struct R2VectorView vertices); +RAPIER_API R2SharedShape *RAPIER_CALL r2ConvexPolylineSharedShape(struct R2VectorView vertices); #endif /** @@ -8539,8 +8386,8 @@ R2SharedShape *r2ConvexPolylineSharedShape(struct R2VectorView vertices); * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2RoundConvexHullSharedShape(struct R2VectorView vertices, +RAPIER_API +R2SharedShape *RAPIER_CALL r2RoundConvexHullSharedShape(struct R2VectorView vertices, R2Real border_radius); /** @@ -8549,8 +8396,8 @@ R2SharedShape *r2RoundConvexHullSharedShape(struct R2VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2TrimeshSharedShapeWithFlags(struct R2VectorView vertices, +RAPIER_API +R2SharedShape *RAPIER_CALL r2TrimeshSharedShapeWithFlags(struct R2VectorView vertices, struct R2TriangleView indices, uint32_t flags); @@ -8558,22 +8405,22 @@ R2SharedShape *r2TrimeshSharedShapeWithFlags(struct R2VectorView vertices, * Create an owned world. Release it with FreeWorld. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R2World *r2NewWorld(void); +RAPIER_API struct R2World *RAPIER_CALL r2NewWorld(void); /** * Free a world. NULL is allowed. Rejects destruction from an active callback. * The caller must prevent other threads from starting calls during destruction. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R2Status r2FreeWorld(struct R2World *world); +RAPIER_API R2Status RAPIER_CALL r2FreeWorld(struct R2World *world); /** * Compute a velocity correction from callback-visible body state, updating the PID controller * history. The context is valid only during its callback. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2VelocityCorrection r2ReadPidController_RigidBodyCorrection(const struct R2ReadContext *context, +RAPIER_API +struct R2VelocityCorrection RAPIER_CALL r2ReadPidController_RigidBodyCorrection(const struct R2ReadContext *context, struct R2PidController *controller, R2Real dt, struct R2RigidBodyHandle body, @@ -8586,15 +8433,15 @@ struct R2VelocityCorrection r2ReadPidController_RigidBodyCorrection(const struct * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL size_t r2ReadRigidBodyCount(const struct R2ReadContext *context); +RAPIER_API size_t RAPIER_CALL r2ReadRigidBodyCount(const struct R2ReadContext *context); /** * Copy entity handles. Uses only the callback-scoped read context; never retain the context. * @see @ref output_buffers * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -size_t r2ReadRigidBodyHandles(const struct R2ReadContext *context, +RAPIER_API +size_t RAPIER_CALL r2ReadRigidBodyHandles(const struct R2ReadContext *context, struct R2RigidBodyHandle *buffer, size_t capacity); @@ -8603,8 +8450,8 @@ size_t r2ReadRigidBodyHandles(const struct R2ReadContext *context, * false. Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadRigidBody_Contains(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadRigidBody_Contains(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8612,15 +8459,15 @@ R2Bool r2ReadRigidBody_Contains(const struct R2ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL size_t r2ReadColliderCount(const struct R2ReadContext *context); +RAPIER_API size_t RAPIER_CALL r2ReadColliderCount(const struct R2ReadContext *context); /** * Copy entity handles. Uses only the callback-scoped read context; never retain the context. * @see @ref output_buffers * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -size_t r2ReadColliderHandles(const struct R2ReadContext *context, +RAPIER_API +size_t RAPIER_CALL r2ReadColliderHandles(const struct R2ReadContext *context, struct R2ColliderHandle *buffer, size_t capacity); @@ -8629,8 +8476,8 @@ size_t r2ReadColliderHandles(const struct R2ReadContext *context, * Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadCollider_Contains(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadCollider_Contains(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8639,8 +8486,8 @@ R2Bool r2ReadCollider_Contains(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -size_t r2ReadCollider_ShapeIdentity(const struct R2ReadContext *context, +RAPIER_API +size_t RAPIER_CALL r2ReadCollider_ShapeIdentity(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8648,8 +8495,8 @@ size_t r2ReadCollider_ShapeIdentity(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2MassProperties r2ReadCollider_MassProperties(const struct R2ReadContext *context, +RAPIER_API +struct R2MassProperties RAPIER_CALL r2ReadCollider_MassProperties(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8657,8 +8504,8 @@ struct R2MassProperties r2ReadCollider_MassProperties(const struct R2ReadContext * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -uint8_t r2ReadRigidBody_LockedAxes(const struct R2ReadContext *context, +RAPIER_API +uint8_t RAPIER_CALL r2ReadRigidBody_LockedAxes(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8666,8 +8513,8 @@ uint8_t r2ReadRigidBody_LockedAxes(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadCollider_IsVoxels(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadCollider_IsVoxels(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8675,8 +8522,8 @@ R2Bool r2ReadCollider_IsVoxels(const struct R2ReadContext *context, * read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2VoxelQuery r2ReadCollider_VoxelAtFlatId(const struct R2ReadContext *context, +RAPIER_API +struct R2VoxelQuery RAPIER_CALL r2ReadCollider_VoxelAtFlatId(const struct R2ReadContext *context, struct R2ColliderHandle handle, uint32_t id); @@ -8685,8 +8532,8 @@ struct R2VoxelQuery r2ReadCollider_VoxelAtFlatId(const struct R2ReadContext *con * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Pose r2ReadRigidBody_NextPosition(const struct R2ReadContext *context, +RAPIER_API +struct R2Pose RAPIER_CALL r2ReadRigidBody_NextPosition(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8694,8 +8541,8 @@ struct R2Pose r2ReadRigidBody_NextPosition(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Rotation r2ReadRigidBody_Rotation(const struct R2ReadContext *context, +RAPIER_API +struct R2Rotation RAPIER_CALL r2ReadRigidBody_Rotation(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8703,8 +8550,8 @@ struct R2Rotation r2ReadRigidBody_Rotation(const struct R2ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Vector r2ReadRigidBody_CenterOfMass(const struct R2ReadContext *context, +RAPIER_API +struct R2Vector RAPIER_CALL r2ReadRigidBody_CenterOfMass(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8712,8 +8559,8 @@ struct R2Vector r2ReadRigidBody_CenterOfMass(const struct R2ReadContext *context * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Vector r2ReadRigidBody_LocalCenterOfMass(const struct R2ReadContext *context, +RAPIER_API +struct R2Vector RAPIER_CALL r2ReadRigidBody_LocalCenterOfMass(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8721,8 +8568,8 @@ struct R2Vector r2ReadRigidBody_LocalCenterOfMass(const struct R2ReadContext *co * read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Vector r2ReadRigidBody_UserForce(const struct R2ReadContext *context, +RAPIER_API +struct R2Vector RAPIER_CALL r2ReadRigidBody_UserForce(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8730,8 +8577,8 @@ struct R2Vector r2ReadRigidBody_UserForce(const struct R2ReadContext *context, * read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2AngVector r2ReadRigidBody_UserTorque(const struct R2ReadContext *context, +RAPIER_API +R2AngVector RAPIER_CALL r2ReadRigidBody_UserTorque(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8739,8 +8586,8 @@ R2AngVector r2ReadRigidBody_UserTorque(const struct R2ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -uint32_t r2ReadRigidBody_BodyType(const struct R2ReadContext *context, +RAPIER_API +uint32_t RAPIER_CALL r2ReadRigidBody_BodyType(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8748,8 +8595,8 @@ uint32_t r2ReadRigidBody_BodyType(const struct R2ReadContext *context, * context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadRigidBody_Mass(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadRigidBody_Mass(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8757,8 +8604,8 @@ R2Real r2ReadRigidBody_Mass(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadRigidBody_GravityScale(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadRigidBody_GravityScale(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8766,8 +8613,8 @@ R2Real r2ReadRigidBody_GravityScale(const struct R2ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadRigidBody_LinearDamping(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadRigidBody_LinearDamping(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8775,8 +8622,8 @@ R2Real r2ReadRigidBody_LinearDamping(const struct R2ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadRigidBody_AngularDamping(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadRigidBody_AngularDamping(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8784,8 +8631,8 @@ R2Real r2ReadRigidBody_AngularDamping(const struct R2ReadContext *context, * the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadRigidBody_KineticEnergy(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadRigidBody_KineticEnergy(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8793,8 +8640,8 @@ R2Real r2ReadRigidBody_KineticEnergy(const struct R2ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadRigidBody_SoftCcdPrediction(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadRigidBody_SoftCcdPrediction(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8802,8 +8649,8 @@ R2Real r2ReadRigidBody_SoftCcdPrediction(const struct R2ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadRigidBody_IsCcdEnabled(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadRigidBody_IsCcdEnabled(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8811,8 +8658,8 @@ R2Bool r2ReadRigidBody_IsCcdEnabled(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadRigidBody_IsDynamic(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadRigidBody_IsDynamic(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8820,8 +8667,8 @@ R2Bool r2ReadRigidBody_IsDynamic(const struct R2ReadContext *context, * only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2SoftBodyHandle r2ReadRigidBody_SoftBody(const struct R2ReadContext *context, +RAPIER_API +struct R2SoftBodyHandle RAPIER_CALL r2ReadRigidBody_SoftBody(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8829,8 +8676,8 @@ struct R2SoftBodyHandle r2ReadRigidBody_SoftBody(const struct R2ReadContext *con * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadRigidBody_IsSoftFrame(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadRigidBody_IsSoftFrame(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8838,8 +8685,8 @@ R2Bool r2ReadRigidBody_IsSoftFrame(const struct R2ReadContext *context, * the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadRigidBody_IsFixed(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadRigidBody_IsFixed(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8847,8 +8694,8 @@ R2Bool r2ReadRigidBody_IsFixed(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadRigidBody_IsKinematic(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadRigidBody_IsKinematic(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8856,8 +8703,8 @@ R2Bool r2ReadRigidBody_IsKinematic(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadRigidBody_IsMoving(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadRigidBody_IsMoving(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8865,8 +8712,8 @@ R2Bool r2ReadRigidBody_IsMoving(const struct R2ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadRigidBody_IsCcdActive(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadRigidBody_IsCcdActive(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -8874,8 +8721,8 @@ R2Bool r2ReadRigidBody_IsCcdActive(const struct R2ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Vector r2ReadRigidBody_VelocityAtPoint(const struct R2ReadContext *context, +RAPIER_API +struct R2Vector RAPIER_CALL r2ReadRigidBody_VelocityAtPoint(const struct R2ReadContext *context, struct R2RigidBodyHandle handle, struct R2Vector point); @@ -8885,8 +8732,8 @@ struct R2Vector r2ReadRigidBody_VelocityAtPoint(const struct R2ReadContext *cont * @see @ref output_buffers * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -size_t r2ReadRigidBody_Colliders(const struct R2ReadContext *context, +RAPIER_API +size_t RAPIER_CALL r2ReadRigidBody_Colliders(const struct R2ReadContext *context, struct R2RigidBodyHandle handle, struct R2ColliderHandle *buffer, size_t capacity); @@ -8897,8 +8744,8 @@ size_t r2ReadRigidBody_Colliders(const struct R2ReadContext *context, * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadRigidBody_GyroscopicForcesEnabled(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadRigidBody_GyroscopicForcesEnabled(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); #endif @@ -8907,8 +8754,8 @@ R2Bool r2ReadRigidBody_GyroscopicForcesEnabled(const struct R2ReadContext *conte * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Rotation r2ReadCollider_Rotation(const struct R2ReadContext *context, +RAPIER_API +struct R2Rotation RAPIER_CALL r2ReadCollider_Rotation(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8916,8 +8763,8 @@ struct R2Rotation r2ReadCollider_Rotation(const struct R2ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2InteractionGroups r2ReadCollider_CollisionGroups(const struct R2ReadContext *context, +RAPIER_API +struct R2InteractionGroups RAPIER_CALL r2ReadCollider_CollisionGroups(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8925,8 +8772,8 @@ struct R2InteractionGroups r2ReadCollider_CollisionGroups(const struct R2ReadCon * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2InteractionGroups r2ReadCollider_SolverGroups(const struct R2ReadContext *context, +RAPIER_API +struct R2InteractionGroups RAPIER_CALL r2ReadCollider_SolverGroups(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8934,8 +8781,8 @@ struct R2InteractionGroups r2ReadCollider_SolverGroups(const struct R2ReadContex * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2UserData r2ReadCollider_UserData(const struct R2ReadContext *context, +RAPIER_API +struct R2UserData RAPIER_CALL r2ReadCollider_UserData(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8943,16 +8790,16 @@ struct R2UserData r2ReadCollider_UserData(const struct R2ReadContext *context, * R2_CONTACT_FORCE_EVENTS). Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -uint32_t r2ReadCollider_ActiveEvents(const struct R2ReadContext *context, +RAPIER_API +uint32_t RAPIER_CALL r2ReadCollider_ActiveEvents(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** * Return the collider mass. Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadCollider_Mass(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadCollider_Mass(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8960,8 +8807,8 @@ R2Real r2ReadCollider_Mass(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadCollider_Density(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadCollider_Density(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8969,8 +8816,8 @@ R2Real r2ReadCollider_Density(const struct R2ReadContext *context, * context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadCollider_Volume(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadCollider_Volume(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8978,8 +8825,8 @@ R2Real r2ReadCollider_Volume(const struct R2ReadContext *context, * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadCollider_ContactSkin(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadCollider_ContactSkin(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8987,8 +8834,8 @@ R2Real r2ReadCollider_ContactSkin(const struct R2ReadContext *context, * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadCollider_ContactForceEventThreshold(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadCollider_ContactForceEventThreshold(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -8996,8 +8843,8 @@ R2Real r2ReadCollider_ContactForceEventThreshold(const struct R2ReadContext *con * the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadCollider_IsEnabled(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadCollider_IsEnabled(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -9005,8 +8852,8 @@ R2Bool r2ReadCollider_IsEnabled(const struct R2ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Aabb r2ReadCollider_ComputeAabb(const struct R2ReadContext *context, +RAPIER_API +struct R2Aabb RAPIER_CALL r2ReadCollider_ComputeAabb(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -9015,8 +8862,8 @@ struct R2Aabb r2ReadCollider_ComputeAabb(const struct R2ReadContext *context, * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2SharedShape *r2ReadCollider_CloneShape(const struct R2ReadContext *context, +RAPIER_API +R2SharedShape *RAPIER_CALL r2ReadCollider_CloneShape(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -9024,8 +8871,8 @@ R2SharedShape *r2ReadCollider_CloneShape(const struct R2ReadContext *context, * has already been freed. Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Status r2ReadRigidBody_ValidateHandle(const struct R2ReadContext *context, +RAPIER_API +R2Status RAPIER_CALL r2ReadRigidBody_ValidateHandle(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -9033,8 +8880,8 @@ R2Status r2ReadRigidBody_ValidateHandle(const struct R2ReadContext *context, * has already been freed. Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Status r2ReadCollider_ValidateHandle(const struct R2ReadContext *context, +RAPIER_API +R2Status RAPIER_CALL r2ReadCollider_ValidateHandle(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -9042,8 +8889,8 @@ R2Status r2ReadCollider_ValidateHandle(const struct R2ReadContext *context, * the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Pose r2ReadRigidBody_Position(const struct R2ReadContext *context, +RAPIER_API +struct R2Pose RAPIER_CALL r2ReadRigidBody_Position(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -9051,8 +8898,8 @@ struct R2Pose r2ReadRigidBody_Position(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Vector r2ReadRigidBody_Translation(const struct R2ReadContext *context, +RAPIER_API +struct R2Vector RAPIER_CALL r2ReadRigidBody_Translation(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -9060,8 +8907,8 @@ struct R2Vector r2ReadRigidBody_Translation(const struct R2ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Vector r2ReadRigidBody_Linvel(const struct R2ReadContext *context, +RAPIER_API +struct R2Vector RAPIER_CALL r2ReadRigidBody_Linvel(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -9069,8 +8916,8 @@ struct R2Vector r2ReadRigidBody_Linvel(const struct R2ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2AngVector r2ReadRigidBody_Angvel(const struct R2ReadContext *context, +RAPIER_API +R2AngVector RAPIER_CALL r2ReadRigidBody_Angvel(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -9078,8 +8925,8 @@ R2AngVector r2ReadRigidBody_Angvel(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadRigidBody_IsSleeping(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadRigidBody_IsSleeping(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -9087,8 +8934,8 @@ R2Bool r2ReadRigidBody_IsSleeping(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadRigidBody_IsEnabled(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadRigidBody_IsEnabled(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -9096,8 +8943,8 @@ R2Bool r2ReadRigidBody_IsEnabled(const struct R2ReadContext *context, * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2UserData r2ReadRigidBody_UserData(const struct R2ReadContext *context, +RAPIER_API +struct R2UserData RAPIER_CALL r2ReadRigidBody_UserData(const struct R2ReadContext *context, struct R2RigidBodyHandle handle); /** @@ -9105,8 +8952,8 @@ struct R2UserData r2ReadRigidBody_UserData(const struct R2ReadContext *context, * the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Pose r2ReadCollider_Position(const struct R2ReadContext *context, +RAPIER_API +struct R2Pose RAPIER_CALL r2ReadCollider_Position(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -9114,8 +8961,8 @@ struct R2Pose r2ReadCollider_Position(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2Vector r2ReadCollider_Translation(const struct R2ReadContext *context, +RAPIER_API +struct R2Vector RAPIER_CALL r2ReadCollider_Translation(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -9123,8 +8970,8 @@ struct R2Vector r2ReadCollider_Translation(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadCollider_Friction(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadCollider_Friction(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -9132,8 +8979,8 @@ R2Real r2ReadCollider_Friction(const struct R2ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Real r2ReadCollider_Restitution(const struct R2ReadContext *context, +RAPIER_API +R2Real RAPIER_CALL r2ReadCollider_Restitution(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -9141,8 +8988,8 @@ R2Real r2ReadCollider_Restitution(const struct R2ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R2Bool r2ReadCollider_IsSensor(const struct R2ReadContext *context, +RAPIER_API +R2Bool RAPIER_CALL r2ReadCollider_IsSensor(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -9150,8 +8997,8 @@ R2Bool r2ReadCollider_IsSensor(const struct R2ReadContext *context, * with OK status. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R2RigidBodyHandle r2ReadCollider_Parent(const struct R2ReadContext *context, +RAPIER_API +struct R2RigidBodyHandle RAPIER_CALL r2ReadCollider_Parent(const struct R2ReadContext *context, struct R2ColliderHandle handle); /** @@ -9160,8 +9007,8 @@ struct R2RigidBodyHandle r2ReadCollider_Parent(const struct R2ReadContext *conte * @see @ref output_buffers * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -size_t r2ReadRigidBodyReadStates(const struct R2ReadContext *context, +RAPIER_API +size_t RAPIER_CALL r2ReadRigidBodyReadStates(const struct R2ReadContext *context, const struct R2RigidBodyHandle *handles, size_t handle_count, struct R2RigidBodyState *states, @@ -12992,67 +12839,66 @@ extern "C" { * Return native default soft body material. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R3SoftBodyMaterial r3DefaultSoftBodyMaterial(void); +RAPIER_API struct R3SoftBodyMaterial RAPIER_CALL r3DefaultSoftBodyMaterial(void); /** * Return native default soft recovery settings. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R3SoftRecoverySettings r3DefaultSoftRecoverySettings(void); +RAPIER_API struct R3SoftRecoverySettings RAPIER_CALL r3DefaultSoftRecoverySettings(void); #if defined(RAPIER_FEM) /** * Return native default soft fem parameters. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R3SoftFemParameters r3DefaultSoftFemParameters(void); +RAPIER_API struct R3SoftFemParameters RAPIER_CALL r3DefaultSoftFemParameters(void); #endif /** * Return native default soft bodies settings. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R3SoftBodiesSettings r3DefaultSoftBodiesSettings(void); +RAPIER_API struct R3SoftBodiesSettings RAPIER_CALL r3DefaultSoftBodiesSettings(void); /** * Return native default integration parameters. This POD value owns no resources. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R3IntegrationParameters r3DefaultIntegrationParameters(void); +RAPIER_API struct R3IntegrationParameters RAPIER_CALL r3DefaultIntegrationParameters(void); /** * Return a copy of all world integration settings. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -struct R3IntegrationParameters r3IntegrationParameters(const struct R3World *world); +RAPIER_API struct R3IntegrationParameters RAPIER_CALL r3IntegrationParameters(const struct R3World *world); /** * Copies validated values; does not expose a writable alias to Rust memory. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3SetIntegrationParameters(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3SetIntegrationParameters(struct R3World *world, const struct R3IntegrationParameters *data); /** * Return native default joint desc. This POD value owns no resources. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R3JointDesc r3DefaultJointDesc(void); +RAPIER_API struct R3JointDesc RAPIER_CALL r3DefaultJointDesc(void); /** * Return a fixed joint description with native defaults; no allocation. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R3JointDesc r3FixedJointDesc(void); +RAPIER_API struct R3JointDesc RAPIER_CALL r3FixedJointDesc(void); #if defined(RAPIER_DIM2) /** * Return a revolute joint description with native defaults; no allocation. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R3JointDesc r3RevoluteJointDesc(void); +RAPIER_API struct R3JointDesc RAPIER_CALL r3RevoluteJointDesc(void); #endif #if defined(RAPIER_DIM3) @@ -13060,27 +12906,27 @@ RAPIER_API RAPIER_CALL struct R3JointDesc r3RevoluteJointDesc(void); * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R3JointDesc r3RevoluteJointDesc(struct R3Vector axis_vector); +RAPIER_API struct R3JointDesc RAPIER_CALL r3RevoluteJointDesc(struct R3Vector axis_vector); #endif /** * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R3JointDesc r3PrismaticJointDesc(struct R3Vector axis_vector); +RAPIER_API struct R3JointDesc RAPIER_CALL r3PrismaticJointDesc(struct R3Vector axis_vector); /** * Return a rope joint description with native defaults; no allocation. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R3JointDesc r3RopeJointDesc(R3Real length); +RAPIER_API struct R3JointDesc RAPIER_CALL r3RopeJointDesc(R3Real length); /** * Return a spring joint description with native defaults; no allocation. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R3JointDesc r3SpringJointDesc(R3Real length, +RAPIER_API +struct R3JointDesc RAPIER_CALL r3SpringJointDesc(R3Real length, R3Real stiffness, R3Real damping); @@ -13089,7 +12935,7 @@ struct R3JointDesc r3SpringJointDesc(R3Real length, * Return a spherical joint description with native defaults; no allocation. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R3JointDesc r3SphericalJointDesc(void); +RAPIER_API struct R3JointDesc RAPIER_CALL r3SphericalJointDesc(void); #endif #if defined(RAPIER_DIM2) @@ -13097,7 +12943,7 @@ RAPIER_API RAPIER_CALL struct R3JointDesc r3SphericalJointDesc(void); * Returns a joint description. Invalid axes produce nonfinite frames, rejected on insertion. * @ingroup joints */ -RAPIER_API RAPIER_CALL struct R3JointDesc r3PinSlotJointDesc(struct R3Vector axis_vector); +RAPIER_API struct R3JointDesc RAPIER_CALL r3PinSlotJointDesc(struct R3Vector axis_vector); #endif /** @@ -13105,8 +12951,8 @@ RAPIER_API RAPIER_CALL struct R3JointDesc r3PinSlotJointDesc(struct R3Vector axi * wake_up wakes the connected bodies. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R3ImpulseJointHandle r3InsertImpulseJoint(struct R3RigidBodyHandle body1, +RAPIER_API +struct R3ImpulseJointHandle RAPIER_CALL r3InsertImpulseJoint(struct R3RigidBodyHandle body1, struct R3RigidBodyHandle body2, const struct R3JointDesc *joint); @@ -13115,8 +12961,8 @@ struct R3ImpulseJointHandle r3InsertImpulseJoint(struct R3RigidBodyHandle body1, * failure; check r3LastStatus. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R3MultibodyJointHandle r3InsertMultibodyJoint(struct R3RigidBodyHandle body1, +RAPIER_API +struct R3MultibodyJointHandle RAPIER_CALL r3InsertMultibodyJoint(struct R3RigidBodyHandle body1, struct R3RigidBodyHandle body2, const struct R3JointDesc *joint); @@ -13124,29 +12970,29 @@ struct R3MultibodyJointHandle r3InsertMultibodyJoint(struct R3RigidBodyHandle bo * Return native default soft body desc. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R3SoftBodyDesc r3DefaultSoftBodyDesc(void); +RAPIER_API struct R3SoftBodyDesc RAPIER_CALL r3DefaultSoftBodyDesc(void); /** * Consumes no caller-owned resources. All borrowed arrays may be released on return. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyHandle r3InsertSoftBody(struct R3World *world, +RAPIER_API +struct R3SoftBodyHandle RAPIER_CALL r3InsertSoftBody(struct R3World *world, const struct R3SoftBodyDesc *desc); /** * Return native default soft mesh binding desc. This POD value owns no resources. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL struct R3SoftMeshBindingDesc r3DefaultSoftMeshBindingDesc(void); +RAPIER_API struct R3SoftMeshBindingDesc RAPIER_CALL r3DefaultSoftMeshBindingDesc(void); /** * Create a deformable collider bound to a soft-body cluster. The world owns the collider; binding * arrays are borrowed only during insertion. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderHandle r3InsertDeformableCollider(const struct R3ColliderDesc *collider, +RAPIER_API +struct R3ColliderHandle RAPIER_CALL r3InsertDeformableCollider(const struct R3ColliderDesc *collider, const struct R3SoftMeshBindingDesc *binding, struct R3RigidBodyHandle parent); @@ -13154,7 +13000,7 @@ struct R3ColliderHandle r3InsertDeformableCollider(const struct R3ColliderDesc * * Return native default query options. This POD value owns no resources. * @ingroup queries */ -RAPIER_API RAPIER_CALL struct R3QueryOptions r3DefaultQueryOptions(void); +RAPIER_API struct R3QueryOptions RAPIER_CALL r3DefaultQueryOptions(void); /** * Return the closest ray hit, or report R3_NOT_FOUND on a miss. The ray is origin + direction * t @@ -13164,8 +13010,8 @@ RAPIER_API RAPIER_CALL struct R3QueryOptions r3DefaultQueryOptions(void); * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -struct R3RayHit r3CastRay(const struct R3World *world, +RAPIER_API +struct R3RayHit RAPIER_CALL r3CastRay(const struct R3World *world, const struct R3QueryOptions *query_options, struct R3Vector origin, struct R3Vector direction, @@ -13179,8 +13025,8 @@ struct R3RayHit r3CastRay(const struct R3World *world, * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -struct R3PointProjection r3ProjectPoint(const struct R3World *world, +RAPIER_API +struct R3PointProjection RAPIER_CALL r3ProjectPoint(const struct R3World *world, const struct R3QueryOptions *query_options, struct R3Vector point, R3Real max_distance, @@ -13193,8 +13039,8 @@ struct R3PointProjection r3ProjectPoint(const struct R3World *world, * DetectCollisions call. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -struct R3ShapeCastHit r3CastShape(const struct R3World *world, +RAPIER_API +struct R3ShapeCastHit RAPIER_CALL r3CastShape(const struct R3World *world, const struct R3QueryOptions *query_options, struct R3Pose pose, struct R3Vector velocity, @@ -13208,8 +13054,8 @@ struct R3ShapeCastHit r3CastShape(const struct R3World *world, * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -size_t r3IntersectPoint(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3IntersectPoint(const struct R3World *world, const struct R3QueryOptions *query_options, struct R3Vector point, struct R3ColliderHandle *buffer, @@ -13223,8 +13069,8 @@ size_t r3IntersectPoint(const struct R3World *world, * DetectCollisions call. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -size_t r3IntersectShape(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3IntersectShape(const struct R3World *world, const struct R3QueryOptions *query_options, struct R3Pose pose, const R3SharedShape *shape, @@ -13239,8 +13085,8 @@ size_t r3IntersectShape(const struct R3World *world, * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -size_t r3IntersectAabbConservative(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3IntersectAabbConservative(const struct R3World *world, const struct R3QueryOptions *query_options, struct R3Aabb aabb, struct R3ColliderHandle *buffer, @@ -13253,8 +13099,8 @@ size_t r3IntersectAabbConservative(const struct R3World *world, * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -struct R3RayToi r3CastRayToi(const struct R3World *world, +RAPIER_API +struct R3RayToi RAPIER_CALL r3CastRayToi(const struct R3World *world, const struct R3QueryOptions *query_options, struct R3Vector origin, struct R3Vector direction, @@ -13268,8 +13114,8 @@ struct R3RayToi r3CastRayToi(const struct R3World *world, * DetectCollisions call. * @ingroup queries */ -RAPIER_API RAPIER_CALL -struct R3OptionalRayHit r3TryCastRay(const struct R3World *world, +RAPIER_API +struct R3OptionalRayHit RAPIER_CALL r3TryCastRay(const struct R3World *world, const struct R3QueryOptions *query_options, struct R3Vector origin, struct R3Vector direction, @@ -13280,66 +13126,65 @@ struct R3OptionalRayHit r3TryCastRay(const struct R3World *world, * Return a dynamic rigid-body description with native defaults; no allocation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3DynamicRigidBodyDesc(void); +RAPIER_API struct R3RigidBodyDesc RAPIER_CALL r3DynamicRigidBodyDesc(void); /** * Return a fixed rigid-body description with native defaults; no allocation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3FixedRigidBodyDesc(void); +RAPIER_API struct R3RigidBodyDesc RAPIER_CALL r3FixedRigidBodyDesc(void); /** * Return a kinematic position based rigid-body description with native defaults; no allocation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3KinematicPositionBasedRigidBodyDesc(void); +RAPIER_API struct R3RigidBodyDesc RAPIER_CALL r3KinematicPositionBasedRigidBodyDesc(void); /** * Return a kinematic velocity based rigid-body description with native defaults; no allocation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R3RigidBodyDesc r3KinematicVelocityBasedRigidBodyDesc(void); +RAPIER_API struct R3RigidBodyDesc RAPIER_CALL r3KinematicVelocityBasedRigidBodyDesc(void); /** * Return native default shape desc. This POD value owns no resources. * @ingroup shapes */ -RAPIER_API RAPIER_CALL struct R3ShapeDesc r3DefaultShapeDesc(void); +RAPIER_API struct R3ShapeDesc RAPIER_CALL r3DefaultShapeDesc(void); /** * Build an owned shared shape from a description; release it with r3FreeSharedShape. Borrowed * inputs may be released after this call. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R3SharedShape *r3ShapeDesc_Build(const struct R3ShapeDesc *desc); +RAPIER_API R3SharedShape *RAPIER_CALL r3ShapeDesc_Build(const struct R3ShapeDesc *desc); /** * Return native default collider desc. This POD value owns no resources. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R3ColliderDesc r3DefaultColliderDesc(void); +RAPIER_API struct R3ColliderDesc RAPIER_CALL r3DefaultColliderDesc(void); /** * Return a ball description with the supplied radius. * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R3ColliderDesc r3BallColliderDesc(R3Real radius); +RAPIER_API struct R3ColliderDesc RAPIER_CALL r3BallColliderDesc(R3Real radius); /** * Return an axis-aligned box description with the supplied half-extents. * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderDesc r3CuboidColliderDesc(struct R3Vector half_extents); +RAPIER_API struct R3ColliderDesc RAPIER_CALL r3CuboidColliderDesc(struct R3Vector half_extents); /** * Create a body from the description and return its world-bound handle. The world owns the body. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R3RigidBodyHandle r3InsertRigidBody(struct R3World *world, +RAPIER_API +struct R3RigidBodyHandle RAPIER_CALL r3InsertRigidBody(struct R3World *world, const struct R3RigidBodyDesc *desc); /** @@ -13348,8 +13193,8 @@ struct R3RigidBodyHandle r3InsertRigidBody(struct R3World *world, * Invalid or removed parents fail without inserting a collider. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderHandle r3InsertCollider(struct R3RigidBodyHandle parent, +RAPIER_API +struct R3ColliderHandle RAPIER_CALL r3InsertCollider(struct R3RigidBodyHandle parent, const struct R3ColliderDesc *desc); /** @@ -13357,62 +13202,61 @@ struct R3ColliderHandle r3InsertCollider(struct R3RigidBodyHandle parent, * The description is borrowed through this call. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderHandle r3InsertColliderWithoutParent(struct R3World *world, +RAPIER_API +struct R3ColliderHandle RAPIER_CALL r3InsertColliderWithoutParent(struct R3World *world, const struct R3ColliderDesc *desc); /** * Return POD structure sizes for checking foreign-language layouts against this library. * @ingroup errors */ -RAPIER_API RAPIER_CALL struct R3PodLayout r3PodLayout(void); +RAPIER_API struct R3PodLayout RAPIER_CALL r3PodLayout(void); /** * Allocate a character controller with native defaults; release it with * r3FreeKinematicCharacterController. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R3KinematicCharacterController *r3NewKinematicCharacterController(void); +RAPIER_API struct R3KinematicCharacterController *RAPIER_CALL r3NewKinematicCharacterController(void); /** * Release an owned kinematic character controller. NULL is allowed. Do not pass borrowed pointers * or free the object twice. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3FreeKinematicCharacterController(struct R3KinematicCharacterController *controller); +RAPIER_API +R3Status RAPIER_CALL r3FreeKinematicCharacterController(struct R3KinematicCharacterController *controller); /** * Set the up direction; it must be finite and nonzero and is normalized on input. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3KinematicCharacterController_SetUp(struct R3KinematicCharacterController *controller, +RAPIER_API +R3Status RAPIER_CALL r3KinematicCharacterController_SetUp(struct R3KinematicCharacterController *controller, struct R3Vector up); /** * Set the collision separation margin; use a positive absolute or relative character length. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3KinematicCharacterController_SetOffset(struct R3KinematicCharacterController *controller, +RAPIER_API +R3Status RAPIER_CALL r3KinematicCharacterController_SetOffset(struct R3KinematicCharacterController *controller, struct R3CharacterLength offset); /** * Enable or disable sliding along obstacles. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3KinematicCharacterController_SetSlide(struct R3KinematicCharacterController *controller, +RAPIER_API +R3Status RAPIER_CALL r3KinematicCharacterController_SetSlide(struct R3KinematicCharacterController *controller, R3Bool enabled); /** * Set the maximum climb angle and minimum slide angle, in radians. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3KinematicCharacterController_SetSlopes(struct R3KinematicCharacterController *controller, +RAPIER_API +R3Status RAPIER_CALL r3KinematicCharacterController_SetSlopes(struct R3KinematicCharacterController *controller, R3Real max_climb_angle, R3Real min_slide_angle); @@ -13420,8 +13264,8 @@ R3Status r3KinematicCharacterController_SetSlopes(struct R3KinematicCharacterCon * Configure automatic stepping over obstacles. enabled = 0 disables it. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3KinematicCharacterController_SetAutostep(struct R3KinematicCharacterController *controller, +RAPIER_API +R3Status RAPIER_CALL r3KinematicCharacterController_SetAutostep(struct R3KinematicCharacterController *controller, R3Bool enabled, struct R3CharacterLength max_height, struct R3CharacterLength min_width, @@ -13431,8 +13275,8 @@ R3Status r3KinematicCharacterController_SetAutostep(struct R3KinematicCharacterC * Configure downward ground snapping. enabled = 0 disables it. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3KinematicCharacterController_SetSnapToGround(struct R3KinematicCharacterController *controller, +RAPIER_API +R3Status RAPIER_CALL r3KinematicCharacterController_SetSnapToGround(struct R3KinematicCharacterController *controller, R3Bool enabled, struct R3CharacterLength distance); @@ -13443,8 +13287,8 @@ R3Status r3KinematicCharacterController_SetSnapToGround(struct R3KinematicCharac * DetectCollisions call. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R3CharacterMovement r3KinematicCharacterController_MoveShape(const struct R3World *world, +RAPIER_API +struct R3CharacterMovement RAPIER_CALL r3KinematicCharacterController_MoveShape(const struct R3World *world, const struct R3QueryOptions *options, struct R3KinematicCharacterController *controller, R3Real dt, @@ -13457,8 +13301,8 @@ struct R3CharacterMovement r3KinematicCharacterController_MoveShape(const struct * @see @ref output_buffers * @ingroup controllers */ -RAPIER_API RAPIER_CALL -size_t r3KinematicCharacterController_Collisions(const struct R3KinematicCharacterController *controller, +RAPIER_API +size_t RAPIER_CALL r3KinematicCharacterController_Collisions(const struct R3KinematicCharacterController *controller, struct R3CharacterCollision *buffer, size_t capacity); @@ -13467,8 +13311,8 @@ size_t r3KinematicCharacterController_Collisions(const struct R3KinematicCharact * filter. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3KinematicCharacterController_SolveCharacterCollisionImpulses(const struct R3KinematicCharacterController *controller, +RAPIER_API +R3Status RAPIER_CALL r3KinematicCharacterController_SolveCharacterCollisionImpulses(const struct R3KinematicCharacterController *controller, const R3SharedShape *shape, R3Real dt, R3Real mass, @@ -13479,44 +13323,43 @@ R3Status r3KinematicCharacterController_SolveCharacterCollisionImpulses(const st * r3FreePidController. * @ingroup controllers */ -RAPIER_API RAPIER_CALL struct R3PidController *r3NewPidController(void); +RAPIER_API struct R3PidController *RAPIER_CALL r3NewPidController(void); /** * Release an owned pid controller. NULL is allowed. Do not pass borrowed pointers or free the * object twice. * @ingroup controllers */ -RAPIER_API RAPIER_CALL R3Status r3FreePidController(struct R3PidController *controller); +RAPIER_API R3Status RAPIER_CALL r3FreePidController(struct R3PidController *controller); /** * Return a copy of the proportional, integral, and derivative gains. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R3PidGains r3PidController_Gains(const struct R3PidController *controller); +RAPIER_API struct R3PidGains RAPIER_CALL r3PidController_Gains(const struct R3PidController *controller); /** * Replace the proportional, integral, and derivative gains. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3PidController_SetGains(struct R3PidController *controller, +RAPIER_API +R3Status RAPIER_CALL r3PidController_SetGains(struct R3PidController *controller, struct R3PidGains gains); /** * AxesMask bits match Rapier: linear X/Y/Z are 1/2/4, angular X/Y/Z are 8/16/32. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3PidController_SetAxes(struct R3PidController *controller, +RAPIER_API +R3Status RAPIER_CALL r3PidController_SetAxes(struct R3PidController *controller, uint32_t axes); /** * Compute a velocity correction, preserving the body's state and updating PID integrals. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R3VelocityCorrection r3PidController_RigidBodyCorrection(struct R3PidController *controller, +RAPIER_API +struct R3VelocityCorrection RAPIER_CALL r3PidController_RigidBodyCorrection(struct R3PidController *controller, R3Real dt, struct R3RigidBodyHandle body, struct R3Pose target_pose, @@ -13527,15 +13370,15 @@ struct R3VelocityCorrection r3PidController_RigidBodyCorrection(struct R3PidCont * Return a copy of slide, slope, and ground-snap settings. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R3CharacterControllerSettings r3KinematicCharacterController_Settings(const struct R3KinematicCharacterController *controller); +RAPIER_API +struct R3CharacterControllerSettings RAPIER_CALL r3KinematicCharacterController_Settings(const struct R3KinematicCharacterController *controller); #if defined(RAPIER_DIM3) /** * Return native default wheel tuning. This POD value owns no resources. * @ingroup controllers */ -RAPIER_API RAPIER_CALL struct R3WheelTuning r3DefaultWheelTuning(void); +RAPIER_API struct R3WheelTuning RAPIER_CALL r3DefaultWheelTuning(void); #endif #if defined(RAPIER_DIM3) @@ -13544,8 +13387,8 @@ RAPIER_API RAPIER_CALL struct R3WheelTuning r3DefaultWheelTuning(void); * controller. Release with r3FreeDynamicRayCastVehicleController. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -struct R3DynamicRayCastVehicleController *r3NewDynamicRayCastVehicleController(struct R3RigidBodyHandle chassis); +RAPIER_API +struct R3DynamicRayCastVehicleController *RAPIER_CALL r3NewDynamicRayCastVehicleController(struct R3RigidBodyHandle chassis); #endif #if defined(RAPIER_DIM3) @@ -13554,8 +13397,8 @@ struct R3DynamicRayCastVehicleController *r3NewDynamicRayCastVehicleController(s * pointers or free the object twice. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3FreeDynamicRayCastVehicleController(struct R3DynamicRayCastVehicleController *controller); +RAPIER_API +R3Status RAPIER_CALL r3FreeDynamicRayCastVehicleController(struct R3DynamicRayCastVehicleController *controller); #endif #if defined(RAPIER_DIM3) @@ -13564,8 +13407,8 @@ R3Status r3FreeDynamicRayCastVehicleController(struct R3DynamicRayCastVehicleCon * are in chassis-local coordinates. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -size_t r3DynamicRayCastVehicleController_AddWheel(struct R3DynamicRayCastVehicleController *controller, +RAPIER_API +size_t RAPIER_CALL r3DynamicRayCastVehicleController_AddWheel(struct R3DynamicRayCastVehicleController *controller, struct R3Vector connection, struct R3Vector direction, struct R3Vector axle, @@ -13579,8 +13422,8 @@ size_t r3DynamicRayCastVehicleController_AddWheel(struct R3DynamicRayCastVehicle * Set the chassis up/forward axis indices (0 = X, 1 = Y, 2 = Z). * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3DynamicRayCastVehicleController_SetAxes(struct R3DynamicRayCastVehicleController *controller, +RAPIER_API +R3Status RAPIER_CALL r3DynamicRayCastVehicleController_SetAxes(struct R3DynamicRayCastVehicleController *controller, size_t up, size_t forward); #endif @@ -13590,8 +13433,8 @@ R3Status r3DynamicRayCastVehicleController_SetAxes(struct R3DynamicRayCastVehicl * Set a wheel engine force, brake force, and steering angle in radians. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3DynamicRayCastVehicleController_SetWheelControls(struct R3DynamicRayCastVehicleController *controller, +RAPIER_API +R3Status RAPIER_CALL r3DynamicRayCastVehicleController_SetWheelControls(struct R3DynamicRayCastVehicleController *controller, size_t index, R3Real steering, R3Real engine_force, @@ -13603,8 +13446,8 @@ R3Status r3DynamicRayCastVehicleController_SetWheelControls(struct R3DynamicRayC * Ray-cast wheel contacts and apply vehicle forces for dt seconds. Does not step the world. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Status r3DynamicRayCastVehicleController_UpdateVehicle(struct R3DynamicRayCastVehicleController *controller, +RAPIER_API +R3Status RAPIER_CALL r3DynamicRayCastVehicleController_UpdateVehicle(struct R3DynamicRayCastVehicleController *controller, R3Real dt, const struct R3QueryFilter *filter); #endif @@ -13614,8 +13457,8 @@ R3Status r3DynamicRayCastVehicleController_UpdateVehicle(struct R3DynamicRayCast * Return signed chassis speed along its forward direction. * @ingroup controllers */ -RAPIER_API RAPIER_CALL -R3Real r3DynamicRayCastVehicleController_CurrentVehicleSpeed(const struct R3DynamicRayCastVehicleController *controller); +RAPIER_API +R3Real RAPIER_CALL r3DynamicRayCastVehicleController_CurrentVehicleSpeed(const struct R3DynamicRayCastVehicleController *controller); #endif #if defined(RAPIER_DIM3) @@ -13624,8 +13467,8 @@ R3Real r3DynamicRayCastVehicleController_CurrentVehicleSpeed(const struct R3Dyna * @see @ref output_buffers * @ingroup controllers */ -RAPIER_API RAPIER_CALL -size_t r3DynamicRayCastVehicleController_Wheels(const struct R3DynamicRayCastVehicleController *controller, +RAPIER_API +size_t RAPIER_CALL r3DynamicRayCastVehicleController_Wheels(const struct R3DynamicRayCastVehicleController *controller, struct R3WheelState *buffer, size_t capacity); #endif @@ -13635,16 +13478,16 @@ size_t r3DynamicRayCastVehicleController_Wheels(const struct R3DynamicRayCastVeh * querying the broad phase. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBodyPropagateModifiedBodyPositionsToColliders(struct R3World *world); +RAPIER_API +R3Status RAPIER_CALL r3RigidBodyPropagateModifiedBodyPositionsToColliders(struct R3World *world); /** * Copies the island manager's active body handles. * @see @ref output_buffers * @ingroup worlds */ -RAPIER_API RAPIER_CALL -size_t r3ActiveRigidBodies(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3ActiveRigidBodies(const struct R3World *world, struct R3RigidBodyHandle *buffer, size_t capacity); @@ -13652,9 +13495,7 @@ size_t r3ActiveRigidBodies(const struct R3World *world, * Wake a body by handle, including a soft-body cluster proxy. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_WakeUp(struct R3RigidBodyHandle handle, - R3Bool strong); +RAPIER_API R3Status RAPIER_CALL r3RigidBody_WakeUp(struct R3RigidBodyHandle handle, R3Bool strong); /** * Replace this thread's error handler and return the previous handler so it can @@ -13663,7 +13504,7 @@ R3Status r3RigidBody_WakeUp(struct R3RigidBodyHandle handle, * handler may terminate the process. Includes R3_NOT_FOUND query misses. * @ingroup errors */ -RAPIER_API RAPIER_CALL struct R3ErrorHandler r3SetErrorHandler(struct R3ErrorHandler handler); +RAPIER_API struct R3ErrorHandler RAPIER_CALL r3SetErrorHandler(struct R3ErrorHandler handler); /** * Status of the most recent fallible operation on this thread. Reading this or @@ -13672,40 +13513,40 @@ RAPIER_API RAPIER_CALL struct R3ErrorHandler r3SetErrorHandler(struct R3ErrorHan * from errors instead of using a fail-fast error callback. * @ingroup errors */ -RAPIER_API RAPIER_CALL R3Status r3LastStatus(void); +RAPIER_API R3Status RAPIER_CALL r3LastStatus(void); /** * Thread-local UTF-8 diagnostic, valid until the next fallible call on this thread. * @ingroup errors */ -RAPIER_API RAPIER_CALL const char *r3LastError(void); +RAPIER_API const char *RAPIER_CALL r3LastError(void); /** * Create an owned ball shape. Release it with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R3SharedShape *r3BallSharedShape(R3Real radius); +RAPIER_API R3SharedShape *RAPIER_CALL r3BallSharedShape(R3Real radius); /** * Create an owned cuboid shape. Release it with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R3SharedShape *r3CuboidSharedShape(struct R3Vector half_extents); +RAPIER_API R3SharedShape *RAPIER_CALL r3CuboidSharedShape(struct R3Vector half_extents); /** * Create an owned round cuboid shape. Release it with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3RoundCuboidSharedShape(struct R3Vector half_extents, +RAPIER_API +R3SharedShape *RAPIER_CALL r3RoundCuboidSharedShape(struct R3Vector half_extents, R3Real border_radius); /** * Create an owned capsule shape. Release it with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3CapsuleSharedShape(struct R3Vector a, +RAPIER_API +R3SharedShape *RAPIER_CALL r3CapsuleSharedShape(struct R3Vector a, struct R3Vector b, R3Real radius); @@ -13713,16 +13554,14 @@ R3SharedShape *r3CapsuleSharedShape(struct R3Vector a, * Create an owned segment shape. Release it with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3SegmentSharedShape(struct R3Vector a, - struct R3Vector b); +RAPIER_API R3SharedShape *RAPIER_CALL r3SegmentSharedShape(struct R3Vector a, struct R3Vector b); /** * Create an owned triangle shape. Release it with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3TriangleSharedShape(struct R3Vector a, +RAPIER_API +R3SharedShape *RAPIER_CALL r3TriangleSharedShape(struct R3Vector a, struct R3Vector b, struct R3Vector c); @@ -13730,16 +13569,14 @@ R3SharedShape *r3TriangleSharedShape(struct R3Vector a, * Create an owned halfspace shape. Release it with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R3SharedShape *r3HalfspaceSharedShape(struct R3Vector normal); +RAPIER_API R3SharedShape *RAPIER_CALL r3HalfspaceSharedShape(struct R3Vector normal); #if defined(RAPIER_DIM3) /** * Create an owned cylinder shape. Release it with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3CylinderSharedShape(R3Real half_height, - R3Real radius); +RAPIER_API R3SharedShape *RAPIER_CALL r3CylinderSharedShape(R3Real half_height, R3Real radius); #endif #if defined(RAPIER_DIM3) @@ -13747,7 +13584,7 @@ R3SharedShape *r3CylinderSharedShape(R3Real half_height, * Create an owned cone shape. Release it with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R3SharedShape *r3ConeSharedShape(R3Real half_height, R3Real radius); +RAPIER_API R3SharedShape *RAPIER_CALL r3ConeSharedShape(R3Real half_height, R3Real radius); #endif /** @@ -13755,32 +13592,27 @@ RAPIER_API RAPIER_CALL R3SharedShape *r3ConeSharedShape(R3Real half_height, R3Re * shared, not consumed. Release with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3CompoundSharedShape(struct R3CompoundShapeView children); +RAPIER_API R3SharedShape *RAPIER_CALL r3CompoundSharedShape(struct R3CompoundShapeView children); /** * Remove the collider and update its parent body mass properties. wake_up wakes the parent. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3RemoveCollider(struct R3ColliderHandle handle, - R3Bool wake_up); +RAPIER_API R3Status RAPIER_CALL r3RemoveCollider(struct R3ColliderHandle handle, R3Bool wake_up); /** * Remove an impulse joint. wake_up wakes its connected bodies. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3RemoveImpulseJoint(struct R3ImpulseJointHandle handle, - R3Bool wake_up); +RAPIER_API R3Status RAPIER_CALL r3RemoveImpulseJoint(struct R3ImpulseJointHandle handle, R3Bool wake_up); /** * Copy entity handles. * @see @ref output_buffers * @ingroup joints */ -RAPIER_API RAPIER_CALL -size_t r3ImpulseJointHandles(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3ImpulseJointHandles(const struct R3World *world, struct R3ImpulseJointHandle *buffer, size_t capacity); @@ -13788,8 +13620,8 @@ size_t r3ImpulseJointHandles(const struct R3World *world, * Remove an articulation joint. wake_up wakes affected bodies. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3RemoveMultibodyJoint(struct R3MultibodyJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RemoveMultibodyJoint(struct R3MultibodyJointHandle handle, R3Bool wake_up); /** @@ -13797,8 +13629,8 @@ R3Status r3RemoveMultibodyJoint(struct R3MultibodyJointHandle handle, * @see @ref output_buffers * @ingroup joints */ -RAPIER_API RAPIER_CALL -size_t r3MultibodyJointHandles(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3MultibodyJointHandles(const struct R3World *world, struct R3MultibodyJointHandle *buffer, size_t capacity); @@ -13806,28 +13638,26 @@ size_t r3MultibodyJointHandles(const struct R3World *world, * Return the two bodies connected by an impulse joint. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R3JointBodies r3ImpulseJoint_Bodies(struct R3ImpulseJointHandle handle); +RAPIER_API struct R3JointBodies RAPIER_CALL r3ImpulseJoint_Bodies(struct R3ImpulseJointHandle handle); /** * Return native default inverse kinematics options. This POD value owns no resources. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R3InverseKinematicsOptions r3DefaultInverseKinematicsOptions(void); +RAPIER_API struct R3InverseKinematicsOptions RAPIER_CALL r3DefaultInverseKinematicsOptions(void); /** * Return the articulation degrees of freedom associated with the joint. * @ingroup joints */ -RAPIER_API RAPIER_CALL size_t r3MultibodyJoint_Ndofs(struct R3MultibodyJointHandle handle); +RAPIER_API size_t RAPIER_CALL r3MultibodyJoint_Ndofs(struct R3MultibodyJointHandle handle); /** * Read/write displacement buffer must contain exactly ndofs entries; zero it for a fresh solve. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3MultibodyJoint_InverseKinematics(struct R3MultibodyJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3MultibodyJoint_InverseKinematics(struct R3MultibodyJointHandle handle, const struct R3InverseKinematicsOptions *options, struct R3Pose target, R3IkJointCanMove can_move, @@ -13839,8 +13669,8 @@ R3Status r3MultibodyJoint_InverseKinematics(struct R3MultibodyJointHandle handle * Apply generalized articulation displacements in native degree-of-freedom order. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3MultibodyJoint_ApplyDisplacements(struct R3MultibodyJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3MultibodyJoint_ApplyDisplacements(struct R3MultibodyJointHandle handle, const R3Real *displacements, size_t count); @@ -13848,28 +13678,28 @@ R3Status r3MultibodyJoint_ApplyDisplacements(struct R3MultibodyJointHandle handl * Frees an owned object; NULL is allowed. Never free a borrowed pointer. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R3Status r3FreeSharedShape(R3SharedShape *object); +RAPIER_API R3Status RAPIER_CALL r3FreeSharedShape(R3SharedShape *object); /** * Create an owned wrapper sharing the same immutable geometry. Release it with * r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R3SharedShape *r3SharedShape_Clone(const R3SharedShape *object); +RAPIER_API R3SharedShape *RAPIER_CALL r3SharedShape_Clone(const R3SharedShape *object); /** * Return the number of rigid body objects in the world. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL size_t r3RigidBodyCount(const struct R3World *world); +RAPIER_API size_t RAPIER_CALL r3RigidBodyCount(const struct R3World *world); /** * Copy entity handles. * @see @ref output_buffers * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -size_t r3RigidBodyHandles(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3RigidBodyHandles(const struct R3World *world, struct R3RigidBodyHandle *buffer, size_t capacity); @@ -13878,21 +13708,21 @@ size_t r3RigidBodyHandles(const struct R3World *world, * false. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3RigidBody_Contains(struct R3RigidBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3RigidBody_Contains(struct R3RigidBodyHandle handle); /** * Return the number of collider objects in the world. * @ingroup colliders */ -RAPIER_API RAPIER_CALL size_t r3ColliderCount(const struct R3World *world); +RAPIER_API size_t RAPIER_CALL r3ColliderCount(const struct R3World *world); /** * Copy entity handles. * @see @ref output_buffers * @ingroup colliders */ -RAPIER_API RAPIER_CALL -size_t r3ColliderHandles(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3ColliderHandles(const struct R3World *world, struct R3ColliderHandle *buffer, size_t capacity); @@ -13900,21 +13730,21 @@ size_t r3ColliderHandles(const struct R3World *world, * Test whether the live world contains this collider handle. A removed/stale handle returns false. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R3Bool r3Collider_Contains(struct R3ColliderHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3Collider_Contains(struct R3ColliderHandle handle); /** * Return the number of soft body objects in the world. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL size_t r3SoftBodyCount(const struct R3World *world); +RAPIER_API size_t RAPIER_CALL r3SoftBodyCount(const struct R3World *world); /** * Copy entity handles. * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyHandles(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyHandles(const struct R3World *world, struct R3SoftBodyHandle *buffer, size_t capacity); @@ -13923,283 +13753,265 @@ size_t r3SoftBodyHandles(const struct R3World *world, * false. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3SoftBody_Contains(struct R3SoftBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3SoftBody_Contains(struct R3SoftBodyHandle handle); /** * Remove a body and its joints, optionally keeping colliders as standalone objects. * Returns whether a body was removed; a stale handle returns false without error. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Bool r3RemoveRigidBody(struct R3RigidBodyHandle handle, +RAPIER_API +R3Bool RAPIER_CALL r3RemoveRigidBody(struct R3RigidBodyHandle handle, R3Bool remove_attached_colliders); /** * Return the world setting documented by R3IntegrationParameters::dt. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Real r3TimeStep(const struct R3World *world); +RAPIER_API R3Real RAPIER_CALL r3TimeStep(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::dt. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3SetTimeStep(struct R3World *world, R3Real value); +RAPIER_API R3Status RAPIER_CALL r3SetTimeStep(struct R3World *world, R3Real value); /** * Return the world setting documented by R3IntegrationParameters::minCcdDt. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Real r3MinCcdDt(const struct R3World *world); +RAPIER_API R3Real RAPIER_CALL r3MinCcdDt(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::minCcdDt. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3SetMinCcdDt(struct R3World *world, R3Real value); +RAPIER_API R3Status RAPIER_CALL r3SetMinCcdDt(struct R3World *world, R3Real value); /** * Return the world setting documented by R3IntegrationParameters::lengthUnit. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Real r3LengthUnit(const struct R3World *world); +RAPIER_API R3Real RAPIER_CALL r3LengthUnit(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::lengthUnit. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3SetLengthUnit(struct R3World *world, R3Real value); +RAPIER_API R3Status RAPIER_CALL r3SetLengthUnit(struct R3World *world, R3Real value); /** * Return the world setting documented by R3IntegrationParameters::warmstartCoefficient. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Real r3WarmstartCoefficient(const struct R3World *world); +RAPIER_API R3Real RAPIER_CALL r3WarmstartCoefficient(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::warmstartCoefficient. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3SetWarmstartCoefficient(struct R3World *world, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3SetWarmstartCoefficient(struct R3World *world, R3Real value); /** * Return the world setting documented by R3IntegrationParameters::normalizedAllowedLinearError. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Real r3NormalizedAllowedLinearError(const struct R3World *world); +RAPIER_API R3Real RAPIER_CALL r3NormalizedAllowedLinearError(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::normalizedAllowedLinearError. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3SetNormalizedAllowedLinearError(struct R3World *world, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3SetNormalizedAllowedLinearError(struct R3World *world, R3Real value); /** * Return the world setting documented by * R3IntegrationParameters::normalizedMaxCorrectiveVelocity. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Real r3NormalizedMaxCorrectiveVelocity(const struct R3World *world); +RAPIER_API R3Real RAPIER_CALL r3NormalizedMaxCorrectiveVelocity(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::normalizedMaxCorrectiveVelocity. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3SetNormalizedMaxCorrectiveVelocity(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3SetNormalizedMaxCorrectiveVelocity(struct R3World *world, R3Real value); /** * Return the world setting documented by R3IntegrationParameters::normalizedPredictionDistance. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Real r3NormalizedPredictionDistance(const struct R3World *world); +RAPIER_API R3Real RAPIER_CALL r3NormalizedPredictionDistance(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::normalizedPredictionDistance. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3SetNormalizedPredictionDistance(struct R3World *world, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3SetNormalizedPredictionDistance(struct R3World *world, R3Real value); /** * Return the world setting documented by R3IntegrationParameters::normalizedMaxLinearVelocity. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Real r3NormalizedMaxLinearVelocity(const struct R3World *world); +RAPIER_API R3Real RAPIER_CALL r3NormalizedMaxLinearVelocity(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::normalizedMaxLinearVelocity. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3SetNormalizedMaxLinearVelocity(struct R3World *world, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3SetNormalizedMaxLinearVelocity(struct R3World *world, R3Real value); /** * Return the world setting documented by * R3IntegrationParameters::normalizedContactRecycleDistance. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Real r3NormalizedContactRecycleDistance(const struct R3World *world); +RAPIER_API R3Real RAPIER_CALL r3NormalizedContactRecycleDistance(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::normalizedContactRecycleDistance. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3SetNormalizedContactRecycleDistance(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3SetNormalizedContactRecycleDistance(struct R3World *world, R3Real value); /** * Return the world setting documented by R3IntegrationParameters::numSolverIterations. * @ingroup worlds */ -RAPIER_API RAPIER_CALL size_t r3NumSolverIterations(const struct R3World *world); +RAPIER_API size_t RAPIER_CALL r3NumSolverIterations(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::numSolverIterations. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3SetNumSolverIterations(struct R3World *world, - size_t value); +RAPIER_API R3Status RAPIER_CALL r3SetNumSolverIterations(struct R3World *world, size_t value); /** * Return the world setting documented by R3IntegrationParameters::numInternalPgsIterations. * @ingroup worlds */ -RAPIER_API RAPIER_CALL size_t r3NumInternalPgsIterations(const struct R3World *world); +RAPIER_API size_t RAPIER_CALL r3NumInternalPgsIterations(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::numInternalPgsIterations. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3SetNumInternalPgsIterations(struct R3World *world, - size_t value); +RAPIER_API R3Status RAPIER_CALL r3SetNumInternalPgsIterations(struct R3World *world, size_t value); /** * Return the world setting documented by * R3IntegrationParameters::numInternalStabilizationIterations. * @ingroup errors */ -RAPIER_API RAPIER_CALL -size_t r3NumInternalStabilizationIterations(const struct R3World *world); +RAPIER_API size_t RAPIER_CALL r3NumInternalStabilizationIterations(const struct R3World *world); /** * Set the world setting documented by * R3IntegrationParameters::numInternalStabilizationIterations. * @ingroup errors */ -RAPIER_API RAPIER_CALL -R3Status r3SetNumInternalStabilizationIterations(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3SetNumInternalStabilizationIterations(struct R3World *world, size_t value); /** * Return the world setting documented by R3IntegrationParameters::maxCcdSubsteps. * @ingroup worlds */ -RAPIER_API RAPIER_CALL size_t r3MaxCcdSubsteps(const struct R3World *world); +RAPIER_API size_t RAPIER_CALL r3MaxCcdSubsteps(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::maxCcdSubsteps. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3SetMaxCcdSubsteps(struct R3World *world, size_t value); +RAPIER_API R3Status RAPIER_CALL r3SetMaxCcdSubsteps(struct R3World *world, size_t value); /** * Return the world setting documented by R3IntegrationParameters::contactClustering. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Bool r3ContactClustering(const struct R3World *world); +RAPIER_API R3Bool RAPIER_CALL r3ContactClustering(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::contactClustering. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3SetContactClustering(struct R3World *world, R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3SetContactClustering(struct R3World *world, R3Bool value); /** * Return the world setting documented by R3IntegrationParameters::contactRecycling. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Bool r3ContactRecycling(const struct R3World *world); +RAPIER_API R3Bool RAPIER_CALL r3ContactRecycling(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::contactRecycling. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3SetContactRecycling(struct R3World *world, R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3SetContactRecycling(struct R3World *world, R3Bool value); /** * Return the world setting documented by R3IntegrationParameters::frictionInBiasPass. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Bool r3FrictionInBiasPass(const struct R3World *world); +RAPIER_API R3Bool RAPIER_CALL r3FrictionInBiasPass(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::frictionInBiasPass. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3SetFrictionInBiasPass(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3SetFrictionInBiasPass(struct R3World *world, R3Bool value); /** * Return the world setting documented by R3IntegrationParameters::warmstartJoints. * @ingroup joints */ -RAPIER_API RAPIER_CALL R3Bool r3WarmstartJoints(const struct R3World *world); +RAPIER_API R3Bool RAPIER_CALL r3WarmstartJoints(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::warmstartJoints. * @ingroup joints */ -RAPIER_API RAPIER_CALL R3Status r3SetWarmstartJoints(struct R3World *world, R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3SetWarmstartJoints(struct R3World *world, R3Bool value); /** * Return the world setting documented by R3IntegrationParameters::contactSoftness. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SpringCoefficients r3ContactSoftness(const struct R3World *world); +RAPIER_API struct R3SpringCoefficients RAPIER_CALL r3ContactSoftness(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::contactSoftness. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SetContactSoftness(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3SetContactSoftness(struct R3World *world, struct R3SpringCoefficients value); /** * Return the world setting documented by R3IntegrationParameters::staticContactSoftness. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SpringCoefficients r3StaticContactSoftness(const struct R3World *world); +RAPIER_API struct R3SpringCoefficients RAPIER_CALL r3StaticContactSoftness(const struct R3World *world); /** * Set the world setting documented by R3IntegrationParameters::staticContactSoftness. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SetStaticContactSoftness(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3SetStaticContactSoftness(struct R3World *world, struct R3SpringCoefficients value); /** * Applies Rapier's persistent one-way platform logic to the borrowed manifold. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3ContactModificationContext_UpdateAsOnewayPlatform(struct R3ContactModificationContext *context, +RAPIER_API +R3Status RAPIER_CALL r3ContactModificationContext_UpdateAsOnewayPlatform(struct R3ContactModificationContext *context, struct R3Vector allowed_local_n1, R3Real allowed_angle); @@ -14207,36 +14019,36 @@ R3Status r3ContactModificationContext_UpdateAsOnewayPlatform(struct R3ContactMod * Sets the tangent velocity of every rigid solver contact in this manifold. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3ContactModificationContext_SetTangentVelocity(struct R3ContactModificationContext *context, +RAPIER_API +R3Status RAPIER_CALL r3ContactModificationContext_SetTangentVelocity(struct R3ContactModificationContext *context, struct R3Vector velocity); /** * Allocate an empty event collector; release it with r3FreeEventCollector. * @ingroup events */ -RAPIER_API RAPIER_CALL struct R3EventCollector *r3NewEventCollector(void); +RAPIER_API struct R3EventCollector *RAPIER_CALL r3NewEventCollector(void); /** * Release an owned event collector. NULL is allowed. Do not pass borrowed pointers or free the * object twice. * @ingroup events */ -RAPIER_API RAPIER_CALL R3Status r3FreeEventCollector(struct R3EventCollector *events); +RAPIER_API R3Status RAPIER_CALL r3FreeEventCollector(struct R3EventCollector *events); /** * Discard all collected events. Does not change the world. * @ingroup events */ -RAPIER_API RAPIER_CALL R3Status r3EventCollector_Clear(struct R3EventCollector *events); +RAPIER_API R3Status RAPIER_CALL r3EventCollector_Clear(struct R3EventCollector *events); /** * Copy the collected collision start/stop events without removing them. * @see @ref output_buffers * @ingroup events */ -RAPIER_API RAPIER_CALL -size_t r3EventCollector_CollisionEvents(const struct R3EventCollector *events, +RAPIER_API +size_t RAPIER_CALL r3EventCollector_CollisionEvents(const struct R3EventCollector *events, struct R3CollisionEvent *buffer, size_t capacity); @@ -14245,8 +14057,8 @@ size_t r3EventCollector_CollisionEvents(const struct R3EventCollector *events, * @see @ref output_buffers * @ingroup events */ -RAPIER_API RAPIER_CALL -size_t r3EventCollector_ContactForceEvents(const struct R3EventCollector *events, +RAPIER_API +size_t RAPIER_CALL r3EventCollector_ContactForceEvents(const struct R3EventCollector *events, struct R3ContactForceEvent *buffer, size_t capacity); @@ -14254,37 +14066,36 @@ size_t r3EventCollector_ContactForceEvents(const struct R3EventCollector *events * Return the number of queued soft-body tear events. * @ingroup events */ -RAPIER_API RAPIER_CALL -size_t r3EventCollector_TearEventCount(const struct R3EventCollector *events); +RAPIER_API size_t RAPIER_CALL r3EventCollector_TearEventCount(const struct R3EventCollector *events); /** * Return an owned copy of a queued tear event; release with r3FreeSoftBodyTearEvent. Does * not remove the queued event. * @ingroup events */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyTearEvent *r3EventCollector_TearEvent(const struct R3EventCollector *events, +RAPIER_API +struct R3SoftBodyTearEvent *RAPIER_CALL r3EventCollector_TearEvent(const struct R3EventCollector *events, size_t index); /** * Return the world-space gravitational acceleration. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R3Vector r3Gravity(const struct R3World *world); +RAPIER_API struct R3Vector RAPIER_CALL r3Gravity(const struct R3World *world); /** * Set the world-space gravitational acceleration. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3SetGravity(struct R3World *world, struct R3Vector value); +RAPIER_API R3Status RAPIER_CALL r3SetGravity(struct R3World *world, struct R3Vector value); /** * Hooks and events may be NULL. This call invalidates all borrowed set-element pointers. * Advance simulation by one timestep. Hooks and events may be NULL. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3Step(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3Step(struct R3World *world, const struct R3PhysicsHooks *hooks, const struct R3EventCollector *events); @@ -14292,8 +14103,8 @@ R3Status r3Step(struct R3World *world, * Refresh collision detection without advancing simulation. Hooks and events may be NULL. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -R3Status r3DetectCollisions(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3DetectCollisions(struct R3World *world, const struct R3PhysicsHooks *hooks, const struct R3EventCollector *events); @@ -14302,36 +14113,36 @@ R3Status r3DetectCollisions(struct R3World *world, * pointer. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R3ByteView r3Bytes_Data(const struct R3Bytes *bytes); +RAPIER_API struct R3ByteView RAPIER_CALL r3Bytes_Data(const struct R3Bytes *bytes); /** * Release an owned snapshot byte buffer. NULL is allowed. Do not pass borrowed pointers or free * the object twice. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3FreeBytes(struct R3Bytes *bytes); +RAPIER_API R3Status RAPIER_CALL r3FreeBytes(struct R3Bytes *bytes); /** * Return owned snapshot bytes; release them with r3FreeBytes. See @ref snapshots for * restoration and handle lifetimes. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R3Bytes *r3SerializeWorld(const struct R3World *world); +RAPIER_API struct R3Bytes *RAPIER_CALL r3SerializeWorld(const struct R3World *world); /** * Restore ONLY trusted snapshots produced by the identical Rapier build. Snapshots are not a * stable file format. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R3World *r3DeserializeWorld(const uint8_t *data, size_t count); +RAPIER_API struct R3World *RAPIER_CALL r3DeserializeWorld(const uint8_t *data, size_t count); /** * Color is HSLA (hue in degrees), matching Rapier DebugColor. mode uses DebugRenderMode bits. * @see @ref output_buffers * @ingroup worlds */ -RAPIER_API RAPIER_CALL -size_t r3DebugRender(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3DebugRender(const struct R3World *world, uint32_t mode, struct R3DebugLine *buffer, size_t capacity); @@ -14340,234 +14151,200 @@ size_t r3DebugRender(const struct R3World *world, * Set the world setting documented by R3SoftBodiesSettings::resweepStrain. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodiesSetResweepStrain(struct R3World *world, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3SoftBodiesSetResweepStrain(struct R3World *world, R3Real value); /** * Return the world setting documented by R3SoftBodiesSettings::resweepStrain. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Real r3SoftBodiesResweepStrain(const struct R3World *world); +RAPIER_API R3Real RAPIER_CALL r3SoftBodiesResweepStrain(const struct R3World *world); /** * Set the world setting documented by R3SoftBodiesSettings::contactStiffening. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodiesSetContactStiffening(struct R3World *world, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3SoftBodiesSetContactStiffening(struct R3World *world, R3Real value); /** * Return the world setting documented by R3SoftBodiesSettings::contactStiffening. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Real r3SoftBodiesContactStiffening(const struct R3World *world); +RAPIER_API R3Real RAPIER_CALL r3SoftBodiesContactStiffening(const struct R3World *world); /** * Set the world setting documented by R3SoftBodiesSettings::maxExtraSubsteps. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodiesSetMaxExtraSubsteps(struct R3World *world, - size_t value); +RAPIER_API R3Status RAPIER_CALL r3SoftBodiesSetMaxExtraSubsteps(struct R3World *world, size_t value); /** * Return the world setting documented by R3SoftBodiesSettings::maxExtraSubsteps. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL size_t r3SoftBodiesMaxExtraSubsteps(const struct R3World *world); +RAPIER_API size_t RAPIER_CALL r3SoftBodiesMaxExtraSubsteps(const struct R3World *world); /** * Set the world setting documented by R3SoftRecoverySettings::authoredVelocityMargin. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetAuthoredVelocityMargin(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3RecoverySetAuthoredVelocityMargin(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::edgeSpeculation. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetEdgeSpeculation(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetEdgeSpeculation(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::invertedCellDetection. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetInvertedCellDetection(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3RecoverySetInvertedCellDetection(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::selfCrossingDetection. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetSelfCrossingDetection(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3RecoverySetSelfCrossingDetection(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::detectionMotionGating. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetDetectionMotionGating(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3RecoverySetDetectionMotionGating(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::crossBodyDetection. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetCrossBodyDetection(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetCrossBodyDetection(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::selfStandDown. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetSelfStandDown(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetSelfStandDown(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::crossBodyExpelGate. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetCrossBodyExpelGate(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetCrossBodyExpelGate(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::edgeStandDown. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetEdgeStandDown(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetEdgeStandDown(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::crossingRepulsion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetCrossingRepulsion(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetCrossingRepulsion(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::crossingRepulsionGuide. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetCrossingRepulsionGuide(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3RecoverySetCrossingRepulsionGuide(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::crossingRepulsionSelfGuide. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetCrossingRepulsionSelfGuide(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3RecoverySetCrossingRepulsionSelfGuide(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::recoveryPace. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetRecoveryPace(struct R3World *world, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetRecoveryPace(struct R3World *world, R3Real value); /** * Set the world setting documented by R3SoftRecoverySettings::overlapConstraints. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetOverlapConstraints(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetOverlapConstraints(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::overlapRigid. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetOverlapRigid(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetOverlapRigid(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::overlapSkipSelfTangled. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetOverlapSkipSelfTangled(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3RecoverySetOverlapSkipSelfTangled(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::overlapEdgeStandDown. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetOverlapEdgeStandDown(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3RecoverySetOverlapEdgeStandDown(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::overlapConstraintPace. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetOverlapConstraintPace(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3RecoverySetOverlapConstraintPace(struct R3World *world, R3Real value); /** * Set the world setting documented by R3SoftRecoverySettings::overlapSkinVolume. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetOverlapSkinVolume(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetOverlapSkinVolume(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::overlapKeptDepth. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetOverlapKeptDepth(struct R3World *world, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetOverlapKeptDepth(struct R3World *world, R3Real value); /** * Set the world setting documented by R3SoftRecoverySettings::overlapSelfRegions. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetOverlapSelfRegions(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetOverlapSelfRegions(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::overlapNormalPush. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetOverlapNormalPush(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetOverlapNormalPush(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::overlapMultiVolume. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetOverlapMultiVolume(struct R3World *world, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RecoverySetOverlapMultiVolume(struct R3World *world, R3Bool value); /** * Set the world setting documented by R3SoftRecoverySettings::overlapProgressMargin. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RecoverySetOverlapProgressMargin(struct R3World *world, +RAPIER_API +R3Status RAPIER_CALL r3RecoverySetOverlapProgressMargin(struct R3World *world, R3Real value); #if defined(RAPIER_FEM) @@ -14575,9 +14352,7 @@ R3Status r3RecoverySetOverlapProgressMargin(struct R3World *world, * Set the world setting documented by R3SoftFemParameters::linearTolerance. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3FemSetLinearTolerance(struct R3World *world, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3FemSetLinearTolerance(struct R3World *world, R3Real value); #endif #if defined(RAPIER_FEM) @@ -14585,9 +14360,7 @@ R3Status r3FemSetLinearTolerance(struct R3World *world, * Set the world setting documented by R3SoftFemParameters::maxLinearIterations. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3FemSetMaxLinearIterations(struct R3World *world, - size_t value); +RAPIER_API R3Status RAPIER_CALL r3FemSetMaxLinearIterations(struct R3World *world, size_t value); #endif #if defined(RAPIER_FEM) @@ -14595,7 +14368,7 @@ R3Status r3FemSetMaxLinearIterations(struct R3World *world, * Set the world setting documented by R3SoftFemParameters::maxDenseDofs. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Status r3FemSetMaxDenseDofs(struct R3World *world, size_t value); +RAPIER_API R3Status RAPIER_CALL r3FemSetMaxDenseDofs(struct R3World *world, size_t value); #endif /** @@ -14605,7 +14378,7 @@ RAPIER_API RAPIER_CALL R3Status r3FemSetMaxDenseDofs(struct R3World *world, size * pool when constructing the new one fails. The pool is not included in snapshots. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3SetNumThreads(struct R3World *world, size_t num_threads); +RAPIER_API R3Status RAPIER_CALL r3SetNumThreads(struct R3World *world, size_t num_threads); /** * Removes the world's dedicated pool. A parallel build then uses the calling @@ -14613,21 +14386,21 @@ RAPIER_API RAPIER_CALL R3Status r3SetNumThreads(struct R3World *world, size_t nu * Returns R3_UNSUPPORTED in a build without the parallel feature. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3ClearThreadPool(struct R3World *world); +RAPIER_API R3Status RAPIER_CALL r3ClearThreadPool(struct R3World *world); /** * Size of the world's dedicated pool, or zero if a parallel build has no dedicated * pool configured. Returns one for a build without the parallel feature. * @ingroup worlds */ -RAPIER_API RAPIER_CALL size_t r3NumThreads(const struct R3World *world); +RAPIER_API size_t RAPIER_CALL r3NumThreads(const struct R3World *world); /** * Enable or disable the native pipeline profiling counters. Enabling returns * R3_UNSUPPORTED if the library was built without the profiler feature. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3SetCountersEnabled(struct R3World *world, R3Bool enabled); +RAPIER_API R3Status RAPIER_CALL r3SetCountersEnabled(struct R3World *world, R3Bool enabled); /** * Native engine time of the most recent step, in milliseconds, as in the Rust testbed. @@ -14635,64 +14408,63 @@ RAPIER_API RAPIER_CALL R3Status r3SetCountersEnabled(struct R3World *world, R3Bo * and dispatch into a dedicated thread pool; remains unchanged while paused. * @ingroup worlds */ -RAPIER_API RAPIER_CALL double r3StepTimeMs(const struct R3World *world); +RAPIER_API double RAPIER_CALL r3StepTimeMs(const struct R3World *world); /** * Read ONLY trusted legacy rigid-world snapshots from debug_deserialize3.rs, * produced by the identical Rapier build. This is not a stable interchange format. + * * Import trusted legacy Rust testbed rigid-state bytes into a new owned world. Release with * r3FreeWorld; see @ref snapshots. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -struct R3World *r3DeserializeRigidState(const uint8_t *data, - size_t count); +RAPIER_API struct R3World *RAPIER_CALL r3DeserializeRigidState(const uint8_t *data, size_t count); /** * Return native default query filter. This POD value owns no resources. * @ingroup queries */ -RAPIER_API RAPIER_CALL struct R3QueryFilter r3DefaultQueryFilter(void); +RAPIER_API struct R3QueryFilter RAPIER_CALL r3DefaultQueryFilter(void); /** * Return native default shape cast options. This POD value owns no resources. * @ingroup queries */ -RAPIER_API RAPIER_CALL struct R3ShapeCastOptions r3DefaultShapeCastOptions(void); +RAPIER_API struct R3ShapeCastOptions RAPIER_CALL r3DefaultShapeCastOptions(void); /** * Remove a soft body and its associated simulation objects. Invalidates its handle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Status r3RemoveSoftBody(struct R3SoftBodyHandle handle); +RAPIER_API R3Status RAPIER_CALL r3RemoveSoftBody(struct R3SoftBodyHandle handle); /** * Wake the soft body and its rigid proxies. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Status r3SoftBody_WakeUp(struct R3SoftBodyHandle handle); +RAPIER_API R3Status RAPIER_CALL r3SoftBody_WakeUp(struct R3SoftBodyHandle handle); /** * Release an owned soft body tear event. NULL is allowed. Do not pass borrowed pointers or free * the object twice. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Status r3FreeSoftBodyTearEvent(struct R3SoftBodyTearEvent *event); +RAPIER_API R3Status RAPIER_CALL r3FreeSoftBodyTearEvent(struct R3SoftBodyTearEvent *event); /** * Return the source soft-body handle for this tear event. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyHandle r3SoftBodyTearEvent_SoftBody(const struct R3SoftBodyTearEvent *event); +RAPIER_API +struct R3SoftBodyHandle RAPIER_CALL r3SoftBodyTearEvent_SoftBody(const struct R3SoftBodyTearEvent *event); /** * Copy the soft-body handles produced by the tear. * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyTearEvent_Bodies(const struct R3SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyTearEvent_Bodies(const struct R3SoftBodyTearEvent *event, struct R3SoftBodyHandle *buffer, size_t capacity); @@ -14700,8 +14472,8 @@ size_t r3SoftBodyTearEvent_Bodies(const struct R3SoftBodyTearEvent *event, * Return the destination body and particle index for an original particle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3ParticleDestination r3SoftBodyTearEvent_ParticleDestination(const struct R3SoftBodyTearEvent *event, +RAPIER_API +struct R3ParticleDestination RAPIER_CALL r3SoftBodyTearEvent_ParticleDestination(const struct R3SoftBodyTearEvent *event, uint32_t particle); /** @@ -14709,8 +14481,8 @@ struct R3ParticleDestination r3SoftBodyTearEvent_ParticleDestination(const struc * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyTearEvent_TornEdges(const struct R3SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyTearEvent_TornEdges(const struct R3SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); @@ -14719,8 +14491,8 @@ size_t r3SoftBodyTearEvent_TornEdges(const struct R3SoftBodyTearEvent *event, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyTearEvent_TornCells(const struct R3SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyTearEvent_TornCells(const struct R3SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); @@ -14729,8 +14501,8 @@ size_t r3SoftBodyTearEvent_TornCells(const struct R3SoftBodyTearEvent *event, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyTearEvent_RemovedEdges(const struct R3SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyTearEvent_RemovedEdges(const struct R3SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); @@ -14739,8 +14511,8 @@ size_t r3SoftBodyTearEvent_RemovedEdges(const struct R3SoftBodyTearEvent *event, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyTearEvent_SplitParticles(const struct R3SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyTearEvent_SplitParticles(const struct R3SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); @@ -14749,8 +14521,8 @@ size_t r3SoftBodyTearEvent_SplitParticles(const struct R3SoftBodyTearEvent *even * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyTearEvent_InsertedParticles(const struct R3SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyTearEvent_InsertedParticles(const struct R3SoftBodyTearEvent *event, uint32_t *buffer, size_t capacity); @@ -14759,8 +14531,8 @@ size_t r3SoftBodyTearEvent_InsertedParticles(const struct R3SoftBodyTearEvent *e * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyTearEvent_PieceParticles(const struct R3SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyTearEvent_PieceParticles(const struct R3SoftBodyTearEvent *event, size_t piece_index, uint32_t *buffer, size_t capacity); @@ -14770,8 +14542,8 @@ size_t r3SoftBodyTearEvent_PieceParticles(const struct R3SoftBodyTearEvent *even * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyTearEvent_Clusters(const struct R3SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyTearEvent_Clusters(const struct R3SoftBodyTearEvent *event, struct R3SoftClusterSplit *buffer, size_t capacity); @@ -14780,8 +14552,8 @@ size_t r3SoftBodyTearEvent_Clusters(const struct R3SoftBodyTearEvent *event, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyTearEvent_MovedJoints(const struct R3SoftBodyTearEvent *event, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyTearEvent_MovedJoints(const struct R3SoftBodyTearEvent *event, struct R3SoftJointMove *buffer, size_t capacity); @@ -14790,8 +14562,8 @@ size_t r3SoftBodyTearEvent_MovedJoints(const struct R3SoftBodyTearEvent *event, * r3FreeSoftBodyTearEvent. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyTearEvent *r3SoftBody_Tear(struct R3SoftBodyHandle handle, +RAPIER_API +struct R3SoftBodyTearEvent *RAPIER_CALL r3SoftBody_Tear(struct R3SoftBodyHandle handle, const uint32_t *edges, size_t edge_count, const uint32_t *cells, @@ -14801,8 +14573,8 @@ struct R3SoftBodyTearEvent *r3SoftBody_Tear(struct R3SoftBodyHandle handle, * Create a rigid proxy cluster from the supplied particle indices and return its cluster index. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -uint32_t r3SoftBody_AddCluster(struct R3SoftBodyHandle handle, +RAPIER_API +uint32_t RAPIER_CALL r3SoftBody_AddCluster(struct R3SoftBodyHandle handle, const uint32_t *particles, size_t count); @@ -14810,8 +14582,8 @@ uint32_t r3SoftBody_AddCluster(struct R3SoftBodyHandle handle, * Remove the selected cluster and its rigid proxy. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_RemoveCluster(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_RemoveCluster(struct R3SoftBodyHandle handle, uint32_t cluster); /** @@ -14819,8 +14591,8 @@ R3Status r3SoftBody_RemoveCluster(struct R3SoftBodyHandle handle, * found to false; body/index are only written when a destination exists. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3OptionalParticleDestination r3SoftBodyTearEvent_TryParticleDestination(const struct R3SoftBodyTearEvent *event, +RAPIER_API +struct R3OptionalParticleDestination RAPIER_CALL r3SoftBodyTearEvent_TryParticleDestination(const struct R3SoftBodyTearEvent *event, uint32_t particle); /** @@ -14828,8 +14600,8 @@ struct R3OptionalParticleDestination r3SoftBodyTearEvent_TryParticleDestination( * The optional owned event must be freed with FreeSoftBodyTearEvent. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyTearEvent *r3CutSoftBody(struct R3SoftBodyHandle handle, +RAPIER_API +struct R3SoftBodyTearEvent *RAPIER_CALL r3CutSoftBody(struct R3SoftBodyHandle handle, const struct R3Vector *blade); /** @@ -14837,14 +14609,13 @@ struct R3SoftBodyTearEvent *r3CutSoftBody(struct R3SoftBodyHandle handle, * destructor. * @ingroup worlds */ -RAPIER_API RAPIER_CALL -struct R3VolumeMeshParameters r3NewVolumeMeshParameters(R3Real cell_size); +RAPIER_API struct R3VolumeMeshParameters RAPIER_CALL r3NewVolumeMeshParameters(R3Real cell_size); /** * Return ABI version, dimension, scalar size, and pointer size of the linked library. * @ingroup errors */ -RAPIER_API RAPIER_CALL struct R3BuildInfo r3BuildInfo(void); +RAPIER_API struct R3BuildInfo RAPIER_CALL r3BuildInfo(void); /** * Release version of the loaded C bindings, e.g. "0.35.3+c.2". @@ -14853,7 +14624,7 @@ RAPIER_API RAPIER_CALL struct R3BuildInfo r3BuildInfo(void); * This release identifier is independent of the ABI compatibility version. * @ingroup errors */ -RAPIER_API RAPIER_CALL const char *r3Version(void); +RAPIER_API const char *RAPIER_CALL r3Version(void); /** * Cargo profile of the loaded physics library: "debug" or "release". @@ -14862,21 +14633,21 @@ RAPIER_API RAPIER_CALL const char *r3Version(void); * This is independent of the consumer's build mode and of per-package optimization overrides. * @ingroup errors */ -RAPIER_API RAPIER_CALL const char *r3BuildProfile(void); +RAPIER_API const char *RAPIER_CALL r3BuildProfile(void); /** * Return profiling, SIMD width, and parallelism of the linked library. * @ingroup errors */ -RAPIER_API RAPIER_CALL struct R3BuildFeatures r3BuildFeatures(void); +RAPIER_API struct R3BuildFeatures RAPIER_CALL r3BuildFeatures(void); /** * Create an owned heightfield shape from copied samples. 3D samples are column-major, with rows * * columns entries. Release with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3HeightfieldSharedShape(struct R3RealView heights, +RAPIER_API +R3SharedShape *RAPIER_CALL r3HeightfieldSharedShape(struct R3RealView heights, size_t rows, size_t columns, struct R3Vector scale); @@ -14885,24 +14656,24 @@ R3SharedShape *r3HeightfieldSharedShape(struct R3RealView heights, * Compute the shape axis-aligned bounds at the supplied world-space pose. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -struct R3Aabb r3SharedShape_ComputeAabb(const R3SharedShape *shape, +RAPIER_API +struct R3Aabb RAPIER_CALL r3SharedShape_ComputeAabb(const R3SharedShape *shape, struct R3Pose pose); /** * Compute local mass properties for the supplied nonnegative density. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -struct R3MassProperties r3SharedShape_MassProperties(const R3SharedShape *shape, +RAPIER_API +struct R3MassProperties RAPIER_CALL r3SharedShape_MassProperties(const R3SharedShape *shape, R3Real density); /** * Test whether the world-space point lies inside the shape at pose. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3Bool r3SharedShape_ContainsPoint(const R3SharedShape *shape, +RAPIER_API +R3Bool RAPIER_CALL r3SharedShape_ContainsPoint(const R3SharedShape *shape, struct R3Pose pose, struct R3Vector point); @@ -14911,8 +14682,8 @@ R3Bool r3SharedShape_ContainsPoint(const R3SharedShape *shape, * @see @ref output_buffers * @ingroup events */ -RAPIER_API RAPIER_CALL -size_t r3ContactPairs(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3ContactPairs(const struct R3World *world, struct R3ContactPair *buffer, size_t capacity); @@ -14920,8 +14691,8 @@ size_t r3ContactPairs(const struct R3World *world, * Return the narrow-phase contact pair for two colliders, or report R3_NOT_FOUND. * @ingroup events */ -RAPIER_API RAPIER_CALL -struct R3ContactPair r3ContactPair(struct R3ColliderHandle collider1, +RAPIER_API +struct R3ContactPair RAPIER_CALL r3ContactPair(struct R3ColliderHandle collider1, struct R3ColliderHandle collider2); /** @@ -14929,8 +14700,8 @@ struct R3ContactPair r3ContactPair(struct R3ColliderHandle collider1, * @see @ref output_buffers * @ingroup events */ -RAPIER_API RAPIER_CALL -size_t r3IntersectionPairs(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3IntersectionPairs(const struct R3World *world, struct R3IntersectionPair *buffer, size_t capacity); @@ -14941,8 +14712,8 @@ size_t r3IntersectionPairs(const struct R3World *world, * @see @ref output_buffers * @ingroup worlds */ -RAPIER_API RAPIER_CALL -size_t r3ContactPoints(struct R3ColliderHandle collider1, +RAPIER_API +size_t RAPIER_CALL r3ContactPoints(struct R3ColliderHandle collider1, struct R3ColliderHandle collider2, struct R3ContactPoint *buffer, size_t capacity); @@ -14952,8 +14723,8 @@ size_t r3ContactPoints(struct R3ColliderHandle collider1, * @see @ref output_buffers * @ingroup joints */ -RAPIER_API RAPIER_CALL -size_t r3MultibodyJoint_GeneralizedVelocity(struct R3MultibodyJointHandle handle, +RAPIER_API +size_t RAPIER_CALL r3MultibodyJoint_GeneralizedVelocity(struct R3MultibodyJointHandle handle, R3Real *buffer, size_t capacity); @@ -14961,8 +14732,8 @@ size_t r3MultibodyJoint_GeneralizedVelocity(struct R3MultibodyJointHandle handle * Replace articulation generalized velocities; the array length must match its degrees of freedom. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3MultibodyJoint_SetGeneralizedVelocity(struct R3MultibodyJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3MultibodyJoint_SetGeneralizedVelocity(struct R3MultibodyJointHandle handle, const R3Real *values, size_t count); @@ -14970,8 +14741,8 @@ R3Status r3MultibodyJoint_SetGeneralizedVelocity(struct R3MultibodyJointHandle h * Check this before passing any dimension/precision-dependent structs across the ABI. * @ingroup errors */ -RAPIER_API RAPIER_CALL -R3Status r3CheckAbi(uint32_t version, +RAPIER_API +R3Status RAPIER_CALL r3CheckAbi(uint32_t version, uint32_t dimension, size_t real_size, size_t vector_size, @@ -14982,8 +14753,8 @@ R3Status r3CheckAbi(uint32_t version, * controls curved-shape resolution. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -struct R3ShapeMesh *r3SharedShape_Tessellate(const R3SharedShape *shape, +RAPIER_API +struct R3ShapeMesh *RAPIER_CALL r3SharedShape_Tessellate(const R3SharedShape *shape, uint32_t subdivisions); /** @@ -14991,8 +14762,8 @@ struct R3ShapeMesh *r3SharedShape_Tessellate(const R3SharedShape *shape, * @see @ref output_buffers * @ingroup shapes */ -RAPIER_API RAPIER_CALL -size_t r3ShapeMesh_Triangles(const struct R3ShapeMesh *mesh, +RAPIER_API +size_t RAPIER_CALL r3ShapeMesh_Triangles(const struct R3ShapeMesh *mesh, struct R3Vector *buffer, size_t capacity); @@ -15001,8 +14772,8 @@ size_t r3ShapeMesh_Triangles(const struct R3ShapeMesh *mesh, * @see @ref output_buffers * @ingroup shapes */ -RAPIER_API RAPIER_CALL -size_t r3ShapeMesh_Lines(const struct R3ShapeMesh *mesh, +RAPIER_API +size_t RAPIER_CALL r3ShapeMesh_Lines(const struct R3ShapeMesh *mesh, struct R3Vector *buffer, size_t capacity); @@ -15011,15 +14782,15 @@ size_t r3ShapeMesh_Lines(const struct R3ShapeMesh *mesh, * twice. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R3Status r3FreeShapeMesh(struct R3ShapeMesh *mesh); +RAPIER_API R3Status RAPIER_CALL r3FreeShapeMesh(struct R3ShapeMesh *mesh); #if defined(RAPIER_DIM3) /** * Create an owned round cylinder shape. Release it with r3FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3RoundCylinderSharedShape(R3Real half_height, +RAPIER_API +R3SharedShape *RAPIER_CALL r3RoundCylinderSharedShape(R3Real half_height, R3Real radius, R3Real border_radius); #endif @@ -15030,8 +14801,8 @@ R3SharedShape *r3RoundCylinderSharedShape(R3Real half_height, * Cuboids, cones, cylinders, convex polyhedra, trimeshes, and heightfields are also supported. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -struct R3TriMeshData *r3SharedShape_ToTrimesh(const R3SharedShape *shape, +RAPIER_API +struct R3TriMeshData *RAPIER_CALL r3SharedShape_ToTrimesh(const R3SharedShape *shape, uint32_t ntheta, uint32_t nphi); #endif @@ -15042,8 +14813,8 @@ struct R3TriMeshData *r3SharedShape_ToTrimesh(const R3SharedShape *shape, * @see @ref output_buffers * @ingroup shapes */ -RAPIER_API RAPIER_CALL -size_t r3TriMeshData_Vertices(const struct R3TriMeshData *mesh, +RAPIER_API +size_t RAPIER_CALL r3TriMeshData_Vertices(const struct R3TriMeshData *mesh, struct R3Vector *buffer, size_t capacity); #endif @@ -15054,8 +14825,8 @@ size_t r3TriMeshData_Vertices(const struct R3TriMeshData *mesh, * @see @ref output_buffers * @ingroup shapes */ -RAPIER_API RAPIER_CALL -size_t r3TriMeshData_Indices(const struct R3TriMeshData *mesh, +RAPIER_API +size_t RAPIER_CALL r3TriMeshData_Indices(const struct R3TriMeshData *mesh, uint32_t *buffer, size_t capacity); #endif @@ -15066,7 +14837,7 @@ size_t r3TriMeshData_Indices(const struct R3TriMeshData *mesh, * object twice. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R3Status r3FreeTriMeshData(struct R3TriMeshData *mesh); +RAPIER_API R3Status RAPIER_CALL r3FreeTriMeshData(struct R3TriMeshData *mesh); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15074,7 +14845,7 @@ RAPIER_API RAPIER_CALL R3Status r3FreeTriMeshData(struct R3TriMeshData *mesh); * Return native default urdf loader options. This POD value owns no resources. * @ingroup robotics */ -RAPIER_API RAPIER_CALL struct R3UrdfLoaderOptions r3DefaultUrdfLoaderOptions(void); +RAPIER_API struct R3UrdfLoaderOptions RAPIER_CALL r3DefaultUrdfLoaderOptions(void); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15083,7 +14854,7 @@ RAPIER_API RAPIER_CALL struct R3UrdfLoaderOptions r3DefaultUrdfLoaderOptions(voi * twice. * @ingroup robotics */ -RAPIER_API RAPIER_CALL R3Status r3FreeUrdfRobot(struct R3UrdfRobot *object); +RAPIER_API R3Status RAPIER_CALL r3FreeUrdfRobot(struct R3UrdfRobot *object); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15092,8 +14863,8 @@ RAPIER_API RAPIER_CALL R3Status r3FreeUrdfRobot(struct R3UrdfRobot *object); * Options and their blueprint resources are borrowed through this call; the robot is owned. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R3UrdfRobot *r3UrdfRobotFromFile(const char *path, +RAPIER_API +struct R3UrdfRobot *RAPIER_CALL r3UrdfRobotFromFile(const char *path, const struct R3UrdfLoaderOptions *options); #endif @@ -15102,8 +14873,8 @@ struct R3UrdfRobot *r3UrdfRobotFromFile(const char *path, * Apply an additional transform to the loaded robot before insertion. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R3Status r3UrdfRobot_AppendTransform(struct R3UrdfRobot *robot, +RAPIER_API +R3Status RAPIER_CALL r3UrdfRobot_AppendTransform(struct R3UrdfRobot *robot, struct R3Pose transform); #endif @@ -15113,7 +14884,7 @@ R3Status r3UrdfRobot_AppendTransform(struct R3UrdfRobot *robot, * object twice. * @ingroup robotics */ -RAPIER_API RAPIER_CALL R3Status r3FreeUrdfRobotHandles(struct R3UrdfRobotHandles *handles); +RAPIER_API R3Status RAPIER_CALL r3FreeUrdfRobotHandles(struct R3UrdfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15121,8 +14892,8 @@ RAPIER_API RAPIER_CALL R3Status r3FreeUrdfRobotHandles(struct R3UrdfRobotHandles * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R3UrdfRobotHandles *r3UrdfRobot_InsertUsingImpulseJoints(struct R3World *world, +RAPIER_API +struct R3UrdfRobotHandles *RAPIER_CALL r3UrdfRobot_InsertUsingImpulseJoints(struct R3World *world, const struct R3UrdfRobot *robot); #endif @@ -15131,8 +14902,8 @@ struct R3UrdfRobotHandles *r3UrdfRobot_InsertUsingImpulseJoints(struct R3World * * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R3UrdfRobotHandles *r3UrdfRobot_InsertUsingMultibodyJoints(struct R3World *world, +RAPIER_API +struct R3UrdfRobotHandles *RAPIER_CALL r3UrdfRobot_InsertUsingMultibodyJoints(struct R3World *world, const struct R3UrdfRobot *robot, uint8_t options); #endif @@ -15143,8 +14914,8 @@ struct R3UrdfRobotHandles *r3UrdfRobot_InsertUsingMultibodyJoints(struct R3World * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r3UrdfRobotHandles_Bodies(const struct R3UrdfRobotHandles *handles, +RAPIER_API +size_t RAPIER_CALL r3UrdfRobotHandles_Bodies(const struct R3UrdfRobotHandles *handles, struct R3RigidBodyHandle *buffer, size_t capacity); #endif @@ -15154,7 +14925,7 @@ size_t r3UrdfRobotHandles_Bodies(const struct R3UrdfRobotHandles *handles, * Return native default mjcf loader options. This POD value owns no resources. * @ingroup robotics */ -RAPIER_API RAPIER_CALL struct R3MjcfLoaderOptions r3DefaultMjcfLoaderOptions(void); +RAPIER_API struct R3MjcfLoaderOptions RAPIER_CALL r3DefaultMjcfLoaderOptions(void); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15163,7 +14934,7 @@ RAPIER_API RAPIER_CALL struct R3MjcfLoaderOptions r3DefaultMjcfLoaderOptions(voi * twice. * @ingroup robotics */ -RAPIER_API RAPIER_CALL R3Status r3FreeMjcfRobot(struct R3MjcfRobot *object); +RAPIER_API R3Status RAPIER_CALL r3FreeMjcfRobot(struct R3MjcfRobot *object); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15172,8 +14943,8 @@ RAPIER_API RAPIER_CALL R3Status r3FreeMjcfRobot(struct R3MjcfRobot *object); * Options and their blueprint resources are borrowed through this call; the robot is owned. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R3MjcfRobot *r3MjcfRobotFromFile(const char *path, +RAPIER_API +struct R3MjcfRobot *RAPIER_CALL r3MjcfRobotFromFile(const char *path, const struct R3MjcfLoaderOptions *options); #endif @@ -15182,8 +14953,8 @@ struct R3MjcfRobot *r3MjcfRobotFromFile(const char *path, * Apply an additional transform to the loaded robot before insertion. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R3Status r3MjcfRobot_AppendTransform(struct R3MjcfRobot *robot, +RAPIER_API +R3Status RAPIER_CALL r3MjcfRobot_AppendTransform(struct R3MjcfRobot *robot, struct R3Pose transform); #endif @@ -15193,7 +14964,7 @@ R3Status r3MjcfRobot_AppendTransform(struct R3MjcfRobot *robot, * object twice. * @ingroup robotics */ -RAPIER_API RAPIER_CALL R3Status r3FreeMjcfRobotHandles(struct R3MjcfRobotHandles *handles); +RAPIER_API R3Status RAPIER_CALL r3FreeMjcfRobotHandles(struct R3MjcfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15201,8 +14972,8 @@ RAPIER_API RAPIER_CALL R3Status r3FreeMjcfRobotHandles(struct R3MjcfRobotHandles * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R3MjcfRobotHandles *r3MjcfRobot_InsertUsingImpulseJoints(struct R3World *world, +RAPIER_API +struct R3MjcfRobotHandles *RAPIER_CALL r3MjcfRobot_InsertUsingImpulseJoints(struct R3World *world, const struct R3MjcfRobot *robot); #endif @@ -15211,8 +14982,8 @@ struct R3MjcfRobotHandles *r3MjcfRobot_InsertUsingImpulseJoints(struct R3World * * Inserts a clone; the source robot remains owned by the caller. Returns owned handles. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R3MjcfRobotHandles *r3MjcfRobot_InsertUsingMultibodyJoints(struct R3World *world, +RAPIER_API +struct R3MjcfRobotHandles *RAPIER_CALL r3MjcfRobot_InsertUsingMultibodyJoints(struct R3World *world, const struct R3MjcfRobot *robot, uint8_t options); #endif @@ -15223,8 +14994,8 @@ struct R3MjcfRobotHandles *r3MjcfRobot_InsertUsingMultibodyJoints(struct R3World * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r3MjcfRobotHandles_Bodies(const struct R3MjcfRobotHandles *handles, +RAPIER_API +size_t RAPIER_CALL r3MjcfRobotHandles_Bodies(const struct R3MjcfRobotHandles *handles, struct R3RigidBodyHandle *buffer, size_t capacity); #endif @@ -15234,7 +15005,7 @@ size_t r3MjcfRobotHandles_Bodies(const struct R3MjcfRobotHandles *handles, * Resolved model gravity before the caller chooses a world convention. * @ingroup robotics */ -RAPIER_API RAPIER_CALL struct R3Vector r3MjcfRobot_Gravity(const struct R3MjcfRobot *robot); +RAPIER_API struct R3Vector RAPIER_CALL r3MjcfRobot_Gravity(const struct R3MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15242,7 +15013,7 @@ RAPIER_API RAPIER_CALL struct R3Vector r3MjcfRobot_Gravity(const struct R3MjcfRo * Return the number of source MJCF bodies. * @ingroup robotics */ -RAPIER_API RAPIER_CALL size_t r3MjcfRobot_BodyCount(const struct R3MjcfRobot *robot); +RAPIER_API size_t RAPIER_CALL r3MjcfRobot_BodyCount(const struct R3MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15250,9 +15021,7 @@ RAPIER_API RAPIER_CALL size_t r3MjcfRobot_BodyCount(const struct R3MjcfRobot *ro * Return the collider count for a source body index. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r3MjcfRobot_BodyColliderCount(const struct R3MjcfRobot *robot, - size_t body); +RAPIER_API size_t RAPIER_CALL r3MjcfRobot_BodyColliderCount(const struct R3MjcfRobot *robot, size_t body); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15260,8 +15029,8 @@ size_t r3MjcfRobot_BodyColliderCount(const struct R3MjcfRobot *robot, * Set collision groups on a collider in the loaded robot, before insertion. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R3Status r3MjcfRobot_SetBodyColliderCollisionGroups(struct R3MjcfRobot *robot, +RAPIER_API +R3Status RAPIER_CALL r3MjcfRobot_SetBodyColliderCollisionGroups(struct R3MjcfRobot *robot, size_t body, size_t collider, struct R3InteractionGroups groups); @@ -15272,7 +15041,7 @@ R3Status r3MjcfRobot_SetBodyColliderCollisionGroups(struct R3MjcfRobot *robot, * Return the number of imported keyframes. * @ingroup robotics */ -RAPIER_API RAPIER_CALL size_t r3MjcfRobot_KeyframeCount(const struct R3MjcfRobot *robot); +RAPIER_API size_t RAPIER_CALL r3MjcfRobot_KeyframeCount(const struct R3MjcfRobot *robot); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15281,8 +15050,8 @@ RAPIER_API RAPIER_CALL size_t r3MjcfRobot_KeyframeCount(const struct R3MjcfRobot * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r3MjcfRobot_KeyframeName(const struct R3MjcfRobot *robot, +RAPIER_API +size_t RAPIER_CALL r3MjcfRobot_KeyframeName(const struct R3MjcfRobot *robot, size_t key, char *buffer, size_t capacity); @@ -15293,8 +15062,8 @@ size_t r3MjcfRobot_KeyframeName(const struct R3MjcfRobot *robot, * Append a keyframe from the source MJCF model to the loaded robot. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R3Status r3MjcfRobot_AppendKeyframe(struct R3MjcfRobot *robot, +RAPIER_API +R3Status RAPIER_CALL r3MjcfRobot_AppendKeyframe(struct R3MjcfRobot *robot, const struct R3MjcfRobot *source, size_t key); #endif @@ -15305,8 +15074,8 @@ R3Status r3MjcfRobot_AppendKeyframe(struct R3MjcfRobot *robot, * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r3MjcfRobot_KeyframeControls(const struct R3MjcfRobot *robot, +RAPIER_API +size_t RAPIER_CALL r3MjcfRobot_KeyframeControls(const struct R3MjcfRobot *robot, size_t key, R3Real *buffer, size_t capacity); @@ -15317,8 +15086,7 @@ size_t r3MjcfRobot_KeyframeControls(const struct R3MjcfRobot *robot, * Return the number of imported actuators. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r3MjcfRobotHandles_ActuatorCount(const struct R3MjcfRobotHandles *handles); +RAPIER_API size_t RAPIER_CALL r3MjcfRobotHandles_ActuatorCount(const struct R3MjcfRobotHandles *handles); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15326,8 +15094,8 @@ size_t r3MjcfRobotHandles_ActuatorCount(const struct R3MjcfRobotHandles *handles * Apply the selected keyframe to the inserted robot. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R3Status r3MjcfRobotHandles_ApplyKeyframe(const struct R3MjcfRobotHandles *handles, +RAPIER_API +R3Status RAPIER_CALL r3MjcfRobotHandles_ApplyKeyframe(const struct R3MjcfRobotHandles *handles, const struct R3MjcfRobot *robot, size_t key); #endif @@ -15337,8 +15105,8 @@ R3Status r3MjcfRobotHandles_ApplyKeyframe(const struct R3MjcfRobotHandles *handl * Apply actuator controls with per-actuator scaling to the inserted robot. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R3Status r3MjcfRobotHandles_ApplyControlsScaled(const struct R3MjcfRobotHandles *handles, +RAPIER_API +R3Status RAPIER_CALL r3MjcfRobotHandles_ApplyControlsScaled(const struct R3MjcfRobotHandles *handles, const R3Real *controls, size_t count, R3Real gain); @@ -15349,9 +15117,7 @@ R3Status r3MjcfRobotHandles_ApplyControlsScaled(const struct R3MjcfRobotHandles * Return the number of visual meshes for a source body. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r3MjcfRobot_BodyVisualCount(const struct R3MjcfRobot *robot, - size_t body); +RAPIER_API size_t RAPIER_CALL r3MjcfRobot_BodyVisualCount(const struct R3MjcfRobot *robot, size_t body); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15360,8 +15126,8 @@ size_t r3MjcfRobot_BodyVisualCount(const struct R3MjcfRobot *robot, * changes; never free this pointer. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -const R3MjcfVisualMesh *r3MjcfRobot_BodyVisual(const struct R3MjcfRobot *robot, +RAPIER_API +const R3MjcfVisualMesh *RAPIER_CALL r3MjcfRobot_BodyVisual(const struct R3MjcfRobot *robot, size_t body, size_t visual); #endif @@ -15371,8 +15137,7 @@ const R3MjcfVisualMesh *r3MjcfRobot_BodyVisual(const struct R3MjcfRobot *robot, * Return a copy of visual pose, color, material, and geometry-kind flags. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -struct R3MjcfVisualMeshInfo r3MjcfVisualMesh_Info(const R3MjcfVisualMesh *visual); +RAPIER_API struct R3MjcfVisualMeshInfo RAPIER_CALL r3MjcfVisualMesh_Info(const R3MjcfVisualMesh *visual); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15381,8 +15146,7 @@ struct R3MjcfVisualMeshInfo r3MjcfVisualMesh_Info(const R3MjcfVisualMesh *visual * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. * @ingroup robotics */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3MjcfVisualMesh_CloneShape(const R3MjcfVisualMesh *visual); +RAPIER_API R3SharedShape *RAPIER_CALL r3MjcfVisualMesh_CloneShape(const R3MjcfVisualMesh *visual); #endif #if (defined(RAPIER_ROBOTICS) && defined(RAPIER_DIM3) && defined(RAPIER_F32)) @@ -15391,8 +15155,8 @@ R3SharedShape *r3MjcfVisualMesh_CloneShape(const R3MjcfVisualMesh *visual); * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r3MjcfVisualMesh_Uvs(const R3MjcfVisualMesh *visual, +RAPIER_API +size_t RAPIER_CALL r3MjcfVisualMesh_Uvs(const R3MjcfVisualMesh *visual, float *buffer, size_t capacity); #endif @@ -15403,8 +15167,8 @@ size_t r3MjcfVisualMesh_Uvs(const R3MjcfVisualMesh *visual, * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r3MjcfVisualMesh_Normals(const R3MjcfVisualMesh *visual, +RAPIER_API +size_t RAPIER_CALL r3MjcfVisualMesh_Normals(const R3MjcfVisualMesh *visual, float *buffer, size_t capacity); #endif @@ -15415,8 +15179,8 @@ size_t r3MjcfVisualMesh_Normals(const R3MjcfVisualMesh *visual, * @see @ref output_buffers * @ingroup robotics */ -RAPIER_API RAPIER_CALL -size_t r3MjcfVisualMesh_Texture(const R3MjcfVisualMesh *visual, +RAPIER_API +size_t RAPIER_CALL r3MjcfVisualMesh_Texture(const R3MjcfVisualMesh *visual, char *buffer, size_t capacity); #endif @@ -15425,53 +15189,51 @@ size_t r3MjcfVisualMesh_Texture(const R3MjcfVisualMesh *visual, * Return the rigid body world-space pose. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R3Pose r3RigidBody_Position(struct R3RigidBodyHandle handle); +RAPIER_API struct R3Pose RAPIER_CALL r3RigidBody_Position(struct R3RigidBodyHandle handle); /** * Return the rigid body world-space translation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R3Vector r3RigidBody_Translation(struct R3RigidBodyHandle handle); +RAPIER_API struct R3Vector RAPIER_CALL r3RigidBody_Translation(struct R3RigidBodyHandle handle); /** * Return the rigid body world-space linear velocity. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R3Vector r3RigidBody_Linvel(struct R3RigidBodyHandle handle); +RAPIER_API struct R3Vector RAPIER_CALL r3RigidBody_Linvel(struct R3RigidBodyHandle handle); /** * Return the rigid body world-space angular velocity (radians per second). * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3AngVector r3RigidBody_Angvel(struct R3RigidBodyHandle handle); +RAPIER_API R3AngVector RAPIER_CALL r3RigidBody_Angvel(struct R3RigidBodyHandle handle); /** * Return whether the rigid body is sleeping. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsSleeping(struct R3RigidBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3RigidBody_IsSleeping(struct R3RigidBodyHandle handle); /** * Return whether the rigid body is enabled. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsEnabled(struct R3RigidBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3RigidBody_IsEnabled(struct R3RigidBodyHandle handle); /** * Return the rigid body application-owned 128-bit user value. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R3UserData r3RigidBody_UserData(struct R3RigidBodyHandle handle); +RAPIER_API struct R3UserData RAPIER_CALL r3RigidBody_UserData(struct R3RigidBodyHandle handle); /** * Set the rigid body world-space pose. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetPosition(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetPosition(struct R3RigidBodyHandle handle, struct R3Pose value, R3Bool wake_up); @@ -15480,8 +15242,8 @@ R3Status r3RigidBody_SetPosition(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetTranslation(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetTranslation(struct R3RigidBodyHandle handle, struct R3Vector value, R3Bool wake_up); @@ -15490,8 +15252,8 @@ R3Status r3RigidBody_SetTranslation(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetLinvel(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetLinvel(struct R3RigidBodyHandle handle, struct R3Vector value, R3Bool wake_up); @@ -15500,8 +15262,8 @@ R3Status r3RigidBody_SetLinvel(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetAngvel(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetAngvel(struct R3RigidBodyHandle handle, R3AngVector value, R3Bool wake_up); @@ -15509,16 +15271,16 @@ R3Status r3RigidBody_SetAngvel(struct R3RigidBodyHandle handle, * Set the rigid body next kinematic world-space pose. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetNextKinematicPosition(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetNextKinematicPosition(struct R3RigidBodyHandle handle, struct R3Pose value); /** * Set the rigid body next kinematic world-space translation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetNextKinematicTranslation(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetNextKinematicTranslation(struct R3RigidBodyHandle handle, struct R3Vector value); /** @@ -15526,8 +15288,8 @@ R3Status r3RigidBody_SetNextKinematicTranslation(struct R3RigidBodyHandle handle * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetGravityScale(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetGravityScale(struct R3RigidBodyHandle handle, R3Real value, R3Bool wake_up); @@ -15535,32 +15297,30 @@ R3Status r3RigidBody_SetGravityScale(struct R3RigidBodyHandle handle, * Set the rigid body linear damping coefficient. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetLinearDamping(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetLinearDamping(struct R3RigidBodyHandle handle, R3Real value); /** * Set the rigid body angular damping coefficient. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetAngularDamping(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetAngularDamping(struct R3RigidBodyHandle handle, R3Real value); /** * Enable or disable the rigid body. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetEnabled(struct R3RigidBodyHandle handle, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3RigidBody_SetEnabled(struct R3RigidBodyHandle handle, R3Bool value); /** * Set the rigid body application-owned 128-bit user value. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetUserData(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetUserData(struct R3RigidBodyHandle handle, struct R3UserData value); /** @@ -15568,8 +15328,8 @@ R3Status r3RigidBody_SetUserData(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_ApplyImpulse(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_ApplyImpulse(struct R3RigidBodyHandle handle, struct R3Vector value, R3Bool wake_up); @@ -15578,8 +15338,8 @@ R3Status r3RigidBody_ApplyImpulse(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_ApplyImpulseAtPoint(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_ApplyImpulseAtPoint(struct R3RigidBodyHandle handle, struct R3Vector value, struct R3Vector point, R3Bool wake_up); @@ -15589,8 +15349,8 @@ R3Status r3RigidBody_ApplyImpulseAtPoint(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_AddForce(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_AddForce(struct R3RigidBodyHandle handle, struct R3Vector value, R3Bool wake_up); @@ -15599,115 +15359,106 @@ R3Status r3RigidBody_AddForce(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_ResetForces(struct R3RigidBodyHandle handle, - R3Bool wake_up); +RAPIER_API R3Status RAPIER_CALL r3RigidBody_ResetForces(struct R3RigidBodyHandle handle, R3Bool wake_up); /** * Put the body to sleep. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Status r3RigidBody_Sleep(struct R3RigidBodyHandle handle); +RAPIER_API R3Status RAPIER_CALL r3RigidBody_Sleep(struct R3RigidBodyHandle handle); /** * Return the collider world-space pose. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R3Pose r3Collider_Position(struct R3ColliderHandle handle); +RAPIER_API struct R3Pose RAPIER_CALL r3Collider_Position(struct R3ColliderHandle handle); /** * Return the collider world-space translation. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R3Vector r3Collider_Translation(struct R3ColliderHandle handle); +RAPIER_API struct R3Vector RAPIER_CALL r3Collider_Translation(struct R3ColliderHandle handle); /** * Return the collider friction coefficient. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R3Real r3Collider_Friction(struct R3ColliderHandle handle); +RAPIER_API R3Real RAPIER_CALL r3Collider_Friction(struct R3ColliderHandle handle); /** * Return the collider restitution coefficient. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R3Real r3Collider_Restitution(struct R3ColliderHandle handle); +RAPIER_API R3Real RAPIER_CALL r3Collider_Restitution(struct R3ColliderHandle handle); /** * Return whether the collider is a sensor (detects overlaps without contact forces). * @ingroup colliders */ -RAPIER_API RAPIER_CALL R3Bool r3Collider_IsSensor(struct R3ColliderHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3Collider_IsSensor(struct R3ColliderHandle handle); /** * Return the parent body handle, or an invalid handle with OK status for a standalone collider. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3RigidBodyHandle r3Collider_Parent(struct R3ColliderHandle handle); +RAPIER_API struct R3RigidBodyHandle RAPIER_CALL r3Collider_Parent(struct R3ColliderHandle handle); /** * Set the collider world-space pose. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetPosition(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetPosition(struct R3ColliderHandle handle, struct R3Pose value); /** * Set the collider world-space translation. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetTranslation(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetTranslation(struct R3ColliderHandle handle, struct R3Vector value); /** * Set the collider friction coefficient. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetFriction(struct R3ColliderHandle handle, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3Collider_SetFriction(struct R3ColliderHandle handle, R3Real value); /** * Set the collider restitution coefficient. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetRestitution(struct R3ColliderHandle handle, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3Collider_SetRestitution(struct R3ColliderHandle handle, R3Real value); /** * Enable or disable a sensor (detects overlaps without contact forces) for the collider. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetSensor(struct R3ColliderHandle handle, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3Collider_SetSensor(struct R3ColliderHandle handle, R3Bool value); /** * Set the collider collision filtering groups. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetCollisionGroups(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetCollisionGroups(struct R3ColliderHandle handle, struct R3InteractionGroups value); /** * Set the collider application-owned 128-bit user value. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetUserData(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetUserData(struct R3ColliderHandle handle, struct R3UserData value); /** * Return the world-space position of the indexed particle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3Vector r3SoftBody_ParticlePosition(struct R3SoftBodyHandle handle, +RAPIER_API +struct R3Vector RAPIER_CALL r3SoftBody_ParticlePosition(struct R3SoftBodyHandle handle, size_t index); /** @@ -15715,8 +15466,8 @@ struct R3Vector r3SoftBody_ParticlePosition(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_ParticlePositions(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_ParticlePositions(struct R3SoftBodyHandle handle, struct R3Vector *buffer, size_t capacity); @@ -15724,15 +15475,14 @@ size_t r3SoftBody_ParticlePositions(struct R3SoftBodyHandle handle, * Return a copy of the soft body material parameters. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyMaterial r3SoftBody_Material(struct R3SoftBodyHandle handle); +RAPIER_API struct R3SoftBodyMaterial RAPIER_CALL r3SoftBody_Material(struct R3SoftBodyHandle handle); /** * Set the world-space position of the indexed particle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetParticlePosition(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetParticlePosition(struct R3SoftBodyHandle handle, size_t index, struct R3Vector value); @@ -15740,8 +15490,8 @@ R3Status r3SoftBody_SetParticlePosition(struct R3SoftBodyHandle handle, * Copy material parameters into the soft body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetMaterial(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetMaterial(struct R3SoftBodyHandle handle, const struct R3SoftBodyMaterial *data); /** @@ -15749,8 +15499,8 @@ R3Status r3SoftBody_SetMaterial(struct R3SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_AddParticleForce(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_AddParticleForce(struct R3SoftBodyHandle handle, size_t index, struct R3Vector value, R3Bool wake_up); @@ -15761,8 +15511,8 @@ R3Status r3SoftBody_AddParticleForce(struct R3SoftBodyHandle handle, * NULL/0 is a size query. BUFFER_TOO_SMALL returns the required count and leaves states untouched. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -size_t r3RigidBodyReadStates(const struct R3World *world, +RAPIER_API +size_t RAPIER_CALL r3RigidBodyReadStates(const struct R3World *world, const struct R3RigidBodyHandle *handles, size_t handle_count, struct R3RigidBodyState *states, @@ -15772,16 +15522,15 @@ size_t r3RigidBodyReadStates(const struct R3World *world, * Copies joint configuration without returning a borrowed joint pointer. * @ingroup joints */ -RAPIER_API RAPIER_CALL -struct R3JointDesc r3ImpulseJoint_Desc(struct R3ImpulseJointHandle handle); +RAPIER_API struct R3JointDesc RAPIER_CALL r3ImpulseJoint_Desc(struct R3ImpulseJointHandle handle); /** * Replaces configuration after validation, resetting cached limit/motor impulses. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetDesc(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetDesc(struct R3ImpulseJointHandle handle, const struct R3JointDesc *desc, R3Bool wake_up); @@ -15791,8 +15540,8 @@ R3Status r3ImpulseJoint_SetDesc(struct R3ImpulseJointHandle handle, * Geometry and flags are validated when the description is built or inserted. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3Status r3ShapeDesc_SetTrimesh(struct R3ShapeDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3ShapeDesc_SetTrimesh(struct R3ShapeDesc *desc, struct R3VectorView vertices, struct R3TriangleView indices, uint32_t flags); @@ -15803,8 +15552,8 @@ R3Status r3ShapeDesc_SetTrimesh(struct R3ShapeDesc *desc, * Geometry and flags are validated when the description is built or inserted. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3Status r3ShapeDesc_SetPolyline(struct R3ShapeDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3ShapeDesc_SetPolyline(struct R3ShapeDesc *desc, struct R3VectorView vertices, struct R3EdgeView indices, uint32_t flags); @@ -15813,24 +15562,24 @@ R3Status r3ShapeDesc_SetPolyline(struct R3ShapeDesc *desc, * Replace the shape geometry with a borrowed convex hull point cloud. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3Status r3ShapeDesc_SetConvexHull(struct R3ShapeDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3ShapeDesc_SetConvexHull(struct R3ShapeDesc *desc, struct R3VectorView vertices); /** * Select an explicit particle recipe and borrow its positions. Other fields are preserved. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetParticles(struct R3SoftBodyDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3SoftBodyDesc_SetParticles(struct R3SoftBodyDesc *desc, struct R3VectorView positions); /** * Select a surface recipe and borrow its vertices and elements. Other fields are preserved. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetSurfaceMesh(struct R3SoftBodyDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3SoftBodyDesc_SetSurfaceMesh(struct R3SoftBodyDesc *desc, struct R3VectorView vertices, R3SurfaceElementView elements); @@ -15838,8 +15587,8 @@ R3Status r3SoftBodyDesc_SetSurfaceMesh(struct R3SoftBodyDesc *desc, * Borrow skin geometry. Other fields, including skinCollision, are preserved. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetSkin(struct R3SoftBodyDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3SoftBodyDesc_SetSkin(struct R3SoftBodyDesc *desc, struct R3VectorView vertices, R3SurfaceElementView elements); @@ -15850,8 +15599,8 @@ R3Status r3SoftBodyDesc_SetSkin(struct R3SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetMasses(struct R3SoftBodyDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3SoftBodyDesc_SetMasses(struct R3SoftBodyDesc *desc, struct R3RealView view); /** @@ -15861,8 +15610,8 @@ R3Status r3SoftBodyDesc_SetMasses(struct R3SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetPinnedParticles(struct R3SoftBodyDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3SoftBodyDesc_SetPinnedParticles(struct R3SoftBodyDesc *desc, struct R3IndexView view); /** @@ -15872,8 +15621,8 @@ R3Status r3SoftBodyDesc_SetPinnedParticles(struct R3SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetEdges(struct R3SoftBodyDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3SoftBodyDesc_SetEdges(struct R3SoftBodyDesc *desc, struct R3EdgeView view); /** @@ -15883,8 +15632,8 @@ R3Status r3SoftBodyDesc_SetEdges(struct R3SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetBendEdges(struct R3SoftBodyDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3SoftBodyDesc_SetBendEdges(struct R3SoftBodyDesc *desc, struct R3EdgeView view); /** @@ -15894,9 +15643,7 @@ R3Status r3SoftBodyDesc_SetBendEdges(struct R3SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetCells(struct R3SoftBodyDesc *desc, - R3CellView view); +RAPIER_API R3Status RAPIER_CALL r3SoftBodyDesc_SetCells(struct R3SoftBodyDesc *desc, R3CellView view); /** * Borrow surface; preserve all other fields. No allocation or element reads. @@ -15905,8 +15652,8 @@ R3Status r3SoftBodyDesc_SetCells(struct R3SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetSurface(struct R3SoftBodyDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3SoftBodyDesc_SetSurface(struct R3SoftBodyDesc *desc, R3SurfaceElementView view); /** @@ -15916,8 +15663,8 @@ R3Status r3SoftBodyDesc_SetSurface(struct R3SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetTensionOnlyEdges(struct R3SoftBodyDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3SoftBodyDesc_SetTensionOnlyEdges(struct R3SoftBodyDesc *desc, struct R3IndexView view); #if defined(RAPIER_DIM3) @@ -15928,8 +15675,8 @@ R3Status r3SoftBodyDesc_SetTensionOnlyEdges(struct R3SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetDihedrals(struct R3SoftBodyDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3SoftBodyDesc_SetDihedrals(struct R3SoftBodyDesc *desc, struct R3DihedralView view); #endif @@ -15941,8 +15688,8 @@ R3Status r3SoftBodyDesc_SetDihedrals(struct R3SoftBodyDesc *desc, * Invalid view metadata leaves the description unchanged. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBodyDesc_SetWire(struct R3SoftBodyDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3SoftBodyDesc_SetWire(struct R3SoftBodyDesc *desc, struct R3EdgeView view); #endif @@ -15951,8 +15698,8 @@ R3Status r3SoftBodyDesc_SetWire(struct R3SoftBodyDesc *desc, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderDesc r3RoundCuboidColliderDesc(struct R3Vector half_extents, +RAPIER_API +struct R3ColliderDesc RAPIER_CALL r3RoundCuboidColliderDesc(struct R3Vector half_extents, R3Real border_radius); /** @@ -15960,8 +15707,8 @@ struct R3ColliderDesc r3RoundCuboidColliderDesc(struct R3Vector half_extents, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderDesc r3CapsuleColliderDesc(struct R3Vector a, +RAPIER_API +struct R3ColliderDesc RAPIER_CALL r3CapsuleColliderDesc(struct R3Vector a, struct R3Vector b, R3Real radius); @@ -15970,17 +15717,15 @@ struct R3ColliderDesc r3CapsuleColliderDesc(struct R3Vector a, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderDesc r3SegmentColliderDesc(struct R3Vector a, - struct R3Vector b); +RAPIER_API struct R3ColliderDesc RAPIER_CALL r3SegmentColliderDesc(struct R3Vector a, struct R3Vector b); /** * Return a triangle description with vertices a, b, and c. * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderDesc r3TriangleColliderDesc(struct R3Vector a, +RAPIER_API +struct R3ColliderDesc RAPIER_CALL r3TriangleColliderDesc(struct R3Vector a, struct R3Vector b, struct R3Vector c); @@ -15989,7 +15734,7 @@ struct R3ColliderDesc r3TriangleColliderDesc(struct R3Vector a, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R3ColliderDesc r3HalfspaceColliderDesc(struct R3Vector normal); +RAPIER_API struct R3ColliderDesc RAPIER_CALL r3HalfspaceColliderDesc(struct R3Vector normal); #if defined(RAPIER_DIM3) /** @@ -15997,9 +15742,7 @@ RAPIER_API RAPIER_CALL struct R3ColliderDesc r3HalfspaceColliderDesc(struct R3Ve * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderDesc r3CylinderColliderDesc(R3Real half_height, - R3Real radius); +RAPIER_API struct R3ColliderDesc RAPIER_CALL r3CylinderColliderDesc(R3Real half_height, R3Real radius); #endif #if defined(RAPIER_DIM3) @@ -16008,9 +15751,7 @@ struct R3ColliderDesc r3CylinderColliderDesc(R3Real half_height, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderDesc r3ConeColliderDesc(R3Real half_height, - R3Real radius); +RAPIER_API struct R3ColliderDesc RAPIER_CALL r3ConeColliderDesc(R3Real half_height, R3Real radius); #endif #if defined(RAPIER_DIM3) @@ -16019,8 +15760,8 @@ struct R3ColliderDesc r3ConeColliderDesc(R3Real half_height, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderDesc r3RoundCylinderColliderDesc(R3Real half_height, +RAPIER_API +struct R3ColliderDesc RAPIER_CALL r3RoundCylinderColliderDesc(R3Real half_height, R3Real radius, R3Real border_radius); #endif @@ -16030,18 +15771,14 @@ struct R3ColliderDesc r3RoundCylinderColliderDesc(R3Real half_height, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderDesc r3CapsuleXColliderDesc(R3Real half_height, - R3Real radius); +RAPIER_API struct R3ColliderDesc RAPIER_CALL r3CapsuleXColliderDesc(R3Real half_height, R3Real radius); /** * Return a Y-aligned capsule description; half_height is half the segment length, excluding caps. * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderDesc r3CapsuleYColliderDesc(R3Real half_height, - R3Real radius); +RAPIER_API struct R3ColliderDesc RAPIER_CALL r3CapsuleYColliderDesc(R3Real half_height, R3Real radius); #if defined(RAPIER_DIM3) /** @@ -16049,9 +15786,7 @@ struct R3ColliderDesc r3CapsuleYColliderDesc(R3Real half_height, * Returns a description without allocating or validating. Build/insert validates its fields. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3ColliderDesc r3CapsuleZColliderDesc(R3Real half_height, - R3Real radius); +RAPIER_API struct R3ColliderDesc RAPIER_CALL r3CapsuleZColliderDesc(R3Real half_height, R3Real radius); #endif /** @@ -16059,8 +15794,8 @@ struct R3ColliderDesc r3CapsuleZColliderDesc(R3Real half_height, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyDesc r3RopeSoftBodyDesc(struct R3Vector a, +RAPIER_API +struct R3SoftBodyDesc RAPIER_CALL r3RopeSoftBodyDesc(struct R3Vector a, struct R3Vector b, size_t particles); @@ -16070,8 +15805,8 @@ struct R3SoftBodyDesc r3RopeSoftBodyDesc(struct R3Vector a, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyDesc r3GridSoftBodyDesc(struct R3Vector center, +RAPIER_API +struct R3SoftBodyDesc RAPIER_CALL r3GridSoftBodyDesc(struct R3Vector center, struct R3Vector half_extents, size_t nx, size_t ny); @@ -16083,8 +15818,8 @@ struct R3SoftBodyDesc r3GridSoftBodyDesc(struct R3Vector center, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyDesc r3CuboidSoftBodyDesc(struct R3Vector center, +RAPIER_API +struct R3SoftBodyDesc RAPIER_CALL r3CuboidSoftBodyDesc(struct R3Vector center, struct R3Vector half_extents, size_t nx, size_t ny, @@ -16097,8 +15832,8 @@ struct R3SoftBodyDesc r3CuboidSoftBodyDesc(struct R3Vector center, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyDesc r3ClothSoftBodyDesc(struct R3Vector origin, +RAPIER_API +struct R3SoftBodyDesc RAPIER_CALL r3ClothSoftBodyDesc(struct R3Vector origin, struct R3Vector du, struct R3Vector dv, size_t nu, @@ -16112,8 +15847,8 @@ struct R3SoftBodyDesc r3ClothSoftBodyDesc(struct R3Vector origin, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyDesc r3DiskSoftBodyDesc(struct R3Vector center, +RAPIER_API +struct R3SoftBodyDesc RAPIER_CALL r3DiskSoftBodyDesc(struct R3Vector center, R3Real radius, size_t particles); #endif @@ -16124,8 +15859,8 @@ struct R3SoftBodyDesc r3DiskSoftBodyDesc(struct R3Vector center, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyDesc r3SphereSoftBodyDesc(struct R3Vector center, +RAPIER_API +struct R3SoftBodyDesc RAPIER_CALL r3SphereSoftBodyDesc(struct R3Vector center, R3Real radius, uint32_t subdivisions); #endif @@ -16137,8 +15872,8 @@ struct R3SoftBodyDesc r3SphereSoftBodyDesc(struct R3Vector center, * Initializes a recipe without allocating. Geometry is validated during preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyDesc r3ClothTubeSoftBodyDesc(struct R3Vector origin, +RAPIER_API +struct R3SoftBodyDesc RAPIER_CALL r3ClothTubeSoftBodyDesc(struct R3Vector origin, struct R3Vector axis, R3Real radius_start, R3Real radius_end, @@ -16150,8 +15885,8 @@ struct R3SoftBodyDesc r3ClothTubeSoftBodyDesc(struct R3Vector origin, * Initializes a borrowed meshing recipe. Mesh generation happens on preview/insertion. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyDesc r3VolumetricSoftBodyDesc(struct R3VectorView vertices, +RAPIER_API +struct R3SoftBodyDesc RAPIER_CALL r3VolumetricSoftBodyDesc(struct R3VectorView vertices, R3SurfaceElementView surface, struct R3VolumeMeshParameters parameters); @@ -16159,16 +15894,16 @@ struct R3SoftBodyDesc r3VolumetricSoftBodyDesc(struct R3VectorView vertices, * Returns a material with the same softness for each constraint family. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyMaterial r3UniformSoftBodyMaterial(struct R3SpringCoefficients value); +RAPIER_API +struct R3SoftBodyMaterial RAPIER_CALL r3UniformSoftBodyMaterial(struct R3SpringCoefficients value); /** * Copies generated particle positions into caller-owned storage; no persistent builder. * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyDesc_ParticlePositions(const struct R3SoftBodyDesc *desc, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyDesc_ParticlePositions(const struct R3SoftBodyDesc *desc, struct R3Vector *buffer, size_t capacity); @@ -16177,8 +15912,8 @@ size_t r3SoftBodyDesc_ParticlePositions(const struct R3SoftBodyDesc *desc, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBodyDesc_CellIndices(const struct R3SoftBodyDesc *desc, +RAPIER_API +size_t RAPIER_CALL r3SoftBodyDesc_CellIndices(const struct R3SoftBodyDesc *desc, uint32_t *buffer, size_t capacity); @@ -16187,78 +15922,76 @@ size_t r3SoftBodyDesc_CellIndices(const struct R3SoftBodyDesc *desc, * clone alive while using it as a cache key. * @ingroup shapes */ -RAPIER_API RAPIER_CALL size_t r3Collider_ShapeIdentity(struct R3ColliderHandle handle); +RAPIER_API size_t RAPIER_CALL r3Collider_ShapeIdentity(struct R3ColliderHandle handle); /** * Return the soft body particle count. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL size_t r3SoftBody_NumParticles(struct R3SoftBodyHandle handle); +RAPIER_API size_t RAPIER_CALL r3SoftBody_NumParticles(struct R3SoftBodyHandle handle); /** * Return a counter that changes when particle connectivity changes; use it to invalidate mesh * caches. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL uint32_t r3SoftBody_TopologyVersion(struct R3SoftBodyHandle handle); +RAPIER_API uint32_t RAPIER_CALL r3SoftBody_TopologyVersion(struct R3SoftBodyHandle handle); /** * Return the soft body mass. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Real r3SoftBody_Mass(struct R3SoftBodyHandle handle); +RAPIER_API R3Real RAPIER_CALL r3SoftBody_Mass(struct R3SoftBodyHandle handle); /** * Return the soft body current volume. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Real r3SoftBody_Volume(struct R3SoftBodyHandle handle); +RAPIER_API R3Real RAPIER_CALL r3SoftBody_Volume(struct R3SoftBodyHandle handle); /** * Return the soft body undeformed volume. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Real r3SoftBody_RestVolume(struct R3SoftBodyHandle handle); +RAPIER_API R3Real RAPIER_CALL r3SoftBody_RestVolume(struct R3SoftBodyHandle handle); /** * Return the soft body target volume multiplier. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Real r3SoftBody_VolumeFactor(struct R3SoftBodyHandle handle); +RAPIER_API R3Real RAPIER_CALL r3SoftBody_VolumeFactor(struct R3SoftBodyHandle handle); /** * Return the soft body world-space center of mass. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3Vector r3SoftBody_CenterOfMass(struct R3SoftBodyHandle handle); +RAPIER_API struct R3Vector RAPIER_CALL r3SoftBody_CenterOfMass(struct R3SoftBodyHandle handle); /** * Return the soft body root rigid-proxy handle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3RigidBodyHandle r3SoftBody_RootBody(struct R3SoftBodyHandle handle); +RAPIER_API struct R3RigidBodyHandle RAPIER_CALL r3SoftBody_RootBody(struct R3SoftBodyHandle handle); /** * Return whether the soft body is enabled. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3SoftBody_IsEnabled(struct R3SoftBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3SoftBody_IsEnabled(struct R3SoftBodyHandle handle); /** * Return whether the soft body is sleeping. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3SoftBody_IsSleeping(struct R3SoftBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3SoftBody_IsSleeping(struct R3SoftBodyHandle handle); /** * Copy world-space particle velocities. * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_ParticleVelocities(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_ParticleVelocities(struct R3SoftBodyHandle handle, struct R3Vector *buffer, size_t capacity); @@ -16267,8 +16000,8 @@ size_t r3SoftBody_ParticleVelocities(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_Edges(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_Edges(struct R3SoftBodyHandle handle, uint32_t *buffer, size_t capacity); @@ -16277,8 +16010,8 @@ size_t r3SoftBody_Edges(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_Cells(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_Cells(struct R3SoftBodyHandle handle, uint32_t *buffer, size_t capacity); @@ -16287,8 +16020,8 @@ size_t r3SoftBody_Cells(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_Boundary(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_Boundary(struct R3SoftBodyHandle handle, uint32_t *buffer, size_t capacity); @@ -16297,8 +16030,8 @@ size_t r3SoftBody_Boundary(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_Pieces(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_Pieces(struct R3SoftBodyHandle handle, struct R3SoftBodyHandle *buffer, size_t capacity); @@ -16306,8 +16039,8 @@ size_t r3SoftBody_Pieces(struct R3SoftBodyHandle handle, * Set the soft body particle world-space velocity. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetParticleVelocity(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetParticleVelocity(struct R3SoftBodyHandle handle, size_t index, struct R3Vector value); @@ -16315,8 +16048,8 @@ R3Status r3SoftBody_SetParticleVelocity(struct R3SoftBodyHandle handle, * Set the next world-space target position of a pinned particle. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetParticleKinematicTarget(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetParticleKinematicTarget(struct R3SoftBodyHandle handle, size_t index, struct R3Vector value); @@ -16324,8 +16057,8 @@ R3Status r3SoftBody_SetParticleKinematicTarget(struct R3SoftBodyHandle handle, * Enable or disable pinning the particle for the soft body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetParticlePinned(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetParticlePinned(struct R3SoftBodyHandle handle, size_t index, R3Bool value); @@ -16334,8 +16067,8 @@ R3Status r3SoftBody_SetParticlePinned(struct R3SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_ApplyParticleImpulse(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_ApplyParticleImpulse(struct R3SoftBodyHandle handle, size_t index, struct R3Vector value, R3Bool wake_up); @@ -16345,8 +16078,8 @@ R3Status r3SoftBody_ApplyParticleImpulse(struct R3SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_AddForce(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_AddForce(struct R3SoftBodyHandle handle, struct R3Vector value, R3Bool wake_up); @@ -16355,8 +16088,8 @@ R3Status r3SoftBody_AddForce(struct R3SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_ApplyImpulse(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_ApplyImpulse(struct R3SoftBodyHandle handle, struct R3Vector value, R3Bool wake_up); @@ -16365,32 +16098,28 @@ R3Status r3SoftBody_ApplyImpulse(struct R3SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_ResetForces(struct R3SoftBodyHandle handle, - R3Bool wake_up); +RAPIER_API R3Status RAPIER_CALL r3SoftBody_ResetForces(struct R3SoftBodyHandle handle, R3Bool wake_up); /** * Enable or disable the soft body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetEnabled(struct R3SoftBodyHandle handle, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3SoftBody_SetEnabled(struct R3SoftBodyHandle handle, R3Bool value); /** * Set the soft body target volume multiplier. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetVolumeFactor(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetVolumeFactor(struct R3SoftBodyHandle handle, R3Real value); /** * Attach a particle to a rigid body at the supplied body-local anchor. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_AttachParticle(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_AttachParticle(struct R3SoftBodyHandle handle, size_t index, struct R3RigidBodyHandle rigid_body); @@ -16398,17 +16127,15 @@ R3Status r3SoftBody_AttachParticle(struct R3SoftBodyHandle handle, * Remove a particle attachment to a rigid body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_DetachParticle(struct R3SoftBodyHandle handle, - size_t index); +RAPIER_API R3Status RAPIER_CALL r3SoftBody_DetachParticle(struct R3SoftBodyHandle handle, size_t index); /** * Copy cluster indices. * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_Clusters(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_Clusters(struct R3SoftBodyHandle handle, uint32_t *buffer, size_t capacity); @@ -16416,8 +16143,8 @@ size_t r3SoftBody_Clusters(struct R3SoftBodyHandle handle, * Return the rigid proxy for the selected cluster. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3RigidBodyHandle r3SoftBody_ClusterProxy(struct R3SoftBodyHandle handle, +RAPIER_API +struct R3RigidBodyHandle RAPIER_CALL r3SoftBody_ClusterProxy(struct R3SoftBodyHandle handle, uint32_t cluster); /** @@ -16425,8 +16152,8 @@ struct R3RigidBodyHandle r3SoftBody_ClusterProxy(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_ClusterParticles(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_ClusterParticles(struct R3SoftBodyHandle handle, uint32_t cluster, uint32_t *buffer, size_t capacity); @@ -16435,8 +16162,8 @@ size_t r3SoftBody_ClusterParticles(struct R3SoftBodyHandle handle, * Enable or disable pinning the cluster for the soft body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetClusterPinned(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetClusterPinned(struct R3SoftBodyHandle handle, uint32_t cluster, R3Bool value); @@ -16444,8 +16171,8 @@ R3Status r3SoftBody_SetClusterPinned(struct R3SoftBodyHandle handle, * Set the next world-space target pose of a pinned cluster. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetClusterKinematicTarget(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetClusterKinematicTarget(struct R3SoftBodyHandle handle, uint32_t cluster, struct R3Pose value); @@ -16453,8 +16180,8 @@ R3Status r3SoftBody_SetClusterKinematicTarget(struct R3SoftBodyHandle handle, * Enable or disable using cluster shape matching for the soft body. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetClusterShapeMatchingEnabled(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetClusterShapeMatchingEnabled(struct R3SoftBodyHandle handle, uint32_t cluster, R3Bool value); @@ -16462,8 +16189,8 @@ R3Status r3SoftBody_SetClusterShapeMatchingEnabled(struct R3SoftBodyHandle handl * Set the soft body cluster shape-matching stiffness multiplier. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetClusterStiffnessScale(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetClusterStiffnessScale(struct R3SoftBodyHandle handle, uint32_t cluster, R3Real value); @@ -16471,8 +16198,8 @@ R3Status r3SoftBody_SetClusterStiffnessScale(struct R3SoftBodyHandle handle, * Set the soft body cluster tear-resistance multiplier. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetClusterTearResistance(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetClusterTearResistance(struct R3SoftBodyHandle handle, uint32_t cluster, R3Real value); @@ -16481,8 +16208,8 @@ R3Status r3SoftBody_SetClusterTearResistance(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_Meshes(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_Meshes(struct R3SoftBodyHandle handle, struct R3SoftMeshInfo *buffer, size_t capacity); @@ -16491,8 +16218,8 @@ size_t r3SoftBody_Meshes(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_MeshVerticesById(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_MeshVerticesById(struct R3SoftBodyHandle handle, struct R3SoftMeshId id, struct R3Vector *buffer, size_t capacity); @@ -16502,8 +16229,8 @@ size_t r3SoftBody_MeshVerticesById(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_MeshIndicesById(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_MeshIndicesById(struct R3SoftBodyHandle handle, struct R3SoftMeshId id, uint32_t *buffer, size_t capacity); @@ -16513,8 +16240,8 @@ size_t r3SoftBody_MeshIndicesById(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_MeshColliders(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_MeshColliders(struct R3SoftBodyHandle handle, struct R3ColliderHandle *buffer, size_t capacity); @@ -16523,8 +16250,8 @@ size_t r3SoftBody_MeshColliders(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_MeshVertices(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_MeshVertices(struct R3SoftBodyHandle handle, struct R3ColliderHandle collider, struct R3Vector *buffer, size_t capacity); @@ -16534,8 +16261,8 @@ size_t r3SoftBody_MeshVertices(struct R3SoftBodyHandle handle, * @see @ref output_buffers * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_MeshIndices(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_MeshIndices(struct R3SoftBodyHandle handle, struct R3ColliderHandle collider, uint32_t *buffer, size_t capacity); @@ -16544,16 +16271,16 @@ size_t r3SoftBody_MeshIndices(struct R3SoftBodyHandle handle, * Return indices per collision-mesh element (2 for segments, 3 for triangles). * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -size_t r3SoftBody_MeshArity(struct R3SoftBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3SoftBody_MeshArity(struct R3SoftBodyHandle handle, struct R3ColliderHandle collider); /** * Return the selected collision mesh topology revision for cache invalidation. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -uint32_t r3SoftBody_MeshTopologyVersion(struct R3SoftBodyHandle handle, +RAPIER_API +uint32_t RAPIER_CALL r3SoftBody_MeshTopologyVersion(struct R3SoftBodyHandle handle, struct R3ColliderHandle collider); #if defined(RAPIER_FEM) @@ -16561,17 +16288,15 @@ uint32_t r3SoftBody_MeshTopologyVersion(struct R3SoftBodyHandle handle, * Set the soft body soft solver kind (R3_SOFT_SOLVER_*). * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetSolver(struct R3SoftBodyHandle handle, - uint32_t solver); +RAPIER_API R3Status RAPIER_CALL r3SoftBody_SetSolver(struct R3SoftBodyHandle handle, uint32_t solver); #endif /** * Set the soft body cluster shape-matching target pose. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetClusterShapeMatchingTarget(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetClusterShapeMatchingTarget(struct R3SoftBodyHandle handle, uint32_t cluster, const struct R3Pose *target); @@ -16579,8 +16304,8 @@ R3Status r3SoftBody_SetClusterShapeMatchingTarget(struct R3SoftBodyHandle handle * Set the soft body edge tear-resistance multiplier. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3SoftBody_SetEdgeTearResistance(struct R3SoftBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3SoftBody_SetEdgeTearResistance(struct R3SoftBodyHandle handle, size_t index, R3Real resistance); @@ -16588,8 +16313,8 @@ R3Status r3SoftBody_SetEdgeTearResistance(struct R3SoftBodyHandle handle, * Return whether the selected collision mesh is closed. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Bool r3SoftBody_MeshIsClosed(struct R3SoftBodyHandle handle, +RAPIER_API +R3Bool RAPIER_CALL r3SoftBody_MeshIsClosed(struct R3SoftBodyHandle handle, struct R3ColliderHandle collider); /** @@ -16597,8 +16322,8 @@ R3Bool r3SoftBody_MeshIsClosed(struct R3SoftBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetAdditionalMassProperties(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetAdditionalMassProperties(struct R3RigidBodyHandle handle, struct R3MassProperties properties, R3Bool wake_up); @@ -16606,31 +16331,30 @@ R3Status r3RigidBody_SetAdditionalMassProperties(struct R3RigidBodyHandle handle * Recompute body mass and inertia from attached colliders and additional mass properties. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_RecomputeMassPropertiesFromColliders(struct R3RigidBodyHandle handle); +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_RecomputeMassPropertiesFromColliders(struct R3RigidBodyHandle handle); /** * Set the collider local mass properties. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetMassProperties(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetMassProperties(struct R3ColliderHandle handle, struct R3MassProperties properties); /** * Return the collider local mass properties. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3MassProperties r3Collider_MassProperties(struct R3ColliderHandle handle); +RAPIER_API struct R3MassProperties RAPIER_CALL r3Collider_MassProperties(struct R3ColliderHandle handle); /** * Set the rigid body translation/rotation lock bitmask. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetLockedAxes(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetLockedAxes(struct R3RigidBodyHandle handle, uint8_t axes, R3Bool wake_up); @@ -16638,28 +16362,28 @@ R3Status r3RigidBody_SetLockedAxes(struct R3RigidBodyHandle handle, * Return the rigid body translation/rotation lock bitmask. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL uint8_t r3RigidBody_LockedAxes(struct R3RigidBodyHandle handle); +RAPIER_API uint8_t RAPIER_CALL r3RigidBody_LockedAxes(struct R3RigidBodyHandle handle); /** * Return whether the collider is a voxel shape. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R3Bool r3Collider_IsVoxels(struct R3ColliderHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3Collider_IsVoxels(struct R3ColliderHandle handle); /** * Return voxel information at a flat index; found = 0 if absent. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3VoxelQuery r3Collider_VoxelAtFlatId(struct R3ColliderHandle handle, +RAPIER_API +struct R3VoxelQuery RAPIER_CALL r3Collider_VoxelAtFlatId(struct R3ColliderHandle handle, uint32_t id); /** * Fill or clear the voxel at key; the collider must have a voxel shape. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetVoxel(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetVoxel(struct R3ColliderHandle handle, struct R3VoxelKey key, R3Bool filled); @@ -16667,139 +16391,135 @@ R3Status r3Collider_SetVoxel(struct R3ColliderHandle handle, * Return the rigid body next kinematic world-space pose. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R3Pose r3RigidBody_NextPosition(struct R3RigidBodyHandle handle); +RAPIER_API struct R3Pose RAPIER_CALL r3RigidBody_NextPosition(struct R3RigidBodyHandle handle); /** * Return the rigid body world-space rotation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R3Rotation r3RigidBody_Rotation(struct R3RigidBodyHandle handle); +RAPIER_API struct R3Rotation RAPIER_CALL r3RigidBody_Rotation(struct R3RigidBodyHandle handle); /** * Return the rigid body world-space center of mass. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R3Vector r3RigidBody_CenterOfMass(struct R3RigidBodyHandle handle); +RAPIER_API struct R3Vector RAPIER_CALL r3RigidBody_CenterOfMass(struct R3RigidBodyHandle handle); /** * Return the rigid body body-local center of mass. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R3Vector r3RigidBody_LocalCenterOfMass(struct R3RigidBodyHandle handle); +RAPIER_API struct R3Vector RAPIER_CALL r3RigidBody_LocalCenterOfMass(struct R3RigidBodyHandle handle); /** * Return the rigid body accumulated user-applied world-space force. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL struct R3Vector r3RigidBody_UserForce(struct R3RigidBodyHandle handle); +RAPIER_API struct R3Vector RAPIER_CALL r3RigidBody_UserForce(struct R3RigidBodyHandle handle); /** * Return the rigid body accumulated user-applied world-space torque. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3AngVector r3RigidBody_UserTorque(struct R3RigidBodyHandle handle); +RAPIER_API R3AngVector RAPIER_CALL r3RigidBody_UserTorque(struct R3RigidBodyHandle handle); /** * Return the rigid body body type (R3_DYNAMIC, R3_FIXED, or a kinematic kind). * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL uint32_t r3RigidBody_BodyType(struct R3RigidBodyHandle handle); +RAPIER_API uint32_t RAPIER_CALL r3RigidBody_BodyType(struct R3RigidBodyHandle handle); /** * Return the rigid body mass. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Real r3RigidBody_Mass(struct R3RigidBodyHandle handle); +RAPIER_API R3Real RAPIER_CALL r3RigidBody_Mass(struct R3RigidBodyHandle handle); /** * Return the rigid body gravity multiplier. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Real r3RigidBody_GravityScale(struct R3RigidBodyHandle handle); +RAPIER_API R3Real RAPIER_CALL r3RigidBody_GravityScale(struct R3RigidBodyHandle handle); /** * Return the rigid body linear damping coefficient. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Real r3RigidBody_LinearDamping(struct R3RigidBodyHandle handle); +RAPIER_API R3Real RAPIER_CALL r3RigidBody_LinearDamping(struct R3RigidBodyHandle handle); /** * Return the rigid body angular damping coefficient. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Real r3RigidBody_AngularDamping(struct R3RigidBodyHandle handle); +RAPIER_API R3Real RAPIER_CALL r3RigidBody_AngularDamping(struct R3RigidBodyHandle handle); /** * Return the rigid body kinetic energy. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Real r3RigidBody_KineticEnergy(struct R3RigidBodyHandle handle); +RAPIER_API R3Real RAPIER_CALL r3RigidBody_KineticEnergy(struct R3RigidBodyHandle handle); /** * Return the rigid body soft-CCD prediction distance. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Real r3RigidBody_SoftCcdPrediction(struct R3RigidBodyHandle handle); +RAPIER_API R3Real RAPIER_CALL r3RigidBody_SoftCcdPrediction(struct R3RigidBodyHandle handle); /** * Return whether the rigid body is using continuous collision detection. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsCcdEnabled(struct R3RigidBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3RigidBody_IsCcdEnabled(struct R3RigidBodyHandle handle); /** * Return whether the rigid body is dynamic. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsDynamic(struct R3RigidBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3RigidBody_IsDynamic(struct R3RigidBodyHandle handle); /** * Return the associated soft-body handle, or an invalid handle if this is not a soft proxy. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyHandle r3RigidBody_SoftBody(struct R3RigidBodyHandle handle); +RAPIER_API struct R3SoftBodyHandle RAPIER_CALL r3RigidBody_SoftBody(struct R3RigidBodyHandle handle); /** * Return whether the rigid body is a soft-body proxy. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsSoftFrame(struct R3RigidBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3RigidBody_IsSoftFrame(struct R3RigidBodyHandle handle); /** * Return whether the rigid body is fixed. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsFixed(struct R3RigidBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3RigidBody_IsFixed(struct R3RigidBodyHandle handle); /** * Return whether the rigid body is kinematic. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsKinematic(struct R3RigidBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3RigidBody_IsKinematic(struct R3RigidBodyHandle handle); /** * Return whether the rigid body is moving. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsMoving(struct R3RigidBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3RigidBody_IsMoving(struct R3RigidBodyHandle handle); /** * Return whether the rigid body is currently using CCD for its motion. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Bool r3RigidBody_IsCcdActive(struct R3RigidBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3RigidBody_IsCcdActive(struct R3RigidBodyHandle handle); /** * Set the rigid body world-space rotation. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetRotation(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetRotation(struct R3RigidBodyHandle handle, struct R3Rotation value, R3Bool wake_up); @@ -16808,8 +16528,8 @@ R3Status r3RigidBody_SetRotation(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetBodyType(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetBodyType(struct R3RigidBodyHandle handle, uint32_t value, R3Bool wake_up); @@ -16817,8 +16537,8 @@ R3Status r3RigidBody_SetBodyType(struct R3RigidBodyHandle handle, * Set the rigid body next kinematic world-space rotation. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetNextKinematicRotation(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetNextKinematicRotation(struct R3RigidBodyHandle handle, struct R3Rotation value); /** @@ -16826,8 +16546,8 @@ R3Status r3RigidBody_SetNextKinematicRotation(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetAdditionalMass(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetAdditionalMass(struct R3RigidBodyHandle handle, R3Real value, R3Bool wake_up); @@ -16835,16 +16555,16 @@ R3Status r3RigidBody_SetAdditionalMass(struct R3RigidBodyHandle handle, * Set the rigid body soft-CCD prediction distance. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetSoftCcdPrediction(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetSoftCcdPrediction(struct R3RigidBodyHandle handle, R3Real value); /** * Enable or disable using continuous collision detection for the rigid body. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetCcdEnabled(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetCcdEnabled(struct R3RigidBodyHandle handle, R3Bool value); /** @@ -16852,8 +16572,8 @@ R3Status r3RigidBody_SetCcdEnabled(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetTranslationsLocked(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetTranslationsLocked(struct R3RigidBodyHandle handle, R3Bool value, R3Bool wake_up); @@ -16862,8 +16582,8 @@ R3Status r3RigidBody_SetTranslationsLocked(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetRotationsLocked(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetRotationsLocked(struct R3RigidBodyHandle handle, R3Bool value, R3Bool wake_up); @@ -16871,24 +16591,24 @@ R3Status r3RigidBody_SetRotationsLocked(struct R3RigidBodyHandle handle, * Set the rigid body signed dominance group. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetDominanceGroup(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetDominanceGroup(struct R3RigidBodyHandle handle, int8_t value); /** * Set the rigid body additional solver iterations for connected bodies. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetAdditionalSolverIterations(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetAdditionalSolverIterations(struct R3RigidBodyHandle handle, size_t value); /** * Set the rigid body additional PGS iterations. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetAdditionalPgsIterations(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetAdditionalPgsIterations(struct R3RigidBodyHandle handle, size_t value); /** @@ -16896,8 +16616,8 @@ R3Status r3RigidBody_SetAdditionalPgsIterations(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_AddTorque(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_AddTorque(struct R3RigidBodyHandle handle, R3AngVector value, R3Bool wake_up); @@ -16906,8 +16626,8 @@ R3Status r3RigidBody_AddTorque(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_ApplyTorqueImpulse(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_ApplyTorqueImpulse(struct R3RigidBodyHandle handle, R3AngVector value, R3Bool wake_up); @@ -16916,8 +16636,8 @@ R3Status r3RigidBody_ApplyTorqueImpulse(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_AddForceAtPoint(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_AddForceAtPoint(struct R3RigidBodyHandle handle, struct R3Vector value, struct R3Vector point, R3Bool wake_up); @@ -16927,16 +16647,16 @@ R3Status r3RigidBody_AddForceAtPoint(struct R3RigidBodyHandle handle, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_ResetTorques(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_ResetTorques(struct R3RigidBodyHandle handle, R3Bool wake_up); /** * Return world-space velocity at a world-space point, including angular motion. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -struct R3Vector r3RigidBody_VelocityAtPoint(struct R3RigidBodyHandle handle, +RAPIER_API +struct R3Vector RAPIER_CALL r3RigidBody_VelocityAtPoint(struct R3RigidBodyHandle handle, struct R3Vector point); /** @@ -16944,8 +16664,8 @@ struct R3Vector r3RigidBody_VelocityAtPoint(struct R3RigidBodyHandle handle, * @see @ref output_buffers * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -size_t r3RigidBody_Colliders(struct R3RigidBodyHandle handle, +RAPIER_API +size_t RAPIER_CALL r3RigidBody_Colliders(struct R3RigidBodyHandle handle, struct R3ColliderHandle *buffer, size_t capacity); @@ -16954,8 +16674,7 @@ size_t r3RigidBody_Colliders(struct R3RigidBodyHandle handle, * Return whether the rigid body is using gyroscopic forces. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Bool r3RigidBody_GyroscopicForcesEnabled(struct R3RigidBodyHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3RigidBody_GyroscopicForcesEnabled(struct R3RigidBodyHandle handle); #endif #if defined(RAPIER_DIM3) @@ -16963,8 +16682,8 @@ R3Bool r3RigidBody_GyroscopicForcesEnabled(struct R3RigidBodyHandle handle); * Enable or disable using gyroscopic forces for the rigid body. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3RigidBody_SetGyroscopicForcesEnabled(struct R3RigidBodyHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3RigidBody_SetGyroscopicForcesEnabled(struct R3RigidBodyHandle handle, R3Bool enabled); #endif @@ -16972,187 +16691,175 @@ R3Status r3RigidBody_SetGyroscopicForcesEnabled(struct R3RigidBodyHandle handle, * Set the collider mass per unit volume. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetDensity(struct R3ColliderHandle handle, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3Collider_SetDensity(struct R3ColliderHandle handle, R3Real value); /** * Set the collider mass. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetMass(struct R3ColliderHandle handle, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3Collider_SetMass(struct R3ColliderHandle handle, R3Real value); /** * Enable or disable the collider. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetEnabled(struct R3ColliderHandle handle, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3Collider_SetEnabled(struct R3ColliderHandle handle, R3Bool value); /** * Set the collider contact-force filtering groups. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetSolverGroups(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetSolverGroups(struct R3ColliderHandle handle, struct R3InteractionGroups value); /** * Set the collider friction combination rule (R3_COMBINE_*). * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetFrictionCombineRule(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetFrictionCombineRule(struct R3ColliderHandle handle, uint32_t value); /** * Set the collider restitution combination rule (R3_COMBINE_*). * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetRestitutionCombineRule(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetRestitutionCombineRule(struct R3ColliderHandle handle, uint32_t value); /** * Set the collider extra separation skin around the shape. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetContactSkin(struct R3ColliderHandle handle, - R3Real value); +RAPIER_API R3Status RAPIER_CALL r3Collider_SetContactSkin(struct R3ColliderHandle handle, R3Real value); /** * Set the collider force threshold for contact-force events. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetContactForceEventThreshold(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetContactForceEventThreshold(struct R3ColliderHandle handle, R3Real value); /** * Set the collider event-generation bitmask (R3_COLLISION_EVENTS and R3_CONTACT_FORCE_EVENTS). * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetActiveEvents(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetActiveEvents(struct R3ColliderHandle handle, uint32_t value); /** * Set the collider physics-hook activation bitmask. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetActiveHooks(struct R3ColliderHandle handle, - uint32_t value); +RAPIER_API R3Status RAPIER_CALL r3Collider_SetActiveHooks(struct R3ColliderHandle handle, uint32_t value); /** * Set the collider body-type collision activation bitmask. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetActiveCollisionTypes(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetActiveCollisionTypes(struct R3ColliderHandle handle, uint16_t value); /** * Return the collider world-space rotation. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R3Rotation r3Collider_Rotation(struct R3ColliderHandle handle); +RAPIER_API struct R3Rotation RAPIER_CALL r3Collider_Rotation(struct R3ColliderHandle handle); /** * Return the collider collision filtering groups. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3InteractionGroups r3Collider_CollisionGroups(struct R3ColliderHandle handle); +RAPIER_API +struct R3InteractionGroups RAPIER_CALL r3Collider_CollisionGroups(struct R3ColliderHandle handle); /** * Return the collider contact-force filtering groups. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -struct R3InteractionGroups r3Collider_SolverGroups(struct R3ColliderHandle handle); +RAPIER_API struct R3InteractionGroups RAPIER_CALL r3Collider_SolverGroups(struct R3ColliderHandle handle); /** * Return the collider application-owned 128-bit user value. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R3UserData r3Collider_UserData(struct R3ColliderHandle handle); +RAPIER_API struct R3UserData RAPIER_CALL r3Collider_UserData(struct R3ColliderHandle handle); /** * Return the collider event-generation bitmask (R3_COLLISION_EVENTS and * R3_CONTACT_FORCE_EVENTS). * @ingroup colliders */ -RAPIER_API RAPIER_CALL uint32_t r3Collider_ActiveEvents(struct R3ColliderHandle handle); +RAPIER_API uint32_t RAPIER_CALL r3Collider_ActiveEvents(struct R3ColliderHandle handle); /** * Return the collider mass. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R3Real r3Collider_Mass(struct R3ColliderHandle handle); +RAPIER_API R3Real RAPIER_CALL r3Collider_Mass(struct R3ColliderHandle handle); /** * Return the collider mass per unit volume. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R3Real r3Collider_Density(struct R3ColliderHandle handle); +RAPIER_API R3Real RAPIER_CALL r3Collider_Density(struct R3ColliderHandle handle); /** * Return the collider current volume. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R3Real r3Collider_Volume(struct R3ColliderHandle handle); +RAPIER_API R3Real RAPIER_CALL r3Collider_Volume(struct R3ColliderHandle handle); /** * Return the collider extra separation skin around the shape. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R3Real r3Collider_ContactSkin(struct R3ColliderHandle handle); +RAPIER_API R3Real RAPIER_CALL r3Collider_ContactSkin(struct R3ColliderHandle handle); /** * Return the collider force threshold for contact-force events. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Real r3Collider_ContactForceEventThreshold(struct R3ColliderHandle handle); +RAPIER_API R3Real RAPIER_CALL r3Collider_ContactForceEventThreshold(struct R3ColliderHandle handle); /** * Return whether the collider is enabled. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R3Bool r3Collider_IsEnabled(struct R3ColliderHandle handle); +RAPIER_API R3Bool RAPIER_CALL r3Collider_IsEnabled(struct R3ColliderHandle handle); /** * Return the current world-space axis-aligned bounds. * @ingroup colliders */ -RAPIER_API RAPIER_CALL struct R3Aabb r3Collider_ComputeAabb(struct R3ColliderHandle handle); +RAPIER_API struct R3Aabb RAPIER_CALL r3Collider_ComputeAabb(struct R3ColliderHandle handle); /** * Return an owned wrapper sharing the collider geometry. Release with r3FreeSharedShape. * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R3SharedShape *r3Collider_CloneShape(struct R3ColliderHandle handle); +RAPIER_API R3SharedShape *RAPIER_CALL r3Collider_CloneShape(struct R3ColliderHandle handle); /** * Replace collider geometry by sharing shape; the supplied wrapper is not consumed. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetShape(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetShape(struct R3ColliderHandle handle, const R3SharedShape *shape); /** * Set the collider pose relative to the parent rigid body. * @ingroup colliders */ -RAPIER_API RAPIER_CALL -R3Status r3Collider_SetPositionWrtParent(struct R3ColliderHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3Collider_SetPositionWrtParent(struct R3ColliderHandle handle, struct R3Pose value); /** @@ -17160,28 +16867,28 @@ R3Status r3Collider_SetPositionWrtParent(struct R3ColliderHandle handle, * has already been freed. * @ingroup rigid_bodies */ -RAPIER_API RAPIER_CALL R3Status r3RigidBody_ValidateHandle(struct R3RigidBodyHandle handle); +RAPIER_API R3Status RAPIER_CALL r3RigidBody_ValidateHandle(struct R3RigidBodyHandle handle); /** * Validate the index and generation in the live owning world. Cannot detect a world pointer that * has already been freed. * @ingroup colliders */ -RAPIER_API RAPIER_CALL R3Status r3Collider_ValidateHandle(struct R3ColliderHandle handle); +RAPIER_API R3Status RAPIER_CALL r3Collider_ValidateHandle(struct R3ColliderHandle handle); /** * Validate the index and generation in the live owning world. Cannot detect a world pointer that * has already been freed. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL R3Status r3SoftBody_ValidateHandle(struct R3SoftBodyHandle handle); +RAPIER_API R3Status RAPIER_CALL r3SoftBody_ValidateHandle(struct R3SoftBodyHandle handle); /** * Set the joint desc joint frame relative to body 1. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetLocalFrame1(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetLocalFrame1(struct R3JointDesc *desc, struct R3Pose value); /** @@ -17189,8 +16896,8 @@ R3Status r3JointDesc_SetLocalFrame1(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetLocalFrame1(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetLocalFrame1(struct R3ImpulseJointHandle handle, struct R3Pose value, R3Bool wake_up); @@ -17198,8 +16905,8 @@ R3Status r3ImpulseJoint_SetLocalFrame1(struct R3ImpulseJointHandle handle, * Set the joint desc joint frame relative to body 2. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetLocalFrame2(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetLocalFrame2(struct R3JointDesc *desc, struct R3Pose value); /** @@ -17207,8 +16914,8 @@ R3Status r3JointDesc_SetLocalFrame2(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetLocalFrame2(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetLocalFrame2(struct R3ImpulseJointHandle handle, struct R3Pose value, R3Bool wake_up); @@ -17216,8 +16923,8 @@ R3Status r3ImpulseJoint_SetLocalFrame2(struct R3ImpulseJointHandle handle, * Set the joint desc joint anchor relative to body 1. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetLocalAnchor1(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetLocalAnchor1(struct R3JointDesc *desc, struct R3Vector value); /** @@ -17225,8 +16932,8 @@ R3Status r3JointDesc_SetLocalAnchor1(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetLocalAnchor1(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetLocalAnchor1(struct R3ImpulseJointHandle handle, struct R3Vector value, R3Bool wake_up); @@ -17234,8 +16941,8 @@ R3Status r3ImpulseJoint_SetLocalAnchor1(struct R3ImpulseJointHandle handle, * Set the joint desc joint anchor relative to body 2. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetLocalAnchor2(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetLocalAnchor2(struct R3JointDesc *desc, struct R3Vector value); /** @@ -17243,8 +16950,8 @@ R3Status r3JointDesc_SetLocalAnchor2(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetLocalAnchor2(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetLocalAnchor2(struct R3ImpulseJointHandle handle, struct R3Vector value, R3Bool wake_up); @@ -17252,17 +16959,15 @@ R3Status r3ImpulseJoint_SetLocalAnchor2(struct R3ImpulseJointHandle handle, * Enable or disable allowing contacts between connected bodies for the joint desc. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetContactsEnabled(struct R3JointDesc *desc, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3JointDesc_SetContactsEnabled(struct R3JointDesc *desc, R3Bool value); /** * Enable or disable allowing contacts between connected bodies for the impulse joint. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetContactsEnabled(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetContactsEnabled(struct R3ImpulseJointHandle handle, R3Bool value, R3Bool wake_up); @@ -17270,17 +16975,15 @@ R3Status r3ImpulseJoint_SetContactsEnabled(struct R3ImpulseJointHandle handle, * Enable or disable the joint desc. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetEnabled(struct R3JointDesc *desc, - R3Bool value); +RAPIER_API R3Status RAPIER_CALL r3JointDesc_SetEnabled(struct R3JointDesc *desc, R3Bool value); /** * Enable or disable the impulse joint. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetEnabled(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetEnabled(struct R3ImpulseJointHandle handle, R3Bool value, R3Bool wake_up); @@ -17288,8 +16991,8 @@ R3Status r3ImpulseJoint_SetEnabled(struct R3ImpulseJointHandle handle, * Set the joint desc joint spring coefficients. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetSoftness(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetSoftness(struct R3JointDesc *desc, struct R3SpringCoefficients value); /** @@ -17297,8 +17000,8 @@ R3Status r3JointDesc_SetSoftness(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup soft_bodies */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetSoftness(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetSoftness(struct R3ImpulseJointHandle handle, struct R3SpringCoefficients value, R3Bool wake_up); @@ -17306,17 +17009,15 @@ R3Status r3ImpulseJoint_SetSoftness(struct R3ImpulseJointHandle handle, * Set the joint desc translation/rotation lock bitmask. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetLockedAxes(struct R3JointDesc *desc, - uint8_t value); +RAPIER_API R3Status RAPIER_CALL r3JointDesc_SetLockedAxes(struct R3JointDesc *desc, uint8_t value); /** * Set the impulse joint translation/rotation lock bitmask. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetLockedAxes(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetLockedAxes(struct R3ImpulseJointHandle handle, uint8_t value, R3Bool wake_up); @@ -17324,17 +17025,15 @@ R3Status r3ImpulseJoint_SetLockedAxes(struct R3ImpulseJointHandle handle, * Set the joint desc joint axis mask with limits enabled. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetLimitAxes(struct R3JointDesc *desc, - uint8_t value); +RAPIER_API R3Status RAPIER_CALL r3JointDesc_SetLimitAxes(struct R3JointDesc *desc, uint8_t value); /** * Set the impulse joint joint axis mask with limits enabled. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetLimitAxes(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetLimitAxes(struct R3ImpulseJointHandle handle, uint8_t value, R3Bool wake_up); @@ -17342,17 +17041,15 @@ R3Status r3ImpulseJoint_SetLimitAxes(struct R3ImpulseJointHandle handle, * Set the joint desc joint axis mask with motors enabled. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetMotorAxes(struct R3JointDesc *desc, - uint8_t value); +RAPIER_API R3Status RAPIER_CALL r3JointDesc_SetMotorAxes(struct R3JointDesc *desc, uint8_t value); /** * Set the impulse joint joint axis mask with motors enabled. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetMotorAxes(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetMotorAxes(struct R3ImpulseJointHandle handle, uint8_t value, R3Bool wake_up); @@ -17360,17 +17057,15 @@ R3Status r3ImpulseJoint_SetMotorAxes(struct R3ImpulseJointHandle handle, * Set the joint desc coupled joint axis mask. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetCoupledAxes(struct R3JointDesc *desc, - uint8_t value); +RAPIER_API R3Status RAPIER_CALL r3JointDesc_SetCoupledAxes(struct R3JointDesc *desc, uint8_t value); /** * Set the impulse joint coupled joint axis mask. * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetCoupledAxes(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetCoupledAxes(struct R3ImpulseJointHandle handle, uint8_t value, R3Bool wake_up); @@ -17378,8 +17073,8 @@ R3Status r3ImpulseJoint_SetCoupledAxes(struct R3ImpulseJointHandle handle, * Set the joint desc joint principal axis in body 1 local coordinates. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetLocalAxis1(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetLocalAxis1(struct R3JointDesc *desc, struct R3Vector value); /** @@ -17387,8 +17082,8 @@ R3Status r3JointDesc_SetLocalAxis1(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetLocalAxis1(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetLocalAxis1(struct R3ImpulseJointHandle handle, struct R3Vector value, R3Bool wake_up); @@ -17396,8 +17091,8 @@ R3Status r3ImpulseJoint_SetLocalAxis1(struct R3ImpulseJointHandle handle, * Set the joint desc joint principal axis in body 2 local coordinates. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetLocalAxis2(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetLocalAxis2(struct R3JointDesc *desc, struct R3Vector value); /** @@ -17405,8 +17100,8 @@ R3Status r3JointDesc_SetLocalAxis2(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetLocalAxis2(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetLocalAxis2(struct R3ImpulseJointHandle handle, struct R3Vector value, R3Bool wake_up); @@ -17414,8 +17109,8 @@ R3Status r3ImpulseJoint_SetLocalAxis2(struct R3ImpulseJointHandle handle, * Set the joint desc minimum and maximum limits on an axis (linear distance or angular radians). * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetLimits(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetLimits(struct R3JointDesc *desc, uint32_t joint_axis, R3Real min, R3Real max); @@ -17426,8 +17121,8 @@ R3Status r3JointDesc_SetLimits(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetLimits(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetLimits(struct R3ImpulseJointHandle handle, uint32_t joint_axis, R3Real min, R3Real max, @@ -17437,8 +17132,8 @@ R3Status r3ImpulseJoint_SetLimits(struct R3ImpulseJointHandle handle, * Set the joint desc motor position/velocity targets and spring coefficients on an axis. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetMotor(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetMotor(struct R3JointDesc *desc, uint32_t joint_axis, R3Real target_position, R3Real target_velocity, @@ -17450,8 +17145,8 @@ R3Status r3JointDesc_SetMotor(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetMotor(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetMotor(struct R3ImpulseJointHandle handle, uint32_t joint_axis, R3Real target_position, R3Real target_velocity, @@ -17463,8 +17158,8 @@ R3Status r3ImpulseJoint_SetMotor(struct R3ImpulseJointHandle handle, * Set the joint desc maximum motor force or torque on an axis. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetMotorMaxForce(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetMotorMaxForce(struct R3JointDesc *desc, uint32_t joint_axis, R3Real max_force); @@ -17473,8 +17168,8 @@ R3Status r3JointDesc_SetMotorMaxForce(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetMotorMaxForce(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetMotorMaxForce(struct R3ImpulseJointHandle handle, uint32_t joint_axis, R3Real max_force, R3Bool wake_up); @@ -17483,8 +17178,8 @@ R3Status r3ImpulseJoint_SetMotorMaxForce(struct R3ImpulseJointHandle handle, * Set the joint desc motor model on an axis (0 = acceleration-based, 1 = force-based). * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetMotorModel(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetMotorModel(struct R3JointDesc *desc, uint32_t joint_axis, uint32_t model); @@ -17493,8 +17188,8 @@ R3Status r3JointDesc_SetMotorModel(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetMotorModel(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetMotorModel(struct R3ImpulseJointHandle handle, uint32_t joint_axis, uint32_t model, R3Bool wake_up); @@ -17503,8 +17198,8 @@ R3Status r3ImpulseJoint_SetMotorModel(struct R3ImpulseJointHandle handle, * Set the joint desc application-owned 128-bit user value. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetUserData(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetUserData(struct R3JointDesc *desc, struct R3UserData value); /** @@ -17512,8 +17207,8 @@ R3Status r3JointDesc_SetUserData(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetUserData(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetUserData(struct R3ImpulseJointHandle handle, struct R3UserData value, R3Bool wake_up); @@ -17521,8 +17216,8 @@ R3Status r3ImpulseJoint_SetUserData(struct R3ImpulseJointHandle handle, * Set the joint desc motor position target and spring coefficients on an axis. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetMotorPosition(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetMotorPosition(struct R3JointDesc *desc, uint32_t joint_axis, R3Real target_position, R3Real stiffness, @@ -17533,8 +17228,8 @@ R3Status r3JointDesc_SetMotorPosition(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetMotorPosition(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetMotorPosition(struct R3ImpulseJointHandle handle, uint32_t joint_axis, R3Real target_position, R3Real stiffness, @@ -17545,8 +17240,8 @@ R3Status r3ImpulseJoint_SetMotorPosition(struct R3ImpulseJointHandle handle, * Set the joint desc motor velocity target and damping factor on an axis. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3JointDesc_SetMotorVelocity(struct R3JointDesc *desc, +RAPIER_API +R3Status RAPIER_CALL r3JointDesc_SetMotorVelocity(struct R3JointDesc *desc, uint32_t joint_axis, R3Real target_velocity, R3Real factor); @@ -17556,8 +17251,8 @@ R3Status r3JointDesc_SetMotorVelocity(struct R3JointDesc *desc, * wake_up = 1 wakes affected bodies; 0 preserves their sleep state. * @ingroup joints */ -RAPIER_API RAPIER_CALL -R3Status r3ImpulseJoint_SetMotorVelocity(struct R3ImpulseJointHandle handle, +RAPIER_API +R3Status RAPIER_CALL r3ImpulseJoint_SetMotorVelocity(struct R3ImpulseJointHandle handle, uint32_t joint_axis, R3Real target_velocity, R3Real factor, @@ -17569,8 +17264,8 @@ R3Status r3ImpulseJoint_SetMotorVelocity(struct R3ImpulseJointHandle handle, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3ConvexDecompositionSharedShape(struct R3VectorView vertices, +RAPIER_API +R3SharedShape *RAPIER_CALL r3ConvexDecompositionSharedShape(struct R3VectorView vertices, R3SurfaceElementView indices); /** @@ -17579,8 +17274,8 @@ R3SharedShape *r3ConvexDecompositionSharedShape(struct R3VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3VoxelsSharedShapeFromPoints(struct R3Vector voxel_size, +RAPIER_API +R3SharedShape *RAPIER_CALL r3VoxelsSharedShapeFromPoints(struct R3Vector voxel_size, struct R3VectorView points); /** @@ -17589,8 +17284,8 @@ R3SharedShape *r3VoxelsSharedShapeFromPoints(struct R3Vector voxel_size, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3VoxelizedMeshSharedShape(struct R3VectorView vertices, +RAPIER_API +R3SharedShape *RAPIER_CALL r3VoxelizedMeshSharedShape(struct R3VectorView vertices, R3SurfaceElementView indices, R3Real voxel_size); @@ -17599,7 +17294,7 @@ R3SharedShape *r3VoxelizedMeshSharedShape(struct R3VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL R3SharedShape *r3ConvexHullSharedShape(struct R3VectorView vertices); +RAPIER_API R3SharedShape *RAPIER_CALL r3ConvexHullSharedShape(struct R3VectorView vertices); /** * Create an owned triangle mesh from vertices and triangle indices. Release it with @@ -17607,8 +17302,8 @@ RAPIER_API RAPIER_CALL R3SharedShape *r3ConvexHullSharedShape(struct R3VectorVie * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3TrimeshSharedShape(struct R3VectorView vertices, +RAPIER_API +R3SharedShape *RAPIER_CALL r3TrimeshSharedShape(struct R3VectorView vertices, struct R3TriangleView indices); /** @@ -17616,8 +17311,8 @@ R3SharedShape *r3TrimeshSharedShape(struct R3VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3PolylineSharedShape(struct R3VectorView vertices, +RAPIER_API +R3SharedShape *RAPIER_CALL r3PolylineSharedShape(struct R3VectorView vertices, struct R3EdgeView indices); #if defined(RAPIER_DIM2) @@ -17627,8 +17322,8 @@ R3SharedShape *r3PolylineSharedShape(struct R3VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3OrientedPolylineSharedShape(struct R3VectorView vertices, +RAPIER_API +R3SharedShape *RAPIER_CALL r3OrientedPolylineSharedShape(struct R3VectorView vertices, struct R3EdgeView indices); #endif @@ -17639,8 +17334,7 @@ R3SharedShape *r3OrientedPolylineSharedShape(struct R3VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3ConvexPolylineSharedShape(struct R3VectorView vertices); +RAPIER_API R3SharedShape *RAPIER_CALL r3ConvexPolylineSharedShape(struct R3VectorView vertices); #endif /** @@ -17648,8 +17342,8 @@ R3SharedShape *r3ConvexPolylineSharedShape(struct R3VectorView vertices); * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3RoundConvexHullSharedShape(struct R3VectorView vertices, +RAPIER_API +R3SharedShape *RAPIER_CALL r3RoundConvexHullSharedShape(struct R3VectorView vertices, R3Real border_radius); /** @@ -17658,8 +17352,8 @@ R3SharedShape *r3RoundConvexHullSharedShape(struct R3VectorView vertices, * Copies typed input geometry into an owned shared shape; arrays may be released on return. * @ingroup shapes */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3TrimeshSharedShapeWithFlags(struct R3VectorView vertices, +RAPIER_API +R3SharedShape *RAPIER_CALL r3TrimeshSharedShapeWithFlags(struct R3VectorView vertices, struct R3TriangleView indices, uint32_t flags); @@ -17667,22 +17361,22 @@ R3SharedShape *r3TrimeshSharedShapeWithFlags(struct R3VectorView vertices, * Create an owned world. Release it with FreeWorld. * @ingroup worlds */ -RAPIER_API RAPIER_CALL struct R3World *r3NewWorld(void); +RAPIER_API struct R3World *RAPIER_CALL r3NewWorld(void); /** * Free a world. NULL is allowed. Rejects destruction from an active callback. * The caller must prevent other threads from starting calls during destruction. * @ingroup worlds */ -RAPIER_API RAPIER_CALL R3Status r3FreeWorld(struct R3World *world); +RAPIER_API R3Status RAPIER_CALL r3FreeWorld(struct R3World *world); /** * Compute a velocity correction from callback-visible body state, updating the PID controller * history. The context is valid only during its callback. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3VelocityCorrection r3ReadPidController_RigidBodyCorrection(const struct R3ReadContext *context, +RAPIER_API +struct R3VelocityCorrection RAPIER_CALL r3ReadPidController_RigidBodyCorrection(const struct R3ReadContext *context, struct R3PidController *controller, R3Real dt, struct R3RigidBodyHandle body, @@ -17695,15 +17389,15 @@ struct R3VelocityCorrection r3ReadPidController_RigidBodyCorrection(const struct * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL size_t r3ReadRigidBodyCount(const struct R3ReadContext *context); +RAPIER_API size_t RAPIER_CALL r3ReadRigidBodyCount(const struct R3ReadContext *context); /** * Copy entity handles. Uses only the callback-scoped read context; never retain the context. * @see @ref output_buffers * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -size_t r3ReadRigidBodyHandles(const struct R3ReadContext *context, +RAPIER_API +size_t RAPIER_CALL r3ReadRigidBodyHandles(const struct R3ReadContext *context, struct R3RigidBodyHandle *buffer, size_t capacity); @@ -17712,8 +17406,8 @@ size_t r3ReadRigidBodyHandles(const struct R3ReadContext *context, * false. Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadRigidBody_Contains(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadRigidBody_Contains(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17721,15 +17415,15 @@ R3Bool r3ReadRigidBody_Contains(const struct R3ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL size_t r3ReadColliderCount(const struct R3ReadContext *context); +RAPIER_API size_t RAPIER_CALL r3ReadColliderCount(const struct R3ReadContext *context); /** * Copy entity handles. Uses only the callback-scoped read context; never retain the context. * @see @ref output_buffers * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -size_t r3ReadColliderHandles(const struct R3ReadContext *context, +RAPIER_API +size_t RAPIER_CALL r3ReadColliderHandles(const struct R3ReadContext *context, struct R3ColliderHandle *buffer, size_t capacity); @@ -17738,8 +17432,8 @@ size_t r3ReadColliderHandles(const struct R3ReadContext *context, * Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadCollider_Contains(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadCollider_Contains(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -17748,8 +17442,8 @@ R3Bool r3ReadCollider_Contains(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -size_t r3ReadCollider_ShapeIdentity(const struct R3ReadContext *context, +RAPIER_API +size_t RAPIER_CALL r3ReadCollider_ShapeIdentity(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -17757,8 +17451,8 @@ size_t r3ReadCollider_ShapeIdentity(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3MassProperties r3ReadCollider_MassProperties(const struct R3ReadContext *context, +RAPIER_API +struct R3MassProperties RAPIER_CALL r3ReadCollider_MassProperties(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -17766,8 +17460,8 @@ struct R3MassProperties r3ReadCollider_MassProperties(const struct R3ReadContext * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -uint8_t r3ReadRigidBody_LockedAxes(const struct R3ReadContext *context, +RAPIER_API +uint8_t RAPIER_CALL r3ReadRigidBody_LockedAxes(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17775,8 +17469,8 @@ uint8_t r3ReadRigidBody_LockedAxes(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadCollider_IsVoxels(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadCollider_IsVoxels(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -17784,8 +17478,8 @@ R3Bool r3ReadCollider_IsVoxels(const struct R3ReadContext *context, * read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3VoxelQuery r3ReadCollider_VoxelAtFlatId(const struct R3ReadContext *context, +RAPIER_API +struct R3VoxelQuery RAPIER_CALL r3ReadCollider_VoxelAtFlatId(const struct R3ReadContext *context, struct R3ColliderHandle handle, uint32_t id); @@ -17794,8 +17488,8 @@ struct R3VoxelQuery r3ReadCollider_VoxelAtFlatId(const struct R3ReadContext *con * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Pose r3ReadRigidBody_NextPosition(const struct R3ReadContext *context, +RAPIER_API +struct R3Pose RAPIER_CALL r3ReadRigidBody_NextPosition(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17803,8 +17497,8 @@ struct R3Pose r3ReadRigidBody_NextPosition(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Rotation r3ReadRigidBody_Rotation(const struct R3ReadContext *context, +RAPIER_API +struct R3Rotation RAPIER_CALL r3ReadRigidBody_Rotation(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17812,8 +17506,8 @@ struct R3Rotation r3ReadRigidBody_Rotation(const struct R3ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Vector r3ReadRigidBody_CenterOfMass(const struct R3ReadContext *context, +RAPIER_API +struct R3Vector RAPIER_CALL r3ReadRigidBody_CenterOfMass(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17821,8 +17515,8 @@ struct R3Vector r3ReadRigidBody_CenterOfMass(const struct R3ReadContext *context * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Vector r3ReadRigidBody_LocalCenterOfMass(const struct R3ReadContext *context, +RAPIER_API +struct R3Vector RAPIER_CALL r3ReadRigidBody_LocalCenterOfMass(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17830,8 +17524,8 @@ struct R3Vector r3ReadRigidBody_LocalCenterOfMass(const struct R3ReadContext *co * read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Vector r3ReadRigidBody_UserForce(const struct R3ReadContext *context, +RAPIER_API +struct R3Vector RAPIER_CALL r3ReadRigidBody_UserForce(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17839,8 +17533,8 @@ struct R3Vector r3ReadRigidBody_UserForce(const struct R3ReadContext *context, * read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3AngVector r3ReadRigidBody_UserTorque(const struct R3ReadContext *context, +RAPIER_API +R3AngVector RAPIER_CALL r3ReadRigidBody_UserTorque(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17848,8 +17542,8 @@ R3AngVector r3ReadRigidBody_UserTorque(const struct R3ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -uint32_t r3ReadRigidBody_BodyType(const struct R3ReadContext *context, +RAPIER_API +uint32_t RAPIER_CALL r3ReadRigidBody_BodyType(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17857,8 +17551,8 @@ uint32_t r3ReadRigidBody_BodyType(const struct R3ReadContext *context, * context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadRigidBody_Mass(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadRigidBody_Mass(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17866,8 +17560,8 @@ R3Real r3ReadRigidBody_Mass(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadRigidBody_GravityScale(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadRigidBody_GravityScale(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17875,8 +17569,8 @@ R3Real r3ReadRigidBody_GravityScale(const struct R3ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadRigidBody_LinearDamping(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadRigidBody_LinearDamping(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17884,8 +17578,8 @@ R3Real r3ReadRigidBody_LinearDamping(const struct R3ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadRigidBody_AngularDamping(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadRigidBody_AngularDamping(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17893,8 +17587,8 @@ R3Real r3ReadRigidBody_AngularDamping(const struct R3ReadContext *context, * the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadRigidBody_KineticEnergy(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadRigidBody_KineticEnergy(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17902,8 +17596,8 @@ R3Real r3ReadRigidBody_KineticEnergy(const struct R3ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadRigidBody_SoftCcdPrediction(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadRigidBody_SoftCcdPrediction(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17911,8 +17605,8 @@ R3Real r3ReadRigidBody_SoftCcdPrediction(const struct R3ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadRigidBody_IsCcdEnabled(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadRigidBody_IsCcdEnabled(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17920,8 +17614,8 @@ R3Bool r3ReadRigidBody_IsCcdEnabled(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadRigidBody_IsDynamic(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadRigidBody_IsDynamic(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17929,8 +17623,8 @@ R3Bool r3ReadRigidBody_IsDynamic(const struct R3ReadContext *context, * only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3SoftBodyHandle r3ReadRigidBody_SoftBody(const struct R3ReadContext *context, +RAPIER_API +struct R3SoftBodyHandle RAPIER_CALL r3ReadRigidBody_SoftBody(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17938,8 +17632,8 @@ struct R3SoftBodyHandle r3ReadRigidBody_SoftBody(const struct R3ReadContext *con * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadRigidBody_IsSoftFrame(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadRigidBody_IsSoftFrame(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17947,8 +17641,8 @@ R3Bool r3ReadRigidBody_IsSoftFrame(const struct R3ReadContext *context, * the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadRigidBody_IsFixed(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadRigidBody_IsFixed(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17956,8 +17650,8 @@ R3Bool r3ReadRigidBody_IsFixed(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadRigidBody_IsKinematic(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadRigidBody_IsKinematic(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17965,8 +17659,8 @@ R3Bool r3ReadRigidBody_IsKinematic(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadRigidBody_IsMoving(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadRigidBody_IsMoving(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17974,8 +17668,8 @@ R3Bool r3ReadRigidBody_IsMoving(const struct R3ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadRigidBody_IsCcdActive(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadRigidBody_IsCcdActive(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -17983,8 +17677,8 @@ R3Bool r3ReadRigidBody_IsCcdActive(const struct R3ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Vector r3ReadRigidBody_VelocityAtPoint(const struct R3ReadContext *context, +RAPIER_API +struct R3Vector RAPIER_CALL r3ReadRigidBody_VelocityAtPoint(const struct R3ReadContext *context, struct R3RigidBodyHandle handle, struct R3Vector point); @@ -17994,8 +17688,8 @@ struct R3Vector r3ReadRigidBody_VelocityAtPoint(const struct R3ReadContext *cont * @see @ref output_buffers * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -size_t r3ReadRigidBody_Colliders(const struct R3ReadContext *context, +RAPIER_API +size_t RAPIER_CALL r3ReadRigidBody_Colliders(const struct R3ReadContext *context, struct R3RigidBodyHandle handle, struct R3ColliderHandle *buffer, size_t capacity); @@ -18006,8 +17700,8 @@ size_t r3ReadRigidBody_Colliders(const struct R3ReadContext *context, * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadRigidBody_GyroscopicForcesEnabled(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadRigidBody_GyroscopicForcesEnabled(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); #endif @@ -18016,8 +17710,8 @@ R3Bool r3ReadRigidBody_GyroscopicForcesEnabled(const struct R3ReadContext *conte * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Rotation r3ReadCollider_Rotation(const struct R3ReadContext *context, +RAPIER_API +struct R3Rotation RAPIER_CALL r3ReadCollider_Rotation(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18025,8 +17719,8 @@ struct R3Rotation r3ReadCollider_Rotation(const struct R3ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3InteractionGroups r3ReadCollider_CollisionGroups(const struct R3ReadContext *context, +RAPIER_API +struct R3InteractionGroups RAPIER_CALL r3ReadCollider_CollisionGroups(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18034,8 +17728,8 @@ struct R3InteractionGroups r3ReadCollider_CollisionGroups(const struct R3ReadCon * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3InteractionGroups r3ReadCollider_SolverGroups(const struct R3ReadContext *context, +RAPIER_API +struct R3InteractionGroups RAPIER_CALL r3ReadCollider_SolverGroups(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18043,8 +17737,8 @@ struct R3InteractionGroups r3ReadCollider_SolverGroups(const struct R3ReadContex * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3UserData r3ReadCollider_UserData(const struct R3ReadContext *context, +RAPIER_API +struct R3UserData RAPIER_CALL r3ReadCollider_UserData(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18052,16 +17746,16 @@ struct R3UserData r3ReadCollider_UserData(const struct R3ReadContext *context, * R3_CONTACT_FORCE_EVENTS). Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -uint32_t r3ReadCollider_ActiveEvents(const struct R3ReadContext *context, +RAPIER_API +uint32_t RAPIER_CALL r3ReadCollider_ActiveEvents(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** * Return the collider mass. Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadCollider_Mass(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadCollider_Mass(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18069,8 +17763,8 @@ R3Real r3ReadCollider_Mass(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadCollider_Density(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadCollider_Density(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18078,8 +17772,8 @@ R3Real r3ReadCollider_Density(const struct R3ReadContext *context, * context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadCollider_Volume(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadCollider_Volume(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18087,8 +17781,8 @@ R3Real r3ReadCollider_Volume(const struct R3ReadContext *context, * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadCollider_ContactSkin(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadCollider_ContactSkin(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18096,8 +17790,8 @@ R3Real r3ReadCollider_ContactSkin(const struct R3ReadContext *context, * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadCollider_ContactForceEventThreshold(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadCollider_ContactForceEventThreshold(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18105,8 +17799,8 @@ R3Real r3ReadCollider_ContactForceEventThreshold(const struct R3ReadContext *con * the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadCollider_IsEnabled(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadCollider_IsEnabled(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18114,8 +17808,8 @@ R3Bool r3ReadCollider_IsEnabled(const struct R3ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Aabb r3ReadCollider_ComputeAabb(const struct R3ReadContext *context, +RAPIER_API +struct R3Aabb RAPIER_CALL r3ReadCollider_ComputeAabb(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18124,8 +17818,8 @@ struct R3Aabb r3ReadCollider_ComputeAabb(const struct R3ReadContext *context, * Returns an owned shape wrapper sharing the geometry. Release it with FreeSharedShape. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3SharedShape *r3ReadCollider_CloneShape(const struct R3ReadContext *context, +RAPIER_API +R3SharedShape *RAPIER_CALL r3ReadCollider_CloneShape(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18133,8 +17827,8 @@ R3SharedShape *r3ReadCollider_CloneShape(const struct R3ReadContext *context, * has already been freed. Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Status r3ReadRigidBody_ValidateHandle(const struct R3ReadContext *context, +RAPIER_API +R3Status RAPIER_CALL r3ReadRigidBody_ValidateHandle(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -18142,8 +17836,8 @@ R3Status r3ReadRigidBody_ValidateHandle(const struct R3ReadContext *context, * has already been freed. Uses only the callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Status r3ReadCollider_ValidateHandle(const struct R3ReadContext *context, +RAPIER_API +R3Status RAPIER_CALL r3ReadCollider_ValidateHandle(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18151,8 +17845,8 @@ R3Status r3ReadCollider_ValidateHandle(const struct R3ReadContext *context, * the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Pose r3ReadRigidBody_Position(const struct R3ReadContext *context, +RAPIER_API +struct R3Pose RAPIER_CALL r3ReadRigidBody_Position(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -18160,8 +17854,8 @@ struct R3Pose r3ReadRigidBody_Position(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Vector r3ReadRigidBody_Translation(const struct R3ReadContext *context, +RAPIER_API +struct R3Vector RAPIER_CALL r3ReadRigidBody_Translation(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -18169,8 +17863,8 @@ struct R3Vector r3ReadRigidBody_Translation(const struct R3ReadContext *context, * never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Vector r3ReadRigidBody_Linvel(const struct R3ReadContext *context, +RAPIER_API +struct R3Vector RAPIER_CALL r3ReadRigidBody_Linvel(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -18178,8 +17872,8 @@ struct R3Vector r3ReadRigidBody_Linvel(const struct R3ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3AngVector r3ReadRigidBody_Angvel(const struct R3ReadContext *context, +RAPIER_API +R3AngVector RAPIER_CALL r3ReadRigidBody_Angvel(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -18187,8 +17881,8 @@ R3AngVector r3ReadRigidBody_Angvel(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadRigidBody_IsSleeping(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadRigidBody_IsSleeping(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -18196,8 +17890,8 @@ R3Bool r3ReadRigidBody_IsSleeping(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadRigidBody_IsEnabled(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadRigidBody_IsEnabled(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -18205,8 +17899,8 @@ R3Bool r3ReadRigidBody_IsEnabled(const struct R3ReadContext *context, * context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3UserData r3ReadRigidBody_UserData(const struct R3ReadContext *context, +RAPIER_API +struct R3UserData RAPIER_CALL r3ReadRigidBody_UserData(const struct R3ReadContext *context, struct R3RigidBodyHandle handle); /** @@ -18214,8 +17908,8 @@ struct R3UserData r3ReadRigidBody_UserData(const struct R3ReadContext *context, * the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Pose r3ReadCollider_Position(const struct R3ReadContext *context, +RAPIER_API +struct R3Pose RAPIER_CALL r3ReadCollider_Position(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18223,8 +17917,8 @@ struct R3Pose r3ReadCollider_Position(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3Vector r3ReadCollider_Translation(const struct R3ReadContext *context, +RAPIER_API +struct R3Vector RAPIER_CALL r3ReadCollider_Translation(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18232,8 +17926,8 @@ struct R3Vector r3ReadCollider_Translation(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadCollider_Friction(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadCollider_Friction(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18241,8 +17935,8 @@ R3Real r3ReadCollider_Friction(const struct R3ReadContext *context, * retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Real r3ReadCollider_Restitution(const struct R3ReadContext *context, +RAPIER_API +R3Real RAPIER_CALL r3ReadCollider_Restitution(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18250,8 +17944,8 @@ R3Real r3ReadCollider_Restitution(const struct R3ReadContext *context, * callback-scoped read context; never retain the context. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -R3Bool r3ReadCollider_IsSensor(const struct R3ReadContext *context, +RAPIER_API +R3Bool RAPIER_CALL r3ReadCollider_IsSensor(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18259,8 +17953,8 @@ R3Bool r3ReadCollider_IsSensor(const struct R3ReadContext *context, * with OK status. * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -struct R3RigidBodyHandle r3ReadCollider_Parent(const struct R3ReadContext *context, +RAPIER_API +struct R3RigidBodyHandle RAPIER_CALL r3ReadCollider_Parent(const struct R3ReadContext *context, struct R3ColliderHandle handle); /** @@ -18269,8 +17963,8 @@ struct R3RigidBodyHandle r3ReadCollider_Parent(const struct R3ReadContext *conte * @see @ref output_buffers * @ingroup callbacks */ -RAPIER_API RAPIER_CALL -size_t r3ReadRigidBodyReadStates(const struct R3ReadContext *context, +RAPIER_API +size_t RAPIER_CALL r3ReadRigidBodyReadStates(const struct R3ReadContext *context, const struct R3RigidBodyHandle *handles, size_t handle_count, struct R3RigidBodyState *states, diff --git a/c/src/pipeline.rs b/c/src/pipeline.rs index 473b09cc4..29b3ce3a7 100644 --- a/c/src/pipeline.rs +++ b/c/src/pipeline.rs @@ -2274,7 +2274,7 @@ pub unsafe extern "C" fn rpr_step_time_ms(world: *const RprWorld) -> f64 { /// Read ONLY trusted legacy rigid-world snapshots from debug_deserialize3.rs, /// produced by the identical Rapier build. This is not a stable interchange format. - +/// /// Import trusted legacy Rust testbed rigid-state bytes into a new owned world. Release with /// rpr_free_world; see @ref snapshots. /// @ingroup worlds diff --git a/c/tests/initializers.c b/c/tests/initializers.c index c11e2afdf..28119c915 100644 --- a/c/tests/initializers.c +++ b/c/tests/initializers.c @@ -44,7 +44,9 @@ int main(void) { assert(filter.exclude_collider.index == UINT32_MAX && filter.exclude_rigid_body.index == UINT32_MAX); RAPIER_TYPE(QueryOptions) query = RAPIER_FN(DefaultQueryOptions)(); - query.filter = *(&filter); + assert(!query.predicate && !query.userData); + assert(query.filter.exclude_collider.index == UINT32_MAX && + query.filter.exclude_rigid_body.index == UINT32_MAX); RAPIER_TYPE(ShapeCastOptions) options = RAPIER_FN(DefaultShapeCastOptions)(); assert(options.max_time_of_impact > 0); assert(RAPIER_CONST(INVALID_COLLIDER_HANDLE).index == UINT32_MAX); diff --git a/c/tests/pod.c b/c/tests/pod.c index a955d15ef..63b2b6413 100644 --- a/c/tests/pod.c +++ b/c/tests/pod.c @@ -53,7 +53,7 @@ static void test_world_and_deformable_bindings(void) { RAPIER_TYPE(SoftBodyDesc) soft; RAPIER_TYPE(SoftBodyHandle) softHandle; - RAPIER_TYPE(Vector) vertices[3] = {{0}, {0}, {0}}; + RAPIER_TYPE(Vector) vertices[3] = {0}; vertices[1].x = 1; vertices[2].y = 1; soft = RAPIER_FN(DefaultSoftBodyDesc)(); @@ -205,7 +205,7 @@ int main(void) { /* A copied description borrows arrays until insertion. The world then owns copies. */ RAPIER_TYPE(SoftBodyDesc) soft = RAPIER_FN(DefaultSoftBodyDesc)(); - RAPIER_TYPE(Vector) particles[2] = {{0}, {0}}; + RAPIER_TYPE(Vector) particles[2] = {0}; particles[0].y = particles[1].y = 10; particles[1].x = 1; RAPIER_TYPE(Edge) edges[] = {{0, 1}}; diff --git a/c/tools/generate-header.py b/c/tools/generate-header.py index 48c20b1fa..61e467a2e 100644 --- a/c/tools/generate-header.py +++ b/c/tools/generate-header.py @@ -44,6 +44,11 @@ "--crate", "rapier-c-header", "--output", str(raw), ], cwd=project, check=True) text = raw.read_text() + # MSVC requires __cdecl after the return type, notably for struct returns. + # cbindgen's function prefix goes before that type, so insert the calling + # convention at the function declarator instead. + text = re.sub(r"(RAPIER_API\s+[\w\s*]+?)\b(rpr_[a-z0-9_]+)\(", + r"\1RAPIER_CALL \2(", text) # Transparent native wrappers remain opaque to C. for source in sources: for name, native in re.findall(r"pub struct (Rpr\w+)\(pub\(crate\) (\w+)\)", source.read_text()): diff --git a/src_testbed/physics/mod.rs b/src_testbed/physics/mod.rs index 3db7ee6f5..63747fd7b 100644 --- a/src_testbed/physics/mod.rs +++ b/src_testbed/physics/mod.rs @@ -38,6 +38,7 @@ pub fn restore_world(world: &mut PhysicsWorld, snapshot: &PhysicsSnapshot) -> us gravity, integration_parameters, physics_pipeline: _, + collision_pipeline: _, islands, broad_phase, narrow_phase, From ba21fe692cb7ea0265a1da29d0ae8d939415d894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 24 Sep 2026 17:18:30 +0200 Subject: [PATCH 8/8] fix: resolve Windows C testbed build errors --- c/testbed/examples2d/debug_many_colliders2.c | 162 +++++++++++++------ c/testbed/graphics.c | 4 +- c/testbed/gui.c | 8 +- c/testbed/testbed.h | 8 + c/testbed/tests/grab.c | 14 +- c/testbed/tests/soft_render.c | 8 +- c/tests/handles.c | 2 +- c/tests/initializers.c | 2 +- c/tests/integration.c | 5 +- 9 files changed, 140 insertions(+), 73 deletions(-) diff --git a/c/testbed/examples2d/debug_many_colliders2.c b/c/testbed/examples2d/debug_many_colliders2.c index 7b50f23bb..0d110cb3e 100644 --- a/c/testbed/examples2d/debug_many_colliders2.c +++ b/c/testbed/examples2d/debug_many_colliders2.c @@ -3,78 +3,136 @@ #include "rapier_helpers.h" #include "rapier_math.h" -static const R2Vector part0[] = {V(525.0 * 0.01, 104.0 * 0.01, 0), V(540.0 * 0.01, 104.0 * 0.01, 0), - V(419.0 * 0.01, 119.0 * 0.01, 0)}; - -static const R2Vector part1[] = {V(419.0 * 0.01, 119.0 * 0.01, 0), V(449.0 * 0.01, 74.0 * 0.01, 0), - V(510.0 * 0.01, 59.0 * 0.01, 0), V(525.0 * 0.01, 104.0 * 0.01, 0)}; - -static const R2Vector part2[] = {V(299.0 * 0.01, 134.0 * 0.01, 0), V(419.0 * 0.01, 119.0 * 0.01, 0), - V(540.0 * 0.01, 104.0 * 0.01, 0), - V(540.0 * 0.01, 134.0 * 0.01, 0)}; - -static const R2Vector part3[] = {V(315.0 * 0.01, 450.0 * 0.01, 0), V(179.0 * 0.01, 284.0 * 0.01, 0), - V(179.0 * 0.01, 254.0 * 0.01, 0), - V(224.0 * 0.01, 224.0 * 0.01, 0)}; +static const R2Vector part0[] = { + {(R2Real)(525.0 * 0.01), (R2Real)(104.0 * 0.01)}, + {(R2Real)(540.0 * 0.01), (R2Real)(104.0 * 0.01)}, + {(R2Real)(419.0 * 0.01), (R2Real)(119.0 * 0.01)}, +}; + +static const R2Vector part1[] = { + {(R2Real)(419.0 * 0.01), (R2Real)(119.0 * 0.01)}, + {(R2Real)(449.0 * 0.01), (R2Real)(74.0 * 0.01)}, + {(R2Real)(510.0 * 0.01), (R2Real)(59.0 * 0.01)}, + {(R2Real)(525.0 * 0.01), (R2Real)(104.0 * 0.01)}, +}; + +static const R2Vector part2[] = { + {(R2Real)(299.0 * 0.01), (R2Real)(134.0 * 0.01)}, + {(R2Real)(419.0 * 0.01), (R2Real)(119.0 * 0.01)}, + {(R2Real)(540.0 * 0.01), (R2Real)(104.0 * 0.01)}, + {(R2Real)(540.0 * 0.01), (R2Real)(134.0 * 0.01)}, +}; + +static const R2Vector part3[] = { + {(R2Real)(315.0 * 0.01), (R2Real)(450.0 * 0.01)}, + {(R2Real)(179.0 * 0.01), (R2Real)(284.0 * 0.01)}, + {(R2Real)(179.0 * 0.01), (R2Real)(254.0 * 0.01)}, + {(R2Real)(224.0 * 0.01), (R2Real)(224.0 * 0.01)}, +}; static const R2Vector part4[] = { - V(224.0 * 0.01, 224.0 * 0.01, 0), V(224.0 * 0.01, 223.0 * 0.01, 0), - V(299.0 * 0.01, 134.0 * 0.01, 0), V(540.0 * 0.01, 134.0 * 0.01, 0), - V(555.0 * 0.01, 134.0 * 0.01, 0), V(555.0 * 0.01, 209.0 * 0.01, 0), - V(359.0 * 0.01, 465.0 * 0.01, 0), V(315.0 * 0.01, 450.0 * 0.01, 0)}; + {(R2Real)(224.0 * 0.01), (R2Real)(224.0 * 0.01)}, + {(R2Real)(224.0 * 0.01), (R2Real)(223.0 * 0.01)}, + {(R2Real)(299.0 * 0.01), (R2Real)(134.0 * 0.01)}, + {(R2Real)(540.0 * 0.01), (R2Real)(134.0 * 0.01)}, + {(R2Real)(555.0 * 0.01), (R2Real)(134.0 * 0.01)}, + {(R2Real)(555.0 * 0.01), (R2Real)(209.0 * 0.01)}, + {(R2Real)(359.0 * 0.01), (R2Real)(465.0 * 0.01)}, + {(R2Real)(315.0 * 0.01), (R2Real)(450.0 * 0.01)}, +}; static const R2Vector part5[] = { - V(119.0 * 0.01, 359.0 * 0.01, 0), V(134.0 * 0.01, 314.0 * 0.01, 0), - V(179.0 * 0.01, 284.0 * 0.01, 0), V(315.0 * 0.01, 450.0 * 0.01, 0), - V(315.0 * 0.01, 465.0 * 0.01, 0), V(300.0 * 0.01, 465.0 * 0.01, 0)}; - -static const R2Vector part6[] = {V(164.0 * 0.01, 510.0 * 0.01, 0), V(134.0 * 0.01, 495.0 * 0.01, 0), - V(240.0 * 0.01, 510.0 * 0.01, 0)}; - -static const R2Vector part7[] = {V(240.0 * 0.01, 510.0 * 0.01, 0), V(240.0 * 0.01, 525.0 * 0.01, 0), - V(164.0 * 0.01, 525.0 * 0.01, 0), - V(164.0 * 0.01, 510.0 * 0.01, 0)}; + {(R2Real)(119.0 * 0.01), (R2Real)(359.0 * 0.01)}, + {(R2Real)(134.0 * 0.01), (R2Real)(314.0 * 0.01)}, + {(R2Real)(179.0 * 0.01), (R2Real)(284.0 * 0.01)}, + {(R2Real)(315.0 * 0.01), (R2Real)(450.0 * 0.01)}, + {(R2Real)(315.0 * 0.01), (R2Real)(465.0 * 0.01)}, + {(R2Real)(300.0 * 0.01), (R2Real)(465.0 * 0.01)}, +}; + +static const R2Vector part6[] = { + {(R2Real)(164.0 * 0.01), (R2Real)(510.0 * 0.01)}, + {(R2Real)(134.0 * 0.01), (R2Real)(495.0 * 0.01)}, + {(R2Real)(240.0 * 0.01), (R2Real)(510.0 * 0.01)}, +}; + +static const R2Vector part7[] = { + {(R2Real)(240.0 * 0.01), (R2Real)(510.0 * 0.01)}, + {(R2Real)(240.0 * 0.01), (R2Real)(525.0 * 0.01)}, + {(R2Real)(164.0 * 0.01), (R2Real)(525.0 * 0.01)}, + {(R2Real)(164.0 * 0.01), (R2Real)(510.0 * 0.01)}, +}; static const R2Vector part8[] = { - V(134.0 * 0.01, 495.0 * 0.01, 0), V(104.0 * 0.01, 359.0 * 0.01, 0), - V(119.0 * 0.01, 359.0 * 0.01, 0), V(300.0 * 0.01, 465.0 * 0.01, 0), - V(270.0 * 0.01, 510.0 * 0.01, 0), V(240.0 * 0.01, 510.0 * 0.01, 0)}; - -static const R2Vector part9[] = {V(615.0 * 0.01, 269.0 * 0.01, 0), V(660.0 * 0.01, 284.0 * 0.01, 0), - V(660.0 * 0.01, 359.0 * 0.01, 0)}; + {(R2Real)(134.0 * 0.01), (R2Real)(495.0 * 0.01)}, + {(R2Real)(104.0 * 0.01), (R2Real)(359.0 * 0.01)}, + {(R2Real)(119.0 * 0.01), (R2Real)(359.0 * 0.01)}, + {(R2Real)(300.0 * 0.01), (R2Real)(465.0 * 0.01)}, + {(R2Real)(270.0 * 0.01), (R2Real)(510.0 * 0.01)}, + {(R2Real)(240.0 * 0.01), (R2Real)(510.0 * 0.01)}, +}; + +static const R2Vector part9[] = { + {(R2Real)(615.0 * 0.01), (R2Real)(269.0 * 0.01)}, + {(R2Real)(660.0 * 0.01), (R2Real)(284.0 * 0.01)}, + {(R2Real)(660.0 * 0.01), (R2Real)(359.0 * 0.01)}, +}; static const R2Vector part10[] = { - V(673.6813186813187 * 0.01, 390.010989010989 * 0.01, 0), V(660.0 * 0.01, 359.0 * 0.01, 0), - V(675.0 * 0.01, 359.0 * 0.01, 0), V(675.0 * 0.01, 389.0 * 0.01, 0)}; + {(R2Real)(673.6813186813187 * 0.01), (R2Real)(390.010989010989 * 0.01)}, + {(R2Real)(660.0 * 0.01), (R2Real)(359.0 * 0.01)}, + {(R2Real)(675.0 * 0.01), (R2Real)(359.0 * 0.01)}, + {(R2Real)(675.0 * 0.01), (R2Real)(389.0 * 0.01)}, +}; static const R2Vector part11[] = { - V(675.0 * 0.01, 389.0 * 0.01, 0), V(735.0 * 0.01, 434.0 * 0.01, 0), - V(735.0 * 0.01, 495.0 * 0.01, 0), V(720.0 * 0.01, 495.0 * 0.01, 0), - V(673.6813186813187 * 0.01, 390.010989010989 * 0.01, 0)}; + {(R2Real)(675.0 * 0.01), (R2Real)(389.0 * 0.01)}, + {(R2Real)(735.0 * 0.01), (R2Real)(434.0 * 0.01)}, + {(R2Real)(735.0 * 0.01), (R2Real)(495.0 * 0.01)}, + {(R2Real)(720.0 * 0.01), (R2Real)(495.0 * 0.01)}, + {(R2Real)(673.6813186813187 * 0.01), (R2Real)(390.010989010989 * 0.01)}, +}; static const R2Vector part12[] = { - V(645.0 * 0.01, 540.0 * 0.01, 0), V(645.0 * 0.01, 555.0 * 0.01, 0), - V(494.0 * 0.01, 555.0 * 0.01, 0), V(494.0 * 0.01, 540.0 * 0.01, 0)}; + {(R2Real)(645.0 * 0.01), (R2Real)(540.0 * 0.01)}, + {(R2Real)(645.0 * 0.01), (R2Real)(555.0 * 0.01)}, + {(R2Real)(494.0 * 0.01), (R2Real)(555.0 * 0.01)}, + {(R2Real)(494.0 * 0.01), (R2Real)(540.0 * 0.01)}, +}; static const R2Vector part13[] = { - V(705.0 * 0.01, 525.0 * 0.01, 0), V(645.0 * 0.01, 540.0 * 0.01, 0), - V(494.0 * 0.01, 540.0 * 0.01, 0), V(464.0 * 0.01, 540.0 * 0.01, 0), - V(464.0 * 0.01, 525.0 * 0.01, 0)}; + {(R2Real)(705.0 * 0.01), (R2Real)(525.0 * 0.01)}, + {(R2Real)(645.0 * 0.01), (R2Real)(540.0 * 0.01)}, + {(R2Real)(494.0 * 0.01), (R2Real)(540.0 * 0.01)}, + {(R2Real)(464.0 * 0.01), (R2Real)(540.0 * 0.01)}, + {(R2Real)(464.0 * 0.01), (R2Real)(525.0 * 0.01)}, +}; static const R2Vector part14[] = { - V(660.0 * 0.01, 359.0 * 0.01, 0), V(720.0 * 0.01, 495.0 * 0.01, 0), - V(705.0 * 0.01, 525.0 * 0.01, 0), V(464.0 * 0.01, 525.0 * 0.01, 0), - V(434.0 * 0.01, 525.0 * 0.01, 0), V(434.0 * 0.01, 510.0 * 0.01, 0)}; + {(R2Real)(660.0 * 0.01), (R2Real)(359.0 * 0.01)}, + {(R2Real)(720.0 * 0.01), (R2Real)(495.0 * 0.01)}, + {(R2Real)(705.0 * 0.01), (R2Real)(525.0 * 0.01)}, + {(R2Real)(464.0 * 0.01), (R2Real)(525.0 * 0.01)}, + {(R2Real)(434.0 * 0.01), (R2Real)(525.0 * 0.01)}, + {(R2Real)(434.0 * 0.01), (R2Real)(510.0 * 0.01)}, +}; static const R2Vector part15[] = { - V(660.0 * 0.01, 359.0 * 0.01, 0), V(434.0 * 0.01, 510.0 * 0.01, 0), - V(404.0 * 0.01, 510.0 * 0.01, 0), V(404.0 * 0.01, 495.0 * 0.01, 0)}; + {(R2Real)(660.0 * 0.01), (R2Real)(359.0 * 0.01)}, + {(R2Real)(434.0 * 0.01), (R2Real)(510.0 * 0.01)}, + {(R2Real)(404.0 * 0.01), (R2Real)(510.0 * 0.01)}, + {(R2Real)(404.0 * 0.01), (R2Real)(495.0 * 0.01)}, +}; static const R2Vector part16[] = { - V(404.0 * 0.01, 495.0 * 0.01, 0), V(359.0 * 0.01, 495.0 * 0.01, 0), - V(359.0 * 0.01, 465.0 * 0.01, 0), V(555.0 * 0.01, 209.0 * 0.01, 0), - V(570.0 * 0.01, 209.0 * 0.01, 0), V(615.0 * 0.01, 269.0 * 0.01, 0), - V(660.0 * 0.01, 359.0 * 0.01, 0)}; + {(R2Real)(404.0 * 0.01), (R2Real)(495.0 * 0.01)}, + {(R2Real)(359.0 * 0.01), (R2Real)(495.0 * 0.01)}, + {(R2Real)(359.0 * 0.01), (R2Real)(465.0 * 0.01)}, + {(R2Real)(555.0 * 0.01), (R2Real)(209.0 * 0.01)}, + {(R2Real)(570.0 * 0.01), (R2Real)(209.0 * 0.01)}, + {(R2Real)(615.0 * 0.01), (R2Real)(269.0 * 0.01)}, + {(R2Real)(660.0 * 0.01), (R2Real)(359.0 * 0.01)}, +}; static R2ColliderDesc polygon(const R2Vector *position, size_t n) { R2ColliderDesc collider = r2DefaultColliderDesc(); diff --git a/c/testbed/graphics.c b/c/testbed/graphics.c index 2bcfd335b..cc44238a4 100644 --- a/c/testbed/graphics.c +++ b/c/testbed/graphics.c @@ -809,8 +809,8 @@ void tbGraphicsFrameAll(Testbed *t, Camera3D *camera) { Vector3 dir = Vector3Normalize(Vector3Subtract(camera->position, camera->target)); #if defined(RAPIER_DIM2) dir = (Vector3){0, 0, 1}; - camera->fovy = - fmaxf((hi.y - lo.y) * 1.2f, (hi.x - lo.x) * 1.2f * GetScreenHeight() / GetScreenWidth()); + float inverseAspect = (float)GetScreenHeight() / (float)GetScreenWidth(); + camera->fovy = fmaxf((hi.y - lo.y) * 1.2f, (hi.x - lo.x) * 1.2f * inverseAspect); #endif camera->position = Vector3Add(camera->target, Vector3Scale(dir, size * 1.2f)); } diff --git a/c/testbed/gui.c b/c/testbed/gui.c index 565efe85b..bd6a87183 100644 --- a/c/testbed/gui.c +++ b/c/testbed/gui.c @@ -138,7 +138,7 @@ static Camera3D cameraFor(Testbed *t) { #if defined(RAPIER_DIM2) c.position = (Vector3){t->target[0], t->target[1], 100}; c.target.z = 0; - c.fovy = t->viewWidth * (float)GetScreenHeight() / GetScreenWidth(); + c.fovy = t->viewWidth * (float)GetScreenHeight() / (float)GetScreenWidth(); c.projection = CAMERA_ORTHOGRAPHIC; #endif return c; @@ -549,7 +549,7 @@ static void sidebar(Testbed *t, UiState *ui, float width) { static void sceneOverlay(Testbed *t, float sidebarWidth) { igSetNextWindowPos(UI2(sidebarWidth + 18, 12), ImGuiCond_Always, UI2(0, 0)); - igSetNextWindowSize(UI2(fmaxf(100, GetScreenWidth() - sidebarWidth - 36), 0), ImGuiCond_Always); + igSetNextWindowSize(UI2(fmaxf(100, (float)GetScreenWidth() - sidebarWidth - 36), 0), ImGuiCond_Always); igBegin("Scene", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings | @@ -666,7 +666,7 @@ static int renderFrame(Testbed *t, void *context) { camera.fovy = fmaxf(0.05f, camera.fovy * expf(-wheel * 0.12f)); if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { Vector2 d = GetMouseDelta(); - float scale = camera.fovy / GetScreenHeight(); + float scale = camera.fovy / (float)GetScreenHeight(); camera.position.x -= d.x * scale; camera.target.x -= d.x * scale; camera.position.y += d.y * scale; @@ -738,7 +738,7 @@ static int renderFrame(Testbed *t, void *context) { uiStatus(t, tbGrabRelease(t, &viewer->grab)); } else if (!captureMouse && !t->error[0]) { if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - RAPIER_TYPE(Real) radius = (RAPIER_TYPE(Real))(camera.fovy * 8 / GetScreenHeight()); + RAPIER_TYPE(Real) radius = (RAPIER_TYPE(Real))(camera.fovy * 8 / (float)GetScreenHeight()); uiStatus(t, tbGrabBegin(t, &viewer->grab, radius)); } Vector3 direction = Vector3Normalize(Vector3Subtract(camera.target, camera.position)); diff --git a/c/testbed/testbed.h b/c/testbed/testbed.h index 1f91a484a..43f6d145e 100644 --- a/c/testbed/testbed.h +++ b/c/testbed/testbed.h @@ -49,6 +49,11 @@ typedef struct TbRenderMesh { float rgba[4], metallic, roughness, reflectance, emissive[3]; } TbRenderMesh; +#ifdef _MSC_VER +#pragma warning(push) +/* Windows jmp_buf requires 16-byte alignment; padding here is intentional. */ +#pragma warning(disable : 4324) +#endif struct Testbed { TbTint *bodyColors, *colliderColors; size_t bodyColorCount, colliderColorCount; @@ -96,6 +101,9 @@ struct Testbed { size_t settingCount; const char *assetRoot; }; +#ifdef _MSC_VER +#pragma warning(pop) +#endif double tbClock(void); size_t tbChoice(Testbed *, const char *, size_t, const char *const *, size_t, int live, int reset); diff --git a/c/testbed/tests/grab.c b/c/testbed/tests/grab.c index 2ee3b0f5b..e752800cf 100644 --- a/c/testbed/tests/grab.c +++ b/c/testbed/tests/grab.c @@ -57,13 +57,13 @@ static void rigidDrag(void) { addBall(&t, V(3, 0, 0), RAPIER_CONST(FIXED), false); CHECK(RAPIER_FN(Step)(t.world, NULL, NULL)); pointCursor(&t, V(-3, 0, 0)); - CHECK(tbGrabBegin(&t, &grab, .1)); + CHECK(tbGrabBegin(&t, &grab, (RAPIER_TYPE(Real))0.1)); assert(!grab.active); pointCursor(&t, V(3, 0, 0)); - CHECK(tbGrabBegin(&t, &grab, .1)); + CHECK(tbGrabBegin(&t, &grab, (RAPIER_TYPE(Real))0.1)); assert(!grab.active); pointCursor(&t, V(0, 0, 0)); - CHECK(tbGrabBegin(&t, &grab, .1)); + CHECK(tbGrabBegin(&t, &grab, (RAPIER_TYPE(Real))0.1)); assert(grab.active && !grab.soft && grab.body.index == picked.index); checkCounts(&t, 4, 1); pointCursor(&t, V(2, 1, 0)); @@ -81,7 +81,7 @@ static void rigidDrag(void) { assert(!grab.active); checkCounts(&t, 3, 0); pointCursor(&t, position); - CHECK(tbGrabBegin(&t, &grab, .1)); + CHECK(tbGrabBegin(&t, &grab, (RAPIER_TYPE(Real))0.1)); assert(grab.active); RAPIER_TYPE(Bool) removed = RAPIER_FN(RemoveRigidBody)(picked, 1); CHECK(RAPIER_FN(LastStatus)()); @@ -106,7 +106,7 @@ static void articulatedDrag(void) { CHECK(RAPIER_FN(LastStatus)()); CHECK(RAPIER_FN(Step)(t.world, NULL, NULL)); pointCursor(&t, V(0, 0, 0)); - CHECK(tbGrabBegin(&t, &grab, .1)); + CHECK(tbGrabBegin(&t, &grab, (RAPIER_TYPE(Real))0.1)); assert(grab.active && grab.body.index == link.index); pointCursor(&t, V(2, 0, 0)); for (int i = 0; i < 120; ++i) { @@ -136,7 +136,7 @@ static void softDrag(void) { builder = RAPIER_FN(ClothSoftBodyDesc)(V(-.5, -.5, 0), V(.5, 0, 0), V(0, .5, 0), 3, 3); #endif - RAPIER_TYPE(ColliderDesc) surface = RAPIER_FN(BallColliderDesc)(.05); + RAPIER_TYPE(ColliderDesc) surface = RAPIER_FN(BallColliderDesc)((RAPIER_TYPE(Real))0.05); builder.collider = surface; RAPIER_TYPE(SoftBodyHandle) handle = RAPIER_FN(InsertSoftBody)(t.world, &builder); CHECK(RAPIER_FN(LastStatus)()); @@ -149,7 +149,7 @@ static void softDrag(void) { bodyCount = RAPIER_FN(RigidBodyCount)(t.world); CHECK(RAPIER_FN(LastStatus)()); pointCursor(&t, V(-.4, -.4, 0)); - CHECK(tbGrabBegin(&t, &grab, .2)); + CHECK(tbGrabBegin(&t, &grab, (RAPIER_TYPE(Real))0.2)); assert(grab.active && grab.soft); CHECK(RAPIER_FN(SoftBody_ValidateHandle)(handle)); after = RAPIER_FN(SoftBody_Clusters)(handle, NULL, 0); diff --git a/c/testbed/tests/soft_render.c b/c/testbed/tests/soft_render.c index bff2d93e9..764aa2dc1 100644 --- a/c/testbed/tests/soft_render.c +++ b/c/testbed/tests/soft_render.c @@ -4,7 +4,7 @@ #include "rlgl.h" #include -static size_t triangles, lines, translucent; +static size_t triangles, lineCount, translucent; static bool depthWrite = true, pendingTranslucent; static int culling = 1, flushed; @@ -64,7 +64,7 @@ static void recordLine(Vector3 a, Vector3 b, Color color) { (void)color; checkPoint(a); checkPoint(b); - ++lines; + ++lineCount; } static void *boundedRealloc(void *pointer, size_t bytes) { @@ -119,7 +119,7 @@ static int renderFrame(Testbed *t, void *context) { } free(handles); } - triangles = lines = translucent = 0; + triangles = lineCount = translucent = 0; test->graphics.transparentCount = 0; drawSoft(&test->graphics, t, true, (Camera3D){.position = {0, 0, 100}, .fovy = 20}); assert(translucent == 0); /* No sensor geometry in the opaque pass. */ @@ -130,7 +130,7 @@ static int renderFrame(Testbed *t, void *context) { for (size_t i = 1; i < queued; ++i) { assert(test->graphics.transparent[i - 1].depth >= test->graphics.transparent[i].depth); } - assert(triangles + lines > 0); + assert(triangles + lineCount > 0); assert(test->graphics.entryCapacity < 1024); /* Turning surfaces off must also avoid indexing render-only colliders. */ drawSoft(&test->graphics, t, false, (Camera3D){0}); diff --git a/c/tests/handles.c b/c/tests/handles.c index 73cc0ae00..8dfcfad95 100644 --- a/c/tests/handles.c +++ b/c/tests/handles.c @@ -49,7 +49,7 @@ int main(void) { assert(throughSet.y == position.y); /* Retained handles survive storage growth and stepping; no element pointer is kept. */ for (int i = 0; i < 512; ++i) { - desc.position.translation.x = 10 + i; + desc.position.translation.x = (RAPIER_TYPE(Real))(10 + i); RAPIER_FN(InsertRigidBody)(world, &desc); OK(RAPIER_FN(LastStatus)()); } diff --git a/c/tests/initializers.c b/c/tests/initializers.c index 28119c915..e42dda449 100644 --- a/c/tests/initializers.c +++ b/c/tests/initializers.c @@ -13,7 +13,7 @@ int main(void) { assert(handle.index == UINT32_MAX && handle.generation == UINT32_MAX); for (unsigned i = 0; i < 4; ++i) { assert(bodies[i].bodyType == i); - bodies[i].position.translation.x = 2 * i; + bodies[i].position.translation.x = (RAPIER_TYPE(Real))(2 * i); bodies[i].position.translation.y = 5; handle = RAPIER_FN(InsertRigidBody)(world, &bodies[i]); RAPIER_FN(InsertCollider)(handle, &collider); diff --git a/c/tests/integration.c b/c/tests/integration.c index 1c0553549..daa236b0f 100644 --- a/c/tests/integration.c +++ b/c/tests/integration.c @@ -16,9 +16,10 @@ #define EXPECT(expr, code) \ do { \ RAPIER_TYPE(Status) status_ = (expr); \ - if (status_ != (code)) { \ + RAPIER_TYPE(Status) expected_ = (code); \ + if (status_ != expected_) { \ fprintf(stderr, "%s:%d: expected %u, got %u: %s\n", __FILE__, __LINE__, \ - (unsigned)(code), status_, RAPIER_FN(LastError)()); \ + expected_, status_, RAPIER_FN(LastError)()); \ abort(); \ } \ } while (0)