mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-10 18:09:32 +00:00
Add box3d src to project; Add simple build script for Windows
This commit is contained in:
BIN
vendor/box3d/lib/box3d.lib
vendored
BIN
vendor/box3d/lib/box3d.lib
vendored
Binary file not shown.
8
vendor/box3d/src/build.bat
vendored
Normal file
8
vendor/box3d/src/build.bat
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
|
||||
if not exist "..\lib" mkdir ..\lib
|
||||
|
||||
cl -nologo -MT -TC -O2 -c -std:c17 -I "include" src\*.c
|
||||
lib -nologo *.obj -out:..\lib\box3d.lib
|
||||
|
||||
del *.obj
|
||||
256
vendor/box3d/src/src/CMakeLists.txt
vendored
Normal file
256
vendor/box3d/src/src/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,256 @@
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(BOX3D_SOURCE_FILES
|
||||
aabb.c
|
||||
aabb.h
|
||||
algorithm.h
|
||||
arena_allocator.c
|
||||
arena_allocator.h
|
||||
bitset.c
|
||||
bitset.h
|
||||
block_allocator.c
|
||||
block_allocator.h
|
||||
body.c
|
||||
body.h
|
||||
broad_phase.c
|
||||
broad_phase.h
|
||||
capsule.c
|
||||
compound.c
|
||||
compound.h
|
||||
constraint_graph.c
|
||||
constraint_graph.h
|
||||
contact.c
|
||||
contact.h
|
||||
contact_solver.c
|
||||
contact_solver.h
|
||||
container.h
|
||||
convex_manifold.c
|
||||
core.c
|
||||
core.h
|
||||
ctz.h
|
||||
distance.c
|
||||
distance_joint.c
|
||||
dynamic_tree.c
|
||||
height_field.c
|
||||
hull.c
|
||||
id_pool.c
|
||||
id_pool.h
|
||||
island.c
|
||||
island.h
|
||||
joint.c
|
||||
joint.h
|
||||
manifold.c
|
||||
manifold.h
|
||||
math_functions.c
|
||||
math_internal.h
|
||||
mesh.c
|
||||
mesh_contact.c
|
||||
motor_joint.c
|
||||
mover.c
|
||||
name_cache.c
|
||||
name_cache.h
|
||||
parallel_for.c
|
||||
parallel_for.h
|
||||
parallel_joint.c
|
||||
physics_world.c
|
||||
physics_world.h
|
||||
platform.h
|
||||
prismatic_joint.c
|
||||
qsort.h
|
||||
recording.c
|
||||
recording.h
|
||||
recording_ops.inl
|
||||
recording_replay.c
|
||||
recording_replay.h
|
||||
world_snapshot.c
|
||||
world_snapshot.h
|
||||
revolute_joint.c
|
||||
scheduler.c
|
||||
scheduler.h
|
||||
sensor.c
|
||||
sensor.h
|
||||
shape.c
|
||||
shape.h
|
||||
simd.c
|
||||
simd.h
|
||||
solver.c
|
||||
solver.h
|
||||
solver_set.c
|
||||
solver_set.h
|
||||
sphere.c
|
||||
spherical_joint.c
|
||||
table.c
|
||||
table.h
|
||||
timer.c
|
||||
triangle_manifold.c
|
||||
types.c
|
||||
verstable.h
|
||||
weld_joint.c
|
||||
wheel_joint.c
|
||||
|
||||
box3d.natvis
|
||||
)
|
||||
|
||||
set(BOX3D_INCLUDE_FILES
|
||||
../include/box3d/base.h
|
||||
../include/box3d/box3d.h
|
||||
../include/box3d/collision.h
|
||||
../include/box3d/config.h
|
||||
../include/box3d/constants.h
|
||||
../include/box3d/id.h
|
||||
../include/box3d/math_functions.h
|
||||
../include/box3d/types.h
|
||||
)
|
||||
|
||||
# Hide internal functions
|
||||
# https://gcc.gnu.org/wiki/Visibility
|
||||
set(CMAKE_C_VISIBILITY_PRESET hidden)
|
||||
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
|
||||
|
||||
add_library(box3d ${BOX3D_SOURCE_FILES} ${BOX3D_INCLUDE_FILES})
|
||||
|
||||
# target_link_libraries(box3d PRIVATE eastl)
|
||||
|
||||
target_include_directories(box3d
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
set(CMAKE_DEBUG_POSTFIX "d")
|
||||
|
||||
# Box3D uses C17 for _Static_assert and anonymous unions
|
||||
set_target_properties(box3d PROPERTIES
|
||||
C_STANDARD 17
|
||||
C_STANDARD_REQUIRED YES
|
||||
C_EXTENSIONS YES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}
|
||||
)
|
||||
|
||||
if (BOX3D_COMPILE_WARNING_AS_ERROR)
|
||||
set_target_properties(box3d PROPERTIES COMPILE_WARNING_AS_ERROR ON)
|
||||
endif()
|
||||
|
||||
if (BOX3D_PROFILE)
|
||||
target_compile_definitions(box3d PRIVATE BOX3D_PROFILE)
|
||||
set(TRACY_DELAYED_INIT ON CACHE BOOL "Enable delayed init for Tracy" FORCE)
|
||||
set(TRACY_MANUAL_LIFETIME ON CACHE BOOL "Enable manual lifetime for Tracy" FORCE)
|
||||
|
||||
FetchContent_Declare(
|
||||
tracy
|
||||
GIT_REPOSITORY https://github.com/wolfpld/tracy.git
|
||||
GIT_TAG v0.13.1
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(tracy)
|
||||
|
||||
target_link_libraries(box3d PUBLIC TracyClient)
|
||||
endif()
|
||||
|
||||
if(BOX3D_VALIDATE)
|
||||
message(STATUS "Box3D validation ON")
|
||||
target_compile_definitions(box3d PRIVATE BOX3D_VALIDATE)
|
||||
endif()
|
||||
|
||||
if (BOX3D_DISABLE_SIMD)
|
||||
message(STATUS "Box3D SIMD disabled")
|
||||
target_compile_definitions(box3d PRIVATE BOX3D_DISABLE_SIMD)
|
||||
endif()
|
||||
|
||||
if (BOX3D_DOUBLE_PRECISION)
|
||||
message(STATUS "Box3D double precision ON")
|
||||
# PUBLIC: consumers must see the same precision mode in the headers or they cannot match the ABI
|
||||
target_compile_definitions(box3d PUBLIC BOX3D_DOUBLE_PRECISION)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
message(STATUS "Box3D on MSVC")
|
||||
if (BUILD_SHARED_LIBS)
|
||||
# this is needed by DLL users to import Box3D symbols
|
||||
target_compile_definitions(box3d INTERFACE BOX3D_DLL)
|
||||
endif()
|
||||
|
||||
# Visual Studio won't load the natvis unless it is in the project
|
||||
target_sources(box3d PRIVATE box3d.natvis)
|
||||
|
||||
# Enable asserts in release with debug info
|
||||
target_compile_definitions(box3d PUBLIC "$<$<CONFIG:RELWITHDEBINFO>:B3_ENABLE_ASSERT>")
|
||||
|
||||
# Warnings Wall is problematic in mixed C/C++, so using W4
|
||||
target_compile_options(box3d PRIVATE /W4)
|
||||
# target_compile_options(box3d PRIVATE /wd4820 /wd5045 /wd4061 /wd4711 /wd4514 /wd4365 /wd5219 /wd5039)
|
||||
target_compile_options(box3d PRIVATE /wd4820 /wd5045 /wd4061 /wd4711)
|
||||
# 4710 - warn about inline functions that are not inlined
|
||||
target_compile_options(box3d PRIVATE /wd4710 )
|
||||
|
||||
if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
|
||||
target_compile_options(box3d PRIVATE -Wmissing-prototypes)
|
||||
endif()
|
||||
|
||||
elseif (MINGW)
|
||||
message(STATUS "Box3D on MinGW")
|
||||
elseif (APPLE)
|
||||
message(STATUS "Box3D on Apple")
|
||||
target_compile_options(box3d PRIVATE -Wmissing-prototypes -Wall -Wextra -pedantic)
|
||||
elseif (EMSCRIPTEN)
|
||||
message(STATUS "Box3D on Emscripten")
|
||||
# SSE2/wasm SIMD flags are set directory-wide in the top-level CMakeLists so the
|
||||
# tests, which include simd.h, get them too.
|
||||
elseif (UNIX)
|
||||
message(STATUS "Box3D using Unix")
|
||||
target_compile_options(box3d PRIVATE -Wmissing-prototypes -Wall -Wextra -pedantic -Wno-unused-value)
|
||||
if ("${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "aarch64")
|
||||
# raspberry pi
|
||||
# -mfpu=neon
|
||||
# target_compile_options(box3d PRIVATE)
|
||||
else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "src" FILES ${BOX3D_SOURCE_FILES})
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include" PREFIX "include" FILES ${BOX3D_INCLUDE_FILES})
|
||||
|
||||
add_library(box3d::box3d ALIAS box3d)
|
||||
|
||||
# libm is required on many Unix toolchains for sqrtf, etc.
|
||||
if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN)
|
||||
target_link_libraries(box3d PUBLIC m)
|
||||
endif()
|
||||
|
||||
install(
|
||||
TARGETS box3d
|
||||
EXPORT box3dConfig
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
install(
|
||||
EXPORT box3dConfig
|
||||
NAMESPACE box3d::
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/box3d"
|
||||
)
|
||||
|
||||
install(
|
||||
FILES ${BOX3D_INCLUDE_FILES}
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/box3d
|
||||
)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
write_basic_package_version_file(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/box3dConfigVersion.cmake"
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
|
||||
install(
|
||||
FILES "${CMAKE_CURRENT_BINARY_DIR}/box3dConfigVersion.cmake"
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/box3d"
|
||||
)
|
||||
176
vendor/box3d/src/src/aabb.c
vendored
Normal file
176
vendor/box3d/src/src/aabb.c
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "aabb.h"
|
||||
|
||||
#include "math_internal.h"
|
||||
|
||||
#include "box3d/math_functions.h"
|
||||
|
||||
#include <float.h>
|
||||
|
||||
// Similar to Real-time Collision Detection, p179.
|
||||
// todo try
|
||||
// https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection.html
|
||||
bool b3RayCastAABB( b3AABB a, b3Vec3 p1, b3Vec3 p2, float* minFraction, float* maxFraction )
|
||||
{
|
||||
// Ray direction and length
|
||||
b3Vec3 d = b3Sub( p2, p1 );
|
||||
float rayLength = b3Length( d );
|
||||
|
||||
// Handle degenerate ray
|
||||
if ( rayLength < FLT_EPSILON )
|
||||
{
|
||||
// Check if point is inside AABB
|
||||
if ( p1.x >= a.lowerBound.x && p1.x <= a.upperBound.x && p1.y >= a.lowerBound.y && p1.y <= a.upperBound.y &&
|
||||
p1.z >= a.lowerBound.z && p1.z <= a.upperBound.z )
|
||||
{
|
||||
*minFraction = 0.0f;
|
||||
*maxFraction = 0.0f;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
b3Vec3 rayDir = b3MulSV( 1.0f / rayLength, d );
|
||||
|
||||
// Slab method for ray-AABB intersection
|
||||
float tMin = 0.0f;
|
||||
float tMax = rayLength;
|
||||
|
||||
// x-axis
|
||||
{
|
||||
float rayComponent = rayDir.x;
|
||||
float rayStart = p1.x;
|
||||
float boxMin = a.lowerBound.x;
|
||||
float boxMax = a.upperBound.x;
|
||||
|
||||
if ( b3AbsFloat( rayComponent ) < FLT_EPSILON )
|
||||
{
|
||||
// Ray is parallel to slab, check if ray origin is within slab
|
||||
if ( rayStart < boxMin || rayStart > boxMax )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compute intersection distances
|
||||
float t1 = ( boxMin - rayStart ) / rayComponent;
|
||||
float t2 = ( boxMax - rayStart ) / rayComponent;
|
||||
|
||||
// Ensure t1 <= t2
|
||||
if ( t1 > t2 )
|
||||
{
|
||||
float temp = t1;
|
||||
t1 = t2;
|
||||
t2 = temp;
|
||||
}
|
||||
|
||||
// Update intersection interval
|
||||
tMin = b3MaxFloat( tMin, t1 );
|
||||
tMax = b3MinFloat( tMax, t2 );
|
||||
|
||||
// Check for no intersection
|
||||
if ( tMin > tMax )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// y-axis
|
||||
{
|
||||
float rayComponent = rayDir.y;
|
||||
float rayStart = p1.y;
|
||||
float boxMin = a.lowerBound.y;
|
||||
float boxMax = a.upperBound.y;
|
||||
|
||||
if ( b3AbsFloat( rayComponent ) < FLT_EPSILON )
|
||||
{
|
||||
// Ray is parallel to slab, check if ray origin is within slab
|
||||
if ( rayStart < boxMin || rayStart > boxMax )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compute intersection distances
|
||||
float t1 = ( boxMin - rayStart ) / rayComponent;
|
||||
float t2 = ( boxMax - rayStart ) / rayComponent;
|
||||
|
||||
// Ensure t1 <= t2
|
||||
if ( t1 > t2 )
|
||||
{
|
||||
float temp = t1;
|
||||
t1 = t2;
|
||||
t2 = temp;
|
||||
}
|
||||
|
||||
// Update intersection interval
|
||||
tMin = b3MaxFloat( tMin, t1 );
|
||||
tMax = b3MinFloat( tMax, t2 );
|
||||
|
||||
// Check for no intersection
|
||||
if ( tMin > tMax )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// z-axis
|
||||
{
|
||||
float rayComponent = rayDir.z;
|
||||
float rayStart = p1.z;
|
||||
float boxMin = a.lowerBound.z;
|
||||
float boxMax = a.upperBound.z;
|
||||
|
||||
if ( b3AbsFloat( rayComponent ) < FLT_EPSILON )
|
||||
{
|
||||
// Ray is parallel to slab, check if ray origin is within slab
|
||||
if ( rayStart < boxMin || rayStart > boxMax )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compute intersection distances
|
||||
float t1 = ( boxMin - rayStart ) / rayComponent;
|
||||
float t2 = ( boxMax - rayStart ) / rayComponent;
|
||||
|
||||
// Ensure t1 <= t2
|
||||
if ( t1 > t2 )
|
||||
{
|
||||
float temp = t1;
|
||||
t1 = t2;
|
||||
t2 = temp;
|
||||
}
|
||||
|
||||
// Update intersection interval
|
||||
tMin = b3MaxFloat( tMin, t1 );
|
||||
tMax = b3MinFloat( tMax, t2 );
|
||||
|
||||
// Check for no intersection
|
||||
if ( tMin > tMax )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if intersection is behind ray start
|
||||
if ( tMax < 0.0f )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert distances to fractions
|
||||
*minFraction = b3ClampFloat( tMin / rayLength, 0.0f, 1.0f );
|
||||
*maxFraction = b3ClampFloat( tMax / rayLength, 0.0f, 1.0f );
|
||||
|
||||
return true;
|
||||
}
|
||||
80
vendor/box3d/src/src/aabb.h
vendored
Normal file
80
vendor/box3d/src/src/aabb.h
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "box3d/types.h"
|
||||
|
||||
// Ray cast an AABB. This is a custom function used by height fields.
|
||||
bool b3RayCastAABB( b3AABB a, b3Vec3 p1, b3Vec3 p2, float* minFraction, float* maxFraction );
|
||||
|
||||
// Get the surface area (perimeter)
|
||||
static inline float b3Perimeter( b3AABB a )
|
||||
{
|
||||
float wx = a.upperBound.x - a.lowerBound.x;
|
||||
float wy = a.upperBound.y - a.lowerBound.y;
|
||||
float wz = a.upperBound.z - a.lowerBound.z;
|
||||
return 2.0f * ( wx * wz + wy * wx + wz * wy );
|
||||
}
|
||||
|
||||
/// Enlarge a to contain b
|
||||
/// @return true if the AABB grew
|
||||
static inline bool b3EnlargeAABB( b3AABB* a, b3AABB b )
|
||||
{
|
||||
bool changed = false;
|
||||
if ( b.lowerBound.x < a->lowerBound.x )
|
||||
{
|
||||
a->lowerBound.x = b.lowerBound.x;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( b.lowerBound.y < a->lowerBound.y )
|
||||
{
|
||||
a->lowerBound.y = b.lowerBound.y;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( b.lowerBound.z < a->lowerBound.z )
|
||||
{
|
||||
a->lowerBound.z = b.lowerBound.z;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( a->upperBound.x < b.upperBound.x )
|
||||
{
|
||||
a->upperBound.x = b.upperBound.x;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( a->upperBound.y < b.upperBound.y )
|
||||
{
|
||||
a->upperBound.y = b.upperBound.y;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( a->upperBound.z < b.upperBound.z )
|
||||
{
|
||||
a->upperBound.z = b.upperBound.z;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/// Do a and b overlap
|
||||
inline bool b3OverlapAABBs( b3AABB a, b3AABB b )
|
||||
{
|
||||
return !( b.lowerBound.x > a.upperBound.x || b.lowerBound.y > a.upperBound.y || b.lowerBound.z > a.upperBound.z ||
|
||||
a.lowerBound.x > b.upperBound.x || a.lowerBound.y > b.upperBound.y || a.lowerBound.z > b.upperBound.z );
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline b3Vec3 b3FarthestPointOnAABB( b3AABB b, b3Vec3 p )
|
||||
{
|
||||
return (b3Vec3){
|
||||
.x = ( p.x - b.lowerBound.x ) > ( b.upperBound.x - p.x ) ? b.lowerBound.x : b.upperBound.x,
|
||||
.y = ( p.y - b.lowerBound.y ) > ( b.upperBound.y - p.y ) ? b.lowerBound.y : b.upperBound.y,
|
||||
.z = ( p.z - b.lowerBound.z ) > ( b.upperBound.z - p.z ) ? b.lowerBound.z : b.upperBound.z,
|
||||
};
|
||||
}
|
||||
19
vendor/box3d/src/src/algorithm.h
vendored
Normal file
19
vendor/box3d/src/src/algorithm.h
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
// Swap two same-size lvalues through a byte buffer. Avoids __typeof__ so it builds on any C compiler.
|
||||
#define B3_SWAP( x, y ) \
|
||||
do \
|
||||
{ \
|
||||
_Static_assert( sizeof( x ) == sizeof( y ), "size mismatch" ); \
|
||||
char B3_SWAP_TEMP[sizeof( x )]; \
|
||||
memcpy( B3_SWAP_TEMP, &( x ), sizeof( x ) ); \
|
||||
memcpy( &( x ), &( y ), sizeof( x ) ); \
|
||||
memcpy( &( y ), B3_SWAP_TEMP, sizeof( x ) ); \
|
||||
} \
|
||||
while ( 0 )
|
||||
178
vendor/box3d/src/src/arena_allocator.c
vendored
Normal file
178
vendor/box3d/src/src/arena_allocator.c
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "arena_allocator.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
b3Stack b3CreateStack( int capacity )
|
||||
{
|
||||
B3_ASSERT( capacity >= 0 );
|
||||
b3Stack stack = { 0 };
|
||||
stack.capacity = capacity;
|
||||
stack.memory = (char*)b3Alloc( capacity );
|
||||
return stack;
|
||||
}
|
||||
|
||||
void b3DestroyStack( b3Stack* stack )
|
||||
{
|
||||
b3Free( stack->memory, stack->capacity );
|
||||
}
|
||||
|
||||
void* b3StackAlloc( b3Stack* stack, int size, const char* name )
|
||||
{
|
||||
if ( stack->entryCount == B3_MAX_STACK_ENTRIES )
|
||||
{
|
||||
B3_ASSERT( false );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int alignedSize = ( ( size - 1 ) | ( B3_ALIGNMENT - 1 ) ) + 1;
|
||||
|
||||
b3StackEntry entry;
|
||||
entry.size = alignedSize;
|
||||
entry.name = name;
|
||||
if ( stack->index + alignedSize > stack->capacity )
|
||||
{
|
||||
// fall back to the heap (undesirable)
|
||||
entry.data = (char*)b3Alloc( alignedSize );
|
||||
entry.usedMalloc = true;
|
||||
|
||||
B3_ASSERT( ( (uintptr_t)entry.data & ( B3_ALIGNMENT - 1 ) ) == 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.data = stack->memory + stack->index;
|
||||
entry.usedMalloc = false;
|
||||
stack->index += alignedSize;
|
||||
|
||||
B3_ASSERT( ( (uintptr_t)entry.data & ( B3_ALIGNMENT - 1 ) ) == 0 );
|
||||
}
|
||||
|
||||
stack->allocation += alignedSize;
|
||||
if ( stack->allocation > stack->maxAllocation )
|
||||
{
|
||||
stack->maxAllocation = stack->allocation;
|
||||
}
|
||||
|
||||
stack->entries[stack->entryCount] = entry;
|
||||
stack->entryCount += 1;
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
void b3StackFree( b3Stack* stack, void* mem )
|
||||
{
|
||||
int entryCount = stack->entryCount;
|
||||
B3_ASSERT( entryCount > 0 );
|
||||
b3StackEntry* entry = stack->entries + ( entryCount - 1 );
|
||||
B3_ASSERT( mem == entry->data );
|
||||
if ( entry->usedMalloc )
|
||||
{
|
||||
b3Free( mem, entry->size );
|
||||
}
|
||||
else
|
||||
{
|
||||
stack->index -= entry->size;
|
||||
}
|
||||
stack->allocation -= entry->size;
|
||||
stack->entryCount -= 1;
|
||||
}
|
||||
|
||||
void b3GrowStack( b3Stack* stack )
|
||||
{
|
||||
// Stack must not be in use
|
||||
B3_ASSERT( stack->allocation == 0 );
|
||||
|
||||
if ( stack->maxAllocation > stack->capacity )
|
||||
{
|
||||
b3Free( stack->memory, stack->capacity );
|
||||
stack->capacity = stack->maxAllocation + stack->maxAllocation / 2;
|
||||
stack->memory = (char*)b3Alloc( stack->capacity );
|
||||
}
|
||||
}
|
||||
|
||||
int b3GetStackCapacity( b3Stack* stack )
|
||||
{
|
||||
return stack->capacity;
|
||||
}
|
||||
|
||||
int b3GetStackAllocation( b3Stack* stack )
|
||||
{
|
||||
return stack->allocation;
|
||||
}
|
||||
|
||||
int b3GetMaxStackAllocation( b3Stack* stack )
|
||||
{
|
||||
return stack->maxAllocation;
|
||||
}
|
||||
|
||||
b3Arena b3CreateArena( int capacity )
|
||||
{
|
||||
int c = capacity > 8 ? capacity : 8;
|
||||
b3ArenaSharedState* shared = (b3ArenaSharedState*)b3Alloc( sizeof( b3ArenaSharedState ) );
|
||||
*shared = (b3ArenaSharedState){ 0 };
|
||||
|
||||
return (b3Arena){
|
||||
.memory = (char*)b3Alloc( c ),
|
||||
.capacity = c,
|
||||
.index = 0,
|
||||
.shared = shared,
|
||||
};
|
||||
}
|
||||
|
||||
void b3DestroyArena( b3Arena* arena )
|
||||
{
|
||||
b3ArenaSharedState* shared = arena->shared;
|
||||
if ( shared != NULL )
|
||||
{
|
||||
for ( int i = 0; i < shared->overflows.count; ++i )
|
||||
{
|
||||
b3Free( shared->overflows.data[i].data, shared->overflows.data[i].size );
|
||||
}
|
||||
b3Array_Destroy( shared->overflows );
|
||||
b3Free( shared, sizeof( b3ArenaSharedState ) );
|
||||
}
|
||||
b3Free( arena->memory, arena->capacity );
|
||||
*arena = (b3Arena){ 0 };
|
||||
}
|
||||
|
||||
void* b3ArenaOverflowAlloc( b3Arena* arena, int size )
|
||||
{
|
||||
b3ArenaSharedState* shared = arena->shared;
|
||||
char* data = (char*)b3Alloc( size );
|
||||
b3OverflowBlock block = { data, size };
|
||||
b3Array_Push( shared->overflows, block );
|
||||
shared->overflowBytes += size;
|
||||
return data;
|
||||
}
|
||||
|
||||
void b3ArenaSync( b3Arena* arena )
|
||||
{
|
||||
b3ArenaSharedState* shared = arena->shared;
|
||||
|
||||
for ( int i = 0; i < shared->overflows.count; ++i )
|
||||
{
|
||||
b3Free( shared->overflows.data[i].data, shared->overflows.data[i].size );
|
||||
}
|
||||
b3Array_Clear( shared->overflows );
|
||||
|
||||
int demand = shared->maxIndex + shared->overflowBytes;
|
||||
if ( demand > shared->peakDemand )
|
||||
{
|
||||
shared->peakDemand = demand;
|
||||
}
|
||||
if ( demand > arena->capacity )
|
||||
{
|
||||
b3Free( arena->memory, arena->capacity );
|
||||
int newCapacity = demand + demand / 2;
|
||||
arena->memory = (char*)b3Alloc( newCapacity );
|
||||
arena->capacity = newCapacity;
|
||||
}
|
||||
|
||||
arena->index = 0;
|
||||
shared->maxIndex = 0;
|
||||
shared->overflowBytes = 0;
|
||||
}
|
||||
114
vendor/box3d/src/src/arena_allocator.h
vendored
Normal file
114
vendor/box3d/src/src/arena_allocator.h
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
#include "box3d/base.h"
|
||||
#include "container.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define B3_MAX_STACK_ENTRIES 32
|
||||
|
||||
typedef struct b3StackEntry
|
||||
{
|
||||
char* data;
|
||||
const char* name;
|
||||
int size;
|
||||
bool usedMalloc;
|
||||
} b3StackEntry;
|
||||
|
||||
// This is a stack-like arena allocator used for fast per step allocations.
|
||||
// You must nest allocate/free pairs. The code will B3_ASSERT
|
||||
// if you try to interleave multiple allocate/free pairs.
|
||||
// This allocator uses the heap if space is insufficient.
|
||||
// I could remove the need to free entries individually.
|
||||
typedef struct b3Stack
|
||||
{
|
||||
char* memory;
|
||||
int capacity;
|
||||
int index;
|
||||
|
||||
int allocation;
|
||||
int maxAllocation;
|
||||
|
||||
b3StackEntry entries[B3_MAX_STACK_ENTRIES];
|
||||
int entryCount;
|
||||
} b3Stack;
|
||||
|
||||
// Heap-allocated fallback block tracked when an arena bump overflows.
|
||||
typedef struct b3OverflowBlock
|
||||
{
|
||||
char* data;
|
||||
int size;
|
||||
} b3OverflowBlock;
|
||||
|
||||
b3DeclareArray( b3OverflowBlock );
|
||||
|
||||
// Shared, heap-allocated state co-owned by every copy of a b3Arena.
|
||||
// b3Arena is passed by value so its bump pointer auto-restores on
|
||||
// function return, but overflow tracking and watermarks must persist
|
||||
// across copies -- hence this pointer-shared block.
|
||||
typedef struct b3ArenaSharedState
|
||||
{
|
||||
b3Array( b3OverflowBlock ) overflows;
|
||||
int maxIndex; // high water mark of the bump pointer this step
|
||||
int overflowBytes; // total bytes in overflow blocks this step
|
||||
int peakDemand; // all-time peak of (maxIndex + overflowBytes), survives sync
|
||||
} b3ArenaSharedState;
|
||||
|
||||
typedef struct b3Arena
|
||||
{
|
||||
char* memory;
|
||||
int capacity;
|
||||
int index;
|
||||
b3ArenaSharedState* shared;
|
||||
} b3Arena;
|
||||
|
||||
// 16-byte alignment for SSE2 + typical struct alignment.
|
||||
#define B3_ARENA_ALIGNMENT 16
|
||||
|
||||
b3Stack b3CreateStack( int capacity );
|
||||
void b3DestroyStack( b3Stack* stack );
|
||||
|
||||
void* b3StackAlloc( b3Stack* stack, int size, const char* name );
|
||||
void b3StackFree( b3Stack* stack, void* mem );
|
||||
|
||||
// Grow the stack based on usage
|
||||
void b3GrowStack( b3Stack* stack );
|
||||
|
||||
int b3GetStackCapacity( b3Stack* stack );
|
||||
int b3GetStackAllocation( b3Stack* stack );
|
||||
int b3GetMaxStackAllocation( b3Stack* stack );
|
||||
|
||||
b3Arena b3CreateArena( int capacity );
|
||||
void b3DestroyArena( b3Arena* arena );
|
||||
|
||||
// Heap-allocate an overflow block, register it in the shared state, return it.
|
||||
void* b3ArenaOverflowAlloc( b3Arena* arena, int size );
|
||||
|
||||
// Call between simulation steps. Frees this step's overflow blocks and grows
|
||||
// the backing capacity if last step's demand (maxIndex + overflowBytes) exceeded it.
|
||||
void b3ArenaSync( b3Arena* arena );
|
||||
|
||||
static inline void* b3Bump( b3Arena* arena, int size )
|
||||
{
|
||||
if ( size == 0 )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int aligned = ( arena->index + ( B3_ARENA_ALIGNMENT - 1 ) ) & ~( B3_ARENA_ALIGNMENT - 1 );
|
||||
|
||||
if ( aligned + size > arena->capacity )
|
||||
{
|
||||
return b3ArenaOverflowAlloc( arena, size );
|
||||
}
|
||||
|
||||
arena->index = aligned + size;
|
||||
if ( arena->index > arena->shared->maxIndex )
|
||||
{
|
||||
arena->shared->maxIndex = arena->index;
|
||||
}
|
||||
return arena->memory + aligned;
|
||||
}
|
||||
66
vendor/box3d/src/src/bitset.c
vendored
Normal file
66
vendor/box3d/src/src/bitset.c
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "bitset.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
b3BitSet b3CreateBitSet( uint32_t bitCapacity )
|
||||
{
|
||||
b3BitSet bitSet = { 0 };
|
||||
bitSet.blockCapacity = ( bitCapacity + sizeof( uint64_t ) * 8 - 1 ) / ( sizeof( uint64_t ) * 8 );
|
||||
bitSet.blockCount = 0;
|
||||
bitSet.bits = (uint64_t*)b3Alloc( bitSet.blockCapacity * sizeof( uint64_t ) );
|
||||
memset( bitSet.bits, 0, bitSet.blockCapacity * sizeof( uint64_t ) );
|
||||
return bitSet;
|
||||
}
|
||||
|
||||
void b3DestroyBitSet( b3BitSet* bitSet )
|
||||
{
|
||||
b3Free( bitSet->bits, bitSet->blockCapacity * sizeof( uint64_t ) );
|
||||
bitSet->blockCapacity = 0;
|
||||
bitSet->blockCount = 0;
|
||||
bitSet->bits = NULL;
|
||||
}
|
||||
|
||||
void b3SetBitCountAndClear( b3BitSet* bitSet, uint32_t bitCount )
|
||||
{
|
||||
uint32_t blockCount = ( bitCount + sizeof( uint64_t ) * 8 - 1 ) / ( sizeof( uint64_t ) * 8 );
|
||||
if ( bitSet->blockCapacity < blockCount )
|
||||
{
|
||||
b3DestroyBitSet( bitSet );
|
||||
uint32_t newBitCapacity = bitCount + ( bitCount >> 1 );
|
||||
*bitSet = b3CreateBitSet( newBitCapacity );
|
||||
}
|
||||
|
||||
bitSet->blockCount = blockCount;
|
||||
memset( bitSet->bits, 0, bitSet->blockCount * sizeof( uint64_t ) );
|
||||
}
|
||||
|
||||
void b3GrowBitSet( b3BitSet* bitSet, uint32_t blockCount )
|
||||
{
|
||||
B3_ASSERT( blockCount > bitSet->blockCount );
|
||||
if ( blockCount > bitSet->blockCapacity )
|
||||
{
|
||||
uint32_t oldCapacity = bitSet->blockCapacity;
|
||||
bitSet->blockCapacity = blockCount + blockCount / 2;
|
||||
uint64_t* newBits = (uint64_t*)b3Alloc( bitSet->blockCapacity * sizeof( uint64_t ) );
|
||||
memset( newBits, 0, bitSet->blockCapacity * sizeof( uint64_t ) );
|
||||
B3_ASSERT( bitSet->bits != NULL );
|
||||
memcpy( newBits, bitSet->bits, oldCapacity * sizeof( uint64_t ) );
|
||||
b3Free( bitSet->bits, oldCapacity * sizeof( uint64_t ) );
|
||||
bitSet->bits = newBits;
|
||||
}
|
||||
|
||||
bitSet->blockCount = blockCount;
|
||||
}
|
||||
|
||||
void b3InPlaceUnion( b3BitSet* __restrict setA, const b3BitSet* __restrict setB )
|
||||
{
|
||||
B3_ASSERT( setA->blockCount == setB->blockCount );
|
||||
uint32_t blockCount = setA->blockCount;
|
||||
for ( uint32_t i = 0; i < blockCount; ++i )
|
||||
{
|
||||
setA->bits[i] |= setB->bits[i];
|
||||
}
|
||||
}
|
||||
67
vendor/box3d/src/src/bitset.h
vendored
Normal file
67
vendor/box3d/src/src/bitset.h
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Bit set provides fast operations on large arrays of bits.
|
||||
typedef struct b3BitSet
|
||||
{
|
||||
uint64_t* bits;
|
||||
uint32_t blockCapacity;
|
||||
uint32_t blockCount;
|
||||
} b3BitSet;
|
||||
|
||||
|
||||
b3BitSet b3CreateBitSet( uint32_t bitCapacity );
|
||||
void b3DestroyBitSet( b3BitSet* bitSet );
|
||||
void b3SetBitCountAndClear( b3BitSet* bitSet, uint32_t bitCount );
|
||||
void b3InPlaceUnion( b3BitSet* setA, const b3BitSet* setB );
|
||||
void b3GrowBitSet( b3BitSet* bitSet, uint32_t blockCount );
|
||||
int b3CountSetBits( b3BitSet* bitSet );
|
||||
|
||||
static inline void b3SetBit( b3BitSet* bitSet, uint32_t bitIndex )
|
||||
{
|
||||
uint32_t blockIndex = bitIndex / 64;
|
||||
B3_ASSERT( blockIndex < bitSet->blockCount );
|
||||
bitSet->bits[blockIndex] |= ( (uint64_t)1 << bitIndex % 64 );
|
||||
}
|
||||
|
||||
static inline void b3SetBitGrow( b3BitSet* bitSet, uint32_t bitIndex )
|
||||
{
|
||||
uint32_t blockIndex = bitIndex / 64;
|
||||
if ( blockIndex >= bitSet->blockCount )
|
||||
{
|
||||
b3GrowBitSet( bitSet, blockIndex + 1 );
|
||||
}
|
||||
bitSet->bits[blockIndex] |= ( (uint64_t)1 << bitIndex % 64 );
|
||||
}
|
||||
|
||||
static inline void b3ClearBit( b3BitSet* bitSet, uint32_t bitIndex )
|
||||
{
|
||||
uint32_t blockIndex = bitIndex / 64;
|
||||
if ( blockIndex >= bitSet->blockCount )
|
||||
{
|
||||
return;
|
||||
}
|
||||
bitSet->bits[blockIndex] &= ~( (uint64_t)1 << bitIndex % 64 );
|
||||
}
|
||||
|
||||
static inline bool b3GetBit( const b3BitSet* bitSet, uint32_t bitIndex )
|
||||
{
|
||||
uint32_t blockIndex = bitIndex / 64;
|
||||
if ( blockIndex >= bitSet->blockCount )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return ( bitSet->bits[blockIndex] & ( (uint64_t)1 << bitIndex % 64 ) ) != 0;
|
||||
}
|
||||
|
||||
static inline int b3GetBitSetBytes( b3BitSet* bitSet )
|
||||
{
|
||||
return bitSet->blockCapacity * sizeof( uint64_t );
|
||||
}
|
||||
90
vendor/box3d/src/src/block_allocator.c
vendored
Normal file
90
vendor/box3d/src/src/block_allocator.c
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "block_allocator.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
b3BlockAllocator b3CreateBlockAllocator( int elementSize, int initialCount )
|
||||
{
|
||||
B3_ASSERT( elementSize >= (int)sizeof( void* ) );
|
||||
|
||||
b3BlockAllocator allocator = { 0 };
|
||||
|
||||
b3Array_Create( allocator.blocks );
|
||||
allocator.elementSize = elementSize;
|
||||
allocator.freeList = NULL;
|
||||
allocator.nextIndex = 0;
|
||||
allocator.allocationCount = 0;
|
||||
|
||||
if ( initialCount > 0 )
|
||||
{
|
||||
int count = ( initialCount + B3_BLOCK_SIZE - 1 ) >> B3_BLOCK_EXPONENT;
|
||||
b3Array_Resize( allocator.blocks, count );
|
||||
|
||||
for ( int i = 0; i < allocator.blocks.count; ++i )
|
||||
{
|
||||
allocator.blocks.data[i].memory = b3Alloc( B3_BLOCK_SIZE * elementSize );
|
||||
}
|
||||
}
|
||||
|
||||
return allocator;
|
||||
}
|
||||
|
||||
void b3DestroyBlockAllocator( b3BlockAllocator* allocator )
|
||||
{
|
||||
for ( int i = 0; i < allocator->blocks.count; ++i )
|
||||
{
|
||||
b3Free( allocator->blocks.data[i].memory, B3_BLOCK_SIZE * allocator->elementSize );
|
||||
}
|
||||
|
||||
b3Array_Destroy( allocator->blocks );
|
||||
}
|
||||
|
||||
void* b3AllocateElement( b3BlockAllocator* allocator )
|
||||
{
|
||||
B3_ASSERT( allocator != NULL );
|
||||
|
||||
allocator->allocationCount += 1;
|
||||
|
||||
// Pop from free list first
|
||||
if ( allocator->freeList != NULL )
|
||||
{
|
||||
void* element = allocator->freeList;
|
||||
allocator->freeList = *(void**)element;
|
||||
return element;
|
||||
}
|
||||
|
||||
int index = allocator->nextIndex++;
|
||||
|
||||
int requiredBlockCount = ( index >> B3_BLOCK_EXPONENT ) + 1;
|
||||
|
||||
if ( requiredBlockCount > allocator->blocks.count )
|
||||
{
|
||||
int oldCount = allocator->blocks.count;
|
||||
b3Array_Resize( allocator->blocks, requiredBlockCount );
|
||||
|
||||
for ( int i = oldCount; i < requiredBlockCount; ++i )
|
||||
{
|
||||
allocator->blocks.data[i].memory = b3Alloc( B3_BLOCK_SIZE * allocator->elementSize );
|
||||
}
|
||||
}
|
||||
|
||||
int blockIndex = index >> B3_BLOCK_EXPONENT;
|
||||
int blockOffset = index & ( B3_BLOCK_SIZE - 1 );
|
||||
|
||||
return allocator->blocks.data[blockIndex].memory + blockOffset * allocator->elementSize;
|
||||
}
|
||||
|
||||
void b3FreeElement( b3BlockAllocator* allocator, void* element )
|
||||
{
|
||||
B3_ASSERT( allocator != NULL );
|
||||
B3_ASSERT( element != NULL );
|
||||
B3_ASSERT( allocator->allocationCount > 0 );
|
||||
|
||||
allocator->allocationCount -= 1;
|
||||
|
||||
// Push onto free list
|
||||
*(void**)element = allocator->freeList;
|
||||
allocator->freeList = element;
|
||||
}
|
||||
35
vendor/box3d/src/src/block_allocator.h
vendored
Normal file
35
vendor/box3d/src/src/block_allocator.h
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "container.h"
|
||||
|
||||
#include "box3d/base.h"
|
||||
|
||||
#define B3_BLOCK_EXPONENT 8
|
||||
#define B3_BLOCK_SIZE ( 1 << B3_BLOCK_EXPONENT )
|
||||
|
||||
typedef struct b3Block
|
||||
{
|
||||
char* memory;
|
||||
} b3Block;
|
||||
|
||||
b3DeclareArray( b3Block );
|
||||
|
||||
typedef struct b3BlockAllocator
|
||||
{
|
||||
b3Array( b3Block ) blocks;
|
||||
void* freeList;
|
||||
int elementSize;
|
||||
int nextIndex;
|
||||
int allocationCount;
|
||||
} b3BlockAllocator;
|
||||
|
||||
// Element must be large enough to hold a pointer
|
||||
b3BlockAllocator b3CreateBlockAllocator( int elementSize, int initialCount );
|
||||
void b3DestroyBlockAllocator( b3BlockAllocator* allocator );
|
||||
|
||||
// Returns one element of elementSize contiguous bytes. Address is stable until freed.
|
||||
void* b3AllocateElement( b3BlockAllocator* allocator );
|
||||
void b3FreeElement( b3BlockAllocator* allocator, void* element );
|
||||
2454
vendor/box3d/src/src/body.c
vendored
Normal file
2454
vendor/box3d/src/src/body.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
256
vendor/box3d/src/src/body.h
vendored
Normal file
256
vendor/box3d/src/src/body.h
vendored
Normal file
@@ -0,0 +1,256 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "box3d/constants.h"
|
||||
#include "box3d/math_functions.h"
|
||||
#include "box3d/types.h"
|
||||
|
||||
typedef struct b3World b3World;
|
||||
|
||||
enum b3BodyFlags
|
||||
{
|
||||
// This body has fixed translation along the x-axis
|
||||
b3_lockLinearX = 0x00000001,
|
||||
|
||||
// This body has fixed translation along the y-axis
|
||||
b3_lockLinearY = 0x00000002,
|
||||
|
||||
// This body has fixed translation along the z-axis
|
||||
b3_lockLinearZ = 0x00000004,
|
||||
|
||||
// This body has fixed rotation around the x-axis
|
||||
b3_lockAngularX = 0x00000008,
|
||||
|
||||
// This body has fixed rotation around the y-axis
|
||||
b3_lockAngularY = 0x00000010,
|
||||
|
||||
// This body has fixed rotation around the z-axis
|
||||
b3_lockAngularZ = 0x00000020,
|
||||
|
||||
// This flag is used for debug draw
|
||||
b3_isFast = 0x00000040,
|
||||
|
||||
// This dynamic body does a final CCD pass against all body types, but not other bullets
|
||||
b3_isBullet = 0x00000080,
|
||||
|
||||
// This body was speed capped in the current time step
|
||||
b3_isSpeedCapped = 0x00000100,
|
||||
|
||||
// This body had a time of impact event in the current time step
|
||||
b3_hadTimeOfImpact = 0x00000200,
|
||||
|
||||
// This body has no limit on angular velocity
|
||||
b3_allowFastRotation = 0x00000400,
|
||||
|
||||
// This body need's to have its AABB increased
|
||||
b3_enlargeBounds = 0x00000800,
|
||||
|
||||
// This body is dynamic so the solver should write to it.
|
||||
// This prevents writing to kinematic bodies that causes a multithreaded sharing
|
||||
// cache coherence problem even when the values are not changing.
|
||||
// Used for b3BodyState flags.
|
||||
b3_dynamicFlag = 0x00001000,
|
||||
|
||||
b3_enableSleep = 0x00002000,
|
||||
|
||||
b3_bodyEnableContactRecycling = 0x00004000,
|
||||
|
||||
// The user deferred mass computation via the updateBodyMass shape option and mass
|
||||
// data still hasn't been set.
|
||||
b3_dirtyMass = 0x00008000,
|
||||
|
||||
// All lock flags
|
||||
b3_allLocks = b3_lockLinearX | b3_lockLinearY | b3_lockLinearZ | b3_lockAngularX | b3_lockAngularY | b3_lockAngularZ,
|
||||
|
||||
// If all these flags are set then the body has fixed rotation
|
||||
b3_fixedRotation = b3_lockAngularX | b3_lockAngularY | b3_lockAngularZ,
|
||||
|
||||
// These flags are transient per time step. These may be different across b3Body, b3BodySim, and b3BodyState.
|
||||
b3_bodyTransientFlags = b3_isFast | b3_isSpeedCapped | b3_hadTimeOfImpact,
|
||||
};
|
||||
|
||||
// Body organizational details that are not used in the solver.
|
||||
typedef struct b3Body
|
||||
{
|
||||
void* userData;
|
||||
|
||||
// index of solver set stored in b3World
|
||||
// may be B3_NULL_INDEX
|
||||
int setIndex;
|
||||
|
||||
// body sim and state index within set
|
||||
// may be B3_NULL_INDEX
|
||||
int localIndex;
|
||||
|
||||
// [31 : contactId | 1 : edgeIndex]
|
||||
int headContactKey;
|
||||
int contactCount;
|
||||
|
||||
// todo maybe move this to the body sim
|
||||
int headShapeId;
|
||||
int shapeCount;
|
||||
|
||||
int headChainId;
|
||||
|
||||
// [31 : jointId | 1 : edgeIndex]
|
||||
int headJointKey;
|
||||
int jointCount;
|
||||
|
||||
// All enabled dynamic and kinematic bodies are in an island.
|
||||
int islandId;
|
||||
|
||||
// Index into the island's bodies array for O(1) swap-removal.
|
||||
// B3_NULL_INDEX when not in an island.
|
||||
int islandIndex;
|
||||
|
||||
float sleepThreshold;
|
||||
float sleepTime;
|
||||
float sleepVelocity;
|
||||
float mass;
|
||||
|
||||
// local space inertia
|
||||
b3Matrix3 inertia;
|
||||
|
||||
// this is used to adjust the fellAsleep flag in the body move array
|
||||
int bodyMoveIndex;
|
||||
|
||||
int id;
|
||||
|
||||
// b3BodyFlags
|
||||
uint32_t flags;
|
||||
uint32_t nameId;
|
||||
|
||||
b3BodyType type;
|
||||
|
||||
// This is monotonically advanced when a body is allocated in this slot
|
||||
// Used to check for invalid b3BodyId
|
||||
uint16_t generation;
|
||||
} b3Body;
|
||||
|
||||
// Body State
|
||||
// The body state is designed for fast conversion to and from SIMD via scatter-gather.
|
||||
// Only awake dynamic and kinematic bodies have a body state.
|
||||
// This is used in the performance critical constraint solver
|
||||
//
|
||||
// The solver operates on the body state. The body state array does not hold static bodies. Static bodies are shared
|
||||
// across worker threads. It would be okay to read their states, but writing to them would cause cache thrashing across
|
||||
// workers, even if the values don't change.
|
||||
// This causes some trouble when computing anchors. I rotate joint anchors using the body rotation every sub-step. For static
|
||||
// bodies the anchor doesn't rotate. Body A or B could be static and this can lead to lots of branching. This branching
|
||||
// should be minimized.
|
||||
//
|
||||
// Solution 1:
|
||||
// Use delta rotations. This means anchors need to be prepared in world space. The delta rotation for static bodies will be
|
||||
// identity using a dummy state. Base separation and angles need to be computed. Manifolds will be behind a frame, but that
|
||||
// is probably best if bodies move fast.
|
||||
//
|
||||
// Solution 2:
|
||||
// Use full rotation. The anchors for static bodies will be in world space while the anchors for dynamic bodies will be in local
|
||||
// space. Potentially confusing and bug prone.
|
||||
//
|
||||
// Note:
|
||||
// I rotate joint anchors each sub-step but not contact anchors. Joint stability improves a lot by rotating joint anchors
|
||||
// according to substep progress. Contacts have reduced stability when anchors are rotated during substeps, especially for
|
||||
// round shapes.
|
||||
//
|
||||
// 56 bytes
|
||||
// todo_erin measure perf padding to 64 bytes
|
||||
typedef struct b3BodyState
|
||||
{
|
||||
b3Vec3 linearVelocity; // 12
|
||||
b3Vec3 angularVelocity; // 12
|
||||
|
||||
// Using delta position reduces round-off error far from the origin
|
||||
b3Vec3 deltaPosition; // 12
|
||||
|
||||
// Using delta rotation because I cannot access the full rotation on static bodies in
|
||||
// the solver and must use zero delta rotation for static bodies (c,s) = (1,0)
|
||||
b3Quat deltaRotation; // 16
|
||||
|
||||
// b3BodyFlags
|
||||
// Important flags: locking, dynamic
|
||||
uint32_t flags; // 4
|
||||
} b3BodyState;
|
||||
|
||||
// Identity body state, notice the deltaRotation is identity
|
||||
static const b3BodyState b3_identityBodyState = {
|
||||
{ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { { 0.0f, 0.0f, 0.0f }, 1.0f }, 0,
|
||||
};
|
||||
|
||||
// Body simulation data used for integration of position and velocity
|
||||
// Transform data used for collision and solver preparation.
|
||||
typedef struct b3BodySim
|
||||
{
|
||||
// transform for body origin, double translation in large world mode
|
||||
b3WorldTransform transform;
|
||||
|
||||
// center of mass position in world space
|
||||
b3Pos center;
|
||||
|
||||
// previous rotation and COM for TOI
|
||||
b3Quat rotation0;
|
||||
b3Pos center0;
|
||||
|
||||
// location of center of mass relative to the body origin
|
||||
b3Vec3 localCenter;
|
||||
|
||||
b3Vec3 force;
|
||||
b3Vec3 torque;
|
||||
|
||||
float invMass;
|
||||
|
||||
// Rotational inertia about the center of mass. The world space inverse inertia tensor
|
||||
// must be updated whenever the body rotation is modified.
|
||||
b3Matrix3 invInertiaLocal;
|
||||
b3Matrix3 invInertiaWorld;
|
||||
|
||||
float minExtent;
|
||||
b3Vec3 maxExtent;
|
||||
float maxAngularVelocity;
|
||||
float linearDamping;
|
||||
float angularDamping;
|
||||
float gravityScale;
|
||||
|
||||
// Index of b3Body
|
||||
int bodyId;
|
||||
|
||||
// b3BodyFlags
|
||||
uint32_t flags;
|
||||
} b3BodySim;
|
||||
|
||||
// Get a validated body from a world using an id.
|
||||
b3Body* b3GetBodyFullId( b3World* world, b3BodyId bodyId );
|
||||
|
||||
b3WorldTransform b3GetBodyTransformQuick( b3World* world, b3Body* body );
|
||||
b3WorldTransform b3GetBodyTransform( b3World* world, int bodyId );
|
||||
|
||||
// Create a b3BodyId from a raw id.
|
||||
b3BodyId b3MakeBodyId( b3World* world, int bodyId );
|
||||
|
||||
bool b3ShouldBodiesCollide( b3World* world, b3Body* bodyA, b3Body* bodyB );
|
||||
bool b3IsBodyAwake( b3World* world, b3Body* body );
|
||||
|
||||
b3BodySim* b3GetBodySim( b3World* world, b3Body* body );
|
||||
b3BodyState* b3GetBodyState( b3World* world, b3Body* body );
|
||||
|
||||
// careful calling this because it can invalidate body, state, joint, and contact pointers
|
||||
bool b3WakeBody( b3World* world, b3Body* body );
|
||||
bool b3WakeBodyWithLock( b3World* world, b3Body* body );
|
||||
|
||||
void b3UpdateBodyMassData( b3World* world, b3Body* body );
|
||||
void b3SyncBodyFlags( b3World* world, b3Body* body );
|
||||
|
||||
// Make a sweep relative to a base position to keep TOI in float precision far from the origin.
|
||||
static inline b3Sweep b3MakeRelativeSweep( const b3BodySim* bodySim, b3Pos base )
|
||||
{
|
||||
b3Sweep s;
|
||||
s.c1 = b3SubPos( bodySim->center0, base );
|
||||
s.c2 = b3SubPos( bodySim->center, base );
|
||||
s.q1 = bodySim->rotation0;
|
||||
s.q2 = bodySim->transform.q;
|
||||
s.localCenter = bodySim->localCenter;
|
||||
return s;
|
||||
}
|
||||
|
||||
147
vendor/box3d/src/src/box3d.natvis
vendored
Normal file
147
vendor/box3d/src/src/box3d.natvis
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
|
||||
<Type Name="b3Vector2">
|
||||
<DisplayString>[ {x}, {y} ]</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3Vec3">
|
||||
<DisplayString>[ {x}, {y}, {z} ]</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3Quat">
|
||||
<DisplayString>[ {v}, {s} ]</DisplayString>
|
||||
</Type>
|
||||
|
||||
<!-- Distinct types only in BOX3D_DOUBLE_PRECISION builds; otherwise they alias b3Vec3 / b3Transform -->
|
||||
<Type Name="b3Pos">
|
||||
<DisplayString>[ {x}, {y}, {z} ]</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3WorldTransform">
|
||||
<DisplayString>[ {p}, {q} ]</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3WorldId">
|
||||
<DisplayString>index: {index1}</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>b3_worlds[ index1 - 1 ]</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3BodyId">
|
||||
<DisplayString>
|
||||
{b3_worlds[world0].bodies.data[index1-1].name,na},
|
||||
{b3_worlds[world0].bodies.data[index1-1].type}
|
||||
</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem>b3_worlds[ world0 ].bodies.data[index1-1]</ExpandedItem>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3ShapeId">
|
||||
<DisplayString>
|
||||
{b3_worlds[world0].shapes.data[index1-1].type}
|
||||
</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3ManifoldPoint">
|
||||
<DisplayString>
|
||||
{point}, sep = {separation}
|
||||
</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3FeaturePair">
|
||||
<DisplayString>
|
||||
{int(owner1)}:{int(index1)}, {int(owner2)}:{int(index2)}
|
||||
</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3TempManifold">
|
||||
<DisplayString>count: { pointCount }, triangle: { triangleIndex }, cluster: {clusterIndex}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="normal">normal</Item>
|
||||
<Item Name="feature">feature</Item>
|
||||
<Item Name="flags">triangleFlags</Item>
|
||||
<ArrayItems>
|
||||
<Size>pointCount</Size>
|
||||
<ValuePointer>points</ValuePointer>
|
||||
</ArrayItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3ShapeProxy">
|
||||
<DisplayString>{{{count} : {radius}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[count]">count</Item>
|
||||
<Item Name="[radius]">radius</Item>
|
||||
<ArrayItems Condition="count > 0">
|
||||
<Size>count</Size>
|
||||
<ValuePointer>points</ValuePointer>
|
||||
</ArrayItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3SeparationFunction">
|
||||
<DisplayString>type: {type}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[witnessA]">witnessA</Item>
|
||||
<Item Name="[witnessB]">witnessB</Item>
|
||||
<Item Name="[sweepA]">sweepA</Item>
|
||||
<Item Name="[sweepB]">sweepB</Item>
|
||||
<Item Name="[proxyA]">proxyA</Item>
|
||||
<Item Name="[proxyB]">proxyB</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3DynamicArray_*">
|
||||
<DisplayString>{{{count} / {capacity}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[count]">count</Item>
|
||||
<Item Name="[capacity]">capacity</Item>
|
||||
<ArrayItems Condition="count > 0">
|
||||
<Size>count</Size>
|
||||
<ValuePointer>data</ValuePointer>
|
||||
</ArrayItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<!-- natvis does not handle wildcards in struct names -->
|
||||
<Type Name="b3DynamicArray_int">
|
||||
<DisplayString>{{{count} / {capacity}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[count]">count</Item>
|
||||
<Item Name="[capacity]">capacity</Item>
|
||||
<ArrayItems Condition="count > 0">
|
||||
<Size>count</Size>
|
||||
<ValuePointer>data</ValuePointer>
|
||||
</ArrayItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3DynamicArray_Foo">
|
||||
<DisplayString>{{{count} / {capacity}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[count]">count</Item>
|
||||
<Item Name="[capacity]">capacity</Item>
|
||||
<ArrayItems Condition="count > 0">
|
||||
<Size>count</Size>
|
||||
<ValuePointer>data</ValuePointer>
|
||||
</ArrayItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="b3DynamicArray_b3Contact">
|
||||
<DisplayString>{{{count} / {capacity}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[count]">count</Item>
|
||||
<Item Name="[capacity]">capacity</Item>
|
||||
<ArrayItems Condition="count > 0">
|
||||
<Size>count</Size>
|
||||
<ValuePointer>data</ValuePointer>
|
||||
</ArrayItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
</AutoVisualizer>
|
||||
535
vendor/box3d/src/src/broad_phase.c
vendored
Normal file
535
vendor/box3d/src/src/broad_phase.c
vendored
Normal file
@@ -0,0 +1,535 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "broad_phase.h"
|
||||
|
||||
#include "aabb.h"
|
||||
#include "arena_allocator.h"
|
||||
#include "body.h"
|
||||
#include "contact.h"
|
||||
#include "core.h"
|
||||
#include "parallel_for.h"
|
||||
#include "physics_world.h"
|
||||
#include "platform.h"
|
||||
#include "shape.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
void b3CreateBroadPhase( b3BroadPhase* bp, const b3Capacity* capacity )
|
||||
{
|
||||
_Static_assert( b3_bodyTypeCount == 3, "must be three body types" );
|
||||
|
||||
bp->movedProxies[b3_staticBody] = b3CreateBitSet( b3MaxInt( 16, capacity->staticShapeCount ) );
|
||||
bp->movedProxies[b3_kinematicBody] = b3CreateBitSet( 16 );
|
||||
bp->movedProxies[b3_dynamicBody] = b3CreateBitSet( b3MaxInt( 16, capacity->dynamicShapeCount ) );
|
||||
b3Array_Reserve( bp->moveArray, capacity->dynamicShapeCount );
|
||||
bp->moveResults = NULL;
|
||||
bp->movePairs = NULL;
|
||||
bp->movePairCapacity = 0;
|
||||
b3AtomicStoreInt( &bp->movePairIndex, 0 );
|
||||
bp->pairSet = b3CreateSet( 2 * capacity->contactCount );
|
||||
|
||||
int staticCapacity = b3MaxInt( 16, capacity->staticShapeCount );
|
||||
bp->trees[b3_staticBody] = b3DynamicTree_Create( staticCapacity );
|
||||
|
||||
int kinematicCapacity = 16;
|
||||
bp->trees[b3_kinematicBody] = b3DynamicTree_Create( kinematicCapacity );
|
||||
|
||||
int dynamicCapacity = b3MaxInt( 16, capacity->dynamicShapeCount );
|
||||
bp->trees[b3_dynamicBody] = b3DynamicTree_Create( dynamicCapacity );
|
||||
}
|
||||
|
||||
void b3DestroyBroadPhase( b3BroadPhase* bp )
|
||||
{
|
||||
for ( int i = 0; i < b3_bodyTypeCount; ++i )
|
||||
{
|
||||
b3DynamicTree_Destroy( bp->trees + i );
|
||||
}
|
||||
|
||||
for ( int i = 0; i < b3_bodyTypeCount; ++i )
|
||||
{
|
||||
b3DestroyBitSet( &bp->movedProxies[i] );
|
||||
}
|
||||
b3Array_Destroy( bp->moveArray );
|
||||
b3DestroySet( &bp->pairSet );
|
||||
|
||||
*bp = (b3BroadPhase){ 0 };
|
||||
|
||||
memset( bp, 0, sizeof( b3BroadPhase ) );
|
||||
}
|
||||
|
||||
static void b3UnBufferMove( b3BroadPhase* bp, int proxyKey )
|
||||
{
|
||||
b3BodyType proxyType = B3_PROXY_TYPE( proxyKey );
|
||||
int proxyId = B3_PROXY_ID( proxyKey );
|
||||
b3BitSet* set = &bp->movedProxies[proxyType];
|
||||
|
||||
if ( b3GetBit( set, proxyId ) )
|
||||
{
|
||||
b3ClearBit( set, proxyId );
|
||||
|
||||
// Purge from move buffer. Linear search.
|
||||
// todo if I can iterate the move set then I don't need the moveArray
|
||||
int count = bp->moveArray.count;
|
||||
for ( int i = 0; i < count; ++i )
|
||||
{
|
||||
if ( bp->moveArray.data[i] == proxyKey )
|
||||
{
|
||||
b3Array_RemoveSwap( bp->moveArray, i );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int b3BroadPhase_CreateProxy( b3BroadPhase* bp, b3BodyType proxyType, b3AABB aabb, uint64_t categoryBits, int shapeIndex,
|
||||
bool forcePairCreation )
|
||||
{
|
||||
B3_ASSERT( 0 <= proxyType && proxyType < b3_bodyTypeCount );
|
||||
int proxyId = b3DynamicTree_CreateProxy( bp->trees + proxyType, aabb, categoryBits, shapeIndex );
|
||||
int proxyKey = B3_PROXY_KEY( proxyId, proxyType );
|
||||
if ( proxyType != b3_staticBody || forcePairCreation )
|
||||
{
|
||||
b3BufferMove( bp, proxyKey );
|
||||
}
|
||||
return proxyKey;
|
||||
}
|
||||
|
||||
void b3BroadPhase_DestroyProxy( b3BroadPhase* bp, int proxyKey )
|
||||
{
|
||||
b3UnBufferMove( bp, proxyKey );
|
||||
|
||||
b3BodyType proxyType = B3_PROXY_TYPE( proxyKey );
|
||||
int proxyId = B3_PROXY_ID( proxyKey );
|
||||
|
||||
B3_ASSERT( 0 <= proxyType && proxyType <= b3_bodyTypeCount );
|
||||
b3DynamicTree_DestroyProxy( bp->trees + proxyType, proxyId );
|
||||
}
|
||||
|
||||
void b3BroadPhase_MoveProxy( b3BroadPhase* bp, int proxyKey, b3AABB aabb )
|
||||
{
|
||||
b3BodyType proxyType = B3_PROXY_TYPE( proxyKey );
|
||||
int proxyId = B3_PROXY_ID( proxyKey );
|
||||
|
||||
b3DynamicTree_MoveProxy( bp->trees + proxyType, proxyId, aabb );
|
||||
b3BufferMove( bp, proxyKey );
|
||||
}
|
||||
|
||||
void b3BroadPhase_EnlargeProxy( b3BroadPhase* bp, int proxyKey, b3AABB aabb )
|
||||
{
|
||||
B3_ASSERT( proxyKey != B3_NULL_INDEX );
|
||||
int typeIndex = B3_PROXY_TYPE( proxyKey );
|
||||
int proxyId = B3_PROXY_ID( proxyKey );
|
||||
|
||||
B3_ASSERT( typeIndex != b3_staticBody );
|
||||
|
||||
b3DynamicTree_EnlargeProxy( bp->trees + typeIndex, proxyId, aabb );
|
||||
b3BufferMove( bp, proxyKey );
|
||||
}
|
||||
|
||||
typedef struct b3MovePair
|
||||
{
|
||||
int shapeIndexA;
|
||||
int shapeIndexB;
|
||||
int childIndex;
|
||||
b3MovePair* next;
|
||||
bool heap;
|
||||
} b3MovePair;
|
||||
|
||||
typedef struct b3MoveResult
|
||||
{
|
||||
b3MovePair* pairList;
|
||||
} b3MoveResult;
|
||||
|
||||
typedef struct b3QueryPairContext
|
||||
{
|
||||
b3World* world;
|
||||
b3MoveResult* moveResult;
|
||||
b3AABB aabb;
|
||||
b3BodyType queryTreeType;
|
||||
int queryProxyKey;
|
||||
int queryShapeIndex;
|
||||
|
||||
int compoundProxyId;
|
||||
int compoundShapeIndex;
|
||||
} b3QueryPairContext;
|
||||
|
||||
// This is called from b3DynamicTree::Query when we are gathering pairs.
|
||||
static bool b3PairQueryCallback( int proxyId, uint64_t userData, void* context )
|
||||
{
|
||||
b3QueryPairContext* queryContext = (b3QueryPairContext*)context;
|
||||
b3World* world = queryContext->world;
|
||||
int shapeIndex;
|
||||
int childIndex = 0;
|
||||
|
||||
if ( queryContext->compoundShapeIndex == B3_NULL_INDEX )
|
||||
{
|
||||
// Outer query: userData is a shape index.
|
||||
shapeIndex = (int)userData;
|
||||
|
||||
// A proxy cannot form a pair with itself.
|
||||
if ( shapeIndex == queryContext->queryShapeIndex )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
b3Shape* shape = b3Array_Get( world->shapes, shapeIndex );
|
||||
if ( shape->type == b3_compoundShape )
|
||||
{
|
||||
// Query bounds are float world space, so the demoted transform is the matching float frame
|
||||
b3Transform compoundTransform = b3ToRelativeTransform( b3GetBodyTransform( world, shape->bodyId ), b3Pos_zero );
|
||||
b3AABB localAABB = b3AABB_Transform( b3InvertTransform( compoundTransform ), queryContext->aabb );
|
||||
|
||||
// recurse
|
||||
queryContext->compoundShapeIndex = shapeIndex;
|
||||
queryContext->compoundProxyId = proxyId;
|
||||
|
||||
b3DynamicTree_Query( &shape->compound->tree, localAABB, B3_DEFAULT_MASK_BITS, false, b3PairQueryCallback, context );
|
||||
queryContext->compoundShapeIndex = B3_NULL_INDEX;
|
||||
queryContext->compoundProxyId = B3_NULL_INDEX;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inner query into a compound shape: userData is the compound child index, not a shape
|
||||
// index, so do not compare it against queryShapeIndex.
|
||||
shapeIndex = queryContext->compoundShapeIndex;
|
||||
proxyId = queryContext->compoundProxyId;
|
||||
childIndex = (int)userData;
|
||||
}
|
||||
|
||||
b3BroadPhase* broadPhase = &queryContext->world->broadPhase;
|
||||
|
||||
int proxyKey = B3_PROXY_KEY( proxyId, queryContext->queryTreeType );
|
||||
int queryProxyKey = queryContext->queryProxyKey;
|
||||
|
||||
// A proxy cannot form a pair with itself.
|
||||
B3_ASSERT( proxyKey != queryContext->queryProxyKey );
|
||||
|
||||
b3BodyType treeType = queryContext->queryTreeType;
|
||||
b3BodyType queryProxyType = B3_PROXY_TYPE( queryProxyKey );
|
||||
|
||||
// De-duplication
|
||||
// It is important to prevent duplicate contacts from being created. Ideally I can prevent duplicates
|
||||
// early and in the worker. Most of the time the movedProxies bit sets contain dynamic and kinematic
|
||||
// proxies, but sometimes static proxies are in there too (b3ShapeDef::invokeContactCreation or a
|
||||
// modified static shape), so we always have to check.
|
||||
|
||||
// Is this proxy also moving?
|
||||
if ( queryProxyType == b3_dynamicBody )
|
||||
{
|
||||
if ( treeType == b3_dynamicBody && proxyKey < queryProxyKey )
|
||||
{
|
||||
bool moved = b3GetBit( &broadPhase->movedProxies[treeType], proxyId );
|
||||
if ( moved )
|
||||
{
|
||||
// Both proxies are moving. Avoid duplicate pairs.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
B3_ASSERT( treeType == b3_dynamicBody );
|
||||
bool moved = b3GetBit( &broadPhase->movedProxies[treeType], proxyId );
|
||||
if ( moved )
|
||||
{
|
||||
// Both proxies are moving. Avoid duplicate pairs.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t pairKey = b3ShapePairKey( shapeIndex, queryContext->queryShapeIndex, childIndex );
|
||||
if ( b3ContainsKey( &broadPhase->pairSet, pairKey ) )
|
||||
{
|
||||
// contact exists
|
||||
return true;
|
||||
}
|
||||
|
||||
// Order shapes so that B3_SHAPE_PAIR_KEY works correctly
|
||||
int shapeIdA = shapeIndex;
|
||||
int shapeIdB = queryContext->queryShapeIndex;
|
||||
b3Shape* shapeA = b3Array_Get( world->shapes, shapeIdA );
|
||||
b3Shape* shapeB = b3Array_Get( world->shapes, shapeIdB );
|
||||
int bodyIdA = shapeA->bodyId;
|
||||
int bodyIdB = shapeB->bodyId;
|
||||
|
||||
// Are the shapes on the same body?
|
||||
if ( bodyIdA == bodyIdB )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sensors are handled elsewhere
|
||||
if ( shapeA->sensorIndex != B3_NULL_INDEX || shapeB->sensorIndex != B3_NULL_INDEX )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( b3ShouldShapesCollide( shapeA->filter, shapeB->filter ) == false )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Does a joint override collision?
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB );
|
||||
if ( b3ShouldBodiesCollide( world, bodyA, bodyB ) == false )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Custom user filter
|
||||
if ( ( shapeA->flags & b3_enableCustomFiltering ) || ( shapeB->flags & b3_enableCustomFiltering ) )
|
||||
{
|
||||
b3CustomFilterFcn* customFilterFcn = queryContext->world->customFilterFcn;
|
||||
if ( customFilterFcn != NULL )
|
||||
{
|
||||
b3ShapeId idA = { shapeIdA + 1, world->worldId, shapeA->generation };
|
||||
b3ShapeId idB = { shapeIdB + 1, world->worldId, shapeB->generation };
|
||||
bool shouldCollide = customFilterFcn( idA, idB, queryContext->world->customFilterContext );
|
||||
if ( shouldCollide == false )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo per thread to eliminate atomic?
|
||||
int pairIndex = b3AtomicFetchAddInt( &broadPhase->movePairIndex, 1 );
|
||||
|
||||
b3MovePair* pair;
|
||||
if ( pairIndex < broadPhase->movePairCapacity )
|
||||
{
|
||||
pair = broadPhase->movePairs + pairIndex;
|
||||
pair->heap = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo experimenting with ignoring this pair if we ran out of space
|
||||
return true;
|
||||
// pair = (b3MovePair*)b3Alloc( sizeof( b3MovePair ) );
|
||||
// pair->heap = true;
|
||||
}
|
||||
|
||||
pair->shapeIndexA = shapeIdA;
|
||||
pair->shapeIndexB = shapeIdB;
|
||||
pair->childIndex = childIndex;
|
||||
pair->next = queryContext->moveResult->pairList;
|
||||
queryContext->moveResult->pairList = pair;
|
||||
|
||||
// continue the query
|
||||
return true;
|
||||
}
|
||||
|
||||
static void b3FindPairsTask( int startIndex, int endIndex, int workerIndex, void* context )
|
||||
{
|
||||
b3TracyCZoneNC( pair_task, "Pair Task", b3_colorAquamarine, true );
|
||||
|
||||
B3_UNUSED( workerIndex );
|
||||
|
||||
b3World* world = (b3World*)context;
|
||||
b3BroadPhase* bp = &world->broadPhase;
|
||||
|
||||
b3QueryPairContext queryContext = { 0 };
|
||||
queryContext.world = world;
|
||||
queryContext.compoundShapeIndex = B3_NULL_INDEX;
|
||||
|
||||
for ( int i = startIndex; i < endIndex; ++i )
|
||||
{
|
||||
// Initialize move result for this moved proxy
|
||||
queryContext.moveResult = bp->moveResults + i;
|
||||
queryContext.moveResult->pairList = NULL;
|
||||
|
||||
int proxyKey = bp->moveArray.data[i];
|
||||
b3BodyType proxyType = B3_PROXY_TYPE( proxyKey );
|
||||
|
||||
int proxyId = B3_PROXY_ID( proxyKey );
|
||||
queryContext.queryProxyKey = proxyKey;
|
||||
|
||||
const b3DynamicTree* baseTree = bp->trees + proxyType;
|
||||
|
||||
// We have to query the tree with the fat AABB so that
|
||||
// we don't fail to create a contact that may touch later.
|
||||
b3AABB fatAABB = b3DynamicTree_GetAABB( baseTree, proxyId );
|
||||
queryContext.queryShapeIndex = (int)b3DynamicTree_GetUserData( baseTree, proxyId );
|
||||
queryContext.aabb = fatAABB;
|
||||
|
||||
// Compound shape collision invocation is not supported
|
||||
B3_VALIDATE( world->shapes.data[queryContext.queryShapeIndex].type != b3_compoundShape );
|
||||
|
||||
// Query trees. Only dynamic proxies collide with kinematic and static proxies.
|
||||
// Using B3_DEFAULT_MASK_BITS so that b3Filter::groupIndex works.
|
||||
// consider using bits = groupIndex > 0 ? B3_DEFAULT_MASK_BITS : maskBits
|
||||
bool requireAllBits = false;
|
||||
if ( proxyType == b3_dynamicBody )
|
||||
{
|
||||
queryContext.queryTreeType = b3_kinematicBody;
|
||||
b3DynamicTree_Query( bp->trees + b3_kinematicBody, fatAABB, B3_DEFAULT_MASK_BITS, requireAllBits, b3PairQueryCallback,
|
||||
&queryContext );
|
||||
|
||||
queryContext.queryTreeType = b3_staticBody;
|
||||
b3DynamicTree_Query( bp->trees + b3_staticBody, fatAABB, B3_DEFAULT_MASK_BITS, requireAllBits, b3PairQueryCallback,
|
||||
&queryContext );
|
||||
}
|
||||
|
||||
// All proxies collide with dynamic proxies
|
||||
// Using B3_DEFAULT_MASK_BITS so that b3Filter::groupIndex works.
|
||||
queryContext.queryTreeType = b3_dynamicBody;
|
||||
b3DynamicTree_Query( bp->trees + b3_dynamicBody, fatAABB, B3_DEFAULT_MASK_BITS, requireAllBits, b3PairQueryCallback,
|
||||
&queryContext );
|
||||
}
|
||||
|
||||
b3TracyCZoneEnd( pair_task );
|
||||
}
|
||||
|
||||
static void b3UpdateTreesTask( void* context )
|
||||
{
|
||||
b3TracyCZoneNC( tree_task, "Rebuild Trees", b3_colorFireBrick, true );
|
||||
|
||||
b3World* world = (b3World*)context;
|
||||
b3DynamicTree_Rebuild( world->broadPhase.trees + b3_dynamicBody, false );
|
||||
b3DynamicTree_Rebuild( world->broadPhase.trees + b3_kinematicBody, false );
|
||||
|
||||
b3TracyCZoneEnd( tree_task );
|
||||
}
|
||||
|
||||
void b3UpdateBroadPhasePairs( b3World* world )
|
||||
{
|
||||
b3BroadPhase* bp = &world->broadPhase;
|
||||
|
||||
int moveCount = bp->moveArray.count;
|
||||
|
||||
if ( moveCount == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
b3TracyCZoneNC( update_pairs, "Pairs", b3_colorMediumSlateBlue, true );
|
||||
|
||||
b3Stack* alloc = &world->stack;
|
||||
|
||||
// todo these could be in the step context
|
||||
bp->moveResults = (b3MoveResult*)b3StackAlloc( alloc, moveCount * sizeof( b3MoveResult ), "move results" );
|
||||
bp->movePairCapacity = 16 * moveCount;
|
||||
bp->movePairs = (b3MovePair*)b3StackAlloc( alloc, bp->movePairCapacity * sizeof( b3MovePair ), "move pairs" );
|
||||
|
||||
b3AtomicStoreInt( &bp->movePairIndex, 0 );
|
||||
|
||||
#ifndef NDEBUG
|
||||
extern b3AtomicInt b3_probeCount;
|
||||
b3AtomicStoreInt( &b3_probeCount, 0 );
|
||||
#endif
|
||||
|
||||
int minRange = 64;
|
||||
b3ParallelFor( world, b3FindPairsTask, moveCount, minRange, world, "pairs" );
|
||||
|
||||
b3TracyCZoneNC( create_contacts, "Create Contacts", b3_colorCoral, true );
|
||||
|
||||
// Task that can be done in parallel with the narrow-phase
|
||||
// - rebuild the collision tree for dynamic and kinematic bodies to keep their query performance good
|
||||
if ( world->taskCount < B3_MAX_TASKS )
|
||||
{
|
||||
world->userTreeTask = world->enqueueTaskFcn( &b3UpdateTreesTask, world, world->userTaskContext, "rebuild tree" );
|
||||
world->taskCount += 1;
|
||||
world->activeTaskCount += world->userTreeTask == NULL ? 0 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
world->userTreeTask = NULL;
|
||||
b3UpdateTreesTask( world );
|
||||
}
|
||||
|
||||
// Single-threaded work
|
||||
// - Clear move flags
|
||||
// - Create contacts in deterministic order
|
||||
for ( int i = 0; i < moveCount; ++i )
|
||||
{
|
||||
b3MoveResult* result = bp->moveResults + i;
|
||||
b3MovePair* pair = result->pairList;
|
||||
while ( pair != NULL )
|
||||
{
|
||||
int shapeIdA = pair->shapeIndexA;
|
||||
int shapeIdB = pair->shapeIndexB;
|
||||
int childIndex = pair->childIndex;
|
||||
|
||||
b3Shape* shapeA = b3Array_Get( world->shapes, shapeIdA );
|
||||
b3Shape* shapeB = b3Array_Get( world->shapes, shapeIdB );
|
||||
|
||||
b3CreateContact( world, shapeA, shapeB, childIndex );
|
||||
|
||||
if ( pair->heap )
|
||||
{
|
||||
b3MovePair* temp = pair;
|
||||
pair = pair->next;
|
||||
b3Free( temp, sizeof( b3MovePair ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
pair = pair->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset move buffer: clear only the bits that were set this step.
|
||||
// Invariant: bit set in movedProxies[type] iff proxyKey is present in moveArray.
|
||||
for ( int i = 0; i < bp->moveArray.count; ++i )
|
||||
{
|
||||
int proxyKey = bp->moveArray.data[i];
|
||||
b3ClearBit( &bp->movedProxies[B3_PROXY_TYPE( proxyKey )], B3_PROXY_ID( proxyKey ) );
|
||||
}
|
||||
b3Array_Clear( bp->moveArray );
|
||||
|
||||
b3StackFree( alloc, bp->movePairs );
|
||||
bp->movePairs = NULL;
|
||||
b3StackFree( alloc, bp->moveResults );
|
||||
bp->moveResults = NULL;
|
||||
|
||||
b3ValidateSolverSets( world );
|
||||
|
||||
b3TracyCZoneEnd( create_contacts );
|
||||
|
||||
b3TracyCZoneEnd( update_pairs );
|
||||
}
|
||||
|
||||
bool b3BroadPhase_TestOverlap( const b3BroadPhase* bp, int proxyKeyA, int proxyKeyB )
|
||||
{
|
||||
int typeIndexA = B3_PROXY_TYPE( proxyKeyA );
|
||||
int proxyIdA = B3_PROXY_ID( proxyKeyA );
|
||||
int typeIndexB = B3_PROXY_TYPE( proxyKeyB );
|
||||
int proxyIdB = B3_PROXY_ID( proxyKeyB );
|
||||
|
||||
b3AABB aabbA = b3DynamicTree_GetAABB( bp->trees + typeIndexA, proxyIdA );
|
||||
b3AABB aabbB = b3DynamicTree_GetAABB( bp->trees + typeIndexB, proxyIdB );
|
||||
return b3AABB_Overlaps( aabbA, aabbB );
|
||||
}
|
||||
|
||||
int b3BroadPhase_GetShapeIndex( b3BroadPhase* bp, int proxyKey )
|
||||
{
|
||||
int typeIndex = B3_PROXY_TYPE( proxyKey );
|
||||
int proxyId = B3_PROXY_ID( proxyKey );
|
||||
|
||||
return (int)b3DynamicTree_GetUserData( bp->trees + typeIndex, proxyId );
|
||||
}
|
||||
|
||||
void b3ValidateBroadPhase( const b3BroadPhase* bp )
|
||||
{
|
||||
b3DynamicTree_Validate( bp->trees + b3_dynamicBody );
|
||||
b3DynamicTree_Validate( bp->trees + b3_kinematicBody );
|
||||
|
||||
// todo validate every shape AABB is contained in tree AABB
|
||||
}
|
||||
|
||||
void b3ValidateNoEnlarged( const b3BroadPhase* bp )
|
||||
{
|
||||
#if B3_ENABLE_VALIDATION == 1
|
||||
for ( int j = 0; j < b3_bodyTypeCount; ++j )
|
||||
{
|
||||
const b3DynamicTree* tree = bp->trees + j;
|
||||
b3DynamicTree_ValidateNoEnlarged( tree );
|
||||
}
|
||||
#else
|
||||
B3_UNUSED( bp );
|
||||
#endif
|
||||
}
|
||||
79
vendor/box3d/src/src/broad_phase.h
vendored
Normal file
79
vendor/box3d/src/src/broad_phase.h
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "bitset.h"
|
||||
#include "container.h"
|
||||
#include "table.h"
|
||||
|
||||
#include "box3d/collision.h"
|
||||
#include "box3d/types.h"
|
||||
|
||||
typedef struct b3Shape b3Shape;
|
||||
typedef struct b3MovePair b3MovePair;
|
||||
typedef struct b3MoveResult b3MoveResult;
|
||||
typedef struct b3Stack b3Stack;
|
||||
typedef struct b3World b3World;
|
||||
|
||||
// Store the proxy type in the lower 2 bits of the proxy key. This leaves 30 bits for the id.
|
||||
#define B3_PROXY_TYPE( KEY ) ( (b3BodyType)( ( KEY ) & 3 ) )
|
||||
#define B3_PROXY_ID( KEY ) ( ( KEY ) >> 2 )
|
||||
#define B3_PROXY_KEY( ID, TYPE ) ( ( ( ID ) << 2 ) | ( TYPE ) )
|
||||
|
||||
/// The broad-phase is used for computing pairs and performing volume queries and ray casts.
|
||||
/// This broad-phase does not persist pairs. Instead, this reports potentially new pairs.
|
||||
/// It is up to the client to consume the new pairs and to track subsequent overlap.
|
||||
typedef struct b3BroadPhase
|
||||
{
|
||||
b3DynamicTree trees[b3_bodyTypeCount];
|
||||
|
||||
// Per body-type bit sets indexed by proxyId, marking proxies moved this step.
|
||||
// Paired with moveArray which preserves deterministic insertion order for pair queries.
|
||||
b3BitSet movedProxies[b3_bodyTypeCount];
|
||||
b3Array( int ) moveArray;
|
||||
|
||||
// These are the results from the pair query and are used to create new contacts
|
||||
// in deterministic order.
|
||||
// todo these could be in the step context
|
||||
b3MoveResult* moveResults;
|
||||
b3MovePair* movePairs;
|
||||
int movePairCapacity;
|
||||
b3AtomicInt movePairIndex;
|
||||
|
||||
// Tracks shape pairs that have a b3Contact
|
||||
// todo pairSet can grow quite large on the first time step and remain large
|
||||
b3HashSet pairSet;
|
||||
} b3BroadPhase;
|
||||
|
||||
void b3CreateBroadPhase( b3BroadPhase* bp, const b3Capacity* capacity );
|
||||
void b3DestroyBroadPhase( b3BroadPhase* bp );
|
||||
|
||||
int b3BroadPhase_CreateProxy( b3BroadPhase* bp, b3BodyType proxyType, b3AABB aabb, uint64_t categoryBits, int shapeIndex,
|
||||
bool forcePairCreation );
|
||||
void b3BroadPhase_DestroyProxy( b3BroadPhase* bp, int proxyKey );
|
||||
|
||||
void b3BroadPhase_MoveProxy( b3BroadPhase* bp, int proxyKey, b3AABB aabb );
|
||||
void b3BroadPhase_EnlargeProxy( b3BroadPhase* bp, int proxyKey, b3AABB aabb );
|
||||
|
||||
int b3BroadPhase_GetShapeIndex( b3BroadPhase* bp, int proxyKey );
|
||||
|
||||
void b3UpdateBroadPhasePairs( b3World* world );
|
||||
bool b3BroadPhase_TestOverlap( const b3BroadPhase* bp, int proxyKeyA, int proxyKeyB );
|
||||
|
||||
void b3ValidateBroadPhase( const b3BroadPhase* bp );
|
||||
void b3ValidateNoEnlarged( const b3BroadPhase* bp );
|
||||
|
||||
// This is what triggers new contact pairs to be created
|
||||
// Warning: this must be called in deterministic order
|
||||
static inline void b3BufferMove( b3BroadPhase* bp, int queryProxy )
|
||||
{
|
||||
b3BodyType proxyType = B3_PROXY_TYPE( queryProxy );
|
||||
int proxyId = B3_PROXY_ID( queryProxy );
|
||||
b3BitSet* set = &bp->movedProxies[proxyType];
|
||||
if ( b3GetBit( set, proxyId ) == false )
|
||||
{
|
||||
b3SetBitGrow( set, proxyId );
|
||||
b3Array_Push( bp->moveArray, queryProxy );
|
||||
}
|
||||
}
|
||||
385
vendor/box3d/src/src/capsule.c
vendored
Normal file
385
vendor/box3d/src/src/capsule.c
vendored
Normal file
@@ -0,0 +1,385 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "math_internal.h"
|
||||
#include "shape.h"
|
||||
|
||||
#include "box3d/base.h"
|
||||
#include "box3d/collision.h"
|
||||
#include "box3d/constants.h"
|
||||
|
||||
b3MassData b3ComputeCapsuleMass( const b3Capsule* shape, float density )
|
||||
{
|
||||
b3Vec3 c1 = shape->center1;
|
||||
b3Vec3 c2 = shape->center2;
|
||||
float r = shape->radius;
|
||||
|
||||
// Cylinder
|
||||
float cylinderHeight = b3Distance( c1, c2 );
|
||||
float cylinderVolume = B3_PI * r * r * cylinderHeight;
|
||||
float cylinderMass = cylinderVolume * density;
|
||||
|
||||
// Sphere
|
||||
float sphereVolume = ( 4.0f / 3.0f ) * B3_PI * r * r * r;
|
||||
float sphereMass = sphereVolume * density;
|
||||
|
||||
// Local accumulated inertia
|
||||
b3Matrix3 inertia = b3AddMM( b3CylinderInertia( cylinderMass, r, cylinderHeight ), b3SphereInertia( sphereMass, r ) );
|
||||
|
||||
float steiner = 0.125f * sphereMass * ( 3.0f * r + 2.0f * cylinderHeight ) * cylinderHeight;
|
||||
inertia.cx.x += steiner;
|
||||
inertia.cz.z += steiner;
|
||||
|
||||
// Align capsule axis with chosen up-axis
|
||||
b3Matrix3 rotation = b3Mat3_identity;
|
||||
if ( cylinderHeight * cylinderHeight > 1000.0f * FLT_MIN )
|
||||
{
|
||||
b3Vec3 direction = b3Normalize( b3Sub( c2, c1 ) );
|
||||
b3Quat q = b3ComputeQuatBetweenUnitVectors( b3Vec3_axisY, direction );
|
||||
rotation = b3MakeMatrixFromQuat( q );
|
||||
}
|
||||
|
||||
float mass = sphereMass + cylinderMass;
|
||||
b3Vec3 center = b3MulSV( 0.5f, b3Add( c1, c2 ) );
|
||||
|
||||
b3MassData out;
|
||||
out.mass = mass;
|
||||
out.center = center;
|
||||
|
||||
// Rotate the central inertia into the shape frame
|
||||
out.inertia = b3MulMM( rotation, b3MulMM( inertia, b3Transpose( rotation ) ) );
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
b3AABB b3ComputeCapsuleAABB( const b3Capsule* shape, b3Transform transform )
|
||||
{
|
||||
float r = shape->radius;
|
||||
|
||||
b3Vec3 center1 = b3TransformPoint( transform, shape->center1 );
|
||||
b3Vec3 center2 = b3TransformPoint( transform, shape->center2 );
|
||||
b3Vec3 extent = { r, r, r };
|
||||
|
||||
b3AABB aabb;
|
||||
aabb.lowerBound = b3Sub( b3Min( center1, center2 ), extent );
|
||||
aabb.upperBound = b3Add( b3Max( center1, center2 ), extent );
|
||||
return aabb;
|
||||
}
|
||||
|
||||
b3AABB b3ComputeSweptCapsuleAABB( const b3Capsule* shape, b3Transform xf1, b3Transform xf2 )
|
||||
{
|
||||
b3Vec3 r = { shape->radius, shape->radius, shape->radius };
|
||||
b3Vec3 a = b3TransformPoint( xf1, shape->center1 );
|
||||
b3Vec3 b = b3TransformPoint( xf1, shape->center2 );
|
||||
b3Vec3 c = b3TransformPoint( xf2, shape->center1 );
|
||||
b3Vec3 d = b3TransformPoint( xf2, shape->center2 );
|
||||
|
||||
b3AABB aabb = {
|
||||
.lowerBound = b3Sub( b3Min( b3Min( a, b ), b3Min( c, d ) ), r ),
|
||||
.upperBound = b3Add( b3Max( b3Max( a, b ), b3Max( c, d ) ), r ),
|
||||
};
|
||||
return aabb;
|
||||
}
|
||||
|
||||
bool b3OverlapCapsule( const b3Capsule* shape, b3Transform shapeTransform, const b3ShapeProxy* proxy )
|
||||
{
|
||||
b3DistanceInput input;
|
||||
input.proxyA = (b3ShapeProxy){ &shape->center1, 2, shape->radius };
|
||||
input.proxyB = *proxy;
|
||||
input.transform = b3InvMulTransforms( shapeTransform, b3Transform_identity );
|
||||
input.useRadii = true;
|
||||
|
||||
b3SimplexCache cache = { 0 };
|
||||
b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 );
|
||||
return output.distance < B3_OVERLAP_SLOP;
|
||||
}
|
||||
|
||||
// Precision Improvements for Ray / Sphere Intersection - Ray Tracing Gems 2019
|
||||
// http://www.codercorner.com/blog/?p=321
|
||||
b3CastOutput b3RayCastCapsule( const b3Capsule* shape, const b3RayCastInput* input )
|
||||
{
|
||||
B3_ASSERT( b3IsValidRay( input ) );
|
||||
|
||||
b3Vec3 c1 = shape->center1;
|
||||
b3Vec3 c2 = shape->center2;
|
||||
float r = shape->radius;
|
||||
|
||||
// Initialize result structure
|
||||
b3CastOutput output = { 0 };
|
||||
|
||||
b3Vec3 d = b3Sub( c2, c1 );
|
||||
|
||||
// Fall back to sphere if the capsule is short
|
||||
float tol = 0.01f * B3_LINEAR_SLOP;
|
||||
float lengthSquared = b3LengthSquared( d );
|
||||
if ( lengthSquared < tol * tol )
|
||||
{
|
||||
b3Vec3 sphereCenter = b3MulSV( 0.5f, b3Add( shape->center1, shape->center2 ) );
|
||||
b3Sphere sphere = { sphereCenter, shape->radius };
|
||||
return b3RayCastSphere( &sphere, input );
|
||||
}
|
||||
|
||||
// Vector from first center to ray origin.
|
||||
b3Vec3 s = b3Sub( input->origin, c1 );
|
||||
|
||||
// Capsule axis
|
||||
float length = sqrtf( lengthSquared );
|
||||
b3Vec3 axis = b3MulSV( 1.0f / length, d );
|
||||
|
||||
// Project ray origin onto capsule axis.
|
||||
float u = b3Dot( s, axis );
|
||||
|
||||
// Closest point on infinite capsule axis, relative to c1.
|
||||
b3Vec3 c = b3MulSV( u, axis );
|
||||
|
||||
// Vector from closest point to ray origin
|
||||
b3Vec3 sc = b3Sub( s, c );
|
||||
|
||||
// Squared distance from ray origin to capsule axis
|
||||
float sc2 = b3LengthSquared( sc );
|
||||
|
||||
// Is the ray origin within the infinite cylinder along the capsule axis?
|
||||
if ( sc2 < r * r )
|
||||
{
|
||||
// Clamped barycentric coordinate of ray origin projected onto capsule axis.
|
||||
float uClamped = b3ClampFloat( u, 0.0f, length );
|
||||
|
||||
// The closest point on the bounded capsule segment, relative to c1.
|
||||
b3Vec3 cp = b3MulSV( uClamped, axis );
|
||||
|
||||
// Vector from ray origin to closest point on segment.
|
||||
b3Vec3 scp = b3Sub( s, cp );
|
||||
|
||||
// Squared distance of ray origin from capsule segment.
|
||||
float scp2 = b3LengthSquared( scp );
|
||||
|
||||
// Is the ray origin within the capsule?
|
||||
if ( scp2 < r * r )
|
||||
{
|
||||
output.hit = true;
|
||||
output.point = input->origin;
|
||||
return output;
|
||||
}
|
||||
|
||||
// The ray can hit an endcap.
|
||||
b3Sphere sphere = {
|
||||
.center = b3Add( c1, cp ),
|
||||
.radius = r,
|
||||
};
|
||||
|
||||
return b3RayCastSphere( &sphere, input );
|
||||
}
|
||||
|
||||
// Ray axis. A zero length ray reaching here starts outside the capsule, so it misses.
|
||||
// Same zero length convention as b3RayCastSphere.
|
||||
b3Vec3 dr = input->translation;
|
||||
float rayLength;
|
||||
b3Vec3 rayAxis = b3GetLengthAndNormalize( &rayLength, dr );
|
||||
if ( rayLength == 0.0f )
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
// Barycentric coordinate of ray end point.
|
||||
float v = u + input->maxFraction * b3Dot( dr, axis );
|
||||
|
||||
// Early out: does the projected ray fall outside the capsule?
|
||||
if ( ( u < -r && v < -r ) || ( length + r < u && length + r < v ) )
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
// Compute the closest point between the ray segment and the capsule segment.
|
||||
// See Real-Time Collision Detection, section 5.1.9
|
||||
|
||||
// Closest point on capsule : a1 = segment unit axis, t1 = unknown fraction
|
||||
// p1 = t1 * a1
|
||||
|
||||
// Closet point on ray : a2 = ray unit axis, t2 = unknown fraction
|
||||
// p2 = s + t2 * a2
|
||||
|
||||
// Closest point perpendicularity conditions.
|
||||
// dot(p2 - p1, a1) = 0
|
||||
// dot(p2 - p1, a2) = 0
|
||||
|
||||
// Expand
|
||||
// dot(t1 * a1 - s - t2 * a2, a1) = 0
|
||||
// dot(t1 * a1 - s - t2 * a2, a2) = 0
|
||||
|
||||
// Expand
|
||||
// t1 - dot(s, a1) - t2 * dot(a1, a2) = 0
|
||||
// t1 * dot(a1, a2) - dot(s, a2) - t2 = 0
|
||||
|
||||
// Group : a12 = dot(a1, a2), sa1 = dot(s, a1), sa2 = dot(s, a2)
|
||||
// t1 - a12 * t2 = sa1
|
||||
// a12 * t1 - t2 = sa2
|
||||
|
||||
// Solve
|
||||
// https://en.wikipedia.org/wiki/Cramer%27s_rule
|
||||
// I've flipped the signs of the numerator and denominator to give a positive determinant.
|
||||
// det = 1 - a12 * a12
|
||||
// t1 = (sa1 - a12 * sa2) / det
|
||||
// t2 = (a12 * sa1 - sa2) / det
|
||||
|
||||
b3Vec3 a1 = axis;
|
||||
b3Vec3 a2 = rayAxis;
|
||||
float a12 = b3Dot( a1, a2 );
|
||||
|
||||
// Ray distance to the near intersection with the infinite cylinder. Length units.
|
||||
float tr;
|
||||
|
||||
float det = 1.0f - a12 * a12;
|
||||
if ( det < FLT_EPSILON )
|
||||
{
|
||||
// Solve the 2D problem of ray versus circle starting at the ray origin, where the circle is
|
||||
// the axial view of the infinite capsule cylinder. This works well when the ray origin is
|
||||
// not too far from the capsule axis.
|
||||
|
||||
// Instead of a cross product, subtract the parallel part to get a perpendicular vector. Non-dimensional.
|
||||
b3Vec3 perp = b3MulSub( a2, a12, a1 );
|
||||
float perp2 = b3LengthSquared( perp );
|
||||
|
||||
// Project to origin to infinite capsule axis vector onto the perpendicular vector. beta has length units.
|
||||
float beta = b3Dot( sc, perp );
|
||||
|
||||
// Setup quadratic root finder.
|
||||
float gamma = sc2 - r * r;
|
||||
|
||||
// Discriminant
|
||||
float disc = beta * beta - perp2 * gamma;
|
||||
|
||||
// Casting away from the axis, or the perpendicular gap never closes to the radius.
|
||||
if ( beta >= 0.0f || disc < 0.0f )
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
// Quadratic near root. Expressed in an alternate form to avoid the (-beta - sqrt) cancellation as
|
||||
// the ray nears parallel.
|
||||
tr = gamma / ( -beta + sqrtf( disc ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ray and capsules axes are not parallel.
|
||||
|
||||
// Closest points between the infinite ray and the infinite capsule axis.
|
||||
float invDet = 1.0f / det;
|
||||
float sa1 = u;
|
||||
float sa2 = b3Dot( s, a2 );
|
||||
|
||||
float t1 = ( sa1 - a12 * sa2 ) * invDet;
|
||||
float t2 = ( a12 * sa1 - sa2 ) * invDet;
|
||||
|
||||
// Closest points
|
||||
b3Vec3 p1 = b3MulSV( t1, a1 );
|
||||
b3Vec3 p2 = b3MulAdd( s, t2, a2 );
|
||||
|
||||
// Vector from closest point on infinite capsule to infinite ray.
|
||||
b3Vec3 g = b3Sub( p2, p1 );
|
||||
|
||||
float g2 = b3LengthSquared( g );
|
||||
if ( g2 > r * r )
|
||||
{
|
||||
// Early out: closest point on infinite ray is outside infinite cylinder.
|
||||
return output;
|
||||
}
|
||||
|
||||
// Intersect the infinite ray with the infinite cylinder. Like ray versus sphere this is done
|
||||
// relative to the closest point to avoid round-off errors. Length units, not a fraction.
|
||||
// https://en.wikipedia.org/wiki/Line-cylinder_intersection
|
||||
float h = sqrtf( ( r * r - g2 ) * invDet );
|
||||
|
||||
tr = t2 - h;
|
||||
}
|
||||
|
||||
// Outside ray?
|
||||
if ( tr < 0.0f || input->maxFraction * rayLength < tr )
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
// The corresponding distance on the capsule axis. Length units.
|
||||
float tc = u + tr * a12;
|
||||
|
||||
// Outside c1 end?
|
||||
if ( tc < 0.0f )
|
||||
{
|
||||
// Ray cast sphere 1.
|
||||
b3Sphere sphere = {
|
||||
.center = c1,
|
||||
.radius = r,
|
||||
};
|
||||
|
||||
return b3RayCastSphere( &sphere, input );
|
||||
}
|
||||
|
||||
// Outside c2 end?
|
||||
if (length < tc)
|
||||
{
|
||||
// Ray cast sphere 2.
|
||||
b3Sphere sphere = {
|
||||
.center = c2,
|
||||
.radius = r,
|
||||
};
|
||||
|
||||
return b3RayCastSphere( &sphere, input );
|
||||
}
|
||||
|
||||
// Hit point on capsule side, relative to c1.
|
||||
b3Vec3 p = b3MulAdd( s, tr, rayAxis );
|
||||
|
||||
// Hit normal.
|
||||
b3Vec3 normal = b3MulSub( p, tc, axis );
|
||||
normal = b3Normalize( normal );
|
||||
|
||||
output.point = b3Add( c1, p );
|
||||
output.normal = normal;
|
||||
output.fraction = b3ClampFloat( tr / rayLength, 0.0f, input->maxFraction );
|
||||
output.hit = true;
|
||||
return output;
|
||||
}
|
||||
|
||||
b3CastOutput b3ShapeCastCapsule( const b3Capsule* capsule, const b3ShapeCastInput* input )
|
||||
{
|
||||
b3ShapeCastPairInput pairInput;
|
||||
pairInput.proxyA = (b3ShapeProxy){ &capsule->center1, 2, capsule->radius };
|
||||
pairInput.proxyB = input->proxy;
|
||||
pairInput.transform = b3Transform_identity;
|
||||
pairInput.translationB = input->translation;
|
||||
pairInput.maxFraction = input->maxFraction;
|
||||
pairInput.canEncroach = input->canEncroach;
|
||||
|
||||
b3CastOutput output = b3ShapeCast( &pairInput );
|
||||
return output;
|
||||
}
|
||||
|
||||
int b3CollideMoverAndCapsule( b3PlaneResult* result, const b3Capsule* shape, const b3Capsule* mover )
|
||||
{
|
||||
float totalRadius = mover->radius + shape->radius;
|
||||
|
||||
b3SegmentDistanceResult approach = b3SegmentDistance( shape->center1, shape->center2, mover->center1, mover->center2 );
|
||||
|
||||
// The normal points from the shape toward the mover.
|
||||
float distance;
|
||||
b3Vec3 normal = b3GetLengthAndNormalize( &distance, b3Sub( approach.point2, approach.point1 ) );
|
||||
|
||||
if ( distance > totalRadius )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
float linearSlop = B3_LINEAR_SLOP;
|
||||
if ( distance < linearSlop )
|
||||
{
|
||||
// Deep overlap: the core segments intersect. Pick an arbitrary direction perpendicular
|
||||
// the to capsule axis.
|
||||
float moverLength;
|
||||
b3Vec3 moverAxis = b3GetLengthAndNormalize( &moverLength, b3Sub( mover->center2, mover->center1 ) );
|
||||
normal = moverLength > linearSlop ? b3Perp( moverAxis ) : b3Vec3_axisY;
|
||||
distance = 0.0f;
|
||||
}
|
||||
|
||||
b3Plane plane = { normal, totalRadius - distance };
|
||||
*result = (b3PlaneResult){ plane, approach.point1 };
|
||||
return 1;
|
||||
}
|
||||
1196
vendor/box3d/src/src/compound.c
vendored
Normal file
1196
vendor/box3d/src/src/compound.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
14
vendor/box3d/src/src/compound.h
vendored
Normal file
14
vendor/box3d/src/src/compound.h
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "box3d/types.h"
|
||||
|
||||
b3TOIOutput b3CompoundTimeOfImpact( const b3CompoundData* compound, b3Transform transform, const b3ShapeProxy* proxy,
|
||||
const b3Sweep* sweep, float maxFraction );
|
||||
|
||||
// Transforms a sweep for a compound child shape
|
||||
b3Sweep b3MakeCompoundChildSweep( b3Transform compoundTransform, b3Transform childTransform );
|
||||
|
||||
int b3CollideMoverAndCompound( b3PlaneResult* planes, int capacity, const b3CompoundData* shape, const b3Capsule* mover );
|
||||
328
vendor/box3d/src/src/constraint_graph.c
vendored
Normal file
328
vendor/box3d/src/src/constraint_graph.c
vendored
Normal file
@@ -0,0 +1,328 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "constraint_graph.h"
|
||||
|
||||
#include "bitset.h"
|
||||
#include "body.h"
|
||||
#include "contact.h"
|
||||
#include "joint.h"
|
||||
#include "physics_world.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// Solver using graph coloring. Islands are only used for sleep.
|
||||
// High-Performance Physical Simulations on Next-Generation Architecture with Many Cores
|
||||
// http://web.eecs.umich.edu/~msmelyan/papers/physsim_onmanycore_itj.pdf
|
||||
|
||||
// Kinematic bodies have to be treated like dynamic bodies in graph coloring. Unlike static bodies, we cannot use a dummy solver
|
||||
// body for kinematic bodies. We cannot access a kinematic body from multiple threads efficiently because the SIMD solver body
|
||||
// scatter would write to the same kinematic body from multiple threads. Even if these writes don't modify the body, they will
|
||||
// cause horrible cache stalls. To make this feasible I would need a way to block these writes.
|
||||
|
||||
// This is used for debugging by making all constraints be assigned to overflow.
|
||||
#define B3_FORCE_OVERFLOW 0
|
||||
|
||||
static const b3HexColor b3_graphColors[B3_GRAPH_COLOR_COUNT] = {
|
||||
b3_colorRed, b3_colorOrange, b3_colorYellow, b3_colorLimeGreen, b3_colorSpringGreen,
|
||||
b3_colorAqua, b3_colorDodgerBlue, b3_colorBlueViolet, b3_colorMagenta, b3_colorDeepPink,
|
||||
b3_colorCrimson, b3_colorCoral, b3_colorGold, b3_colorGreenYellow, b3_colorMediumSeaGreen,
|
||||
b3_colorTurquoise, b3_colorDeepSkyBlue, b3_colorCornflowerBlue, b3_colorMediumSlateBlue, b3_colorMediumOrchid,
|
||||
b3_colorHotPink, b3_colorTomato, b3_colorKhaki, b3_colorSilver,
|
||||
};
|
||||
|
||||
b3HexColor b3GetGraphColor( int index )
|
||||
{
|
||||
B3_ASSERT( 0 <= index && index < B3_GRAPH_COLOR_COUNT );
|
||||
return b3_graphColors[index];
|
||||
}
|
||||
|
||||
void b3CreateGraph( b3ConstraintGraph* graph, int bodyCapacity )
|
||||
{
|
||||
_Static_assert( B3_GRAPH_COLOR_COUNT >= 2, "must have at least two constraint graph colors" );
|
||||
_Static_assert( B3_OVERFLOW_INDEX == B3_GRAPH_COLOR_COUNT - 1, "bad over flow index" );
|
||||
|
||||
*graph = (b3ConstraintGraph){ 0 };
|
||||
|
||||
bodyCapacity = b3MaxInt( bodyCapacity, 8 );
|
||||
|
||||
// Initialize graph color bit set.
|
||||
// No bitset for overflow color.
|
||||
for ( int i = 0; i < B3_OVERFLOW_INDEX; ++i )
|
||||
{
|
||||
b3GraphColor* color = graph->colors + i;
|
||||
color->bodySet = b3CreateBitSet( bodyCapacity );
|
||||
b3SetBitCountAndClear( &color->bodySet, bodyCapacity );
|
||||
}
|
||||
}
|
||||
|
||||
void b3DestroyGraph( b3ConstraintGraph* graph )
|
||||
{
|
||||
for ( int i = 0; i < B3_GRAPH_COLOR_COUNT; ++i )
|
||||
{
|
||||
b3GraphColor* color = graph->colors + i;
|
||||
|
||||
// The bit set should never be used on the overflow color
|
||||
B3_ASSERT( i != B3_OVERFLOW_INDEX || color->bodySet.bits == NULL );
|
||||
|
||||
b3DestroyBitSet( &color->bodySet );
|
||||
|
||||
b3Array_Destroy( color->convexContacts );
|
||||
b3Array_Destroy( color->contacts );
|
||||
b3Array_Destroy( color->jointSims );
|
||||
}
|
||||
}
|
||||
|
||||
// Contacts are always created as non-touching. They get cloned into the constraint
|
||||
// graph once they are found to be touching.
|
||||
void b3AddContactToGraph( b3World* world, b3Contact* contact )
|
||||
{
|
||||
B3_ASSERT( contact->manifoldCount > 0 );
|
||||
B3_ASSERT( contact->flags & b3_contactTouchingFlag );
|
||||
|
||||
b3ConstraintGraph* graph = &world->constraintGraph;
|
||||
int colorIndex = B3_OVERFLOW_INDEX;
|
||||
|
||||
int bodyIdA = contact->edges[0].bodyId;
|
||||
int bodyIdB = contact->edges[1].bodyId;
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB );
|
||||
|
||||
b3BodyType typeA = bodyA->type;
|
||||
b3BodyType typeB = bodyB->type;
|
||||
B3_ASSERT( typeA == b3_dynamicBody || typeB == b3_dynamicBody );
|
||||
|
||||
#if B3_FORCE_OVERFLOW == 0
|
||||
if ( typeA == b3_dynamicBody && typeB == b3_dynamicBody )
|
||||
{
|
||||
// Dynamic constraint colors cannot encroach on colors reserved for static constraints
|
||||
for ( int i = 0; i < B3_DYNAMIC_COLOR_COUNT; ++i )
|
||||
{
|
||||
b3GraphColor* color = graph->colors + i;
|
||||
if ( b3GetBit( &color->bodySet, bodyIdA ) || b3GetBit( &color->bodySet, bodyIdB ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
b3SetBitGrow( &color->bodySet, bodyIdA );
|
||||
b3SetBitGrow( &color->bodySet, bodyIdB );
|
||||
colorIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ( typeA == b3_dynamicBody )
|
||||
{
|
||||
// Static constraint colors build from the end to get higher priority than dyn-dyn constraints
|
||||
for ( int i = B3_OVERFLOW_INDEX - 1; i >= 1; --i )
|
||||
{
|
||||
b3GraphColor* color = graph->colors + i;
|
||||
if ( b3GetBit( &color->bodySet, bodyIdA ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
b3SetBitGrow( &color->bodySet, bodyIdA );
|
||||
colorIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ( typeB == b3_dynamicBody )
|
||||
{
|
||||
// Static constraint colors build from the end to get higher priority than dyn-dyn constraints
|
||||
for ( int i = B3_OVERFLOW_INDEX - 1; i >= 1; --i )
|
||||
{
|
||||
b3GraphColor* color = graph->colors + i;
|
||||
if ( b3GetBit( &color->bodySet, bodyIdB ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
b3SetBitGrow( &color->bodySet, bodyIdB );
|
||||
colorIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool isScalar = ( contact->flags & b3_simMeshContact ) || colorIndex == B3_OVERFLOW_INDEX;
|
||||
|
||||
b3GraphColor* color = graph->colors + colorIndex;
|
||||
contact->colorIndex = colorIndex;
|
||||
contact->localIndex = isScalar ? color->contacts.count : color->convexContacts.count;
|
||||
contact->bodySimIndexA = bodyA->type == b3_staticBody ? B3_NULL_INDEX : bodyA->localIndex;
|
||||
contact->bodySimIndexB = bodyB->type == b3_staticBody ? B3_NULL_INDEX : bodyB->localIndex;
|
||||
|
||||
if ( isScalar )
|
||||
{
|
||||
B3_ASSERT( contact->manifoldCount < UINT16_MAX );
|
||||
b3ContactSpec spec = {
|
||||
.contactId = contact->contactId,
|
||||
.manifoldStart = 0,
|
||||
.manifoldCount = (uint16_t)contact->manifoldCount,
|
||||
};
|
||||
b3Array_Push( color->contacts, spec );
|
||||
}
|
||||
else
|
||||
{
|
||||
b3Array_Push( color->convexContacts, contact->contactId );
|
||||
}
|
||||
}
|
||||
|
||||
void b3RemoveContactFromGraph( b3World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex, bool meshContact )
|
||||
{
|
||||
b3ConstraintGraph* graph = &world->constraintGraph;
|
||||
|
||||
B3_ASSERT( 0 <= colorIndex && colorIndex < B3_GRAPH_COLOR_COUNT );
|
||||
b3GraphColor* color = graph->colors + colorIndex;
|
||||
|
||||
if ( colorIndex != B3_OVERFLOW_INDEX )
|
||||
{
|
||||
// This might clear a bit for a static body, but this has no effect
|
||||
b3ClearBit( &color->bodySet, bodyIdA );
|
||||
b3ClearBit( &color->bodySet, bodyIdB );
|
||||
}
|
||||
|
||||
if ( meshContact || colorIndex == B3_OVERFLOW_INDEX )
|
||||
{
|
||||
int movedIndex = b3Array_RemoveSwap( color->contacts, localIndex );
|
||||
if ( movedIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// Fix index on swapped contact
|
||||
int movedContactId = color->contacts.data[localIndex].contactId;
|
||||
b3Contact* movedContact = b3Array_Get( world->contacts, movedContactId );
|
||||
B3_ASSERT( movedContact->setIndex == b3_awakeSet );
|
||||
B3_ASSERT( movedContact->colorIndex == colorIndex );
|
||||
B3_ASSERT( movedContact->localIndex == movedIndex );
|
||||
movedContact->localIndex = localIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int movedIndex = b3Array_RemoveSwap( color->convexContacts, localIndex );
|
||||
if ( movedIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// Fix index on swapped contact
|
||||
int movedContactId = color->convexContacts.data[localIndex];
|
||||
b3Contact* movedContact = b3Array_Get( world->contacts, movedContactId );
|
||||
B3_ASSERT( movedContact->setIndex == b3_awakeSet );
|
||||
B3_ASSERT( movedContact->colorIndex == colorIndex );
|
||||
B3_ASSERT( movedContact->localIndex == movedIndex );
|
||||
B3_ASSERT( ( movedContact->flags & b3_simMeshContact ) == 0 );
|
||||
movedContact->localIndex = localIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int b3AssignJointColor( b3ConstraintGraph* graph, int bodyIdA, int bodyIdB, b3BodyType typeA, b3BodyType typeB )
|
||||
{
|
||||
B3_ASSERT( typeA == b3_dynamicBody || typeB == b3_dynamicBody );
|
||||
B3_UNUSED( graph );
|
||||
B3_UNUSED( bodyIdA );
|
||||
B3_UNUSED( bodyIdB );
|
||||
B3_UNUSED( typeA );
|
||||
B3_UNUSED( typeB );
|
||||
|
||||
#if B3_FORCE_OVERFLOW == 0
|
||||
if ( typeA == b3_dynamicBody && typeB == b3_dynamicBody )
|
||||
{
|
||||
// Dynamic constraint colors cannot encroach on colors reserved for static constraints
|
||||
for ( int i = 0; i < B3_DYNAMIC_COLOR_COUNT; ++i )
|
||||
{
|
||||
b3GraphColor* color = graph->colors + i;
|
||||
if ( b3GetBit( &color->bodySet, bodyIdA ) || b3GetBit( &color->bodySet, bodyIdB ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
b3SetBitGrow( &color->bodySet, bodyIdA );
|
||||
b3SetBitGrow( &color->bodySet, bodyIdB );
|
||||
return i;
|
||||
}
|
||||
}
|
||||
else if ( typeA == b3_dynamicBody )
|
||||
{
|
||||
// Static constraint colors build from the end to get higher priority than dyn-dyn constraints
|
||||
for ( int i = B3_OVERFLOW_INDEX - 1; i >= 1; --i )
|
||||
{
|
||||
b3GraphColor* color = graph->colors + i;
|
||||
if ( b3GetBit( &color->bodySet, bodyIdA ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
b3SetBitGrow( &color->bodySet, bodyIdA );
|
||||
return i;
|
||||
}
|
||||
}
|
||||
else if ( typeB == b3_dynamicBody )
|
||||
{
|
||||
// Static constraint colors build from the end to get higher priority than dyn-dyn constraints
|
||||
for ( int i = B3_OVERFLOW_INDEX - 1; i >= 1; --i )
|
||||
{
|
||||
b3GraphColor* color = graph->colors + i;
|
||||
if ( b3GetBit( &color->bodySet, bodyIdB ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
b3SetBitGrow( &color->bodySet, bodyIdB );
|
||||
return i;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return B3_OVERFLOW_INDEX;
|
||||
}
|
||||
|
||||
b3JointSim* b3CreateJointInGraph( b3World* world, b3Joint* joint )
|
||||
{
|
||||
b3ConstraintGraph* graph = &world->constraintGraph;
|
||||
|
||||
int bodyIdA = joint->edges[0].bodyId;
|
||||
int bodyIdB = joint->edges[1].bodyId;
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB );
|
||||
|
||||
int colorIndex = b3AssignJointColor( graph, bodyIdA, bodyIdB, bodyA->type, bodyB->type );
|
||||
|
||||
b3JointSim* jointSim = b3Array_Emplace( graph->colors[colorIndex].jointSims );
|
||||
memset( jointSim, 0, sizeof( b3JointSim ) );
|
||||
|
||||
joint->colorIndex = colorIndex;
|
||||
joint->localIndex = graph->colors[colorIndex].jointSims.count - 1;
|
||||
return jointSim;
|
||||
}
|
||||
|
||||
void b3AddJointToGraph( b3World* world, b3JointSim* jointSim, b3Joint* joint )
|
||||
{
|
||||
b3JointSim* jointDst = b3CreateJointInGraph( world, joint );
|
||||
memcpy( jointDst, jointSim, sizeof( b3JointSim ) );
|
||||
}
|
||||
|
||||
void b3RemoveJointFromGraph( b3World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex )
|
||||
{
|
||||
b3ConstraintGraph* graph = &world->constraintGraph;
|
||||
|
||||
B3_ASSERT( 0 <= colorIndex && colorIndex < B3_GRAPH_COLOR_COUNT );
|
||||
b3GraphColor* color = graph->colors + colorIndex;
|
||||
|
||||
if ( colorIndex != B3_OVERFLOW_INDEX )
|
||||
{
|
||||
// May clear static bodies, no effect
|
||||
b3ClearBit( &color->bodySet, bodyIdA );
|
||||
b3ClearBit( &color->bodySet, bodyIdB );
|
||||
}
|
||||
|
||||
int movedIndex = b3Array_RemoveSwap( color->jointSims, localIndex );
|
||||
if ( movedIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// Fix moved joint
|
||||
b3JointSim* movedJointSim = color->jointSims.data + localIndex;
|
||||
int movedId = movedJointSim->jointId;
|
||||
b3Joint* movedJoint = b3Array_Get( world->joints, movedId );
|
||||
B3_ASSERT( movedJoint->setIndex == b3_awakeSet );
|
||||
B3_ASSERT( movedJoint->colorIndex == colorIndex );
|
||||
B3_ASSERT( movedJoint->localIndex == movedIndex );
|
||||
movedJoint->localIndex = localIndex;
|
||||
}
|
||||
}
|
||||
77
vendor/box3d/src/src/constraint_graph.h
vendored
Normal file
77
vendor/box3d/src/src/constraint_graph.h
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "bitset.h"
|
||||
#include "contact.h"
|
||||
#include "container.h"
|
||||
#include "solver.h"
|
||||
#include "solver_set.h"
|
||||
#include "box3d/constants.h"
|
||||
|
||||
typedef struct b3Body b3Body;
|
||||
typedef struct b3Contact b3Contact;
|
||||
typedef struct b3JointSim b3JointSim;
|
||||
typedef struct b3Joint b3Joint;
|
||||
typedef struct b3StepContext b3StepContext;
|
||||
typedef struct b3World b3World;
|
||||
|
||||
// This holds constraints that cannot fit the graph color limit. This happens when a single dynamic body
|
||||
// is touching many other bodies.
|
||||
#define B3_OVERFLOW_INDEX ( B3_GRAPH_COLOR_COUNT - 1 )
|
||||
|
||||
// This keeps constraints involving two dynamic bodies at a lower solver priority than constraints
|
||||
// involving a dynamic and static bodies. This reduces tunneling due to push through.
|
||||
#define B3_DYNAMIC_COLOR_COUNT ( B3_GRAPH_COLOR_COUNT - 4 )
|
||||
|
||||
// todo optimize mesh contact constraints
|
||||
// They can be lumped with convex contacts and I can use the bitset event to re-link if the manifold count increases
|
||||
// Could also create a group for two wide manifolds and use bitset event
|
||||
// This could create ping-pong jitter so may need to pin to high water mark or introduce hysteresis somehow
|
||||
//
|
||||
// Dirk has the idea to do graph coloring based on manifolds. This suggests mesh contact will have manifolds
|
||||
// in multiple graph colors. So each manifold with have a color and local index.
|
||||
// Some concerns about this:
|
||||
// - manifolds don't have a strong identity, would this affect stability/jitter?
|
||||
// - this creates a lot of static graph colors and can overflow
|
||||
|
||||
typedef struct b3GraphColor
|
||||
{
|
||||
// This bitset is indexed by bodyId so this is over-sized to encompass static bodies
|
||||
// however I never traverse these bits or use the bit count for anything
|
||||
// This bitset is unused on the overflow color.
|
||||
b3BitSet bodySet;
|
||||
|
||||
// cache friendly arrays
|
||||
b3Array( b3JointSim ) jointSims;
|
||||
|
||||
b3Array( int ) convexContacts;
|
||||
b3Array( b3ContactSpec ) contacts;
|
||||
|
||||
// These are used for convex contacts
|
||||
struct b3ContactConstraintWide* wideConstraints;
|
||||
int wideConstraintCount;
|
||||
|
||||
// These are used for mesh and overflow contacts
|
||||
struct b3ManifoldConstraint* manifoldConstraints;
|
||||
int manifoldConstraintCount;
|
||||
struct b3ContactConstraint* contactConstraints;
|
||||
int contactConstraintCount;
|
||||
} b3GraphColor;
|
||||
|
||||
typedef struct b3ConstraintGraph
|
||||
{
|
||||
// including overflow at the end
|
||||
b3GraphColor colors[B3_GRAPH_COLOR_COUNT];
|
||||
} b3ConstraintGraph;
|
||||
|
||||
void b3CreateGraph( b3ConstraintGraph* graph, int bodyCapacity );
|
||||
void b3DestroyGraph( b3ConstraintGraph* graph );
|
||||
|
||||
void b3AddContactToGraph( b3World* world, b3Contact* contact );
|
||||
void b3RemoveContactFromGraph( b3World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex, bool meshContact );
|
||||
|
||||
b3JointSim* b3CreateJointInGraph( b3World* world, b3Joint* joint );
|
||||
void b3AddJointToGraph( b3World* world, b3JointSim* jointSim, b3Joint* joint );
|
||||
void b3RemoveJointFromGraph( b3World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex );
|
||||
873
vendor/box3d/src/src/contact.c
vendored
Normal file
873
vendor/box3d/src/src/contact.c
vendored
Normal file
@@ -0,0 +1,873 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "contact.h"
|
||||
|
||||
#include "algorithm.h"
|
||||
#include "body.h"
|
||||
#include "compound.h"
|
||||
#include "island.h"
|
||||
#include "manifold.h"
|
||||
#include "physics_world.h"
|
||||
#include "shape.h"
|
||||
#include "solver_set.h"
|
||||
#include "table.h"
|
||||
|
||||
#include "box3d/box3d.h"
|
||||
|
||||
// Contacts and determinism
|
||||
// A deterministic simulation requires contacts to exist in the same order in b3Island no matter the thread count.
|
||||
// The order must reproduce from run to run. This is necessary because the Gauss-Seidel constraint solver is order dependent.
|
||||
//
|
||||
// Creation:
|
||||
// - Contacts are created using results from b3UpdateBroadPhasePairs
|
||||
// - These results are ordered according to the order of the broad-phase move array
|
||||
// - The move array is ordered according to the shape creation order using a bitset.
|
||||
// - The island/shape/body order is determined by creation order
|
||||
// - Logically contacts are only created for awake bodies, so they are immediately added to the awake contact array (serially)
|
||||
//
|
||||
// Island linking:
|
||||
// - The awake contact array is built from the body-contact graph for all awake bodies in awake islands.
|
||||
// - Awake contacts are solved in parallel and they generate contact state changes.
|
||||
// - These state changes may link islands together using union find.
|
||||
// - The state changes are ordered using a bit array that encompasses all contacts
|
||||
// - As long as contacts are created in deterministic order, island link order is deterministic.
|
||||
// - This keeps the order of contacts in islands deterministic
|
||||
|
||||
// Manifold functions should compute important results in local space to improve precision. However, this
|
||||
// interface function takes two world transforms instead of a relative transform for these reasons:
|
||||
//
|
||||
// First:
|
||||
// The anchors need to be computed relative to the shape origin in world space. This is necessary so the
|
||||
// solver does not need to access static body transforms. Not even in constraint preparation. This approach
|
||||
// has world space vectors yet retains precision.
|
||||
//
|
||||
// Second:
|
||||
// b3ManifoldPoint::point is very useful for debugging and it is in world space.
|
||||
//
|
||||
// Third:
|
||||
// The user may call the manifold functions directly and they should be easy to use and have easy to use
|
||||
// results.
|
||||
// typedef b3Manifold b3ManifoldFcn( const b3Shape* shapeA, b3Transform xfA, const b3Shape* shapeB, b3Transform xfB,
|
||||
// b3ContactCache* cache );
|
||||
|
||||
static b3Contact* b3GetContactFullId( b3World* world, b3ContactId contactId )
|
||||
{
|
||||
int id = contactId.index1 - 1;
|
||||
b3Contact* contact = b3Array_Get( world->contacts, id );
|
||||
B3_ASSERT( contact->contactId == id && contact->generation == contactId.generation );
|
||||
return contact;
|
||||
}
|
||||
|
||||
b3ContactData b3Contact_GetData( b3ContactId contactId )
|
||||
{
|
||||
b3World* world = b3GetWorld( contactId.world0 );
|
||||
b3Contact* contact = b3GetContactFullId( world, contactId );
|
||||
|
||||
const b3Shape* shapeA = b3Array_Get( world->shapes, contact->shapeIdA );
|
||||
const b3Shape* shapeB = b3Array_Get( world->shapes, contact->shapeIdB );
|
||||
|
||||
b3ContactData data = { 0 };
|
||||
data.contactId = contactId;
|
||||
data.shapeIdA = (b3ShapeId){
|
||||
.index1 = shapeA->id + 1,
|
||||
.world0 = contactId.world0,
|
||||
.generation = shapeA->generation,
|
||||
};
|
||||
data.shapeIdB = (b3ShapeId){
|
||||
.index1 = shapeB->id + 1,
|
||||
.world0 = contactId.world0,
|
||||
.generation = shapeB->generation,
|
||||
};
|
||||
|
||||
if ( contact->manifoldCount > 0 )
|
||||
{
|
||||
data.manifolds = contact->manifolds;
|
||||
data.manifoldCount = contact->manifoldCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.manifolds = NULL;
|
||||
data.manifoldCount = 0;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
struct b3ContactRegister
|
||||
{
|
||||
// b3ManifoldFcn* fcn;
|
||||
bool supported;
|
||||
bool primary;
|
||||
};
|
||||
|
||||
static struct b3ContactRegister s_registers[b3_shapeTypeCount][b3_shapeTypeCount];
|
||||
static bool s_initialized = false;
|
||||
|
||||
static void b3AddType( b3ShapeType type1, b3ShapeType type2 )
|
||||
{
|
||||
B3_ASSERT( 0 <= type1 && type1 < b3_shapeTypeCount );
|
||||
B3_ASSERT( 0 <= type2 && type2 < b3_shapeTypeCount );
|
||||
|
||||
s_registers[type1][type2].supported = true;
|
||||
s_registers[type1][type2].primary = true;
|
||||
|
||||
if ( type1 != type2 )
|
||||
{
|
||||
s_registers[type2][type1].supported = true;
|
||||
s_registers[type2][type1].primary = false;
|
||||
}
|
||||
}
|
||||
|
||||
void b3InitializeContactRegisters( void )
|
||||
{
|
||||
if ( s_initialized == false )
|
||||
{
|
||||
b3AddType( b3_sphereShape, b3_sphereShape );
|
||||
b3AddType( b3_capsuleShape, b3_sphereShape );
|
||||
b3AddType( b3_capsuleShape, b3_capsuleShape );
|
||||
b3AddType( b3_compoundShape, b3_sphereShape );
|
||||
b3AddType( b3_compoundShape, b3_capsuleShape );
|
||||
b3AddType( b3_compoundShape, b3_hullShape );
|
||||
b3AddType( b3_hullShape, b3_sphereShape );
|
||||
b3AddType( b3_hullShape, b3_capsuleShape );
|
||||
b3AddType( b3_hullShape, b3_hullShape );
|
||||
b3AddType( b3_meshShape, b3_sphereShape );
|
||||
b3AddType( b3_meshShape, b3_capsuleShape );
|
||||
b3AddType( b3_meshShape, b3_hullShape );
|
||||
b3AddType( b3_heightShape, b3_sphereShape );
|
||||
b3AddType( b3_heightShape, b3_capsuleShape );
|
||||
b3AddType( b3_heightShape, b3_hullShape );
|
||||
s_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void b3CreateContact( b3World* world, b3Shape* shapeA, b3Shape* shapeB, int childIndex )
|
||||
{
|
||||
b3ShapeType typeA = shapeA->type;
|
||||
b3ShapeType typeB = shapeB->type;
|
||||
|
||||
B3_ASSERT( 0 <= typeA && typeA < b3_shapeTypeCount );
|
||||
B3_ASSERT( 0 <= typeB && typeB < b3_shapeTypeCount );
|
||||
|
||||
if ( s_registers[typeA][typeB].supported == false )
|
||||
{
|
||||
// For example, no mesh vs mesh collision
|
||||
return;
|
||||
}
|
||||
|
||||
if ( s_registers[typeA][typeB].primary == false )
|
||||
{
|
||||
// flip order
|
||||
b3CreateContact( world, shapeB, shapeA, childIndex );
|
||||
return;
|
||||
}
|
||||
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, shapeA->bodyId );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, shapeB->bodyId );
|
||||
|
||||
B3_ASSERT( bodyA->setIndex != b3_disabledSet && bodyB->setIndex != b3_disabledSet );
|
||||
B3_ASSERT( bodyA->setIndex != b3_staticSet || bodyB->setIndex != b3_staticSet );
|
||||
|
||||
int setIndex;
|
||||
if ( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet )
|
||||
{
|
||||
setIndex = b3_awakeSet;
|
||||
}
|
||||
else
|
||||
{
|
||||
// sleeping and non-touching contacts live in the disabled set
|
||||
// later if this set is found to be touching then the sleeping
|
||||
// islands will be linked and the contact moved to the merged island
|
||||
|
||||
// This is possible if a shape moves slightly then falls asleep
|
||||
setIndex = b3_disabledSet;
|
||||
}
|
||||
|
||||
b3SolverSet* set = b3Array_Get( world->solverSets, setIndex );
|
||||
|
||||
// Create contact key and contact
|
||||
int contactId = b3AllocId( &world->contactIdPool );
|
||||
if ( contactId == world->contacts.count )
|
||||
{
|
||||
b3Contact emptyContact = { 0 };
|
||||
b3Array_Push( world->contacts, emptyContact );
|
||||
}
|
||||
|
||||
int shapeIdA = shapeA->id;
|
||||
int shapeIdB = shapeB->id;
|
||||
|
||||
b3Contact* contact = b3Array_Get( world->contacts, contactId );
|
||||
int generation = contact->generation;
|
||||
*contact = (b3Contact){ 0 };
|
||||
contact->contactId = contactId;
|
||||
contact->generation = generation + 1;
|
||||
contact->setIndex = setIndex;
|
||||
contact->colorIndex = B3_NULL_INDEX;
|
||||
contact->localIndex = set->contactIndices.count;
|
||||
contact->islandId = B3_NULL_INDEX;
|
||||
contact->islandIndex = B3_NULL_INDEX;
|
||||
contact->shapeIdA = shapeIdA;
|
||||
contact->shapeIdB = shapeIdB;
|
||||
contact->childIndex = childIndex;
|
||||
|
||||
// Both bodies must enable recycling
|
||||
if ( ( bodyA->flags & b3_bodyEnableContactRecycling ) != 0 && ( bodyB->flags & b3_bodyEnableContactRecycling ) != 0 )
|
||||
{
|
||||
contact->flags |= b3_contactRecycleFlag;
|
||||
}
|
||||
|
||||
if ( shapeA->type == b3_meshShape || shapeA->type == b3_heightShape )
|
||||
{
|
||||
contact->flags |= b3_simMeshContact;
|
||||
}
|
||||
else if ( shapeA->type == b3_compoundShape )
|
||||
{
|
||||
b3ChildShape child = b3GetCompoundChild( shapeA->compound, childIndex );
|
||||
if ( child.type == b3_meshShape )
|
||||
{
|
||||
contact->flags |= b3_simMeshContact;
|
||||
}
|
||||
}
|
||||
|
||||
// todo impose these restrictions to make life easier
|
||||
B3_ASSERT( shapeB->type == b3_sphereShape || shapeB->type == b3_capsuleShape || shapeB->type == b3_hullShape );
|
||||
// B3_ASSERT( bodyB->type != b3_staticBody );
|
||||
|
||||
// Is either body static?
|
||||
// Note: it is possible to have a dynamic mesh collide with a static convex shape. Maybe I should disallow this.
|
||||
if ( bodyA->type == b3_staticBody || bodyB->type == b3_staticBody )
|
||||
{
|
||||
contact->flags |= b3_contactStaticFlag;
|
||||
}
|
||||
|
||||
B3_ASSERT( shapeA->sensorIndex == B3_NULL_INDEX && shapeB->sensorIndex == B3_NULL_INDEX );
|
||||
|
||||
if ( ( shapeA->flags & b3_enableContactEvents ) || ( shapeB->flags & b3_enableContactEvents ) )
|
||||
{
|
||||
contact->flags |= b3_contactEnableContactEvents;
|
||||
}
|
||||
|
||||
if ( ( shapeA->flags & b3_enableSpeculative ) && ( shapeB->flags & b3_enableSpeculative ) )
|
||||
{
|
||||
contact->flags |= b3_enableSpeculativePoints;
|
||||
}
|
||||
|
||||
// Connect to body A
|
||||
{
|
||||
contact->edges[0].bodyId = shapeA->bodyId;
|
||||
contact->edges[0].prevKey = B3_NULL_INDEX;
|
||||
contact->edges[0].nextKey = bodyA->headContactKey;
|
||||
|
||||
int keyA = ( contactId << 1 ) | 0;
|
||||
int headContactKey = bodyA->headContactKey;
|
||||
if ( headContactKey != B3_NULL_INDEX )
|
||||
{
|
||||
b3Contact* headContact = b3Array_Get( world->contacts, headContactKey >> 1 );
|
||||
headContact->edges[headContactKey & 1].prevKey = keyA;
|
||||
}
|
||||
bodyA->headContactKey = keyA;
|
||||
bodyA->contactCount += 1;
|
||||
}
|
||||
|
||||
// Connect to body B
|
||||
{
|
||||
contact->edges[1].bodyId = shapeB->bodyId;
|
||||
contact->edges[1].prevKey = B3_NULL_INDEX;
|
||||
contact->edges[1].nextKey = bodyB->headContactKey;
|
||||
|
||||
int keyB = ( contactId << 1 ) | 1;
|
||||
int headContactKey = bodyB->headContactKey;
|
||||
if ( bodyB->headContactKey != B3_NULL_INDEX )
|
||||
{
|
||||
b3Contact* headContact = b3Array_Get( world->contacts, headContactKey >> 1 );
|
||||
headContact->edges[headContactKey & 1].prevKey = keyB;
|
||||
}
|
||||
bodyB->headContactKey = keyB;
|
||||
bodyB->contactCount += 1;
|
||||
}
|
||||
|
||||
// Add to pair set for fast lookup
|
||||
uint64_t pairKey = b3ShapePairKey( shapeIdA, shapeIdB, childIndex );
|
||||
b3AddKey( &world->broadPhase.pairSet, pairKey );
|
||||
|
||||
// Contacts are created as non-touching. Later if they are found to be touching
|
||||
// they will link islands and be moved into the constraint graph.
|
||||
b3Array_Push( set->contactIndices, contactId );
|
||||
|
||||
float radiusA = 0.0f;
|
||||
if ( typeA == b3_sphereShape )
|
||||
{
|
||||
radiusA = shapeA->sphere.radius;
|
||||
}
|
||||
else if ( typeA == b3_capsuleShape )
|
||||
{
|
||||
radiusA = shapeA->capsule.radius;
|
||||
}
|
||||
|
||||
float radiusB = 0.0f;
|
||||
if ( typeB == b3_sphereShape )
|
||||
{
|
||||
radiusB = shapeB->sphere.radius;
|
||||
}
|
||||
else if ( typeB == b3_capsuleShape )
|
||||
{
|
||||
radiusB = shapeB->capsule.radius;
|
||||
}
|
||||
|
||||
float maxRadius = b3MaxFloat( radiusA, radiusB );
|
||||
|
||||
// Assuming the rolling resistance doesn't change
|
||||
contact->rollingResistance =
|
||||
b3MaxFloat( b3GetShapeMaterials( shapeA )[0].rollingResistance, b3GetShapeMaterials( shapeB )[0].rollingResistance ) *
|
||||
maxRadius;
|
||||
|
||||
if ( ( shapeA->flags & b3_enablePreSolveEvents ) || ( shapeB->flags & b3_enablePreSolveEvents ) )
|
||||
{
|
||||
contact->flags |= b3_simEnablePreSolveEvents;
|
||||
}
|
||||
}
|
||||
|
||||
// A contact is destroyed when:
|
||||
// - broad-phase proxies stop overlapping
|
||||
// - a body is destroyed
|
||||
// - a body is disabled
|
||||
// - a body changes type from dynamic to kinematic or static
|
||||
// - a shape is destroyed
|
||||
// - contact filtering is modified
|
||||
void b3DestroyContact( b3World* world, b3Contact* contact, bool wakeBodies )
|
||||
{
|
||||
// Remove pair from set
|
||||
uint64_t pairKey = b3ShapePairKey( contact->shapeIdA, contact->shapeIdB, contact->childIndex );
|
||||
b3RemoveKey( &world->broadPhase.pairSet, pairKey );
|
||||
|
||||
b3FreeManifolds( world, contact->manifolds, contact->manifoldCount );
|
||||
contact->manifolds = NULL;
|
||||
contact->manifoldCount = 0;
|
||||
|
||||
b3ContactEdge* edgeA = contact->edges + 0;
|
||||
b3ContactEdge* edgeB = contact->edges + 1;
|
||||
|
||||
int bodyIdA = edgeA->bodyId;
|
||||
int bodyIdB = edgeB->bodyId;
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB );
|
||||
|
||||
uint32_t flags = contact->flags;
|
||||
bool touching = ( flags & b3_contactTouchingFlag ) != 0;
|
||||
|
||||
// End touch event
|
||||
if ( touching && ( flags & b3_contactEnableContactEvents ) != 0 )
|
||||
{
|
||||
uint16_t worldId = world->worldId;
|
||||
const b3Shape* shapeA = b3Array_Get( world->shapes, contact->shapeIdA );
|
||||
const b3Shape* shapeB = b3Array_Get( world->shapes, contact->shapeIdB );
|
||||
b3ShapeId shapeIdA = { shapeA->id + 1, worldId, shapeA->generation };
|
||||
b3ShapeId shapeIdB = { shapeB->id + 1, worldId, shapeB->generation };
|
||||
|
||||
b3ContactId contactId = {
|
||||
.index1 = contact->contactId + 1,
|
||||
.world0 = world->worldId,
|
||||
.padding = 0,
|
||||
.generation = contact->generation,
|
||||
};
|
||||
|
||||
b3ContactEndTouchEvent event = {
|
||||
.shapeIdA = shapeIdA,
|
||||
.shapeIdB = shapeIdB,
|
||||
.contactId = contactId,
|
||||
};
|
||||
|
||||
b3Array_Push( world->contactEndEvents[world->endEventArrayIndex], event );
|
||||
}
|
||||
|
||||
// Remove from body A
|
||||
if ( edgeA->prevKey != B3_NULL_INDEX )
|
||||
{
|
||||
b3Contact* prevContact = b3Array_Get( world->contacts, edgeA->prevKey >> 1 );
|
||||
b3ContactEdge* prevEdge = prevContact->edges + ( edgeA->prevKey & 1 );
|
||||
prevEdge->nextKey = edgeA->nextKey;
|
||||
}
|
||||
|
||||
if ( edgeA->nextKey != B3_NULL_INDEX )
|
||||
{
|
||||
b3Contact* nextContact = b3Array_Get( world->contacts, edgeA->nextKey >> 1 );
|
||||
b3ContactEdge* nextEdge = nextContact->edges + ( edgeA->nextKey & 1 );
|
||||
nextEdge->prevKey = edgeA->prevKey;
|
||||
}
|
||||
|
||||
int contactId = contact->contactId;
|
||||
|
||||
int edgeKeyA = ( contactId << 1 ) | 0;
|
||||
if ( bodyA->headContactKey == edgeKeyA )
|
||||
{
|
||||
bodyA->headContactKey = edgeA->nextKey;
|
||||
}
|
||||
|
||||
bodyA->contactCount -= 1;
|
||||
|
||||
// Remove from body B
|
||||
if ( edgeB->prevKey != B3_NULL_INDEX )
|
||||
{
|
||||
b3Contact* prevContact = b3Array_Get( world->contacts, edgeB->prevKey >> 1 );
|
||||
b3ContactEdge* prevEdge = prevContact->edges + ( edgeB->prevKey & 1 );
|
||||
prevEdge->nextKey = edgeB->nextKey;
|
||||
}
|
||||
|
||||
if ( edgeB->nextKey != B3_NULL_INDEX )
|
||||
{
|
||||
b3Contact* nextContact = b3Array_Get( world->contacts, edgeB->nextKey >> 1 );
|
||||
b3ContactEdge* nextEdge = nextContact->edges + ( edgeB->nextKey & 1 );
|
||||
nextEdge->prevKey = edgeB->prevKey;
|
||||
}
|
||||
|
||||
int edgeKeyB = ( contactId << 1 ) | 1;
|
||||
if ( bodyB->headContactKey == edgeKeyB )
|
||||
{
|
||||
bodyB->headContactKey = edgeB->nextKey;
|
||||
}
|
||||
|
||||
bodyB->contactCount -= 1;
|
||||
|
||||
if ( contact->flags & b3_simMeshContact )
|
||||
{
|
||||
b3Array_Destroy( contact->meshContact.triangleCache );
|
||||
}
|
||||
|
||||
// Remove contact from the array that owns it
|
||||
if ( contact->islandId != B3_NULL_INDEX )
|
||||
{
|
||||
b3UnlinkContact( world, contact );
|
||||
}
|
||||
|
||||
if ( contact->colorIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// contact is an active constraint
|
||||
B3_ASSERT( contact->setIndex == b3_awakeSet );
|
||||
bool meshContact = contact->flags & b3_simMeshContact;
|
||||
b3RemoveContactFromGraph( world, bodyIdA, bodyIdB, contact->colorIndex, contact->localIndex, meshContact );
|
||||
}
|
||||
else
|
||||
{
|
||||
// contact is non-touching or is sleeping or is a sensor
|
||||
B3_ASSERT( contact->setIndex != b3_awakeSet || ( contact->flags & b3_contactTouchingFlag ) == 0 );
|
||||
b3SolverSet* set = b3Array_Get( world->solverSets, contact->setIndex );
|
||||
|
||||
int localIndex = contact->localIndex;
|
||||
int movedIndex = b3Array_RemoveSwap( set->contactIndices, localIndex );
|
||||
if ( movedIndex != B3_NULL_INDEX )
|
||||
{
|
||||
int movedContactIndex = set->contactIndices.data[localIndex];
|
||||
b3Contact* movedContact = b3Array_Get( world->contacts, movedContactIndex );
|
||||
movedContact->localIndex = localIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Free contact and id (preserve generation)
|
||||
contact->contactId = B3_NULL_INDEX;
|
||||
contact->setIndex = B3_NULL_INDEX;
|
||||
contact->colorIndex = B3_NULL_INDEX;
|
||||
contact->localIndex = B3_NULL_INDEX;
|
||||
b3FreeId( &world->contactIdPool, contactId );
|
||||
|
||||
if ( wakeBodies && touching )
|
||||
{
|
||||
b3WakeBody( world, bodyA );
|
||||
b3WakeBody( world, bodyB );
|
||||
}
|
||||
}
|
||||
|
||||
static bool b3ComputeConvexManifold( b3World* world, int workerIndex, b3Contact* contact, const b3Shape* shapeA,
|
||||
b3WorldTransform xfA, const b3Shape* shapeB, b3WorldTransform xfB, b3Arena arena )
|
||||
{
|
||||
b3ShapeType typeA = shapeA->type;
|
||||
b3ShapeType typeB = shapeB->type;
|
||||
|
||||
b3ContactCache* cache = &contact->convexContact.cache;
|
||||
|
||||
int pointCapacity = 32;
|
||||
b3LocalManifoldPoint* pointBuffer = (b3LocalManifoldPoint*)b3Bump( &arena, pointCapacity * sizeof( b3LocalManifoldPoint ) );
|
||||
|
||||
b3LocalManifold geomManifold = { 0 };
|
||||
geomManifold.points = pointBuffer;
|
||||
|
||||
b3Transform transformBtoA = b3InvMulWorldTransforms( xfA, xfB );
|
||||
|
||||
if ( typeA == b3_sphereShape )
|
||||
{
|
||||
B3_ASSERT( typeB == b3_sphereShape );
|
||||
b3CollideSpheres( &geomManifold, pointCapacity, &shapeA->sphere, &shapeB->sphere, transformBtoA );
|
||||
}
|
||||
else if ( typeA == b3_capsuleShape )
|
||||
{
|
||||
if ( typeB == b3_sphereShape )
|
||||
{
|
||||
b3CollideCapsuleAndSphere( &geomManifold, pointCapacity, &shapeA->capsule, &shapeB->sphere, transformBtoA );
|
||||
}
|
||||
else
|
||||
{
|
||||
B3_ASSERT( typeB == b3_capsuleShape );
|
||||
b3CollideCapsules( &geomManifold, pointCapacity, &shapeA->capsule, &shapeB->capsule, transformBtoA );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
B3_ASSERT( typeA == b3_hullShape );
|
||||
|
||||
if ( typeB == b3_sphereShape )
|
||||
{
|
||||
b3CollideHullAndSphere( &geomManifold, pointCapacity, shapeA->hull, &shapeB->sphere, transformBtoA,
|
||||
&cache->simplexCache );
|
||||
}
|
||||
else if ( typeB == b3_capsuleShape )
|
||||
{
|
||||
b3CollideHullAndCapsule( &geomManifold, pointCapacity, shapeA->hull, &shapeB->capsule, transformBtoA,
|
||||
&cache->simplexCache );
|
||||
}
|
||||
else
|
||||
{
|
||||
B3_ASSERT( typeB == b3_hullShape );
|
||||
b3CollideHulls( &geomManifold, pointCapacity, shapeA->hull, shapeB->hull, transformBtoA, &cache->satCache );
|
||||
world->taskContexts.data[workerIndex].satCallCount += 1;
|
||||
world->taskContexts.data[workerIndex].satCacheHitCount += cache->satCache.hit;
|
||||
}
|
||||
}
|
||||
|
||||
if ( geomManifold.pointCount == 0 )
|
||||
{
|
||||
if ( contact->manifoldCount > 0 )
|
||||
{
|
||||
b3FreeManifolds( world, contact->manifolds, contact->manifoldCount );
|
||||
contact->manifolds = NULL;
|
||||
contact->manifoldCount = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
b3ManifoldPoint oldPoints[B3_MAX_MANIFOLD_POINTS];
|
||||
int oldCount = 0;
|
||||
|
||||
if ( contact->manifoldCount == 0 )
|
||||
{
|
||||
contact->manifolds = b3AllocateManifolds( world, 1 );
|
||||
contact->manifoldCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
oldCount = contact->manifolds[0].pointCount;
|
||||
memcpy( oldPoints, contact->manifolds[0].points, oldCount * sizeof( b3ManifoldPoint ) );
|
||||
}
|
||||
|
||||
b3Manifold* manifold = contact->manifolds;
|
||||
manifold->pointCount = geomManifold.pointCount;
|
||||
|
||||
b3Matrix3 matrixA = b3MakeMatrixFromQuat( xfA.q );
|
||||
manifold->normal = b3MulMV( matrixA, geomManifold.normal );
|
||||
|
||||
// Store point data in contact
|
||||
for ( int i = 0; i < geomManifold.pointCount; ++i )
|
||||
{
|
||||
const b3LocalManifoldPoint* source = geomManifold.points + i;
|
||||
b3ManifoldPoint* target = manifold->points + i;
|
||||
|
||||
// Contact points are computed in frame A
|
||||
target->anchorA = b3MulMV( matrixA, source->point );
|
||||
target->anchorB = b3Add( target->anchorA, b3SubPos( xfA.p, xfB.p ) );
|
||||
target->separation = source->separation;
|
||||
target->featureId = b3MakeFeatureId( source->pair );
|
||||
target->triangleIndex = B3_NULL_INDEX;
|
||||
target->normalVelocity = 0.0f;
|
||||
}
|
||||
|
||||
// Copy impulses from old points
|
||||
for ( int i = 0; i < geomManifold.pointCount; ++i )
|
||||
{
|
||||
b3ManifoldPoint* pt2 = manifold->points + i;
|
||||
pt2->totalNormalImpulse = 0.0f;
|
||||
pt2->persisted = false;
|
||||
|
||||
for ( int j = 0; j < oldCount; ++j )
|
||||
{
|
||||
b3ManifoldPoint* pt1 = oldPoints + j;
|
||||
|
||||
if ( pt2->featureId == pt1->featureId )
|
||||
{
|
||||
pt2->normalImpulse = pt1->normalImpulse;
|
||||
pt2->persisted = true;
|
||||
|
||||
// claimed
|
||||
pt1->featureId = UINT32_MAX;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( pt2->persisted == false )
|
||||
{
|
||||
pt2->normalImpulse = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool b3UpdateConvexContact( b3World* world, int workerIndex, b3Contact* contact, b3Shape* shapeA, b3WorldTransform xfA,
|
||||
b3Shape* shapeB, b3WorldTransform xfB, bool flip, b3Arena arena )
|
||||
{
|
||||
// Compute new manifold
|
||||
bool touching = b3ComputeConvexManifold( world, workerIndex, contact, shapeA, xfA, shapeB, xfB, arena );
|
||||
|
||||
if ( touching == false )
|
||||
{
|
||||
B3_ASSERT( contact->manifolds == NULL && contact->manifoldCount == 0 );
|
||||
return false;
|
||||
}
|
||||
|
||||
B3_ASSERT( contact->manifoldCount == 1 );
|
||||
|
||||
if ( flip )
|
||||
{
|
||||
// Not flipping the feature ids because they just need to match and flipping is consistent.
|
||||
b3Manifold* manifold = contact->manifolds + 0;
|
||||
manifold->normal = b3Neg( manifold->normal );
|
||||
int pointCount = manifold->pointCount;
|
||||
for ( int i = 0; i < pointCount; ++i )
|
||||
{
|
||||
b3ManifoldPoint* mp = manifold->points + i;
|
||||
B3_SWAP( mp->anchorA, mp->anchorB );
|
||||
}
|
||||
}
|
||||
|
||||
const b3SurfaceMaterial* materialA = b3GetShapeMaterials( shapeA );
|
||||
const b3SurfaceMaterial* materialB = b3GetShapeMaterials( shapeB );
|
||||
|
||||
// Keep these updated in case the values on the shapes are modified
|
||||
contact->friction =
|
||||
world->frictionCallback( materialA->friction, materialA->userMaterialId, materialB->friction, materialB->userMaterialId );
|
||||
contact->restitution = world->restitutionCallback( materialA->restitution, materialA->userMaterialId, materialB->restitution,
|
||||
materialB->userMaterialId );
|
||||
|
||||
if ( materialA->rollingResistance > 0.0f || materialB->rollingResistance > 0.0f )
|
||||
{
|
||||
b3ShapeType typeA = shapeA->type;
|
||||
b3ShapeType typeB = shapeB->type;
|
||||
|
||||
float radiusA = 0.0f;
|
||||
if ( typeA == b3_sphereShape )
|
||||
{
|
||||
radiusA = shapeA->sphere.radius;
|
||||
}
|
||||
else if ( typeA == b3_capsuleShape )
|
||||
{
|
||||
radiusA = shapeA->capsule.radius;
|
||||
}
|
||||
else if ( typeA == b3_hullShape )
|
||||
{
|
||||
radiusA = 0.25f * shapeA->hull->innerRadius;
|
||||
}
|
||||
|
||||
float radiusB = 0.0f;
|
||||
if ( typeB == b3_sphereShape )
|
||||
{
|
||||
radiusB = shapeB->sphere.radius;
|
||||
}
|
||||
else if ( typeB == b3_capsuleShape )
|
||||
{
|
||||
radiusB = shapeB->capsule.radius;
|
||||
}
|
||||
else if ( typeB == b3_hullShape )
|
||||
{
|
||||
radiusB = 0.25f * shapeB->hull->innerRadius;
|
||||
}
|
||||
|
||||
float maxRadius = b3MaxFloat( radiusA, radiusB );
|
||||
contact->rollingResistance = b3MaxFloat( materialA->rollingResistance, materialB->rollingResistance ) * maxRadius;
|
||||
}
|
||||
else
|
||||
{
|
||||
contact->rollingResistance = 0.0f;
|
||||
}
|
||||
|
||||
b3Vec3 tangentVelocityA = b3RotateVector( xfA.q, materialA->tangentVelocity );
|
||||
b3Vec3 tangentVelocityB = b3RotateVector( xfB.q, materialB->tangentVelocity );
|
||||
contact->tangentVelocity = b3Sub( tangentVelocityA, tangentVelocityB );
|
||||
|
||||
if ( world->preSolveFcn && ( contact->flags & b3_simEnablePreSolveEvents ) != 0 )
|
||||
{
|
||||
b3ShapeId shapeIdA = { shapeA->id + 1, world->worldId, shapeA->generation };
|
||||
b3ShapeId shapeIdB = { shapeB->id + 1, world->worldId, shapeB->generation };
|
||||
|
||||
// this call assumes thread safety
|
||||
b3Pos point = b3OffsetPos( xfA.p, contact->manifolds[0].points[0].anchorA );
|
||||
b3Vec3 normal = contact->manifolds[0].normal;
|
||||
touching = world->preSolveFcn( shapeIdA, shapeIdB, point, normal, world->preSolveContext );
|
||||
if ( touching == false )
|
||||
{
|
||||
// disable contact
|
||||
b3FreeManifolds( world, contact->manifolds, contact->manifoldCount );
|
||||
contact->manifolds = NULL;
|
||||
contact->manifoldCount = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ( shapeA->flags & b3_enableHitEvents ) || ( shapeB->flags & b3_enableHitEvents ) )
|
||||
{
|
||||
contact->flags |= b3_simEnableHitEvent;
|
||||
}
|
||||
else
|
||||
{
|
||||
contact->flags &= ~b3_simEnableHitEvent;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update the contact manifold and touching status.
|
||||
// Note: do not assume the shape AABBs are overlapping or are valid.
|
||||
bool b3UpdateContact( b3World* world, int workerIndex, b3Contact* contact, b3Shape* shapeA, b3Vec3 localCenterA,
|
||||
b3WorldTransform xfA, b3Shape* shapeB, b3Vec3 localCenterB, b3WorldTransform xfB, bool isFast,
|
||||
b3Arena arena )
|
||||
{
|
||||
bool touching;
|
||||
|
||||
B3_ASSERT( shapeB->type != b3_compoundShape );
|
||||
|
||||
if ( shapeA->type == b3_compoundShape )
|
||||
{
|
||||
int childIndex = contact->childIndex;
|
||||
b3ChildShape child = b3GetCompoundChild( shapeA->compound, childIndex );
|
||||
|
||||
// Temporary child shape to match existing function signatures
|
||||
b3Shape childShapeA;
|
||||
memcpy( &childShapeA, shapeA, sizeof( b3Shape ) );
|
||||
|
||||
childShapeA.type = child.type;
|
||||
|
||||
if ( child.type == b3_capsuleShape )
|
||||
{
|
||||
childShapeA.capsule = child.capsule;
|
||||
if ( shapeB->type == b3_hullShape )
|
||||
{
|
||||
// Flip
|
||||
bool flip = true;
|
||||
touching = b3UpdateConvexContact( world, workerIndex, contact, shapeB, xfB, &childShapeA, xfA, flip, arena );
|
||||
}
|
||||
else
|
||||
{
|
||||
bool flip = false;
|
||||
touching = b3UpdateConvexContact( world, workerIndex, contact, &childShapeA, xfA, shapeB, xfB, flip, arena );
|
||||
}
|
||||
}
|
||||
else if ( child.type == b3_hullShape )
|
||||
{
|
||||
childShapeA.hull = child.hull;
|
||||
b3WorldTransform xfChild = b3MulWorldTransforms( xfA, child.transform );
|
||||
bool flip = false;
|
||||
touching = b3UpdateConvexContact( world, workerIndex, contact, &childShapeA, xfChild, shapeB, xfB, flip, arena );
|
||||
}
|
||||
else if ( child.type == b3_meshShape )
|
||||
{
|
||||
childShapeA.mesh = child.mesh;
|
||||
b3WorldTransform xfChild = b3MulWorldTransforms( xfA, child.transform );
|
||||
|
||||
touching = b3ComputeMeshManifolds( world, workerIndex, contact, &childShapeA, child.materialIndices, xfChild, shapeB,
|
||||
xfB, isFast, arena );
|
||||
|
||||
if ( touching && ( ( shapeA->flags & b3_enableHitEvents ) || ( shapeB->flags & b3_enableHitEvents ) ) )
|
||||
{
|
||||
contact->flags |= b3_simEnableHitEvent;
|
||||
}
|
||||
else
|
||||
{
|
||||
contact->flags &= ~b3_simEnableHitEvent;
|
||||
}
|
||||
|
||||
B3_ASSERT( ( touching == true && contact->manifoldCount > 0 ) ||
|
||||
( touching == false && contact->manifoldCount == 0 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
B3_ASSERT( child.type == b3_sphereShape );
|
||||
|
||||
childShapeA.sphere = child.sphere;
|
||||
if ( shapeB->type == b3_capsuleShape || shapeB->type == b3_hullShape )
|
||||
{
|
||||
// Flip
|
||||
bool flip = true;
|
||||
touching = b3UpdateConvexContact( world, workerIndex, contact, shapeB, xfB, &childShapeA, xfA, flip, arena );
|
||||
}
|
||||
else
|
||||
{
|
||||
bool flip = false;
|
||||
touching = b3UpdateConvexContact( world, workerIndex, contact, &childShapeA, xfA, shapeB, xfB, flip, arena );
|
||||
}
|
||||
}
|
||||
|
||||
// The anchor is relative to the child origin but oriented in world space.
|
||||
// Offset the anchor to be relative to the compound origin.
|
||||
int manifoldCount = contact->manifoldCount;
|
||||
b3Vec3 offset = b3RotateVector( xfA.q, child.transform.p );
|
||||
for ( int i = 0; i < manifoldCount; ++i )
|
||||
{
|
||||
b3Manifold* manifold = contact->manifolds + i;
|
||||
int pointCount = manifold->pointCount;
|
||||
for ( int j = 0; j < pointCount; ++j )
|
||||
{
|
||||
b3ManifoldPoint* mp = manifold->points + j;
|
||||
mp->anchorA = b3Add( mp->anchorA, offset );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( shapeA->type == b3_meshShape || shapeA->type == b3_heightShape )
|
||||
{
|
||||
// Does this contact touch a mesh or height-field?
|
||||
|
||||
// Compute mesh manifolds
|
||||
touching = b3ComputeMeshManifolds( world, workerIndex, contact, shapeA, NULL, xfA, shapeB, xfB, isFast, arena );
|
||||
|
||||
if ( touching && ( ( shapeA->flags & b3_enableHitEvents ) || ( shapeB->flags & b3_enableHitEvents ) ) )
|
||||
{
|
||||
contact->flags |= b3_simEnableHitEvent;
|
||||
}
|
||||
else
|
||||
{
|
||||
contact->flags &= ~b3_simEnableHitEvent;
|
||||
}
|
||||
|
||||
B3_ASSERT( ( touching == true && contact->manifoldCount > 0 ) || ( touching == false && contact->manifoldCount == 0 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Convex-vs-convex
|
||||
bool flip = false;
|
||||
touching = b3UpdateConvexContact( world, workerIndex, contact, shapeA, xfA, shapeB, xfB, flip, arena );
|
||||
}
|
||||
|
||||
if ( touching )
|
||||
{
|
||||
b3Vec3 centerA = b3RotateVector( xfA.q, localCenterA );
|
||||
b3Vec3 centerB = b3RotateVector( xfB.q, localCenterB );
|
||||
|
||||
// Adjust anchors to be relative to center of mass
|
||||
for ( int i = 0; i < contact->manifoldCount; ++i )
|
||||
{
|
||||
b3Manifold* manifold = contact->manifolds + i;
|
||||
for ( int j = 0; j < manifold->pointCount; ++j )
|
||||
{
|
||||
b3ManifoldPoint* mp = manifold->points + j;
|
||||
mp->anchorA = b3Sub( mp->anchorA, centerA );
|
||||
mp->anchorB = b3Sub( mp->anchorB, centerB );
|
||||
}
|
||||
}
|
||||
|
||||
contact->flags |= b3_simTouchingFlag;
|
||||
}
|
||||
else
|
||||
{
|
||||
contact->flags &= ~b3_simTouchingFlag;
|
||||
}
|
||||
|
||||
return touching;
|
||||
}
|
||||
183
vendor/box3d/src/src/contact.h
vendored
Normal file
183
vendor/box3d/src/src/contact.h
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arena_allocator.h"
|
||||
#include "container.h"
|
||||
|
||||
#include "box3d/collision.h"
|
||||
#include "box3d/types.h"
|
||||
|
||||
#define B3_FORCE_GHOST_COLLISIONS 0
|
||||
|
||||
typedef struct b3Shape b3Shape;
|
||||
typedef struct b3World b3World;
|
||||
|
||||
typedef union b3ContactCache
|
||||
{
|
||||
b3SATCache satCache;
|
||||
b3SimplexCache simplexCache;
|
||||
} b3ContactCache;
|
||||
|
||||
typedef struct b3TriangleCache
|
||||
{
|
||||
int triangleIndex;
|
||||
b3ContactCache cache;
|
||||
} b3TriangleCache;
|
||||
|
||||
b3DeclareArray( b3TriangleCache );
|
||||
|
||||
enum b3ContactFlags
|
||||
{
|
||||
// Set when the solid shapes are touching.
|
||||
b3_contactTouchingFlag = 0x00000001,
|
||||
|
||||
// Contact has a hit event
|
||||
b3_contactHitEventFlag = 0x00000002,
|
||||
|
||||
// This contact wants contact events
|
||||
b3_contactEnableContactEvents = 0x00000004,
|
||||
|
||||
// This is contact is between a dynamic and static body
|
||||
b3_contactStaticFlag = 0x00000008,
|
||||
|
||||
b3_contactRecycleFlag = 0x00000010,
|
||||
|
||||
// Set when the shapes are touching
|
||||
b3_simTouchingFlag = 0x00010000,
|
||||
|
||||
// This contact no longer has overlapping AABBs
|
||||
b3_simDisjoint = 0x00020000,
|
||||
|
||||
// This contact started touching
|
||||
b3_simStartedTouching = 0x00040000,
|
||||
|
||||
// This contact stopped touching
|
||||
b3_simStoppedTouching = 0x00080000,
|
||||
|
||||
// This contact has a hit event
|
||||
b3_simEnableHitEvent = 0x00100000,
|
||||
|
||||
// This contact wants pre-solve events
|
||||
b3_simEnablePreSolveEvents = 0x00200000,
|
||||
|
||||
// This is a mesh contact
|
||||
b3_simMeshContact = 0x00400000,
|
||||
|
||||
// Relative transform is cached for contact recycling
|
||||
b3_relativeTransformValid = 0x00800000,
|
||||
|
||||
// Enable speculative contact points
|
||||
b3_enableSpeculativePoints = 0x01000000,
|
||||
};
|
||||
|
||||
// A contact edge is used to connect bodies and contacts together
|
||||
// in a contact graph where each body is a node and each contact
|
||||
// is an edge. A contact edge belongs to a doubly linked list
|
||||
// maintained in each attached body. Each contact has two contact
|
||||
// edges, one for each attached body.
|
||||
typedef struct b3ContactEdge
|
||||
{
|
||||
int bodyId;
|
||||
int prevKey;
|
||||
int nextKey;
|
||||
} b3ContactEdge;
|
||||
|
||||
typedef struct b3MeshContact
|
||||
{
|
||||
b3Array( b3TriangleCache ) triangleCache;
|
||||
b3AABB queryBounds;
|
||||
} b3MeshContact;
|
||||
|
||||
typedef struct b3ConvexContact
|
||||
{
|
||||
b3ContactCache cache;
|
||||
} b3ConvexContact;
|
||||
|
||||
// Represents the persistent interaction between two shapes
|
||||
typedef struct b3Contact
|
||||
{
|
||||
// index of simulation set stored in b3World
|
||||
// B3_NULL_INDEX when slot is free
|
||||
int setIndex;
|
||||
|
||||
// index into the constraint graph color array
|
||||
// B3_NULL_INDEX for non-touching or sleeping contacts
|
||||
// B3_NULL_INDEX when slot is free
|
||||
int colorIndex;
|
||||
|
||||
// contact index within set or graph color
|
||||
// B3_NULL_INDEX when slot is free
|
||||
int localIndex;
|
||||
|
||||
b3ContactEdge edges[2];
|
||||
int shapeIdA;
|
||||
int shapeIdB;
|
||||
int childIndex;
|
||||
|
||||
// A contact only belongs to an island if touching, otherwise B3_NULL_INDEX.
|
||||
int islandId;
|
||||
|
||||
// Index into the island's contacts array for O(1) swap-removal.
|
||||
// B3_NULL_INDEX when not in an island.
|
||||
int islandIndex;
|
||||
|
||||
// Back index into b3World::contacts
|
||||
int contactId;
|
||||
|
||||
// These are transient and cached for improved performance. B3_NULL_INDEX for static bodies.
|
||||
int bodySimIndexA;
|
||||
int bodySimIndexB;
|
||||
|
||||
// b3ContactFlags
|
||||
uint32_t flags;
|
||||
|
||||
b3Manifold* manifolds;
|
||||
int manifoldCount;
|
||||
|
||||
// Cache for contact recycling.
|
||||
b3Quat cachedRotationA;
|
||||
b3Quat cachedRotationB;
|
||||
b3Transform cachedRelativePose;
|
||||
|
||||
// Mixed friction and restitution
|
||||
float friction;
|
||||
|
||||
// Usage determined by b3_simMeshContact in simFlags
|
||||
union
|
||||
{
|
||||
b3ConvexContact convexContact;
|
||||
b3MeshContact meshContact;
|
||||
};
|
||||
|
||||
float restitution;
|
||||
float rollingResistance;
|
||||
b3Vec3 tangentVelocity;
|
||||
|
||||
// This is monotonically advanced when a contact is allocated in this slot
|
||||
// Used to check for invalid b3ContactId
|
||||
uint32_t generation;
|
||||
} b3Contact;
|
||||
|
||||
typedef struct b3ContactSpec
|
||||
{
|
||||
int contactId;
|
||||
|
||||
// Start of the global manifold constraint array
|
||||
int manifoldStart;
|
||||
uint16_t manifoldCount;
|
||||
} b3ContactSpec;
|
||||
|
||||
b3DeclareArray( b3ContactSpec );
|
||||
|
||||
void b3InitializeContactRegisters( void );
|
||||
|
||||
void b3CreateContact( b3World* world, b3Shape* shapeA, b3Shape* shapeB, int childIndex );
|
||||
void b3DestroyContact( b3World* world, b3Contact* contact, bool wakeBodies );
|
||||
|
||||
bool b3UpdateContact( b3World* world, int workerIndex, b3Contact* contact, b3Shape* shapeA, b3Vec3 localCenterA, b3WorldTransform xfA,
|
||||
b3Shape* shapeB, b3Vec3 localCenterB, b3WorldTransform xfB, bool isFast, b3Arena arena );
|
||||
|
||||
bool b3ComputeMeshManifolds( b3World* world, int workerIndex, b3Contact* contact, const b3Shape* shapeA, const int* materialMap,
|
||||
b3WorldTransform xfA, const b3Shape* shapeB, b3WorldTransform xfB, bool isFast, b3Arena arena );
|
||||
2472
vendor/box3d/src/src/contact_solver.c
vendored
Normal file
2472
vendor/box3d/src/src/contact_solver.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
73
vendor/box3d/src/src/contact_solver.h
vendored
Normal file
73
vendor/box3d/src/src/contact_solver.h
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "math_internal.h"
|
||||
#include "solver.h"
|
||||
|
||||
typedef struct b3ManifoldConstraintPoint
|
||||
{
|
||||
b3Vec3 rA, rB;
|
||||
float baseSeparation;
|
||||
float relativeVelocity;
|
||||
float normalImpulse;
|
||||
float totalNormalImpulse;
|
||||
float normalMass;
|
||||
float leverArm;
|
||||
} b3ManifoldConstraintPoint;
|
||||
|
||||
typedef struct b3ManifoldConstraint
|
||||
{
|
||||
// todo use pointer buffer
|
||||
b3ManifoldConstraintPoint points[4];
|
||||
int pointCount;
|
||||
b3Vec3 normal;
|
||||
b3Vec3 tangent1;
|
||||
b3Vec3 tangent2;
|
||||
b3Vec3 originA, originB;
|
||||
float twistMass;
|
||||
float twistImpulse;
|
||||
b3Matrix2 tangentMass;
|
||||
b3Vec2 frictionImpulse;
|
||||
b3Vec3 rollingImpulse;
|
||||
float tangentVelocity1;
|
||||
float tangentVelocity2;
|
||||
} b3ManifoldConstraint;
|
||||
|
||||
typedef struct b3ContactConstraint
|
||||
{
|
||||
b3ManifoldConstraint* constraints;
|
||||
struct b3Contact* contact;
|
||||
int indexA;
|
||||
int indexB;
|
||||
float invMassA, invMassB;
|
||||
b3Matrix3 invIA, invIB;
|
||||
b3Softness softness;
|
||||
b3Matrix3 rollingMass;
|
||||
float friction;
|
||||
float restitution;
|
||||
float rollingResistance;
|
||||
int manifoldCount;
|
||||
} b3ContactConstraint;
|
||||
|
||||
int b3GetWideContactConstraintByteCount( void );
|
||||
|
||||
// Overflow contacts don't fit into the constraint graph coloring
|
||||
void b3PrepareContacts_Overflow( b3StepContext* context );
|
||||
void b3WarmStartContacts_Overflow( b3StepContext* context );
|
||||
void b3SolveContacts_Overflow( b3StepContext* context, bool useBias );
|
||||
void b3ApplyRestitution_Overflow( b3StepContext* context );
|
||||
void b3StoreImpulses_Overflow( b3StepContext* context );
|
||||
|
||||
void b3PrepareContacts_Mesh( b3SolverBlock block, b3StepContext* context );
|
||||
void b3WarmStartContacts_Mesh( b3SolverBlock block, b3StepContext* context );
|
||||
void b3SolveContacts_Mesh( b3SolverBlock block, b3StepContext* context, bool useBias );
|
||||
void b3ApplyRestitution_Mesh( b3SolverBlock block, b3StepContext* context );
|
||||
void b3StoreImpulses_Mesh( b3SolverBlock block, b3StepContext* context, int workerIndex );
|
||||
|
||||
void b3PrepareContacts_Convex( b3SolverBlock block, b3StepContext* context );
|
||||
void b3WarmStartContacts_Convex( b3SolverBlock block, b3StepContext* context );
|
||||
void b3SolveContacts_Convex( b3SolverBlock block, b3StepContext* context, bool useBias );
|
||||
void b3ApplyRestitution_Convex( b3SolverBlock block, b3StepContext* context );
|
||||
void b3StoreImpulses_Convex( b3SolverBlock block, b3StepContext* context, int workerIndex );
|
||||
184
vendor/box3d/src/src/container.h
vendored
Normal file
184
vendor/box3d/src/src/container.h
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "algorithm.h"
|
||||
#include "core.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#define b3DeclareArray( T ) \
|
||||
typedef struct b3DynamicArray_##T \
|
||||
{ \
|
||||
struct T* data; \
|
||||
int count; \
|
||||
int capacity; \
|
||||
} b3DynamicArray_##T
|
||||
|
||||
#define b3DeclareArrayNative( T ) \
|
||||
typedef struct b3DynamicArray_##T \
|
||||
{ \
|
||||
T* data; \
|
||||
int count; \
|
||||
int capacity; \
|
||||
} b3DynamicArray_##T
|
||||
|
||||
// Define an array.
|
||||
// It may be zero initialized:
|
||||
// b3Array(int) myArray = { 0 };
|
||||
#define b3Array( T ) b3DynamicArray_##T
|
||||
|
||||
// Alternative to zero initialization
|
||||
#define b3Array_Create( a ) \
|
||||
do \
|
||||
{ \
|
||||
( a ).data = NULL; \
|
||||
( a ).count = 0; \
|
||||
( a ).capacity = 0; \
|
||||
} \
|
||||
while ( 0 )
|
||||
|
||||
#define b3Array_CreateN( a, n ) \
|
||||
do \
|
||||
{ \
|
||||
( a ).data = ( n ) > 0 ? b3GrowAlloc( NULL, 0, ( n ) * sizeof( *( a ).data ) ) : NULL; \
|
||||
( a ).count = 0; \
|
||||
( a ).capacity = ( n ); \
|
||||
} \
|
||||
while ( 0 )
|
||||
|
||||
#define b3Array_Destroy( a ) \
|
||||
do \
|
||||
{ \
|
||||
b3Free( ( a ).data, ( a ).capacity * sizeof( *( a ).data ) ); \
|
||||
( a ).data = NULL; \
|
||||
( a ).count = 0; \
|
||||
( a ).capacity = 0; \
|
||||
} \
|
||||
while ( 0 )
|
||||
|
||||
#define b3Array_Reserve( a, n ) \
|
||||
do \
|
||||
{ \
|
||||
if ( ( a ).capacity < n ) \
|
||||
{ \
|
||||
int oldSize = ( a ).capacity * sizeof( *( a ).data ); \
|
||||
int newSize = ( n ) * sizeof( *( a ).data ); \
|
||||
( a ).data = b3GrowAlloc( ( a ).data, oldSize, newSize ); \
|
||||
( a ).capacity = ( n ); \
|
||||
} \
|
||||
} \
|
||||
while ( 0 )
|
||||
|
||||
#define b3Array_Resize( a, n ) \
|
||||
do \
|
||||
{ \
|
||||
b3Array_Reserve( a, n ); \
|
||||
( a ).count = ( n ); \
|
||||
} \
|
||||
while ( 0 )
|
||||
|
||||
// Push a new element by value
|
||||
#define b3Array_Push( a, value ) \
|
||||
do \
|
||||
{ \
|
||||
if ( ( a ).count >= ( a ).capacity ) \
|
||||
{ \
|
||||
int oldSize = ( a ).capacity * sizeof( *( a ).data ); \
|
||||
int newCapacity = ( a ).capacity == 0 ? 8 : 2 * ( a ).capacity; \
|
||||
int newSize = newCapacity * sizeof( *( a ).data ); \
|
||||
( a ).data = b3GrowAlloc( ( a ).data, oldSize, newSize ); \
|
||||
( a ).capacity = newCapacity; \
|
||||
} \
|
||||
( a ).data[( a ).count++] = ( value ); \
|
||||
} \
|
||||
while ( 0 )
|
||||
|
||||
// Get a pointer to an element
|
||||
#define b3Array_Get( a, index ) ( B3_ASSERT( 0 <= ( index ) && ( index ) < ( a ).count ), ( a ).data + ( index ) )
|
||||
|
||||
// Create a new uninitialized element and return a pointer to it
|
||||
#define b3Array_Emplace( a ) \
|
||||
( b3EmplaceHelper( (void**)&( a ).data, &( a ).count, &( a ).capacity, sizeof( *( a ).data ) ) )
|
||||
|
||||
// Remove the last element and return it by value.
|
||||
#define b3Array_Pop( a ) ( B3_ASSERT( 0 < ( a ).count ), ( a ).data[-1 + ( a ).count--] )
|
||||
|
||||
// Add an uninitialized element and return its index.
|
||||
#define b3Array_AddIndex( a ) \
|
||||
( b3EmplaceHelper( (void**)&( a ).data, &( a ).count, &( a ).capacity, sizeof( *( a ).data ) ), ( a ).count - 1 )
|
||||
|
||||
// Append a contiguous run of values. _n is used to cache the input count while avoiding naming conflicts.
|
||||
#define b3Array_Append( a, src, n ) \
|
||||
do \
|
||||
{ \
|
||||
int _n = ( n ); \
|
||||
if ( ( a ).count + _n > ( a ).capacity ) \
|
||||
{ \
|
||||
int req = ( a ).count + _n; \
|
||||
int newCapacity = req > 2 ? req + ( req >> 1 ) : 8; \
|
||||
int oldSize = ( a ).capacity * sizeof( *( a ).data ); \
|
||||
int newSize = newCapacity * sizeof( *( a ).data ); \
|
||||
( a ).data = b3GrowAlloc( ( a ).data, oldSize, newSize ); \
|
||||
( a ).capacity = newCapacity; \
|
||||
} \
|
||||
memcpy( ( a ).data + ( a ).count, ( src ), _n * sizeof( *( a ).data ) ); \
|
||||
( a ).count += _n; \
|
||||
} \
|
||||
while ( 0 )
|
||||
|
||||
// Zero the entire allocated buffer (capacity, not just count).
|
||||
#define b3Array_MemZero( a ) \
|
||||
do \
|
||||
{ \
|
||||
if ( ( a ).capacity > 0 ) \
|
||||
{ \
|
||||
memset( ( a ).data, 0, ( a ).capacity * sizeof( *( a ).data ) ); \
|
||||
} \
|
||||
} \
|
||||
while ( 0 )
|
||||
|
||||
// Remove and element by swapping with the last element. If the index is the last element it returns
|
||||
// B3_NULL_INDEX, otherwise it returns the index of the last element (which is now out of bounds).
|
||||
#define b3Array_RemoveSwap( a, index ) b3RemoveHelper( ( a ).data, &( a ).count, ( index ), sizeof( *( a ).data ) )
|
||||
|
||||
B3_INLINE void* b3EmplaceHelper( void** data, int* count, int* capacity, int elem_size )
|
||||
{
|
||||
if ( *count >= *capacity )
|
||||
{
|
||||
int oldCapacity = *capacity;
|
||||
int oldSize = oldCapacity * elem_size;
|
||||
int newCapacity = ( oldCapacity == 0 ? 16 : 2 * oldCapacity );
|
||||
int newSize = newCapacity * elem_size;
|
||||
*data = b3GrowAlloc( *data, oldSize, newSize );
|
||||
*capacity = newCapacity;
|
||||
}
|
||||
return (char*)*data + ( *count )++ * elem_size;
|
||||
}
|
||||
|
||||
B3_INLINE int b3RemoveHelper( void* data, int* count, int index, int elementSize )
|
||||
{
|
||||
B3_ASSERT( 0 <= index && index < *count && "Array index out of bounds" );
|
||||
|
||||
( *count )--;
|
||||
if ( index != *count )
|
||||
{
|
||||
memcpy( (char*)data + index * elementSize, (char*)data + ( *count ) * elementSize, elementSize );
|
||||
return *count;
|
||||
}
|
||||
|
||||
return B3_NULL_INDEX;
|
||||
}
|
||||
|
||||
#define b3Array_Clear( a ) \
|
||||
do \
|
||||
{ \
|
||||
( a ).count = 0; \
|
||||
} \
|
||||
while ( 0 )
|
||||
|
||||
#define b3Array_ByteCount( a ) ( ( a ).capacity * (int)sizeof( *( a ).data ) )
|
||||
|
||||
b3DeclareArrayNative( int );
|
||||
1600
vendor/box3d/src/src/convex_manifold.c
vendored
Normal file
1600
vendor/box3d/src/src/convex_manifold.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
255
vendor/box3d/src/src/core.c
vendored
Normal file
255
vendor/box3d/src/src/core.c
vendored
Normal file
@@ -0,0 +1,255 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#if defined( B3_COMPILER_MSVC )
|
||||
// CRTDBG requires these to be included first
|
||||
#define _CRTDBG_MAP_ALLOC
|
||||
#include <crtdbg.h>
|
||||
#include <stdlib.h>
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include "box3d/constants.h"
|
||||
#include "box3d/math_functions.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef BOX2D_PROFILE
|
||||
|
||||
#include <tracy/TracyC.h>
|
||||
#define b3TracyCAlloc( ptr, size ) TracyCAlloc( ptr, size )
|
||||
#define b3TracyCFree( ptr ) TracyCFree( ptr )
|
||||
|
||||
#else
|
||||
|
||||
#define b3TracyCAlloc( ptr, size )
|
||||
#define b3TracyCFree( ptr )
|
||||
|
||||
#endif
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// This allows the user to change the length units at runtime
|
||||
static float b3_lengthUnitsPerMeter = 1.0f;
|
||||
|
||||
void b3SetLengthUnitsPerMeter( float lengthUnits )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( lengthUnits ) && lengthUnits > 0.0f );
|
||||
b3_lengthUnitsPerMeter = lengthUnits;
|
||||
}
|
||||
|
||||
float b3GetLengthUnitsPerMeter( void )
|
||||
{
|
||||
return b3_lengthUnitsPerMeter;
|
||||
}
|
||||
|
||||
static float b3_stallThreshold = FLT_MAX;
|
||||
|
||||
void b3SetStallThreshold( float seconds )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( seconds ) && seconds > 0.0f );
|
||||
b3_stallThreshold = seconds;
|
||||
}
|
||||
|
||||
float b3GetStallThreshold( void )
|
||||
{
|
||||
return b3_stallThreshold;
|
||||
}
|
||||
|
||||
static int b3DefaultAssertFcn( const char* condition, const char* fileName, int lineNumber )
|
||||
{
|
||||
printf( "BOX3D ASSERTION: %s, %s, line %d\n", condition, fileName, lineNumber );
|
||||
|
||||
// return non-zero to break to debugger
|
||||
return 1;
|
||||
}
|
||||
|
||||
b3AssertFcn* b3AssertHandler = b3DefaultAssertFcn;
|
||||
|
||||
void b3SetAssertFcn( b3AssertFcn* assertFcn )
|
||||
{
|
||||
B3_ASSERT( assertFcn != NULL );
|
||||
b3AssertHandler = assertFcn;
|
||||
}
|
||||
|
||||
#if !defined( NDEBUG ) || defined( B3_ENABLE_ASSERT )
|
||||
int b3InternalAssert( const char* condition, const char* fileName, int lineNumber )
|
||||
{
|
||||
int result = b3AssertHandler( condition, fileName, lineNumber );
|
||||
if ( result )
|
||||
{
|
||||
B3_BREAKPOINT;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void b3DefaultLogFcn( const char* message )
|
||||
{
|
||||
printf( "Box3D: %s\n", message );
|
||||
}
|
||||
|
||||
b3LogFcn* b3LogHandler = b3DefaultLogFcn;
|
||||
|
||||
void b3SetLogFcn( b3LogFcn* logFcn )
|
||||
{
|
||||
B3_ASSERT( logFcn != NULL );
|
||||
b3LogHandler = logFcn;
|
||||
}
|
||||
|
||||
void b3Log( const char* format, ... )
|
||||
{
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
char buffer[512];
|
||||
vsnprintf( buffer, sizeof( buffer ), format, args );
|
||||
b3LogHandler( buffer );
|
||||
va_end( args );
|
||||
}
|
||||
|
||||
b3Version b3GetVersion( void )
|
||||
{
|
||||
return (b3Version){ 0, 1, 0 };
|
||||
}
|
||||
|
||||
bool b3IsDoublePrecision( void )
|
||||
{
|
||||
#if defined( BOX3D_DOUBLE_PRECISION )
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static b3AllocFcn* b3_allocFcn = NULL;
|
||||
static b3FreeFcn* b3_freeFcn = NULL;
|
||||
|
||||
b3AtomicInt b3_byteCount;
|
||||
|
||||
void b3SetAllocator( b3AllocFcn* allocFcn, b3FreeFcn* freeFcn )
|
||||
{
|
||||
b3_allocFcn = allocFcn;
|
||||
b3_freeFcn = freeFcn;
|
||||
}
|
||||
|
||||
void* b3Alloc( size_t size )
|
||||
{
|
||||
if ( size == 0 )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// This could cause some sharing issues, however Box3D rarely calls b3Alloc.
|
||||
// todo this is not true, Box3D allocates a lot.
|
||||
b3AtomicFetchAddInt( &b3_byteCount, (int)size );
|
||||
|
||||
// Allocation must be a multiple of B3_ALIGNMENT (required by spec).
|
||||
// https://en.cppreference.com/w/c/memory/aligned_alloc
|
||||
int alignedSize = ( ( (int)size - 1 ) | ( B3_ALIGNMENT - 1 ) ) + 1;
|
||||
|
||||
if ( b3_allocFcn != NULL )
|
||||
{
|
||||
void* ptr = b3_allocFcn( alignedSize, B3_ALIGNMENT );
|
||||
b3TracyCAlloc( ptr, size );
|
||||
|
||||
B3_ASSERT( ptr != NULL );
|
||||
B3_ASSERT( ( (uintptr_t)ptr & ( B3_ALIGNMENT - 1 ) ) == 0 );
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
#ifdef B3_PLATFORM_WINDOWS
|
||||
void* ptr = _aligned_malloc( alignedSize, B3_ALIGNMENT );
|
||||
#elif defined( B3_PLATFORM_ANDROID )
|
||||
void* ptr = NULL;
|
||||
if ( posix_memalign( &ptr, B3_ALIGNMENT, alignedSize ) != 0 )
|
||||
{
|
||||
// allocation failed, exit the application
|
||||
exit( EXIT_FAILURE );
|
||||
}
|
||||
#else
|
||||
void* ptr = aligned_alloc( B3_ALIGNMENT, alignedSize );
|
||||
#endif
|
||||
|
||||
b3TracyCAlloc( ptr, size );
|
||||
|
||||
B3_ASSERT( ptr != NULL );
|
||||
B3_ASSERT( ( (uintptr_t)ptr & ( B3_ALIGNMENT - 1 ) ) == 0 );
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void b3Free( void* mem, size_t size )
|
||||
{
|
||||
if ( mem == NULL )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
b3TracyCFree( mem );
|
||||
|
||||
if ( b3_freeFcn != NULL )
|
||||
{
|
||||
b3_freeFcn( mem );
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef B3_PLATFORM_WINDOWS
|
||||
_aligned_free( mem );
|
||||
#else
|
||||
free( mem );
|
||||
#endif
|
||||
}
|
||||
|
||||
b3AtomicFetchAddInt( &b3_byteCount, -(int)size );
|
||||
}
|
||||
|
||||
void* b3GrowAlloc( void* oldMem, int oldSize, int newSize )
|
||||
{
|
||||
B3_ASSERT( newSize > oldSize );
|
||||
void* newMem = b3Alloc( newSize );
|
||||
if ( oldSize > 0 )
|
||||
{
|
||||
memcpy( newMem, oldMem, oldSize );
|
||||
b3Free( oldMem, oldSize );
|
||||
}
|
||||
return newMem;
|
||||
}
|
||||
|
||||
int b3GetByteCount( void )
|
||||
{
|
||||
return b3AtomicLoadInt( &b3_byteCount );
|
||||
}
|
||||
|
||||
void* b3AllocZeroed( size_t size )
|
||||
{
|
||||
void* mem = b3Alloc( size );
|
||||
memset( mem, 0, size );
|
||||
return mem;
|
||||
}
|
||||
|
||||
// Not used. Keeping around in case I need this.
|
||||
void b3StrCpy( char* dst, int size, const char* src )
|
||||
{
|
||||
B3_ASSERT( size > 0 );
|
||||
|
||||
if ( src != NULL )
|
||||
{
|
||||
#if defined( _MSC_VER )
|
||||
strncpy_s( dst, size, src, size - 1 );
|
||||
#else
|
||||
strncpy( dst, src, size - 1 );
|
||||
dst[size - 1] = 0;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
memset( dst, 0, size );
|
||||
}
|
||||
}
|
||||
161
vendor/box3d/src/src/core.h
vendored
Normal file
161
vendor/box3d/src/src/core.h
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "box3d/base.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define B3_DEBUG 0
|
||||
#else
|
||||
#define B3_DEBUG 1
|
||||
#endif
|
||||
|
||||
// Define platform
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define B3_PLATFORM_WINDOWS
|
||||
#elif defined( __ANDROID__ )
|
||||
#define B3_PLATFORM_ANDROID
|
||||
#elif defined( __linux__ )
|
||||
#define B3_PLATFORM_LINUX
|
||||
#elif defined( __APPLE__ )
|
||||
#include <TargetConditionals.h>
|
||||
#if defined( TARGET_OS_IPHONE ) && !TARGET_OS_IPHONE
|
||||
#define B3_PLATFORM_MACOS
|
||||
#else
|
||||
#define B3_PLATFORM_IOS
|
||||
#endif
|
||||
#elif defined( __EMSCRIPTEN__ )
|
||||
#define B3_PLATFORM_WASM
|
||||
#else
|
||||
#define B3_PLATFORM_UNKNOWN
|
||||
#endif
|
||||
|
||||
// Define CPU
|
||||
#if defined( __x86_64__ ) || defined( _M_X64 ) || defined( __i386__ ) || defined( _M_IX86 )
|
||||
#define B3_CPU_X86_X64
|
||||
#elif defined( __aarch64__ ) || defined( _M_ARM64 ) || defined( __arm__ ) || defined( _M_ARM )
|
||||
#define B3_CPU_ARM
|
||||
#elif defined( __EMSCRIPTEN__ )
|
||||
#define B3_CPU_WASM
|
||||
#else
|
||||
#define B3_CPU_UNKNOWN
|
||||
#endif
|
||||
|
||||
// Define SIMD
|
||||
#if defined( BOX3D_DISABLE_SIMD )
|
||||
#define B3_SIMD_NONE
|
||||
#define B3_SIMD_WIDTH 4
|
||||
//#pragma message("B3_SIMD_NONE")
|
||||
#else
|
||||
#if defined( B3_CPU_X86_X64 )
|
||||
#define B3_SIMD_SSE2
|
||||
#define B3_SIMD_WIDTH 4
|
||||
//#pragma message("B3_SIMD_SSE2")
|
||||
#elif defined( B3_CPU_ARM )
|
||||
#define B3_SIMD_NEON
|
||||
#define B3_SIMD_WIDTH 4
|
||||
//#pragma message("B3_SIMD_NEON")
|
||||
#elif defined( B3_CPU_WASM )
|
||||
#define B3_CPU_WASM
|
||||
#define B3_SIMD_SSE2
|
||||
#define B3_SIMD_WIDTH 4
|
||||
//#pragma message("B3_SIMD_SSE2")
|
||||
#else
|
||||
#define B3_SIMD_NONE
|
||||
#define B3_SIMD_WIDTH 4
|
||||
//#pragma message("B3_SIMD_NONE")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Define compiler
|
||||
#if defined( __clang__ )
|
||||
#define B3_COMPILER_CLANG
|
||||
#elif defined( __GNUC__ )
|
||||
#define B3_COMPILER_GCC
|
||||
#elif defined( _MSC_VER )
|
||||
#define B3_COMPILER_MSVC
|
||||
#endif
|
||||
|
||||
/// Tracy profiler instrumentation
|
||||
/// https://github.com/wolfpld/tracy
|
||||
#ifdef BOX3D_PROFILE
|
||||
#include <tracy/TracyC.h>
|
||||
#define b3TracyCZoneC( ctx, color, active ) TracyCZoneC( ctx, color, active )
|
||||
#define b3TracyCZoneNC( ctx, name, color, active ) TracyCZoneNC( ctx, name, color, active )
|
||||
#define b3TracyCZoneEnd( ctx ) TracyCZoneEnd( ctx )
|
||||
#define b3TracyCFrame TracyCFrameMark
|
||||
#else
|
||||
#define b3TracyCZoneC( ctx, color, active )
|
||||
#define b3TracyCZoneNC( ctx, name, color, active )
|
||||
#define b3TracyCZoneEnd( ctx )
|
||||
#define b3TracyCFrame
|
||||
#endif
|
||||
|
||||
// clang-format on
|
||||
|
||||
typedef struct b3AtomicInt
|
||||
{
|
||||
int value;
|
||||
} b3AtomicInt;
|
||||
|
||||
typedef struct b3AtomicU32
|
||||
{
|
||||
uint32_t value;
|
||||
} b3AtomicU32;
|
||||
|
||||
// Minimum memory alignment used for all allocations
|
||||
#define B3_ALIGNMENT 16
|
||||
|
||||
// Returns the number of elements of an array
|
||||
#define B3_ARRAY_COUNT( A ) (int)( sizeof( A ) / sizeof( A[0] ) )
|
||||
|
||||
// Used to prevent the compiler from warning about unused variables
|
||||
#define B3_UNUSED( ... ) (void)sizeof( ( __VA_ARGS__, 0 ) )
|
||||
|
||||
// Use to validate definitions. Do not take my cookie.
|
||||
#define B3_SECRET_COOKIE 1152023
|
||||
|
||||
#define B3_CHECK_DEF( DEF ) B3_ASSERT( DEF->internalValue == B3_SECRET_COOKIE )
|
||||
#define B3_CHECK_JOINT_DEF( DEF ) B3_ASSERT( DEF->base.internalValue == B3_SECRET_COOKIE )
|
||||
|
||||
// These macros help avoid sizeof bugs
|
||||
#define B3_ALLOC( T, N ) (T*)b3Alloc( N * sizeof( T ) );
|
||||
#define B3_FREE( M, T, N ) b3Free( M, N * sizeof( T ) );
|
||||
|
||||
void* b3Alloc( size_t size );
|
||||
void* b3AllocZeroed( size_t size );
|
||||
void b3Free( void* mem, size_t size );
|
||||
void* b3GrowAlloc( void* oldMem, int oldSize, int newSize );
|
||||
|
||||
void b3Log( const char* format, ... );
|
||||
|
||||
// Geometry content hashes reserve zero to mean unhashed
|
||||
static inline uint32_t b3NonZeroHash( uint32_t hash )
|
||||
{
|
||||
return hash != 0 ? hash : 1;
|
||||
}
|
||||
|
||||
typedef struct b3Mutex b3Mutex;
|
||||
b3Mutex* b3CreateMutex( void );
|
||||
void b3DestroyMutex( b3Mutex* m );
|
||||
void b3LockMutex( b3Mutex* m );
|
||||
void b3UnlockMutex( b3Mutex* m );
|
||||
|
||||
typedef struct b3Semaphore b3Semaphore;
|
||||
b3Semaphore* b3CreateSemaphore( int initCount );
|
||||
void b3DestroySemaphore( b3Semaphore* s );
|
||||
void b3WaitSemaphore( b3Semaphore* s );
|
||||
void b3SignalSemaphore( b3Semaphore* s );
|
||||
|
||||
typedef void b3ThreadFunction( void* context );
|
||||
typedef struct b3Thread b3Thread;
|
||||
// Name may be NULL, otherwise it is copied.
|
||||
b3Thread* b3CreateThread( b3ThreadFunction* function, void* context, const char* name );
|
||||
void b3JoinThread( b3Thread* t );
|
||||
|
||||
void b3StrCpy( char* dst, int size, const char* src );
|
||||
134
vendor/box3d/src/src/ctz.h
vendored
Normal file
134
vendor/box3d/src/src/ctz.h
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "box3d/base.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined( _MSC_VER ) && !defined( __clang__ )
|
||||
|
||||
#include <intrin.h>
|
||||
|
||||
// https://en.wikipedia.org/wiki/Find_first_set
|
||||
|
||||
static inline uint32_t b3CTZ32( uint32_t block )
|
||||
{
|
||||
unsigned long index;
|
||||
_BitScanForward( &index, block );
|
||||
return index;
|
||||
}
|
||||
|
||||
// This function doesn't need to be fast, so using the Ivy Bridge fallback.
|
||||
static inline uint32_t b3CLZ32( uint32_t value )
|
||||
{
|
||||
#if 1
|
||||
|
||||
// Use BSR (Bit Scan Reverse) which is available on Ivy Bridge
|
||||
unsigned long index;
|
||||
if ( _BitScanReverse( &index, value ) )
|
||||
{
|
||||
// BSR gives the index of the most significant 1-bit
|
||||
// We need to invert this to get the number of leading zeros
|
||||
return 31 - index;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If x is 0, BSR sets the zero flag and doesn't modify index
|
||||
// LZCNT should return 32 for an input of 0
|
||||
return 32;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
return __lzcnt( value );
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t b3CTZ64( uint64_t block )
|
||||
{
|
||||
unsigned long index;
|
||||
|
||||
#ifdef _WIN64
|
||||
_BitScanForward64( &index, block );
|
||||
#else
|
||||
// 32-bit fall back
|
||||
if ( (uint32_t)block != 0 )
|
||||
{
|
||||
_BitScanForward( &index, (uint32_t)block );
|
||||
}
|
||||
else
|
||||
{
|
||||
_BitScanForward( &index, (uint32_t)( block >> 32 ) );
|
||||
index += 32;
|
||||
}
|
||||
#endif
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static inline int b3PopCount64( uint64_t block )
|
||||
{
|
||||
return (int)__popcnt64( block );
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline uint32_t b3CTZ32( uint32_t block )
|
||||
{
|
||||
return __builtin_ctz( block );
|
||||
}
|
||||
|
||||
static inline uint32_t b3CLZ32( uint32_t value )
|
||||
{
|
||||
return __builtin_clz( value );
|
||||
}
|
||||
|
||||
static inline uint32_t b3CTZ64( uint64_t block )
|
||||
{
|
||||
return __builtin_ctzll( block );
|
||||
}
|
||||
|
||||
static inline int b3PopCount64( uint64_t block )
|
||||
{
|
||||
return __builtin_popcountll( block );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static inline bool b3IsPowerOf2( int x )
|
||||
{
|
||||
return ( x & ( x - 1 ) ) == 0;
|
||||
}
|
||||
|
||||
static inline int b3BoundingPowerOf2( int x )
|
||||
{
|
||||
if ( x <= 1 )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 32 - (int)b3CLZ32( (uint32_t)x - 1 );
|
||||
}
|
||||
|
||||
static inline int b3RoundUpPowerOf2( int x )
|
||||
{
|
||||
if ( x <= 1 )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1 << ( 32 - (int)b3CLZ32( (uint32_t)x - 1 ) );
|
||||
}
|
||||
|
||||
static inline int b3LowerPowerOf2Exponent( int x )
|
||||
{
|
||||
B3_ASSERT( x > 0 );
|
||||
int clz = (int)b3CLZ32( (uint32_t)x );
|
||||
|
||||
// Position of most significant bit = floor(log2(M))
|
||||
return 31 - clz;
|
||||
}
|
||||
1904
vendor/box3d/src/src/distance.c
vendored
Normal file
1904
vendor/box3d/src/src/distance.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
579
vendor/box3d/src/src/distance_joint.c
vendored
Normal file
579
vendor/box3d/src/src/distance_joint.c
vendored
Normal file
@@ -0,0 +1,579 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "body.h"
|
||||
#include "core.h"
|
||||
#include "joint.h"
|
||||
#include "physics_world.h"
|
||||
#include "recording.h"
|
||||
#include "solver.h"
|
||||
#include "solver_set.h"
|
||||
|
||||
// needed for dll export
|
||||
#include "box3d/box3d.h"
|
||||
|
||||
void b3DistanceJoint_SetLength( b3JointId jointId, float length )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, DistanceJointSetLength, jointId, length );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
|
||||
joint->length = b3ClampFloat( length, B3_LINEAR_SLOP, B3_HUGE );
|
||||
joint->impulse = 0.0f;
|
||||
joint->lowerImpulse = 0.0f;
|
||||
joint->upperImpulse = 0.0f;
|
||||
}
|
||||
|
||||
float b3DistanceJoint_GetLength( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
return joint->length;
|
||||
}
|
||||
|
||||
void b3DistanceJoint_EnableLimit( b3JointId jointId, bool enableLimit )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, DistanceJointEnableLimit, jointId, enableLimit );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
joint->enableLimit = enableLimit;
|
||||
}
|
||||
|
||||
bool b3DistanceJoint_IsLimitEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
return joint->distanceJoint.enableLimit;
|
||||
}
|
||||
|
||||
void b3DistanceJoint_SetLengthRange( b3JointId jointId, float minLength, float maxLength )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, DistanceJointSetLengthRange, jointId, minLength, maxLength );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
|
||||
minLength = b3ClampFloat( minLength, B3_LINEAR_SLOP, B3_HUGE );
|
||||
maxLength = b3ClampFloat( maxLength, B3_LINEAR_SLOP, B3_HUGE );
|
||||
joint->minLength = b3MinFloat( minLength, maxLength );
|
||||
joint->maxLength = b3MaxFloat( minLength, maxLength );
|
||||
joint->impulse = 0.0f;
|
||||
joint->lowerImpulse = 0.0f;
|
||||
joint->upperImpulse = 0.0f;
|
||||
}
|
||||
|
||||
float b3DistanceJoint_GetMinLength( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
return joint->minLength;
|
||||
}
|
||||
|
||||
float b3DistanceJoint_GetMaxLength( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
return joint->maxLength;
|
||||
}
|
||||
|
||||
float b3DistanceJoint_GetCurrentLength( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
|
||||
b3World* world = b3GetUnlockedWorld( jointId.world0 );
|
||||
if ( world == NULL )
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA );
|
||||
b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB );
|
||||
|
||||
b3Pos pA = b3TransformWorldPoint( transformA, base->localFrameA.p );
|
||||
b3Pos pB = b3TransformWorldPoint( transformB, base->localFrameB.p );
|
||||
b3Vec3 d = b3SubPos( pB, pA );
|
||||
float length = b3Length( d );
|
||||
return length;
|
||||
}
|
||||
|
||||
void b3DistanceJoint_EnableSpring( b3JointId jointId, bool enableSpring )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, DistanceJointEnableSpring, jointId, enableSpring );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
base->distanceJoint.enableSpring = enableSpring;
|
||||
}
|
||||
|
||||
bool b3DistanceJoint_IsSpringEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
return base->distanceJoint.enableSpring;
|
||||
}
|
||||
|
||||
|
||||
void b3DistanceJoint_SetSpringForceRange( b3JointId jointId, float lowerForce, float upperForce )
|
||||
{
|
||||
B3_ASSERT( lowerForce <= upperForce );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, DistanceJointSetSpringForceRange, jointId, lowerForce, upperForce );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
base->distanceJoint.lowerSpringForce = lowerForce;
|
||||
base->distanceJoint.upperSpringForce = upperForce;
|
||||
}
|
||||
|
||||
void b3DistanceJoint_GetSpringForceRange( b3JointId jointId, float* lowerForce, float* upperForce )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
*lowerForce = base->distanceJoint.lowerSpringForce;
|
||||
*upperForce = base->distanceJoint.upperSpringForce;
|
||||
}
|
||||
|
||||
void b3DistanceJoint_SetSpringHertz( b3JointId jointId, float hertz )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, DistanceJointSetSpringHertz, jointId, hertz );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
base->distanceJoint.hertz = hertz;
|
||||
}
|
||||
|
||||
void b3DistanceJoint_SetSpringDampingRatio( b3JointId jointId, float dampingRatio )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, DistanceJointSetSpringDampingRatio, jointId, dampingRatio );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
base->distanceJoint.dampingRatio = dampingRatio;
|
||||
}
|
||||
|
||||
float b3DistanceJoint_GetSpringHertz( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
return joint->hertz;
|
||||
}
|
||||
|
||||
float b3DistanceJoint_GetSpringDampingRatio( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
return joint->dampingRatio;
|
||||
}
|
||||
|
||||
void b3DistanceJoint_EnableMotor( b3JointId jointId, bool enableMotor )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, DistanceJointEnableMotor, jointId, enableMotor );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
if ( enableMotor != joint->distanceJoint.enableMotor )
|
||||
{
|
||||
joint->distanceJoint.enableMotor = enableMotor;
|
||||
joint->distanceJoint.motorImpulse = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
bool b3DistanceJoint_IsMotorEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
return joint->distanceJoint.enableMotor;
|
||||
}
|
||||
|
||||
void b3DistanceJoint_SetMotorSpeed( b3JointId jointId, float motorSpeed )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, DistanceJointSetMotorSpeed, jointId, motorSpeed );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
joint->distanceJoint.motorSpeed = motorSpeed;
|
||||
}
|
||||
|
||||
float b3DistanceJoint_GetMotorSpeed( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
return joint->distanceJoint.motorSpeed;
|
||||
}
|
||||
|
||||
float b3DistanceJoint_GetMotorForce( b3JointId jointId )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
return world->inv_h * base->distanceJoint.motorImpulse;
|
||||
}
|
||||
|
||||
void b3DistanceJoint_SetMaxMotorForce( b3JointId jointId, float force )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, DistanceJointSetMaxMotorForce, jointId, force );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
joint->distanceJoint.maxMotorForce = force;
|
||||
}
|
||||
|
||||
float b3DistanceJoint_GetMaxMotorForce( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint );
|
||||
return joint->distanceJoint.maxMotorForce;
|
||||
}
|
||||
|
||||
b3Vec3 b3GetDistanceJointForce( b3World* world, b3JointSim* base )
|
||||
{
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
|
||||
b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA );
|
||||
b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB );
|
||||
|
||||
b3Pos pA = b3TransformWorldPoint( transformA, base->localFrameA.p );
|
||||
b3Pos pB = b3TransformWorldPoint( transformB, base->localFrameB.p );
|
||||
b3Vec3 d = b3SubPos( pB, pA );
|
||||
b3Vec3 axis = b3Normalize( d );
|
||||
float force = ( joint->impulse + joint->lowerImpulse - joint->upperImpulse + joint->motorImpulse ) * world->inv_h;
|
||||
return b3MulSV( force, axis );
|
||||
}
|
||||
|
||||
// 1-D constrained system
|
||||
// m (v2 - v1) = lambda
|
||||
// v2 + (beta/h) * x1 + gamma * lambda = 0, gamma has units of inverse mass.
|
||||
// x2 = x1 + h * v2
|
||||
|
||||
// 1-D mass-damper-spring system
|
||||
// m (v2 - v1) + h * d * v2 + h * k *
|
||||
|
||||
// C = norm(p2 - p1) - L
|
||||
// u = (p2 - p1) / norm(p2 - p1)
|
||||
// Cdot = dot(u, v2 + cross(w2, r2) - v1 - cross(w1, r1))
|
||||
// J = [-u -cross(r1, u) u cross(r2, u)]
|
||||
// K = J * invM * JT
|
||||
// = invMass1 + invI1 * cross(r1, u)^2 + invMass2 + invI2 * cross(r2, u)^2
|
||||
|
||||
void b3PrepareDistanceJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_distanceJoint );
|
||||
|
||||
// chase body id to the solver set where the body lives
|
||||
int idA = base->bodyIdA;
|
||||
int idB = base->bodyIdB;
|
||||
|
||||
b3World* world = context->world;
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, idA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, idB );
|
||||
|
||||
B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet );
|
||||
|
||||
b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex );
|
||||
b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex );
|
||||
|
||||
int localIndexA = bodyA->localIndex;
|
||||
int localIndexB = bodyB->localIndex;
|
||||
|
||||
b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA );
|
||||
b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB );
|
||||
|
||||
float mA = bodySimA->invMass;
|
||||
b3Matrix3 iA = bodySimA->invInertiaWorld;
|
||||
float mB = bodySimB->invMass;
|
||||
b3Matrix3 iB = bodySimB->invInertiaWorld;
|
||||
|
||||
base->invMassA = mA;
|
||||
base->invMassB = mB;
|
||||
base->invIA = iA;
|
||||
base->invIB = iB;
|
||||
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
|
||||
joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX;
|
||||
joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX;
|
||||
|
||||
// initial anchors in world space
|
||||
joint->anchorA = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) );
|
||||
joint->anchorB = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) );
|
||||
joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center );
|
||||
|
||||
b3Vec3 rA = joint->anchorA;
|
||||
b3Vec3 rB = joint->anchorB;
|
||||
b3Vec3 separation = b3Add( b3Sub( rB, rA ), joint->deltaCenter );
|
||||
b3Vec3 axis = b3Normalize( separation );
|
||||
|
||||
// compute effective mass
|
||||
b3Vec3 crA = b3Cross( rA, axis );
|
||||
b3Vec3 crB = b3Cross( rB, axis );
|
||||
float k = mA + mB + b3Dot( crA, b3MulMV( iA, crA ) ) + b3Dot( crB, b3MulMV( iB, crB ) );
|
||||
joint->axialMass = k > 0.0f ? 1.0f / k : 0.0f;
|
||||
|
||||
joint->distanceSoftness = b3MakeSoft( joint->hertz, joint->dampingRatio, context->h );
|
||||
|
||||
if ( context->enableWarmStarting == false )
|
||||
{
|
||||
joint->impulse = 0.0f;
|
||||
joint->lowerImpulse = 0.0f;
|
||||
joint->upperImpulse = 0.0f;
|
||||
joint->motorImpulse = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void b3WarmStartDistanceJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_distanceJoint );
|
||||
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->anchorA );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->anchorB );
|
||||
|
||||
b3Vec3 ds = b3Add( b3Sub( stateB->deltaPosition, stateA->deltaPosition ), b3Sub( rB, rA ) );
|
||||
b3Vec3 separation = b3Add( joint->deltaCenter, ds );
|
||||
b3Vec3 axis = b3Normalize( separation );
|
||||
|
||||
float axialImpulse = joint->impulse + joint->lowerImpulse - joint->upperImpulse + joint->motorImpulse;
|
||||
b3Vec3 P = b3MulSV( axialImpulse, axis );
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->linearVelocity = b3MulSub( stateA->linearVelocity, mA, P );
|
||||
stateA->angularVelocity = b3Sub( stateA->angularVelocity, b3MulMV( iA, b3Cross( rA, P ) ) );
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->linearVelocity = b3MulAdd( stateB->linearVelocity, mB, P );
|
||||
stateB->angularVelocity = b3Add( stateB->angularVelocity, b3MulMV( iB, b3Cross( rB, P ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
void b3SolveDistanceJoint( b3JointSim* base, b3StepContext* context, bool useBias )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_distanceJoint );
|
||||
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 vA = stateA->linearVelocity;
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 vB = stateB->linearVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
|
||||
// current anchors
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->anchorA );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->anchorB );
|
||||
|
||||
// current separation
|
||||
b3Vec3 ds = b3Add( b3Sub( stateB->deltaPosition, stateA->deltaPosition ), b3Sub( rB, rA ) );
|
||||
b3Vec3 separation = b3Add( joint->deltaCenter, ds );
|
||||
|
||||
float length = b3Length( separation );
|
||||
b3Vec3 axis = b3Normalize( separation );
|
||||
|
||||
// joint is soft if
|
||||
// - spring is enabled
|
||||
// - and (joint limit is disabled or limits are not equal)
|
||||
if ( joint->enableSpring && ( joint->minLength < joint->maxLength || joint->enableLimit == false ) )
|
||||
{
|
||||
// spring
|
||||
if ( joint->hertz > 0.0f )
|
||||
{
|
||||
// Cdot = dot(u, v + cross(w, r))
|
||||
b3Vec3 vr = b3Add( b3Sub( vB, vA ), b3Sub( b3Cross( wB, rB ), b3Cross( wA, rA ) ) );
|
||||
float Cdot = b3Dot( axis, vr );
|
||||
float C = length - joint->length;
|
||||
float bias = joint->distanceSoftness.biasRate * C;
|
||||
|
||||
float m = joint->distanceSoftness.massScale * joint->axialMass;
|
||||
float oldImpulse = joint->impulse;
|
||||
float impulse = -m * ( Cdot + bias ) - joint->distanceSoftness.impulseScale * oldImpulse;
|
||||
float h = context->h;
|
||||
joint->impulse = b3ClampFloat( joint->impulse + impulse, joint->lowerSpringForce * h, joint->upperSpringForce * h );
|
||||
impulse = joint->impulse - oldImpulse;
|
||||
|
||||
b3Vec3 P = b3MulSV( impulse, axis );
|
||||
vA = b3MulSub( vA, mA, P );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) );
|
||||
vB = b3MulAdd( vB, mB, P );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) );
|
||||
}
|
||||
|
||||
if ( joint->enableLimit )
|
||||
{
|
||||
// lower limit
|
||||
{
|
||||
b3Vec3 vr = b3Add( b3Sub( vB, vA ), b3Sub( b3Cross( wB, rB ), b3Cross( wA, rA ) ) );
|
||||
float Cdot = b3Dot( axis, vr );
|
||||
|
||||
float C = length - joint->minLength;
|
||||
|
||||
float bias = 0.0f;
|
||||
float massCoeff = 1.0f;
|
||||
float impulseCoeff = 0.0f;
|
||||
if ( C > 0.0f )
|
||||
{
|
||||
// speculative
|
||||
bias = C * context->inv_h;
|
||||
}
|
||||
else if ( useBias )
|
||||
{
|
||||
bias = base->constraintSoftness.biasRate * C;
|
||||
massCoeff = base->constraintSoftness.massScale;
|
||||
impulseCoeff = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
float impulse = -massCoeff * joint->axialMass * ( Cdot + bias ) - impulseCoeff * joint->lowerImpulse;
|
||||
float newImpulse = b3MaxFloat( 0.0f, joint->lowerImpulse + impulse );
|
||||
impulse = newImpulse - joint->lowerImpulse;
|
||||
joint->lowerImpulse = newImpulse;
|
||||
|
||||
b3Vec3 P = b3MulSV( impulse, axis );
|
||||
vA = b3MulSub( vA, mA, P );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) );
|
||||
vB = b3MulAdd( vB, mB, P );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) );
|
||||
}
|
||||
|
||||
// upper
|
||||
{
|
||||
b3Vec3 vr = b3Add( b3Sub( vA, vB ), b3Sub( b3Cross( wA, rA ), b3Cross( wB, rB ) ) );
|
||||
float Cdot = b3Dot( axis, vr );
|
||||
|
||||
float C = joint->maxLength - length;
|
||||
|
||||
float bias = 0.0f;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( C > 0.0f )
|
||||
{
|
||||
// speculative
|
||||
bias = C * context->inv_h;
|
||||
}
|
||||
else if ( useBias )
|
||||
{
|
||||
bias = base->constraintSoftness.biasRate * C;
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
float impulse = -massScale * joint->axialMass * ( Cdot + bias ) - impulseScale * joint->upperImpulse;
|
||||
float newImpulse = b3MaxFloat( 0.0f, joint->upperImpulse + impulse );
|
||||
impulse = newImpulse - joint->upperImpulse;
|
||||
joint->upperImpulse = newImpulse;
|
||||
|
||||
b3Vec3 P = b3MulSV( -impulse, axis );
|
||||
vA = b3MulSub( vA, mA, P );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) );
|
||||
vB = b3MulAdd( vB, mB, P );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( joint->enableMotor )
|
||||
{
|
||||
b3Vec3 vr = b3Add( b3Sub( vB, vA ), b3Sub( b3Cross( wB, rB ), b3Cross( wA, rA ) ) );
|
||||
float Cdot = b3Dot( axis, vr );
|
||||
float impulse = joint->axialMass * ( joint->motorSpeed - Cdot );
|
||||
float oldImpulse = joint->motorImpulse;
|
||||
float maxImpulse = context->h * joint->maxMotorForce;
|
||||
joint->motorImpulse = b3ClampFloat( joint->motorImpulse + impulse, -maxImpulse, maxImpulse );
|
||||
impulse = joint->motorImpulse - oldImpulse;
|
||||
|
||||
b3Vec3 P = b3MulSV( impulse, axis );
|
||||
vA = b3MulSub( vA, mA, P );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) );
|
||||
vB = b3MulAdd( vB, mB, P );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// rigid constraint
|
||||
b3Vec3 vr = b3Add( b3Sub( vB, vA ), b3Sub( b3Cross( wB, rB ), b3Cross( wA, rA ) ) );
|
||||
float Cdot = b3Dot( axis, vr );
|
||||
|
||||
float C = length - joint->length;
|
||||
|
||||
float bias = 0.0f;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( useBias )
|
||||
{
|
||||
bias = base->constraintSoftness.biasRate * C;
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
float impulse = -massScale * joint->axialMass * ( Cdot + bias ) - impulseScale * joint->impulse;
|
||||
joint->impulse += impulse;
|
||||
|
||||
b3Vec3 P = b3MulSV( impulse, axis );
|
||||
vA = b3MulSub( vA, mA, P );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) );
|
||||
vB = b3MulAdd( vB, mB, P );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) );
|
||||
}
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->linearVelocity = vA;
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->linearVelocity = vB;
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
|
||||
void b3DrawDistanceJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_distanceJoint );
|
||||
|
||||
b3DistanceJoint* joint = &base->distanceJoint;
|
||||
|
||||
b3Pos pA = b3TransformWorldPoint( transformA, base->localFrameA.p );
|
||||
b3Pos pB = b3TransformWorldPoint( transformB, base->localFrameB.p );
|
||||
|
||||
b3Vec3 axis = b3Normalize( b3SubPos( pB, pA ) );
|
||||
|
||||
if ( joint->minLength < joint->maxLength && joint->enableLimit )
|
||||
{
|
||||
b3Pos pMin = b3OffsetPos( pA, b3MulSV( joint->minLength, axis ) );
|
||||
b3Pos pMax = b3OffsetPos( pA, b3MulSV( joint->maxLength, axis ) );
|
||||
|
||||
if ( joint->minLength > B3_LINEAR_SLOP )
|
||||
{
|
||||
draw->DrawPointFcn( pMin, 6.0f, b3_colorLightGreen, draw->context );
|
||||
}
|
||||
|
||||
if ( joint->maxLength < B3_HUGE )
|
||||
{
|
||||
draw->DrawPointFcn(pMax, 6.0f, b3_colorRed, draw->context);
|
||||
}
|
||||
|
||||
if ( joint->minLength > B3_LINEAR_SLOP && joint->maxLength < B3_HUGE )
|
||||
{
|
||||
draw->DrawSegmentFcn( pMin, pMax, b3_colorGray, draw->context );
|
||||
}
|
||||
}
|
||||
|
||||
draw->DrawSegmentFcn( pA, pB, b3_colorWhite, draw->context );
|
||||
draw->DrawPointFcn( pA, 4.0f, b3_colorWhite, draw->context );
|
||||
draw->DrawPointFcn( pB, 4.0f, b3_colorWhite, draw->context );
|
||||
|
||||
if ( joint->hertz > 0.0f && joint->enableSpring )
|
||||
{
|
||||
b3Pos pRest = b3OffsetPos( pA, b3MulSV( joint->length, axis ) );
|
||||
draw->DrawPointFcn( pRest, 4.0f, b3_colorBlue, draw->context );
|
||||
}
|
||||
}
|
||||
2186
vendor/box3d/src/src/dynamic_tree.c
vendored
Normal file
2186
vendor/box3d/src/src/dynamic_tree.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1583
vendor/box3d/src/src/height_field.c
vendored
Normal file
1583
vendor/box3d/src/src/height_field.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2791
vendor/box3d/src/src/hull.c
vendored
Normal file
2791
vendor/box3d/src/src/hull.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24
vendor/box3d/src/src/hull_map.h
vendored
Normal file
24
vendor/box3d/src/src/hull_map.h
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "box3d/types.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
// Content hash and equality over the whole baked hull. Shared by every hull
|
||||
// de-duplication map so they agree on identity.
|
||||
uint64_t b3HashHullData( const b3HullData* hull );
|
||||
bool b3CompareHullData( const b3HullData* hull1, const b3HullData* hull2 );
|
||||
|
||||
// Map keyed by hull content. The world hull database stores a reference count,
|
||||
// compound baking stores a byte offset. Implementation lives in hull.c.
|
||||
#define NAME b3HullMap
|
||||
#define KEY_TY const b3HullData*
|
||||
#define VAL_TY int
|
||||
#define HEADER_MODE
|
||||
#include "verstable.h"
|
||||
|
||||
// Total map allocation in bytes, excluding the stored hull data
|
||||
size_t b3HullMapByteCount( b3HullMap* map );
|
||||
73
vendor/box3d/src/src/id_pool.c
vendored
Normal file
73
vendor/box3d/src/src/id_pool.c
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "id_pool.h"
|
||||
|
||||
b3IdPool b3CreateIdPool( void )
|
||||
{
|
||||
b3IdPool pool = { 0 };
|
||||
b3Array_Reserve( pool.freeArray, 32 );
|
||||
return pool;
|
||||
}
|
||||
|
||||
void b3DestroyIdPool( b3IdPool* pool )
|
||||
{
|
||||
b3Array_Destroy( pool->freeArray );
|
||||
*pool = (b3IdPool){ 0 };
|
||||
}
|
||||
|
||||
int b3AllocId( b3IdPool* pool )
|
||||
{
|
||||
int count = pool->freeArray.count;
|
||||
if ( count > 0 )
|
||||
{
|
||||
int id = b3Array_Pop( pool->freeArray );
|
||||
return id;
|
||||
}
|
||||
|
||||
int id = pool->nextIndex;
|
||||
pool->nextIndex += 1;
|
||||
return id;
|
||||
}
|
||||
|
||||
void b3FreeId( b3IdPool* pool, int id )
|
||||
{
|
||||
B3_ASSERT( pool->nextIndex > 0 );
|
||||
B3_ASSERT( 0 <= id && id < pool->nextIndex );
|
||||
|
||||
// todo does not work with assertion above
|
||||
// should probably be `id == pool->nextIndex - 1`
|
||||
if ( id == pool->nextIndex )
|
||||
{
|
||||
pool->nextIndex -= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
b3Array_Push( pool->freeArray, id );
|
||||
}
|
||||
|
||||
#if B3_ENABLE_VALIDATION
|
||||
|
||||
void b3ValidateFreeId( const b3IdPool* pool, int id )
|
||||
{
|
||||
int freeCount = pool->freeArray.count;
|
||||
for ( int i = 0; i < freeCount; ++i )
|
||||
{
|
||||
if ( pool->freeArray.data[i] == id )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
B3_ASSERT( 0 );
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void b3ValidateFreeId( const b3IdPool* pool, int id )
|
||||
{
|
||||
B3_UNUSED( pool );
|
||||
B3_UNUSED( id );
|
||||
}
|
||||
|
||||
#endif
|
||||
34
vendor/box3d/src/src/id_pool.h
vendored
Normal file
34
vendor/box3d/src/src/id_pool.h
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "container.h"
|
||||
|
||||
typedef struct b3IdPool
|
||||
{
|
||||
b3Array( int ) freeArray;
|
||||
int nextIndex;
|
||||
} b3IdPool;
|
||||
|
||||
b3IdPool b3CreateIdPool( void );
|
||||
void b3DestroyIdPool( b3IdPool* pool );
|
||||
|
||||
int b3AllocId( b3IdPool* pool );
|
||||
void b3FreeId( b3IdPool* pool, int id );
|
||||
void b3ValidateFreeId( const b3IdPool* pool, int id );
|
||||
|
||||
static inline int b3GetIdCount( const b3IdPool* pool )
|
||||
{
|
||||
return pool->nextIndex - pool->freeArray.count;
|
||||
}
|
||||
|
||||
static inline int b3GetIdCapacity( const b3IdPool* pool )
|
||||
{
|
||||
return pool->nextIndex;
|
||||
}
|
||||
|
||||
static inline int b3GetIdBytes( const b3IdPool* pool )
|
||||
{
|
||||
return b3Array_ByteCount( pool->freeArray );
|
||||
}
|
||||
735
vendor/box3d/src/src/island.c
vendored
Normal file
735
vendor/box3d/src/src/island.c
vendored
Normal file
@@ -0,0 +1,735 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "island.h"
|
||||
|
||||
#include "body.h"
|
||||
#include "contact.h"
|
||||
#include "core.h"
|
||||
#include "id_pool.h"
|
||||
#include "joint.h"
|
||||
#include "physics_world.h"
|
||||
#include "solver_set.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
b3Island* b3CreateIsland( b3World* world, int setIndex )
|
||||
{
|
||||
B3_ASSERT( setIndex == b3_awakeSet || setIndex >= b3_firstSleepingSet );
|
||||
|
||||
int islandId = b3AllocId( &world->islandIdPool );
|
||||
|
||||
if ( islandId == world->islands.count )
|
||||
{
|
||||
b3Island emptyIsland = { 0 };
|
||||
b3Array_Push( world->islands, emptyIsland );
|
||||
}
|
||||
else
|
||||
{
|
||||
B3_ASSERT( world->islands.data[islandId].setIndex == B3_NULL_INDEX );
|
||||
}
|
||||
|
||||
b3SolverSet* set = b3Array_Get( world->solverSets, setIndex );
|
||||
|
||||
b3Island* island = b3Array_Get( world->islands, islandId );
|
||||
island->setIndex = setIndex;
|
||||
island->localIndex = set->islandSims.count;
|
||||
island->islandId = islandId;
|
||||
b3Array_Create( island->bodies );
|
||||
b3Array_Create( island->contacts );
|
||||
b3Array_Create( island->joints );
|
||||
island->constraintRemoveCount = 0;
|
||||
|
||||
b3IslandSim* islandSim = b3Array_Emplace( set->islandSims );
|
||||
islandSim->islandId = islandId;
|
||||
|
||||
return island;
|
||||
}
|
||||
|
||||
void b3DestroyIsland( b3World* world, int islandId )
|
||||
{
|
||||
if ( world->splitIslandId == islandId )
|
||||
{
|
||||
world->splitIslandId = B3_NULL_INDEX;
|
||||
}
|
||||
|
||||
// assume island is empty
|
||||
b3Island* island = b3Array_Get( world->islands, islandId );
|
||||
b3SolverSet* set = b3Array_Get( world->solverSets, island->setIndex );
|
||||
{
|
||||
int localIndex = island->localIndex;
|
||||
int lastIndex = set->islandSims.count - 1;
|
||||
B3_ASSERT( 0 <= localIndex && localIndex <= lastIndex );
|
||||
int moveIslandId = set->islandSims.data[lastIndex].islandId;
|
||||
set->islandSims.data[localIndex] = set->islandSims.data[lastIndex];
|
||||
world->islands.data[moveIslandId].localIndex = localIndex;
|
||||
set->islandSims.count -= 1;
|
||||
}
|
||||
|
||||
// Free island and id (preserve island generation)
|
||||
b3Array_Destroy( island->bodies );
|
||||
b3Array_Destroy( island->contacts );
|
||||
b3Array_Destroy( island->joints );
|
||||
island->constraintRemoveCount = 0;
|
||||
island->localIndex = B3_NULL_INDEX;
|
||||
island->islandId = B3_NULL_INDEX;
|
||||
island->setIndex = B3_NULL_INDEX;
|
||||
|
||||
b3FreeId( &world->islandIdPool, islandId );
|
||||
}
|
||||
|
||||
static int b3MergeIslands( b3World* world, int islandIdA, int islandIdB )
|
||||
{
|
||||
if ( islandIdA == islandIdB )
|
||||
{
|
||||
return islandIdA;
|
||||
}
|
||||
|
||||
if ( islandIdA == B3_NULL_INDEX )
|
||||
{
|
||||
B3_ASSERT( islandIdB != B3_NULL_INDEX );
|
||||
return islandIdB;
|
||||
}
|
||||
|
||||
if ( islandIdB == B3_NULL_INDEX )
|
||||
{
|
||||
B3_ASSERT( islandIdA != B3_NULL_INDEX );
|
||||
return islandIdA;
|
||||
}
|
||||
|
||||
b3Island* smallIsland;
|
||||
b3Island* bigIsland;
|
||||
{
|
||||
b3Island* islandA = b3Array_Get( world->islands, islandIdA );
|
||||
b3Island* islandB = b3Array_Get( world->islands, islandIdB );
|
||||
|
||||
// Keep the biggest island to reduce cache misses
|
||||
if ( islandA->bodies.count >= islandB->bodies.count )
|
||||
{
|
||||
bigIsland = islandA;
|
||||
smallIsland = islandB;
|
||||
}
|
||||
else
|
||||
{
|
||||
bigIsland = islandB;
|
||||
smallIsland = islandA;
|
||||
}
|
||||
}
|
||||
|
||||
int bigIslandId = bigIsland->islandId;
|
||||
b3Array_Reserve( bigIsland->bodies, bigIsland->bodies.count + smallIsland->bodies.count );
|
||||
|
||||
// Move bodies from smaller island to larger island
|
||||
for ( int i = 0; i < smallIsland->bodies.count; ++i )
|
||||
{
|
||||
int bodyId = smallIsland->bodies.data[i];
|
||||
b3Body* body = b3Array_Get( world->bodies, bodyId );
|
||||
B3_VALIDATE( body->islandId == smallIsland->islandId );
|
||||
body->islandId = bigIslandId;
|
||||
body->islandIndex = bigIsland->bodies.count;
|
||||
b3Array_Push( bigIsland->bodies, bodyId );
|
||||
}
|
||||
|
||||
// Migrate contacts from smaller island to larger island
|
||||
if ( smallIsland->contacts.count > 0 )
|
||||
{
|
||||
b3Array_Reserve( bigIsland->contacts, bigIsland->contacts.count + smallIsland->contacts.count );
|
||||
|
||||
for ( int i = 0; i < smallIsland->contacts.count; ++i )
|
||||
{
|
||||
b3ContactLink* link = smallIsland->contacts.data + i;
|
||||
b3Contact* contact = b3Array_Get( world->contacts, link->contactId );
|
||||
contact->islandId = bigIslandId;
|
||||
contact->islandIndex = bigIsland->contacts.count;
|
||||
b3Array_Push( bigIsland->contacts, *link );
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate joints from smaller island to larger island
|
||||
if ( smallIsland->joints.count > 0 )
|
||||
{
|
||||
b3Array_Reserve( bigIsland->joints, bigIsland->joints.count + smallIsland->joints.count );
|
||||
|
||||
for ( int i = 0; i < smallIsland->joints.count; ++i )
|
||||
{
|
||||
b3JointLink* link = smallIsland->joints.data + i;
|
||||
b3Joint* joint = b3Array_Get( world->joints, link->jointId );
|
||||
joint->islandId = bigIslandId;
|
||||
joint->islandIndex = bigIsland->joints.count;
|
||||
b3Array_Push( bigIsland->joints, *link );
|
||||
}
|
||||
}
|
||||
|
||||
// Track removed constraints
|
||||
bigIsland->constraintRemoveCount += smallIsland->constraintRemoveCount;
|
||||
|
||||
b3DestroyIsland( world, smallIsland->islandId );
|
||||
|
||||
b3ValidateIsland( world, bigIslandId );
|
||||
|
||||
return bigIslandId;
|
||||
}
|
||||
|
||||
static void b3AddContactToIsland( b3World* world, int islandId, b3Contact* contact )
|
||||
{
|
||||
B3_ASSERT( contact->islandId == B3_NULL_INDEX );
|
||||
B3_ASSERT( contact->islandIndex == B3_NULL_INDEX );
|
||||
|
||||
b3Island* island = b3Array_Get( world->islands, islandId );
|
||||
|
||||
contact->islandId = islandId;
|
||||
contact->islandIndex = island->contacts.count;
|
||||
|
||||
b3ContactLink link;
|
||||
link.contactId = contact->contactId;
|
||||
link.bodyIdA = contact->edges[0].bodyId;
|
||||
link.bodyIdB = contact->edges[1].bodyId;
|
||||
b3Array_Push( island->contacts, link );
|
||||
|
||||
b3ValidateIsland( world, islandId );
|
||||
}
|
||||
|
||||
// Link a contact into an island.
|
||||
void b3LinkContact( b3World* world, b3Contact* contact )
|
||||
{
|
||||
B3_ASSERT( ( contact->flags & b3_contactTouchingFlag ) != 0 );
|
||||
|
||||
int bodyIdA = contact->edges[0].bodyId;
|
||||
int bodyIdB = contact->edges[1].bodyId;
|
||||
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB );
|
||||
|
||||
B3_ASSERT( bodyA->setIndex != b3_disabledSet && bodyB->setIndex != b3_disabledSet );
|
||||
B3_ASSERT( bodyA->setIndex != b3_staticSet || bodyB->setIndex != b3_staticSet );
|
||||
|
||||
// Wake bodyB if bodyA is awake and bodyB is sleeping
|
||||
if ( bodyA->setIndex == b3_awakeSet && bodyB->setIndex >= b3_firstSleepingSet )
|
||||
{
|
||||
b3WakeSolverSet( world, bodyB->setIndex );
|
||||
}
|
||||
|
||||
// Wake bodyA if bodyB is awake and bodyA is sleeping
|
||||
if ( bodyB->setIndex == b3_awakeSet && bodyA->setIndex >= b3_firstSleepingSet )
|
||||
{
|
||||
b3WakeSolverSet( world, bodyA->setIndex );
|
||||
}
|
||||
|
||||
int islandIdA = bodyA->islandId;
|
||||
int islandIdB = bodyB->islandId;
|
||||
|
||||
// Static bodies have null island indices.
|
||||
B3_ASSERT( bodyA->setIndex != b3_staticSet || islandIdA == B3_NULL_INDEX );
|
||||
B3_ASSERT( bodyB->setIndex != b3_staticSet || islandIdB == B3_NULL_INDEX );
|
||||
B3_ASSERT( islandIdA != B3_NULL_INDEX || islandIdB != B3_NULL_INDEX );
|
||||
|
||||
// Merge islands. This will destroy one of the islands.
|
||||
int finalIslandId = b3MergeIslands( world, islandIdA, islandIdB );
|
||||
|
||||
// Add contact to the island that survived
|
||||
b3AddContactToIsland( world, finalIslandId, contact );
|
||||
}
|
||||
|
||||
// This is called when a contact no longer has contact points or when a contact is destroyed.
|
||||
void b3UnlinkContact( b3World* world, b3Contact* contact )
|
||||
{
|
||||
B3_ASSERT( contact->islandId != B3_NULL_INDEX );
|
||||
|
||||
// remove from island
|
||||
int islandId = contact->islandId;
|
||||
b3Island* island = b3Array_Get( world->islands, islandId );
|
||||
|
||||
int removeIndex = contact->islandIndex;
|
||||
B3_ASSERT( 0 <= removeIndex && removeIndex < island->contacts.count );
|
||||
B3_ASSERT( island->contacts.data[removeIndex].contactId == contact->contactId );
|
||||
|
||||
int movedIndex = b3Array_RemoveSwap( island->contacts, removeIndex );
|
||||
if ( movedIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// Fix islandIndex on the contact that was swapped into removeIndex
|
||||
b3ContactLink* movedLink = island->contacts.data + removeIndex;
|
||||
b3Contact* movedContact = b3Array_Get( world->contacts, movedLink->contactId );
|
||||
B3_ASSERT( movedContact->islandIndex == movedIndex );
|
||||
movedContact->islandIndex = removeIndex;
|
||||
}
|
||||
|
||||
contact->islandId = B3_NULL_INDEX;
|
||||
contact->islandIndex = B3_NULL_INDEX;
|
||||
island->constraintRemoveCount += 1;
|
||||
|
||||
b3ValidateIsland( world, islandId );
|
||||
}
|
||||
|
||||
static void b3AddJointToIsland( b3World* world, int islandId, b3Joint* joint )
|
||||
{
|
||||
B3_ASSERT( joint->islandId == B3_NULL_INDEX );
|
||||
B3_ASSERT( joint->islandIndex == B3_NULL_INDEX );
|
||||
|
||||
b3Island* island = b3Array_Get( world->islands, islandId );
|
||||
|
||||
joint->islandId = islandId;
|
||||
joint->islandIndex = island->joints.count;
|
||||
|
||||
b3JointLink link;
|
||||
link.jointId = joint->jointId;
|
||||
link.bodyIdA = joint->edges[0].bodyId;
|
||||
link.bodyIdB = joint->edges[1].bodyId;
|
||||
b3Array_Push( island->joints, link );
|
||||
|
||||
b3ValidateIsland( world, islandId );
|
||||
}
|
||||
|
||||
void b3LinkJoint( b3World* world, b3Joint* joint )
|
||||
{
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, joint->edges[0].bodyId );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, joint->edges[1].bodyId );
|
||||
|
||||
B3_ASSERT( bodyA->type == b3_dynamicBody || bodyB->type == b3_dynamicBody );
|
||||
|
||||
if ( bodyA->setIndex == b3_awakeSet && bodyB->setIndex >= b3_firstSleepingSet )
|
||||
{
|
||||
b3WakeSolverSet( world, bodyB->setIndex );
|
||||
}
|
||||
else if ( bodyB->setIndex == b3_awakeSet && bodyA->setIndex >= b3_firstSleepingSet )
|
||||
{
|
||||
b3WakeSolverSet( world, bodyA->setIndex );
|
||||
}
|
||||
|
||||
int islandIdA = bodyA->islandId;
|
||||
int islandIdB = bodyB->islandId;
|
||||
|
||||
B3_ASSERT( islandIdA != B3_NULL_INDEX || islandIdB != B3_NULL_INDEX );
|
||||
|
||||
// Merge islands. This will destroy one of the islands.
|
||||
int finalIslandId = b3MergeIslands( world, islandIdA, islandIdB );
|
||||
|
||||
// Add joint the island that survived
|
||||
b3AddJointToIsland( world, finalIslandId, joint );
|
||||
}
|
||||
|
||||
void b3UnlinkJoint( b3World* world, b3Joint* joint )
|
||||
{
|
||||
if ( joint->islandId == B3_NULL_INDEX )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// remove from island
|
||||
int islandId = joint->islandId;
|
||||
b3Island* island = b3Array_Get( world->islands, islandId );
|
||||
|
||||
int removeIndex = joint->islandIndex;
|
||||
B3_ASSERT( 0 <= removeIndex && removeIndex < island->joints.count );
|
||||
B3_ASSERT( island->joints.data[removeIndex].jointId == joint->jointId );
|
||||
|
||||
int movedIndex = b3Array_RemoveSwap( island->joints, removeIndex );
|
||||
if ( movedIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// Fix islandIndex on the joint that was swapped into removeIndex
|
||||
b3JointLink* movedLink = island->joints.data + removeIndex;
|
||||
b3Joint* movedJoint = b3Array_Get( world->joints, movedLink->jointId );
|
||||
B3_ASSERT( movedJoint->islandIndex == movedIndex );
|
||||
movedJoint->islandIndex = removeIndex;
|
||||
}
|
||||
|
||||
joint->islandId = B3_NULL_INDEX;
|
||||
joint->islandIndex = B3_NULL_INDEX;
|
||||
island->constraintRemoveCount += 1;
|
||||
|
||||
b3ValidateIsland( world, islandId );
|
||||
}
|
||||
|
||||
// Find parent of a node. Use path halving to speed up further queries.
|
||||
static inline int b3IslandFindParent( int* parents, int node )
|
||||
{
|
||||
// Walk the chain of parents to find the node that is its own parent (the root)
|
||||
while ( parents[node] != node )
|
||||
{
|
||||
int grandParent = parents[parents[node]];
|
||||
parents[node] = grandParent;
|
||||
node = grandParent;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// Connect the components containing node1 and node2.
|
||||
// Uses rank to keep tree balanced. Tracks per-component contact and joint counts.
|
||||
static inline void b3IslandUnion( int* parents, int* ranks, int node1, int node2, int* contactCounts, int* jointCounts )
|
||||
{
|
||||
int root1 = b3IslandFindParent( parents, node1 );
|
||||
int root2 = b3IslandFindParent( parents, node2 );
|
||||
if ( root1 != root2 )
|
||||
{
|
||||
if ( ranks[root1] < ranks[root2] )
|
||||
{
|
||||
parents[root1] = root2;
|
||||
contactCounts[root2] += contactCounts[root1];
|
||||
jointCounts[root2] += jointCounts[root1];
|
||||
}
|
||||
else if ( ranks[root1] > ranks[root2] )
|
||||
{
|
||||
parents[root2] = root1;
|
||||
contactCounts[root1] += contactCounts[root2];
|
||||
jointCounts[root1] += jointCounts[root2];
|
||||
}
|
||||
else
|
||||
{
|
||||
parents[root2] = root1;
|
||||
ranks[root1] += 1;
|
||||
contactCounts[root1] += contactCounts[root2];
|
||||
jointCounts[root1] += jointCounts[root2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This uses union-find.
|
||||
// https://en.wikipedia.org/wiki/Disjoint-set_data_structure
|
||||
void b3SplitIsland( b3World* world, int baseId )
|
||||
{
|
||||
b3Island* baseIsland = b3Array_Get( world->islands, baseId );
|
||||
B3_ASSERT( baseIsland->constraintRemoveCount > 0 );
|
||||
B3_ASSERT( baseIsland->setIndex == b3_awakeSet );
|
||||
|
||||
b3ValidateIsland( world, baseId );
|
||||
|
||||
// Cache base island fields before b3CreateIsland, which may reallocate
|
||||
// world->islands and invalidate the baseIsland pointer.
|
||||
int baseBodyCount = baseIsland->bodies.count;
|
||||
int* baseBodyIds = baseIsland->bodies.data;
|
||||
int baseBodyCapacity = baseIsland->bodies.capacity;
|
||||
|
||||
int baseContactCount = baseIsland->contacts.count;
|
||||
b3ContactLink* baseContacts = baseIsland->contacts.data;
|
||||
int baseContactCapacity = baseIsland->contacts.capacity;
|
||||
|
||||
int baseJointCount = baseIsland->joints.count;
|
||||
b3JointLink* baseJoints = baseIsland->joints.data;
|
||||
int baseJointCapacity = baseIsland->joints.capacity;
|
||||
|
||||
b3Stack* alloc = &world->stack;
|
||||
|
||||
// No lock is needed because I ensure the allocator is not used while this task is active.
|
||||
// Allocate contactCounts and jointCounts before ranks so ranks can be freed first (LIFO arena).
|
||||
int* parents = b3StackAlloc( alloc, baseBodyCount * sizeof( int ), "parents" );
|
||||
int* contactCounts = b3StackAlloc( alloc, baseBodyCount * sizeof( int ), "contact counts" );
|
||||
int* jointCounts = b3StackAlloc( alloc, baseBodyCount * sizeof( int ), "joint counts" );
|
||||
int* ranks = b3StackAlloc( alloc, baseBodyCount * sizeof( int ), "ranks" );
|
||||
for ( int i = 0; i < baseBodyCount; ++i )
|
||||
{
|
||||
parents[i] = i;
|
||||
ranks[i] = 0;
|
||||
contactCounts[i] = 0;
|
||||
jointCounts[i] = 0;
|
||||
}
|
||||
|
||||
b3Body* bodies = world->bodies.data;
|
||||
|
||||
// Union over contacts, tracking per-component contact counts
|
||||
for ( int i = 0; i < baseContactCount; ++i )
|
||||
{
|
||||
int bodyIdA = baseContacts[i].bodyIdA;
|
||||
int bodyIdB = baseContacts[i].bodyIdB;
|
||||
B3_VALIDATE( 0 <= bodyIdA && bodyIdA < world->bodies.count );
|
||||
B3_VALIDATE( 0 <= bodyIdB && bodyIdB < world->bodies.count );
|
||||
b3Body* bodyA = bodies + bodyIdA;
|
||||
b3Body* bodyB = bodies + bodyIdB;
|
||||
int islandIndexA = bodyA->islandIndex;
|
||||
int islandIndexB = bodyB->islandIndex;
|
||||
|
||||
// Only connect non-static bodies
|
||||
if ( islandIndexA != B3_NULL_INDEX && islandIndexB != B3_NULL_INDEX )
|
||||
{
|
||||
B3_VALIDATE( 0 <= islandIndexA && islandIndexA < baseBodyCount );
|
||||
B3_VALIDATE( 0 <= islandIndexB && islandIndexB < baseBodyCount );
|
||||
b3IslandUnion( parents, ranks, islandIndexA, islandIndexB, contactCounts, jointCounts );
|
||||
int root = b3IslandFindParent( parents, islandIndexA );
|
||||
contactCounts[root] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int islandIndex = islandIndexA != B3_NULL_INDEX ? islandIndexA : islandIndexB;
|
||||
int root = b3IslandFindParent( parents, islandIndex );
|
||||
contactCounts[root] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Union over joints, tracking per-component joint counts
|
||||
for ( int i = 0; i < baseJointCount; ++i )
|
||||
{
|
||||
int bodyIdA = baseJoints[i].bodyIdA;
|
||||
int bodyIdB = baseJoints[i].bodyIdB;
|
||||
B3_VALIDATE( 0 <= bodyIdA && bodyIdA < world->bodies.count );
|
||||
B3_VALIDATE( 0 <= bodyIdB && bodyIdB < world->bodies.count );
|
||||
b3Body* bodyA = bodies + bodyIdA;
|
||||
b3Body* bodyB = bodies + bodyIdB;
|
||||
int islandIndexA = bodyA->islandIndex;
|
||||
int islandIndexB = bodyB->islandIndex;
|
||||
|
||||
// Only connect non-static bodies
|
||||
if ( islandIndexA != B3_NULL_INDEX && islandIndexB != B3_NULL_INDEX )
|
||||
{
|
||||
B3_VALIDATE( 0 <= islandIndexA && islandIndexA < baseBodyCount );
|
||||
B3_VALIDATE( 0 <= islandIndexB && islandIndexB < baseBodyCount );
|
||||
b3IslandUnion( parents, ranks, islandIndexA, islandIndexB, contactCounts, jointCounts );
|
||||
int root = b3IslandFindParent( parents, islandIndexA );
|
||||
jointCounts[root] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int islandIndex = islandIndexA != B3_NULL_INDEX ? islandIndexA : islandIndexB;
|
||||
int root = b3IslandFindParent( parents, islandIndex );
|
||||
jointCounts[root] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Done with ranks
|
||||
b3StackFree( alloc, ranks );
|
||||
ranks = NULL;
|
||||
|
||||
// Flatten all parent indices and count connected components.
|
||||
int componentCount = 0;
|
||||
for ( int i = 0; i < baseBodyCount; ++i )
|
||||
{
|
||||
parents[i] = b3IslandFindParent( parents, i );
|
||||
if ( parents[i] == i )
|
||||
{
|
||||
componentCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Early return — island is still fully connected, no split needed.
|
||||
if ( componentCount == 1 )
|
||||
{
|
||||
baseIsland->constraintRemoveCount = 0;
|
||||
b3StackFree( alloc, jointCounts );
|
||||
b3StackFree( alloc, contactCounts );
|
||||
b3StackFree( alloc, parents );
|
||||
return;
|
||||
}
|
||||
|
||||
// Detach body/contact/joint arrays from base island so b3DestroyIsland won't free them
|
||||
baseIsland->bodies.data = NULL;
|
||||
baseIsland->bodies.count = 0;
|
||||
baseIsland->bodies.capacity = 0;
|
||||
|
||||
baseIsland->contacts.data = NULL;
|
||||
baseIsland->contacts.count = 0;
|
||||
baseIsland->contacts.capacity = 0;
|
||||
|
||||
baseIsland->joints.data = NULL;
|
||||
baseIsland->joints.count = 0;
|
||||
baseIsland->joints.capacity = 0;
|
||||
|
||||
// Null so code below doesn't accidentally use this.
|
||||
baseIsland = NULL;
|
||||
|
||||
// Map from body index to new island index. Only set for root bodies.
|
||||
int* rootMap = b3StackAlloc( alloc, baseBodyCount * sizeof( int ), "root map" );
|
||||
for ( int i = 0; i < baseBodyCount; ++i )
|
||||
{
|
||||
rootMap[i] = B3_NULL_INDEX;
|
||||
}
|
||||
|
||||
int* componentBodyCounts = b3StackAlloc( alloc, componentCount * sizeof( int ), "component body counts" );
|
||||
int* componentContactCounts = b3StackAlloc( alloc, componentCount * sizeof( int ), "component contact counts" );
|
||||
int* componentJointCounts = b3StackAlloc( alloc, componentCount * sizeof( int ), "component joint counts" );
|
||||
int islandCount = 0;
|
||||
|
||||
// Find the root body for each body and create islands as needed.
|
||||
// Extract per-component counts from the root nodes' accumulated counts.
|
||||
for ( int i = 0; i < baseBodyCount; ++i )
|
||||
{
|
||||
int rootIndex = parents[i];
|
||||
if ( rootMap[rootIndex] == B3_NULL_INDEX )
|
||||
{
|
||||
rootMap[rootIndex] = islandCount;
|
||||
componentBodyCounts[islandCount] = 0;
|
||||
componentContactCounts[islandCount] = contactCounts[rootIndex];
|
||||
componentJointCounts[islandCount] = jointCounts[rootIndex];
|
||||
islandCount += 1;
|
||||
}
|
||||
|
||||
componentBodyCounts[rootMap[rootIndex]] += 1;
|
||||
}
|
||||
|
||||
B3_ASSERT( islandCount == componentCount );
|
||||
|
||||
// Map from new island index to island id
|
||||
int* islandIds = b3StackAlloc( alloc, islandCount * sizeof( int ), "island ids" );
|
||||
|
||||
// Create new islands and reserve body/contact/joint arrays
|
||||
for ( int i = 0; i < islandCount; ++i )
|
||||
{
|
||||
// WARNING: this invalidates baseIsland pointer
|
||||
b3Island* newIsland = b3CreateIsland( world, b3_awakeSet );
|
||||
islandIds[i] = newIsland->islandId;
|
||||
|
||||
// Reserve arrays to avoid wasteful growth and memcpy.
|
||||
b3Array_Reserve( newIsland->bodies, componentBodyCounts[i] );
|
||||
b3Array_Reserve( newIsland->contacts, componentContactCounts[i] );
|
||||
b3Array_Reserve( newIsland->joints, componentJointCounts[i] );
|
||||
}
|
||||
|
||||
// Assign bodies to new islands
|
||||
for ( int i = 0; i < baseBodyCount; ++i )
|
||||
{
|
||||
int bodyId = baseBodyIds[i];
|
||||
int root = b3IslandFindParent( parents, i );
|
||||
int newIslandId = islandIds[rootMap[root]];
|
||||
|
||||
b3Body* body = b3Array_Get( world->bodies, bodyId );
|
||||
b3Island* newIsland = b3Array_Get( world->islands, newIslandId );
|
||||
|
||||
body->islandId = newIslandId;
|
||||
body->islandIndex = newIsland->bodies.count;
|
||||
|
||||
// Ensure the array has the correct capacity
|
||||
B3_VALIDATE( newIsland->bodies.count < newIsland->bodies.capacity );
|
||||
b3Array_Push( newIsland->bodies, bodyId );
|
||||
}
|
||||
|
||||
// Assign contacts to the island of their bodies
|
||||
for ( int i = 0; i < baseContactCount; ++i )
|
||||
{
|
||||
b3ContactLink* link = baseContacts + i;
|
||||
b3Contact* contact = b3Array_Get( world->contacts, link->contactId );
|
||||
|
||||
// Static bodies don't have an island id.
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, link->bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, link->bodyIdB );
|
||||
int targetIslandId = bodyA->islandId != B3_NULL_INDEX ? bodyA->islandId : bodyB->islandId;
|
||||
|
||||
b3Island* targetIsland = b3Array_Get( world->islands, targetIslandId );
|
||||
contact->islandId = targetIslandId;
|
||||
contact->islandIndex = targetIsland->contacts.count;
|
||||
|
||||
// Ensure the array has the correct capacity
|
||||
B3_VALIDATE( targetIsland->contacts.count < targetIsland->contacts.capacity );
|
||||
b3Array_Push( targetIsland->contacts, *link );
|
||||
}
|
||||
|
||||
// Assign joints to the island of their bodies
|
||||
for ( int i = 0; i < baseJointCount; ++i )
|
||||
{
|
||||
b3JointLink* link = baseJoints + i;
|
||||
b3Joint* joint = b3Array_Get( world->joints, link->jointId );
|
||||
|
||||
// Static bodies don't have an island id.
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, link->bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, link->bodyIdB );
|
||||
int targetIslandId = bodyA->islandId != B3_NULL_INDEX ? bodyA->islandId : bodyB->islandId;
|
||||
|
||||
b3Island* targetIsland = b3Array_Get( world->islands, targetIslandId );
|
||||
joint->islandId = targetIslandId;
|
||||
joint->islandIndex = targetIsland->joints.count;
|
||||
|
||||
// Ensure the array has the correct capacity
|
||||
B3_VALIDATE( targetIsland->joints.count < targetIsland->joints.capacity );
|
||||
b3Array_Push( targetIsland->joints, *link );
|
||||
}
|
||||
|
||||
// Destroy the base island
|
||||
b3DestroyIsland( world, baseId );
|
||||
|
||||
// Free the detached arrays manually
|
||||
b3Free( baseBodyIds, baseBodyCapacity * sizeof( int ) );
|
||||
b3Free( baseContacts, baseContactCapacity * sizeof( b3ContactLink ) );
|
||||
b3Free( baseJoints, baseJointCapacity * sizeof( b3JointLink ) );
|
||||
|
||||
// Free arena items in LIFO order
|
||||
b3StackFree( alloc, islandIds );
|
||||
b3StackFree( alloc, componentJointCounts );
|
||||
b3StackFree( alloc, componentContactCounts );
|
||||
b3StackFree( alloc, componentBodyCounts );
|
||||
b3StackFree( alloc, rootMap );
|
||||
b3StackFree( alloc, jointCounts );
|
||||
b3StackFree( alloc, contactCounts );
|
||||
b3StackFree( alloc, parents );
|
||||
}
|
||||
|
||||
// Split an island because some contacts and/or joints have been removed.
|
||||
// This is called during the constraint solve while islands are not being touched. This uses union find and
|
||||
// touches a lot of memory, so it can be slow.
|
||||
// Note: contacts/joints connected to static bodies must belong to an island but don't affect island connectivity
|
||||
// Note: static bodies are never in an island
|
||||
// Note: this task interacts with some allocators without locks under the assumption that no other tasks
|
||||
// are interacting with these data structures.
|
||||
void b3SplitIslandTask( void* context )
|
||||
{
|
||||
b3TracyCZoneNC( split, "Split Island", b3_colorOlive, true );
|
||||
|
||||
uint64_t ticks = b3GetTicks();
|
||||
b3World* world = context;
|
||||
|
||||
B3_ASSERT( world->splitIslandId != B3_NULL_INDEX );
|
||||
|
||||
b3SplitIsland( world, world->splitIslandId );
|
||||
|
||||
world->splitIslandId = B3_NULL_INDEX;
|
||||
world->profile.splitIslands += b3GetMilliseconds( ticks );
|
||||
b3TracyCZoneEnd( split );
|
||||
}
|
||||
|
||||
#if B3_ENABLE_VALIDATION
|
||||
void b3ValidateIsland( b3World* world, int islandId )
|
||||
{
|
||||
if ( islandId == B3_NULL_INDEX )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
b3Island* island = b3Array_Get( world->islands, islandId );
|
||||
B3_ASSERT( island->islandId == islandId );
|
||||
B3_ASSERT( island->setIndex != B3_NULL_INDEX );
|
||||
|
||||
{
|
||||
B3_ASSERT( island->bodies.count > 0 );
|
||||
B3_ASSERT( island->bodies.count <= b3GetIdCount( &world->bodyIdPool ) );
|
||||
|
||||
for ( int i = 0; i < island->bodies.count; ++i )
|
||||
{
|
||||
b3Body* body = b3Array_Get( world->bodies, island->bodies.data[i] );
|
||||
B3_ASSERT( body->islandId == islandId );
|
||||
B3_ASSERT( body->islandIndex == i );
|
||||
B3_ASSERT( body->setIndex == island->setIndex );
|
||||
}
|
||||
}
|
||||
|
||||
if ( island->contacts.count > 0 )
|
||||
{
|
||||
B3_ASSERT( island->contacts.count <= b3GetIdCount( &world->contactIdPool ) );
|
||||
|
||||
for ( int i = 0; i < island->contacts.count; ++i )
|
||||
{
|
||||
b3ContactLink* link = island->contacts.data + i;
|
||||
b3Contact* contact = b3Array_Get( world->contacts, link->contactId );
|
||||
B3_ASSERT( contact->setIndex == island->setIndex );
|
||||
B3_ASSERT( contact->islandId == islandId );
|
||||
B3_ASSERT( contact->islandIndex == i );
|
||||
}
|
||||
}
|
||||
|
||||
if ( island->joints.count > 0 )
|
||||
{
|
||||
B3_ASSERT( island->joints.count <= b3GetIdCount( &world->jointIdPool ) );
|
||||
|
||||
for ( int i = 0; i < island->joints.count; ++i )
|
||||
{
|
||||
b3JointLink* link = island->joints.data + i;
|
||||
b3Joint* joint = b3Array_Get( world->joints, link->jointId );
|
||||
B3_ASSERT( joint->setIndex == island->setIndex );
|
||||
B3_ASSERT( joint->islandId == islandId );
|
||||
B3_ASSERT( joint->islandIndex == i );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void b3ValidateIsland( b3World* world, int islandId )
|
||||
{
|
||||
B3_UNUSED( world );
|
||||
B3_UNUSED( islandId );
|
||||
}
|
||||
#endif
|
||||
101
vendor/box3d/src/src/island.h
vendored
Normal file
101
vendor/box3d/src/src/island.h
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "container.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct b3Contact b3Contact;
|
||||
typedef struct b3Joint b3Joint;
|
||||
typedef struct b3World b3World;
|
||||
|
||||
// Cached contact data stored in the island for fast contiguous iteration.
|
||||
// Avoids touching b3Contact during union-find in b3SplitIsland.
|
||||
typedef struct b3ContactLink
|
||||
{
|
||||
int contactId;
|
||||
int bodyIdA;
|
||||
int bodyIdB;
|
||||
} b3ContactLink;
|
||||
|
||||
b3DeclareArray( b3ContactLink );
|
||||
|
||||
// Cached joint data stored in the island for fast contiguous iteration.
|
||||
typedef struct b3JointLink
|
||||
{
|
||||
int jointId;
|
||||
int bodyIdA;
|
||||
int bodyIdB;
|
||||
} b3JointLink;
|
||||
|
||||
b3DeclareArray( b3JointLink );
|
||||
|
||||
// Deterministic solver
|
||||
//
|
||||
// Collide all awake contacts
|
||||
// Use bit array to emit start/stop touching events in defined order, per thread. Try using contact index, assuming contacts are
|
||||
// created in a deterministic order. bit-wise OR together bit arrays and issue changes:
|
||||
// - start touching: merge islands - temporary linked list - mark root island dirty - wake all - largest island is root
|
||||
// - stop touching: increment constraintRemoveCount
|
||||
|
||||
// Persistent island for awake bodies, joints, and contacts.
|
||||
// Contacts are touching.
|
||||
// Contacts and joints may connect to static bodies, but static bodies are not in the island.
|
||||
// https://en.wikipedia.org/wiki/Component_(graph_theory)
|
||||
// https://en.wikipedia.org/wiki/Dynamic_connectivity
|
||||
typedef struct b3Island
|
||||
{
|
||||
// index of solver set stored in b3World
|
||||
// may be B3_NULL_INDEX
|
||||
int setIndex;
|
||||
|
||||
// island index within set
|
||||
// may be B3_NULL_INDEX
|
||||
int localIndex;
|
||||
|
||||
int islandId;
|
||||
|
||||
// Keeps track of how many contacts have been removed from this island.
|
||||
// This is used to determine if an island is a candidate for splitting.
|
||||
int constraintRemoveCount;
|
||||
|
||||
// I tried using a stack array for this but the data pointer goes out of
|
||||
// sync when the world island array grows.
|
||||
b3Array( int ) bodies;
|
||||
|
||||
// Contacts and joints that belong to this island. May connect to static
|
||||
// bodies not in the island.
|
||||
// Each link has the two body ids so that b3SplitIsland's union-find pass
|
||||
// never needs to touch b3Contact/b3Joint.
|
||||
b3Array( b3ContactLink ) contacts;
|
||||
b3Array( b3JointLink ) joints;
|
||||
|
||||
} b3Island;
|
||||
|
||||
// This is used to move islands across solver sets
|
||||
typedef struct b3IslandSim
|
||||
{
|
||||
int islandId;
|
||||
} b3IslandSim;
|
||||
|
||||
b3Island* b3CreateIsland( b3World* world, int setIndex );
|
||||
void b3DestroyIsland( b3World* world, int islandId );
|
||||
|
||||
// Link contacts into the island graph when it starts having contact points
|
||||
void b3LinkContact( b3World* world, b3Contact* contact );
|
||||
|
||||
// Unlink contact from the island graph when it stops having contact points
|
||||
void b3UnlinkContact( b3World* world, b3Contact* contact );
|
||||
|
||||
// Link a joint into the island graph when it is created
|
||||
void b3LinkJoint( b3World* world, b3Joint* joint );
|
||||
|
||||
// Unlink a joint from the island graph when it is destroyed
|
||||
void b3UnlinkJoint( b3World* world, b3Joint* joint );
|
||||
|
||||
void b3SplitIsland( b3World* world, int baseId );
|
||||
void b3SplitIslandTask( void* context );
|
||||
|
||||
void b3ValidateIsland( b3World* world, int islandId );
|
||||
1748
vendor/box3d/src/src/joint.c
vendored
Normal file
1748
vendor/box3d/src/src/joint.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
415
vendor/box3d/src/src/joint.h
vendored
Normal file
415
vendor/box3d/src/src/joint.h
vendored
Normal file
@@ -0,0 +1,415 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "math_internal.h"
|
||||
#include "solver.h"
|
||||
|
||||
#include "box3d/types.h"
|
||||
|
||||
typedef struct b3DebugDraw b3DebugDraw;
|
||||
typedef struct b3StepContext b3StepContext;
|
||||
typedef struct b3World b3World;
|
||||
|
||||
/// A joint edge is used to connect bodies and joints together
|
||||
/// in a joint graph where each body is a node and each joint
|
||||
/// is an edge. A joint edge belongs to a doubly linked list
|
||||
/// maintained in each attached body. Each joint has two joint
|
||||
/// nodes, one for each attached body.
|
||||
typedef struct b3JointEdge
|
||||
{
|
||||
int bodyId;
|
||||
int prevKey;
|
||||
int nextKey;
|
||||
} b3JointEdge;
|
||||
|
||||
// Map from b3JointId to b3Joint in the solver sets
|
||||
typedef struct b3Joint
|
||||
{
|
||||
void* userData;
|
||||
|
||||
// index of simulation set stored in b3World
|
||||
// B3_NULL_INDEX when slot is free
|
||||
int setIndex;
|
||||
|
||||
// index into the constraint graph color array, may be B3_NULL_INDEX for sleeping/disabled joints
|
||||
// B3_NULL_INDEX when slot is free
|
||||
int colorIndex;
|
||||
|
||||
// joint index within set or graph color
|
||||
// B3_NULL_INDEX when slot is free
|
||||
int localIndex;
|
||||
|
||||
b3JointEdge edges[2];
|
||||
|
||||
int jointId;
|
||||
int islandId;
|
||||
|
||||
// Index into the island's joints array for O(1) swap-removal.
|
||||
// B3_NULL_INDEX when not in an island.
|
||||
int islandIndex;
|
||||
|
||||
float drawScale;
|
||||
|
||||
b3JointType type;
|
||||
|
||||
// This is monotonically advanced when a body is allocated in this slot
|
||||
// Used to check for invalid b3JointId
|
||||
uint16_t generation;
|
||||
|
||||
bool collideConnected;
|
||||
} b3Joint;
|
||||
|
||||
typedef struct b3DistanceJoint
|
||||
{
|
||||
float length;
|
||||
float hertz;
|
||||
float dampingRatio;
|
||||
float lowerSpringForce;
|
||||
float upperSpringForce;
|
||||
float minLength;
|
||||
float maxLength;
|
||||
|
||||
float maxMotorForce;
|
||||
float motorSpeed;
|
||||
|
||||
float impulse;
|
||||
float lowerImpulse;
|
||||
float upperImpulse;
|
||||
float motorImpulse;
|
||||
|
||||
int indexA;
|
||||
int indexB;
|
||||
b3Vec3 anchorA;
|
||||
b3Vec3 anchorB;
|
||||
b3Vec3 deltaCenter;
|
||||
b3Softness distanceSoftness;
|
||||
float axialMass;
|
||||
|
||||
bool enableSpring;
|
||||
bool enableLimit;
|
||||
bool enableMotor;
|
||||
} b3DistanceJoint;
|
||||
|
||||
typedef struct b3MotorJoint
|
||||
{
|
||||
b3Vec3 linearVelocity;
|
||||
b3Vec3 angularVelocity;
|
||||
float maxVelocityForce;
|
||||
float maxVelocityTorque;
|
||||
float linearHertz;
|
||||
float linearDampingRatio;
|
||||
float maxSpringForce;
|
||||
float angularHertz;
|
||||
float angularDampingRatio;
|
||||
float maxSpringTorque;
|
||||
|
||||
b3Vec3 linearVelocityImpulse;
|
||||
b3Vec3 angularVelocityImpulse;
|
||||
b3Vec3 linearSpringImpulse;
|
||||
b3Vec3 angularSpringImpulse;
|
||||
|
||||
b3Softness linearSpring;
|
||||
b3Softness angularSpring;
|
||||
|
||||
int indexA;
|
||||
int indexB;
|
||||
b3Transform frameA;
|
||||
b3Transform frameB;
|
||||
b3Vec3 deltaCenter;
|
||||
b3Matrix3 angularMass;
|
||||
} b3MotorJoint;
|
||||
|
||||
typedef struct b3ParallelJoint
|
||||
{
|
||||
float hertz;
|
||||
float dampingRatio;
|
||||
float maxTorque;
|
||||
|
||||
b3Vec2 perpImpulse;
|
||||
b3Vec3 perpAxisX;
|
||||
b3Vec3 perpAxisY;
|
||||
|
||||
b3Quat quatA;
|
||||
b3Quat quatB;
|
||||
int indexA;
|
||||
int indexB;
|
||||
b3Softness softness;
|
||||
} b3ParallelJoint;
|
||||
|
||||
typedef struct b3PrismaticJoint
|
||||
{
|
||||
b3Vec2 perpImpulse;
|
||||
b3Vec3 angularImpulse;
|
||||
float springImpulse;
|
||||
float motorImpulse;
|
||||
float lowerImpulse;
|
||||
float upperImpulse;
|
||||
float hertz;
|
||||
float dampingRatio;
|
||||
float maxMotorForce;
|
||||
float motorSpeed;
|
||||
float targetTranslation;
|
||||
float lowerTranslation;
|
||||
float upperTranslation;
|
||||
|
||||
int indexA;
|
||||
int indexB;
|
||||
b3Transform frameA;
|
||||
b3Transform frameB;
|
||||
b3Vec3 jointAxis;
|
||||
b3Vec3 perpAxisY;
|
||||
b3Vec3 perpAxisZ;
|
||||
b3Vec3 deltaCenter;
|
||||
float deltaAngle;
|
||||
b3Matrix3 rotationMass;
|
||||
b3Softness springSoftness;
|
||||
|
||||
bool enableSpring;
|
||||
bool enableLimit;
|
||||
bool enableMotor;
|
||||
} b3PrismaticJoint;
|
||||
|
||||
typedef struct b3RevoluteJoint
|
||||
{
|
||||
b3Vec3 linearImpulse;
|
||||
b3Vec2 perpImpulse;
|
||||
float springImpulse;
|
||||
float motorImpulse;
|
||||
float lowerImpulse;
|
||||
float upperImpulse;
|
||||
float hertz;
|
||||
float dampingRatio;
|
||||
float maxMotorTorque;
|
||||
float motorSpeed;
|
||||
float targetAngle;
|
||||
float lowerAngle;
|
||||
float upperAngle;
|
||||
|
||||
int indexA;
|
||||
int indexB;
|
||||
b3Transform frameA;
|
||||
b3Transform frameB;
|
||||
b3Vec3 rotationAxisZ;
|
||||
b3Vec3 perpAxisX;
|
||||
b3Vec3 perpAxisY;
|
||||
b3Vec3 deltaCenter;
|
||||
float deltaAngle;
|
||||
float axialMass;
|
||||
b3Softness springSoftness;
|
||||
|
||||
bool enableSpring;
|
||||
bool enableMotor;
|
||||
bool enableLimit;
|
||||
} b3RevoluteJoint;
|
||||
|
||||
typedef struct b3SphericalJoint
|
||||
{
|
||||
b3Vec3 linearImpulse;
|
||||
b3Vec3 springImpulse;
|
||||
b3Vec3 motorImpulse;
|
||||
float lowerTwistImpulse;
|
||||
float upperTwistImpulse;
|
||||
float swingImpulse;
|
||||
float hertz;
|
||||
float dampingRatio;
|
||||
float maxMotorTorque;
|
||||
b3Vec3 motorVelocity;
|
||||
float lowerTwistAngle;
|
||||
float upperTwistAngle;
|
||||
float coneAngle;
|
||||
b3Quat targetRotation;
|
||||
|
||||
int indexA;
|
||||
int indexB;
|
||||
b3Transform frameA;
|
||||
b3Transform frameB;
|
||||
b3Vec3 deltaCenter;
|
||||
b3Vec3 swingAxis;
|
||||
b3Vec3 twistJacobian;
|
||||
|
||||
b3Matrix3 rotationMass;
|
||||
float swingMass;
|
||||
float twistMass;
|
||||
b3Softness springSoftness;
|
||||
|
||||
bool enableSpring;
|
||||
bool enableMotor;
|
||||
bool enableConeLimit;
|
||||
bool enableTwistLimit;
|
||||
} b3SphericalJoint;
|
||||
|
||||
typedef struct b3WeldJoint
|
||||
{
|
||||
float linearHertz;
|
||||
float linearDampingRatio;
|
||||
float angularHertz;
|
||||
float angularDampingRatio;
|
||||
|
||||
b3Softness linearSpring;
|
||||
b3Softness angularSpring;
|
||||
b3Vec3 linearImpulse;
|
||||
b3Vec3 angularImpulse;
|
||||
|
||||
int indexA;
|
||||
int indexB;
|
||||
b3Transform frameA;
|
||||
b3Transform frameB;
|
||||
b3Vec3 deltaCenter;
|
||||
|
||||
b3Matrix3 angularMass;
|
||||
} b3WeldJoint;
|
||||
|
||||
typedef struct b3WheelJoint
|
||||
{
|
||||
b3Vec2 linearImpulse;
|
||||
b3Vec2 angularImpulse;
|
||||
float spinImpulse;
|
||||
float maxSpinTorque;
|
||||
float spinSpeed;
|
||||
float suspensionSpringImpulse;
|
||||
float lowerSuspensionImpulse;
|
||||
float upperSuspensionImpulse;
|
||||
float lowerSuspensionLimit;
|
||||
float upperSuspensionLimit;
|
||||
float suspensionHertz;
|
||||
float suspensionDampingRatio;
|
||||
float steeringSpringImpulse;
|
||||
float lowerSteeringImpulse;
|
||||
float upperSteeringImpulse;
|
||||
float lowerSteeringLimit;
|
||||
float upperSteeringLimit;
|
||||
float targetSteeringAngle;
|
||||
float maxSteeringTorque;
|
||||
float steeringHertz;
|
||||
float steeringDampingRatio;
|
||||
|
||||
int indexA;
|
||||
int indexB;
|
||||
b3Transform frameA;
|
||||
b3Transform frameB;
|
||||
b3Vec3 deltaCenter;
|
||||
float spinMass;
|
||||
float suspensionMass;
|
||||
float steeringMass;
|
||||
b3Softness suspensionSoftness;
|
||||
b3Softness steeringSoftness;
|
||||
|
||||
bool enableSpinMotor;
|
||||
bool enableSuspensionSpring;
|
||||
bool enableSuspensionLimit;
|
||||
bool enableSteering;
|
||||
bool enableSteeringLimit;
|
||||
bool enableSteeringMotor;
|
||||
} b3WheelJoint;
|
||||
|
||||
/// The base joint class. Joints are used to constraint two bodies together in
|
||||
/// various fashions. Some joints also feature limits and motors.
|
||||
typedef struct b3JointSim
|
||||
{
|
||||
int jointId;
|
||||
|
||||
int bodyIdA;
|
||||
int bodyIdB;
|
||||
|
||||
b3JointType type;
|
||||
|
||||
// Joint frames local to body origin
|
||||
b3Transform localFrameA;
|
||||
b3Transform localFrameB;
|
||||
|
||||
float invMassA, invMassB;
|
||||
b3Matrix3 invIA, invIB;
|
||||
|
||||
float constraintHertz;
|
||||
float constraintDampingRatio;
|
||||
|
||||
b3Softness constraintSoftness;
|
||||
|
||||
float forceThreshold;
|
||||
float torqueThreshold;
|
||||
|
||||
bool fixedRotation;
|
||||
|
||||
union
|
||||
{
|
||||
b3DistanceJoint distanceJoint;
|
||||
b3MotorJoint motorJoint;
|
||||
b3ParallelJoint parallelJoint;
|
||||
b3RevoluteJoint revoluteJoint;
|
||||
b3SphericalJoint sphericalJoint;
|
||||
b3PrismaticJoint prismaticJoint;
|
||||
b3WeldJoint weldJoint;
|
||||
b3WheelJoint wheelJoint;
|
||||
};
|
||||
} b3JointSim;
|
||||
|
||||
void b3DestroyJointInternal( b3World* world, b3Joint* joint, bool wakeBodies );
|
||||
|
||||
b3Joint* b3GetJointFullId( b3World* world, b3JointId jointId );
|
||||
b3JointSim* b3GetJointSim( b3World* world, b3Joint* joint );
|
||||
b3JointSim* b3GetJointSimCheckType( b3JointId jointId, b3JointType type );
|
||||
|
||||
void b3PrepareJoint( b3JointSim* joint, b3StepContext* context );
|
||||
void b3WarmStartJoint( b3JointSim* joint, b3StepContext* context );
|
||||
void b3SolveJoint( b3JointSim* joint, b3StepContext* context, bool useBias );
|
||||
|
||||
void b3PrepareJoints_Overflow( b3StepContext* context );
|
||||
void b3WarmStartJoints_Overflow( b3StepContext* context );
|
||||
void b3SolveJoints_Overflow( b3StepContext* context, bool useBias );
|
||||
|
||||
void b3GetJointReaction( b3World* world, b3JointSim* sim, float invTimeStep, float* force, float* torque );
|
||||
|
||||
void b3DrawJoint( b3DebugDraw* draw, b3World* world, b3Joint* joint );
|
||||
|
||||
b3Vec3 b3GetDistanceJointForce( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetMotorJointForce( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetPrismaticJointForce( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetRevoluteJointForce( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetSphericalJointForce( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetWeldJointForce( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetWheelJointForce( b3World* world, b3JointSim* base );
|
||||
|
||||
b3Vec3 b3GetMotorJointTorque( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetParallelJointTorque( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetPrismaticJointTorque( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetRevoluteJointTorque( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetSphericalJointTorque( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetWeldJointTorque( b3World* world, b3JointSim* base );
|
||||
b3Vec3 b3GetWheelJointTorque( b3World* world, b3JointSim* base );
|
||||
|
||||
void b3PrepareDistanceJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3PrepareMotorJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3PrepareParallelJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3PreparePrismaticJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3PrepareRevoluteJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3PrepareSphericalJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3PrepareWeldJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3PrepareWheelJoint( b3JointSim* base, b3StepContext* context );
|
||||
|
||||
void b3WarmStartDistanceJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3WarmStartMotorJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3WarmStartParallelJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3WarmStartPrismaticJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3WarmStartRevoluteJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3WarmStartSphericalJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3WarmStartWeldJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3WarmStartWheelJoint( b3JointSim* base, b3StepContext* context );
|
||||
|
||||
void b3SolveDistanceJoint( b3JointSim* base, b3StepContext* context, bool useBias );
|
||||
void b3SolveMotorJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3SolveParallelJoint( b3JointSim* base, b3StepContext* context );
|
||||
void b3SolvePrismaticJoint( b3JointSim* base, b3StepContext* context, bool useBias );
|
||||
void b3SolveRevoluteJoint( b3JointSim* base, b3StepContext* context, bool useBias );
|
||||
void b3SolveSphericalJoint( b3JointSim* base, b3StepContext* context, bool useBias );
|
||||
void b3SolveWeldJoint( b3JointSim* base, b3StepContext* context, bool useBias );
|
||||
void b3SolveWheelJoint( b3JointSim* base, b3StepContext* context, bool useBias );
|
||||
|
||||
void b3DrawDistanceJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB );
|
||||
void b3DrawParallelJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale );
|
||||
void b3DrawPrismaticJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale );
|
||||
void b3DrawRevoluteJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale );
|
||||
void b3DrawSphericalJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale );
|
||||
void b3DrawWeldJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale );
|
||||
void b3DrawWheelJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale );
|
||||
246
vendor/box3d/src/src/manifold.c
vendored
Normal file
246
vendor/box3d/src/src/manifold.c
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Dirk Gregorius contributed portions of this code
|
||||
|
||||
#include "manifold.h"
|
||||
|
||||
#include "algorithm.h"
|
||||
#include "shape.h"
|
||||
|
||||
#include "box3d/math_functions.h"
|
||||
|
||||
// p1 : origin on edge 1
|
||||
// e1 : edge 1
|
||||
// c1 : shape 1 centroid
|
||||
// p2 : origin on edge 2
|
||||
// e2 : edge 2
|
||||
// c2 : shape 2 centroid
|
||||
float b3EdgeEdgeSeparation( b3Vec3 p1, b3Vec3 e1, b3Vec3 c1, b3Vec3 p2, b3Vec3 e2, b3Vec3 c2 )
|
||||
{
|
||||
// Build search direction
|
||||
b3Vec3 u = b3Cross( e1, e2 );
|
||||
float length = b3Length( u );
|
||||
|
||||
// Skip near parallel edges: |e1 x e1| = sin(alpha) * |e1| * |e2|
|
||||
const float kTolerance = 0.005f;
|
||||
if ( length < kTolerance * sqrtf( b3LengthSquared( e1 ) * b3LengthSquared( e2 ) ) )
|
||||
{
|
||||
return -FLT_MAX;
|
||||
}
|
||||
|
||||
if ( length * length < 1000.0f * FLT_MIN )
|
||||
{
|
||||
return -FLT_MAX;
|
||||
}
|
||||
|
||||
b3Vec3 n = b3MulSV( 1.0f / length, u );
|
||||
|
||||
// Make sure normal points away from the first shape
|
||||
// For a triangle, it is possible that N is aligned with the triangle normal and the sign
|
||||
// value can be close to zero and flicker between small negative and positive values, leading to
|
||||
// an incorrect separation value. So we assume the other hull has some volume and pick the most
|
||||
// significant sign value to orient N.
|
||||
float sign1 = b3Dot( n, b3Sub( p1, c1 ) );
|
||||
float sign2 = b3Dot( n, b3Sub( p2, c2 ) );
|
||||
if ( b3AbsFloat( sign1 ) > b3AbsFloat( sign2 ) )
|
||||
{
|
||||
if ( sign1 < 0.0f )
|
||||
{
|
||||
n = b3Neg( n );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( sign2 > 0.0f )
|
||||
{
|
||||
n = b3Neg( n );
|
||||
}
|
||||
}
|
||||
|
||||
// s = Dot(n, p2) - d = Dot(n, p2) - Dot(n, p1) = Dot(n, p2 - p1)
|
||||
return b3Dot( n, b3Sub( p2, p1 ) );
|
||||
}
|
||||
|
||||
// This was extended to make the wedge shape get the correct incident face.
|
||||
// Instead of looking directly for the most anti-parallel face, we first find the closest vertex (passed in).
|
||||
// Then we look for all edges coming out of that vertex and look for the edge that is
|
||||
// most perpendicular to the reference normal.
|
||||
// Then from that edge, we select the adjacent face that is most anti-parallel to the reference normal.
|
||||
int b3FindIncidentFace( const b3HullData* hull, b3Vec3 refNormal, int vertexIndex )
|
||||
{
|
||||
const b3HullVertex* vertices = b3GetHullVertices( hull );
|
||||
const b3HullHalfEdge* edges = b3GetHullEdges( hull );
|
||||
const b3Plane* planes = b3GetHullPlanes( hull );
|
||||
const b3Vec3* points = b3GetHullPoints( hull );
|
||||
|
||||
int minEdgeIndex = -1;
|
||||
float minEdgeProjection = FLT_MAX;
|
||||
|
||||
const b3HullVertex* vertex = vertices + vertexIndex;
|
||||
B3_ASSERT( vertex );
|
||||
|
||||
int edgeIndex = vertex->edge;
|
||||
const b3HullHalfEdge* edge = edges + edgeIndex;
|
||||
b3Vec3 edgeOrigin = points[edge->origin];
|
||||
B3_ASSERT( edge->origin == vertexIndex );
|
||||
|
||||
do
|
||||
{
|
||||
const b3HullHalfEdge* twin = edges + edge->twin;
|
||||
b3Vec3 twinOrigin = points[twin->origin];
|
||||
|
||||
b3Vec3 axis = b3Normalize( b3Sub( twinOrigin, edgeOrigin ) );
|
||||
float edgeProjection = b3AbsFloat( b3Dot( axis, refNormal ) );
|
||||
if ( edgeProjection < minEdgeProjection )
|
||||
{
|
||||
minEdgeIndex = edgeIndex;
|
||||
minEdgeProjection = edgeProjection;
|
||||
}
|
||||
|
||||
edgeIndex = twin->next;
|
||||
edge = edges + edgeIndex;
|
||||
B3_ASSERT( edge->origin == vertexIndex );
|
||||
}
|
||||
while ( edge != edges + vertex->edge );
|
||||
B3_ASSERT( minEdgeIndex >= 0 );
|
||||
|
||||
const b3HullHalfEdge* minEdge = edges + minEdgeIndex;
|
||||
int minFaceIndex1 = minEdge->face;
|
||||
b3Plane minPlane1 = planes[minFaceIndex1];
|
||||
|
||||
const b3HullHalfEdge* minTwin = edges + minEdge->twin;
|
||||
int minFaceIndex2 = minTwin->face;
|
||||
b3Plane minPlane2 = planes[minFaceIndex2];
|
||||
|
||||
return b3Dot( minPlane1.normal, refNormal ) < b3Dot( minPlane2.normal, refNormal ) ? minFaceIndex1 : minFaceIndex2;
|
||||
}
|
||||
|
||||
b3FeaturePair b3MakeFeaturePair( b3FeatureOwner owner1, int index1, b3FeatureOwner owner2, int index2 )
|
||||
{
|
||||
B3_ASSERT( 0 <= index1 && index1 <= UINT8_MAX );
|
||||
B3_ASSERT( 0 <= index2 && index2 <= UINT8_MAX );
|
||||
|
||||
b3FeaturePair pair;
|
||||
pair.index1 = (uint8_t)index1;
|
||||
pair.owner1 = (uint8_t)owner1;
|
||||
pair.index2 = (uint8_t)index2;
|
||||
pair.owner2 = (uint8_t)owner2;
|
||||
return pair;
|
||||
}
|
||||
|
||||
// This logic seems wrong but it is designed so that choosing
|
||||
// face A or B as the reference face does not change the resulting
|
||||
// feature pair. This way the contact impulses are persisted even
|
||||
// if there is reference face flip-flop. This is verified in the
|
||||
// HullAndHull sample using the b3SATCache with a manual feature
|
||||
// specified such as b3_manualFaceAxisA.
|
||||
b3FeaturePair b3FlipPair( b3FeaturePair pair )
|
||||
{
|
||||
B3_ASSERT( pair.owner1 == 0 || pair.owner1 == 1 );
|
||||
B3_ASSERT( pair.owner2 == 0 || pair.owner2 == 1 );
|
||||
B3_SWAP( pair.owner1, pair.owner2 );
|
||||
pair.owner1 = 1 - pair.owner1;
|
||||
pair.owner2 = 1 - pair.owner2;
|
||||
B3_SWAP( pair.index1, pair.index2 );
|
||||
return pair;
|
||||
}
|
||||
|
||||
#if B3_ENABLE_VALIDATION
|
||||
bool b3ValidatePolygon( b3ClipVertex* polygon, int count )
|
||||
{
|
||||
// Empty polygons are valid (we can clip away all points when re-constructing manifolds from cache)
|
||||
if ( count == 0 )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate that incoming and outgoing edges match
|
||||
b3ClipVertex vertex1 = polygon[count - 1];
|
||||
for ( int i = 0; i < count; ++i )
|
||||
{
|
||||
b3ClipVertex vertex2 = polygon[i];
|
||||
|
||||
if ( vertex1.pair.owner2 != vertex2.pair.owner1 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( vertex1.pair.index2 != vertex2.pair.index1 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
vertex1 = vertex2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
int b3ClipPolygon( b3ClipVertex* out, b3ClipVertex* polygon, int count, b3Plane clipPlane, int edge, b3Plane refPlane )
|
||||
{
|
||||
B3_ASSERT( count >= 3 );
|
||||
|
||||
b3ClipVertex vertex1 = polygon[count - 1];
|
||||
float distance1 = b3PlaneSeparation( clipPlane, vertex1.position );
|
||||
int outCount = 0;
|
||||
|
||||
for ( int index = 0; index < count; ++index )
|
||||
{
|
||||
b3ClipVertex vertex2 = polygon[index];
|
||||
float distance2 = b3PlaneSeparation( clipPlane, vertex2.position );
|
||||
|
||||
// Clip edge against plane (Sutherland-Hodgman clipping)
|
||||
if ( distance1 <= 0.0f && distance2 <= 0.0f )
|
||||
{
|
||||
// Both vertices are behind the plane - keep vertex2
|
||||
out[outCount] = vertex2;
|
||||
outCount += 1;
|
||||
}
|
||||
else if ( distance1 <= 0.0f && distance2 > 0.0f )
|
||||
{
|
||||
// Vertex1 is behind of the plane, vertex2 is in front -> intersection point
|
||||
float fraction = distance1 / ( distance1 - distance2 );
|
||||
b3Vec3 position = b3MulAdd( vertex1.position, fraction, b3Sub( vertex2.position, vertex1.position ) );
|
||||
|
||||
// Keep intersection point and adjust outgoing edge
|
||||
b3ClipVertex vertex;
|
||||
vertex.position = position;
|
||||
vertex.separation = b3PlaneSeparation( refPlane, position );
|
||||
vertex.pair = vertex2.pair;
|
||||
vertex.pair.owner2 = b3_featureShapeA;
|
||||
vertex.pair.index2 = (uint8_t)edge;
|
||||
out[outCount] = vertex;
|
||||
outCount += 1;
|
||||
}
|
||||
else if ( distance2 <= 0.0f && distance1 > 0.0f )
|
||||
{
|
||||
// Vertex1 is in front, vertex2 is behind of the plane, -> intersection point
|
||||
float fraction = distance1 / ( distance1 - distance2 );
|
||||
b3Vec3 position = b3MulAdd( vertex1.position, fraction, b3Sub( vertex2.position, vertex1.position ) );
|
||||
|
||||
// Keep intersection point and adjust incoming edge
|
||||
b3ClipVertex vertex;
|
||||
vertex.position = position;
|
||||
vertex.separation = b3PlaneSeparation( refPlane, position );
|
||||
vertex.pair = vertex1.pair;
|
||||
vertex.pair.owner1 = b3_featureShapeA;
|
||||
vertex.pair.index1 = (uint8_t)edge;
|
||||
out[outCount] = vertex;
|
||||
outCount += 1;
|
||||
|
||||
// And also keep vertex2
|
||||
out[outCount] = vertex2;
|
||||
outCount += 1;
|
||||
}
|
||||
|
||||
// Keep vertex2 as starting vertex for next edge
|
||||
vertex1 = vertex2;
|
||||
distance1 = distance2;
|
||||
}
|
||||
|
||||
B3_VALIDATE( b3ValidatePolygon( out, outCount ) );
|
||||
|
||||
return outCount;
|
||||
}
|
||||
57
vendor/box3d/src/src/manifold.h
vendored
Normal file
57
vendor/box3d/src/src/manifold.h
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "box3d/collision.h"
|
||||
#include "box3d/math_functions.h"
|
||||
|
||||
#define B3_MAX_CLIP_POINTS 64
|
||||
|
||||
typedef struct b3FaceQuery
|
||||
{
|
||||
float separation;
|
||||
int faceIndex;
|
||||
int vertexIndex;
|
||||
} b3FaceQuery;
|
||||
|
||||
typedef struct b3EdgeQuery
|
||||
{
|
||||
float separation;
|
||||
int indexA;
|
||||
int indexB;
|
||||
} b3EdgeQuery;
|
||||
|
||||
typedef struct b3ClipVertex
|
||||
{
|
||||
b3Vec3 position;
|
||||
float separation;
|
||||
b3FeaturePair pair;
|
||||
} b3ClipVertex;
|
||||
|
||||
typedef enum b3FeatureOwner
|
||||
{
|
||||
b3_featureShapeA = 0,
|
||||
b3_featureShapeB = 1
|
||||
} b3FeatureOwner;
|
||||
|
||||
float b3EdgeEdgeSeparation( b3Vec3 p1, b3Vec3 e1, b3Vec3 c1, b3Vec3 p2, b3Vec3 e2, b3Vec3 c2 );
|
||||
int b3FindIncidentFace( const b3HullData* hull, b3Vec3 refNormal, int vertexIndex );
|
||||
b3FeaturePair b3MakeFeaturePair( b3FeatureOwner owner1, int index1, b3FeatureOwner owner2, int index2 );
|
||||
|
||||
b3FeaturePair b3FlipPair( b3FeaturePair pair );
|
||||
|
||||
int b3ClipPolygon( b3ClipVertex* out, b3ClipVertex* polygon, int count, b3Plane clipPlane, int edge, b3Plane refPlane );
|
||||
|
||||
#if B3_ENABLE_VALIDATION
|
||||
bool b3ValidatePolygon( b3ClipVertex* polygon, int count );
|
||||
#endif
|
||||
|
||||
// For single point contact, such as sphere-sphere, sphere-capsule, sphere-triangle
|
||||
static const b3FeaturePair b3FeaturePair_single = { 0 };
|
||||
|
||||
static inline uint32_t b3MakeFeatureId( b3FeaturePair pair )
|
||||
{
|
||||
return ( (uint32_t)pair.owner1 << 24 ) | ( (uint32_t)pair.index1 << 16 ) | ( (uint32_t)pair.owner2 << 8 ) |
|
||||
(uint32_t)pair.index2;
|
||||
}
|
||||
616
vendor/box3d/src/src/math_functions.c
vendored
Normal file
616
vendor/box3d/src/src/math_functions.c
vendored
Normal file
@@ -0,0 +1,616 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "box3d/math_functions.h"
|
||||
|
||||
#include "math_internal.h"
|
||||
|
||||
#include "box3d/collision.h"
|
||||
#include "box3d/constants.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
bool b3IsValidFloat( float a )
|
||||
{
|
||||
if ( isnan( a ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isinf( a ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool b3IsValidVec3( b3Vec3 a )
|
||||
{
|
||||
if ( isnan( a.x ) || isnan( a.y ) || isnan( a.z ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isinf( a.x ) || isinf( a.y ) || isinf( a.z ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool b3IsValidQuat( b3Quat a )
|
||||
{
|
||||
if ( isnan( a.v.x ) || isnan( a.v.y ) || isnan( a.v.z ) || isnan( a.s ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isinf( a.v.x ) || isinf( a.v.y ) || isinf( a.v.z ) || isinf( a.s ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return b3IsNormalizedQuat( a );
|
||||
}
|
||||
|
||||
bool b3IsValidTransform( b3Transform a )
|
||||
{
|
||||
return b3IsValidVec3( a.p ) && b3IsValidQuat( a.q );
|
||||
}
|
||||
|
||||
bool b3IsValidMatrix3( b3Matrix3 a )
|
||||
{
|
||||
return b3IsValidVec3( a.cx ) && b3IsValidVec3( a.cy ) && b3IsValidVec3( a.cz );
|
||||
}
|
||||
|
||||
bool b3IsValidAABB( b3AABB a )
|
||||
{
|
||||
if ( b3IsValidVec3( a.lowerBound ) == false )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( b3IsValidVec3( a.upperBound ) == false )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( a.lowerBound.x > a.upperBound.x )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( a.lowerBound.y > a.upperBound.y )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( a.lowerBound.z > a.upperBound.z )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool b3IsBoundedAABB( b3AABB a )
|
||||
{
|
||||
if ( a.lowerBound.x < -B3_HUGE || a.lowerBound.y < -B3_HUGE || a.lowerBound.z < -B3_HUGE )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( a.upperBound.x > B3_HUGE || a.upperBound.y > B3_HUGE || a.upperBound.z > B3_HUGE )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool b3IsSaneAABB( b3AABB a )
|
||||
{
|
||||
if ( b3IsValidAABB( a ) == false )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( a.lowerBound.x < -B3_HUGE || a.lowerBound.y < -B3_HUGE || a.lowerBound.z < -B3_HUGE )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( a.upperBound.x > B3_HUGE || a.upperBound.y > B3_HUGE || a.upperBound.z > B3_HUGE )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool b3IsValidPlane( b3Plane a )
|
||||
{
|
||||
if ( b3IsValidVec3( a.normal ) == false )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( b3IsNormalized( a.normal ) == false )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return b3IsValidFloat( a.offset );
|
||||
}
|
||||
|
||||
bool b3IsValidPosition( b3Pos p )
|
||||
{
|
||||
if ( isnan( p.x ) || isnan( p.y ) || isnan( p.z ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isinf( p.x ) || isinf( p.y ) || isinf( p.z ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool b3IsValidWorldTransform( b3WorldTransform t )
|
||||
{
|
||||
return b3IsValidPosition( t.p ) && b3IsValidQuat( t.q );
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/46210708/atan2-approximation-with-11bits-in-mantissa-on-x86with-sse2-and-armwith-vfpv4
|
||||
float b3Atan2( float y, float x )
|
||||
{
|
||||
// Added check for (0,0) to match atan2f and avoid NaN
|
||||
if ( x == 0.0f && y == 0.0f )
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float ax = b3AbsFloat( x );
|
||||
float ay = b3AbsFloat( y );
|
||||
float mx = b3MaxFloat( ay, ax );
|
||||
float mn = b3MinFloat( ay, ax );
|
||||
float a = mn / mx;
|
||||
|
||||
// Minimax polynomial approximation to atan(a) on [0,1]
|
||||
float s = a * a;
|
||||
float c = s * a;
|
||||
float q = s * s;
|
||||
float r = 0.024840285f * q + 0.18681418f;
|
||||
float t = -0.094097948f * q - 0.33213072f;
|
||||
r = r * s + t;
|
||||
r = r * c + a;
|
||||
|
||||
// Map to full circle
|
||||
if ( ay > ax )
|
||||
{
|
||||
r = 1.57079637f - r;
|
||||
}
|
||||
|
||||
if ( x < 0 )
|
||||
{
|
||||
r = 3.14159274f - r;
|
||||
}
|
||||
|
||||
if ( y < 0 )
|
||||
{
|
||||
r = -r;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
// Approximate cosine and sine for determinism. In my testing cosf and sinf produced
|
||||
// the same results on x64 and ARM using MSVC, GCC, and Clang. However, I don't trust
|
||||
// this result.
|
||||
// https://en.wikipedia.org/wiki/Bh%C4%81skara_I%27s_sine_approximation_formula
|
||||
b3CosSin b3ComputeCosSin( float radians )
|
||||
{
|
||||
#if 0
|
||||
return {
|
||||
cosf( radians ),
|
||||
sinf( radians ),
|
||||
};
|
||||
#else
|
||||
float x = b3UnwindAngle( radians );
|
||||
float pi2 = B3_PI * B3_PI;
|
||||
|
||||
// cosine needs angle in [-pi/2, pi/2]
|
||||
float c;
|
||||
if ( x < -0.5f * B3_PI )
|
||||
{
|
||||
float y = x + B3_PI;
|
||||
float y2 = y * y;
|
||||
c = -( pi2 - 4.0f * y2 ) / ( pi2 + y2 );
|
||||
}
|
||||
else if ( x > 0.5f * B3_PI )
|
||||
{
|
||||
float y = x - B3_PI;
|
||||
float y2 = y * y;
|
||||
c = -( pi2 - 4.0f * y2 ) / ( pi2 + y2 );
|
||||
}
|
||||
else
|
||||
{
|
||||
float y2 = x * x;
|
||||
c = ( pi2 - 4.0f * y2 ) / ( pi2 + y2 );
|
||||
}
|
||||
|
||||
// sine needs angle in [0, pi]
|
||||
float s;
|
||||
if ( x < 0.0f )
|
||||
{
|
||||
float y = x + B3_PI;
|
||||
s = -16.0f * y * ( B3_PI - y ) / ( 5.0f * pi2 - 4.0f * y * ( B3_PI - y ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
s = 16.0f * x * ( B3_PI - x ) / ( 5.0f * pi2 - 4.0f * x * ( B3_PI - x ) );
|
||||
}
|
||||
|
||||
float mag = sqrtf( s * s + c * c );
|
||||
float invMag = mag > 0.0f ? 1.0f / mag : 0.0f;
|
||||
b3CosSin cs = { c * invMag, s * invMag };
|
||||
return cs;
|
||||
#endif
|
||||
}
|
||||
|
||||
b3Quat b3MakeQuatFromMatrix( const b3Matrix3* m )
|
||||
{
|
||||
b3Vec3 c1 = m->cx;
|
||||
b3Vec3 c2 = m->cy;
|
||||
b3Vec3 c3 = m->cz;
|
||||
|
||||
b3Quat q;
|
||||
|
||||
float trace = m->cx.x + m->cy.y + m->cz.z;
|
||||
if ( trace >= 0.0f )
|
||||
{
|
||||
q.v.x = c2.z - c3.y;
|
||||
q.v.y = c3.x - c1.z;
|
||||
q.v.z = c1.y - c2.x;
|
||||
q.s = trace + 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( c1.x > c2.y && c1.x > c3.z )
|
||||
{
|
||||
q.v.x = c1.x - c2.y - c3.z + 1.0f;
|
||||
q.v.y = c2.x + c1.y;
|
||||
q.v.z = c3.x + c1.z;
|
||||
q.s = c2.z - c3.y;
|
||||
}
|
||||
else if ( c2.y > c3.z )
|
||||
{
|
||||
q.v.x = c1.y + c2.x;
|
||||
q.v.y = c2.y - c3.z - c1.x + 1.0f;
|
||||
q.v.z = c3.y + c2.z;
|
||||
q.s = c3.x - c1.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
q.v.x = c1.z + c3.x;
|
||||
q.v.y = c2.z + c3.y;
|
||||
q.v.z = c3.z - c1.x - c2.y + 1.0f;
|
||||
q.s = c1.y - c2.x;
|
||||
}
|
||||
}
|
||||
|
||||
// The algorithm is simplified and made more accurate by normalizing at the end
|
||||
return b3NormalizeQuat( q );
|
||||
}
|
||||
|
||||
b3Quat b3ComputeQuatBetweenUnitVectors( b3Vec3 v1, b3Vec3 v2 )
|
||||
{
|
||||
B3_ASSERT( b3IsNormalized( v1 ) );
|
||||
B3_ASSERT( b3IsNormalized( v2 ) );
|
||||
|
||||
b3Quat out;
|
||||
|
||||
b3Vec3 m = b3Lerp( v1, v2, 0.5f );
|
||||
float tolerance = 100.0f * FLT_EPSILON;
|
||||
if ( b3LengthSquared( m ) > tolerance * tolerance )
|
||||
{
|
||||
out.v = b3Cross( v1, m );
|
||||
out.s = b3Dot( v1, m );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Anti-parallel: Use a perpendicular vector
|
||||
if ( b3AbsFloat( v1.x ) > 0.5f )
|
||||
{
|
||||
out.v.x = v1.y;
|
||||
out.v.y = -v1.x;
|
||||
out.v.z = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.v.x = 0.0f;
|
||||
out.v.y = v1.z;
|
||||
out.v.z = -v1.y;
|
||||
}
|
||||
|
||||
out.s = 0.0f;
|
||||
}
|
||||
|
||||
// The algorithm is simplified and made more accurate by normalizing at the end
|
||||
return b3NormalizeQuat( out );
|
||||
}
|
||||
|
||||
b3SegmentDistanceResult b3LineDistance( b3Vec3 p1, b3Vec3 d1, b3Vec3 p2, b3Vec3 d2 )
|
||||
{
|
||||
b3SegmentDistanceResult result;
|
||||
|
||||
// Solve A*x = b
|
||||
float a11 = b3Dot( d1, d1 );
|
||||
float a12 = -b3Dot( d1, d2 );
|
||||
float a21 = b3Dot( d2, d1 );
|
||||
float a22 = -b3Dot( d2, d2 );
|
||||
|
||||
b3Vec3 w = b3Sub( p1, p2 );
|
||||
float b1 = -b3Dot( d1, w );
|
||||
float b2 = -b3Dot( d2, w );
|
||||
|
||||
float det = a11 * a22 - a12 * a21;
|
||||
if ( det * det < 1000.0f * FLT_MIN )
|
||||
{
|
||||
// Lines are parallel - project p2 onto line L1: x1 = p1 + s1 * d1
|
||||
float s1 = b3Dot( b3Sub( p2, p1 ), d1 ) / b3Dot( d1, d1 );
|
||||
float s2 = 0.0f;
|
||||
|
||||
result.point1 = b3MulAdd( p1, s1, d1 );
|
||||
result.fraction1 = s1;
|
||||
result.point2 = b3MulAdd( p2, s2, d2 );
|
||||
result.fraction2 = s2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
float s1 = ( a22 * b1 - a12 * b2 ) / det;
|
||||
float s2 = ( a11 * b2 - a21 * b1 ) / det;
|
||||
|
||||
result.point1 = b3MulAdd( p1, s1, d1 );
|
||||
result.fraction1 = s1;
|
||||
result.point2 = b3MulAdd( p2, s2, d2 );
|
||||
result.fraction2 = s2;
|
||||
return result;
|
||||
}
|
||||
|
||||
b3SegmentDistanceResult b3SegmentDistance( b3Vec3 p1, b3Vec3 q1, b3Vec3 p2, b3Vec3 q2 )
|
||||
{
|
||||
b3SegmentDistanceResult result;
|
||||
|
||||
b3Vec3 d1 = b3Sub( q1, p1 );
|
||||
b3Vec3 d2 = b3Sub( q2, p2 );
|
||||
b3Vec3 r = b3Sub( p1, p2 );
|
||||
|
||||
float a = b3Dot( d1, d1 );
|
||||
float b = b3Dot( d1, d2 );
|
||||
float c = b3Dot( d1, r );
|
||||
float e = b3Dot( d2, d2 );
|
||||
float f = b3Dot( d2, r );
|
||||
|
||||
// Check if one of the segments degenerates into a point
|
||||
if ( a < 100.0f * FLT_EPSILON && e < 100.0f * FLT_EPSILON )
|
||||
{
|
||||
// Both segments degenerate into points
|
||||
result.point1 = p1;
|
||||
result.fraction1 = 0.0f;
|
||||
result.point2 = p2;
|
||||
result.fraction2 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if ( a < 100.0f * FLT_EPSILON )
|
||||
{
|
||||
// First segment degenerates into a point
|
||||
float s2 = b3ClampFloat( f / e, 0.0f, 1.0f );
|
||||
|
||||
result.point1 = p1;
|
||||
result.fraction1 = 0.0f;
|
||||
result.point2 = b3MulAdd( p2, s2, d2 );
|
||||
result.fraction2 = s2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if ( e < 100.0f * FLT_EPSILON )
|
||||
{
|
||||
// Second segment degenerates into a point
|
||||
float s1 = b3ClampFloat( -c / a, 0.0f, 1.0f );
|
||||
|
||||
result.point1 = b3MulAdd( p1, s1, d1 );
|
||||
result.fraction1 = s1;
|
||||
result.point2 = p2;
|
||||
result.fraction2 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Non-degenerate case
|
||||
float denom = a * e - b * b;
|
||||
float s1 = denom > 1000.0f * FLT_MIN ? b3ClampFloat( ( b * f - c * e ) / denom, 0.0f, 1.0f ) : 0.0f;
|
||||
float s2 = ( b * s1 + f ) / e;
|
||||
|
||||
// Clamp lambda2 and recompute lambda1 if necessary
|
||||
if ( s2 < 0.0f )
|
||||
{
|
||||
s1 = b3ClampFloat( -c / a, 0.0f, 1.0f );
|
||||
s2 = 0.0f;
|
||||
}
|
||||
else if ( s2 > 1.0f )
|
||||
{
|
||||
s1 = b3ClampFloat( ( b - c ) / a, 0.0f, 1.0f );
|
||||
s2 = 1.0f;
|
||||
}
|
||||
|
||||
result.point1 = b3MulAdd( p1, s1, d1 );
|
||||
result.fraction1 = s1;
|
||||
result.point2 = b3MulAdd( p2, s2, d2 );
|
||||
result.fraction2 = s2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
b3Vec3 b3PointToSegmentDistance( b3Vec3 a, b3Vec3 b, b3Vec3 q )
|
||||
{
|
||||
b3Vec3 ab = b3Sub( b, a );
|
||||
b3Vec3 aq = b3Sub( q, a );
|
||||
|
||||
float alpha = b3Dot( ab, aq );
|
||||
|
||||
if ( alpha <= 0.0f )
|
||||
{
|
||||
// q projects outside interval [a, b] on the side of a
|
||||
return a;
|
||||
}
|
||||
else
|
||||
{
|
||||
float denominator = b3Dot( ab, ab );
|
||||
if ( alpha > denominator )
|
||||
{
|
||||
// q projects outside interval [a, b] on the side of b
|
||||
return b;
|
||||
}
|
||||
else
|
||||
{
|
||||
// q projects inside interval [a, b]
|
||||
alpha /= denominator;
|
||||
return b3MulAdd( a, alpha, ab );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b3TrianglePoint b3ClosestPointOnTriangle( b3Vec3 a, b3Vec3 b, b3Vec3 c, b3Vec3 q )
|
||||
{
|
||||
// Check if P lies in vertex region of A
|
||||
b3Vec3 ab = b3Sub( b, a );
|
||||
b3Vec3 ac = b3Sub( c, a );
|
||||
b3Vec3 aq = b3Sub( q, a );
|
||||
|
||||
float d1 = b3Dot( ab, aq );
|
||||
float d2 = b3Dot( ac, aq );
|
||||
if ( d1 <= 0.0f && d2 <= 0.0f )
|
||||
{
|
||||
return (b3TrianglePoint){ a, b3_featureVertex1 };
|
||||
}
|
||||
|
||||
// Check if P lies in vertex region of B
|
||||
b3Vec3 bq = b3Sub( q, b );
|
||||
|
||||
float d3 = b3Dot( ab, bq );
|
||||
float d4 = b3Dot( ac, bq );
|
||||
if ( d3 > 0.0f && d4 <= d3 )
|
||||
{
|
||||
return (b3TrianglePoint){ b, b3_featureVertex2 };
|
||||
}
|
||||
|
||||
// Check if P lies in edge region AB
|
||||
float vc = d1 * d4 - d3 * d2;
|
||||
if ( vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f )
|
||||
{
|
||||
float t = d1 / ( d1 - d3 );
|
||||
return (b3TrianglePoint){ b3MulAdd( a, t, ab ), b3_featureEdge1 };
|
||||
}
|
||||
|
||||
// Check if P lies in vertex region of C
|
||||
b3Vec3 cq = b3Sub( q, c );
|
||||
|
||||
float d5 = b3Dot( ab, cq );
|
||||
float d6 = b3Dot( ac, cq );
|
||||
if ( d6 >= 0.0f && d5 <= d6 )
|
||||
{
|
||||
return (b3TrianglePoint){ c, b3_featureVertex3 };
|
||||
}
|
||||
|
||||
// Check if P lies in edge region AC
|
||||
float vb = d5 * d2 - d1 * d6;
|
||||
if ( vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f )
|
||||
{
|
||||
float t = d2 / ( d2 - d6 );
|
||||
return (b3TrianglePoint){ b3MulAdd( a, t, ac ), b3_featureEdge3 };
|
||||
}
|
||||
|
||||
// Check if P lies in edge region of BC
|
||||
float va = d3 * d6 - d5 * d4;
|
||||
if ( va <= 0.0f && d4 >= d3 && d5 >= d6 )
|
||||
{
|
||||
b3Vec3 bc = b3Sub( c, b );
|
||||
|
||||
float t = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) );
|
||||
return (b3TrianglePoint){ b3MulAdd( b, t, bc ), b3_featureEdge2 };
|
||||
}
|
||||
|
||||
// P inside face region ABC
|
||||
float t1 = vb / ( va + vb + vc );
|
||||
float t2 = vc / ( va + vb + vc );
|
||||
|
||||
b3Vec3 p = b3MulAdd( a, t1, ab );
|
||||
p = b3MulAdd( p, t2, ac );
|
||||
return (b3TrianglePoint){ p, b3_featureTriangleFace };
|
||||
}
|
||||
|
||||
b3Matrix3 b3SphereInertia( float mass, float radius )
|
||||
{
|
||||
float i = 0.4f * mass * radius * radius;
|
||||
return b3MakeDiagonalMatrix( i, i, i );
|
||||
}
|
||||
|
||||
b3Matrix3 b3CylinderInertia( float mass, float radius, float height )
|
||||
{
|
||||
float ixx = mass * ( 3 * radius * radius + height * height ) / 12.0f;
|
||||
float iyy = 0.5f * mass * radius * radius;
|
||||
return b3MakeDiagonalMatrix( ixx, iyy, ixx );
|
||||
}
|
||||
|
||||
b3Matrix3 b3BoxInertia( float mass, b3Vec3 min, b3Vec3 max )
|
||||
{
|
||||
b3Vec3 delta = b3Sub( max, min );
|
||||
float ixx = mass * ( delta.y * delta.y + delta.z * delta.z ) / 12.0f;
|
||||
float iyy = mass * ( delta.x * delta.x + delta.z * delta.z ) / 12.0f;
|
||||
float izz = mass * ( delta.x * delta.x + delta.y * delta.y ) / 12.0f;
|
||||
|
||||
return b3MakeDiagonalMatrix( ixx, iyy, izz );
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Parallel_axis_theorem
|
||||
b3Matrix3 b3Steiner( float mass, b3Vec3 origin )
|
||||
{
|
||||
// Usage: Io = Ic + Is and Ic = Io - Is
|
||||
float ixx = mass * ( origin.y * origin.y + origin.z * origin.z );
|
||||
float iyy = mass * ( origin.x * origin.x + origin.z * origin.z );
|
||||
float izz = mass * ( origin.x * origin.x + origin.y * origin.y );
|
||||
float ixy = -mass * origin.x * origin.y;
|
||||
float ixz = -mass * origin.x * origin.z;
|
||||
float iyz = -mass * origin.y * origin.z;
|
||||
|
||||
// Write
|
||||
b3Matrix3 out;
|
||||
out.cx.x = ixx;
|
||||
out.cy.x = ixy;
|
||||
out.cz.x = ixz;
|
||||
out.cx.y = ixy;
|
||||
out.cy.y = iyy;
|
||||
out.cz.y = iyz;
|
||||
out.cx.z = ixz;
|
||||
out.cy.z = iyz;
|
||||
out.cz.z = izz;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool b3IsValidRay( const b3RayCastInput* input )
|
||||
{
|
||||
bool isValid = b3IsValidVec3( input->origin ) && b3IsValidVec3( input->translation ) &&
|
||||
b3IsValidFloat( input->maxFraction ) && 0.0f <= input->maxFraction && input->maxFraction < B3_HUGE;
|
||||
return isValid;
|
||||
}
|
||||
480
vendor/box3d/src/src/math_internal.h
vendored
Normal file
480
vendor/box3d/src/src/math_internal.h
vendored
Normal file
@@ -0,0 +1,480 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include "box3d/collision.h"
|
||||
#include "box3d/math_functions.h"
|
||||
|
||||
struct b3Sweep;
|
||||
struct b3Plane;
|
||||
|
||||
#define B3_TWO_PI 6.283185307f
|
||||
#define B3_PI_OVER_TWO 1.570796327f
|
||||
#define B3_PI_OVER_FOUR 0.785398163f
|
||||
#define B3_SQRT3 1.732050808f
|
||||
|
||||
// todo eliminate this
|
||||
static const b3AABB B3_BOUNDS3_EMPTY = { { FLT_MAX, FLT_MAX, FLT_MAX }, { -FLT_MAX, -FLT_MAX, -FLT_MAX } };
|
||||
|
||||
typedef struct b3Matrix2
|
||||
{
|
||||
b3Vec2 cx, cy;
|
||||
} b3Matrix2;
|
||||
|
||||
typedef struct b3Triangle
|
||||
{
|
||||
b3Vec3 vertices[3];
|
||||
int i1, i2, i3;
|
||||
int flags;
|
||||
} b3Triangle;
|
||||
|
||||
typedef struct b3TrianglePoint
|
||||
{
|
||||
b3Vec3 point;
|
||||
b3TriangleFeature feature;
|
||||
} b3TrianglePoint;
|
||||
|
||||
typedef struct b3ShapeExtent
|
||||
{
|
||||
float minExtent;
|
||||
b3Vec3 maxExtent;
|
||||
} b3ShapeExtent;
|
||||
|
||||
b3TrianglePoint b3ClosestPointOnTriangle( b3Vec3 a, b3Vec3 b, b3Vec3 c, b3Vec3 q );
|
||||
|
||||
float b3IntersectSegmentTriangle( b3Vec3 p, b3Vec3 q, b3Vec3 a, b3Vec3 b, b3Vec3 c );
|
||||
float b3IntersectSegmentSphere( b3Vec3 p, b3Vec3 q, b3Vec3 c, float r );
|
||||
|
||||
b3MassData b3ComputeMassProperties( int triangleCount, const int* triangles, int vertexCount, const b3Vec3* vertices,
|
||||
float density );
|
||||
|
||||
bool b3IsValidMassData( const b3MassData* massData );
|
||||
|
||||
b3Matrix3 b3SphereInertia( float mass, float radius );
|
||||
b3Matrix3 b3CylinderInertia( float mass, float radius, float height );
|
||||
b3Matrix3 b3BoxInertia( float mass, b3Vec3 min, b3Vec3 max );
|
||||
|
||||
// Inertia helper (Io = Ic + Is and Ic = Io - Is)
|
||||
int b3GetProxySupport( const b3ShapeProxy* proxy, b3Vec3 axis );
|
||||
int b3GetPointSupport( const b3Vec3* points, int count, b3Vec3 axis );
|
||||
|
||||
static inline size_t b3AlignUp8( size_t x )
|
||||
{
|
||||
return ( x + 7u ) & ~(size_t)7u;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Floor_and_ceiling_functions
|
||||
static inline int b3CeilingInt( int numerator, int denominator )
|
||||
{
|
||||
B3_VALIDATE( denominator > 0 );
|
||||
return ( numerator + denominator - 1 ) / denominator;
|
||||
}
|
||||
|
||||
// Assumes denominator == 2^exponent
|
||||
static inline int b3CeilingPow2( int numerator, int denominator, int exponent )
|
||||
{
|
||||
B3_VALIDATE( exponent > 0 && ( denominator == 1 << exponent ) );
|
||||
return ( numerator + denominator - 1 ) >> exponent;
|
||||
}
|
||||
|
||||
bool b3IsSweepNormalized( b3Sweep* sweep );
|
||||
|
||||
static inline float b3Dot2( b3Vec2 v1, b3Vec2 v2 )
|
||||
{
|
||||
return v1.x * v2.x + v1.y * v2.y;
|
||||
}
|
||||
|
||||
static inline float b3Length2( b3Vec2 v )
|
||||
{
|
||||
return sqrtf( b3Dot2( v, v ) );
|
||||
}
|
||||
|
||||
static inline float b3LengthSquared2( b3Vec2 v )
|
||||
{
|
||||
return b3Dot2( v, v );
|
||||
}
|
||||
|
||||
static inline b3Vec2 b3MinVec2( b3Vec2 v1, b3Vec2 v2 )
|
||||
{
|
||||
b3Vec2 v;
|
||||
v.x = b3MinFloat( v1.x, v2.x );
|
||||
v.y = b3MinFloat( v1.y, v2.y );
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline b3Vec2 b3MaxVec2( b3Vec2 v1, b3Vec2 v2 )
|
||||
{
|
||||
b3Vec2 v;
|
||||
v.x = b3MaxFloat( v1.x, v2.x );
|
||||
v.y = b3MaxFloat( v1.y, v2.y );
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline void b3Store( float* dst, b3Vec3 src )
|
||||
{
|
||||
dst[0] = src.x;
|
||||
dst[1] = src.y;
|
||||
dst[2] = src.z;
|
||||
}
|
||||
|
||||
static inline b3Vec3 b3ClampLength( b3Vec3 v, float maxLength )
|
||||
{
|
||||
float lengthSq = b3LengthSquared( v );
|
||||
if ( lengthSq <= maxLength * maxLength )
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
float length = sqrtf( lengthSq );
|
||||
return b3MulSV( maxLength / length, v );
|
||||
}
|
||||
|
||||
// Assume v is a unit vector
|
||||
static inline b3Vec3 b3ArbitraryPerp( b3Vec3 v )
|
||||
{
|
||||
// Suppose vector a has all equal components and is a unit vector: a = (s, s, s)
|
||||
// Then 3*s*s = 1, s = sqrt(1/3) = 0.57735. This means that at least one component
|
||||
// of a unit vector must be greater or equal to 0.57735.
|
||||
b3Vec3 p;
|
||||
if ( v.x < -0.5f || 0.5f < v.x )
|
||||
{
|
||||
// x is non-zero and it should not go into the x component
|
||||
// dot([ay + bz, cx, dx], [x, y, z]) = ayx + bzx + cxy + dzx
|
||||
// for the dot product to be zero need: c = -a, d = -b
|
||||
float a = 0.67f;
|
||||
float b = -0.42f;
|
||||
p = B3_LITERAL( b3Vec3 ){ a * v.y + b * v.z, -a * v.x, -b * v.x };
|
||||
}
|
||||
else if ( v.y < -0.5f || 0.5f < v.y )
|
||||
{
|
||||
// y is non-zero and it should not go into the y component
|
||||
// p = [ay, bx + cz, dy]
|
||||
// axy + bxy + cyz + dyz = 0
|
||||
// b = -a, d = -c
|
||||
float a = 0.67f;
|
||||
float c = -0.42f;
|
||||
p = B3_LITERAL( b3Vec3 ){ a * v.y, -a * v.x + c * v.z, -c * v.y };
|
||||
}
|
||||
else
|
||||
{
|
||||
// This would trip if the input is not a unit vector
|
||||
B3_VALIDATE( v.z < -0.5f || 0.5f < v.z );
|
||||
|
||||
// z is non-zero and it should not go into the z component
|
||||
// p = [az, bz, cx + dy]
|
||||
// axz + byz + cxz + dyz = 0
|
||||
// c = -a, d = -b
|
||||
float a = 0.67f;
|
||||
float b = -0.42f;
|
||||
p = B3_LITERAL( b3Vec3 ){ a * v.z, b * v.z, -a * v.x - b * v.y };
|
||||
}
|
||||
|
||||
B3_VALIDATE( b3LengthSquared( p ) > 0.1f );
|
||||
B3_VALIDATE( b3AbsFloat( b3Dot( p, v ) ) < 100.0f * FLT_EPSILON );
|
||||
|
||||
return b3Normalize( p );
|
||||
}
|
||||
|
||||
static inline b3Quat b3QuatFromExponentialMap( b3Vec3 v )
|
||||
{
|
||||
// Exponential map (Grassia)
|
||||
float threshold = 0.018581361f;
|
||||
|
||||
float angle = b3Length( v );
|
||||
if ( angle < threshold )
|
||||
{
|
||||
// Taylor expansion
|
||||
b3Quat out;
|
||||
out.v = b3MulSV( 0.5f + angle * angle / 48.0f, v );
|
||||
out.s = b3Cos( 0.5f * angle );
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
return b3MakeQuatFromAxisAngle( b3MulSV( 1.0f / angle, v ), angle );
|
||||
}
|
||||
|
||||
/// Integrate rotation from angular velocity
|
||||
/// @param q1 initial rotation
|
||||
/// @param deltaRotation the angular displacement vector in radians (angular velocity multiplied by the time step)
|
||||
/// q2 = q1 + 0.5 * omega * q1
|
||||
static inline b3Quat b3IntegrateRotation( b3Quat q1, b3Vec3 deltaRotation )
|
||||
{
|
||||
#if 1
|
||||
// https://fgiesen.wordpress.com/2012/08/24/quaternion-differentiation/
|
||||
b3Quat qd = { b3MulSV( 0.5f, deltaRotation ), 0.0f };
|
||||
qd = b3MulQuat( qd, q1 );
|
||||
b3Quat q2 = { b3Add( q1.v, qd.v ), qd.s + q1.s };
|
||||
q2 = b3NormalizeQuat( q2 );
|
||||
return q2;
|
||||
#else
|
||||
return b3NormalizeQuat( b3MulQuat(b3QuatFromExponentialMap( deltaRotation ), q1) );
|
||||
#endif
|
||||
}
|
||||
|
||||
// Pseudo angular velocity from a quaternion target
|
||||
// w = 2 * (target - q) * conj(q)
|
||||
static inline b3Vec3 b3DeltaQuatToRotation( b3Quat q, b3Quat target )
|
||||
{
|
||||
b3Quat s = q;
|
||||
if ( b3DotQuat( q, target ) < 0.0f )
|
||||
{
|
||||
// Correct polarity
|
||||
s = b3NegateQuat( q );
|
||||
}
|
||||
|
||||
b3Quat diff = { b3Sub( target.v, s.v ), target.s - s.s };
|
||||
b3Quat product = b3MulQuat( diff, b3Conjugate( s ) );
|
||||
return b3MulSV( 2.0f, product.v );
|
||||
}
|
||||
|
||||
static inline float b3ScalarTripleProduct( b3Vec3 a, b3Vec3 b, b3Vec3 c )
|
||||
{
|
||||
b3Vec3 d;
|
||||
d.x = b.y * c.z - b.z * c.y;
|
||||
d.y = b.z * c.x - b.x * c.z;
|
||||
d.z = b.x * c.y - b.y * c.x;
|
||||
return a.x * d.x + a.y * d.y + a.z * d.z;
|
||||
}
|
||||
|
||||
// Get a value by index. Avoid undefined behavior of code like (&v.x)[2].
|
||||
static inline float b3GetByIndex( b3Vec3 v, int index )
|
||||
{
|
||||
B3_VALIDATE( 0 <= index && index < 3 );
|
||||
float temp[3] = { v.x, v.y, v.z };
|
||||
return temp[index];
|
||||
}
|
||||
|
||||
static inline int b3MajorAxis( b3Vec3 v )
|
||||
{
|
||||
return v.x < v.y ? ( v.y < v.z ? 2 : 1 ) : ( v.x < v.z ? 2 : 0 );
|
||||
}
|
||||
|
||||
static inline float b3MinElement( b3Vec3 v )
|
||||
{
|
||||
return b3MinFloat( v.x, b3MinFloat( v.y, v.z ) );
|
||||
}
|
||||
|
||||
static inline float b3MaxElement( b3Vec3 v )
|
||||
{
|
||||
return b3MaxFloat( v.x, b3MaxFloat( v.y, v.z ) );
|
||||
}
|
||||
|
||||
static inline int b3MaxElementIndex( b3Vec3 v )
|
||||
{
|
||||
return v.x < v.y ? ( v.y < v.z ? 2 : 1 ) : ( v.x < v.z ? 2 : 0 );
|
||||
}
|
||||
|
||||
static inline b3Vec2 b3Add2( b3Vec2 a, b3Vec2 b )
|
||||
{
|
||||
b3Vec2 c = { a.x + b.x, a.y + b.y };
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline b3Vec2 b3Sub2( b3Vec2 a, b3Vec2 b )
|
||||
{
|
||||
b3Vec2 c = { a.x - b.x, a.y - b.y };
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline b3Vec2 b3Neg2( b3Vec2 v )
|
||||
{
|
||||
b3Vec2 c = { -v.x, -v.y };
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline b3Vec2 b3MulSV2( float s, b3Vec2 v )
|
||||
{
|
||||
b3Vec2 c = { s * v.x, s * v.y };
|
||||
return c;
|
||||
}
|
||||
|
||||
// a + s * b
|
||||
static inline b3Vec2 b3MulAdd2( b3Vec2 a, float s, b3Vec2 b )
|
||||
{
|
||||
b3Vec2 c = { a.x + s * b.x, a.y + s * b.y };
|
||||
return c;
|
||||
}
|
||||
|
||||
// a - s * b
|
||||
static inline b3Vec2 b3MulSub2( b3Vec2 a, float s, b3Vec2 b )
|
||||
{
|
||||
b3Vec2 c = { a.x - s * b.x, a.y - s * b.y };
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline float b3Cross2( b3Vec2 a, b3Vec2 b )
|
||||
{
|
||||
return a.x * b.y - a.y * b.x;
|
||||
}
|
||||
|
||||
static inline float b3DistanceSquared2( b3Vec2 a, b3Vec2 b )
|
||||
{
|
||||
float dx = b.x - a.x;
|
||||
float dy = b.y - a.y;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
static inline b3Vec2 b3MulMV2( b3Matrix2 m, b3Vec2 a )
|
||||
{
|
||||
b3Vec2 b = { m.cx.x * a.x + m.cy.x * a.y, m.cx.y * a.x + m.cy.y * a.y };
|
||||
return b;
|
||||
}
|
||||
|
||||
static inline b3Matrix2 b3MulMM2( b3Matrix2 m1, b3Matrix2 m2 )
|
||||
{
|
||||
b3Matrix2 out;
|
||||
out.cx = b3MulMV2( m1, m2.cx );
|
||||
out.cy = b3MulMV2( m1, m2.cy );
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline float b3Det2( b3Matrix2 m )
|
||||
{
|
||||
return m.cx.x * m.cy.y - m.cx.y * m.cy.x;
|
||||
}
|
||||
|
||||
static inline b3Matrix2 b3Invert2( b3Matrix2 m )
|
||||
{
|
||||
float det = b3Det2( m );
|
||||
if ( b3AbsFloat( det ) > 1000.0f * FLT_MIN )
|
||||
{
|
||||
float invDet = 1.0f / det;
|
||||
return B3_LITERAL( b3Matrix2 ){
|
||||
{ invDet * m.cy.y, -invDet * m.cx.y },
|
||||
{ -invDet * m.cy.x, invDet * m.cx.x },
|
||||
};
|
||||
}
|
||||
|
||||
return B3_LITERAL( b3Matrix2 ){ { 0.0f, 0.0f }, { 0.0f, 0.0f } };
|
||||
}
|
||||
|
||||
// Assumes positive semi-definite
|
||||
static inline b3Vec2 b3Solve2( b3Matrix2 m, b3Vec2 b )
|
||||
{
|
||||
float det = b3Det2( m );
|
||||
if ( det > 1000.0f * FLT_MIN )
|
||||
{
|
||||
float invDet = 1.0f / det;
|
||||
return B3_LITERAL( b3Vec2 ){
|
||||
invDet * m.cy.y * b.x - invDet * m.cy.x * b.y,
|
||||
-invDet * m.cx.y * b.x + invDet * m.cx.x * b.y,
|
||||
};
|
||||
}
|
||||
|
||||
return B3_LITERAL( b3Vec2 ){ 0.0f, 0.0f };
|
||||
}
|
||||
|
||||
// Convenience function: s * a + t * b + u * c
|
||||
static inline b3Vec3 b3Blend3( float s, b3Vec3 a, float t, b3Vec3 b, float u, b3Vec3 c )
|
||||
{
|
||||
b3Vec3 d = {
|
||||
s * a.x + t * b.x + u * c.x,
|
||||
s * a.y + t * b.y + u * c.y,
|
||||
s * a.z + t * b.z + u * c.z,
|
||||
};
|
||||
return d;
|
||||
}
|
||||
|
||||
static inline b3Vec3 b3ModifiedCross( b3Vec3 a, b3Vec3 b )
|
||||
{
|
||||
b3Vec3 c;
|
||||
c.x = a.y * b.z + a.z * b.y;
|
||||
c.y = a.z * b.x + a.x * b.z;
|
||||
c.z = a.x * b.y + a.y * b.x;
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline b3Matrix3 b3MakeDiagonalMatrix( float a, float b, float c )
|
||||
{
|
||||
return (b3Matrix3){ { a, 0.0f, 0.0f }, { 0.0f, b, 0.0f }, { 0.0f, 0.0f, c } };
|
||||
}
|
||||
|
||||
static inline b3Matrix3 b3Skew( b3Vec3 v )
|
||||
{
|
||||
b3Matrix3 out;
|
||||
out.cx = (b3Vec3){ 0, v.z, -v.y };
|
||||
out.cy = (b3Vec3){ -v.z, 0, v.x };
|
||||
out.cz = (b3Vec3){ v.y, -v.x, 0 };
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline b3Plane b3NormalizePlane( b3Plane plane )
|
||||
{
|
||||
float invLength = 1.0f / b3Length( plane.normal );
|
||||
return (b3Plane){ b3MulSV( invLength, plane.normal ), invLength * plane.offset };
|
||||
}
|
||||
|
||||
static inline b3Plane b3MakePlaneFromNormalAndPoint( b3Vec3 normal, b3Vec3 point )
|
||||
{
|
||||
return (b3Plane){ normal, b3Dot( normal, point ) };
|
||||
}
|
||||
|
||||
static inline b3Plane b3MakePlaneFromPoints( b3Vec3 point1, b3Vec3 point2, b3Vec3 point3 )
|
||||
{
|
||||
b3Plane plane;
|
||||
plane.normal = b3Cross( b3Sub( point2, point1 ), b3Sub( point3, point1 ) );
|
||||
plane.normal = b3Normalize( plane.normal );
|
||||
plane.offset = b3Dot( plane.normal, point1 );
|
||||
return plane;
|
||||
}
|
||||
|
||||
static inline b3Vec3 b3MakeNormalFromPoints( b3Vec3 point1, b3Vec3 point2, b3Vec3 point3 )
|
||||
{
|
||||
b3Vec3 normal = b3Cross( b3Sub( point2, point1 ), b3Sub( point3, point1 ) );
|
||||
return b3Normalize( normal );
|
||||
}
|
||||
|
||||
// normal2 = q * normal1
|
||||
// offset2 = dot(normal2, p) + offset1
|
||||
static inline b3Plane b3TransformPlane( b3Transform transform, b3Plane plane )
|
||||
{
|
||||
b3Vec3 normal = b3RotateVector( transform.q, plane.normal );
|
||||
return B3_LITERAL( b3Plane ){ normal, plane.offset + b3Dot( normal, transform.p ) };
|
||||
}
|
||||
|
||||
/// Signed separation of a point from a plane
|
||||
static inline float b3PlaneSeparation( b3Plane plane, b3Vec3 point )
|
||||
{
|
||||
return b3Dot( plane.normal, point ) - plane.offset;
|
||||
}
|
||||
|
||||
// Negative if p is below the triangle v1-v2-v3
|
||||
static inline float b3SignedVolume( b3Vec3 v1, b3Vec3 v2, b3Vec3 v3, b3Vec3 p )
|
||||
{
|
||||
b3Vec3 e1 = b3Sub( v2, v1 );
|
||||
b3Vec3 e2 = b3Sub( v3, v1 );
|
||||
b3Vec3 n = b3Cross( e1, e2 );
|
||||
return b3Dot( n, b3Sub( p, v1 ) );
|
||||
}
|
||||
|
||||
// todo eliminate this
|
||||
static inline bool b3IsWithinSegments( const b3SegmentDistanceResult* result )
|
||||
{
|
||||
return ( 0.0f <= result->fraction1 && result->fraction1 <= 1.0f ) &&
|
||||
( 0.0f <= result->fraction2 && result->fraction2 <= 1.0f );
|
||||
}
|
||||
|
||||
static inline b3Matrix3 b3RotateInertia( b3Quat q, b3Matrix3 centralInertia )
|
||||
{
|
||||
b3Matrix3 rotationMatrix = b3MakeMatrixFromQuat( q );
|
||||
b3Matrix3 inertia = b3MulMM( rotationMatrix, b3MulMM( centralInertia, b3Transpose( rotationMatrix ) ) );
|
||||
return inertia;
|
||||
}
|
||||
|
||||
static inline b3Matrix3 b3TransformInertia( b3Transform transform, b3Matrix3 centralInertia, float mass )
|
||||
{
|
||||
b3Matrix3 inertia = b3RotateInertia( transform.q, centralInertia );
|
||||
inertia = b3AddMM( inertia, b3Steiner( mass, transform.p ) );
|
||||
return inertia;
|
||||
}
|
||||
|
||||
// Add a point to an AABB.
|
||||
static inline b3AABB b3AABB_AddPoint( b3AABB a, b3Vec3 point )
|
||||
{
|
||||
return (b3AABB){ b3Min( a.lowerBound, point ), b3Max( a.upperBound, point ) };
|
||||
}
|
||||
2411
vendor/box3d/src/src/mesh.c
vendored
Normal file
2411
vendor/box3d/src/src/mesh.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1186
vendor/box3d/src/src/mesh_contact.c
vendored
Normal file
1186
vendor/box3d/src/src/mesh_contact.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
439
vendor/box3d/src/src/motor_joint.c
vendored
Normal file
439
vendor/box3d/src/src/motor_joint.c
vendored
Normal file
@@ -0,0 +1,439 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "body.h"
|
||||
#include "core.h"
|
||||
#include "joint.h"
|
||||
#include "math_internal.h"
|
||||
#include "physics_world.h"
|
||||
#include "solver.h"
|
||||
#include "solver_set.h"
|
||||
#include "recording.h"
|
||||
|
||||
// needed for dll export
|
||||
#include "box3d/box3d.h"
|
||||
|
||||
void b3MotorJoint_SetLinearVelocity( b3JointId jointId, b3Vec3 velocity )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, MotorJointSetLinearVelocity, jointId, velocity );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
joint->motorJoint.linearVelocity = velocity;
|
||||
}
|
||||
|
||||
b3Vec3 b3MotorJoint_GetLinearVelocity( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
return joint->motorJoint.linearVelocity;
|
||||
}
|
||||
|
||||
void b3MotorJoint_SetAngularVelocity( b3JointId jointId, b3Vec3 velocity )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, MotorJointSetAngularVelocity, jointId, velocity );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
joint->motorJoint.angularVelocity = velocity;
|
||||
}
|
||||
|
||||
b3Vec3 b3MotorJoint_GetAngularVelocity( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
return joint->motorJoint.angularVelocity;
|
||||
}
|
||||
|
||||
void b3MotorJoint_SetMaxVelocityTorque( b3JointId jointId, float maxTorque )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, MotorJointSetMaxVelocityTorque, jointId, maxTorque );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
joint->motorJoint.maxVelocityTorque = maxTorque;
|
||||
}
|
||||
|
||||
float b3MotorJoint_GetMaxVelocityTorque( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
return joint->motorJoint.maxVelocityTorque;
|
||||
}
|
||||
|
||||
void b3MotorJoint_SetMaxVelocityForce( b3JointId jointId, float maxForce )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, MotorJointSetMaxVelocityForce, jointId, maxForce );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
joint->motorJoint.maxVelocityForce = maxForce;
|
||||
}
|
||||
|
||||
float b3MotorJoint_GetMaxVelocityForce( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
return joint->motorJoint.maxVelocityForce;
|
||||
}
|
||||
|
||||
void b3MotorJoint_SetLinearHertz( b3JointId jointId, float hertz )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, MotorJointSetLinearHertz, jointId, hertz );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
joint->motorJoint.linearHertz = hertz;
|
||||
}
|
||||
|
||||
float b3MotorJoint_GetLinearHertz( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
return joint->motorJoint.linearHertz;
|
||||
}
|
||||
|
||||
void b3MotorJoint_SetLinearDampingRatio( b3JointId jointId, float damping )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, MotorJointSetLinearDampingRatio, jointId, damping );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
joint->motorJoint.linearDampingRatio = damping;
|
||||
}
|
||||
|
||||
float b3MotorJoint_GetLinearDampingRatio( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
return joint->motorJoint.linearDampingRatio;
|
||||
}
|
||||
|
||||
void b3MotorJoint_SetAngularHertz( b3JointId jointId, float hertz )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, MotorJointSetAngularHertz, jointId, hertz );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
joint->motorJoint.angularHertz = hertz;
|
||||
}
|
||||
|
||||
float b3MotorJoint_GetAngularHertz( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
return joint->motorJoint.angularHertz;
|
||||
}
|
||||
|
||||
void b3MotorJoint_SetAngularDampingRatio( b3JointId jointId, float damping )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, MotorJointSetAngularDampingRatio, jointId, damping );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
joint->motorJoint.angularDampingRatio = damping;
|
||||
}
|
||||
|
||||
float b3MotorJoint_GetAngularDampingRatio( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
return joint->motorJoint.angularDampingRatio;
|
||||
}
|
||||
|
||||
void b3MotorJoint_SetMaxSpringForce( b3JointId jointId, float maxForce )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, MotorJointSetMaxSpringForce, jointId, maxForce );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
joint->motorJoint.maxSpringForce = b3MaxFloat( 0.0f, maxForce );
|
||||
}
|
||||
|
||||
float b3MotorJoint_GetMaxSpringForce( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
return joint->motorJoint.maxSpringForce;
|
||||
}
|
||||
|
||||
void b3MotorJoint_SetMaxSpringTorque( b3JointId jointId, float maxTorque )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, MotorJointSetMaxSpringTorque, jointId, maxTorque );
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
joint->motorJoint.maxSpringTorque = b3MaxFloat( 0.0f, maxTorque );
|
||||
}
|
||||
|
||||
float b3MotorJoint_GetMaxSpringTorque( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint );
|
||||
return joint->motorJoint.maxSpringTorque;
|
||||
}
|
||||
|
||||
b3Vec3 b3GetMotorJointForce( b3World* world, b3JointSim* base )
|
||||
{
|
||||
b3Vec3 force = b3MulSV( world->inv_h, b3Add( base->motorJoint.linearVelocityImpulse, base->motorJoint.linearSpringImpulse ) );
|
||||
return force;
|
||||
}
|
||||
|
||||
b3Vec3 b3GetMotorJointTorque( b3World* world, b3JointSim* base )
|
||||
{
|
||||
return b3MulSV( world->inv_h, b3Add( base->motorJoint.angularVelocityImpulse, base->motorJoint.angularSpringImpulse ) );
|
||||
}
|
||||
|
||||
// Point-to-point constraint
|
||||
// C = p2 - p1
|
||||
// Cdot = v2 - v1
|
||||
// = v2 + cross(w2, r2) - v1 - cross(w1, r1)
|
||||
// J = [-I -r1_skew I r2_skew ]
|
||||
// Identity used:
|
||||
// w k % (rx i + ry j) = w * (-ry i + rx j)
|
||||
|
||||
// Angle constraint
|
||||
// C = angle2 - angle1 - referenceAngle
|
||||
// Cdot = w2 - w1
|
||||
// J = [0 0 -1 0 0 1]
|
||||
// K = invI1 + invI2
|
||||
|
||||
void b3PrepareMotorJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_motorJoint );
|
||||
|
||||
// chase body id to the solver set where the body lives
|
||||
int idA = base->bodyIdA;
|
||||
int idB = base->bodyIdB;
|
||||
|
||||
b3World* world = context->world;
|
||||
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, idA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, idB );
|
||||
|
||||
B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet );
|
||||
|
||||
b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex );
|
||||
b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex );
|
||||
|
||||
int localIndexA = bodyA->localIndex;
|
||||
int localIndexB = bodyB->localIndex;
|
||||
|
||||
b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA );
|
||||
b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB );
|
||||
|
||||
base->invMassA = bodySimA->invMass;
|
||||
base->invMassB = bodySimB->invMass;
|
||||
base->invIA = bodySimA->invInertiaWorld;
|
||||
base->invIB = bodySimB->invInertiaWorld;
|
||||
|
||||
b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB );
|
||||
base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN;
|
||||
|
||||
b3MotorJoint* joint = &base->motorJoint;
|
||||
joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX;
|
||||
joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX;
|
||||
|
||||
// Compute joint anchor frames with world space rotation, relative to center of mass
|
||||
joint->frameA.q = b3MulQuat( bodySimA->transform.q, base->localFrameA.q );
|
||||
joint->frameA.p = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) );
|
||||
joint->frameB.q = b3MulQuat( bodySimB->transform.q, base->localFrameB.q );
|
||||
joint->frameB.p = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) );
|
||||
|
||||
// Compute the initial center delta. Incremental position updates are relative to this.
|
||||
joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center );
|
||||
|
||||
joint->linearSpring = b3MakeSoft( joint->linearHertz, joint->linearDampingRatio, context->h );
|
||||
joint->angularSpring = b3MakeSoft( joint->angularHertz, joint->angularDampingRatio, context->h );
|
||||
|
||||
joint->angularMass = b3InvertMatrix( invInertiaSum );
|
||||
|
||||
if ( context->enableWarmStarting == false )
|
||||
{
|
||||
joint->linearVelocityImpulse = b3Vec3_zero;
|
||||
joint->angularVelocityImpulse = b3Vec3_zero;
|
||||
joint->linearSpringImpulse = b3Vec3_zero;
|
||||
joint->angularSpringImpulse = b3Vec3_zero;
|
||||
}
|
||||
}
|
||||
|
||||
void b3WarmStartMotorJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_motorJoint );
|
||||
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
b3MotorJoint* joint = &base->motorJoint;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p );
|
||||
|
||||
b3Vec3 linearImpulse = b3Add( joint->linearVelocityImpulse, joint->linearSpringImpulse );
|
||||
b3Vec3 angularImpulse = b3Add( joint->angularVelocityImpulse, joint->angularSpringImpulse );
|
||||
|
||||
stateA->linearVelocity = b3MulSub( stateA->linearVelocity, mA, linearImpulse );
|
||||
stateA->angularVelocity = b3Sub( stateA->angularVelocity, b3MulMV( iA, b3Add( b3Cross( rA, linearImpulse ), angularImpulse ) ) );
|
||||
stateB->linearVelocity = b3MulAdd( stateB->linearVelocity, mB, linearImpulse );
|
||||
stateB->angularVelocity = b3Add( stateB->angularVelocity, b3MulMV( iB, b3Add( b3Cross( rB, linearImpulse ), angularImpulse ) ) );
|
||||
}
|
||||
|
||||
void b3SolveMotorJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_motorJoint );
|
||||
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3MotorJoint* joint = &base->motorJoint;
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 vA = stateA->linearVelocity;
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 vB = stateB->linearVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
|
||||
b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q );
|
||||
b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q );
|
||||
|
||||
if ( b3DotQuat( quatA, quatB ) < 0.0f )
|
||||
{
|
||||
// this keeps the rotation angle in the range [-pi, pi]
|
||||
quatB = b3NegateQuat( quatB );
|
||||
}
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( quatA, quatB );
|
||||
|
||||
// angular spring
|
||||
if ( joint->maxSpringTorque > 0.0f && joint->angularHertz > 0.0f )
|
||||
{
|
||||
b3Quat targetQuat = b3Quat_identity;
|
||||
b3Vec3 deltaRotation = b3DeltaQuatToRotation( relQ, targetQuat );
|
||||
b3Vec3 c = b3Neg( b3RotateVector( quatA, deltaRotation ) );
|
||||
|
||||
b3Vec3 bias = b3MulSV( joint->angularSpring.biasRate, c );
|
||||
float massScale = joint->angularSpring.massScale;
|
||||
float impulseScale = joint->angularSpring.impulseScale;
|
||||
|
||||
b3Vec3 cdot = b3Sub( wB, wA );
|
||||
|
||||
float maxImpulse = context->h * joint->maxSpringTorque;
|
||||
b3Vec3 oldImpulse = joint->angularSpringImpulse;
|
||||
b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b3MulMV( joint->angularMass, b3Add( cdot, bias ) ) ), impulseScale, oldImpulse );
|
||||
joint->angularSpringImpulse = b3Add( oldImpulse, impulse );
|
||||
if ( b3LengthSquared( joint->angularSpringImpulse ) > maxImpulse * maxImpulse )
|
||||
{
|
||||
joint->angularSpringImpulse = b3MulSV( maxImpulse, b3Normalize( joint->angularSpringImpulse ) );
|
||||
}
|
||||
impulse = b3Sub( joint->angularSpringImpulse, oldImpulse );
|
||||
|
||||
wA = b3Sub( wA, b3MulMV( iA, impulse ) );
|
||||
wB = b3Add( wB, b3MulMV( iB, impulse ) );
|
||||
}
|
||||
|
||||
// angular velocity
|
||||
if ( joint->maxVelocityTorque > 0.0 )
|
||||
{
|
||||
b3Vec3 cdot = b3Sub( b3Sub( wB, wA ), joint->angularVelocity );
|
||||
b3Vec3 impulse = b3Neg( b3MulMV( joint->angularMass, cdot ) );
|
||||
|
||||
float maxImpulse = context->h * joint->maxVelocityTorque;
|
||||
b3Vec3 oldImpulse = joint->angularVelocityImpulse;
|
||||
joint->angularVelocityImpulse = b3Add( oldImpulse, impulse );
|
||||
if ( b3LengthSquared( joint->angularVelocityImpulse ) > maxImpulse * maxImpulse )
|
||||
{
|
||||
joint->angularVelocityImpulse = b3MulSV( maxImpulse, b3Normalize( joint->angularVelocityImpulse ) );
|
||||
}
|
||||
impulse = b3Sub( joint->angularVelocityImpulse, oldImpulse );
|
||||
|
||||
wA = b3Sub( wA, b3MulMV( iA, impulse ) );
|
||||
wB = b3Add( wB, b3MulMV( iB, impulse ) );
|
||||
}
|
||||
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p );
|
||||
|
||||
// linear spring
|
||||
if ( joint->maxSpringForce > 0.0f && joint->linearHertz > 0.0f )
|
||||
{
|
||||
b3Vec3 dcA = stateA->deltaPosition;
|
||||
b3Vec3 dcB = stateB->deltaPosition;
|
||||
b3Vec3 c = b3Add( b3Add( b3Sub( dcB, dcA ), b3Sub( rB, rA ) ), joint->deltaCenter );
|
||||
|
||||
b3Vec3 bias = b3MulSV( joint->linearSpring.biasRate, c );
|
||||
float massScale = joint->linearSpring.massScale;
|
||||
float impulseScale = joint->linearSpring.impulseScale;
|
||||
|
||||
b3Vec3 cdot = b3Sub( b3Add( vB, b3Cross( wB, rB ) ), b3Add( vA, b3Cross( wA, rA ) ) );
|
||||
|
||||
//// K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)]
|
||||
b3Matrix3 sA = b3Skew( rA );
|
||||
b3Matrix3 sB = b3Skew( rB );
|
||||
b3Matrix3 kA = b3MulMM( sA, b3MulMM( base->invIA, sA ) );
|
||||
b3Matrix3 kB = b3MulMM( sB, b3MulMM( base->invIB, sB ) );
|
||||
b3Matrix3 k = b3NegateMat3( b3AddMM( kA, kB ) );
|
||||
k.cx.x += mA + mB;
|
||||
k.cy.y += mA + mB;
|
||||
k.cz.z += mA + mB;
|
||||
|
||||
b3Vec3 b = b3Solve3( k, b3Add( cdot, bias ) );
|
||||
|
||||
b3Vec3 oldImpulse = joint->linearSpringImpulse;
|
||||
b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b ), impulseScale, oldImpulse );
|
||||
float maxImpulse = context->h * joint->maxSpringForce;
|
||||
joint->linearSpringImpulse = b3Add( joint->linearSpringImpulse, impulse );
|
||||
|
||||
if ( b3LengthSquared( joint->linearSpringImpulse ) > maxImpulse * maxImpulse )
|
||||
{
|
||||
joint->linearSpringImpulse = b3MulSV( maxImpulse, b3Normalize( joint->linearSpringImpulse ) );
|
||||
}
|
||||
|
||||
impulse = b3Sub( joint->linearSpringImpulse, oldImpulse );
|
||||
|
||||
vA = b3MulSub( vA, mA, impulse );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) );
|
||||
vB = b3MulAdd( vB, mB, impulse );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) );
|
||||
}
|
||||
|
||||
// linear velocity
|
||||
if ( joint->maxVelocityForce > 0.0f )
|
||||
{
|
||||
b3Vec3 cdot = b3Sub( b3Add( vB, b3Cross( wB, rB ) ), b3Add( vA, b3Cross( wA, rA ) ) );
|
||||
cdot = b3Sub( cdot, joint->linearVelocity );
|
||||
//// K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)]
|
||||
b3Matrix3 sA = b3Skew( rA );
|
||||
b3Matrix3 sB = b3Skew( rB );
|
||||
b3Matrix3 kA = b3MulMM( sA, b3MulMM( base->invIA, sA ) );
|
||||
b3Matrix3 kB = b3MulMM( sB, b3MulMM( base->invIB, sB ) );
|
||||
b3Matrix3 k = b3NegateMat3( b3AddMM( kA, kB ) );
|
||||
k.cx.x += mA + mB;
|
||||
k.cy.y += mA + mB;
|
||||
k.cz.z += mA + mB;
|
||||
|
||||
b3Vec3 b = b3Solve3( k, cdot );
|
||||
b3Vec3 impulse = b3Neg( b );
|
||||
|
||||
b3Vec3 oldImpulse = joint->linearVelocityImpulse;
|
||||
float maxImpulse = context->h * joint->maxVelocityForce;
|
||||
joint->linearVelocityImpulse = b3Add( joint->linearVelocityImpulse, impulse );
|
||||
|
||||
if ( b3LengthSquared( joint->linearVelocityImpulse ) > maxImpulse * maxImpulse )
|
||||
{
|
||||
joint->linearVelocityImpulse = b3MulSV( maxImpulse, b3Normalize( joint->linearVelocityImpulse ) );
|
||||
}
|
||||
|
||||
impulse = b3Sub( joint->linearVelocityImpulse, oldImpulse );
|
||||
|
||||
vA = b3MulSub( vA, mA, impulse );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) );
|
||||
vB = b3MulAdd( vB, mB, impulse );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) );
|
||||
}
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->linearVelocity = vA;
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->linearVelocity = vB;
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
70
vendor/box3d/src/src/mover.c
vendored
Normal file
70
vendor/box3d/src/src/mover.c
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "math_internal.h"
|
||||
|
||||
#include "box3d/collision.h"
|
||||
#include "box3d/constants.h"
|
||||
|
||||
b3PlaneSolverResult b3SolvePlanes( b3Vec3 targetDelta, b3CollisionPlane* planes, int count )
|
||||
{
|
||||
for ( int i = 0; i < count; ++i )
|
||||
{
|
||||
planes[i].push = 0.0f;
|
||||
}
|
||||
|
||||
b3Vec3 delta = targetDelta;
|
||||
float tolerance = B3_LINEAR_SLOP;
|
||||
|
||||
int iteration;
|
||||
for ( iteration = 0; iteration < 20; ++iteration )
|
||||
{
|
||||
float totalPush = 0.0f;
|
||||
for ( int planeIndex = 0; planeIndex < count; ++planeIndex )
|
||||
{
|
||||
b3CollisionPlane* plane = planes + planeIndex;
|
||||
|
||||
// Add slop to prevent jitter
|
||||
float separation = b3PlaneSeparation( plane->plane, delta ) + B3_LINEAR_SLOP;
|
||||
|
||||
float push = -separation;
|
||||
|
||||
// Clamp accumulated push
|
||||
float accumulatedPush = plane->push;
|
||||
plane->push = b3ClampFloat( plane->push + push, 0.0f, plane->pushLimit );
|
||||
push = plane->push - accumulatedPush;
|
||||
delta = b3MulAdd( delta, push, plane->plane.normal );
|
||||
|
||||
// Track maximum push for convergence
|
||||
totalPush += b3AbsFloat( push );
|
||||
}
|
||||
|
||||
if ( totalPush < tolerance )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (b3PlaneSolverResult){
|
||||
.delta = delta,
|
||||
.iterationCount = iteration,
|
||||
};
|
||||
}
|
||||
|
||||
b3Vec3 b3ClipVector( b3Vec3 vector, const b3CollisionPlane* planes, int count )
|
||||
{
|
||||
b3Vec3 v = vector;
|
||||
|
||||
for ( int planeIndex = 0; planeIndex < count; ++planeIndex )
|
||||
{
|
||||
const b3CollisionPlane* plane = planes + planeIndex;
|
||||
if ( plane->push == 0.0f || plane->clipVelocity == false )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
v = b3MulSub( v, b3MinFloat( 0.0f, b3Dot( v, plane->plane.normal ) ), plane->plane.normal );
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
155
vendor/box3d/src/src/name_cache.c
vendored
Normal file
155
vendor/box3d/src/src/name_cache.c
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "name_cache.h"
|
||||
|
||||
#include "recording.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define NAME b3NameMap
|
||||
#define KEY_TY uint32_t
|
||||
#define VAL_TY int
|
||||
#define HASH_FN vt_hash_integer
|
||||
#define CMPR_FN vt_cmpr_integer
|
||||
#define MALLOC_FN b3Alloc
|
||||
#define FREE_FN b3Free
|
||||
#include "verstable.h"
|
||||
|
||||
#define FNV32_OFFSET_BASIS 0x811c9dc5u
|
||||
#define FNV32_PRIME 0x01000193u
|
||||
|
||||
// This is designed to hash small strings and while avoiding collisions
|
||||
// for a 32-bit hash.
|
||||
// Note: changing this will break recordings
|
||||
uint32_t b3Hash32( const void* data, size_t length )
|
||||
{
|
||||
// Cast to unsigned to avoid sign extension
|
||||
const unsigned char* p = data;
|
||||
|
||||
// FNV-1a
|
||||
uint32_t h = FNV32_OFFSET_BASIS;
|
||||
for ( size_t i = 0; i < length; ++i )
|
||||
{
|
||||
h ^= p[i];
|
||||
h *= FNV32_PRIME;
|
||||
}
|
||||
|
||||
// Murmur3 finalizer to help with short strings
|
||||
h ^= h >> 16;
|
||||
h *= 0x85ebca6bu;
|
||||
h ^= h >> 13;
|
||||
h *= 0xc2b2ae35u;
|
||||
h ^= h >> 16;
|
||||
return h;
|
||||
}
|
||||
|
||||
b3NameCache b3CreateNameCache( void )
|
||||
{
|
||||
b3NameCache cache = { 0 };
|
||||
b3Array_Create( cache.entries );
|
||||
|
||||
b3NameMap* map = b3Alloc( sizeof( b3NameMap ) );
|
||||
b3NameMap_init( map );
|
||||
cache.map = map;
|
||||
return cache;
|
||||
}
|
||||
|
||||
void b3DestroyNameCache( b3NameCache* cache )
|
||||
{
|
||||
int count = cache->entries.count;
|
||||
for ( int i = 0; i < count; ++i )
|
||||
{
|
||||
b3NameEntry* entry = cache->entries.data + i;
|
||||
b3Free( entry->name, entry->length + 1 );
|
||||
}
|
||||
|
||||
b3Array_Destroy( cache->entries );
|
||||
b3NameMap_cleanup( (b3NameMap*)cache->map );
|
||||
b3Free( cache->map, sizeof( b3NameMap ) );
|
||||
}
|
||||
|
||||
uint32_t b3AddName( b3NameCache* cache, const char* name )
|
||||
{
|
||||
if ( name == NULL || name[0] == 0 )
|
||||
{
|
||||
return B3_NULL_NAME;
|
||||
}
|
||||
|
||||
size_t length = strlen( name );
|
||||
uint32_t id = b3Hash32( name, length );
|
||||
|
||||
id = id == 0 ? 1 : id;
|
||||
|
||||
b3NameMap* map = cache->map;
|
||||
b3NameMap_itr itr = b3NameMap_get( map, id );
|
||||
if ( b3NameMap_is_end( itr ) == false )
|
||||
{
|
||||
if ( strcmp( cache->entries.data[itr.data->val].name, name ) != 0 )
|
||||
{
|
||||
// Different name, same hash
|
||||
b3Log( "Hash collision on %s", name );
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
char* clone = b3Alloc( length + 1 );
|
||||
memcpy( clone, name, length + 1 );
|
||||
int index = cache->entries.count;
|
||||
b3NameEntry entry = {
|
||||
.hash = id,
|
||||
.name = clone,
|
||||
.length = (int)length,
|
||||
};
|
||||
|
||||
b3Array_Push( cache->entries, entry );
|
||||
|
||||
b3NameMap_insert( map, id, index );
|
||||
return id;
|
||||
}
|
||||
|
||||
const char* b3FindName( const b3NameCache* cache, uint32_t id )
|
||||
{
|
||||
b3NameMap* map = cache->map;
|
||||
b3NameMap_itr itr = b3NameMap_get( map, id );
|
||||
if ( b3NameMap_is_end( itr ) == false )
|
||||
{
|
||||
int index = itr.data->val;
|
||||
const b3NameEntry* entry = b3Array_Get( cache->entries, index );
|
||||
return entry->name;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void b3LoadName( b3NameCache* cache, uint32_t id, char* name, int length )
|
||||
{
|
||||
if ( name == NULL )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( name[0] == 0 || id == B3_NULL_NAME )
|
||||
{
|
||||
b3Free( name, length + 1 );
|
||||
return;
|
||||
}
|
||||
|
||||
b3NameMap* map = cache->map;
|
||||
b3NameMap_itr itr = b3NameMap_get( map, id );
|
||||
if ( b3NameMap_is_end( itr ) == false )
|
||||
{
|
||||
b3Free( name, length + 1 );
|
||||
return;
|
||||
}
|
||||
|
||||
int index = cache->entries.count;
|
||||
b3NameEntry entry = {
|
||||
.hash = id,
|
||||
.name = name,
|
||||
.length = length,
|
||||
};
|
||||
|
||||
b3Array_Push( cache->entries, entry );
|
||||
b3NameMap_insert( map, id, index );
|
||||
}
|
||||
42
vendor/box3d/src/src/name_cache.h
vendored
Normal file
42
vendor/box3d/src/src/name_cache.h
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "container.h"
|
||||
|
||||
#define B3_NULL_NAME 0u
|
||||
|
||||
typedef struct b3NameEntry
|
||||
{
|
||||
// Names are used for debugging and I'm favoring simplicity and
|
||||
// minimal storage. Collisions will be logged.
|
||||
uint32_t hash;
|
||||
int length;
|
||||
char* name;
|
||||
} b3NameEntry;
|
||||
|
||||
b3DeclareArray( b3NameEntry );
|
||||
|
||||
typedef struct b3NameCache
|
||||
{
|
||||
b3Array( b3NameEntry ) entries;
|
||||
void* map;
|
||||
} b3NameCache;
|
||||
|
||||
b3NameCache b3CreateNameCache( void );
|
||||
void b3DestroyNameCache( b3NameCache* cache );
|
||||
|
||||
uint32_t b3AddName( b3NameCache* cache, const char* name );
|
||||
const char* b3FindName( const b3NameCache* cache, uint32_t id );
|
||||
|
||||
// Load a name from a recording. Name ownership is transferred.
|
||||
void b3LoadName( b3NameCache* cache, uint32_t id, char* name, int length );
|
||||
|
||||
uint32_t b3Hash32( const void* data, size_t length );
|
||||
|
||||
static inline const char* b3FindNameWithDefault( const b3NameCache* cache, uint32_t id, const char* def )
|
||||
{
|
||||
const char* name = b3FindName( cache, id );
|
||||
return name == NULL ? def : name;
|
||||
}
|
||||
135
vendor/box3d/src/src/parallel_for.c
vendored
Normal file
135
vendor/box3d/src/src/parallel_for.c
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "parallel_for.h"
|
||||
|
||||
#include "core.h"
|
||||
#include "physics_world.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include "box3d/base.h"
|
||||
#include "box3d/constants.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
// Shared state for one b3ParallelFor invocation. Workers race on nextBlock to
|
||||
// claim work, so a slow chunk can't strand the other threads.
|
||||
typedef struct b3ParallelForShared
|
||||
{
|
||||
b3AtomicInt nextBlock;
|
||||
int blockCount;
|
||||
int blockSize;
|
||||
int itemCount;
|
||||
b3ParallelForCallback* callback;
|
||||
void* context;
|
||||
} b3ParallelForShared;
|
||||
|
||||
typedef struct b3ParallelForTask
|
||||
{
|
||||
b3ParallelForShared* shared;
|
||||
int workerIndex;
|
||||
} b3ParallelForTask;
|
||||
|
||||
static void b3ParallelForTrampoline( void* taskContext )
|
||||
{
|
||||
b3ParallelForTask* task = (b3ParallelForTask*)taskContext;
|
||||
b3ParallelForShared* shared = task->shared;
|
||||
int workerIndex = task->workerIndex;
|
||||
void* context = shared->context;
|
||||
b3ParallelForCallback* callback = shared->callback;
|
||||
|
||||
int blockCount = shared->blockCount;
|
||||
int blockSize = shared->blockSize;
|
||||
int itemCount = shared->itemCount;
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
int blockIndex = b3AtomicFetchAddInt( &shared->nextBlock, 1 );
|
||||
if ( blockIndex >= blockCount )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int start = blockIndex * blockSize;
|
||||
int end = start + blockSize;
|
||||
if ( end > itemCount )
|
||||
{
|
||||
end = itemCount;
|
||||
}
|
||||
|
||||
callback( start, end, workerIndex, context );
|
||||
}
|
||||
}
|
||||
|
||||
void b3ParallelFor( b3World* world, b3ParallelForCallback* callback, int itemCount, int minRange, void* context,
|
||||
const char* name )
|
||||
{
|
||||
if ( itemCount <= 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
B3_ASSERT( minRange > 0 );
|
||||
|
||||
int workerCount = world->workerCount;
|
||||
B3_ASSERT( 0 < workerCount && workerCount <= B3_MAX_WORKERS );
|
||||
|
||||
// Target multiple blocks per worker to reduce thread stalls.
|
||||
// block size grows once items exceed maxBlockCount * minRange
|
||||
// so the block count stays bounded and per-block sync overhead stays low.
|
||||
int blocksPerWorker = 4;
|
||||
int maxBlockCount = blocksPerWorker * workerCount;
|
||||
|
||||
int blockSize;
|
||||
int blockCount;
|
||||
if ( itemCount <= minRange * maxBlockCount )
|
||||
{
|
||||
blockSize = minRange;
|
||||
blockCount = ( itemCount + blockSize - 1 ) / blockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
blockSize = ( itemCount + maxBlockCount - 1 ) / maxBlockCount;
|
||||
blockCount = ( itemCount + blockSize - 1 ) / blockSize;
|
||||
}
|
||||
B3_ASSERT( blockCount >= 1 );
|
||||
B3_ASSERT( blockSize * blockCount >= itemCount );
|
||||
|
||||
// No point enqueueing more tasks than blocks.
|
||||
int taskCount = workerCount < blockCount ? workerCount : blockCount;
|
||||
|
||||
b3ParallelForShared shared;
|
||||
shared.blockCount = blockCount;
|
||||
shared.blockSize = blockSize;
|
||||
shared.itemCount = itemCount;
|
||||
shared.callback = callback;
|
||||
shared.context = context;
|
||||
b3AtomicStoreInt( &shared.nextBlock, 0 );
|
||||
|
||||
b3ParallelForTask tasks[B3_MAX_WORKERS];
|
||||
void* handles[B3_MAX_WORKERS];
|
||||
for ( int i = 0; i < taskCount; ++i )
|
||||
{
|
||||
tasks[i].shared = &shared;
|
||||
tasks[i].workerIndex = i;
|
||||
|
||||
if (world->taskCount < B3_MAX_TASKS)
|
||||
{
|
||||
handles[i] = world->enqueueTaskFcn( &b3ParallelForTrampoline, tasks + i, world->userTaskContext, name );
|
||||
world->taskCount += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
handles[i] = NULL;
|
||||
b3ParallelForTrampoline( tasks + i );
|
||||
}
|
||||
}
|
||||
|
||||
for ( int i = 0; i < taskCount; ++i )
|
||||
{
|
||||
if ( handles[i] != NULL )
|
||||
{
|
||||
world->finishTaskFcn( handles[i], world->userTaskContext );
|
||||
}
|
||||
}
|
||||
}
|
||||
23
vendor/box3d/src/src/parallel_for.h
vendored
Normal file
23
vendor/box3d/src/src/parallel_for.h
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef struct b3World b3World;
|
||||
|
||||
// Callback invoked by b3ParallelFor to process a range of items. May be called
|
||||
// multiple times per worker: work is divided into blocks that workers claim
|
||||
// atomically, so a worker that finishes early picks up the next unclaimed
|
||||
// block instead of sitting idle. workerIndex is the worker identity and is
|
||||
// stable across all invocations from the same worker, so it is safe to use as
|
||||
// an index into per-worker state (e.g. world->taskContexts.data + workerIndex).
|
||||
typedef void b3ParallelForCallback( int startIndex, int endIndex, int workerIndex, void* context );
|
||||
|
||||
// Divide [0, itemCount) into blocks and process them with cooperative claiming:
|
||||
// up to world->workerCount tasks are enqueued, and each task loops, atomically
|
||||
// claiming the next unclaimed block until the range is drained. Blocks the
|
||||
// caller until all work is complete. minRange is the minimum block size; block
|
||||
// size grows once itemCount exceeds 4 * workerCount * minRange so block count
|
||||
// stays bounded.
|
||||
void b3ParallelFor( b3World* world, b3ParallelForCallback* callback, int itemCount, int minRange, void* context,
|
||||
const char* name );
|
||||
251
vendor/box3d/src/src/parallel_joint.c
vendored
Normal file
251
vendor/box3d/src/src/parallel_joint.c
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "body.h"
|
||||
#include "core.h"
|
||||
#include "joint.h"
|
||||
#include "math_internal.h"
|
||||
#include "physics_world.h"
|
||||
#include "solver.h"
|
||||
#include "solver_set.h"
|
||||
#include "recording.h"
|
||||
|
||||
// needed for dll export
|
||||
#include "box3d/box3d.h"
|
||||
|
||||
void b3ParallelJoint_SetSpringHertz( b3JointId jointId, float hertz )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, ParallelJointSetSpringHertz, jointId, hertz );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint );
|
||||
base->parallelJoint.hertz = hertz;
|
||||
}
|
||||
|
||||
float b3ParallelJoint_GetSpringHertz( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint );
|
||||
return base->parallelJoint.hertz;
|
||||
}
|
||||
|
||||
void b3ParallelJoint_SetSpringDampingRatio( b3JointId jointId, float dampingRatio )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, ParallelJointSetSpringDampingRatio, jointId, dampingRatio );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint );
|
||||
base->parallelJoint.dampingRatio = dampingRatio;
|
||||
}
|
||||
|
||||
float b3ParallelJoint_GetSpringDampingRatio( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint );
|
||||
return base->parallelJoint.dampingRatio;
|
||||
}
|
||||
|
||||
void b3ParallelJoint_SetMaxTorque( b3JointId jointId, float maxForce )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( maxForce ) && maxForce >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, ParallelJointSetMaxTorque, jointId, maxForce );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint );
|
||||
base->parallelJoint.maxTorque = maxForce;
|
||||
}
|
||||
|
||||
float b3ParallelJoint_GetMaxTorque( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint );
|
||||
return base->parallelJoint.maxTorque;
|
||||
}
|
||||
|
||||
b3Vec3 b3GetParallelJointTorque( b3World* world, b3JointSim* base )
|
||||
{
|
||||
b3ParallelJoint* joint = &base->parallelJoint;
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( joint->quatA, joint->quatB );
|
||||
joint->perpAxisX = b3MulSV( 0.5f, b3RotateVector( joint->quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) );
|
||||
joint->perpAxisY = b3MulSV( 0.5f, b3RotateVector( joint->quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) );
|
||||
|
||||
b3Vec3 angularImpulse = b3Blend2( joint->perpImpulse.x, joint->perpAxisX, joint->perpImpulse.y, joint->perpAxisY );
|
||||
b3Vec3 torque = b3MulSV( world->inv_h, angularImpulse );
|
||||
return torque;
|
||||
}
|
||||
|
||||
void b3PrepareParallelJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_parallelJoint );
|
||||
|
||||
b3World* world = context->world;
|
||||
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB );
|
||||
|
||||
B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet );
|
||||
b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex );
|
||||
b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex );
|
||||
|
||||
int localIndexA = bodyA->localIndex;
|
||||
int localIndexB = bodyB->localIndex;
|
||||
|
||||
b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA );
|
||||
b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB );
|
||||
|
||||
base->invMassA = bodySimA->invMass;
|
||||
base->invMassB = bodySimB->invMass;
|
||||
base->invIA = bodySimA->invInertiaWorld;
|
||||
base->invIB = bodySimB->invInertiaWorld;
|
||||
|
||||
b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB );
|
||||
base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN;
|
||||
|
||||
b3ParallelJoint* joint = &base->parallelJoint;
|
||||
joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX;
|
||||
joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX;
|
||||
|
||||
// Compute joint anchor frames with world space rotation, relative to center of mass
|
||||
joint->quatA = b3MulQuat( bodySimA->transform.q, base->localFrameA.q );
|
||||
joint->quatB = b3MulQuat( bodySimB->transform.q, base->localFrameB.q );
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( joint->quatA, joint->quatB );
|
||||
|
||||
{
|
||||
// These are needed for warm starting
|
||||
joint->perpAxisX = b3MulSV( 0.5f, b3RotateVector( joint->quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) );
|
||||
joint->perpAxisY = b3MulSV( 0.5f, b3RotateVector( joint->quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) );
|
||||
}
|
||||
|
||||
joint->softness = b3MakeSoft( joint->hertz, joint->dampingRatio, context->h );
|
||||
|
||||
if ( context->enableWarmStarting == false )
|
||||
{
|
||||
joint->perpImpulse = (b3Vec2){ 0.0f, 0.0f };
|
||||
}
|
||||
}
|
||||
|
||||
void b3WarmStartParallelJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_parallelJoint );
|
||||
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3ParallelJoint* joint = &base->parallelJoint;
|
||||
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
|
||||
b3Vec3 angularImpulse = b3Blend2( joint->perpImpulse.x, joint->perpAxisX, joint->perpImpulse.y, joint->perpAxisY );
|
||||
|
||||
wA = b3Sub( wA, b3MulMV( iA, angularImpulse ) );
|
||||
wB = b3Add( wB, b3MulMV( iB, angularImpulse ) );
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
|
||||
void b3SolveParallelJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3ParallelJoint* joint = &base->parallelJoint;
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
|
||||
bool fixedRotation = base->fixedRotation;
|
||||
b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->quatA );
|
||||
b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->quatB );
|
||||
|
||||
if ( b3DotQuat( quatA, quatB ) < 0.0f )
|
||||
{
|
||||
// this keeps the rotation angle in the range [-pi, pi]
|
||||
quatB = b3NegateQuat( quatB );
|
||||
}
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( quatA, quatB );
|
||||
|
||||
if ( fixedRotation == false && joint->maxTorque > 0.0f )
|
||||
{
|
||||
b3Vec2 c = { relQ.v.x, relQ.v.y };
|
||||
b3Vec2 bias = { joint->softness.biasRate * c.x, joint->softness.biasRate * c.y };
|
||||
float massScale = joint->softness.massScale;
|
||||
float impulseScale = joint->softness.impulseScale;
|
||||
|
||||
// Collinearity constraint as 2-by-2
|
||||
b3Vec3 perpAxisX = b3MulSV( 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) );
|
||||
b3Vec3 perpAxisY = b3MulSV( 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) );
|
||||
joint->perpAxisX = perpAxisX;
|
||||
joint->perpAxisY = perpAxisY;
|
||||
|
||||
b3Matrix3 invInertiaSum = b3AddMM( iA, iB );
|
||||
float kxx = b3Dot( perpAxisX, b3MulMV( invInertiaSum, perpAxisX ) );
|
||||
float kyy = b3Dot( perpAxisY, b3MulMV( invInertiaSum, perpAxisY ) );
|
||||
float kxy = b3Dot( perpAxisX, b3MulMV( invInertiaSum, perpAxisY ) );
|
||||
|
||||
b3Matrix2 k = { { kxx, kxy }, { kxy, kyy } };
|
||||
|
||||
b3Vec3 wRel = b3Sub( wB, wA );
|
||||
b3Vec2 cdot = { b3Dot( wRel, perpAxisX ), b3Dot( wRel, perpAxisY ) };
|
||||
|
||||
float maxImpulse = context->h * joint->maxTorque;
|
||||
b3Vec2 oldImpulse = joint->perpImpulse;
|
||||
b3Vec2 cdotPlusBias = { cdot.x + bias.x, cdot.y + bias.y };
|
||||
b3Vec2 sol = b3Solve2( k, cdotPlusBias );
|
||||
b3Vec2 deltaImpulse = {
|
||||
-massScale * sol.x - impulseScale * oldImpulse.x,
|
||||
-massScale * sol.y - impulseScale * oldImpulse.y,
|
||||
};
|
||||
joint->perpImpulse = (b3Vec2){ oldImpulse.x + deltaImpulse.x, oldImpulse.y + deltaImpulse.y };
|
||||
if ( b3LengthSquared2( joint->perpImpulse ) > maxImpulse * maxImpulse )
|
||||
{
|
||||
float s = maxImpulse / b3Length2( joint->perpImpulse );
|
||||
joint->perpImpulse = (b3Vec2){ s * joint->perpImpulse.x, s * joint->perpImpulse.y };
|
||||
}
|
||||
|
||||
deltaImpulse = b3Sub2( joint->perpImpulse, oldImpulse );
|
||||
|
||||
b3Vec3 angularImpulse = b3Blend2( deltaImpulse.x, perpAxisX, deltaImpulse.y, perpAxisY );
|
||||
wA = b3Sub( wA, b3MulMV( iA, angularImpulse ) );
|
||||
wB = b3Add( wB, b3MulMV( iB, angularImpulse ) );
|
||||
}
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
|
||||
void b3DrawParallelJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale )
|
||||
{
|
||||
float length = 0.1f * scale;
|
||||
|
||||
b3WorldTransform frameA = b3MulWorldTransforms( transformA, base->localFrameA );
|
||||
draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length, b3RotateVector( frameA.q, b3Vec3_axisZ ) ) ), b3_colorGreen, draw->context );
|
||||
|
||||
b3WorldTransform frameB = b3MulWorldTransforms( transformB, base->localFrameB );
|
||||
draw->DrawSegmentFcn( frameB.p, b3OffsetPos( frameB.p, b3MulSV( length, b3RotateVector( frameB.q, b3Vec3_axisZ ) ) ), b3_colorBlue, draw->context );
|
||||
}
|
||||
4099
vendor/box3d/src/src/physics_world.c
vendored
Normal file
4099
vendor/box3d/src/src/physics_world.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
350
vendor/box3d/src/src/physics_world.h
vendored
Normal file
350
vendor/box3d/src/src/physics_world.h
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arena_allocator.h"
|
||||
#include "bitset.h"
|
||||
#include "block_allocator.h"
|
||||
#include "broad_phase.h"
|
||||
#include "constraint_graph.h"
|
||||
#include "id_pool.h"
|
||||
#include "name_cache.h"
|
||||
|
||||
#include "box3d/types.h"
|
||||
|
||||
#define B3_DEBUG_POINT_CAPACITY 64
|
||||
#define B3_DEBUG_LINE_CAPACITY 64
|
||||
|
||||
typedef struct b3Body b3Body;
|
||||
typedef struct b3Recording b3Recording;
|
||||
typedef struct b3Contact b3Contact;
|
||||
typedef struct b3Island b3Island;
|
||||
typedef struct b3Joint b3Joint;
|
||||
typedef struct b3Sensor b3Sensor;
|
||||
typedef struct b3SensorTaskContext b3SensorTaskContext;
|
||||
typedef struct b3SensorHit b3SensorHit;
|
||||
typedef struct b3Shape b3Shape;
|
||||
typedef struct b3SolverSet b3SolverSet;
|
||||
|
||||
b3DeclareArray( b3BlockAllocator );
|
||||
b3DeclareArray( b3Body );
|
||||
b3DeclareArray( b3SolverSet );
|
||||
b3DeclareArray( b3Joint );
|
||||
b3DeclareArray( b3Contact );
|
||||
b3DeclareArray( b3Island );
|
||||
b3DeclareArray( b3Shape );
|
||||
b3DeclareArray( b3Sensor );
|
||||
b3DeclareArray( b3SensorTaskContext );
|
||||
b3DeclareArray( b3SensorHit );
|
||||
b3DeclareArray( b3BodyMoveEvent );
|
||||
b3DeclareArray( b3SensorBeginTouchEvent );
|
||||
b3DeclareArray( b3ContactBeginTouchEvent );
|
||||
b3DeclareArray( b3SensorEndTouchEvent );
|
||||
b3DeclareArray( b3ContactEndTouchEvent );
|
||||
b3DeclareArray( b3ContactHitEvent );
|
||||
b3DeclareArray( b3JointEvent );
|
||||
|
||||
enum b3SetType
|
||||
{
|
||||
b3_staticSet = 0,
|
||||
b3_disabledSet = 1,
|
||||
b3_awakeSet = 2,
|
||||
b3_firstSleepingSet = 3,
|
||||
};
|
||||
|
||||
typedef struct b3DebugPoint
|
||||
{
|
||||
b3Pos p;
|
||||
int label;
|
||||
float value;
|
||||
b3HexColor color;
|
||||
} b3DebugPoint;
|
||||
|
||||
typedef struct b3DebugLine
|
||||
{
|
||||
b3Pos p1, p2;
|
||||
int label;
|
||||
b3HexColor color;
|
||||
} b3DebugLine;
|
||||
|
||||
// Per thread task storage
|
||||
typedef struct b3TaskContext
|
||||
{
|
||||
b3Arena arena;
|
||||
|
||||
// Collect per thread sensor continuous hit events.
|
||||
b3Array( b3SensorHit ) sensorHits;
|
||||
|
||||
// These bits align with the b3ConstraintGraph::contactBlocks and signal a change in contact status
|
||||
b3BitSet contactStateBitSet;
|
||||
|
||||
// These bits align with the joint id capacity and signal a change in contact status
|
||||
b3BitSet jointStateBitSet;
|
||||
|
||||
// These bits align with the contact id capacity and signal a hit event.
|
||||
b3BitSet hitEventBitSet;
|
||||
|
||||
// Fast-path flag: true when this worker set at least one bit in hitEventBitSet this step.
|
||||
bool hasHitEvents;
|
||||
|
||||
// Used to track bodies with shapes that have enlarged AABBs. This avoids having a bit array
|
||||
// that is very large when there are many static shapes.
|
||||
b3BitSet enlargedSimBitSet;
|
||||
|
||||
// Used to put islands to sleep
|
||||
b3BitSet awakeIslandBitSet;
|
||||
|
||||
// Per worker split island candidate
|
||||
float splitSleepTime;
|
||||
int splitIslandId;
|
||||
|
||||
// Profiling
|
||||
int satCallCount;
|
||||
int satCacheHitCount;
|
||||
int distanceIterations;
|
||||
int pushBackIterations;
|
||||
int rootIterations;
|
||||
|
||||
// Number of contacts recycled this step (collide pass).
|
||||
int recycledContactCount;
|
||||
|
||||
b3DebugPoint points[B3_DEBUG_POINT_CAPACITY];
|
||||
int pointCount;
|
||||
|
||||
b3DebugLine lines[B3_DEBUG_LINE_CAPACITY];
|
||||
int lineCount;
|
||||
|
||||
int manifoldCounts[B3_CONTACT_MANIFOLD_COUNT_BUCKETS];
|
||||
// Prevent false sharing
|
||||
char cacheLine[64];
|
||||
} b3TaskContext;
|
||||
|
||||
b3DeclareArray( b3TaskContext );
|
||||
|
||||
// The world struct manages all physics entities, dynamic simulation, and asynchronous queries.
|
||||
// The world also contains efficient memory management facilities.
|
||||
typedef struct b3World
|
||||
{
|
||||
b3Stack stack;
|
||||
b3BroadPhase broadPhase;
|
||||
b3ConstraintGraph constraintGraph;
|
||||
|
||||
// Manifold allocators have one allocator for each manifold count.
|
||||
b3Array( b3BlockAllocator ) manifoldAllocators;
|
||||
b3Mutex* manifoldAllocatorMutex;
|
||||
|
||||
// The body id pool is used to allocate and recycle body ids. Body ids
|
||||
// provide a stable identifier for users, but incur caches misses when used
|
||||
// to access body data. Aligns with b3Body.
|
||||
b3IdPool bodyIdPool;
|
||||
|
||||
// This is a sparse array that maps body ids to the body data
|
||||
// stored in solver sets. As sims move within a set or across set.
|
||||
// Indices come from id pool.
|
||||
b3Array( b3Body ) bodies;
|
||||
|
||||
// Provides free list for solver sets.
|
||||
b3IdPool solverSetIdPool;
|
||||
|
||||
// Solvers sets allow sims to be stored in contiguous arrays. The first
|
||||
// set is all static sims. The second set is active sims. The third set is disabled
|
||||
// sims. The remaining sets are sleeping islands.
|
||||
b3Array( b3SolverSet ) solverSets;
|
||||
|
||||
// Used to create stable ids for joints
|
||||
b3IdPool jointIdPool;
|
||||
|
||||
// This is a sparse array that maps joint ids to the joint data stored in the constraint graph
|
||||
// or in the solver sets.
|
||||
b3Array( b3Joint ) joints;
|
||||
|
||||
// Used to create stable ids for contacts
|
||||
b3IdPool contactIdPool;
|
||||
|
||||
// This is a sparse array that maps contact ids to the contact data stored in the constraint graph
|
||||
// or in the solver sets.
|
||||
b3Array( b3Contact ) contacts;
|
||||
|
||||
// Used to create stable ids for islands
|
||||
b3IdPool islandIdPool;
|
||||
|
||||
// This is a sparse array that maps island ids to the island data stored in the solver sets.
|
||||
b3Array( b3Island ) islands;
|
||||
|
||||
b3IdPool shapeIdPool;
|
||||
|
||||
// These are sparse arrays that point into the pools above
|
||||
b3Array( b3Shape ) shapes;
|
||||
|
||||
// Reference counted store of shared hull data keyed by content. Shapes hold a
|
||||
// pointer to the owned copy here. Opaque to avoid leaking the verstable map
|
||||
// type into this header.
|
||||
void* hullDatabase;
|
||||
|
||||
// Name cache for shape and body names. This works with recording.
|
||||
b3NameCache names;
|
||||
|
||||
// This is a dense array of sensor data.
|
||||
b3Array( b3Sensor ) sensors;
|
||||
|
||||
// Per thread storage
|
||||
b3Array( b3TaskContext ) taskContexts;
|
||||
b3Array( b3SensorTaskContext ) sensorTaskContexts;
|
||||
|
||||
b3Array( b3BodyMoveEvent ) bodyMoveEvents;
|
||||
b3Array( b3SensorBeginTouchEvent ) sensorBeginEvents;
|
||||
b3Array( b3ContactBeginTouchEvent ) contactBeginEvents;
|
||||
|
||||
// End events are double buffered so that the user doesn't need to flush events
|
||||
b3Array( b3SensorEndTouchEvent ) sensorEndEvents[2];
|
||||
b3Array( b3ContactEndTouchEvent ) contactEndEvents[2];
|
||||
int endEventArrayIndex;
|
||||
|
||||
b3Array( b3ContactHitEvent ) contactHitEvents;
|
||||
b3Array( b3JointEvent ) jointEvents;
|
||||
|
||||
// Used to track debug draw
|
||||
b3BitSet debugBodySet;
|
||||
b3BitSet debugJointSet;
|
||||
b3BitSet debugContactSet;
|
||||
b3BitSet debugIslandSet;
|
||||
b3CreateDebugShapeCallback* createDebugShape;
|
||||
b3DestroyDebugShapeCallback* destroyDebugShape;
|
||||
void* userDebugShapeContext;
|
||||
|
||||
// Id that is incremented every time step
|
||||
uint64_t stepIndex;
|
||||
|
||||
// Identify islands for splitting as follows:
|
||||
// - I want to split islands so smaller islands can sleep
|
||||
// - when a body comes to rest and its sleep timer trips, I can look at the island and flag it for splitting
|
||||
// if it has removed constraints
|
||||
// - islands that have removed constraints must be put split first because I don't want to wake bodies incorrectly
|
||||
// - otherwise I can use the awake islands that have bodies wanting to sleep as the splitting candidates
|
||||
// - if no bodies want to sleep then there is no reason to perform island splitting
|
||||
int splitIslandId;
|
||||
|
||||
b3Vec3 gravity;
|
||||
float hitEventThreshold;
|
||||
float restitutionThreshold;
|
||||
float maxLinearSpeed;
|
||||
float contactSpeed;
|
||||
float contactHertz;
|
||||
float contactDampingRatio;
|
||||
float contactRecycleDistance;
|
||||
|
||||
b3FrictionCallback* frictionCallback;
|
||||
b3RestitutionCallback* restitutionCallback;
|
||||
|
||||
uint16_t generation;
|
||||
|
||||
b3Profile profile;
|
||||
int satCallCount;
|
||||
int satCacheHitCount;
|
||||
int manifoldCounts[B3_CONTACT_MANIFOLD_COUNT_BUCKETS];
|
||||
|
||||
b3Capacity maxCapacity;
|
||||
|
||||
b3PreSolveFcn* preSolveFcn;
|
||||
void* preSolveContext;
|
||||
|
||||
b3CustomFilterFcn* customFilterFcn;
|
||||
void* customFilterContext;
|
||||
|
||||
int workerCount;
|
||||
b3EnqueueTaskCallback* enqueueTaskFcn;
|
||||
b3FinishTaskCallback* finishTaskFcn;
|
||||
void* userTaskContext;
|
||||
void* userTreeTask;
|
||||
|
||||
struct b3Scheduler* scheduler;
|
||||
|
||||
void* userData;
|
||||
|
||||
// Non-NULL while a recording session is active. Set by b3World_StartRecording,
|
||||
// cleared by b3World_StopRecording. Hooks in mutators check this before writing.
|
||||
struct b3Recording* recording;
|
||||
|
||||
// latest inverse sub-step
|
||||
float inv_h;
|
||||
|
||||
// latest inverse full-step
|
||||
float inv_dt;
|
||||
|
||||
int activeTaskCount;
|
||||
int taskCount;
|
||||
|
||||
uint16_t worldId;
|
||||
|
||||
bool enableSleep;
|
||||
|
||||
// This indicates there is a world write operation in progress. This is for debugging and
|
||||
// not a real mutex. This should have minimal performance impact.
|
||||
bool locked;
|
||||
bool enableWarmStarting;
|
||||
bool enableContinuous;
|
||||
bool enableSpeculative;
|
||||
bool inUse;
|
||||
} b3World;
|
||||
|
||||
b3World* b3GetUnlockedWorldFromId( b3WorldId id );
|
||||
b3World* b3GetWorldFromId( b3WorldId id );
|
||||
|
||||
b3World* b3GetUnlockedWorld( int index );
|
||||
b3World* b3GetWorld( int index );
|
||||
|
||||
void b3ValidateConnectivity( b3World* world );
|
||||
void b3ValidateSolverSets( b3World* world );
|
||||
void b3ValidateContacts( b3World* world );
|
||||
|
||||
// Register a hull in the world database, returning the owned shared copy. Identical hulls
|
||||
// share one copy with a reference count. The input may be freed after this call.
|
||||
const b3HullData* b3AddHullToDatabase( b3World* world, const b3HullData* src );
|
||||
|
||||
// Like b3AddHullToDatabase but takes ownership of a heap hull: inserted directly on a miss,
|
||||
// freed on a hit. Avoids cloning data the caller already allocated.
|
||||
const b3HullData* b3AddOwnedHullToDatabase( b3World* world, b3HullData* owned );
|
||||
|
||||
// Release a reference to a shared hull. The owned copy is freed when the count reaches zero.
|
||||
void b3RemoveHullFromDatabase( b3World* world, const b3HullData* data );
|
||||
|
||||
static inline b3Manifold* b3AllocateManifolds( b3World* world, int count )
|
||||
{
|
||||
if ( count == 0 )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int index = count - 1;
|
||||
|
||||
// Need lock because this is called from the parallel narrow phase
|
||||
b3LockMutex( world->manifoldAllocatorMutex );
|
||||
int currentCount = world->manifoldAllocators.count;
|
||||
for ( int i = currentCount; i < count; ++i )
|
||||
{
|
||||
b3BlockAllocator allocator = b3CreateBlockAllocator( ( i + 1 ) * sizeof( b3Manifold ), 2 * B3_BLOCK_SIZE );
|
||||
b3Array_Push( world->manifoldAllocators, allocator );
|
||||
}
|
||||
|
||||
b3BlockAllocator* allocator = b3Array_Get( world->manifoldAllocators, index );
|
||||
b3Manifold* manifolds = (b3Manifold*)b3AllocateElement( allocator );
|
||||
b3UnlockMutex( world->manifoldAllocatorMutex );
|
||||
memset( manifolds, 0, count * sizeof( b3Manifold ) );
|
||||
return manifolds;
|
||||
}
|
||||
|
||||
static inline void b3FreeManifolds( b3World* world, b3Manifold* manifolds, int count )
|
||||
{
|
||||
if ( count == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int index = count - 1;
|
||||
b3LockMutex( world->manifoldAllocatorMutex );
|
||||
b3BlockAllocator* allocator = b3Array_Get( world->manifoldAllocators, index );
|
||||
b3FreeElement( allocator, manifolds );
|
||||
b3UnlockMutex( world->manifoldAllocatorMutex );
|
||||
}
|
||||
|
||||
104
vendor/box3d/src/src/platform.h
vendored
Normal file
104
vendor/box3d/src/src/platform.h
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Software prefetch hint. T0 brings the line into all cache levels.
|
||||
// On x86 MSVC exposes _mm_prefetch, ARM MSVC uses __prefetch instead.
|
||||
// clang/gcc provide __builtin_prefetch on every target.
|
||||
#if defined( B3_COMPILER_MSVC )
|
||||
#include <intrin.h>
|
||||
#if defined( B3_CPU_X86_X64 )
|
||||
#define b3Prefetch( addr ) _mm_prefetch( (const char*)( addr ), _MM_HINT_T0 )
|
||||
#else
|
||||
#define b3Prefetch( addr ) __prefetch( (const void*)( addr ) )
|
||||
#endif
|
||||
#elif defined( B3_COMPILER_CLANG ) || defined( B3_COMPILER_GCC )
|
||||
#define b3Prefetch( addr ) __builtin_prefetch( (const void*)( addr ), 0, 3 )
|
||||
#else
|
||||
#define b3Prefetch( addr ) ( (void)( addr ) )
|
||||
#endif
|
||||
|
||||
static inline void b3AtomicStoreInt( b3AtomicInt* a, int value )
|
||||
{
|
||||
#if defined( _MSC_VER )
|
||||
(void)_InterlockedExchange( (long*)&a->value, value );
|
||||
#elif defined( __GNUC__ ) || defined( __clang__ )
|
||||
__atomic_store_n( &a->value, value, __ATOMIC_SEQ_CST );
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int b3AtomicLoadInt( b3AtomicInt* a )
|
||||
{
|
||||
#if defined( _MSC_VER ) && !defined( __clang__ )
|
||||
int value = __iso_volatile_load32( (volatile __int32*)&a->value );
|
||||
#if defined( _M_ARM ) || defined( _M_ARM64 ) || defined( _M_ARM64EC )
|
||||
__dmb( 0xB );
|
||||
#else
|
||||
_ReadWriteBarrier();
|
||||
#endif
|
||||
return value;
|
||||
#elif defined( __GNUC__ ) || defined( __clang__ )
|
||||
return __atomic_load_n( &a->value, __ATOMIC_SEQ_CST );
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int b3AtomicFetchAddInt( b3AtomicInt* a, int increment )
|
||||
{
|
||||
#if defined( _MSC_VER )
|
||||
return _InterlockedExchangeAdd( (long*)&a->value, (long)increment );
|
||||
#elif defined( __GNUC__ ) || defined( __clang__ )
|
||||
return __atomic_fetch_add( &a->value, increment, __ATOMIC_SEQ_CST );
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool b3AtomicCompareExchangeInt( b3AtomicInt* a, int expected, int desired )
|
||||
{
|
||||
#if defined( _MSC_VER )
|
||||
return _InterlockedCompareExchange( (long*)&a->value, (long)desired, (long)expected ) == expected;
|
||||
#elif defined( __GNUC__ ) || defined( __clang__ )
|
||||
// The value written to expected is ignored
|
||||
return __atomic_compare_exchange_n( &a->value, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST );
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void b3AtomicStoreU32( b3AtomicU32* a, uint32_t value )
|
||||
{
|
||||
#if defined( _MSC_VER )
|
||||
(void)_InterlockedExchange( (long*)&a->value, value );
|
||||
#elif defined( __GNUC__ ) || defined( __clang__ )
|
||||
__atomic_store_n( &a->value, value, __ATOMIC_SEQ_CST );
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t b3AtomicLoadU32( b3AtomicU32* a )
|
||||
{
|
||||
#if defined( _MSC_VER ) && !defined( __clang__ )
|
||||
uint32_t value = (uint32_t)__iso_volatile_load32( (volatile __int32*)&a->value );
|
||||
#if defined( _M_ARM ) || defined( _M_ARM64 ) || defined( _M_ARM64EC )
|
||||
__dmb( 0xB );
|
||||
#else
|
||||
_ReadWriteBarrier();
|
||||
#endif
|
||||
return value;
|
||||
#elif defined( __GNUC__ ) || defined( __clang__ )
|
||||
return __atomic_load_n( &a->value, __ATOMIC_SEQ_CST );
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
}
|
||||
725
vendor/box3d/src/src/prismatic_joint.c
vendored
Normal file
725
vendor/box3d/src/src/prismatic_joint.c
vendored
Normal file
@@ -0,0 +1,725 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "body.h"
|
||||
#include "core.h"
|
||||
#include "joint.h"
|
||||
#include "math_internal.h"
|
||||
#include "physics_world.h"
|
||||
#include "solver.h"
|
||||
#include "solver_set.h"
|
||||
#include "recording.h"
|
||||
|
||||
// needed for dll export
|
||||
#include "box3d/box3d.h"
|
||||
|
||||
// Linear constraint (point-to-line)
|
||||
// joint axis is along joint frame A local z-axis
|
||||
// perpX and perpY are world vectors fixed in A
|
||||
//
|
||||
// d = pB - pA = xB + rB - xA - rA
|
||||
// Cx = dot(perpX, d)
|
||||
// Cy = dot(perpY, d)
|
||||
|
||||
// CdotX = dot(d, cross(wA, perpX)) + dot(perpX, vB + cross(wB, rB) - vA - cross(wA, rA))
|
||||
// = -dot(perpX, vA) - dot(cross(d + rA, perpX), wA) + dot(perpX, vB) + dot(cross(rB, perpX), vB)
|
||||
// Jx = [-perpX, -cross(d + rA, perpX), perpX, cross(rB, perpX)]
|
||||
// similar for perpY
|
||||
//
|
||||
// Simplification dropping dot(d, cross(wA, perpX)) (todo needs testing)
|
||||
// CdotXs = dot(perpX, vB + cross(wB, rB) - vA - cross(wA, rA))
|
||||
// Jxs = [-perpX, -cross(rA, perpX), perpX, cross(rB, perpX)]
|
||||
|
||||
// Motor/limit/spring linear constraint
|
||||
// axis is the world joint axis fixed in A
|
||||
|
||||
// C = dot(axis, d)
|
||||
// Cdot = dot(d, cross(wA, axis)) + dot(axis, vB + cross(wB, rB) - vA - cross(wA, rA))
|
||||
// Cdot = -dot(axis, vA) - dot(cross(d + rA, axis), wA) + dot(axis, vB) + dot(cross(rB, axis), vB)
|
||||
// J = [-axis -cross(d + rA, axis) axis cross(rB, axis)]
|
||||
//
|
||||
// Simplified (todo needs testing)
|
||||
// Cdot = -dot(axis, vA) - dot(cross(rA, axis), wA) + dot(axis, vB) + dot(cross(rB, axis), vB)
|
||||
// J = [-axis -cross(rA, axis) axis cross(rB, axis)]
|
||||
|
||||
// Predictive limit is applied even when the limit is not active.
|
||||
// Prevents a constraint speed that can lead to a constraint error in one time step.
|
||||
// Want C2 = C1 + h * Cdot >= 0
|
||||
// Or:
|
||||
// Cdot + C1/h >= 0
|
||||
// I do not apply a negative constraint error because that is handled in position correction.
|
||||
// So:
|
||||
// Cdot + max(C1, 0)/h >= 0
|
||||
|
||||
void b3PrismaticJoint_EnableLimit( b3JointId jointId, bool enableLimit )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, PrismaticJointEnableLimit, jointId, enableLimit );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
if ( enableLimit != base->prismaticJoint.enableLimit )
|
||||
{
|
||||
base->prismaticJoint.lowerImpulse = 0.0f;
|
||||
base->prismaticJoint.upperImpulse = 0.0f;
|
||||
}
|
||||
base->prismaticJoint.enableLimit = enableLimit;
|
||||
}
|
||||
|
||||
bool b3PrismaticJoint_IsLimitEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
return base->prismaticJoint.enableLimit;
|
||||
}
|
||||
|
||||
float b3PrismaticJoint_GetLowerLimit( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
return base->prismaticJoint.lowerTranslation;
|
||||
}
|
||||
|
||||
float b3PrismaticJoint_GetUpperLimit( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
return base->prismaticJoint.upperTranslation;
|
||||
}
|
||||
|
||||
void b3PrismaticJoint_SetLimits( b3JointId jointId, float lower, float upper )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( lower ) && b3IsValidFloat( upper ) );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, PrismaticJointSetLimits, jointId, lower, upper );
|
||||
float lowerAngle = b3MinFloat( lower, upper );
|
||||
float upperAngle = b3MaxFloat( lower, upper );
|
||||
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
base->prismaticJoint.lowerTranslation = lowerAngle;
|
||||
base->prismaticJoint.upperTranslation = upperAngle;
|
||||
}
|
||||
|
||||
float b3PrismaticJoint_GetTranslation( b3JointId jointId )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA );
|
||||
b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB );
|
||||
|
||||
b3Vec3 jointAxis = b3RotateVector( base->localFrameA.q, b3Vec3_axisX );
|
||||
jointAxis = b3RotateVector( transformA.q, jointAxis );
|
||||
|
||||
b3Vec3 anchorA = b3RotateVector( transformA.q, base->localFrameA.p );
|
||||
b3Vec3 anchorB = b3RotateVector( transformB.q, base->localFrameB.p );
|
||||
b3Vec3 d = b3Add( b3SubPos( transformB.p, transformA.p ), b3Sub( anchorB, anchorA ) );
|
||||
float translation = b3Dot( d, jointAxis );
|
||||
return translation;
|
||||
}
|
||||
|
||||
void b3PrismaticJoint_EnableSpring( b3JointId jointId, bool enableSpring )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, PrismaticJointEnableSpring, jointId, enableSpring );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
if ( enableSpring != base->prismaticJoint.enableSpring )
|
||||
{
|
||||
base->prismaticJoint.springImpulse = 0.0f;
|
||||
}
|
||||
base->prismaticJoint.enableSpring = enableSpring;
|
||||
}
|
||||
|
||||
bool b3PrismaticJoint_IsSpringEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
return base->prismaticJoint.enableSpring;
|
||||
}
|
||||
|
||||
void b3PrismaticJoint_SetTargetTranslation( b3JointId jointId, float targetTranslation )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( targetTranslation ) );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, PrismaticJointSetTargetTranslation, jointId, targetTranslation );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
base->prismaticJoint.targetTranslation = targetTranslation;
|
||||
}
|
||||
|
||||
float b3PrismaticJoint_GetTargetTranslation( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
return base->prismaticJoint.targetTranslation;
|
||||
}
|
||||
|
||||
void b3PrismaticJoint_SetSpringHertz( b3JointId jointId, float hertz )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, PrismaticJointSetSpringHertz, jointId, hertz );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
base->prismaticJoint.hertz = hertz;
|
||||
}
|
||||
|
||||
float b3PrismaticJoint_GetSpringHertz( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
return base->prismaticJoint.hertz;
|
||||
}
|
||||
|
||||
void b3PrismaticJoint_SetSpringDampingRatio( b3JointId jointId, float dampingRatio )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, PrismaticJointSetSpringDampingRatio, jointId, dampingRatio );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
base->prismaticJoint.dampingRatio = dampingRatio;
|
||||
}
|
||||
|
||||
float b3PrismaticJoint_GetSpringDampingRatio( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
return base->prismaticJoint.dampingRatio;
|
||||
}
|
||||
|
||||
void b3PrismaticJoint_EnableMotor( b3JointId jointId, bool enableMotor )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, PrismaticJointEnableMotor, jointId, enableMotor );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
if ( enableMotor != base->prismaticJoint.enableMotor )
|
||||
{
|
||||
base->prismaticJoint.motorImpulse = 0.0f;
|
||||
}
|
||||
base->prismaticJoint.enableMotor = enableMotor;
|
||||
}
|
||||
|
||||
bool b3PrismaticJoint_IsMotorEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
return base->prismaticJoint.enableMotor;
|
||||
}
|
||||
|
||||
void b3PrismaticJoint_SetMotorSpeed( b3JointId jointId, float motorSpeed )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( motorSpeed ) );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, PrismaticJointSetMotorSpeed, jointId, motorSpeed );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
base->prismaticJoint.motorSpeed = motorSpeed;
|
||||
}
|
||||
|
||||
float b3PrismaticJoint_GetMotorSpeed( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
return base->prismaticJoint.motorSpeed;
|
||||
}
|
||||
|
||||
void b3PrismaticJoint_SetMaxMotorForce( b3JointId jointId, float maxForce )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( maxForce ) && maxForce >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, PrismaticJointSetMaxMotorForce, jointId, maxForce );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
base->prismaticJoint.maxMotorForce = maxForce;
|
||||
}
|
||||
|
||||
float b3PrismaticJoint_GetMaxMotorForce( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
return base->prismaticJoint.maxMotorForce;
|
||||
}
|
||||
|
||||
float b3PrismaticJoint_GetMotorForce( b3JointId jointId )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
return world->inv_h * base->prismaticJoint.motorImpulse;
|
||||
}
|
||||
|
||||
float b3PrismaticJoint_GetSpeed( b3JointId jointId )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint );
|
||||
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB );
|
||||
b3BodySim* bodySimA = b3GetBodySim( world, bodyA );
|
||||
b3BodySim* bodySimB = b3GetBodySim( world, bodyB );
|
||||
b3BodyState* stateA = b3GetBodyState( world, bodyA );
|
||||
b3BodyState* stateB = b3GetBodyState( world, bodyB );
|
||||
|
||||
b3Quat qA = bodySimA->transform.q;
|
||||
b3Quat qB = bodySimB->transform.q;
|
||||
|
||||
b3Vec3 axisA = b3RotateVector( qA, b3RotateVector( base->localFrameA.q, b3Vec3_axisX ) );
|
||||
b3Vec3 rA = b3RotateVector( qA, b3Sub( base->localFrameA.p, bodySimA->localCenter ) );
|
||||
b3Vec3 rB = b3RotateVector( qB, b3Sub( base->localFrameB.p, bodySimB->localCenter ) );
|
||||
|
||||
// Difference the centers in double so the speed stays exact far from the origin.
|
||||
b3Vec3 d = b3Add( b3SubPos( bodySimB->center, bodySimA->center ), b3Sub( rB, rA ) );
|
||||
|
||||
b3Vec3 vA = stateA ? stateA->linearVelocity : b3Vec3_zero;
|
||||
b3Vec3 vB = stateB ? stateB->linearVelocity : b3Vec3_zero;
|
||||
b3Vec3 wA = stateA ? stateA->angularVelocity : b3Vec3_zero;
|
||||
b3Vec3 wB = stateB ? stateB->angularVelocity : b3Vec3_zero;
|
||||
|
||||
b3Vec3 vRel = b3Sub( b3Add( vB, b3Cross( wB, rB ) ), b3Add( vA, b3Cross( wA, rA ) ) );
|
||||
|
||||
// The axis moves with body A, so account for its rotation.
|
||||
float speed = b3Dot( d, b3Cross( wA, axisA ) ) + b3Dot( axisA, vRel );
|
||||
return speed;
|
||||
}
|
||||
|
||||
b3Vec3 b3GetPrismaticJointForce( b3World* world, b3JointSim* base )
|
||||
{
|
||||
b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA );
|
||||
b3PrismaticJoint* joint = &base->prismaticJoint;
|
||||
|
||||
// impulse in joint space
|
||||
b3Vec3 impulse = {
|
||||
joint->perpImpulse.x,
|
||||
joint->perpImpulse.y,
|
||||
joint->motorImpulse + joint->lowerImpulse + joint->upperImpulse + joint->springImpulse,
|
||||
};
|
||||
|
||||
// convert impulse to force
|
||||
b3Vec3 force = b3MulSV( world->inv_h, impulse );
|
||||
|
||||
// convert to body space
|
||||
force = b3RotateVector( base->localFrameA.q, force );
|
||||
|
||||
// convert to world space
|
||||
force = b3RotateVector( transformA.q, force );
|
||||
return force;
|
||||
}
|
||||
|
||||
b3Vec3 b3GetPrismaticJointTorque( b3World* world, b3JointSim* base )
|
||||
{
|
||||
b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA );
|
||||
b3PrismaticJoint* joint = &base->prismaticJoint;
|
||||
|
||||
b3Vec3 torque = b3MulSV( world->inv_h, joint->angularImpulse );
|
||||
torque = b3RotateVector( base->localFrameA.q, torque );
|
||||
torque = b3RotateVector( transformA.q, torque );
|
||||
return torque;
|
||||
}
|
||||
|
||||
void b3PreparePrismaticJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_prismaticJoint );
|
||||
|
||||
b3World* world = context->world;
|
||||
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB );
|
||||
|
||||
B3_ASSERT( bodyB->setIndex == b3_awakeSet );
|
||||
b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex );
|
||||
b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex );
|
||||
|
||||
int localIndexA = bodyA->localIndex;
|
||||
int localIndexB = bodyB->localIndex;
|
||||
|
||||
b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA );
|
||||
b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB );
|
||||
|
||||
base->invMassA = bodySimA->invMass;
|
||||
base->invMassB = bodySimB->invMass;
|
||||
base->invIA = bodySimA->invInertiaWorld;
|
||||
base->invIB = bodySimB->invInertiaWorld;
|
||||
|
||||
b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB );
|
||||
base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN;
|
||||
|
||||
b3PrismaticJoint* joint = &base->prismaticJoint;
|
||||
joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX;
|
||||
joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX;
|
||||
|
||||
// Compute joint anchor frames with world space rotation, relative to center of mass
|
||||
joint->frameA.q = b3MulQuat( bodySimA->transform.q, base->localFrameA.q );
|
||||
joint->frameA.p = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) );
|
||||
joint->frameB.q = b3MulQuat( bodySimB->transform.q, base->localFrameB.q );
|
||||
joint->frameB.p = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) );
|
||||
|
||||
joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center );
|
||||
joint->rotationMass = b3InvertMatrix( invInertiaSum );
|
||||
|
||||
// Initial joint axes in world space
|
||||
b3Matrix3 matrixA = b3MakeMatrixFromQuat( joint->frameA.q );
|
||||
joint->jointAxis = matrixA.cx;
|
||||
joint->perpAxisY = matrixA.cy;
|
||||
joint->perpAxisZ = matrixA.cz;
|
||||
|
||||
joint->springSoftness = b3MakeSoft( joint->hertz, joint->dampingRatio, context->h );
|
||||
|
||||
if ( context->enableWarmStarting == false )
|
||||
{
|
||||
joint->perpImpulse = (b3Vec2){ 0.0f, 0.0f };
|
||||
joint->angularImpulse = (b3Vec3){ 0.0f, 0.0f, 0.0f };
|
||||
joint->motorImpulse = 0.0f;
|
||||
joint->springImpulse = 0.0f;
|
||||
joint->lowerImpulse = 0.0f;
|
||||
joint->upperImpulse = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void b3WarmStartPrismaticJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_prismaticJoint );
|
||||
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3PrismaticJoint* joint = &base->prismaticJoint;
|
||||
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
// todo make this code and the wheel joint more similar
|
||||
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p );
|
||||
b3Vec3 d = b3Add( b3Add( b3Sub( stateB->deltaPosition, stateA->deltaPosition ), joint->deltaCenter ), b3Sub( rB, rA ) );
|
||||
b3Vec3 jointAxis = b3RotateVector( stateA->deltaRotation, joint->jointAxis );
|
||||
b3Vec3 sAx = b3Cross( b3Add( rA, d ), jointAxis );
|
||||
b3Vec3 sBx = b3Cross( rB, jointAxis );
|
||||
|
||||
b3Vec3 perpY = b3RotateVector( stateA->deltaRotation, joint->perpAxisY );
|
||||
b3Vec3 perpZ = b3RotateVector( stateA->deltaRotation, joint->perpAxisZ );
|
||||
b3Vec3 sAy = b3Cross( b3Add( rA, d ), perpY );
|
||||
b3Vec3 sBy = b3Cross( rB, perpY );
|
||||
b3Vec3 sAz = b3Cross( b3Add( rA, d ), perpZ );
|
||||
b3Vec3 sBz = b3Cross( rB, perpZ );
|
||||
|
||||
float axialImpulse = joint->springImpulse + joint->motorImpulse + joint->lowerImpulse - joint->upperImpulse;
|
||||
b3Vec2 perpImpulse = joint->perpImpulse;
|
||||
|
||||
b3Vec3 P = b3Blend3( axialImpulse, jointAxis, perpImpulse.x, perpY, perpImpulse.y, perpZ );
|
||||
b3Vec3 LA = b3Add( b3Blend3( axialImpulse, sAx, perpImpulse.x, sAy, perpImpulse.y, sAz ), joint->angularImpulse );
|
||||
b3Vec3 LB = b3Add( b3Blend3( axialImpulse, sBx, perpImpulse.x, sBy, perpImpulse.y, sBz ), joint->angularImpulse );
|
||||
|
||||
b3Vec3 vA = stateA->linearVelocity;
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 vB = stateB->linearVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
vA = b3MulSub( vA, mA, P );
|
||||
wA = b3Sub( wA, b3MulMV( iA, LA ) );
|
||||
vB = b3MulAdd( vB, mB, P );
|
||||
wB = b3Add( wB, b3MulMV( iB, LB ) );
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->linearVelocity = vA;
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->linearVelocity = vB;
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
|
||||
void b3SolvePrismaticJoint( b3JointSim* base, b3StepContext* context, bool useBias )
|
||||
{
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3PrismaticJoint* joint = &base->prismaticJoint;
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 vA = stateA->linearVelocity;
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 vB = stateB->linearVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
|
||||
bool fixedRotation = base->fixedRotation;
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p );
|
||||
|
||||
b3Vec3 dcA = stateA->deltaPosition;
|
||||
b3Vec3 dcB = stateB->deltaPosition;
|
||||
b3Vec3 d = b3Add( b3Add( b3Sub( dcB, dcA ), joint->deltaCenter ), b3Sub( rB, rA ) );
|
||||
|
||||
b3Vec3 jointAxis = b3RotateVector( stateA->deltaRotation, joint->jointAxis );
|
||||
b3Vec3 sAx = b3Cross( b3Add( rA, d ), jointAxis );
|
||||
b3Vec3 sBx = b3Cross( rB, jointAxis );
|
||||
float jointTranslation = b3Dot( d, jointAxis );
|
||||
float targetTranslation = joint->targetTranslation;
|
||||
|
||||
// The axial effective mass must be fresh to avoid divergence when the joint is stressed
|
||||
float ka = mA + mB + b3Dot( sAx, b3MulMV( iA, sAx ) ) + b3Dot( sBx, b3MulMV( iB, sBx ) );
|
||||
float axialMass = ka > 0.0f ? 1.0f / ka : 0.0f;
|
||||
|
||||
// Solve spring
|
||||
if ( joint->enableSpring && fixedRotation == false )
|
||||
{
|
||||
// Get the substep relative rotation
|
||||
float c = jointTranslation - targetTranslation;
|
||||
|
||||
float bias = joint->springSoftness.biasRate * c;
|
||||
float massScale = joint->springSoftness.massScale;
|
||||
float impulseScale = joint->springSoftness.impulseScale;
|
||||
|
||||
b3Vec3 vRel = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, b3Add( rA, d ) ) );
|
||||
float cdot = b3Dot( vRel, jointAxis );
|
||||
float deltaImpulse = -massScale * axialMass * ( cdot + bias ) - impulseScale * joint->springImpulse;
|
||||
joint->springImpulse += deltaImpulse;
|
||||
|
||||
b3Vec3 P = b3MulSV( deltaImpulse, jointAxis );
|
||||
b3Vec3 LA = b3MulSV( deltaImpulse, sAx );
|
||||
b3Vec3 LB = b3MulSV( deltaImpulse, sBx );
|
||||
|
||||
vA = b3MulSub( vA, mA, P );
|
||||
wA = b3Sub( wA, b3MulMV( iA, LA ) );
|
||||
vB = b3MulAdd( vB, mB, P );
|
||||
wB = b3Add( wB, b3MulMV( iB, LB ) );
|
||||
}
|
||||
|
||||
if ( joint->enableMotor && fixedRotation == false )
|
||||
{
|
||||
b3Vec3 vRel = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, b3Add( rA, d ) ) );
|
||||
float cdot = b3Dot( vRel, jointAxis ) - joint->motorSpeed;
|
||||
|
||||
float deltaImpulse = -axialMass * cdot;
|
||||
float newImpulse = joint->motorImpulse + deltaImpulse;
|
||||
float maxImpulse = joint->maxMotorForce * context->h;
|
||||
newImpulse = b3ClampFloat( newImpulse, -maxImpulse, maxImpulse );
|
||||
deltaImpulse = newImpulse - joint->motorImpulse;
|
||||
joint->motorImpulse = newImpulse;
|
||||
|
||||
b3Vec3 P = b3MulSV( deltaImpulse, jointAxis );
|
||||
b3Vec3 LA = b3MulSV( deltaImpulse, sAx );
|
||||
b3Vec3 LB = b3MulSV( deltaImpulse, sBx );
|
||||
|
||||
vA = b3MulSub( vA, mA, P );
|
||||
wA = b3Sub( wA, b3MulMV( iA, LA ) );
|
||||
vB = b3MulAdd( vB, mB, P );
|
||||
wB = b3Add( wB, b3MulMV( iB, LB ) );
|
||||
}
|
||||
|
||||
if ( joint->enableLimit && fixedRotation == false )
|
||||
{
|
||||
float speculativeDistance = 0.25f * ( joint->upperTranslation - joint->lowerTranslation );
|
||||
|
||||
// Lower limit
|
||||
{
|
||||
float C = jointTranslation - joint->lowerTranslation;
|
||||
|
||||
if ( C < speculativeDistance )
|
||||
{
|
||||
float bias = 0.0f;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( C > 0.0f )
|
||||
{
|
||||
// speculation
|
||||
bias = C * context->inv_h;
|
||||
}
|
||||
else if ( useBias )
|
||||
{
|
||||
bias = base->constraintSoftness.biasRate * C;
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
b3Vec3 vRel = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, b3Add( rA, d ) ) );
|
||||
float cdot = b3Dot( vRel, jointAxis );
|
||||
float oldImpulse = joint->lowerImpulse;
|
||||
float deltaImpulse = -massScale * axialMass * ( cdot + bias ) - impulseScale * oldImpulse;
|
||||
joint->lowerImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f );
|
||||
deltaImpulse = joint->lowerImpulse - oldImpulse;
|
||||
|
||||
b3Vec3 P = b3MulSV( deltaImpulse, jointAxis );
|
||||
b3Vec3 LA = b3MulSV( deltaImpulse, sAx );
|
||||
b3Vec3 LB = b3MulSV( deltaImpulse, sBx );
|
||||
|
||||
vA = b3MulSub( vA, mA, P );
|
||||
wA = b3Sub( wA, b3MulMV( iA, LA ) );
|
||||
vB = b3MulAdd( vB, mB, P );
|
||||
wB = b3Add( wB, b3MulMV( iB, LB ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
joint->lowerImpulse = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Upper limit
|
||||
{
|
||||
float C = joint->upperTranslation - jointTranslation;
|
||||
|
||||
if ( C < speculativeDistance )
|
||||
{
|
||||
float bias = 0.0f;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( C > 0.0f )
|
||||
{
|
||||
// speculation
|
||||
bias = C * context->inv_h;
|
||||
}
|
||||
else if ( useBias )
|
||||
{
|
||||
bias = base->constraintSoftness.biasRate * C;
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
// sign flipped on Cdot
|
||||
b3Vec3 vRel = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, b3Add( rA, d ) ) );
|
||||
float cdot = -b3Dot( vRel, jointAxis );
|
||||
float oldImpulse = joint->upperImpulse;
|
||||
float deltaImpulse = -massScale * axialMass * ( cdot + bias ) - impulseScale * oldImpulse;
|
||||
joint->upperImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f );
|
||||
|
||||
// sign flipped on applied impulse
|
||||
float negDeltaImpulse = oldImpulse - joint->upperImpulse;
|
||||
b3Vec3 P = b3MulSV( negDeltaImpulse, jointAxis );
|
||||
b3Vec3 LA = b3MulSV( negDeltaImpulse, sAx );
|
||||
b3Vec3 LB = b3MulSV( negDeltaImpulse, sBx );
|
||||
|
||||
vA = b3MulSub( vA, mA, P );
|
||||
wA = b3Sub( wA, b3MulMV( iA, LA ) );
|
||||
vB = b3MulAdd( vB, mB, P );
|
||||
wB = b3Add( wB, b3MulMV( iB, LB ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
joint->upperImpulse = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rotation constraint
|
||||
if ( fixedRotation == false )
|
||||
{
|
||||
b3Vec3 bias = { 0.0f, 0.0f, 0.0f };
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
|
||||
if ( useBias )
|
||||
{
|
||||
b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q );
|
||||
b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q );
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( quatA, quatB );
|
||||
b3Quat targetQuat = b3Quat_identity;
|
||||
b3Vec3 deltaRotation = b3DeltaQuatToRotation( relQ, targetQuat );
|
||||
b3Vec3 c = b3Neg( b3RotateVector( quatA, deltaRotation ) );
|
||||
|
||||
bias = b3MulSV( base->constraintSoftness.biasRate, c );
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
b3Vec3 cdot = b3Sub( wB, wA );
|
||||
b3Vec3 impulse = b3Sub(
|
||||
b3MulSV( -massScale, b3MulMV( joint->rotationMass, b3Add( cdot, bias ) ) ),
|
||||
b3MulSV( impulseScale, joint->angularImpulse ) );
|
||||
joint->angularImpulse = b3Add( joint->angularImpulse, impulse );
|
||||
|
||||
wA = b3Sub( wA, b3MulMV( iA, impulse ) );
|
||||
wB = b3Add( wB, b3MulMV( iB, impulse ) );
|
||||
}
|
||||
|
||||
// Solve point-to-line constraint
|
||||
{
|
||||
b3Vec3 perpY = b3RotateVector( stateA->deltaRotation, joint->perpAxisY );
|
||||
b3Vec3 perpZ = b3RotateVector( stateA->deltaRotation, joint->perpAxisZ );
|
||||
|
||||
b3Vec2 bias = { 0.0f, 0.0f };
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( useBias )
|
||||
{
|
||||
b3Vec2 c = { b3Dot( perpY, d ), b3Dot( perpZ, d ) };
|
||||
bias = b3MulSV2( base->constraintSoftness.biasRate, c );
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
b3Vec3 vRel = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, b3Add( rA, d ) ) );
|
||||
b3Vec2 cdot = { b3Dot( perpY, vRel ), b3Dot( perpZ, vRel ) };
|
||||
|
||||
// K = [(1/mA + 1/mB) * eye(2) - skew(rA) * invIA * skew(rA) - skew(rB) * invIB * skew(rB)]
|
||||
// Jx = [-perpX, -cross(d + rA, perpX), perpX, cross(rB, perpX)]
|
||||
b3Vec3 sAy = b3Cross( b3Add( rA, d ), perpY );
|
||||
b3Vec3 sBy = b3Cross( rB, perpY );
|
||||
b3Vec3 sAz = b3Cross( b3Add( rA, d ), perpZ );
|
||||
b3Vec3 sBz = b3Cross( rB, perpZ );
|
||||
|
||||
float kyy = mA + mB + b3Dot( sAy, b3MulMV( iA, sAy ) ) + b3Dot( sBy, b3MulMV( iB, sBy ) );
|
||||
float kyz = b3Dot( sAy, b3MulMV( iA, sAz ) ) + b3Dot( sBy, b3MulMV( iB, sBz ) );
|
||||
float kzz = mA + mB + b3Dot( sAz, b3MulMV( iA, sAz ) ) + b3Dot( sBz, b3MulMV( iB, sBz ) );
|
||||
|
||||
b3Matrix2 K = { { kyy, kyz }, { kyz, kzz } };
|
||||
|
||||
b3Vec2 oldImpulse = joint->perpImpulse;
|
||||
b3Vec2 sol = b3Solve2( K, b3Add2( cdot, bias ) );
|
||||
b3Vec2 deltaImpulse = b3Sub2( b3MulSV2( -massScale, sol ), b3MulSV2( impulseScale, oldImpulse ) );
|
||||
joint->perpImpulse = b3Add2( oldImpulse, deltaImpulse );
|
||||
|
||||
b3Vec3 P = b3Blend2( deltaImpulse.x, perpY, deltaImpulse.y, perpZ );
|
||||
|
||||
vA = b3MulSub( vA, mA, P );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Blend2( deltaImpulse.x, sAy, deltaImpulse.y, sAz ) ) );
|
||||
vB = b3MulAdd( vB, mB, P );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Blend2( deltaImpulse.x, sBy, deltaImpulse.y, sBz ) ) );
|
||||
}
|
||||
|
||||
B3_ASSERT( b3IsValidVec3( vA ) );
|
||||
B3_ASSERT( b3IsValidVec3( wA ) );
|
||||
B3_ASSERT( b3IsValidVec3( vB ) );
|
||||
B3_ASSERT( b3IsValidVec3( wB ) );
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->linearVelocity = vA;
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->linearVelocity = vB;
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
|
||||
void b3DrawPrismaticJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale )
|
||||
{
|
||||
b3WorldTransform frameA = b3MulWorldTransforms( transformA, base->localFrameA );
|
||||
b3WorldTransform frameB = b3MulWorldTransforms( transformB, base->localFrameB );
|
||||
|
||||
b3Matrix3 R = b3MakeMatrixFromQuat( frameA.q );
|
||||
b3Vec3 axis = R.cx;
|
||||
b3Vec3 perpY = R.cy;
|
||||
b3Vec3 perpZ = R.cz;
|
||||
|
||||
float s = 0.2f * scale;
|
||||
draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( s, perpY ) ), b3_colorGreen, draw->context );
|
||||
draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( s, perpZ ) ), b3_colorBlue, draw->context );
|
||||
|
||||
b3PrismaticJoint* joint = &base->prismaticJoint;
|
||||
if ( joint->enableLimit )
|
||||
{
|
||||
b3Pos p1 = b3OffsetPos( frameA.p, b3MulSV( joint->lowerTranslation, axis ) );
|
||||
b3Pos p2 = b3OffsetPos( frameA.p, b3MulSV( joint->upperTranslation, axis ) );
|
||||
draw->DrawSegmentFcn( p1, p2, b3_colorOrange, draw->context );
|
||||
draw->DrawPointFcn( p1, 10.0f, b3_colorGreen, draw->context );
|
||||
draw->DrawPointFcn( p2, 10.0f, b3_colorRed, draw->context );
|
||||
}
|
||||
else
|
||||
{
|
||||
b3Pos p1 = b3OffsetPos( frameA.p, b3MulSV( -0.5f * scale, axis ) );
|
||||
b3Pos p2 = b3OffsetPos( frameA.p, b3MulSV( 0.5f * scale, axis ) );
|
||||
draw->DrawSegmentFcn( p1, p2, b3_colorOrange, draw->context );
|
||||
}
|
||||
|
||||
draw->DrawPointFcn( frameB.p, 8.0f, b3_colorViolet, draw->context );
|
||||
}
|
||||
186
vendor/box3d/src/src/qsort.h
vendored
Normal file
186
vendor/box3d/src/src/qsort.h
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2017 Alexey Tourbin
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a traditional Quicksort implementation which mostly follows
|
||||
* [Sedgewick 1978]. Sorting is performed entirely on array indices,
|
||||
* while actual access to the array elements is abstracted out with the
|
||||
* user-defined `LESS` and `SWAP` primitives.
|
||||
*
|
||||
* Synopsis:
|
||||
* QSORT(N, LESS, SWAP);
|
||||
* where
|
||||
* N - the number of elements in A[];
|
||||
* LESS(i, j) - compares A[i] to A[j];
|
||||
* SWAP(i, j) - exchanges A[i] with A[j].
|
||||
*/
|
||||
|
||||
#ifndef QSORT_H
|
||||
#define QSORT_H
|
||||
|
||||
/* Sort 3 elements. */
|
||||
#define Q_SORT3(q_a1, q_a2, q_a3, Q_LESS, Q_SWAP) \
|
||||
do { \
|
||||
if (Q_LESS(q_a2, q_a1)) { \
|
||||
if (Q_LESS(q_a3, q_a2)) \
|
||||
Q_SWAP(q_a1, q_a3); \
|
||||
else { \
|
||||
Q_SWAP(q_a1, q_a2); \
|
||||
if (Q_LESS(q_a3, q_a2)) \
|
||||
Q_SWAP(q_a2, q_a3); \
|
||||
} \
|
||||
} \
|
||||
else if (Q_LESS(q_a3, q_a2)) { \
|
||||
Q_SWAP(q_a2, q_a3); \
|
||||
if (Q_LESS(q_a2, q_a1)) \
|
||||
Q_SWAP(q_a1, q_a2); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Partition [q_l,q_r] around a pivot. After partitioning,
|
||||
* [q_l,q_j] are the elements that are less than or equal to the pivot,
|
||||
* while [q_i,q_r] are the elements greater than or equal to the pivot. */
|
||||
#define Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP) \
|
||||
do { \
|
||||
/* The middle element, not to be confused with the median. */ \
|
||||
Q_UINT q_m = q_l + ((q_r - q_l) >> 1); \
|
||||
/* Reorder the second, the middle, and the last items. \
|
||||
* As [Edelkamp Weiss 2016] explain, using the second element \
|
||||
* instead of the first one helps avoid bad behaviour for \
|
||||
* decreasingly sorted arrays. This method is used in recent \
|
||||
* versions of gcc's std::sort, see gcc bug 58437#c13, although \
|
||||
* the details are somewhat different (cf. #c14). */ \
|
||||
Q_SORT3(q_l + 1, q_m, q_r, Q_LESS, Q_SWAP); \
|
||||
/* Place the median at the beginning. */ \
|
||||
Q_SWAP(q_l, q_m); \
|
||||
/* Partition [q_l+2, q_r-1] around the median which is in q_l. \
|
||||
* q_i and q_j are initially off by one, they get decremented \
|
||||
* in the do-while loops. */ \
|
||||
q_i = q_l + 1; q_j = q_r; \
|
||||
while (1) { \
|
||||
do q_i++; while (Q_LESS(q_i, q_l)); \
|
||||
do q_j--; while (Q_LESS(q_l, q_j)); \
|
||||
if (q_i >= q_j) break; /* Sedgewick says "until j < i" */ \
|
||||
Q_SWAP(q_i, q_j); \
|
||||
} \
|
||||
/* Compensate for the i==j case. */ \
|
||||
q_i = q_j + 1; \
|
||||
/* Put the median to its final place. */ \
|
||||
Q_SWAP(q_l, q_j); \
|
||||
/* The median is not part of the left subfile. */ \
|
||||
q_j--; \
|
||||
} while (0)
|
||||
|
||||
/* Insertion sort is applied to small subfiles - this is contrary to
|
||||
* Sedgewick's suggestion to run a separate insertion sort pass after
|
||||
* the partitioning is done. The reason I don't like a separate pass
|
||||
* is that it triggers extra comparisons, because it can't see that the
|
||||
* medians are already in their final positions and need not be rechecked.
|
||||
* Since I do not assume that comparisons are cheap, I also do not try
|
||||
* to eliminate the (q_j > q_l) boundary check. */
|
||||
#define Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP) \
|
||||
do { \
|
||||
Q_UINT q_i, q_j; \
|
||||
/* For each item starting with the second... */ \
|
||||
for (q_i = q_l + 1; q_i <= q_r; q_i++) \
|
||||
/* move it down the array so that the first part is sorted. */ \
|
||||
for (q_j = q_i; q_j > q_l && (Q_LESS(q_j, q_j - 1)); q_j--) \
|
||||
Q_SWAP(q_j, q_j - 1); \
|
||||
} while (0)
|
||||
|
||||
/* When the size of [q_l,q_r], i.e. q_r-q_l+1, is greater than or equal to
|
||||
* Q_THRESH, the algorithm performs recursive partitioning. When the size
|
||||
* drops below Q_THRESH, the algorithm switches to insertion sort.
|
||||
* The minimum valid value is probably 5 (with 5 items, the second and
|
||||
* the middle items, the middle itself being rounded down, are distinct). */
|
||||
#define Q_THRESH 16
|
||||
|
||||
/* The main loop. */
|
||||
#define Q_LOOP(Q_UINT, Q_N, Q_LESS, Q_SWAP) \
|
||||
do { \
|
||||
Q_UINT q_l = 0; \
|
||||
Q_UINT q_r = (Q_N) - 1; \
|
||||
Q_UINT q_sp = 0; /* the number of frames pushed to the stack */ \
|
||||
struct { Q_UINT q_l, q_r; } \
|
||||
/* On 32-bit platforms, to sort a "char[3GB+]" array, \
|
||||
* it may take full 32 stack frames. On 64-bit CPUs, \
|
||||
* though, the address space is limited to 48 bits. \
|
||||
* The usage is further reduced if Q_N has a 32-bit type. */ \
|
||||
q_st[sizeof(Q_UINT) > 4 && sizeof(Q_N) > 4 ? 48 : 32]; \
|
||||
while (1) { \
|
||||
if (q_r - q_l + 1 >= Q_THRESH) { \
|
||||
Q_UINT q_i, q_j; \
|
||||
Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP); \
|
||||
/* Now have two subfiles: [q_l,q_j] and [q_i,q_r]. \
|
||||
* Dealing with them depends on which one is bigger. */ \
|
||||
if (q_j - q_l >= q_r - q_i) \
|
||||
Q_SUBFILES(q_l, q_j, q_i, q_r); \
|
||||
else \
|
||||
Q_SUBFILES(q_i, q_r, q_l, q_j); \
|
||||
} \
|
||||
else { \
|
||||
Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP); \
|
||||
/* Pop subfiles from the stack, until it gets empty. */ \
|
||||
if (q_sp == 0) break; \
|
||||
q_sp--; \
|
||||
q_l = q_st[q_sp].q_l; \
|
||||
q_r = q_st[q_sp].q_r; \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* The missing part: dealing with subfiles.
|
||||
* Assumes that the first subfile is not smaller than the second. */
|
||||
#define Q_SUBFILES(q_l1, q_r1, q_l2, q_r2) \
|
||||
do { \
|
||||
/* If the second subfile is only a single element, it needs \
|
||||
* no further processing. The first subfile will be processed \
|
||||
* on the next iteration (both subfiles cannot be only a single \
|
||||
* element, due to Q_THRESH). */ \
|
||||
if (q_l2 == q_r2) { \
|
||||
q_l = q_l1; \
|
||||
q_r = q_r1; \
|
||||
} \
|
||||
else { \
|
||||
/* Otherwise, both subfiles need processing. \
|
||||
* Push the larger subfile onto the stack. */ \
|
||||
q_st[q_sp].q_l = q_l1; \
|
||||
q_st[q_sp].q_r = q_r1; \
|
||||
q_sp++; \
|
||||
/* Process the smaller subfile on the next iteration. */ \
|
||||
q_l = q_l2; \
|
||||
q_r = q_r2; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* And now, ladies and gentlemen, may I proudly present to you... */
|
||||
#define QSORT(Q_N, Q_LESS, Q_SWAP) \
|
||||
do { \
|
||||
if ((Q_N) > 1) \
|
||||
/* We could check sizeof(Q_N) and use "unsigned", but at least \
|
||||
* on x86_64, this has the performance penalty of up to 5%. */ \
|
||||
Q_LOOP(unsigned long, Q_N, Q_LESS, Q_SWAP); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
||||
/* ex:set ts=8 sts=4 sw=4 noet: */
|
||||
1279
vendor/box3d/src/src/recording.c
vendored
Normal file
1279
vendor/box3d/src/src/recording.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
386
vendor/box3d/src/src/recording.h
vendored
Normal file
386
vendor/box3d/src/src/recording.h
vendored
Normal file
@@ -0,0 +1,386 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include "box3d/id.h"
|
||||
#include "box3d/math_functions.h"
|
||||
#include "box3d/types.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
// FNV-1a 64-bit constants
|
||||
#define B3_SNAP_FNV_INIT 14695981039346656037ull
|
||||
#define B3_SNAP_FNV_PRIME 1099511628211ull
|
||||
|
||||
// Mix a world position at full width so the determinism gate validates past float precision
|
||||
// when the body is far from the origin.
|
||||
static inline uint64_t b3FnvMixPosition( uint64_t hash, b3Pos p )
|
||||
{
|
||||
#if defined( BOX3D_DOUBLE_PRECISION )
|
||||
uint64_t bx, by, bz;
|
||||
memcpy( &bx, &p.x, 8 );
|
||||
memcpy( &by, &p.y, 8 );
|
||||
memcpy( &bz, &p.z, 8 );
|
||||
#else
|
||||
uint32_t fx, fy, fz;
|
||||
memcpy( &fx, &p.x, 4 );
|
||||
memcpy( &fy, &p.y, 4 );
|
||||
memcpy( &fz, &p.z, 4 );
|
||||
uint64_t bx = fx, by = fy, bz = fz;
|
||||
#endif
|
||||
hash = ( hash ^ bx ) * B3_SNAP_FNV_PRIME;
|
||||
hash = ( hash ^ by ) * B3_SNAP_FNV_PRIME;
|
||||
hash = ( hash ^ bz ) * B3_SNAP_FNV_PRIME;
|
||||
return hash;
|
||||
}
|
||||
|
||||
typedef struct b3World b3World;
|
||||
|
||||
// Magic 'B3RC' in little-endian: bytes B(0x42) 3(0x33) R(0x52) C(0x43)
|
||||
#define B3_REC_MAGIC 0x43523342u
|
||||
|
||||
// Major recording version is bumped when writers change.
|
||||
// Major version 4 added b3ShapeDef::enableSpeculativeContact
|
||||
#define B3_REC_VERSION_MAJOR 4
|
||||
|
||||
// Minor tracks op-stream additions that keep the 48 byte header shape.
|
||||
// Minor version 3 added name cache.
|
||||
#define B3_REC_VERSION_MINOR 3
|
||||
|
||||
// File header, fixed 48 bytes, little-endian. Contains the registry locator so the player
|
||||
// can load geometry before replaying any ops.
|
||||
typedef struct b3RecHeader
|
||||
{
|
||||
uint32_t magic; // 'B3RC' = 0x43523342
|
||||
uint16_t versionMajor; // B3_REC_VERSION_MAJOR
|
||||
uint16_t versionMinor; // B3_REC_VERSION_MINOR
|
||||
uint8_t pointerWidth; // sizeof(void*), gates POD-def struct layout
|
||||
uint8_t bigEndian; // 0 on all supported targets
|
||||
uint8_t validationEnabled; // 1 if built with BOX3D_VALIDATE, diagnostic only
|
||||
uint8_t reserved;
|
||||
float lengthScale; // b3GetLengthUnitsPerMeter()
|
||||
uint32_t reserved2;
|
||||
uint32_t reserved3; // explicit pad so the 64-bit fields align with no implicit gap
|
||||
uint64_t snapshotSize; // bytes of snapshot blob after the header (0 in Phase 1)
|
||||
uint64_t registryOffset; // absolute offset to trailing registry block, backpatched at stop
|
||||
uint64_t registryByteCount; // size of the registry block
|
||||
} b3RecHeader;
|
||||
|
||||
_Static_assert( sizeof( b3RecHeader ) == 48, "recording header must be 48 bytes" );
|
||||
|
||||
// Growable append-only byte buffer. Doubles on demand. countOnly mode tallies size without
|
||||
// allocating, used to size a buffer cheaply before a second filling pass.
|
||||
typedef struct b3RecBuffer
|
||||
{
|
||||
uint8_t* data;
|
||||
int capacity;
|
||||
int size;
|
||||
bool countOnly;
|
||||
} b3RecBuffer;
|
||||
|
||||
// Geometry kinds for the trailing registry section
|
||||
typedef enum b3GeometryKind
|
||||
{
|
||||
b3_geometryHull,
|
||||
b3_geometryMesh,
|
||||
b3_geometryHeightField,
|
||||
b3_geometryCompound,
|
||||
} b3GeometryKind;
|
||||
|
||||
// One entry per unique geometry blob. id == index in the entries array.
|
||||
// hashNext chains entries that share a content hash so dedup stays exact under a hash collision,
|
||||
// which the keyframe registry depends on to never grow during capture. B3_NULL_INDEX ends the chain.
|
||||
typedef struct b3GeometryEntry
|
||||
{
|
||||
uint64_t contentHash;
|
||||
uint32_t id;
|
||||
b3GeometryKind kind;
|
||||
int byteCount;
|
||||
uint8_t* bytes;
|
||||
int hashNext;
|
||||
} b3GeometryEntry;
|
||||
|
||||
// Growable array of geometry entries. Ids are array indices, so the array is serialized in order.
|
||||
// dedupMap maps content hash to entry id for O(1) dedup; it is opaque here and owned by recording.c.
|
||||
typedef struct b3GeometryRegistry
|
||||
{
|
||||
b3GeometryEntry* entries;
|
||||
int count;
|
||||
int capacity;
|
||||
void* dedupMap;
|
||||
} b3GeometryRegistry;
|
||||
|
||||
// Limit the maximum query name length to make recording simpler. Query names longer than this
|
||||
// probably indicate a bug in user code.
|
||||
#define B3_MAX_QUERY_NAME_LENGTH 64
|
||||
|
||||
// Query tag from b3QueryFilter.
|
||||
// Stored once per key in the trailing block so a tagged query carries only the 8 byte key on the wire.
|
||||
// Shared by the recorder (accumulate) and the player (load).
|
||||
typedef struct b3RecTag
|
||||
{
|
||||
// hash of (id, queryName)
|
||||
uint64_t key;
|
||||
uint64_t id;
|
||||
char queryName[B3_MAX_QUERY_NAME_LENGTH + 1];
|
||||
} b3RecTag;
|
||||
|
||||
// User-owned recording buffer. The world appends into it while active; the host saves and
|
||||
// destroys it. Opaque across the public API.
|
||||
typedef struct b3Recording
|
||||
{
|
||||
b3RecBuffer buffer;
|
||||
int recordStart; // offset of the 3-byte size field for u24 backpatch
|
||||
b3Mutex* lock; // serializes record writes from concurrent threads
|
||||
b3GeometryRegistry registry;
|
||||
|
||||
// Interned query tags accumulated during capture, written to the tail of the registry block at stop.
|
||||
// tagMap maps a tag key to its index for O(1) dedup.
|
||||
b3RecTag* tags;
|
||||
int tagCount;
|
||||
int tagCapacity;
|
||||
void* tagMap;
|
||||
|
||||
// Union of world bounds over every recorded step, written at stop.
|
||||
b3AABB accumulatedBounds;
|
||||
bool haveBounds;
|
||||
} b3Recording;
|
||||
|
||||
// C type aliases per TAG, used in the X-macro codegen arg structs
|
||||
typedef bool b3RecCType_BOOL;
|
||||
typedef int32_t b3RecCType_I32;
|
||||
typedef uint8_t b3RecCType_U8;
|
||||
typedef uint16_t b3RecCType_U16;
|
||||
typedef uint32_t b3RecCType_U32;
|
||||
typedef uint64_t b3RecCType_U64;
|
||||
typedef float b3RecCType_F32;
|
||||
typedef double b3RecCType_F64;
|
||||
typedef b3Vec3 b3RecCType_VEC3;
|
||||
typedef b3Quat b3RecCType_QUAT;
|
||||
typedef b3Transform b3RecCType_TRANSFORM;
|
||||
typedef b3Pos b3RecCType_POSITION;
|
||||
typedef b3WorldTransform b3RecCType_WORLDXF;
|
||||
typedef b3Matrix3 b3RecCType_MATRIX3;
|
||||
typedef b3AABB b3RecCType_AABB;
|
||||
typedef b3Sphere b3RecCType_SPHERE;
|
||||
typedef b3Capsule b3RecCType_CAPSULE;
|
||||
typedef b3QueryFilter b3RecCType_QUERYFILTER;
|
||||
typedef b3ShapeProxy b3RecCType_SHAPEPROXY;
|
||||
// Geometry reference: a plain u32 id into the trailing registry
|
||||
typedef uint32_t b3RecCType_GEOMID;
|
||||
typedef b3Filter b3RecCType_FILTER;
|
||||
typedef b3SurfaceMaterial b3RecCType_MATERIAL;
|
||||
typedef b3MassData b3RecCType_MASSDATA;
|
||||
typedef b3MotionLocks b3RecCType_LOCKS;
|
||||
typedef const char* b3RecCType_STR;
|
||||
typedef b3WorldId b3RecCType_WORLDID;
|
||||
typedef b3BodyId b3RecCType_BODYID;
|
||||
typedef b3ShapeId b3RecCType_SHAPEID;
|
||||
typedef b3JointId b3RecCType_JOINTID;
|
||||
typedef b3BodyDef b3RecCType_BODYDEF;
|
||||
typedef b3ShapeDef b3RecCType_SHAPEDEF;
|
||||
typedef b3ExplosionDef b3RecCType_EXPLOSIONDEF;
|
||||
typedef b3ParallelJointDef b3RecCType_PARALLELJOINTDEF;
|
||||
typedef b3DistanceJointDef b3RecCType_DISTANCEJOINTDEF;
|
||||
typedef b3FilterJointDef b3RecCType_FILTERJOINTDEF;
|
||||
typedef b3MotorJointDef b3RecCType_MOTORJOINTDEF;
|
||||
typedef b3PrismaticJointDef b3RecCType_PRISMATICJOINTDEF;
|
||||
typedef b3RevoluteJointDef b3RecCType_REVOLUTEJOINTDEF;
|
||||
typedef b3SphericalJointDef b3RecCType_SPHERICALJOINTDEF;
|
||||
typedef b3WeldJointDef b3RecCType_WELDJOINTDEF;
|
||||
typedef b3WheelJointDef b3RecCType_WHEELJOINTDEF;
|
||||
|
||||
// Codegen pass 1a: arg structs. Generated here so call sites (body.c, shape.c, etc.) can see them.
|
||||
#define ARG( TAG, field ) b3RecCType_##TAG field;
|
||||
#define B3_REC_OP( op, Name, RET, ... ) \
|
||||
typedef struct \
|
||||
{ \
|
||||
__VA_ARGS__ \
|
||||
} b3RecArgs_##Name;
|
||||
#include "recording_ops.inl"
|
||||
#undef B3_REC_OP
|
||||
#undef ARG
|
||||
|
||||
// Opcode constants generated from the manifest, so call sites name an op instead of a raw byte and
|
||||
// can't drift from the manifest if an op is renumbered.
|
||||
enum
|
||||
{
|
||||
#define B3_REC_OP( op, Name, RET, ... ) b3_recOp##Name = ( op ),
|
||||
#include "recording_ops.inl"
|
||||
#undef B3_REC_OP
|
||||
};
|
||||
|
||||
// Low-level buffer helpers
|
||||
void b3RecBufAppend( b3RecBuffer* buf, const void* data, int size );
|
||||
void b3RecBufFree( b3RecBuffer* buf );
|
||||
|
||||
// Write primitives
|
||||
void b3RecW_U8( b3RecBuffer* buf, uint8_t v );
|
||||
void b3RecW_U16( b3RecBuffer* buf, uint16_t v );
|
||||
void b3RecW_U32( b3RecBuffer* buf, uint32_t v );
|
||||
void b3RecW_U64( b3RecBuffer* buf, uint64_t v );
|
||||
void b3RecW_I32( b3RecBuffer* buf, int32_t v );
|
||||
void b3RecW_F32( b3RecBuffer* buf, float v );
|
||||
void b3RecW_F64( b3RecBuffer* buf, double v );
|
||||
void b3RecW_BOOL( b3RecBuffer* buf, bool v );
|
||||
void b3RecW_VEC3( b3RecBuffer* buf, b3Vec3 v );
|
||||
void b3RecW_QUAT( b3RecBuffer* buf, b3Quat v );
|
||||
void b3RecW_TRANSFORM( b3RecBuffer* buf, b3Transform v );
|
||||
// World position: doubles in large-world mode, floats otherwise (wire-identical to VEC3 in float build)
|
||||
void b3RecW_POSITION( b3RecBuffer* buf, b3Pos v );
|
||||
void b3RecW_WORLDXF( b3RecBuffer* buf, b3WorldTransform v );
|
||||
void b3RecW_MATRIX3( b3RecBuffer* buf, b3Matrix3 v );
|
||||
void b3RecW_AABB( b3RecBuffer* buf, b3AABB v );
|
||||
void b3RecW_QUERYFILTER( b3RecBuffer* buf, b3QueryFilter v );
|
||||
void b3RecW_SHAPEPROXY( b3RecBuffer* buf, b3ShapeProxy v );
|
||||
void b3RecW_TREESTATS( b3RecBuffer* buf, b3TreeStats v );
|
||||
void b3RecW_RAYRESULT( b3RecBuffer* buf, b3RayResult v );
|
||||
void b3RecW_PLANERESULT( b3RecBuffer* buf, b3PlaneResult v );
|
||||
void b3RecW_WORLDID( b3RecBuffer* buf, b3WorldId v );
|
||||
void b3RecW_BODYID( b3RecBuffer* buf, b3BodyId v );
|
||||
void b3RecW_SHAPEID( b3RecBuffer* buf, b3ShapeId v );
|
||||
void b3RecW_JOINTID( b3RecBuffer* buf, b3JointId v );
|
||||
void b3RecW_SPHERE( b3RecBuffer* buf, b3Sphere v );
|
||||
void b3RecW_CAPSULE( b3RecBuffer* buf, b3Capsule v );
|
||||
void b3RecW_GEOMID( b3RecBuffer* buf, uint32_t v );
|
||||
void b3RecW_FILTER( b3RecBuffer* buf, b3Filter v );
|
||||
void b3RecW_MATERIAL( b3RecBuffer* buf, b3SurfaceMaterial v );
|
||||
void b3RecW_MASSDATA( b3RecBuffer* buf, b3MassData v );
|
||||
void b3RecW_LOCKS( b3RecBuffer* buf, b3MotionLocks v );
|
||||
void b3RecW_EXPLOSIONDEF( b3RecBuffer* buf, b3ExplosionDef v );
|
||||
void b3RecW_BODYDEF( b3RecBuffer* buf, b3BodyDef v );
|
||||
void b3RecW_SHAPEDEF( b3RecBuffer* buf, b3ShapeDef v );
|
||||
void b3RecW_PARALLELJOINTDEF( b3RecBuffer* buf, b3ParallelJointDef v );
|
||||
void b3RecW_DISTANCEJOINTDEF( b3RecBuffer* buf, b3DistanceJointDef v );
|
||||
void b3RecW_FILTERJOINTDEF( b3RecBuffer* buf, b3FilterJointDef v );
|
||||
void b3RecW_MOTORJOINTDEF( b3RecBuffer* buf, b3MotorJointDef v );
|
||||
void b3RecW_PRISMATICJOINTDEF( b3RecBuffer* buf, b3PrismaticJointDef v );
|
||||
void b3RecW_REVOLUTEJOINTDEF( b3RecBuffer* buf, b3RevoluteJointDef v );
|
||||
void b3RecW_SPHERICALJOINTDEF( b3RecBuffer* buf, b3SphericalJointDef v );
|
||||
void b3RecW_WELDJOINTDEF( b3RecBuffer* buf, b3WeldJointDef v );
|
||||
void b3RecW_WHEELJOINTDEF( b3RecBuffer* buf, b3WheelJointDef v );
|
||||
|
||||
// Record framing
|
||||
void b3RecBeginRecord( b3Recording* rec, uint8_t opcode );
|
||||
void b3RecEndRecord( b3Recording* rec );
|
||||
|
||||
// Per-op arg writers (no framing) and full writers (framing + args), generated from the manifest.
|
||||
#define B3_REC_OP( op, Name, RET, ... ) \
|
||||
void b3RecWriteArgs_##Name( b3Recording* rec, const b3RecArgs_##Name* a ); \
|
||||
void b3RecWrite_##Name( b3Recording* rec, const b3RecArgs_##Name* a );
|
||||
#include "recording_ops.inl"
|
||||
#undef B3_REC_OP
|
||||
|
||||
// Create ops: declare writers that also append the returned id inside the record.
|
||||
#define B3_REC_RETDECL_RET_NONE( Name )
|
||||
#define B3_REC_RETDECL_RET_BODYID( Name ) void b3RecWriteRet_##Name( b3Recording* rec, const b3RecArgs_##Name* a, b3BodyId id );
|
||||
#define B3_REC_RETDECL_RET_SHAPEID( Name ) void b3RecWriteRet_##Name( b3Recording* rec, const b3RecArgs_##Name* a, b3ShapeId id );
|
||||
#define B3_REC_RETDECL_RET_JOINTID( Name ) void b3RecWriteRet_##Name( b3Recording* rec, const b3RecArgs_##Name* a, b3JointId id );
|
||||
#define B3_REC_OP( op, Name, RET, ... ) B3_REC_RETDECL_##RET( Name )
|
||||
#include "recording_ops.inl"
|
||||
#undef B3_REC_OP
|
||||
#undef B3_REC_RETDECL_RET_NONE
|
||||
#undef B3_REC_RETDECL_RET_BODYID
|
||||
#undef B3_REC_RETDECL_RET_SHAPEID
|
||||
#undef B3_REC_RETDECL_RET_JOINTID
|
||||
|
||||
// Record a void op. The branch is free when recording is off.
|
||||
#define B3_REC( world, Name, ... ) \
|
||||
do \
|
||||
{ \
|
||||
if ( ( world )->recording != NULL ) \
|
||||
{ \
|
||||
b3RecArgs_##Name recArgs = { __VA_ARGS__ }; \
|
||||
b3RecWrite_##Name( ( world )->recording, &recArgs ); \
|
||||
} \
|
||||
} \
|
||||
while ( 0 )
|
||||
|
||||
// Record a create op and its returned id in one framed record. Place after the real create call.
|
||||
#define B3_REC_CREATE( world, Name, id, ... ) \
|
||||
do \
|
||||
{ \
|
||||
if ( ( world )->recording != NULL ) \
|
||||
{ \
|
||||
b3RecArgs_##Name recCreateArgs = { __VA_ARGS__ }; \
|
||||
b3RecWriteRet_##Name( ( world )->recording, &recCreateArgs, id ); \
|
||||
} \
|
||||
} \
|
||||
while ( 0 )
|
||||
|
||||
// Patch helpers for the query hit-count backfill
|
||||
int b3RecReserveU32( b3RecBuffer* buf );
|
||||
void b3RecPatchU32( b3RecBuffer* buf, int offset, uint32_t v );
|
||||
|
||||
// Commit a finished query record under the lock. The payload buffer stays owned by the caller.
|
||||
void b3RecCommitRecord( b3Recording* rec, uint8_t opcode, const uint8_t* payload, int payloadSize );
|
||||
|
||||
// Per-query writer context: holds the user fcn+ctx, the local payload buffer, and the hit counter
|
||||
typedef struct b3RecQueryWriter
|
||||
{
|
||||
union
|
||||
{
|
||||
b3OverlapResultFcn* overlapFcn;
|
||||
b3CastResultFcn* castFcn;
|
||||
b3PlaneResultFcn* planeFcn;
|
||||
b3MoverFilterFcn* moverFilterFcn;
|
||||
} userFcn;
|
||||
void* userContext;
|
||||
b3RecBuffer buf; // per-call local payload, heap-backed
|
||||
int countOffset; // offset of the reserved u32 hit-count slot
|
||||
uint32_t hitCount;
|
||||
uint64_t tagId; // caller query id, 0 = untagged. Emitted as a QueryTag before the record.
|
||||
const char* tagName; // caller query name, interned by id. NULL = none.
|
||||
} b3RecQueryWriter;
|
||||
|
||||
void b3RecQueryBegin( b3RecQueryWriter* w, void* context, uint64_t tagId, const char* tagName );
|
||||
void b3RecQueryCommit( b3Recording* rec, uint8_t opcode, b3RecQueryWriter* w );
|
||||
|
||||
// Recording trampolines: replace the user fcn so hits are captured before dispatch. The overlap
|
||||
// trampoline doubles for the mover filter, which has the same bool(shapeId, ctx) shape.
|
||||
bool b3RecOverlapTrampoline( b3ShapeId id, void* ctx );
|
||||
float b3RecCastTrampoline( b3ShapeId id, b3Pos point, b3Vec3 normal, float fraction, uint64_t userMaterialId, int triangleIndex,
|
||||
int childIndex, void* ctx );
|
||||
bool b3RecPlaneTrampoline( b3ShapeId id, const b3PlaneResult* planes, int planeCount, void* ctx );
|
||||
|
||||
// Geometry registry
|
||||
uint32_t b3InternGeometry( b3GeometryRegistry* reg, b3GeometryKind kind, uint64_t contentHash, uint8_t* bytes, int byteCount );
|
||||
// Append an entry unconditionally and return its id, which equals its array index. Unlike
|
||||
// b3InternGeometry it never deduplicates, so the keyframe seed can mirror slots 1:1 even when an
|
||||
// already-recorded file carries byte-identical duplicate slots (a hash collision wrote them apart).
|
||||
uint32_t b3AppendGeometry( b3GeometryRegistry* reg, b3GeometryKind kind, uint64_t contentHash, uint8_t* bytes, int byteCount );
|
||||
void b3FreeRegistry( b3GeometryRegistry* reg );
|
||||
void b3RecWriteRegistry( b3Recording* rec );
|
||||
|
||||
// Hash a query (id, name) pair into the stable key the viewer tracks the query by. Never returns 0,
|
||||
// so the key doubles as a tagged/untagged flag.
|
||||
uint64_t b3HashQueryTag( uint64_t id, const char* name );
|
||||
|
||||
// Record a key->(id, name) mapping once, deduped by key (a repeated key keeps its first id/name).
|
||||
void b3RecInternTag( b3Recording* rec, uint64_t key, uint64_t id, const char* name );
|
||||
|
||||
// Intern each large geometry kind and return a stable u32 id for use in create ops.
|
||||
// Caller does NOT free bytes; b3InternGeometry takes ownership (frees on duplicate).
|
||||
uint32_t b3RecInternHull( b3Recording* rec, const b3HullData* hull );
|
||||
uint32_t b3RecInternMesh( b3Recording* rec, const b3MeshData* mesh );
|
||||
uint32_t b3RecInternHeightField( b3Recording* rec, const b3HeightFieldData* hf );
|
||||
uint32_t b3RecInternCompound( b3Recording* rec, const b3CompoundData* compound );
|
||||
|
||||
uint64_t b3Hash64Blob( const uint8_t* bytes, int n );
|
||||
|
||||
// Lifecycle engine-side hooks
|
||||
void b3StartRecordingIntoBuffer( b3World* world, b3Recording* recording );
|
||||
void b3StopRecordingInternal( b3World* world );
|
||||
|
||||
// Fold one step's world bounds into the running union.
|
||||
void b3RecAccumulateBounds( b3Recording* rec, b3AABB bounds );
|
||||
|
||||
// Deterministic hash over all body transforms and velocities.
|
||||
// Called by both recorder and replayer to verify simulation reproduces exactly.
|
||||
uint64_t b3HashWorldState( b3World* world );
|
||||
232
vendor/box3d/src/src/recording_ops.inl
vendored
Normal file
232
vendor/box3d/src/src/recording_ops.inl
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// X-macro manifest for the recording system.
|
||||
// Include with B3_REC_OP and ARG defined. No commas between ARG tokens.
|
||||
//
|
||||
// B3_REC_OP( opcode, Name, RET, ARGS )
|
||||
// RET in { RET_NONE, RET_BODYID, RET_SHAPEID, RET_JOINTID }
|
||||
// ARGS = zero or more ARG( TAG, fieldName ) tokens, NO commas between them
|
||||
//
|
||||
// Opcode ranges:
|
||||
// 0x00-0x0F world lifecycle and config
|
||||
// 0x10-0x1F body create/destroy
|
||||
// 0x20-0x3F body mutators
|
||||
// 0x40-0x4F shape create/destroy
|
||||
// 0x50-0x6F shape mutators
|
||||
// 0x80 step
|
||||
// 0x90-0xE7 joints (create, generic, per-type)
|
||||
// 0xE8-0xEF spatial queries
|
||||
// 0xF0-0xFF markers
|
||||
|
||||
// Recordings are seeded with a world snapshot, not a CreateWorld op. DestroyWorld stays as the
|
||||
// end-of-session marker the viewer reads.
|
||||
B3_REC_OP( 0x01, DestroyWorld, RET_NONE, ARG( WORLDID, world ) )
|
||||
B3_REC_OP( 0x80, Step, RET_NONE, ARG( WORLDID, world ) ARG( F32, dt ) ARG( I32, subStepCount ) )
|
||||
|
||||
// World config. The world arg is informational; replay always targets its own world.
|
||||
B3_REC_OP( 0x02, WorldEnableSleeping, RET_NONE, ARG( WORLDID, world ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0x03, WorldEnableContinuous, RET_NONE, ARG( WORLDID, world ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0x04, WorldSetRestitutionThreshold, RET_NONE, ARG( WORLDID, world ) ARG( F32, value ) )
|
||||
B3_REC_OP( 0x05, WorldSetHitEventThreshold, RET_NONE, ARG( WORLDID, world ) ARG( F32, value ) )
|
||||
B3_REC_OP( 0x06, WorldSetGravity, RET_NONE, ARG( WORLDID, world ) ARG( VEC3, gravity ) )
|
||||
B3_REC_OP( 0x07, WorldExplode, RET_NONE, ARG( WORLDID, world ) ARG( EXPLOSIONDEF, def ) )
|
||||
B3_REC_OP( 0x08, WorldSetContactTuning, RET_NONE,
|
||||
ARG( WORLDID, world ) ARG( F32, hertz ) ARG( F32, dampingRatio ) ARG( F32, contactSpeed ) )
|
||||
B3_REC_OP( 0x09, WorldSetContactRecycleDistance, RET_NONE, ARG( WORLDID, world ) ARG( F32, recycleDistance ) )
|
||||
B3_REC_OP( 0x0A, WorldSetMaximumLinearSpeed, RET_NONE, ARG( WORLDID, world ) ARG( F32, maximumLinearSpeed ) )
|
||||
B3_REC_OP( 0x0B, WorldEnableWarmStarting, RET_NONE, ARG( WORLDID, world ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0x0C, WorldRebuildStaticTree, RET_NONE, ARG( WORLDID, world ) )
|
||||
B3_REC_OP( 0x0D, WorldEnableSpeculative, RET_NONE, ARG( WORLDID, world ) ARG( BOOL, flag ) )
|
||||
|
||||
// Body
|
||||
B3_REC_OP( 0x10, CreateBody, RET_BODYID, ARG( WORLDID, world ) ARG( BODYDEF, def ) )
|
||||
B3_REC_OP( 0x11, DestroyBody, RET_NONE, ARG( BODYID, body ) )
|
||||
B3_REC_OP( 0x20, BodySetTransform, RET_NONE, ARG( BODYID, body ) ARG( POSITION, position ) ARG( QUAT, rotation ) )
|
||||
B3_REC_OP( 0x21, BodySetLinearVelocity, RET_NONE, ARG( BODYID, body ) ARG( VEC3, v ) )
|
||||
B3_REC_OP( 0x22, BodySetType, RET_NONE, ARG( BODYID, body ) ARG( I32, type ) )
|
||||
B3_REC_OP( 0x23, BodySetName, RET_NONE, ARG( BODYID, body ) ARG( STR, name ) )
|
||||
B3_REC_OP( 0x24, BodySetAngularVelocity, RET_NONE, ARG( BODYID, body ) ARG( VEC3, w ) )
|
||||
B3_REC_OP( 0x25, BodySetTargetTransform, RET_NONE, ARG( BODYID, body ) ARG( WORLDXF, target ) ARG( F32, timeStep ) ARG( BOOL, wake ) )
|
||||
B3_REC_OP( 0x26, BodyApplyForce, RET_NONE, ARG( BODYID, body ) ARG( VEC3, force ) ARG( POSITION, point ) ARG( BOOL, wake ) )
|
||||
B3_REC_OP( 0x27, BodyApplyForceToCenter, RET_NONE, ARG( BODYID, body ) ARG( VEC3, force ) ARG( BOOL, wake ) )
|
||||
B3_REC_OP( 0x28, BodyApplyTorque, RET_NONE, ARG( BODYID, body ) ARG( VEC3, torque ) ARG( BOOL, wake ) )
|
||||
B3_REC_OP( 0x29, BodyApplyLinearImpulse, RET_NONE, ARG( BODYID, body ) ARG( VEC3, impulse ) ARG( POSITION, point ) ARG( BOOL, wake ) )
|
||||
B3_REC_OP( 0x2A, BodyApplyLinearImpulseToCenter, RET_NONE, ARG( BODYID, body ) ARG( VEC3, impulse ) ARG( BOOL, wake ) )
|
||||
B3_REC_OP( 0x2B, BodyApplyAngularImpulse, RET_NONE, ARG( BODYID, body ) ARG( VEC3, impulse ) ARG( BOOL, wake ) )
|
||||
B3_REC_OP( 0x2C, BodySetMassData, RET_NONE, ARG( BODYID, body ) ARG( MASSDATA, massData ) )
|
||||
B3_REC_OP( 0x2D, BodyApplyMassFromShapes, RET_NONE, ARG( BODYID, body ) )
|
||||
B3_REC_OP( 0x2E, BodySetLinearDamping, RET_NONE, ARG( BODYID, body ) ARG( F32, damping ) )
|
||||
B3_REC_OP( 0x2F, BodySetAngularDamping, RET_NONE, ARG( BODYID, body ) ARG( F32, damping ) )
|
||||
B3_REC_OP( 0x30, BodySetGravityScale, RET_NONE, ARG( BODYID, body ) ARG( F32, scale ) )
|
||||
B3_REC_OP( 0x31, BodySetAwake, RET_NONE, ARG( BODYID, body ) ARG( BOOL, awake ) )
|
||||
B3_REC_OP( 0x32, BodyEnableSleep, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0x33, BodySetSleepThreshold, RET_NONE, ARG( BODYID, body ) ARG( F32, threshold ) )
|
||||
B3_REC_OP( 0x34, BodyDisable, RET_NONE, ARG( BODYID, body ) )
|
||||
B3_REC_OP( 0x35, BodyEnable, RET_NONE, ARG( BODYID, body ) )
|
||||
B3_REC_OP( 0x36, BodySetMotionLocks, RET_NONE, ARG( BODYID, body ) ARG( LOCKS, locks ) )
|
||||
B3_REC_OP( 0x37, BodySetBullet, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0x38, BodyEnableContactRecycling, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0x39, BodyEnableHitEvents, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) )
|
||||
|
||||
// Shape create/destroy
|
||||
B3_REC_OP( 0x40, CreateSphereShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( SPHERE, sphere ) )
|
||||
B3_REC_OP( 0x41, CreateCapsuleShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( CAPSULE, capsule ) )
|
||||
B3_REC_OP( 0x42, CreateHullShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( GEOMID, geometryId ) )
|
||||
B3_REC_OP( 0x43, CreateMeshShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( GEOMID, geometryId ) ARG( VEC3, scale ) )
|
||||
B3_REC_OP( 0x44, CreateHeightFieldShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( GEOMID, geometryId ) )
|
||||
B3_REC_OP( 0x45, CreateCompoundShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( GEOMID, geometryId ) )
|
||||
B3_REC_OP( 0x46, DestroyShape, RET_NONE, ARG( SHAPEID, shape ) ARG( BOOL, updateBodyMass ) )
|
||||
|
||||
// Shape mutators
|
||||
B3_REC_OP( 0x50, ShapeSetDensity, RET_NONE, ARG( SHAPEID, shape ) ARG( F32, density ) ARG( BOOL, updateBodyMass ) )
|
||||
B3_REC_OP( 0x51, ShapeSetFriction, RET_NONE, ARG( SHAPEID, shape ) ARG( F32, friction ) )
|
||||
B3_REC_OP( 0x52, ShapeSetRestitution, RET_NONE, ARG( SHAPEID, shape ) ARG( F32, restitution ) )
|
||||
B3_REC_OP( 0x53, ShapeSetSurfaceMaterial, RET_NONE, ARG( SHAPEID, shape ) ARG( MATERIAL, material ) )
|
||||
B3_REC_OP( 0x54, ShapeSetFilter, RET_NONE, ARG( SHAPEID, shape ) ARG( FILTER, filter ) ARG( BOOL, invokeContacts ) )
|
||||
B3_REC_OP( 0x55, ShapeEnableSensorEvents, RET_NONE, ARG( SHAPEID, shape ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0x56, ShapeEnableContactEvents, RET_NONE, ARG( SHAPEID, shape ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0x57, ShapeEnablePreSolveEvents, RET_NONE, ARG( SHAPEID, shape ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0x58, ShapeEnableHitEvents, RET_NONE, ARG( SHAPEID, shape ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0x59, ShapeSetSphere, RET_NONE, ARG( SHAPEID, shape ) ARG( SPHERE, sphere ) )
|
||||
B3_REC_OP( 0x5A, ShapeSetCapsule, RET_NONE, ARG( SHAPEID, shape ) ARG( CAPSULE, capsule ) )
|
||||
B3_REC_OP( 0x5B, ShapeApplyWind, RET_NONE,
|
||||
ARG( SHAPEID, shape ) ARG( VEC3, wind ) ARG( F32, drag ) ARG( F32, lift ) ARG( F32, maxSpeed ) ARG( BOOL, wake ) )
|
||||
B3_REC_OP( 0x5C, ShapeSetName, RET_NONE, ARG( SHAPEID, shape ) ARG( STR, name ) )
|
||||
|
||||
// Joint create and destroy
|
||||
B3_REC_OP( 0x90, CreateParallelJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( PARALLELJOINTDEF, def ) )
|
||||
B3_REC_OP( 0x91, CreateDistanceJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( DISTANCEJOINTDEF, def ) )
|
||||
B3_REC_OP( 0x92, CreateFilterJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( FILTERJOINTDEF, def ) )
|
||||
B3_REC_OP( 0x93, CreateMotorJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( MOTORJOINTDEF, def ) )
|
||||
B3_REC_OP( 0x94, CreatePrismaticJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( PRISMATICJOINTDEF, def ) )
|
||||
B3_REC_OP( 0x95, CreateRevoluteJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( REVOLUTEJOINTDEF, def ) )
|
||||
B3_REC_OP( 0x96, CreateSphericalJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( SPHERICALJOINTDEF, def ) )
|
||||
B3_REC_OP( 0x97, CreateWeldJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( WELDJOINTDEF, def ) )
|
||||
B3_REC_OP( 0x98, CreateWheelJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( WHEELJOINTDEF, def ) )
|
||||
B3_REC_OP( 0x99, DestroyJoint, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, wakeAttached ) )
|
||||
|
||||
// Generic joint mutators
|
||||
B3_REC_OP( 0x9A, JointSetLocalFrameA, RET_NONE, ARG( JOINTID, joint ) ARG( TRANSFORM, localFrame ) )
|
||||
B3_REC_OP( 0x9B, JointSetLocalFrameB, RET_NONE, ARG( JOINTID, joint ) ARG( TRANSFORM, localFrame ) )
|
||||
B3_REC_OP( 0x9C, JointSetCollideConnected, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, shouldCollide ) )
|
||||
B3_REC_OP( 0x9D, JointWakeBodies, RET_NONE, ARG( JOINTID, joint ) )
|
||||
B3_REC_OP( 0x9E, JointSetConstraintTuning, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ARG( F32, dampingRatio ) )
|
||||
B3_REC_OP( 0x9F, JointSetForceThreshold, RET_NONE, ARG( JOINTID, joint ) ARG( F32, threshold ) )
|
||||
B3_REC_OP( 0xA0, JointSetTorqueThreshold, RET_NONE, ARG( JOINTID, joint ) ARG( F32, threshold ) )
|
||||
|
||||
// Parallel joint
|
||||
B3_REC_OP( 0xA1, ParallelJointSetSpringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) )
|
||||
B3_REC_OP( 0xA2, ParallelJointSetSpringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) )
|
||||
B3_REC_OP( 0xA3, ParallelJointSetMaxTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, maxTorque ) )
|
||||
|
||||
// Distance joint
|
||||
B3_REC_OP( 0xA4, DistanceJointSetLength, RET_NONE, ARG( JOINTID, joint ) ARG( F32, length ) )
|
||||
B3_REC_OP( 0xA5, DistanceJointEnableSpring, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableSpring ) )
|
||||
B3_REC_OP( 0xA6, DistanceJointSetSpringForceRange, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lowerForce ) ARG( F32, upperForce ) )
|
||||
B3_REC_OP( 0xA7, DistanceJointSetSpringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) )
|
||||
B3_REC_OP( 0xA8, DistanceJointSetSpringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) )
|
||||
B3_REC_OP( 0xA9, DistanceJointEnableLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableLimit ) )
|
||||
B3_REC_OP( 0xAA, DistanceJointSetLengthRange, RET_NONE, ARG( JOINTID, joint ) ARG( F32, minLength ) ARG( F32, maxLength ) )
|
||||
B3_REC_OP( 0xAB, DistanceJointEnableMotor, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableMotor ) )
|
||||
B3_REC_OP( 0xAC, DistanceJointSetMotorSpeed, RET_NONE, ARG( JOINTID, joint ) ARG( F32, motorSpeed ) )
|
||||
B3_REC_OP( 0xAD, DistanceJointSetMaxMotorForce, RET_NONE, ARG( JOINTID, joint ) ARG( F32, force ) )
|
||||
|
||||
// Motor joint
|
||||
B3_REC_OP( 0xAE, MotorJointSetLinearVelocity, RET_NONE, ARG( JOINTID, joint ) ARG( VEC3, velocity ) )
|
||||
B3_REC_OP( 0xAF, MotorJointSetAngularVelocity, RET_NONE, ARG( JOINTID, joint ) ARG( VEC3, velocity ) )
|
||||
B3_REC_OP( 0xB0, MotorJointSetMaxVelocityForce, RET_NONE, ARG( JOINTID, joint ) ARG( F32, maxForce ) )
|
||||
B3_REC_OP( 0xB1, MotorJointSetMaxVelocityTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, maxTorque ) )
|
||||
B3_REC_OP( 0xB2, MotorJointSetLinearHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) )
|
||||
B3_REC_OP( 0xB3, MotorJointSetLinearDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, damping ) )
|
||||
B3_REC_OP( 0xB4, MotorJointSetAngularHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) )
|
||||
B3_REC_OP( 0xB5, MotorJointSetAngularDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, damping ) )
|
||||
B3_REC_OP( 0xB6, MotorJointSetMaxSpringForce, RET_NONE, ARG( JOINTID, joint ) ARG( F32, maxForce ) )
|
||||
B3_REC_OP( 0xB7, MotorJointSetMaxSpringTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, maxTorque ) )
|
||||
|
||||
// Prismatic joint
|
||||
B3_REC_OP( 0xB8, PrismaticJointEnableSpring, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableSpring ) )
|
||||
B3_REC_OP( 0xB9, PrismaticJointSetSpringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) )
|
||||
B3_REC_OP( 0xBA, PrismaticJointSetSpringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) )
|
||||
B3_REC_OP( 0xBB, PrismaticJointSetTargetTranslation, RET_NONE, ARG( JOINTID, joint ) ARG( F32, translation ) )
|
||||
B3_REC_OP( 0xBC, PrismaticJointEnableLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableLimit ) )
|
||||
B3_REC_OP( 0xBD, PrismaticJointSetLimits, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lower ) ARG( F32, upper ) )
|
||||
B3_REC_OP( 0xBE, PrismaticJointEnableMotor, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableMotor ) )
|
||||
B3_REC_OP( 0xBF, PrismaticJointSetMotorSpeed, RET_NONE, ARG( JOINTID, joint ) ARG( F32, motorSpeed ) )
|
||||
B3_REC_OP( 0xC0, PrismaticJointSetMaxMotorForce, RET_NONE, ARG( JOINTID, joint ) ARG( F32, force ) )
|
||||
|
||||
// Revolute joint
|
||||
B3_REC_OP( 0xC1, RevoluteJointEnableSpring, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableSpring ) )
|
||||
B3_REC_OP( 0xC2, RevoluteJointSetSpringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) )
|
||||
B3_REC_OP( 0xC3, RevoluteJointSetSpringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) )
|
||||
B3_REC_OP( 0xC4, RevoluteJointSetTargetAngle, RET_NONE, ARG( JOINTID, joint ) ARG( F32, angle ) )
|
||||
B3_REC_OP( 0xC5, RevoluteJointEnableLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableLimit ) )
|
||||
B3_REC_OP( 0xC6, RevoluteJointSetLimits, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lower ) ARG( F32, upper ) )
|
||||
B3_REC_OP( 0xC7, RevoluteJointEnableMotor, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableMotor ) )
|
||||
B3_REC_OP( 0xC8, RevoluteJointSetMotorSpeed, RET_NONE, ARG( JOINTID, joint ) ARG( F32, motorSpeed ) )
|
||||
B3_REC_OP( 0xC9, RevoluteJointSetMaxMotorTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, torque ) )
|
||||
|
||||
// Spherical joint
|
||||
B3_REC_OP( 0xCA, SphericalJointEnableConeLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableLimit ) )
|
||||
B3_REC_OP( 0xCB, SphericalJointSetConeLimit, RET_NONE, ARG( JOINTID, joint ) ARG( F32, angleRadians ) )
|
||||
B3_REC_OP( 0xCC, SphericalJointEnableTwistLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableLimit ) )
|
||||
B3_REC_OP( 0xCD, SphericalJointSetTwistLimits, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lower ) ARG( F32, upper ) )
|
||||
B3_REC_OP( 0xCE, SphericalJointEnableSpring, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableSpring ) )
|
||||
B3_REC_OP( 0xCF, SphericalJointSetSpringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) )
|
||||
B3_REC_OP( 0xD0, SphericalJointSetSpringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) )
|
||||
B3_REC_OP( 0xD1, SphericalJointSetTargetRotation, RET_NONE, ARG( JOINTID, joint ) ARG( QUAT, targetRotation ) )
|
||||
B3_REC_OP( 0xD2, SphericalJointEnableMotor, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableMotor ) )
|
||||
B3_REC_OP( 0xD3, SphericalJointSetMotorVelocity, RET_NONE, ARG( JOINTID, joint ) ARG( VEC3, motorVelocity ) )
|
||||
B3_REC_OP( 0xD4, SphericalJointSetMaxMotorTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, torque ) )
|
||||
|
||||
// Weld joint
|
||||
B3_REC_OP( 0xD5, WeldJointSetLinearHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) )
|
||||
B3_REC_OP( 0xD6, WeldJointSetLinearDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) )
|
||||
B3_REC_OP( 0xD7, WeldJointSetAngularHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) )
|
||||
B3_REC_OP( 0xD8, WeldJointSetAngularDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) )
|
||||
|
||||
// Wheel joint
|
||||
B3_REC_OP( 0xD9, WheelJointEnableSuspension, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0xDA, WheelJointSetSuspensionHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) )
|
||||
B3_REC_OP( 0xDB, WheelJointSetSuspensionDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) )
|
||||
B3_REC_OP( 0xDC, WheelJointEnableSuspensionLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0xDD, WheelJointSetSuspensionLimits, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lower ) ARG( F32, upper ) )
|
||||
B3_REC_OP( 0xDE, WheelJointEnableSpinMotor, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0xDF, WheelJointSetSpinMotorSpeed, RET_NONE, ARG( JOINTID, joint ) ARG( F32, speed ) )
|
||||
|
||||
// Wheel joint continued, overflow past the 0xDF range.
|
||||
B3_REC_OP( 0xE0, WheelJointSetMaxSpinTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, torque ) )
|
||||
B3_REC_OP( 0xE1, WheelJointEnableSteering, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0xE2, WheelJointSetSteeringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) )
|
||||
B3_REC_OP( 0xE3, WheelJointSetSteeringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) )
|
||||
B3_REC_OP( 0xE4, WheelJointSetMaxSteeringTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, torque ) )
|
||||
B3_REC_OP( 0xE5, WheelJointEnableSteeringLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, flag ) )
|
||||
B3_REC_OP( 0xE6, WheelJointSetSteeringLimits, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lower ) ARG( F32, upper ) )
|
||||
B3_REC_OP( 0xE7, WheelJointSetTargetSteeringAngle, RET_NONE, ARG( JOINTID, joint ) ARG( F32, radians ) )
|
||||
|
||||
// Spatial queries. Inputs flow through the manifest (reader side). The hit tail and result are
|
||||
// hand-written in recording.c / recording_replay.c since they are variable length.
|
||||
B3_REC_OP( 0xE8, QueryOverlapAABB, RET_NONE, ARG( WORLDID, world ) ARG( AABB, aabb ) ARG( QUERYFILTER, filter ) )
|
||||
B3_REC_OP( 0xE9, QueryOverlapShape, RET_NONE,
|
||||
ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( SHAPEPROXY, proxy ) ARG( QUERYFILTER, filter ) )
|
||||
B3_REC_OP( 0xEA, QueryCastRay, RET_NONE,
|
||||
ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( VEC3, translation ) ARG( QUERYFILTER, filter ) )
|
||||
B3_REC_OP( 0xEB, QueryCastShape, RET_NONE,
|
||||
ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( SHAPEPROXY, proxy ) ARG( VEC3, translation )
|
||||
ARG( QUERYFILTER, filter ) )
|
||||
B3_REC_OP( 0xEC, QueryCastRayClosest, RET_NONE,
|
||||
ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( VEC3, translation ) ARG( QUERYFILTER, filter ) )
|
||||
B3_REC_OP( 0xED, QueryCastMover, RET_NONE,
|
||||
ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( CAPSULE, mover ) ARG( VEC3, translation ) ARG( QUERYFILTER, filter ) )
|
||||
B3_REC_OP( 0xEE, QueryCollideMover, RET_NONE,
|
||||
ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( CAPSULE, mover ) ARG( QUERYFILTER, filter ) )
|
||||
|
||||
// Identity key (hash of the caller id + label) for the query that immediately follows. Emitted only
|
||||
// for a tagged query. The id and label are interned in the trailing tag table, so only the 8 byte key
|
||||
// rides the stream.
|
||||
B3_REC_OP( 0xEF, QueryTag, RET_NONE, ARG( U64, key ) )
|
||||
|
||||
B3_REC_OP( 0xF1, StateHash, RET_NONE, ARG( WORLDID, world ) ARG( U64, hash ) )
|
||||
|
||||
// Accumulated world bounds over the whole recording, written once at stop.
|
||||
B3_REC_OP( 0xF2, RecordingBounds, RET_NONE, ARG( AABB, bounds ) )
|
||||
3634
vendor/box3d/src/src/recording_replay.c
vendored
Normal file
3634
vendor/box3d/src/src/recording_replay.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
258
vendor/box3d/src/src/recording_replay.h
vendored
Normal file
258
vendor/box3d/src/src/recording_replay.h
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "recording.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct b3RecPlayer b3RecPlayer;
|
||||
|
||||
// A single recorded callback hit, used both as reader scratch during a query replay and as the
|
||||
// per-frame draw store. For collide-mover, one hit is one plane, with planeCount and userReturnB
|
||||
// replicated across a shape's planes so the replay walker can re-group and compare per shape.
|
||||
typedef struct b3RecRecordedHit
|
||||
{
|
||||
b3ShapeId id;
|
||||
b3Pos point;
|
||||
b3Vec3 normal;
|
||||
float fraction;
|
||||
uint64_t userMaterialId;
|
||||
int triangleIndex;
|
||||
int childIndex;
|
||||
b3PlaneResult plane; // collide-mover: this plane
|
||||
int planeCount; // collide-mover: planes in this hit's shape group (replicated)
|
||||
float userReturnF; // cast queries
|
||||
bool userReturnB; // overlap / collide-mover (per shape, replicated)
|
||||
} b3RecRecordedHit;
|
||||
|
||||
// Recorded query kind, matching the public b3RecQueryType order.
|
||||
typedef enum b3RecQueryKind
|
||||
{
|
||||
B3_RECQ_OVERLAP_AABB,
|
||||
B3_RECQ_OVERLAP_SHAPE,
|
||||
B3_RECQ_CAST_RAY,
|
||||
B3_RECQ_CAST_SHAPE,
|
||||
B3_RECQ_CAST_RAY_CLOSEST,
|
||||
B3_RECQ_CAST_MOVER,
|
||||
B3_RECQ_COLLIDE_MOVER,
|
||||
} b3RecQueryKind;
|
||||
|
||||
// Per-frame draw record for one query call. Self-contained (no aliased pointers) so the player's
|
||||
// frameQueries array can grow with a plain memcpy. Geometry is origin relative except the overlap
|
||||
// AABB, which is world space in 3D.
|
||||
typedef struct b3RecDrawQuery
|
||||
{
|
||||
int kind;
|
||||
uint64_t key; // identity key (hash of caller id+name), 0 = untagged
|
||||
b3QueryFilter filter;
|
||||
b3AABB aabb; // world-space bounds of the query, swept for casts
|
||||
b3Vec3 proxyPoints[B3_MAX_SHAPE_CAST_POINTS]; // overlap/cast shape proxy, origin relative
|
||||
int proxyCount;
|
||||
float proxyRadius;
|
||||
b3Capsule mover; // cast/collide mover, origin relative
|
||||
b3Pos origin;
|
||||
b3Vec3 translation;
|
||||
float castFraction; // cast-mover result fraction
|
||||
b3RayResult rayResult; // cast-ray-closest result
|
||||
b3ShapeId shape;
|
||||
int hitStart; // first hit in the player's frameHits store
|
||||
int hitCount;
|
||||
} b3RecDrawQuery;
|
||||
|
||||
// One slot in the preloaded geometry registry. Loaded from the trailing block before any
|
||||
// ops run; live pointer built lazily on first shape create that references the id.
|
||||
typedef struct b3RegistrySlot
|
||||
{
|
||||
b3GeometryKind kind;
|
||||
int byteCount;
|
||||
uint8_t* bytes; // raw bytes from the file (always freed at teardown)
|
||||
void* live; // reconstructed live object, freed after b3DestroyWorld
|
||||
} b3RegistrySlot;
|
||||
|
||||
// This is used to simplify the scratch buffer lifetime. Names longer than this probably
|
||||
// indicate a bug.
|
||||
#define B3_MAX_NAME_LENGTH 256
|
||||
|
||||
// Reader state threaded through the replay loop and all dispatch functions
|
||||
typedef struct b3RecReader
|
||||
{
|
||||
const uint8_t* data;
|
||||
int size;
|
||||
int cursor;
|
||||
b3WorldId replayWorldId;
|
||||
bool ok; // false on read overrun or id mismatch, fatal stop
|
||||
bool diverged; // a StateHash failed, non-fatal
|
||||
|
||||
// Player that owns this reader, or NULL during a headless b3ValidateReplay. Body
|
||||
// create/destroy and the bounds record fold back into it for the outliner and camera framing.
|
||||
b3RecPlayer* owner;
|
||||
|
||||
// Scratch for per-triangle materials in shape defs (grown on demand, freed at teardown)
|
||||
b3SurfaceMaterial* matScratch;
|
||||
int matScratchCap;
|
||||
|
||||
// Scratch for string reads. Used by b3RecR_STR to pass names to b3BodyDef and b3ShapeDef.
|
||||
char stringBuffers[4][B3_MAX_NAME_LENGTH + 1];
|
||||
int nextString;
|
||||
|
||||
// Preloaded geometry registry
|
||||
b3RegistrySlot* slots;
|
||||
int slotCount;
|
||||
|
||||
// Preloaded query-tag table (key -> id, name), loaded with the registry. Resolves the caller id and
|
||||
// label for the viewer. tagMap maps a key to its tag index for O(1) lookup; opaque, owned by the player.
|
||||
b3RecTag* tags;
|
||||
int tagCount; // tags that loaded; a truncated tail loads fewer
|
||||
int tagCapacity; // tags allocated, used to free the array
|
||||
void* tagMap;
|
||||
|
||||
// Key from the QueryTag op preceding the next query, consumed by the next stash. 0 = untagged.
|
||||
uint64_t pendingQueryKey;
|
||||
|
||||
// Scratch for recorded query hits; grown on demand, freed with the player.
|
||||
b3RecRecordedHit* hits;
|
||||
int hitCap;
|
||||
|
||||
// Scratch for a shape-proxy point cloud read from the stream. b3ShapeProxy holds the points
|
||||
// behind a pointer, so a decoded proxy borrows this until the next proxy read or teardown.
|
||||
b3Vec3* proxyScratch;
|
||||
int proxyScratchCap;
|
||||
} b3RecReader;
|
||||
|
||||
// Stored snapshot for fast backward seek.
|
||||
typedef struct b3RecKeyframe
|
||||
{
|
||||
uint8_t* image; // serialized world image at the end of this frame
|
||||
int imageSize;
|
||||
int imageCapacity; // allocation size (may exceed imageSize)
|
||||
int frame; // frame index this restores to
|
||||
int cursor; // op-stream cursor for the frame AFTER this one
|
||||
int divergeFrame; // divergeFrame state at capture
|
||||
bool diverged; // rdr.diverged state at capture
|
||||
|
||||
// Outliner body list as it stood at this frame, restored verbatim so ordinals are stable.
|
||||
b3BodyId* bodyIds;
|
||||
int bodyIdCount;
|
||||
} b3RecKeyframe;
|
||||
|
||||
typedef struct b3RecPlayer
|
||||
{
|
||||
uint8_t* data; // owned copy of recording bytes
|
||||
int size;
|
||||
int headerEnd; // first byte of op stream (past header + snapshot blob)
|
||||
int registryEnd; // end of op stream = start of registry block (or size)
|
||||
float lengthScale;
|
||||
float previousLengthScale;
|
||||
int frame;
|
||||
int frameCount;
|
||||
float recordedDt;
|
||||
int recordedSubStepCount;
|
||||
int recordedWorkerCount; // worker count requested for the replay world
|
||||
b3AABB bounds; // accumulated world bounds, decoded from the trailing record
|
||||
bool atEnd;
|
||||
// Indicates all ops for the step have been consumed up to the world step op. The next sub-step
|
||||
// will clear this and perform the world step.
|
||||
bool atPreStep;
|
||||
int divergeFrame; // first frame that diverged, -1 until then
|
||||
|
||||
// Outliner body list, indexed by creation ordinal. Holes (null ids) mark destroyed bodies so
|
||||
// later ordinals never shift. Snapshotted into each keyframe and the frame-0 copy, not rebuilt
|
||||
// from the world, so a stored selection survives backward seeks.
|
||||
b3BodyId* bodyIds;
|
||||
int bodyIdCount;
|
||||
int bodyIdCap;
|
||||
b3BodyId* frame0BodyIds;
|
||||
int frame0BodyIdCount;
|
||||
|
||||
// Per-frame query store, reset at the top of each StepFrame and filled by the query dispatchers.
|
||||
// Drawn by b3RecPlayer_DrawFrameQueries and inspected via the public GetFrameQuery API.
|
||||
b3RecDrawQuery* frameQueries;
|
||||
int frameQueryCount;
|
||||
int frameQueryCap;
|
||||
b3RecRecordedHit* frameHits;
|
||||
int frameHitCount;
|
||||
int frameHitCap;
|
||||
|
||||
// Host debug-shape callbacks applied to every world the player creates. The 3D
|
||||
// sample renderer builds GPU meshes here, so a replay world without them draws
|
||||
// nothing. Set once via b3RecPlayer_SetDebugShapeCallbacks; persisted so a world
|
||||
// rebuilt under new callbacks keeps drawing.
|
||||
b3CreateDebugShapeCallback* createDebugShape;
|
||||
b3DestroyDebugShapeCallback* destroyDebugShape;
|
||||
void* debugShapeContext;
|
||||
|
||||
b3RecReader rdr;
|
||||
|
||||
// Frame-0 restore image, points into the owned data copy. Restart and backward seek
|
||||
// deserialize this in place so the replay world id stays stable.
|
||||
const uint8_t* frame0Image;
|
||||
int frame0Size;
|
||||
|
||||
// Keyframe ring
|
||||
b3RecKeyframe* keyframes;
|
||||
int keyframeCount;
|
||||
int keyframeCapacity;
|
||||
size_t keyframeBudget;
|
||||
size_t keyframeBytes;
|
||||
int keyframeMinInterval;
|
||||
int keyframeInterval;
|
||||
int lastKeyframeFrame;
|
||||
|
||||
// Pre-populated recording used by b3SerializeWorld during keyframe capture.
|
||||
// Its registry mirrors rdr.slots so geometry ids stay stable.
|
||||
b3Recording* keyframeRec;
|
||||
} b3RecPlayer;
|
||||
|
||||
// Read primitives
|
||||
uint8_t b3RecR_U8( b3RecReader* rdr );
|
||||
uint16_t b3RecR_U16( b3RecReader* rdr );
|
||||
uint32_t b3RecR_U24( b3RecReader* rdr );
|
||||
uint32_t b3RecR_U32( b3RecReader* rdr );
|
||||
uint64_t b3RecR_U64( b3RecReader* rdr );
|
||||
int32_t b3RecR_I32( b3RecReader* rdr );
|
||||
float b3RecR_F32( b3RecReader* rdr );
|
||||
double b3RecR_F64( b3RecReader* rdr );
|
||||
bool b3RecR_BOOL( b3RecReader* rdr );
|
||||
b3Vec3 b3RecR_VEC3( b3RecReader* rdr );
|
||||
b3Quat b3RecR_QUAT( b3RecReader* rdr );
|
||||
b3Transform b3RecR_TRANSFORM( b3RecReader* rdr );
|
||||
b3Pos b3RecR_POSITION( b3RecReader* rdr );
|
||||
b3WorldTransform b3RecR_WORLDXF( b3RecReader* rdr );
|
||||
b3Matrix3 b3RecR_MATRIX3( b3RecReader* rdr );
|
||||
b3AABB b3RecR_AABB( b3RecReader* rdr );
|
||||
b3WorldId b3RecR_WORLDID( b3RecReader* rdr );
|
||||
b3BodyId b3RecR_BODYID( b3RecReader* rdr );
|
||||
b3ShapeId b3RecR_SHAPEID( b3RecReader* rdr );
|
||||
b3JointId b3RecR_JOINTID( b3RecReader* rdr );
|
||||
b3Sphere b3RecR_SPHERE( b3RecReader* rdr );
|
||||
b3Capsule b3RecR_CAPSULE( b3RecReader* rdr );
|
||||
uint32_t b3RecR_GEOMID( b3RecReader* rdr );
|
||||
b3Filter b3RecR_FILTER( b3RecReader* rdr );
|
||||
b3SurfaceMaterial b3RecR_MATERIAL( b3RecReader* rdr );
|
||||
b3MassData b3RecR_MASSDATA( b3RecReader* rdr );
|
||||
b3MotionLocks b3RecR_LOCKS( b3RecReader* rdr );
|
||||
const char* b3RecR_STR( b3RecReader* rdr );
|
||||
b3ExplosionDef b3RecR_EXPLOSIONDEF( b3RecReader* rdr );
|
||||
b3BodyDef b3RecR_BODYDEF( b3RecReader* rdr );
|
||||
b3ShapeDef b3RecR_SHAPEDEF( b3RecReader* rdr );
|
||||
b3ParallelJointDef b3RecR_PARALLELJOINTDEF( b3RecReader* rdr );
|
||||
b3DistanceJointDef b3RecR_DISTANCEJOINTDEF( b3RecReader* rdr );
|
||||
b3FilterJointDef b3RecR_FILTERJOINTDEF( b3RecReader* rdr );
|
||||
b3MotorJointDef b3RecR_MOTORJOINTDEF( b3RecReader* rdr );
|
||||
b3PrismaticJointDef b3RecR_PRISMATICJOINTDEF( b3RecReader* rdr );
|
||||
b3RevoluteJointDef b3RecR_REVOLUTEJOINTDEF( b3RecReader* rdr );
|
||||
b3SphericalJointDef b3RecR_SPHERICALJOINTDEF( b3RecReader* rdr );
|
||||
b3WeldJointDef b3RecR_WELDJOINTDEF( b3RecReader* rdr );
|
||||
b3WheelJointDef b3RecR_WHEELJOINTDEF( b3RecReader* rdr );
|
||||
b3QueryFilter b3RecR_QUERYFILTER( b3RecReader* rdr );
|
||||
b3ShapeProxy b3RecR_SHAPEPROXY( b3RecReader* rdr );
|
||||
b3TreeStats b3RecR_TREESTATS( b3RecReader* rdr );
|
||||
b3RayResult b3RecR_RAYRESULT( b3RecReader* rdr );
|
||||
b3PlaneResult b3RecR_PLANERESULT( b3RecReader* rdr );
|
||||
|
||||
// Grow the reader's hit scratch to at least n entries, preserving contents. n is bounded by the
|
||||
// file size since every recorded hit consumes at least one byte.
|
||||
void b3RecEnsureHits( b3RecReader* rdr, int n );
|
||||
673
vendor/box3d/src/src/revolute_joint.c
vendored
Normal file
673
vendor/box3d/src/src/revolute_joint.c
vendored
Normal file
@@ -0,0 +1,673 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "body.h"
|
||||
#include "joint.h"
|
||||
#include "math_internal.h"
|
||||
#include "physics_world.h"
|
||||
#include "solver.h"
|
||||
#include "solver_set.h"
|
||||
#include "recording.h"
|
||||
|
||||
// needed for dll export
|
||||
#include "box3d/box3d.h"
|
||||
|
||||
// Point-to-point linear constraint
|
||||
// C = pB - pA
|
||||
// Cdot = vB - vA
|
||||
// = vB + cross(wB, rB) - vA - cross(wA, rA)
|
||||
// Cdot = J * v
|
||||
// J = [-E -skew(rA) E skew(rB) ]
|
||||
//
|
||||
// K = J * invM * JT
|
||||
// = [(1/mA + 1/mB) * E - skew(rA) * invIA * skew(rA) - skew(rB) * invIB * skew(rB)]
|
||||
|
||||
// Perpendicularity constraint
|
||||
// frameA = qA * localFrameA
|
||||
// frameB = qB * localFrameB
|
||||
// qRel = conj(frameA) * frameB
|
||||
// C = [qRel.x; qRel.y]
|
||||
// qRelDot = 0.5 * conj(frameA) * (wB - wA) * frameB
|
||||
// Cdot = [qRelDot.x, qRelDot.y]
|
||||
// Pulling out wB and wA
|
||||
// sr = qRel.s
|
||||
// vr = qRel.v
|
||||
// Jx = 0.5 * rotate(frameA, sr * ex + cross(vr, ex))
|
||||
// Jy = 0.5 * rotate(frameA, sr * ey + cross(vr, ey))
|
||||
|
||||
// Motor constraint
|
||||
// Cdot = wB - wA
|
||||
// J = [0 0 -E 0 0 E]
|
||||
// K = invIA + invIB
|
||||
|
||||
void b3RevoluteJoint_EnableLimit( b3JointId jointId, bool enableLimit )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, RevoluteJointEnableLimit, jointId, enableLimit );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
if ( enableLimit != base->revoluteJoint.enableLimit )
|
||||
{
|
||||
base->revoluteJoint.lowerImpulse = 0.0f;
|
||||
base->revoluteJoint.upperImpulse = 0.0f;
|
||||
}
|
||||
base->revoluteJoint.enableLimit = enableLimit;
|
||||
}
|
||||
|
||||
bool b3RevoluteJoint_IsLimitEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
return base->revoluteJoint.enableLimit;
|
||||
}
|
||||
|
||||
float b3RevoluteJoint_GetLowerLimit( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
return base->revoluteJoint.lowerAngle;
|
||||
}
|
||||
|
||||
float b3RevoluteJoint_GetUpperLimit( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
return base->revoluteJoint.upperAngle;
|
||||
}
|
||||
|
||||
void b3RevoluteJoint_SetLimits( b3JointId jointId, float lowerLimitRadians, float upperLimitRadians )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( lowerLimitRadians ) && b3IsValidFloat( upperLimitRadians ) );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, RevoluteJointSetLimits, jointId, lowerLimitRadians, upperLimitRadians );
|
||||
|
||||
float lowerAngle = b3MinFloat( lowerLimitRadians, upperLimitRadians );
|
||||
float upperAngle = b3MaxFloat( lowerLimitRadians, upperLimitRadians );
|
||||
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
base->revoluteJoint.lowerAngle = b3ClampFloat( lowerAngle, -0.99f * B3_PI, 0.99f * B3_PI );
|
||||
base->revoluteJoint.upperAngle = b3ClampFloat( upperAngle, -0.99f * B3_PI, 0.99f * B3_PI );
|
||||
}
|
||||
|
||||
float b3RevoluteJoint_GetAngle( b3JointId jointId )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA );
|
||||
b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB );
|
||||
|
||||
b3Quat quatA = b3MulQuat( transformA.q, base->localFrameA.q );
|
||||
b3Quat quatB = b3MulQuat( transformB.q, base->localFrameB.q );
|
||||
|
||||
if ( b3DotQuat( quatA, quatB ) < 0.0f )
|
||||
{
|
||||
// this keeps the twist angle in the range [-pi, pi]
|
||||
quatB = b3NegateQuat( quatB );
|
||||
}
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( quatA, quatB );
|
||||
|
||||
return b3GetTwistAngle( relQ );
|
||||
}
|
||||
|
||||
void b3RevoluteJoint_EnableSpring( b3JointId jointId, bool enableSpring )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, RevoluteJointEnableSpring, jointId, enableSpring );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
if ( enableSpring != base->revoluteJoint.enableSpring )
|
||||
{
|
||||
base->revoluteJoint.springImpulse = 0.0f;
|
||||
}
|
||||
base->revoluteJoint.enableSpring = enableSpring;
|
||||
}
|
||||
|
||||
bool b3RevoluteJoint_IsSpringEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
return base->revoluteJoint.enableSpring;
|
||||
}
|
||||
|
||||
void b3RevoluteJoint_SetTargetAngle( b3JointId jointId, float targetRadians )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( targetRadians ) && -B3_PI <= targetRadians && targetRadians <= B3_PI );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, RevoluteJointSetTargetAngle, jointId, targetRadians );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
base->revoluteJoint.targetAngle = targetRadians;
|
||||
}
|
||||
|
||||
float b3RevoluteJoint_GetTargetAngle( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
return base->revoluteJoint.targetAngle;
|
||||
}
|
||||
|
||||
void b3RevoluteJoint_SetSpringHertz( b3JointId jointId, float hertz )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, RevoluteJointSetSpringHertz, jointId, hertz );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
base->revoluteJoint.hertz = hertz;
|
||||
}
|
||||
|
||||
float b3RevoluteJoint_GetSpringHertz( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
return base->revoluteJoint.hertz;
|
||||
}
|
||||
|
||||
void b3RevoluteJoint_SetSpringDampingRatio( b3JointId jointId, float dampingRatio )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, RevoluteJointSetSpringDampingRatio, jointId, dampingRatio );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
base->revoluteJoint.dampingRatio = dampingRatio;
|
||||
}
|
||||
|
||||
float b3RevoluteJoint_GetSpringDampingRatio( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
return base->revoluteJoint.dampingRatio;
|
||||
}
|
||||
|
||||
void b3RevoluteJoint_EnableMotor( b3JointId jointId, bool enableMotor )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, RevoluteJointEnableMotor, jointId, enableMotor );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
if ( enableMotor != base->revoluteJoint.enableMotor )
|
||||
{
|
||||
base->revoluteJoint.motorImpulse = 0.0f;
|
||||
}
|
||||
base->revoluteJoint.enableMotor = enableMotor;
|
||||
}
|
||||
|
||||
bool b3RevoluteJoint_IsMotorEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
return base->revoluteJoint.enableMotor;
|
||||
}
|
||||
|
||||
void b3RevoluteJoint_SetMotorSpeed( b3JointId jointId, float motorSpeed )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( motorSpeed ) );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, RevoluteJointSetMotorSpeed, jointId, motorSpeed );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
base->revoluteJoint.motorSpeed = motorSpeed;
|
||||
}
|
||||
|
||||
float b3RevoluteJoint_GetMotorSpeed( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
return base->revoluteJoint.motorSpeed;
|
||||
}
|
||||
|
||||
void b3RevoluteJoint_SetMaxMotorTorque( b3JointId jointId, float maxForce )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( maxForce ) && maxForce >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, RevoluteJointSetMaxMotorTorque, jointId, maxForce );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
base->revoluteJoint.maxMotorTorque = maxForce;
|
||||
}
|
||||
|
||||
float b3RevoluteJoint_GetMaxMotorTorque( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
return base->revoluteJoint.maxMotorTorque;
|
||||
}
|
||||
|
||||
float b3RevoluteJoint_GetMotorTorque( b3JointId jointId )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint );
|
||||
return world->inv_h * base->revoluteJoint.motorImpulse;
|
||||
}
|
||||
|
||||
b3Vec3 b3GetRevoluteJointForce( b3World* world, b3JointSim* base )
|
||||
{
|
||||
b3Vec3 force = b3MulSV( world->inv_h, base->revoluteJoint.linearImpulse );
|
||||
return force;
|
||||
}
|
||||
|
||||
b3Vec3 b3GetRevoluteJointTorque( b3World* world, b3JointSim* base )
|
||||
{
|
||||
b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA );
|
||||
b3RevoluteJoint* joint = &base->revoluteJoint;
|
||||
b3Vec3 axis = b3RotateVector( base->localFrameA.q, b3Vec3_axisZ );
|
||||
axis = b3RotateVector( transformA.q, axis );
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( joint->frameA.q, joint->frameB.q );
|
||||
|
||||
// These are needed for warm starting
|
||||
joint->perpAxisX = b3MulSV(
|
||||
0.5f, b3RotateVector( joint->frameA.q, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) );
|
||||
joint->perpAxisY = b3MulSV(
|
||||
0.5f, b3RotateVector( joint->frameA.q, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) );
|
||||
|
||||
float axialImpulse = joint->springImpulse + joint->motorImpulse + joint->lowerImpulse - joint->upperImpulse;
|
||||
b3Vec3 angularImpulse =
|
||||
b3Add( b3MulSV( joint->perpImpulse.x, joint->perpAxisX ), b3MulSV( joint->perpImpulse.y, joint->perpAxisY ) );
|
||||
angularImpulse = b3MulAdd( angularImpulse, axialImpulse, joint->rotationAxisZ );
|
||||
|
||||
// todo add pivot torque
|
||||
b3Vec3 impulse = b3MulAdd( angularImpulse,
|
||||
joint->springImpulse + joint->motorImpulse + joint->lowerImpulse - joint->upperImpulse, axis );
|
||||
b3Vec3 torque = b3MulSV( world->inv_h, impulse );
|
||||
return torque;
|
||||
}
|
||||
|
||||
void b3PrepareRevoluteJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_revoluteJoint );
|
||||
|
||||
b3World* world = context->world;
|
||||
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB );
|
||||
|
||||
B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet );
|
||||
b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex );
|
||||
b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex );
|
||||
|
||||
int localIndexA = bodyA->localIndex;
|
||||
int localIndexB = bodyB->localIndex;
|
||||
|
||||
b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA );
|
||||
b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB );
|
||||
|
||||
base->invMassA = bodySimA->invMass;
|
||||
base->invMassB = bodySimB->invMass;
|
||||
base->invIA = bodySimA->invInertiaWorld;
|
||||
base->invIB = bodySimB->invInertiaWorld;
|
||||
|
||||
b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB );
|
||||
base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN;
|
||||
|
||||
b3RevoluteJoint* joint = &base->revoluteJoint;
|
||||
joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX;
|
||||
joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX;
|
||||
|
||||
// Compute joint anchor frames with world space rotation, relative to center of mass
|
||||
// Avoid round-off here as much as possible.
|
||||
// b3Vec3 pf = (xf.p - c) + rot(xf.q, f.p)
|
||||
// pf = xf.p - (xf.p + rot(xf.q, lc)) + rot(xf.q, f.p)
|
||||
// pf = rot(xf.q, f.p - lc)
|
||||
joint->frameA.q = b3MulQuat( bodySimA->transform.q, base->localFrameA.q );
|
||||
joint->frameA.p = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) );
|
||||
joint->frameB.q = b3MulQuat( bodySimB->transform.q, base->localFrameB.q );
|
||||
joint->frameB.p = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) );
|
||||
|
||||
joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center );
|
||||
|
||||
{
|
||||
// Rotation axis is the z-axis of body A.
|
||||
b3Vec3 rotationAxisZ = b3RotateVector( joint->frameA.q, b3Vec3_axisZ );
|
||||
float k = b3Dot( rotationAxisZ, b3MulMV( invInertiaSum, rotationAxisZ ) );
|
||||
joint->axialMass = k > 0.0f ? 1.0f / k : 0.0f;
|
||||
joint->rotationAxisZ = rotationAxisZ;
|
||||
}
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( joint->frameA.q, joint->frameB.q );
|
||||
|
||||
{
|
||||
// These are needed for warm starting
|
||||
joint->perpAxisX = b3MulSV(
|
||||
0.5f, b3RotateVector( joint->frameA.q, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) );
|
||||
joint->perpAxisY = b3MulSV(
|
||||
0.5f, b3RotateVector( joint->frameA.q, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) );
|
||||
}
|
||||
|
||||
joint->springSoftness = b3MakeSoft( joint->hertz, joint->dampingRatio, context->h );
|
||||
|
||||
if ( context->enableWarmStarting == false )
|
||||
{
|
||||
joint->linearImpulse = b3Vec3_zero;
|
||||
joint->perpImpulse = (b3Vec2){ 0.0f, 0.0f };
|
||||
joint->motorImpulse = 0.0f;
|
||||
joint->springImpulse = 0.0f;
|
||||
joint->lowerImpulse = 0.0f;
|
||||
joint->upperImpulse = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void b3WarmStartRevoluteJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_revoluteJoint );
|
||||
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3RevoluteJoint* joint = &base->revoluteJoint;
|
||||
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 vA = stateA->linearVelocity;
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 vB = stateB->linearVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p );
|
||||
|
||||
float axialImpulse = joint->springImpulse + joint->motorImpulse + joint->lowerImpulse - joint->upperImpulse;
|
||||
b3Vec3 angularImpulse =
|
||||
b3Add( b3MulSV( joint->perpImpulse.x, joint->perpAxisX ), b3MulSV( joint->perpImpulse.y, joint->perpAxisY ) );
|
||||
angularImpulse = b3MulAdd( angularImpulse, axialImpulse, joint->rotationAxisZ );
|
||||
|
||||
vA = b3MulSub( vA, mA, joint->linearImpulse );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Add( b3Cross( rA, joint->linearImpulse ), angularImpulse ) ) );
|
||||
|
||||
vB = b3MulAdd( vB, mB, joint->linearImpulse );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Add( b3Cross( rB, joint->linearImpulse ), angularImpulse ) ) );
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->linearVelocity = vA;
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->linearVelocity = vB;
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
|
||||
void b3SolveRevoluteJoint( b3JointSim* base, b3StepContext* context, bool useBias )
|
||||
{
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3RevoluteJoint* joint = &base->revoluteJoint;
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 vA = stateA->linearVelocity;
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 vB = stateB->linearVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
|
||||
bool fixedRotation = base->fixedRotation;
|
||||
b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q );
|
||||
b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q );
|
||||
|
||||
if ( b3DotQuat( quatA, quatB ) < 0.0f )
|
||||
{
|
||||
// this keeps the rotation angle in the range [-pi, pi]
|
||||
quatB = b3NegateQuat( quatB );
|
||||
}
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( quatA, quatB );
|
||||
|
||||
// Solve spring
|
||||
if ( joint->enableSpring && fixedRotation == false )
|
||||
{
|
||||
// Get the substep relative rotation
|
||||
float targetAngle = joint->targetAngle;
|
||||
float angle = b3GetTwistAngle( relQ );
|
||||
float c = angle - targetAngle;
|
||||
|
||||
float bias = joint->springSoftness.biasRate * c;
|
||||
float massScale = joint->springSoftness.massScale;
|
||||
float impulseScale = joint->springSoftness.impulseScale;
|
||||
float cdot = b3Dot( b3Sub( wB, wA ), joint->rotationAxisZ );
|
||||
|
||||
float deltaImpulse = -massScale * joint->axialMass * ( cdot + bias ) - impulseScale * joint->springImpulse;
|
||||
joint->springImpulse += deltaImpulse;
|
||||
|
||||
wA = b3MulSub( wA, deltaImpulse, b3MulMV( iA, joint->rotationAxisZ ) );
|
||||
wB = b3MulAdd( wB, deltaImpulse, b3MulMV( iB, joint->rotationAxisZ ) );
|
||||
}
|
||||
|
||||
if ( joint->enableMotor && fixedRotation == false )
|
||||
{
|
||||
float cdot = b3Dot( b3Sub( wB, wA ), joint->rotationAxisZ ) - joint->motorSpeed;
|
||||
|
||||
float deltaImpulse = -joint->axialMass * cdot;
|
||||
float newImpulse = joint->motorImpulse + deltaImpulse;
|
||||
float maxImpulse = joint->maxMotorTorque * context->h;
|
||||
newImpulse = b3ClampFloat( newImpulse, -maxImpulse, maxImpulse );
|
||||
deltaImpulse = newImpulse - joint->motorImpulse;
|
||||
joint->motorImpulse = newImpulse;
|
||||
|
||||
wA = b3MulSub( wA, deltaImpulse, b3MulMV( iA, joint->rotationAxisZ ) );
|
||||
wB = b3MulAdd( wB, deltaImpulse, b3MulMV( iB, joint->rotationAxisZ ) );
|
||||
}
|
||||
|
||||
if ( joint->enableLimit && fixedRotation == false )
|
||||
{
|
||||
float angle = b3GetTwistAngle( relQ );
|
||||
|
||||
// todo does an updated twist axis help?
|
||||
|
||||
b3Vec3 axis = joint->rotationAxisZ;
|
||||
|
||||
// Lower limit
|
||||
{
|
||||
float c = angle - joint->lowerAngle;
|
||||
float bias = 0.0f;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( c > 0.0f )
|
||||
{
|
||||
// speculation
|
||||
bias = c * context->inv_h;
|
||||
}
|
||||
else if ( useBias )
|
||||
{
|
||||
bias = base->constraintSoftness.biasRate * c;
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
float cdot = b3Dot( b3Sub( wB, wA ), axis );
|
||||
float oldImpulse = joint->lowerImpulse;
|
||||
float deltaImpulse = -massScale * joint->axialMass * ( cdot + bias ) - impulseScale * oldImpulse;
|
||||
joint->lowerImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f );
|
||||
deltaImpulse = joint->lowerImpulse - oldImpulse;
|
||||
|
||||
wA = b3MulSub( wA, deltaImpulse, b3MulMV( iA, axis ) );
|
||||
wB = b3MulAdd( wB, deltaImpulse, b3MulMV( iB, axis ) );
|
||||
}
|
||||
|
||||
// Upper limit
|
||||
{
|
||||
float c = joint->upperAngle - angle;
|
||||
float bias = 0.0f;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( c > 0.0f )
|
||||
{
|
||||
// speculation
|
||||
bias = c * context->inv_h;
|
||||
}
|
||||
else if ( useBias )
|
||||
{
|
||||
bias = base->constraintSoftness.biasRate * c;
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
// sign flipped on Cdot
|
||||
float cdot = b3Dot( b3Sub( wA, wB ), axis );
|
||||
float oldImpulse = joint->upperImpulse;
|
||||
float deltaImpulse = -massScale * joint->axialMass * ( cdot + bias ) - impulseScale * oldImpulse;
|
||||
joint->upperImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f );
|
||||
deltaImpulse = joint->upperImpulse - oldImpulse;
|
||||
|
||||
// sign flipped on applied impulse
|
||||
wA = b3MulAdd( wA, deltaImpulse, b3MulMV( iA, axis ) );
|
||||
wB = b3MulSub( wB, deltaImpulse, b3MulMV( iB, axis ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Collinearity constraint
|
||||
if ( fixedRotation == false )
|
||||
{
|
||||
b3Vec2 bias = { 0.0f, 0.0f };
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
|
||||
if ( useBias )
|
||||
{
|
||||
b3Vec2 c = { relQ.v.x, relQ.v.y };
|
||||
bias = b3MulSV2( base->constraintSoftness.biasRate, c );
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
// Collinearity constraint as 2-by-2
|
||||
b3Vec3 perpAxisX =
|
||||
b3MulSV( 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) );
|
||||
b3Vec3 perpAxisY =
|
||||
b3MulSV( 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) );
|
||||
joint->perpAxisX = perpAxisX;
|
||||
joint->perpAxisY = perpAxisY;
|
||||
|
||||
b3Matrix3 invInertiaSum = b3AddMM( iA, iB );
|
||||
float kxx = b3Dot( perpAxisX, b3MulMV( invInertiaSum, perpAxisX ) );
|
||||
float kyy = b3Dot( perpAxisY, b3MulMV( invInertiaSum, perpAxisY ) );
|
||||
float kxy = b3Dot( perpAxisX, b3MulMV( invInertiaSum, perpAxisY ) );
|
||||
|
||||
b3Matrix2 k = { { kxx, kxy }, { kxy, kyy } };
|
||||
|
||||
b3Vec3 wRel = b3Sub( wB, wA );
|
||||
b3Vec2 cdot = { b3Dot( wRel, perpAxisX ), b3Dot( wRel, perpAxisY ) };
|
||||
b3Vec2 oldImpulse = joint->perpImpulse;
|
||||
b3Vec2 sol = b3Solve2( k, b3Add2( cdot, bias ) );
|
||||
b3Vec2 deltaImpulse = b3Sub2( b3MulSV2( -massScale, sol ), b3MulSV2( impulseScale, oldImpulse ) );
|
||||
joint->perpImpulse = b3Add2( joint->perpImpulse, deltaImpulse );
|
||||
|
||||
b3Vec3 angularImpulse = b3Add( b3MulSV( deltaImpulse.x, perpAxisX ), b3MulSV( deltaImpulse.y, perpAxisY ) );
|
||||
wA = b3Sub( wA, b3MulMV( iA, angularImpulse ) );
|
||||
wB = b3Add( wB, b3MulMV( iB, angularImpulse ) );
|
||||
}
|
||||
|
||||
// Solve point-to-point constraint
|
||||
{
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p );
|
||||
|
||||
b3Vec3 cdot = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, rA ) );
|
||||
|
||||
b3Vec3 bias = b3Vec3_zero;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( useBias )
|
||||
{
|
||||
b3Vec3 dcA = stateA->deltaPosition;
|
||||
b3Vec3 dcB = stateB->deltaPosition;
|
||||
|
||||
b3Vec3 separation = b3Add( b3Add( b3Sub( dcB, dcA ), b3Sub( rB, rA ) ), joint->deltaCenter );
|
||||
|
||||
bias = b3MulSV( base->constraintSoftness.biasRate, separation );
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
//// K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)]
|
||||
b3Matrix3 sA = b3Skew( rA );
|
||||
b3Matrix3 sB = b3Skew( rB );
|
||||
b3Matrix3 kA = b3MulMM( sA, b3MulMM( base->invIA, sA ) );
|
||||
b3Matrix3 kB = b3MulMM( sB, b3MulMM( base->invIB, sB ) );
|
||||
b3Matrix3 k = b3NegateMat3( b3AddMM( kA, kB ) );
|
||||
k.cx.x += mA + mB;
|
||||
k.cy.y += mA + mB;
|
||||
k.cz.z += mA + mB;
|
||||
|
||||
b3Vec3 b = b3Solve3( k, b3Add( cdot, bias ) );
|
||||
|
||||
b3Vec3 impulse = b3Sub( b3MulSV( -massScale, b ), b3MulSV( impulseScale, joint->linearImpulse ) );
|
||||
joint->linearImpulse = b3Add( joint->linearImpulse, impulse );
|
||||
|
||||
vA = b3MulSub( vA, mA, impulse );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) );
|
||||
vB = b3MulAdd( vB, mB, impulse );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) );
|
||||
}
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->linearVelocity = vA;
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->linearVelocity = vB;
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
|
||||
void b3DrawRevoluteJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale )
|
||||
{
|
||||
b3WorldTransform frameA = b3MulWorldTransforms( transformA, base->localFrameA );
|
||||
|
||||
float length1 = 0.1f * scale;
|
||||
draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisX ) ) ), b3_colorRed,
|
||||
draw->context );
|
||||
draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisY ) ) ), b3_colorGreen,
|
||||
draw->context );
|
||||
draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisZ ) ) ), b3_colorBlue,
|
||||
draw->context );
|
||||
|
||||
b3WorldTransform frameB = b3MulWorldTransforms( transformB, base->localFrameB );
|
||||
|
||||
b3RevoluteJoint* joint = &base->revoluteJoint;
|
||||
enum { kSliceCount = 16 };
|
||||
|
||||
// Twist limit
|
||||
if ( joint->enableLimit )
|
||||
{
|
||||
b3Quat quatA = frameA.q;
|
||||
b3Quat quatB = frameB.q;
|
||||
|
||||
if ( b3DotQuat( quatA, quatB ) < 0.0f )
|
||||
{
|
||||
// this keeps the twist angle in the range [-pi, pi]
|
||||
quatB = b3NegateQuat( quatB );
|
||||
}
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( quatA, quatB );
|
||||
|
||||
const float wedgeRadius = 0.2f * scale;
|
||||
for ( int index = 0; index < kSliceCount; ++index )
|
||||
{
|
||||
float t1 = (float)( index + 0 ) / kSliceCount;
|
||||
float alpha1 = ( 1.0f - t1 ) * joint->lowerAngle + t1 * joint->upperAngle;
|
||||
float t2 = (float)( index + 1 ) / kSliceCount;
|
||||
float alpha2 = ( 1.0f - t2 ) * joint->lowerAngle + t2 * joint->upperAngle;
|
||||
|
||||
b3Vec3 vertex1 = { wedgeRadius * b3Cos( alpha1 ), wedgeRadius * b3Sin( alpha1 ), 0.0f };
|
||||
b3Vec3 vertex2 = { wedgeRadius * b3Cos( alpha2 ), wedgeRadius * b3Sin( alpha2 ), 0.0f };
|
||||
|
||||
if ( index == 0 )
|
||||
{
|
||||
draw->DrawSegmentFcn( frameA.p, b3TransformWorldPoint( frameA, vertex1 ), b3_colorCyan, draw->context );
|
||||
}
|
||||
|
||||
if ( index == kSliceCount - 1 )
|
||||
{
|
||||
draw->DrawSegmentFcn( b3TransformWorldPoint( frameA, vertex2 ), frameA.p, b3_colorCyan, draw->context );
|
||||
}
|
||||
draw->DrawSegmentFcn( b3TransformWorldPoint( frameA, vertex1 ), b3TransformWorldPoint( frameA, vertex2 ), b3_colorCyan,
|
||||
draw->context );
|
||||
}
|
||||
|
||||
float twistAngle = b3GetTwistAngle( relQ );
|
||||
b3Vec3 p2 = { wedgeRadius * b3Cos( twistAngle ), wedgeRadius * b3Sin( twistAngle ), 0.0f };
|
||||
draw->DrawSegmentFcn( frameA.p, b3TransformWorldPoint( frameA, p2 ), b3_colorYellow, draw->context );
|
||||
}
|
||||
}
|
||||
198
vendor/box3d/src/src/scheduler.c
vendored
Normal file
198
vendor/box3d/src/src/scheduler.c
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "platform.h"
|
||||
#include "core.h"
|
||||
#include "scheduler.h"
|
||||
|
||||
#include "box3d/base.h"
|
||||
#include "box3d/constants.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
enum b3SchedulerTaskStatus
|
||||
{
|
||||
b3_schedulerFree = 0,
|
||||
b3_schedulerPending = 1,
|
||||
b3_schedulerClaimed = 2,
|
||||
b3_schedulerComplete = 3,
|
||||
};
|
||||
|
||||
typedef struct b3SchedulerTask
|
||||
{
|
||||
b3TaskCallback* callback;
|
||||
void* taskContext;
|
||||
b3AtomicInt status;
|
||||
} b3SchedulerTask;
|
||||
|
||||
typedef struct b3SchedulerWorkerContext
|
||||
{
|
||||
struct b3Scheduler* scheduler;
|
||||
int threadIndex;
|
||||
} b3SchedulerWorkerContext;
|
||||
|
||||
typedef struct b3Scheduler
|
||||
{
|
||||
b3Thread* threads[B3_MAX_WORKERS];
|
||||
b3SchedulerWorkerContext workerContexts[B3_MAX_WORKERS];
|
||||
|
||||
// total workers including main thread
|
||||
int workerCount;
|
||||
|
||||
// threads created = workerCount - 1
|
||||
int threadCount;
|
||||
|
||||
b3SchedulerTask tasks[B3_MAX_TASKS];
|
||||
b3AtomicInt nextSlot;
|
||||
|
||||
b3Semaphore* taskSemaphore;
|
||||
b3AtomicInt shutdown;
|
||||
} b3Scheduler;
|
||||
|
||||
// Try to claim and execute one pending task.
|
||||
// Returns true if work was performed, false otherwise.
|
||||
static bool b3SchedulerExecuteOne( b3Scheduler* scheduler )
|
||||
{
|
||||
int taskCount = b3AtomicLoadInt( &scheduler->nextSlot );
|
||||
for ( int t = 0; t < taskCount; ++t )
|
||||
{
|
||||
b3SchedulerTask* task = scheduler->tasks + t;
|
||||
if ( b3AtomicLoadInt( &task->status ) != b3_schedulerPending )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( b3AtomicCompareExchangeInt( &task->status, b3_schedulerPending, b3_schedulerClaimed ) == false )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
task->callback( task->taskContext );
|
||||
|
||||
b3AtomicStoreInt( &task->status, b3_schedulerComplete );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Background worker thread entry point.
|
||||
static void b3SchedulerWorkerMain( void* context )
|
||||
{
|
||||
b3SchedulerWorkerContext* workerContext = context;
|
||||
b3Scheduler* scheduler = workerContext->scheduler;
|
||||
|
||||
while ( true )
|
||||
{
|
||||
b3WaitSemaphore( scheduler->taskSemaphore );
|
||||
|
||||
if ( b3AtomicLoadInt( &scheduler->shutdown ) != 0 )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Claim and execute all available work
|
||||
while ( b3SchedulerExecuteOne( scheduler ) )
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b3Scheduler* b3CreateScheduler( int workerCount )
|
||||
{
|
||||
B3_ASSERT( 0 < workerCount && workerCount <= B3_MAX_WORKERS );
|
||||
|
||||
b3Scheduler* scheduler = b3Alloc( sizeof( b3Scheduler ) );
|
||||
memset( scheduler, 0, sizeof( b3Scheduler ) );
|
||||
|
||||
scheduler->workerCount = workerCount;
|
||||
int threadCount = workerCount - 1;
|
||||
scheduler->threadCount = threadCount;
|
||||
scheduler->taskSemaphore = b3CreateSemaphore( 0 );
|
||||
b3AtomicStoreInt( &scheduler->shutdown, 0 );
|
||||
b3AtomicStoreInt( &scheduler->nextSlot, 0 );
|
||||
|
||||
// Background threads use indices 1..workerCount-1.
|
||||
// Main thread uses index 0.
|
||||
for ( int i = 0; i < threadCount; ++i )
|
||||
{
|
||||
scheduler->workerContexts[i].scheduler = scheduler;
|
||||
scheduler->workerContexts[i].threadIndex = i + 1;
|
||||
|
||||
char name[16];
|
||||
snprintf( name, sizeof( name ), "box2d_worker_%02d", i + 1 );
|
||||
scheduler->threads[i] = b3CreateThread( b3SchedulerWorkerMain, scheduler->workerContexts + i, name );
|
||||
}
|
||||
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
void b3DestroyScheduler( b3Scheduler* scheduler )
|
||||
{
|
||||
b3AtomicStoreInt( &scheduler->shutdown, 1 );
|
||||
|
||||
// Wake all background threads so they see the shutdown flag
|
||||
for ( int i = 0; i < scheduler->threadCount; ++i )
|
||||
{
|
||||
b3SignalSemaphore( scheduler->taskSemaphore );
|
||||
}
|
||||
|
||||
for ( int i = 0; i < scheduler->threadCount; ++i )
|
||||
{
|
||||
b3JoinThread( scheduler->threads[i] );
|
||||
scheduler->threads[i] = NULL;
|
||||
}
|
||||
|
||||
b3DestroySemaphore( scheduler->taskSemaphore );
|
||||
b3Free( scheduler, sizeof( b3Scheduler ) );
|
||||
}
|
||||
|
||||
void b3ResetScheduler( b3Scheduler* scheduler )
|
||||
{
|
||||
b3AtomicStoreInt( &scheduler->nextSlot, 0 );
|
||||
}
|
||||
|
||||
void* b3SchedulerEnqueueTask( b3TaskCallback* task, void* taskContext, void* userContext, const char* name )
|
||||
{
|
||||
B3_UNUSED( name );
|
||||
b3Scheduler* scheduler = userContext;
|
||||
|
||||
int slot = b3AtomicFetchAddInt( &scheduler->nextSlot, 1 );
|
||||
B3_ASSERT( slot < B3_MAX_TASKS );
|
||||
|
||||
b3SchedulerTask* schedulerTask = scheduler->tasks + slot;
|
||||
schedulerTask->callback = task;
|
||||
schedulerTask->taskContext = taskContext;
|
||||
|
||||
// Memory fence: status must be published after callback and context are written
|
||||
b3AtomicStoreInt( &schedulerTask->status, b3_schedulerPending );
|
||||
|
||||
// One wake per enqueue is enough: at most one worker picks up each task.
|
||||
b3SignalSemaphore( scheduler->taskSemaphore );
|
||||
|
||||
return schedulerTask;
|
||||
}
|
||||
|
||||
void b3SchedulerFinishTask( void* userTask, void* userContext )
|
||||
{
|
||||
if ( userTask == NULL )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
b3Scheduler* scheduler = userContext;
|
||||
b3SchedulerTask* waitTask = userTask;
|
||||
|
||||
// Main thread helps execute any available work while waiting for the
|
||||
// target task to complete. This keeps the main thread from idling when
|
||||
// background threads are busy on other tasks from the same phase.
|
||||
while ( b3AtomicLoadInt( &waitTask->status ) != b3_schedulerComplete )
|
||||
{
|
||||
if ( b3SchedulerExecuteOne( scheduler ) == false )
|
||||
{
|
||||
b3Yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
vendor/box3d/src/src/scheduler.h
vendored
Normal file
15
vendor/box3d/src/src/scheduler.h
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef void b3TaskCallback( void* taskContext );
|
||||
typedef struct b3Scheduler b3Scheduler;
|
||||
|
||||
b3Scheduler* b3CreateScheduler( int workerCount );
|
||||
void b3DestroyScheduler( b3Scheduler* scheduler );
|
||||
void b3ResetScheduler( b3Scheduler* scheduler );
|
||||
|
||||
// See b3EnqueueTaskCallback and b3FinishTaskCallback
|
||||
void* b3SchedulerEnqueueTask( b3TaskCallback* task, void* taskContext, void* userContext, const char* name );
|
||||
void b3SchedulerFinishTask( void* userTask, void* userContext );
|
||||
452
vendor/box3d/src/src/sensor.c
vendored
Normal file
452
vendor/box3d/src/src/sensor.c
vendored
Normal file
@@ -0,0 +1,452 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "sensor.h"
|
||||
|
||||
#include "body.h"
|
||||
#include "contact.h"
|
||||
#include "ctz.h"
|
||||
#include "physics_world.h"
|
||||
#include "shape.h"
|
||||
|
||||
#include "box3d/collision.h"
|
||||
|
||||
// for qsort
|
||||
#include "parallel_for.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct b3SensorQueryContext
|
||||
{
|
||||
b3World* world;
|
||||
b3SensorTaskContext* taskContext;
|
||||
b3Sensor* sensor;
|
||||
b3Shape* sensorShape;
|
||||
b3Transform transform;
|
||||
} b3SensorQueryContext;
|
||||
|
||||
static bool b3OverlapSensor( b3Shape* sensorShape, b3Transform sensorTransform, b3Shape* visitorShape,
|
||||
b3Transform visitorTransform )
|
||||
{
|
||||
b3ShapeType type = sensorShape->type;
|
||||
|
||||
b3ShapeProxy proxy = b3MakeShapeProxy( visitorShape );
|
||||
|
||||
// Get the visitor shape in the frame of the sensor
|
||||
b3Transform relativeTransform = b3InvMulTransforms( sensorTransform, visitorTransform );
|
||||
|
||||
b3Vec3 localPoints[B3_MAX_SHAPE_CAST_POINTS];
|
||||
b3ShapeProxy localProxy;
|
||||
|
||||
localProxy.count = b3MinInt( proxy.count, B3_MAX_SHAPE_CAST_POINTS );
|
||||
for ( int i = 0; i < localProxy.count; ++i )
|
||||
{
|
||||
localPoints[i] = b3TransformPoint( relativeTransform, proxy.points[i] );
|
||||
}
|
||||
|
||||
localProxy.points = localPoints;
|
||||
localProxy.radius = proxy.radius;
|
||||
|
||||
switch ( type )
|
||||
{
|
||||
case b3_capsuleShape:
|
||||
return b3OverlapCapsule( &sensorShape->capsule, b3Transform_identity, &localProxy );
|
||||
|
||||
case b3_compoundShape:
|
||||
return b3OverlapCompound( sensorShape->compound, b3Transform_identity, &localProxy );
|
||||
|
||||
case b3_heightShape:
|
||||
return b3OverlapHeightField( sensorShape->heightField, b3Transform_identity, &localProxy );
|
||||
|
||||
case b3_hullShape:
|
||||
return b3OverlapHull( sensorShape->hull, b3Transform_identity, &localProxy );
|
||||
|
||||
case b3_meshShape:
|
||||
return b3OverlapMesh( &sensorShape->mesh, b3Transform_identity, &localProxy );
|
||||
|
||||
case b3_sphereShape:
|
||||
return b3OverlapSphere( &sensorShape->sphere, b3Transform_identity, &localProxy );
|
||||
|
||||
default:
|
||||
B3_ASSERT( false );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Sensor shapes need to
|
||||
// - detect begin and end overlap events
|
||||
// - events must be reported in deterministic order
|
||||
// - maintain an active list of overlaps for query
|
||||
|
||||
// Assumption
|
||||
// - sensors don't detect shapes on the same body
|
||||
|
||||
// Algorithm
|
||||
// Query all sensors for overlaps
|
||||
// Check against previous overlaps
|
||||
|
||||
// Data structures
|
||||
// Each sensor has an double buffered array of overlaps
|
||||
// These overlaps use a shape reference with index and generation
|
||||
|
||||
static bool b3SensorQueryCallback( int proxyId, uint64_t userData, void* context )
|
||||
{
|
||||
B3_UNUSED( proxyId );
|
||||
|
||||
int shapeId = (int)userData;
|
||||
|
||||
b3SensorQueryContext* queryContext = (b3SensorQueryContext*)context;
|
||||
b3Shape* sensorShape = queryContext->sensorShape;
|
||||
int sensorShapeId = sensorShape->id;
|
||||
|
||||
if ( shapeId == sensorShapeId )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
b3World* world = queryContext->world;
|
||||
b3Shape* otherShape = b3Array_Get( world->shapes, shapeId );
|
||||
|
||||
// Mesh vs mesh is not supported
|
||||
if ( ( otherShape->type == b3_meshShape || otherShape->type == b3_heightShape ) &&
|
||||
( sensorShape->type == b3_meshShape || sensorShape->type == b3_heightShape ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Are sensor events enabled on the other shape?
|
||||
if ( ( otherShape->flags & b3_enableSensorEvents ) == 0 )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip shapes on the same body
|
||||
if ( otherShape->bodyId == sensorShape->bodyId )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check filter
|
||||
if ( b3ShouldShapesCollide( sensorShape->filter, otherShape->filter ) == false )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Custom user filter
|
||||
if ( ( sensorShape->flags & b3_enableCustomFiltering ) || ( otherShape->flags & b3_enableCustomFiltering ) )
|
||||
{
|
||||
b3CustomFilterFcn* customFilterFcn = queryContext->world->customFilterFcn;
|
||||
if ( customFilterFcn != NULL )
|
||||
{
|
||||
b3ShapeId idA = { sensorShapeId + 1, world->worldId, sensorShape->generation };
|
||||
b3ShapeId idB = { shapeId + 1, world->worldId, otherShape->generation };
|
||||
bool shouldCollide = customFilterFcn( idA, idB, queryContext->world->customFilterContext );
|
||||
if ( shouldCollide == false )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b3Transform otherTransform = b3ToRelativeTransform( b3GetBodyTransform( world, otherShape->bodyId ), b3Pos_zero );
|
||||
|
||||
bool overlap = b3OverlapSensor( sensorShape, queryContext->transform, otherShape, otherTransform );
|
||||
if ( overlap == false )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Record the overlap
|
||||
b3Sensor* sensor = queryContext->sensor;
|
||||
b3Visitor* shapeRef = b3Array_Emplace( sensor->overlaps2 );
|
||||
shapeRef->shapeId = shapeId;
|
||||
shapeRef->generation = otherShape->generation;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int b3CompareVisitors( const void* a, const void* b )
|
||||
{
|
||||
const b3Visitor* sa = (const b3Visitor*)a;
|
||||
const b3Visitor* sb = (const b3Visitor*)b;
|
||||
|
||||
if ( sa->shapeId < sb->shapeId )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void b3SensorTask( int startIndex, int endIndex, int workerIndex, void* context )
|
||||
{
|
||||
b3TracyCZoneNC( sensor_task, "Overlap", b3_colorBrown, true );
|
||||
|
||||
b3World* world = (b3World*)context;
|
||||
B3_ASSERT( (int)workerIndex < world->workerCount );
|
||||
b3SensorTaskContext* taskContext = world->sensorTaskContexts.data + workerIndex;
|
||||
|
||||
B3_ASSERT( startIndex < endIndex );
|
||||
|
||||
b3DynamicTree* trees = world->broadPhase.trees;
|
||||
for ( int sensorIndex = startIndex; sensorIndex < endIndex; ++sensorIndex )
|
||||
{
|
||||
b3Sensor* sensor = b3Array_Get( world->sensors, sensorIndex );
|
||||
b3Shape* sensorShape = b3Array_Get( world->shapes, sensor->shapeId );
|
||||
|
||||
// Swap overlap arrays
|
||||
b3Array( b3Visitor ) temp = sensor->overlaps1;
|
||||
sensor->overlaps1 = sensor->overlaps2;
|
||||
sensor->overlaps2 = temp;
|
||||
b3Array_Clear( sensor->overlaps2 );
|
||||
|
||||
// Append sensor hits
|
||||
b3Array_Append( sensor->overlaps2, sensor->hits.data, sensor->hits.count );
|
||||
|
||||
// Clear the hits
|
||||
b3Array_Clear( sensor->hits );
|
||||
|
||||
b3Body* body = b3Array_Get( world->bodies, sensorShape->bodyId );
|
||||
if ( body->setIndex == b3_disabledSet || ( sensorShape->flags & b3_enableSensorEvents ) == 0 )
|
||||
{
|
||||
if ( sensor->overlaps1.count != 0 )
|
||||
{
|
||||
// This sensor is dropping all overlaps because it has been disabled.
|
||||
b3SetBit( &taskContext->eventBits, sensorIndex );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
b3Transform transform = b3ToRelativeTransform( b3GetBodyTransformQuick( world, body ), b3Pos_zero );
|
||||
|
||||
b3SensorQueryContext queryContext = {
|
||||
.world = world,
|
||||
.taskContext = taskContext,
|
||||
.sensor = sensor,
|
||||
.sensorShape = sensorShape,
|
||||
.transform = transform,
|
||||
};
|
||||
|
||||
B3_ASSERT( sensorShape->sensorIndex == sensorIndex );
|
||||
b3AABB queryBounds = sensorShape->aabb;
|
||||
|
||||
// Query all trees
|
||||
b3DynamicTree_Query( trees + 0, queryBounds, sensorShape->filter.maskBits, false, b3SensorQueryCallback, &queryContext );
|
||||
b3DynamicTree_Query( trees + 1, queryBounds, sensorShape->filter.maskBits, false, b3SensorQueryCallback, &queryContext );
|
||||
b3DynamicTree_Query( trees + 2, queryBounds, sensorShape->filter.maskBits, false, b3SensorQueryCallback, &queryContext );
|
||||
|
||||
// Sort the overlaps to enable finding begin and end events.
|
||||
qsort( sensor->overlaps2.data, sensor->overlaps2.count, sizeof( b3Visitor ), b3CompareVisitors );
|
||||
|
||||
// Remove duplicates from overlaps2 (sorted). Duplicates are possible due to the hit events appended earlier.
|
||||
int uniqueCount = 0;
|
||||
int overlapCount = sensor->overlaps2.count;
|
||||
b3Visitor* overlapData = sensor->overlaps2.data;
|
||||
for ( int i = 0; i < overlapCount; ++i )
|
||||
{
|
||||
if ( uniqueCount == 0 || overlapData[i].shapeId != overlapData[uniqueCount - 1].shapeId )
|
||||
{
|
||||
overlapData[uniqueCount] = overlapData[i];
|
||||
uniqueCount += 1;
|
||||
}
|
||||
}
|
||||
sensor->overlaps2.count = uniqueCount;
|
||||
|
||||
int count1 = sensor->overlaps1.count;
|
||||
int count2 = sensor->overlaps2.count;
|
||||
if ( count1 != count2 )
|
||||
{
|
||||
// something changed
|
||||
b3SetBit( &taskContext->eventBits, sensorIndex );
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( int i = 0; i < count1; ++i )
|
||||
{
|
||||
b3Visitor* s1 = sensor->overlaps1.data + i;
|
||||
b3Visitor* s2 = sensor->overlaps2.data + i;
|
||||
|
||||
if ( s1->shapeId != s2->shapeId || s1->generation != s2->generation )
|
||||
{
|
||||
// something changed
|
||||
b3SetBit( &taskContext->eventBits, sensorIndex );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b3TracyCZoneEnd( sensor_task );
|
||||
}
|
||||
|
||||
void b3OverlapSensors( b3World* world )
|
||||
{
|
||||
int sensorCount = world->sensors.count;
|
||||
if ( sensorCount == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
B3_ASSERT( world->workerCount > 0 );
|
||||
|
||||
b3TracyCZoneNC( overlap_sensors, "Sensors", b3_colorMediumPurple, true );
|
||||
|
||||
for ( int i = 0; i < world->workerCount; ++i )
|
||||
{
|
||||
b3SetBitCountAndClear( &world->sensorTaskContexts.data[i].eventBits, sensorCount );
|
||||
}
|
||||
|
||||
// Parallel-for sensors overlaps
|
||||
int minRange = 16;
|
||||
b3ParallelFor( world, b3SensorTask, sensorCount, minRange, world, "sensors" );
|
||||
|
||||
b3TracyCZoneNC( sensor_state, "Events", b3_colorLightSlateGray, true );
|
||||
|
||||
b3BitSet* bitSet = &world->sensorTaskContexts.data[0].eventBits;
|
||||
for ( int i = 1; i < world->workerCount; ++i )
|
||||
{
|
||||
b3InPlaceUnion( bitSet, &world->sensorTaskContexts.data[i].eventBits );
|
||||
}
|
||||
|
||||
// Iterate sensors bits and publish events
|
||||
// Process sensor state changes. Iterate over set bits
|
||||
uint64_t* bits = bitSet->bits;
|
||||
uint32_t blockCount = bitSet->blockCount;
|
||||
|
||||
for ( uint32_t k = 0; k < blockCount; ++k )
|
||||
{
|
||||
uint64_t word = bits[k];
|
||||
while ( word != 0 )
|
||||
{
|
||||
uint32_t ctz = b3CTZ64( word );
|
||||
int sensorIndex = (int)( 64 * k + ctz );
|
||||
|
||||
b3Sensor* sensor = b3Array_Get( world->sensors, sensorIndex );
|
||||
b3Shape* sensorShape = b3Array_Get( world->shapes, sensor->shapeId );
|
||||
b3ShapeId sensorId = { sensor->shapeId + 1, world->worldId, sensorShape->generation };
|
||||
|
||||
int count1 = sensor->overlaps1.count;
|
||||
int count2 = sensor->overlaps2.count;
|
||||
const b3Visitor* refs1 = sensor->overlaps1.data;
|
||||
const b3Visitor* refs2 = sensor->overlaps2.data;
|
||||
|
||||
// overlaps1 can have overlaps that end
|
||||
// overlaps2 can have overlaps that begin
|
||||
int index1 = 0, index2 = 0;
|
||||
while ( index1 < count1 && index2 < count2 )
|
||||
{
|
||||
const b3Visitor* r1 = refs1 + index1;
|
||||
const b3Visitor* r2 = refs2 + index2;
|
||||
if ( r1->shapeId == r2->shapeId )
|
||||
{
|
||||
if ( r1->generation < r2->generation )
|
||||
{
|
||||
// end
|
||||
b3ShapeId visitorId = { r1->shapeId + 1, world->worldId, r1->generation };
|
||||
b3SensorEndTouchEvent event = {
|
||||
.sensorShapeId = sensorId,
|
||||
.visitorShapeId = visitorId,
|
||||
};
|
||||
b3Array_Push( world->sensorEndEvents[world->endEventArrayIndex], event );
|
||||
index1 += 1;
|
||||
}
|
||||
else if ( r1->generation > r2->generation )
|
||||
{
|
||||
// begin
|
||||
b3ShapeId visitorId = { r2->shapeId + 1, world->worldId, r2->generation };
|
||||
b3SensorBeginTouchEvent event = { sensorId, visitorId };
|
||||
b3Array_Push( world->sensorBeginEvents, event );
|
||||
index2 += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// persisted
|
||||
index1 += 1;
|
||||
index2 += 1;
|
||||
}
|
||||
}
|
||||
else if ( r1->shapeId < r2->shapeId )
|
||||
{
|
||||
// end
|
||||
b3ShapeId visitorId = { r1->shapeId + 1, world->worldId, r1->generation };
|
||||
b3SensorEndTouchEvent event = { sensorId, visitorId };
|
||||
b3Array_Push( world->sensorEndEvents[world->endEventArrayIndex], event );
|
||||
index1 += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// begin
|
||||
b3ShapeId visitorId = { r2->shapeId + 1, world->worldId, r2->generation };
|
||||
b3SensorBeginTouchEvent event = { sensorId, visitorId };
|
||||
b3Array_Push( world->sensorBeginEvents, event );
|
||||
index2 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
while ( index1 < count1 )
|
||||
{
|
||||
// end
|
||||
const b3Visitor* r1 = refs1 + index1;
|
||||
b3ShapeId visitorId = { r1->shapeId + 1, world->worldId, r1->generation };
|
||||
b3SensorEndTouchEvent event = { sensorId, visitorId };
|
||||
b3Array_Push( world->sensorEndEvents[world->endEventArrayIndex], event );
|
||||
index1 += 1;
|
||||
}
|
||||
|
||||
while ( index2 < count2 )
|
||||
{
|
||||
// begin
|
||||
const b3Visitor* r2 = refs2 + index2;
|
||||
b3ShapeId visitorId = { r2->shapeId + 1, world->worldId, r2->generation };
|
||||
b3SensorBeginTouchEvent event = { sensorId, visitorId };
|
||||
b3Array_Push( world->sensorBeginEvents, event );
|
||||
index2 += 1;
|
||||
}
|
||||
|
||||
// Clear the smallest set bit
|
||||
word = word & ( word - 1 );
|
||||
}
|
||||
}
|
||||
|
||||
b3TracyCZoneEnd( sensor_state );
|
||||
b3TracyCZoneEnd( overlap_sensors );
|
||||
}
|
||||
|
||||
void b3DestroySensor( b3World* world, b3Shape* sensorShape )
|
||||
{
|
||||
b3Sensor* sensor = b3Array_Get( world->sensors, sensorShape->sensorIndex );
|
||||
for ( int i = 0; i < sensor->overlaps2.count; ++i )
|
||||
{
|
||||
b3Visitor* ref = sensor->overlaps2.data + i;
|
||||
b3SensorEndTouchEvent event = {
|
||||
.sensorShapeId =
|
||||
{
|
||||
.index1 = sensorShape->id + 1,
|
||||
.world0 = world->worldId,
|
||||
.generation = sensorShape->generation,
|
||||
},
|
||||
.visitorShapeId =
|
||||
{
|
||||
.index1 = ref->shapeId + 1,
|
||||
.world0 = world->worldId,
|
||||
.generation = ref->generation,
|
||||
},
|
||||
};
|
||||
|
||||
b3Array_Push( world->sensorEndEvents[world->endEventArrayIndex], event );
|
||||
}
|
||||
|
||||
// Destroy sensor
|
||||
b3Array_Destroy( sensor->hits );
|
||||
b3Array_Destroy( sensor->overlaps1 );
|
||||
b3Array_Destroy( sensor->overlaps2 );
|
||||
|
||||
int movedIndex = b3Array_RemoveSwap( world->sensors, sensorShape->sensorIndex );
|
||||
if ( movedIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// Fixup moved sensor
|
||||
b3Sensor* movedSensor = b3Array_Get( world->sensors, sensorShape->sensorIndex );
|
||||
b3Shape* otherSensorShape = b3Array_Get( world->shapes, movedSensor->shapeId );
|
||||
otherSensorShape->sensorIndex = sensorShape->sensorIndex;
|
||||
}
|
||||
}
|
||||
41
vendor/box3d/src/src/sensor.h
vendored
Normal file
41
vendor/box3d/src/src/sensor.h
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "bitset.h"
|
||||
#include "container.h"
|
||||
|
||||
typedef struct b3Shape b3Shape;
|
||||
typedef struct b3World b3World;
|
||||
|
||||
// Used to track shapes that hit sensors using time of impact
|
||||
typedef struct b3SensorHit
|
||||
{
|
||||
int sensorId;
|
||||
int visitorId;
|
||||
} b3SensorHit;
|
||||
|
||||
typedef struct b3Visitor
|
||||
{
|
||||
int shapeId;
|
||||
uint16_t generation;
|
||||
} b3Visitor;
|
||||
|
||||
b3DeclareArray( b3Visitor );
|
||||
|
||||
typedef struct b3Sensor
|
||||
{
|
||||
b3Array( b3Visitor ) hits;
|
||||
b3Array( b3Visitor ) overlaps1;
|
||||
b3Array( b3Visitor ) overlaps2;
|
||||
int shapeId;
|
||||
} b3Sensor;
|
||||
|
||||
typedef struct b3SensorTaskContext
|
||||
{
|
||||
b3BitSet eventBits;
|
||||
} b3SensorTaskContext;
|
||||
|
||||
void b3OverlapSensors( b3World* world );
|
||||
void b3DestroySensor( b3World* world, b3Shape* sensorShape );
|
||||
2393
vendor/box3d/src/src/shape.c
vendored
Normal file
2393
vendor/box3d/src/src/shape.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
155
vendor/box3d/src/src/shape.h
vendored
Normal file
155
vendor/box3d/src/src/shape.h
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "math_internal.h"
|
||||
|
||||
#include "box3d/types.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct b3BroadPhase b3BroadPhase;
|
||||
typedef struct b3World b3World;
|
||||
|
||||
typedef enum b3ShapeFlags
|
||||
{
|
||||
b3_enableSensorEvents = 0x01,
|
||||
b3_enableContactEvents = 0x02,
|
||||
b3_enableCustomFiltering = 0x04,
|
||||
b3_enableHitEvents = 0x08,
|
||||
b3_enablePreSolveEvents = 0x10,
|
||||
b3_enlargedAABB = 0x20,
|
||||
b3_enableSpeculative = 0x40,
|
||||
} b3ShapeFlags;
|
||||
|
||||
typedef struct b3Shape
|
||||
{
|
||||
int id;
|
||||
int bodyId;
|
||||
int prevShapeId;
|
||||
int nextShapeId;
|
||||
int sensorIndex;
|
||||
int proxyKey;
|
||||
b3ShapeType type;
|
||||
float density;
|
||||
float explosionScale;
|
||||
float aabbMargin;
|
||||
|
||||
b3AABB aabb;
|
||||
b3AABB fatAABB;
|
||||
b3Vec3 localCentroid;
|
||||
|
||||
int materialCount;
|
||||
b3SurfaceMaterial material;
|
||||
b3SurfaceMaterial* materials;
|
||||
|
||||
b3Filter filter;
|
||||
void* userData;
|
||||
void* userShape;
|
||||
|
||||
uint32_t nameId;
|
||||
uint16_t generation;
|
||||
|
||||
// b3ShapeFlags
|
||||
uint8_t flags;
|
||||
|
||||
union
|
||||
{
|
||||
b3Capsule capsule;
|
||||
b3Sphere sphere;
|
||||
const b3HullData* hull;
|
||||
b3Mesh mesh;
|
||||
const b3HeightFieldData* heightField;
|
||||
const b3CompoundData* compound;
|
||||
};
|
||||
|
||||
} b3Shape;
|
||||
|
||||
// A single material shape keeps its material inline. Multi material meshes and compounds own a heap
|
||||
// array. Reach the materials the same way for both: a single material shape presents its inline
|
||||
// material as a one element array. Do not cache the pointer, the shapes array can move.
|
||||
static inline b3SurfaceMaterial* b3GetShapeMaterials( const b3Shape* shape )
|
||||
{
|
||||
return shape->materials != NULL ? shape->materials : (b3SurfaceMaterial*)&shape->material;
|
||||
}
|
||||
|
||||
void b3CreateShapeProxy( b3Shape* shape, b3BroadPhase* bp, b3BodyType type, b3WorldTransform transform, bool forcePairCreation );
|
||||
void b3DestroyShapeProxy( b3Shape* shape, b3BroadPhase* bp );
|
||||
|
||||
void b3DestroyShapeAllocations( b3World* world, b3Shape* shape );
|
||||
|
||||
b3MassData b3ComputeShapeMass( const b3Shape* shape );
|
||||
b3ShapeExtent b3ComputeShapeExtent( const b3Shape* shape, b3Vec3 localCenter );
|
||||
|
||||
b3AABB b3ComputeSweptSphereAABB( const b3Sphere* shape, b3Transform xf1, b3Transform xf2 );
|
||||
b3AABB b3ComputeSweptCapsuleAABB( const b3Capsule* shape, b3Transform xf1, b3Transform xf2 );
|
||||
|
||||
b3AABB b3ComputeShapeAABB( const b3Shape* shape, b3Transform transform );
|
||||
|
||||
// Conservative world AABB for a shape inflated by extra margin. In double precision mode the
|
||||
// box is built in the body local frame, translated by the double origin, and rounded outward.
|
||||
b3AABB b3ComputeFatShapeAABB( const b3Shape* shape, b3WorldTransform transform, float extra );
|
||||
b3AABB b3ComputeSweptShapeAABB( const b3Shape* shape, const b3Sweep* sweep, float time );
|
||||
b3Vec3 b3GetShapeCentroid( const b3Shape* shape );
|
||||
float b3GetShapeArea( const b3Shape* shape );
|
||||
float b3GetShapeProjectedArea( const b3Shape* shape, b3Vec3 planeNormal );
|
||||
uint64_t b3GetShapeUserMaterialId( const b3Shape* shape, int childIndex, int triangleIndex );
|
||||
|
||||
b3ShapeProxy b3MakeShapeProxy( const b3Shape* shape );
|
||||
b3ShapeProxy b3MakeLocalProxy( const b3ShapeProxy* proxy, b3Transform transform, b3Vec3* buffer );
|
||||
b3AABB b3ComputeProxyAABB( const b3ShapeProxy* proxy );
|
||||
|
||||
b3CastOutput b3RayCastShape( const b3Shape* shape, b3Transform transform, const b3RayCastInput* input );
|
||||
b3CastOutput b3ShapeCastShape( const b3Shape* shape, b3Transform transform, const b3ShapeCastInput* input );
|
||||
bool b3OverlapShape( const b3Shape* shape, b3Transform transform, const b3ShapeProxy* proxy );
|
||||
|
||||
float b3GetShapeArea( const b3Shape* shape );
|
||||
float b3GetShapeProjectedArea( const b3Shape* shape, b3Vec3 planeNormal );
|
||||
b3TOIOutput b3ShapeTimeOfImpact( b3Shape* shapeA, b3Shape* shapeB, b3Sweep* sweepA, b3Sweep* sweepB, float maxFraction );
|
||||
|
||||
int b3CollideMoverAndSphere( b3PlaneResult* result, const b3Sphere* shape, const b3Capsule* mover );
|
||||
int b3CollideMoverAndCapsule( b3PlaneResult* result, const b3Capsule* shape, const b3Capsule* mover );
|
||||
int b3CollideMoverAndHull( b3PlaneResult* result, const b3HullData* shape, const b3Capsule* mover );
|
||||
int b3CollideMoverAndMesh( b3PlaneResult* planes, int capacity, const b3Mesh* shape, const b3Capsule* mover );
|
||||
int b3CollideMoverAndHeightField( b3PlaneResult* results, int capacity, const b3HeightFieldData* shape, const b3Capsule* mover );
|
||||
int b3CollideMover( b3PlaneResult* planes, int planeCapacity, const b3Shape* shape, b3Transform transform,
|
||||
const b3Capsule* mover );
|
||||
|
||||
// Hull
|
||||
int b3FindHullSupportVertex( const b3HullData* hull, b3Vec3 direction );
|
||||
int b3FindHullSupportFace( const b3HullData* hull, b3Vec3 direction );
|
||||
bool b3IsValidHull( const b3HullData* hull );
|
||||
b3AABB b3ComputeSweptHullAABB( const b3HullData* shape, b3Transform xf1, b3Transform xf2 );
|
||||
b3ShapeExtent b3ComputeHullExtent( const b3HullData* hull, b3Vec3 origin );
|
||||
float b3ComputeHullProjectedArea( const b3HullData* hull, b3Vec3 direction );
|
||||
|
||||
// Height field
|
||||
b3Triangle b3GetHeightFieldTriangle( const b3HeightFieldData* heightField, int triangleIndex );
|
||||
int b3GetHeightFieldMaterial( const b3HeightFieldData* heightField, int triangleIndex );
|
||||
|
||||
static inline int b3GetHeightFieldTriangleCount( const b3HeightFieldData* heightField )
|
||||
{
|
||||
int cellCount = ( heightField->rowCount - 1 ) * ( heightField->columnCount - 1 );
|
||||
return 2 * cellCount;
|
||||
}
|
||||
|
||||
// Mesh
|
||||
b3Triangle b3GetMeshTriangle( const b3Mesh* mesh, int triangleIndex );
|
||||
bool b3IsValidMesh( const b3MeshData* meshData );
|
||||
|
||||
static inline bool b3ShouldShapesCollide( b3Filter filterA, b3Filter filterB )
|
||||
{
|
||||
if ( filterA.groupIndex == filterB.groupIndex && filterA.groupIndex != 0 )
|
||||
{
|
||||
return filterA.groupIndex > 0;
|
||||
}
|
||||
|
||||
return ( filterA.maskBits & filterB.categoryBits ) != 0 && ( filterA.categoryBits & filterB.maskBits ) != 0;
|
||||
}
|
||||
|
||||
static inline bool b3ShouldQueryCollide( const b3Filter* shapeFilter, const b3QueryFilter* queryFilter )
|
||||
{
|
||||
return ( shapeFilter->categoryBits & queryFilter->maskBits ) != 0 &&
|
||||
( shapeFilter->maskBits & queryFilter->categoryBits ) != 0;
|
||||
}
|
||||
202
vendor/box3d/src/src/simd.c
vendored
Normal file
202
vendor/box3d/src/src/simd.c
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Dirk Gregorius contributed portions of this code
|
||||
|
||||
#include "simd.h"
|
||||
|
||||
#if defined( B3_SIMD_SSE2 )
|
||||
|
||||
#define B3_TRANSPOSE3( C1, C2, C3 ) \
|
||||
{ \
|
||||
b3V32 T1 = _mm_unpacklo_ps( ( C1 ), ( C2 ) ); \
|
||||
b3V32 T2 = _mm_unpackhi_ps( ( C1 ), ( C2 ) ); \
|
||||
( C1 ) = _mm_shuffle_ps( ( T1 ), ( C3 ), _MM_SHUFFLE( 0, 0, 1, 0 ) ); \
|
||||
( C2 ) = _mm_shuffle_ps( ( T1 ), ( C3 ), _MM_SHUFFLE( 1, 1, 3, 2 ) ); \
|
||||
( C3 ) = _mm_shuffle_ps( ( T2 ), ( C3 ), _MM_SHUFFLE( 2, 2, 1, 0 ) ); \
|
||||
}
|
||||
|
||||
static inline b3V32 b3SplatXV( b3V32 v )
|
||||
{
|
||||
return _mm_shuffle_ps( v, v, _MM_SHUFFLE( 0, 0, 0, 0 ) );
|
||||
}
|
||||
|
||||
static inline b3V32 b3SplatYV( b3V32 v )
|
||||
{
|
||||
return _mm_shuffle_ps( v, v, _MM_SHUFFLE( 1, 1, 1, 1 ) );
|
||||
}
|
||||
|
||||
static inline b3V32 b3SplatZV( b3V32 v )
|
||||
{
|
||||
return _mm_shuffle_ps( v, v, _MM_SHUFFLE( 2, 2, 2, 2 ) );
|
||||
}
|
||||
|
||||
static inline bool b3AnyGreaterEq3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
b3V32 v = _mm_cmpge_ps( a, b );
|
||||
return ( _mm_movemask_ps( v ) & 0x07 ) != 0;
|
||||
}
|
||||
|
||||
static inline b3V32 b3Dot3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
b3V32 m = _mm_mul_ps( a, b );
|
||||
b3V32 x = _mm_shuffle_ps( m, m, _MM_SHUFFLE( 0, 0, 0, 0 ) );
|
||||
b3V32 y = _mm_shuffle_ps( m, m, _MM_SHUFFLE( 1, 1, 1, 1 ) );
|
||||
b3V32 z = _mm_shuffle_ps( m, m, _MM_SHUFFLE( 2, 2, 2, 2 ) );
|
||||
|
||||
return _mm_add_ps( _mm_add_ps( x, y ), z );
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define B3_TRANSPOSE3( C1, C2, C3 ) \
|
||||
{ \
|
||||
float temp1 = C1.y; \
|
||||
float temp2 = C1.z; \
|
||||
float temp3 = C2.z; \
|
||||
\
|
||||
C1.y = C2.x; \
|
||||
C1.z = C3.x; \
|
||||
C2.z = C3.y; \
|
||||
\
|
||||
C2.x = temp1; \
|
||||
C3.x = temp2; \
|
||||
C3.y = temp3; \
|
||||
}
|
||||
|
||||
static inline b3V32 b3SplatXV( b3V32 a )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){ a.x, a.x, a.x };
|
||||
}
|
||||
|
||||
static inline b3V32 b3SplatYV( b3V32 a )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){ a.y, a.y, a.y };
|
||||
}
|
||||
|
||||
static inline b3V32 b3SplatZV( b3V32 a )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){ a.z, a.z, a.z };
|
||||
}
|
||||
|
||||
static inline bool b3AnyGreaterEq3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
return a.x >= b.x || a.y >= b.y || a.z >= b.z;
|
||||
}
|
||||
|
||||
static inline b3V32 b3Dot3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
float d = a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
return B3_LITERAL( b3V32 ){ d, d, d };
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool b3TestBoundsTriangleOverlap( b3V32 nodeCenter, b3V32 nodeExtent, b3V32 vertex1, b3V32 vertex2, b3V32 vertex3 )
|
||||
{
|
||||
b3V32 two = b3SplatV( 2.0f );
|
||||
|
||||
// Setup triangle
|
||||
vertex1 = b3SubV( vertex1, nodeCenter );
|
||||
vertex2 = b3SubV( vertex2, nodeCenter );
|
||||
vertex3 = b3SubV( vertex3, nodeCenter );
|
||||
|
||||
// Face separation
|
||||
b3V32 triangleMin = b3MinV( vertex1, b3MinV( vertex2, vertex3 ) );
|
||||
b3V32 triangleMax = b3MaxV( vertex1, b3MaxV( vertex2, vertex3 ) );
|
||||
|
||||
b3V32 separation1 = b3SubV( triangleMin, nodeExtent );
|
||||
b3V32 separation2 = b3AddV( triangleMax, nodeExtent );
|
||||
|
||||
b3V32 faceSeparation = b3MaxV( separation1, b3NegV( separation2 ) );
|
||||
if ( b3AnyGreater3V( faceSeparation, b3_zeroV ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// SAT: Face separation
|
||||
b3V32 edge1 = b3SubV( vertex2, vertex1 );
|
||||
b3V32 edge2 = b3SubV( vertex3, vertex2 );
|
||||
b3V32 edge3 = b3SubV( vertex1, vertex3 );
|
||||
|
||||
b3V32 normal = b3CrossV( edge1, edge2 );
|
||||
|
||||
b3V32 triangleSeparation = b3SubV( b3AbsV( b3Dot3V( normal, vertex1 ) ), b3Dot3V( b3AbsV( normal ), nodeExtent ) );
|
||||
if ( b3AnyGreater3V( triangleSeparation, b3_zeroV ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// SAT: Edge separation
|
||||
b3V32 edgeSeparation1 = b3SubV( b3SubV( b3AbsV( b3CrossV( edge1, b3AddV( vertex1, vertex3 ) ) ), b3AbsV( b3CrossV( edge1, edge3 ) ) ),
|
||||
b3MulV( two, b3ModifiedCrossV( b3AbsV( edge1 ), nodeExtent ) ) );
|
||||
if ( b3AnyGreater3V( edgeSeparation1, b3_zeroV ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
b3V32 edgeSeparation2 = b3SubV( b3SubV( b3AbsV( b3CrossV( edge2, b3AddV( vertex1, vertex2 ) ) ), b3AbsV( b3CrossV( edge2, edge1 ) ) ),
|
||||
b3MulV( two, b3ModifiedCrossV( b3AbsV( edge2 ), nodeExtent ) ) );
|
||||
if ( b3AnyGreater3V( edgeSeparation2, b3_zeroV ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
b3V32 edgeSeparation3 = b3SubV( b3SubV( b3AbsV( b3CrossV( edge3, b3AddV( vertex2, vertex3 ) ) ), b3AbsV( b3CrossV( edge3, edge2 ) ) ),
|
||||
b3MulV( two, b3ModifiedCrossV( b3AbsV( edge3 ), nodeExtent ) ) );
|
||||
if ( b3AnyGreater3V( edgeSeparation3, b3_zeroV ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float b3IntersectRayTriangle( b3V32 rayStart, b3V32 rayDelta, b3V32 vertex1, b3V32 vertex2, b3V32 vertex3 )
|
||||
{
|
||||
// Test if ray intersects this triangle sharing same calculations for each triangle
|
||||
{
|
||||
b3V32 edge1 = b3SubV( vertex3, vertex2 );
|
||||
b3V32 edge2 = b3SubV( vertex1, vertex3 );
|
||||
b3V32 edge3 = b3SubV( vertex2, vertex1 );
|
||||
|
||||
b3V32 midPoint1 = b3MulV( b3_halfV, b3AddV( vertex2, vertex3 ) );
|
||||
b3V32 midPoint2 = b3MulV( b3_halfV, b3AddV( vertex3, vertex1 ) );
|
||||
b3V32 midPoint3 = b3MulV( b3_halfV, b3AddV( vertex1, vertex2 ) );
|
||||
|
||||
b3V32 normal1 = b3CrossV( edge1, b3SubV( midPoint1, rayStart ) );
|
||||
b3V32 normal2 = b3CrossV( edge2, b3SubV( midPoint2, rayStart ) );
|
||||
b3V32 normal3 = b3CrossV( edge3, b3SubV( midPoint3, rayStart ) );
|
||||
B3_TRANSPOSE3( normal1, normal2, normal3 );
|
||||
|
||||
b3V32 rayDeltaX = b3SplatXV( rayDelta );
|
||||
b3V32 rayDeltaY = b3SplatYV( rayDelta );
|
||||
b3V32 rayDeltaZ = b3SplatZV( rayDelta );
|
||||
|
||||
b3V32 volumes = b3AddV( b3AddV( b3MulV( normal1, rayDeltaX ), b3MulV( normal2, rayDeltaY ) ), b3MulV( normal3, rayDeltaZ ) );
|
||||
if ( b3AnyLess3V( volumes, b3_zeroV ) )
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute intersection with triangle plane
|
||||
b3V32 edge1 = b3SubV( vertex2, vertex1 );
|
||||
b3V32 edge2 = b3SubV( vertex3, vertex1 );
|
||||
b3V32 normal = b3CrossV( edge1, edge2 );
|
||||
|
||||
b3V32 denominator = b3Dot3V( normal, rayDelta );
|
||||
if ( b3AnyGreaterEq3V( denominator, b3_zeroV ) )
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
b3V32 lambda = b3DivV( b3Dot3V( normal, b3SubV( vertex1, rayStart ) ), denominator );
|
||||
if ( b3AnyLessEq3V( lambda, b3_zeroV ) )
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
lambda = b3MinV( lambda, b3_oneV );
|
||||
return b3GetXV( lambda );
|
||||
}
|
||||
357
vendor/box3d/src/src/simd.h
vendored
Normal file
357
vendor/box3d/src/src/simd.h
vendored
Normal file
@@ -0,0 +1,357 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#if defined( B3_SIMD_SSE2 )
|
||||
|
||||
#include <emmintrin.h>
|
||||
|
||||
// wide float holds 4 numbers
|
||||
typedef __m128 b3V32;
|
||||
|
||||
typedef union b3128
|
||||
{
|
||||
b3V32 v;
|
||||
float f[4];
|
||||
} b3128;
|
||||
|
||||
#if defined( _MSC_VER ) && !defined( __clang__ )
|
||||
|
||||
static const b3V32 b3_zeroV = { { 0.0f, 0.0f, 0.0f, 0.0f } };
|
||||
static const b3V32 b3_halfV = { { 0.5f, 0.5f, 0.5f, 0.5f } };
|
||||
static const b3V32 b3_oneV = { { 1.0f, 1.0f, 1.0f, 1.0f } };
|
||||
|
||||
#else
|
||||
|
||||
static const b3V32 b3_zeroV = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
static const b3V32 b3_halfV = { 0.5f, 0.5f, 0.5f, 0.5f };
|
||||
static const b3V32 b3_oneV = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
|
||||
#endif
|
||||
|
||||
static inline b3V32 b3AddV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return _mm_add_ps( a, b );
|
||||
}
|
||||
|
||||
static inline b3V32 b3SubV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return _mm_sub_ps( a, b );
|
||||
}
|
||||
|
||||
static inline b3V32 b3MulV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return _mm_mul_ps( a, b );
|
||||
}
|
||||
|
||||
static inline b3V32 b3DivV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return _mm_div_ps( a, b );
|
||||
}
|
||||
|
||||
static inline b3V32 b3NegV( b3V32 a )
|
||||
{
|
||||
return _mm_sub_ps( _mm_setzero_ps(), a );
|
||||
}
|
||||
|
||||
static inline b3V32 b3LoadV( const float* src )
|
||||
{
|
||||
// Loads exactly 12 bytes: 8 via movsd, 4 via movss.
|
||||
// Result lane 3 is implicitly zero from the partial loads.
|
||||
__m128 xy = _mm_castpd_ps( _mm_load_sd( (const double*)( src ) ) );
|
||||
__m128 z = _mm_load_ss( src + 2 );
|
||||
return _mm_movelh_ps( xy, z ); // { src[0], src[1], src[2], 0.0f }
|
||||
}
|
||||
|
||||
static inline b3V32 b3ZeroV( void )
|
||||
{
|
||||
return _mm_setzero_ps();
|
||||
}
|
||||
|
||||
static inline float b3GetXV( b3V32 a )
|
||||
{
|
||||
return _mm_cvtss_f32( a );
|
||||
}
|
||||
|
||||
static inline float b3GetYV( b3V32 a )
|
||||
{
|
||||
return _mm_cvtss_f32( _mm_shuffle_ps( a, a, _MM_SHUFFLE( 1, 1, 1, 1 ) ) );
|
||||
}
|
||||
|
||||
static inline float b3GetZV( b3V32 a )
|
||||
{
|
||||
return _mm_cvtss_f32( _mm_shuffle_ps( a, a, _MM_SHUFFLE( 2, 2, 2, 2 ) ) );
|
||||
}
|
||||
|
||||
static inline float b3GetV( b3V32 a, int index )
|
||||
{
|
||||
b3128 b;
|
||||
b.v = a;
|
||||
return b.f[index];
|
||||
}
|
||||
|
||||
static inline b3V32 b3SplatV( float x )
|
||||
{
|
||||
return _mm_set_ps1( x );
|
||||
}
|
||||
|
||||
static inline b3V32 b3AbsV( b3V32 a )
|
||||
{
|
||||
// Abs( V ) = Max( -V, V )
|
||||
b3V32 zero = _mm_setzero_ps();
|
||||
return _mm_max_ps( _mm_sub_ps( zero, a ), a );
|
||||
}
|
||||
|
||||
static inline b3V32 b3MinV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return _mm_min_ps( a, b );
|
||||
}
|
||||
|
||||
static inline b3V32 b3MaxV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return _mm_max_ps( a, b );
|
||||
}
|
||||
|
||||
static inline b3V32 b3CrossV( b3V32 a, b3V32 b )
|
||||
{
|
||||
b3V32 yzX1 = _mm_shuffle_ps( a, a, _MM_SHUFFLE( 3, 0, 2, 1 ) );
|
||||
b3V32 zxY1 = _mm_shuffle_ps( a, a, _MM_SHUFFLE( 3, 1, 0, 2 ) );
|
||||
b3V32 yzX2 = _mm_shuffle_ps( b, b, _MM_SHUFFLE( 3, 0, 2, 1 ) );
|
||||
b3V32 zxY2 = _mm_shuffle_ps( b, b, _MM_SHUFFLE( 3, 1, 0, 2 ) );
|
||||
|
||||
return _mm_sub_ps( _mm_mul_ps( yzX1, zxY2 ), _mm_mul_ps( zxY1, yzX2 ) );
|
||||
}
|
||||
|
||||
static inline b3V32 b3ModifiedCrossV( b3V32 a, b3V32 b )
|
||||
{
|
||||
b3V32 yzX1 = _mm_shuffle_ps( a, a, _MM_SHUFFLE( 3, 0, 2, 1 ) );
|
||||
b3V32 zxY1 = _mm_shuffle_ps( a, a, _MM_SHUFFLE( 3, 1, 0, 2 ) );
|
||||
b3V32 yzX2 = _mm_shuffle_ps( b, b, _MM_SHUFFLE( 3, 0, 2, 1 ) );
|
||||
b3V32 zxY2 = _mm_shuffle_ps( b, b, _MM_SHUFFLE( 3, 1, 0, 2 ) );
|
||||
|
||||
return _mm_add_ps( _mm_mul_ps( yzX1, zxY2 ), _mm_mul_ps( zxY1, yzX2 ) );
|
||||
}
|
||||
|
||||
static inline bool b3AnyLess3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
b3V32 v = _mm_cmplt_ps( a, b );
|
||||
return ( _mm_movemask_ps( v ) & 0x07 ) != 0;
|
||||
}
|
||||
|
||||
static inline bool b3AnyLessEq3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
b3V32 v = _mm_cmple_ps( a, b );
|
||||
return ( _mm_movemask_ps( v ) & 0x07 ) != 0;
|
||||
}
|
||||
|
||||
static inline bool b3AnyGreater3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
b3V32 v = _mm_cmpgt_ps( a, b );
|
||||
return ( _mm_movemask_ps( v ) & 0x07 ) != 0;
|
||||
}
|
||||
|
||||
static inline bool b3AllLessEq3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
b3V32 v = _mm_cmple_ps( a, b );
|
||||
return ( _mm_movemask_ps( v ) & 0x07 ) == 0x07;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// I don't expect the use case of b3V32 to benefit from Neon code.
|
||||
// In particular the cross product is very complex in Neon.
|
||||
|
||||
// scalar math
|
||||
typedef struct b3V32
|
||||
{
|
||||
float x, y, z;
|
||||
} b3V32;
|
||||
|
||||
typedef union b3128
|
||||
{
|
||||
b3V32 v;
|
||||
float f[3];
|
||||
} b3128;
|
||||
|
||||
static const b3V32 b3_zeroV = { 0.0f, 0.0f, 0.0f };
|
||||
static const b3V32 b3_halfV = { 0.5f, 0.5f, 0.5f };
|
||||
static const b3V32 b3_oneV = { 1.0f, 1.0f, 1.0f };
|
||||
|
||||
static inline b3V32 b3AddV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){
|
||||
a.x + b.x,
|
||||
a.y + b.y,
|
||||
a.z + b.z,
|
||||
};
|
||||
}
|
||||
|
||||
static inline b3V32 b3SubV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){
|
||||
a.x - b.x,
|
||||
a.y - b.y,
|
||||
a.z - b.z,
|
||||
};
|
||||
}
|
||||
|
||||
static inline b3V32 b3MulV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){
|
||||
a.x * b.x,
|
||||
a.y * b.y,
|
||||
a.z * b.z,
|
||||
};
|
||||
}
|
||||
|
||||
static inline b3V32 b3DivV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){
|
||||
a.x / b.x,
|
||||
a.y / b.y,
|
||||
a.z / b.z,
|
||||
};
|
||||
}
|
||||
|
||||
static inline b3V32 b3NegV( b3V32 a )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){
|
||||
-a.x,
|
||||
-a.y,
|
||||
-a.z,
|
||||
};
|
||||
}
|
||||
|
||||
// Unaligned loads are much faster on recent hardware with little to no penalty
|
||||
static inline b3V32 b3LoadV( const float* src )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){ src[0], src[1], src[2] };
|
||||
}
|
||||
|
||||
static inline b3V32 b3ZeroV( void )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){ 0.0f, 0.0f, 0.0f };
|
||||
}
|
||||
|
||||
static inline float b3GetXV( b3V32 a )
|
||||
{
|
||||
return a.x;
|
||||
}
|
||||
|
||||
static inline float b3GetYV( b3V32 a )
|
||||
{
|
||||
return a.y;
|
||||
}
|
||||
|
||||
static inline float b3GetZV( b3V32 a )
|
||||
{
|
||||
return a.z;
|
||||
}
|
||||
|
||||
static inline float b3GetV( b3V32 a, int index )
|
||||
{
|
||||
b3128 b;
|
||||
b.v = a;
|
||||
return b.f[index];
|
||||
}
|
||||
|
||||
static inline b3V32 b3SplatV( float x )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){ x, x, x };
|
||||
}
|
||||
|
||||
static inline b3V32 b3AbsV( b3V32 a )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){
|
||||
a.x < 0.0f ? -a.x : a.x,
|
||||
a.y < 0.0f ? -a.y : a.y,
|
||||
a.z < 0.0f ? -a.z : a.z,
|
||||
};
|
||||
}
|
||||
|
||||
static inline b3V32 b3MinV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){
|
||||
a.x < b.x ? a.x : b.x,
|
||||
a.y < b.y ? a.y : b.y,
|
||||
a.z < b.z ? a.z : b.z,
|
||||
};
|
||||
}
|
||||
|
||||
static inline b3V32 b3MaxV( b3V32 a, b3V32 b )
|
||||
{
|
||||
return B3_LITERAL( b3V32 ){
|
||||
a.x > b.x ? a.x : b.x,
|
||||
a.y > b.y ? a.y : b.y,
|
||||
a.z > b.z ? a.z : b.z,
|
||||
};
|
||||
}
|
||||
|
||||
static inline b3V32 b3CrossV( b3V32 a, b3V32 b )
|
||||
{
|
||||
b3V32 c;
|
||||
c.x = a.y * b.z - a.z * b.y;
|
||||
c.y = a.z * b.x - a.x * b.z;
|
||||
c.z = a.x * b.y - a.y * b.x;
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline b3V32 b3ModifiedCrossV( b3V32 a, b3V32 b )
|
||||
{
|
||||
b3V32 c;
|
||||
c.x = a.y * b.z + a.z * b.y;
|
||||
c.y = a.z * b.x + a.x * b.z;
|
||||
c.z = a.x * b.y + a.y * b.x;
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline bool b3AnyLess3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
return a.x < b.x || a.y < b.y || a.z < b.z;
|
||||
}
|
||||
|
||||
static inline bool b3AnyLessEq3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
return a.x <= b.x || a.y <= b.y || a.z <= b.z;
|
||||
}
|
||||
|
||||
static inline bool b3AnyGreater3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
return a.x > b.x || a.y > b.y || a.z > b.z;
|
||||
}
|
||||
|
||||
static inline bool b3AllLessEq3V( b3V32 a, b3V32 b )
|
||||
{
|
||||
return a.x <= b.x && a.y <= b.y && a.z <= b.z;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static inline bool b3TestBoundsOverlap( b3V32 nodeMin1, b3V32 nodeMax1, b3V32 nodeMin2, b3V32 nodeMax2 )
|
||||
{
|
||||
b3V32 separation = b3MaxV( b3SubV( nodeMin2, nodeMax1 ), b3SubV( nodeMin1, nodeMax2 ) );
|
||||
return b3AllLessEq3V( separation, b3_zeroV );
|
||||
}
|
||||
|
||||
// Test a ray for edge separation with an AABB (Gino, p80).
|
||||
static inline bool b3TestBoundsRayOverlap( b3V32 nodeMin, b3V32 nodeMax, b3V32 rayStart, b3V32 rayDelta )
|
||||
{
|
||||
// Setup node
|
||||
b3V32 nodeCenter = b3MulV( b3_halfV, b3AddV( nodeMin, nodeMax ) );
|
||||
b3V32 nodeExtent = b3SubV( nodeMax, nodeCenter );
|
||||
|
||||
// Setup ray
|
||||
rayStart = b3SubV( rayStart, nodeCenter );
|
||||
|
||||
// SAT: Edge separation
|
||||
b3V32 edgeSeparation = b3SubV( b3AbsV( b3CrossV( rayDelta, rayStart ) ), b3ModifiedCrossV( b3AbsV( rayDelta ), nodeExtent ) );
|
||||
return b3AllLessEq3V( edgeSeparation, b3_zeroV );
|
||||
}
|
||||
|
||||
bool b3TestBoundsTriangleOverlap( b3V32 nodeCenter, b3V32 nodeExtent, b3V32 vertex1, b3V32 vertex2, b3V32 vertex3 );
|
||||
float b3IntersectRayTriangle( b3V32 rayStart, b3V32 rayDelta, b3V32 vertex1, b3V32 vertex2, b3V32 vertex3 );
|
||||
2328
vendor/box3d/src/src/solver.c
vendored
Normal file
2328
vendor/box3d/src/src/solver.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
306
vendor/box3d/src/src/solver.h
vendored
Normal file
306
vendor/box3d/src/src/solver.h
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Solver work is partitioned into fixed-size blocks that worker threads claim
|
||||
// in parallel via atomic CAS on a per-block syncIndex. The descriptor (b3SolverBlock)
|
||||
// and the atomic counter sit in a wrapping b3SyncBlock so the CAS-winner can
|
||||
// pass the descriptor by value into stage tasks without aliasing the atomic
|
||||
// memory other threads are CAS-writing. Three properties of this design
|
||||
// matter for performance:
|
||||
//
|
||||
// 1. Distributed contention. Per-block atomic syncIndex avoids the cache line stampede
|
||||
// that a single shared fetch_add counter would cause. Once a worker
|
||||
// settles into a block range, its CAS targets live in its own L1.
|
||||
//
|
||||
// 2. Monotonic syncIndex across iterations. Iterative stages (warm start,
|
||||
// solve, relax) reuse the same block array every sub-step iteration.
|
||||
// syncIndex grows each iteration; workers CAS (prev, prev+1), so the
|
||||
// main thread never touches any per-block state between iterations.
|
||||
// Non-iterative stages simply use syncIndex 1.
|
||||
//
|
||||
// 3. L2 affinity across iterations. Each worker picks a start offset from
|
||||
// its workerIndex, then scans forward and (after wrap) backward:
|
||||
//
|
||||
// blocks: [0] [1] [2] [3] [4] [5] [6] [7]
|
||||
// ^ ^ ^ ^
|
||||
// W0 W1 W2 W3 <- start offsets
|
||||
//
|
||||
// W0 claims 0,1,2,3 (forward), W1 claims 4,5, etc. Under balanced load
|
||||
// each worker re-hits the same block range every iteration, keeping that
|
||||
// range's hot data resident in its L2. A failed CAS means a neighbour
|
||||
// already claimed the block, so the stealing worker stops -- preserving
|
||||
// locality under mild imbalance while still draining the queue.
|
||||
//
|
||||
// A graph color stage lays out joint blocks first, then contact blocks:
|
||||
//
|
||||
// stage->blocks ->
|
||||
// +------+------+------+------+------+------+------+
|
||||
// | J0 | J1 | J2 | C0 | C1 | C2 | C3 |
|
||||
// +------+------+------+------+------+------+------+
|
||||
// <-- graphJointBlocks --><---- graphContactBlocks ---->
|
||||
//
|
||||
// Each block carries its type so the dispatcher routes J-blocks to the joint
|
||||
// solver and C-blocks to the SIMD contact solver; both kinds run concurrently
|
||||
// within the stage -- no barrier between them. The type tag lives on the
|
||||
// block (not the stage) so that mixed-type stages can keep the concurrency.
|
||||
//
|
||||
// The solver threading model is inspired by https://github.com/bepu/bepuphysics2
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "container.h"
|
||||
#include "core.h"
|
||||
|
||||
#include "box3d/math_functions.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct b3BodySim b3BodySim;
|
||||
typedef struct b3BodyState b3BodyState;
|
||||
typedef struct b3ContactConstraint b3ContactConstraint;
|
||||
typedef struct b3ContactConstraintWide b3ContactConstraintWide;
|
||||
typedef struct b3ContactSpec b3ContactSpec;
|
||||
typedef struct b3JointSim b3JointSim;
|
||||
typedef struct b3Manifold b3Manifold;
|
||||
typedef struct b3ManifoldConstraint b3ManifoldConstraint;
|
||||
typedef struct b3World b3World;
|
||||
|
||||
// Solver stages
|
||||
typedef enum b3SolverStageType
|
||||
{
|
||||
b3_stagePrepareJoints,
|
||||
b3_stagePrepareWideContacts,
|
||||
b3_stagePrepareContacts,
|
||||
b3_stageIntegrateVelocities,
|
||||
b3_stageWarmStart,
|
||||
b3_stageSolve,
|
||||
b3_stageIntegratePositions,
|
||||
b3_stageRelax,
|
||||
b3_stageRestitution,
|
||||
b3_stageStoreWideImpulses,
|
||||
b3_stageStoreImpulses,
|
||||
} b3SolverStageType;
|
||||
|
||||
typedef enum b3SolverBlockType
|
||||
{
|
||||
// Block for iterating across bodies.
|
||||
b3_bodyBlock,
|
||||
|
||||
// Block for iterating across joints. For prepare.
|
||||
b3_jointBlock,
|
||||
|
||||
// Block for iterating across wide contacts. For prepare and store.
|
||||
b3_wideContactBlock,
|
||||
|
||||
// Block for iterating across contacts. For prepare and store.
|
||||
b3_contactBlock,
|
||||
|
||||
// Block for iterating across joints of a single graph color.
|
||||
b3_graphJointBlock,
|
||||
|
||||
// Block for iterating across wide contacts of a single graph color.
|
||||
b3_graphWideContactBlock,
|
||||
|
||||
// Block for iterating across contacts of a single graph color.
|
||||
b3_graphContactBlock,
|
||||
|
||||
// Block for processing overflow constraints
|
||||
b3_overflowBlock,
|
||||
} b3SolverBlockType;
|
||||
|
||||
// Solver block describes a multithreaded unit of work.
|
||||
typedef struct b3SolverBlock
|
||||
{
|
||||
int startIndex;
|
||||
uint16_t count;
|
||||
// b3SolverBlockType
|
||||
uint8_t blockType;
|
||||
uint8_t colorIndex;
|
||||
} b3SolverBlock;
|
||||
|
||||
// A unit of multithreaded work along with atomic synchronization. The syncIndex grows
|
||||
// monotonically allowing the solver block to be re-used across sub-steps.
|
||||
typedef struct b3SyncBlock
|
||||
{
|
||||
b3SolverBlock block;
|
||||
b3AtomicInt syncIndex;
|
||||
} b3SyncBlock;
|
||||
|
||||
// Each stage must be completed before going to the next stage.
|
||||
// Non-iterative stages use a stage instance once while iterative stages re-use the same instance each iteration.
|
||||
typedef struct b3SolverStage
|
||||
{
|
||||
b3SyncBlock* blocks;
|
||||
b3SolverStageType type;
|
||||
int blockCount;
|
||||
uint8_t colorIndex;
|
||||
b3AtomicInt completionCount;
|
||||
} b3SolverStage;
|
||||
|
||||
// Constraint softness
|
||||
typedef struct b3Softness
|
||||
{
|
||||
float biasRate;
|
||||
float massScale;
|
||||
float impulseScale;
|
||||
} b3Softness;
|
||||
|
||||
// Prepare/store run as a flat parallel-for over the whole wide-constraint
|
||||
// range. Each span maps a slice of that range back to the owning color's
|
||||
// contacts so workers can decode flat wide-slot indices without touching
|
||||
// graph state. The spans array has one entry per active color plus a sentinel
|
||||
// whose start == wideContactCount.
|
||||
typedef struct b3WidePrepareSpan
|
||||
{
|
||||
int start;
|
||||
int count;
|
||||
int* contacts;
|
||||
} b3WidePrepareSpan;
|
||||
|
||||
typedef struct b3ContactPrepareSpan
|
||||
{
|
||||
int start;
|
||||
int count;
|
||||
b3ContactSpec* contacts;
|
||||
} b3ContactPrepareSpan;
|
||||
|
||||
typedef struct b3JointPrepareSpan
|
||||
{
|
||||
int start;
|
||||
int count;
|
||||
b3JointSim* joints;
|
||||
} b3JointPrepareSpan;
|
||||
|
||||
// Context for a time step. Recreated each time step.
|
||||
typedef struct b3StepContext
|
||||
{
|
||||
// time step
|
||||
float dt;
|
||||
|
||||
// inverse time step (0 if dt == 0).
|
||||
float inv_dt;
|
||||
|
||||
// sub-step
|
||||
float h;
|
||||
float inv_h;
|
||||
|
||||
int subStepCount;
|
||||
|
||||
b3Softness contactSoftness;
|
||||
b3Softness staticSoftness;
|
||||
|
||||
float restitutionThreshold;
|
||||
float maxLinearVelocity;
|
||||
|
||||
struct b3World* world;
|
||||
struct b3ConstraintGraph* graph;
|
||||
|
||||
// shortcut to body states from awake set
|
||||
b3BodyState* states;
|
||||
|
||||
// shortcut to body sims from awake set
|
||||
b3BodySim* sims;
|
||||
|
||||
// array of all shape ids for shapes that have enlarged AABBs
|
||||
int* enlargedShapes;
|
||||
int enlargedShapeCount;
|
||||
|
||||
// Array of bullet bodies that need continuous collision handling
|
||||
int* bulletBodies;
|
||||
b3AtomicInt bulletBodyCount;
|
||||
|
||||
// Contact ids for simplified parallel-for access. Used in narrow-phase.
|
||||
// These contacts may or may not be touching. They are associated with awake bodies.
|
||||
int* awakeContactIndices;
|
||||
|
||||
// Flat view of the wide contact constraint array used by prepare and store.
|
||||
// prepareSpans has activeColorCount + 1 entries, the last being a sentinel
|
||||
// at wideContactCount. wideContactConstraints is the contiguous base
|
||||
// pointer; per-color slices live at colors[i].wideConstraints.
|
||||
struct b3ContactConstraintWide* wideConstraints;
|
||||
b3WidePrepareSpan* widePrepareSpans;
|
||||
int wideContactCount;
|
||||
|
||||
// Similar for mesh/overflow contact constraints
|
||||
struct b3ManifoldConstraint* manifoldConstraints;
|
||||
struct b3ContactConstraint* contactConstraints;
|
||||
b3ContactPrepareSpan* contactPrepareSpans;
|
||||
b3ContactPrepareSpan* overflowSpans;
|
||||
b3JointPrepareSpan* jointPrepareSpans;
|
||||
|
||||
int activeColorCount;
|
||||
int workerCount;
|
||||
|
||||
b3SolverStage* stages;
|
||||
int stageCount;
|
||||
bool enableWarmStarting;
|
||||
|
||||
// padding to prevent false sharing
|
||||
char padding1[64];
|
||||
|
||||
// This atomic is central to multi-threaded solver task synchronization.
|
||||
// It prevents ABA problems by monotonically growing as the solver advances.
|
||||
// This means a delayed worker thread will catch up without repeating already completed
|
||||
// work (causing a race condition).
|
||||
// sync index (16-bits) | stage type (16-bits)
|
||||
b3AtomicU32 atomicSyncBits;
|
||||
|
||||
// padding to prevent false sharing
|
||||
char padding2[64];
|
||||
|
||||
// Race flag claimed by whichever runner reaches b3SolverTask with workerIndex 0 first.
|
||||
// The calling thread of b3World_Step also races for this slot so the orchestrator can
|
||||
// always make progress, regardless of how the user's task system schedules tasks (out
|
||||
// of order, fewer threads than workers, or synchronously inside enqueueTaskFcn). The
|
||||
// loser of the race no-ops as workerIndex 0.
|
||||
b3AtomicInt mainClaimed;
|
||||
|
||||
// padding to prevent false sharing
|
||||
char padding3[64];
|
||||
} b3StepContext;
|
||||
|
||||
void b3Solve( b3World* world, b3StepContext* stepContext );
|
||||
|
||||
static inline b3Softness b3MakeSoft( float hertz, float zeta, float h )
|
||||
{
|
||||
if ( hertz == 0.0f )
|
||||
{
|
||||
return B3_LITERAL( b3Softness ){
|
||||
.biasRate = 0.0f,
|
||||
.massScale = 0.0f,
|
||||
.impulseScale = 0.0f,
|
||||
};
|
||||
}
|
||||
|
||||
float omega = 2.0f * B3_PI * hertz;
|
||||
float a1 = 2.0f * zeta + h * omega;
|
||||
float a2 = h * omega * a1;
|
||||
float a3 = 1.0f / ( 1.0f + a2 );
|
||||
|
||||
// bias = w / (2 * z + hw)
|
||||
// massScale = hw * (2 * z + hw) / (1 + hw * (2 * z + hw))
|
||||
// impulseScale = 1 / (1 + hw * (2 * z + hw))
|
||||
|
||||
// If z == 0
|
||||
// bias = 1/h
|
||||
// massScale = hw^2 / (1 + hw^2)
|
||||
// impulseScale = 1 / (1 + hw^2)
|
||||
|
||||
// w -> inf
|
||||
// bias = 1/h
|
||||
// massScale = 1
|
||||
// impulseScale = 0
|
||||
|
||||
// if w = pi / 4 * inv_h
|
||||
// massScale = (pi/4)^2 / (1 + (pi/4)^2) = pi^2 / (16 + pi^2) ~= 0.38
|
||||
// impulseScale = 1 / (1 + (pi/4)^2) = 16 / (16 + pi^2) ~= 0.62
|
||||
|
||||
// In all cases:
|
||||
// massScale + impulseScale == 1
|
||||
|
||||
return ( b3Softness ){
|
||||
.biasRate = omega / a1,
|
||||
.massScale = a2 * a3,
|
||||
.impulseScale = a3,
|
||||
};
|
||||
}
|
||||
633
vendor/box3d/src/src/solver_set.c
vendored
Normal file
633
vendor/box3d/src/src/solver_set.c
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "solver_set.h"
|
||||
|
||||
#include "body.h"
|
||||
#include "constraint_graph.h"
|
||||
#include "contact.h"
|
||||
#include "island.h"
|
||||
#include "joint.h"
|
||||
#include "physics_world.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
void b3DestroySolverSet( b3World* world, int setIndex )
|
||||
{
|
||||
b3SolverSet* set = b3Array_Get( world->solverSets, setIndex );
|
||||
|
||||
b3Array_Destroy( set->bodySims );
|
||||
b3Array_Destroy( set->bodyStates );
|
||||
b3Array_Destroy( set->contactIndices );
|
||||
b3Array_Destroy( set->jointSims );
|
||||
b3Array_Destroy( set->islandSims );
|
||||
|
||||
b3FreeId( &world->solverSetIdPool, setIndex );
|
||||
*set = (b3SolverSet){ 0 };
|
||||
set->setIndex = B3_NULL_INDEX;
|
||||
}
|
||||
|
||||
// Wake a solver set. Does not merge islands.
|
||||
// Contacts can be in several places:
|
||||
// 1. non-touching contacts in the disabled set
|
||||
// 2. non-touching contacts already in the awake set
|
||||
// 3. touching contacts in the sleeping set
|
||||
// This handles contact types 1 and 3. Type 2 doesn't need any action.
|
||||
void b3WakeSolverSet( b3World* world, int setIndex )
|
||||
{
|
||||
B3_ASSERT( setIndex >= b3_firstSleepingSet );
|
||||
b3SolverSet* set = b3Array_Get( world->solverSets, setIndex );
|
||||
b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet );
|
||||
b3SolverSet* disabledSet = b3Array_Get( world->solverSets, b3_disabledSet );
|
||||
|
||||
b3Body* bodies = world->bodies.data;
|
||||
|
||||
int bodyCount = set->bodySims.count;
|
||||
for ( int i = 0; i < bodyCount; ++i )
|
||||
{
|
||||
b3BodySim* simSrc = set->bodySims.data + i;
|
||||
|
||||
b3Body* body = bodies + simSrc->bodyId;
|
||||
B3_ASSERT( body->setIndex == setIndex );
|
||||
body->setIndex = b3_awakeSet;
|
||||
body->localIndex = awakeSet->bodySims.count;
|
||||
|
||||
// Reset sleep timer
|
||||
body->sleepTime = 0.0f;
|
||||
|
||||
b3BodySim* simDst = b3Array_Emplace( awakeSet->bodySims );
|
||||
memcpy( simDst, simSrc, sizeof( b3BodySim ) );
|
||||
|
||||
b3BodyState* state = b3Array_Emplace( awakeSet->bodyStates );
|
||||
*state = b3_identityBodyState;
|
||||
state->flags = body->flags;
|
||||
|
||||
// move non-touching contacts from disabled set to awake set
|
||||
int contactKey = body->headContactKey;
|
||||
while ( contactKey != B3_NULL_INDEX )
|
||||
{
|
||||
int edgeIndex = contactKey & 1;
|
||||
int contactId = contactKey >> 1;
|
||||
|
||||
b3Contact* contact = b3Array_Get( world->contacts, contactId );
|
||||
contactKey = contact->edges[edgeIndex].nextKey;
|
||||
|
||||
if ( contact->setIndex != b3_disabledSet )
|
||||
{
|
||||
B3_ASSERT( contact->setIndex == b3_awakeSet || contact->setIndex == setIndex );
|
||||
continue;
|
||||
}
|
||||
|
||||
int localIndex = contact->localIndex;
|
||||
|
||||
B3_ASSERT( 0 <= localIndex && localIndex < disabledSet->contactIndices.count );
|
||||
B3_ASSERT( disabledSet->contactIndices.data[localIndex] == contactId );
|
||||
|
||||
B3_ASSERT( ( contact->flags & b3_contactTouchingFlag ) == 0 && contact->manifoldCount == 0 );
|
||||
|
||||
contact->setIndex = b3_awakeSet;
|
||||
contact->localIndex = awakeSet->contactIndices.count;
|
||||
b3Array_Push( awakeSet->contactIndices, contactId );
|
||||
|
||||
int movedLocalIndex = b3Array_RemoveSwap( disabledSet->contactIndices, localIndex );
|
||||
if ( movedLocalIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// fix moved element
|
||||
int movedContactIndex = disabledSet->contactIndices.data[localIndex];
|
||||
b3Contact* movedContact = b3Array_Get( world->contacts, movedContactIndex );
|
||||
B3_ASSERT( movedContact->localIndex == movedLocalIndex );
|
||||
movedContact->localIndex = localIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer touching contacts from sleeping set to constraint graph.
|
||||
{
|
||||
int contactCount = set->contactIndices.count;
|
||||
for ( int i = 0; i < contactCount; ++i )
|
||||
{
|
||||
int contactIndex = set->contactIndices.data[i];
|
||||
b3Contact* contact = b3Array_Get( world->contacts, contactIndex );
|
||||
B3_ASSERT( contact->flags & b3_contactTouchingFlag );
|
||||
B3_ASSERT( contact->flags & b3_simTouchingFlag );
|
||||
B3_ASSERT( contact->setIndex == setIndex );
|
||||
b3AddContactToGraph( world, contact );
|
||||
contact->setIndex = b3_awakeSet;
|
||||
}
|
||||
}
|
||||
|
||||
// transfer joints from sleeping set to awake set
|
||||
{
|
||||
int jointCount = set->jointSims.count;
|
||||
for ( int i = 0; i < jointCount; ++i )
|
||||
{
|
||||
b3JointSim* jointSim = set->jointSims.data + i;
|
||||
b3Joint* joint = b3Array_Get( world->joints, jointSim->jointId );
|
||||
B3_ASSERT( joint->setIndex == setIndex );
|
||||
b3AddJointToGraph( world, jointSim, joint );
|
||||
joint->setIndex = b3_awakeSet;
|
||||
}
|
||||
}
|
||||
|
||||
// transfer island from sleeping set to awake set
|
||||
// Usually a sleeping set has only one island, but it is possible
|
||||
// that joints are created between sleeping islands and they
|
||||
// are moved to the same sleeping set.
|
||||
{
|
||||
int islandCount = set->islandSims.count;
|
||||
for ( int i = 0; i < islandCount; ++i )
|
||||
{
|
||||
b3IslandSim* islandSrc = set->islandSims.data + i;
|
||||
b3Island* island = b3Array_Get( world->islands, islandSrc->islandId );
|
||||
island->setIndex = b3_awakeSet;
|
||||
island->localIndex = awakeSet->islandSims.count;
|
||||
b3IslandSim* islandDst = b3Array_Emplace( awakeSet->islandSims );
|
||||
memcpy( islandDst, islandSrc, sizeof( b3IslandSim ) );
|
||||
}
|
||||
}
|
||||
|
||||
// destroy the sleeping set
|
||||
b3DestroySolverSet( world, setIndex );
|
||||
}
|
||||
|
||||
void b3TrySleepIsland( b3World* world, int islandId )
|
||||
{
|
||||
b3Island* island = b3Array_Get( world->islands, islandId );
|
||||
B3_ASSERT( island->setIndex == b3_awakeSet );
|
||||
|
||||
// Cannot put an island to sleep while it has a pending split and more than one body.
|
||||
if ( island->constraintRemoveCount > 0 && island->bodies.count > 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// island is sleeping
|
||||
// - create new sleeping solver set
|
||||
// - move island to sleeping solver set
|
||||
// - identify non-touching contacts that should move to sleeping solver set or disabled set
|
||||
// - remove old island
|
||||
// - fix island
|
||||
int sleepSetId = b3AllocId( &world->solverSetIdPool );
|
||||
if ( sleepSetId == world->solverSets.count )
|
||||
{
|
||||
b3SolverSet set = { 0 };
|
||||
set.setIndex = B3_NULL_INDEX;
|
||||
b3Array_Push( world->solverSets, set );
|
||||
}
|
||||
|
||||
b3SolverSet* sleepSet = b3Array_Get( world->solverSets, sleepSetId );
|
||||
*sleepSet = (b3SolverSet){ 0 };
|
||||
|
||||
// grab awake set after creating the sleep set because the solver set array may have been resized
|
||||
b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet );
|
||||
b3SolverSet* disabledSet = b3Array_Get( world->solverSets, b3_disabledSet );
|
||||
B3_ASSERT( 0 <= island->localIndex && island->localIndex < awakeSet->islandSims.count );
|
||||
|
||||
sleepSet->setIndex = sleepSetId;
|
||||
b3Array_Reserve( sleepSet->bodySims, island->bodies.count );
|
||||
b3Array_Reserve( sleepSet->contactIndices, island->contacts.count );
|
||||
b3Array_Reserve( sleepSet->jointSims, island->joints.count );
|
||||
|
||||
// move awake bodies to sleeping set
|
||||
// this shuffles around bodies in the awake set
|
||||
{
|
||||
for ( int i = 0; i < island->bodies.count; ++i )
|
||||
{
|
||||
int bodyId = island->bodies.data[i];
|
||||
b3Body* body = b3Array_Get( world->bodies, bodyId );
|
||||
B3_ASSERT( body->setIndex == b3_awakeSet );
|
||||
B3_ASSERT( body->islandId == islandId );
|
||||
B3_ASSERT( body->islandIndex == i );
|
||||
|
||||
// Update the body move event to indicate this body fell asleep
|
||||
// It could happen the body is forced asleep before it ever moves.
|
||||
if ( body->bodyMoveIndex != B3_NULL_INDEX )
|
||||
{
|
||||
b3BodyMoveEvent* moveEvent = b3Array_Get( world->bodyMoveEvents, body->bodyMoveIndex );
|
||||
B3_ASSERT( moveEvent->bodyId.index1 - 1 == bodyId );
|
||||
B3_ASSERT( moveEvent->bodyId.generation == body->generation );
|
||||
moveEvent->fellAsleep = true;
|
||||
body->bodyMoveIndex = B3_NULL_INDEX;
|
||||
}
|
||||
|
||||
int awakeBodyIndex = body->localIndex;
|
||||
b3BodySim* awakeSim = b3Array_Get( awakeSet->bodySims, awakeBodyIndex );
|
||||
|
||||
// move body sim to sleep set
|
||||
int sleepBodyIndex = sleepSet->bodySims.count;
|
||||
b3BodySim* sleepBodySim = b3Array_Emplace( sleepSet->bodySims );
|
||||
memcpy( sleepBodySim, awakeSim, sizeof( b3BodySim ) );
|
||||
|
||||
int movedIndex = b3Array_RemoveSwap( awakeSet->bodySims, awakeBodyIndex );
|
||||
if ( movedIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// fix local index on moved element
|
||||
b3BodySim* movedSim = awakeSet->bodySims.data + awakeBodyIndex;
|
||||
int movedId = movedSim->bodyId;
|
||||
b3Body* movedBody = b3Array_Get( world->bodies, movedId );
|
||||
B3_ASSERT( movedBody->localIndex == movedIndex );
|
||||
movedBody->localIndex = awakeBodyIndex;
|
||||
}
|
||||
|
||||
// destroy state, no need to clone
|
||||
b3Array_RemoveSwap( awakeSet->bodyStates, awakeBodyIndex );
|
||||
|
||||
body->setIndex = sleepSetId;
|
||||
body->localIndex = sleepBodyIndex;
|
||||
|
||||
// Move non-touching contacts to the disabled set.
|
||||
// Non-touching contacts may exist between sleeping islands and there is no clear ownership.
|
||||
int contactKey = body->headContactKey;
|
||||
while ( contactKey != B3_NULL_INDEX )
|
||||
{
|
||||
int contactId = contactKey >> 1;
|
||||
int edgeIndex = contactKey & 1;
|
||||
|
||||
b3Contact* contact = b3Array_Get( world->contacts, contactId );
|
||||
|
||||
B3_ASSERT( contact->setIndex == b3_awakeSet || contact->setIndex == b3_disabledSet );
|
||||
contactKey = contact->edges[edgeIndex].nextKey;
|
||||
|
||||
if ( contact->setIndex == b3_disabledSet )
|
||||
{
|
||||
// already moved to disabled set by another body in the island
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( contact->colorIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// contact is touching and will be moved separately
|
||||
B3_ASSERT( ( contact->flags & b3_contactTouchingFlag ) != 0 );
|
||||
continue;
|
||||
}
|
||||
|
||||
// the other body may still be awake, it still may go to sleep and then it will be responsible
|
||||
// for moving this contact to the disabled set.
|
||||
int otherEdgeIndex = edgeIndex ^ 1;
|
||||
int otherBodyId = contact->edges[otherEdgeIndex].bodyId;
|
||||
b3Body* otherBody = b3Array_Get( world->bodies, otherBodyId );
|
||||
if ( otherBody->setIndex == b3_awakeSet )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int localIndex = contact->localIndex;
|
||||
B3_ASSERT( awakeSet->contactIndices.data[localIndex] == contactId );
|
||||
|
||||
B3_ASSERT( contact->manifoldCount == 0 );
|
||||
B3_ASSERT( ( contact->flags & b3_contactTouchingFlag ) == 0 );
|
||||
|
||||
// Move the non-touching contact to the disabled set.
|
||||
contact->setIndex = b3_disabledSet;
|
||||
|
||||
// This is mandatory for validation to work correctly
|
||||
contact->localIndex = disabledSet->contactIndices.count;
|
||||
b3Array_Push( disabledSet->contactIndices, contact->contactId );
|
||||
|
||||
int movedLocalIndex = b3Array_RemoveSwap( awakeSet->contactIndices, localIndex );
|
||||
if ( movedLocalIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// fix moved element
|
||||
int movedContactIndex = awakeSet->contactIndices.data[localIndex];
|
||||
b3Contact* movedContact = b3Array_Get( world->contacts, movedContactIndex );
|
||||
B3_ASSERT( movedContact->localIndex == movedLocalIndex );
|
||||
movedContact->localIndex = localIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move touching contacts to sleeping set
|
||||
// this shuffles contacts in the awake set
|
||||
{
|
||||
for ( int i = 0; i < island->contacts.count; ++i )
|
||||
{
|
||||
int contactId = island->contacts.data[i].contactId;
|
||||
b3Contact* contact = b3Array_Get( world->contacts, contactId );
|
||||
B3_ASSERT( contact->setIndex == b3_awakeSet );
|
||||
B3_ASSERT( contact->islandId == islandId );
|
||||
B3_ASSERT( contact->islandIndex == i );
|
||||
int colorIndex = contact->colorIndex;
|
||||
B3_ASSERT( 0 <= colorIndex && colorIndex < B3_GRAPH_COLOR_COUNT );
|
||||
|
||||
b3GraphColor* color = world->constraintGraph.colors + colorIndex;
|
||||
|
||||
// Remove bodies from graph coloring associated with this constraint
|
||||
if ( colorIndex != B3_OVERFLOW_INDEX )
|
||||
{
|
||||
// might clear a bit for a static body, but this has no effect
|
||||
b3ClearBit( &color->bodySet, contact->edges[0].bodyId );
|
||||
b3ClearBit( &color->bodySet, contact->edges[1].bodyId );
|
||||
}
|
||||
|
||||
int sleepContactIndex = sleepSet->contactIndices.count;
|
||||
b3Array_Push( sleepSet->contactIndices, contactId );
|
||||
|
||||
int localIndex = contact->localIndex;
|
||||
if ( ( contact->flags & b3_simMeshContact ) || colorIndex == B3_OVERFLOW_INDEX )
|
||||
{
|
||||
int movedLocalIndex = b3Array_RemoveSwap( color->contacts, localIndex );
|
||||
if ( movedLocalIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// fix moved element
|
||||
int movedContactId = color->contacts.data[localIndex].contactId;
|
||||
b3Contact* movedContact = b3Array_Get( world->contacts, movedContactId );
|
||||
B3_ASSERT( movedContact->localIndex == movedLocalIndex );
|
||||
movedContact->localIndex = localIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int movedLocalIndex = b3Array_RemoveSwap( color->convexContacts, localIndex );
|
||||
if ( movedLocalIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// fix moved element
|
||||
int movedContactId = color->convexContacts.data[localIndex];
|
||||
b3Contact* movedContact = b3Array_Get( world->contacts, movedContactId );
|
||||
B3_ASSERT( movedContact->localIndex == movedLocalIndex );
|
||||
movedContact->localIndex = localIndex;
|
||||
}
|
||||
}
|
||||
|
||||
contact->setIndex = sleepSetId;
|
||||
contact->colorIndex = B3_NULL_INDEX;
|
||||
contact->localIndex = sleepContactIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// move joints
|
||||
// this shuffles joints in the awake set
|
||||
{
|
||||
for ( int i = 0; i < island->joints.count; ++i )
|
||||
{
|
||||
int jointId = island->joints.data[i].jointId;
|
||||
b3Joint* joint = b3Array_Get( world->joints, jointId );
|
||||
B3_ASSERT( joint->setIndex == b3_awakeSet );
|
||||
B3_ASSERT( joint->islandId == islandId );
|
||||
B3_ASSERT( joint->islandIndex == i );
|
||||
int colorIndex = joint->colorIndex;
|
||||
int localIndex = joint->localIndex;
|
||||
|
||||
B3_ASSERT( 0 <= colorIndex && colorIndex < B3_GRAPH_COLOR_COUNT );
|
||||
|
||||
b3GraphColor* color = world->constraintGraph.colors + colorIndex;
|
||||
|
||||
b3JointSim* awakeJointSim = b3Array_Get( color->jointSims, localIndex );
|
||||
|
||||
if ( colorIndex != B3_OVERFLOW_INDEX )
|
||||
{
|
||||
// might clear a bit for a static body, but this has no effect
|
||||
b3ClearBit( &color->bodySet, joint->edges[0].bodyId );
|
||||
b3ClearBit( &color->bodySet, joint->edges[1].bodyId );
|
||||
}
|
||||
|
||||
int sleepJointIndex = sleepSet->jointSims.count;
|
||||
b3JointSim* sleepJointSim = b3Array_Emplace( sleepSet->jointSims );
|
||||
memcpy( sleepJointSim, awakeJointSim, sizeof( b3JointSim ) );
|
||||
|
||||
int movedIndex = b3Array_RemoveSwap( color->jointSims, localIndex );
|
||||
if ( movedIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// fix moved element
|
||||
b3JointSim* movedJointSim = color->jointSims.data + localIndex;
|
||||
int movedId = movedJointSim->jointId;
|
||||
b3Joint* movedJoint = b3Array_Get( world->joints, movedId );
|
||||
B3_ASSERT( movedJoint->localIndex == movedIndex );
|
||||
movedJoint->localIndex = localIndex;
|
||||
}
|
||||
|
||||
joint->setIndex = sleepSetId;
|
||||
joint->colorIndex = B3_NULL_INDEX;
|
||||
joint->localIndex = sleepJointIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// move island struct
|
||||
{
|
||||
B3_ASSERT( island->setIndex == b3_awakeSet );
|
||||
|
||||
int islandIndex = island->localIndex;
|
||||
b3IslandSim* sleepIsland = b3Array_Emplace( sleepSet->islandSims );
|
||||
sleepIsland->islandId = islandId;
|
||||
|
||||
int movedIslandIndex = b3Array_RemoveSwap( awakeSet->islandSims, islandIndex );
|
||||
if ( movedIslandIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// fix index on moved element
|
||||
b3IslandSim* movedIslandSim = awakeSet->islandSims.data + islandIndex;
|
||||
int movedIslandId = movedIslandSim->islandId;
|
||||
b3Island* movedIsland = b3Array_Get( world->islands, movedIslandId );
|
||||
B3_ASSERT( movedIsland->localIndex == movedIslandIndex );
|
||||
movedIsland->localIndex = islandIndex;
|
||||
}
|
||||
|
||||
island->setIndex = sleepSetId;
|
||||
island->localIndex = 0;
|
||||
}
|
||||
|
||||
if ( world->splitIslandId == islandId )
|
||||
{
|
||||
world->splitIslandId = B3_NULL_INDEX;
|
||||
}
|
||||
|
||||
b3ValidateSolverSets( world );
|
||||
}
|
||||
|
||||
// This is called when joints are created between sets. I want to allow the sets
|
||||
// to continue sleeping if both are asleep. Otherwise one set is waked.
|
||||
// Islands will get merge when the set is woke.
|
||||
void b3MergeSolverSets( b3World* world, int setId1, int setId2 )
|
||||
{
|
||||
B3_ASSERT( setId1 >= b3_firstSleepingSet );
|
||||
B3_ASSERT( setId2 >= b3_firstSleepingSet );
|
||||
b3SolverSet* set1 = b3Array_Get( world->solverSets, setId1 );
|
||||
b3SolverSet* set2 = b3Array_Get( world->solverSets, setId2 );
|
||||
|
||||
// Move the fewest number of bodies
|
||||
if ( set1->bodySims.count < set2->bodySims.count )
|
||||
{
|
||||
b3SolverSet* tempSet = set1;
|
||||
set1 = set2;
|
||||
set2 = tempSet;
|
||||
|
||||
int tempId = setId1;
|
||||
setId1 = setId2;
|
||||
setId2 = tempId;
|
||||
}
|
||||
|
||||
// transfer bodies
|
||||
{
|
||||
b3Body* bodies = world->bodies.data;
|
||||
int bodyCount = set2->bodySims.count;
|
||||
for ( int i = 0; i < bodyCount; ++i )
|
||||
{
|
||||
b3BodySim* simSrc = set2->bodySims.data + i;
|
||||
|
||||
b3Body* body = bodies + simSrc->bodyId;
|
||||
B3_ASSERT( body->setIndex == setId2 );
|
||||
body->setIndex = setId1;
|
||||
body->localIndex = set1->bodySims.count;
|
||||
|
||||
b3BodySim* simDst = b3Array_Emplace( set1->bodySims );
|
||||
memcpy( simDst, simSrc, sizeof( b3BodySim ) );
|
||||
}
|
||||
}
|
||||
|
||||
// transfer contacts
|
||||
{
|
||||
int contactCount = set2->contactIndices.count;
|
||||
for ( int i = 0; i < contactCount; ++i )
|
||||
{
|
||||
int contactIndex = set2->contactIndices.data[i];
|
||||
b3Contact* contact = b3Array_Get( world->contacts, contactIndex );
|
||||
B3_ASSERT( contact->setIndex == setId2 );
|
||||
contact->setIndex = setId1;
|
||||
contact->localIndex = set1->contactIndices.count;
|
||||
b3Array_Push( set1->contactIndices, contactIndex );
|
||||
}
|
||||
}
|
||||
|
||||
// transfer joints
|
||||
{
|
||||
int jointCount = set2->jointSims.count;
|
||||
for ( int i = 0; i < jointCount; ++i )
|
||||
{
|
||||
b3JointSim* jointSrc = set2->jointSims.data + i;
|
||||
|
||||
b3Joint* joint = b3Array_Get( world->joints, jointSrc->jointId );
|
||||
B3_ASSERT( joint->setIndex == setId2 );
|
||||
joint->setIndex = setId1;
|
||||
joint->localIndex = set1->jointSims.count;
|
||||
|
||||
b3JointSim* jointDst = b3Array_Emplace( set1->jointSims );
|
||||
memcpy( jointDst, jointSrc, sizeof( b3JointSim ) );
|
||||
}
|
||||
}
|
||||
|
||||
// transfer islands
|
||||
{
|
||||
int islandCount = set2->islandSims.count;
|
||||
for ( int i = 0; i < islandCount; ++i )
|
||||
{
|
||||
b3IslandSim* islandSrc = set2->islandSims.data + i;
|
||||
int islandId = islandSrc->islandId;
|
||||
|
||||
b3Island* island = b3Array_Get( world->islands, islandId );
|
||||
island->setIndex = setId1;
|
||||
island->localIndex = set1->islandSims.count;
|
||||
|
||||
b3IslandSim* islandDst = b3Array_Emplace( set1->islandSims );
|
||||
memcpy( islandDst, islandSrc, sizeof( b3IslandSim ) );
|
||||
}
|
||||
}
|
||||
|
||||
// destroy the merged set
|
||||
// Warning: need to be careful not to destroy things that got transferred, like triangle caches.
|
||||
b3DestroySolverSet( world, setId2 );
|
||||
|
||||
b3ValidateSolverSets( world );
|
||||
}
|
||||
|
||||
void b3TransferBody( b3World* world, b3SolverSet* targetSet, b3SolverSet* sourceSet, b3Body* body )
|
||||
{
|
||||
if ( targetSet == sourceSet )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int sourceIndex = body->localIndex;
|
||||
b3BodySim* sourceSim = b3Array_Get( sourceSet->bodySims, sourceIndex );
|
||||
|
||||
int targetIndex = targetSet->bodySims.count;
|
||||
b3BodySim* targetSim = b3Array_Emplace( targetSet->bodySims );
|
||||
memcpy( targetSim, sourceSim, sizeof( b3BodySim ) );
|
||||
|
||||
// Clear transient body flags
|
||||
targetSim->flags &= ~( b3_isFast | b3_isSpeedCapped | b3_hadTimeOfImpact );
|
||||
|
||||
// Remove body sim from solver set that owns it
|
||||
int movedIndex = b3Array_RemoveSwap( sourceSet->bodySims, sourceIndex );
|
||||
if ( movedIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// Fix moved body index
|
||||
b3BodySim* movedSim = sourceSet->bodySims.data + sourceIndex;
|
||||
int movedId = movedSim->bodyId;
|
||||
b3Body* movedBody = b3Array_Get( world->bodies, movedId );
|
||||
B3_ASSERT( movedBody->localIndex == movedIndex );
|
||||
movedBody->localIndex = sourceIndex;
|
||||
}
|
||||
|
||||
if ( sourceSet->setIndex == b3_awakeSet )
|
||||
{
|
||||
b3Array_RemoveSwap( sourceSet->bodyStates, sourceIndex );
|
||||
}
|
||||
else if ( targetSet->setIndex == b3_awakeSet )
|
||||
{
|
||||
b3BodyState* state = b3Array_Emplace( targetSet->bodyStates );
|
||||
*state = b3_identityBodyState;
|
||||
state->flags = body->flags;
|
||||
}
|
||||
|
||||
body->setIndex = targetSet->setIndex;
|
||||
body->localIndex = targetIndex;
|
||||
}
|
||||
|
||||
void b3TransferJoint( b3World* world, b3SolverSet* targetSet, b3SolverSet* sourceSet, b3Joint* joint )
|
||||
{
|
||||
if ( targetSet == sourceSet )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int localIndex = joint->localIndex;
|
||||
int colorIndex = joint->colorIndex;
|
||||
|
||||
// Retrieve source.
|
||||
b3JointSim* sourceSim;
|
||||
if ( sourceSet->setIndex == b3_awakeSet )
|
||||
{
|
||||
B3_ASSERT( 0 <= colorIndex && colorIndex < B3_GRAPH_COLOR_COUNT );
|
||||
b3GraphColor* color = world->constraintGraph.colors + colorIndex;
|
||||
|
||||
sourceSim = b3Array_Get( color->jointSims, localIndex );
|
||||
}
|
||||
else
|
||||
{
|
||||
B3_ASSERT( colorIndex == B3_NULL_INDEX );
|
||||
sourceSim = b3Array_Get( sourceSet->jointSims, localIndex );
|
||||
}
|
||||
|
||||
// Create target and copy. Fix joint.
|
||||
if ( targetSet->setIndex == b3_awakeSet )
|
||||
{
|
||||
b3AddJointToGraph( world, sourceSim, joint );
|
||||
joint->setIndex = b3_awakeSet;
|
||||
}
|
||||
else
|
||||
{
|
||||
joint->setIndex = targetSet->setIndex;
|
||||
joint->localIndex = targetSet->jointSims.count;
|
||||
joint->colorIndex = B3_NULL_INDEX;
|
||||
|
||||
b3JointSim* targetSim = b3Array_Emplace( targetSet->jointSims );
|
||||
memcpy( targetSim, sourceSim, sizeof( b3JointSim ) );
|
||||
}
|
||||
|
||||
// Destroy source.
|
||||
if ( sourceSet->setIndex == b3_awakeSet )
|
||||
{
|
||||
b3RemoveJointFromGraph( world, joint->edges[0].bodyId, joint->edges[1].bodyId, colorIndex, localIndex );
|
||||
}
|
||||
else
|
||||
{
|
||||
int movedIndex = b3Array_RemoveSwap( sourceSet->jointSims, localIndex );
|
||||
if ( movedIndex != B3_NULL_INDEX )
|
||||
{
|
||||
// fix swapped element
|
||||
b3JointSim* movedJointSim = sourceSet->jointSims.data + localIndex;
|
||||
int movedId = movedJointSim->jointId;
|
||||
b3Joint* movedJoint = b3Array_Get( world->joints, movedId );
|
||||
movedJoint->localIndex = localIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
vendor/box3d/src/src/solver_set.h
vendored
Normal file
67
vendor/box3d/src/src/solver_set.h
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "container.h"
|
||||
#include "contact.h"
|
||||
|
||||
typedef struct b3Body b3Body;
|
||||
typedef struct b3BodySim b3BodySim;
|
||||
typedef struct b3BodyState b3BodyState;
|
||||
typedef struct b3IslandSim b3IslandSim;
|
||||
typedef struct b3Joint b3Joint;
|
||||
typedef struct b3JointSim b3JointSim;
|
||||
typedef struct b3World b3World;
|
||||
|
||||
b3DeclareArray( b3BodySim );
|
||||
b3DeclareArray( b3BodyState );
|
||||
b3DeclareArray( b3JointSim );
|
||||
b3DeclareArray( b3IslandSim );
|
||||
|
||||
// This holds solver set data. The following sets are used:
|
||||
// - static set for all static bodies and joints between static bodies
|
||||
// - active set for all active bodies with body states (no
|
||||
// contacts or joints)
|
||||
// - disabled set for disabled bodies and their joints
|
||||
// - all further sets are sleeping island sets along with their contacts and joints
|
||||
// The purpose of solver sets is to achieve high memory locality.
|
||||
// https://www.youtube.com/watch?v=nZNd5FjSquk
|
||||
typedef struct b3SolverSet
|
||||
{
|
||||
// Body array. Empty for unused set.
|
||||
b3Array( b3BodySim ) bodySims;
|
||||
|
||||
// Body state only exists for active set
|
||||
b3Array( b3BodyState ) bodyStates;
|
||||
|
||||
// This holds sleeping/disabled joints. Empty for static/active set.
|
||||
b3Array( b3JointSim ) jointSims;
|
||||
|
||||
// This holds all contacts for sleeping sets.
|
||||
// This holds non-touching contacts for the awake set.
|
||||
// This should be empty for the static and disabled sets.
|
||||
b3Array( int ) contactIndices;
|
||||
|
||||
// The awake set has an array of islands. Sleeping sets normally have a single islands. However, joints
|
||||
// created between sleeping sets causes the sets to merge, leaving them with multiple islands. These sleeping
|
||||
// islands will be naturally merged with the set is woken.
|
||||
// The static and disabled sets have no islands.
|
||||
// Islands live in the solver sets to limit the number of islands that need to be considered for sleeping.
|
||||
b3Array( b3IslandSim ) islandSims;
|
||||
|
||||
// Aligns with b3World::solverSetIdPool. Used to create a stable id for body/contact/joint/islands.
|
||||
int setIndex;
|
||||
} b3SolverSet;
|
||||
|
||||
void b3DestroySolverSet( b3World* world, int setIndex );
|
||||
|
||||
void b3WakeSolverSet( b3World* world, int setIndex );
|
||||
void b3TrySleepIsland( b3World* world, int islandId );
|
||||
|
||||
// Merge set 2 into set 1 then destroy set 2.
|
||||
// Warning: any pointers into these sets will be orphaned.
|
||||
void b3MergeSolverSets( b3World* world, int setIndex1, int setIndex2 );
|
||||
|
||||
void b3TransferBody( b3World* world, b3SolverSet* targetSet, b3SolverSet* sourceSet, b3Body* body );
|
||||
void b3TransferJoint( b3World* world, b3SolverSet* targetSet, b3SolverSet* sourceSet, b3Joint* joint );
|
||||
247
vendor/box3d/src/src/sphere.c
vendored
Normal file
247
vendor/box3d/src/src/sphere.c
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Dirk Gregorius contributed portions of this code
|
||||
|
||||
#include "math_internal.h"
|
||||
#include "shape.h"
|
||||
|
||||
#include "box3d/base.h"
|
||||
#include "box3d/collision.h"
|
||||
#include "box3d/constants.h"
|
||||
|
||||
b3MassData b3ComputeSphereMass( const b3Sphere* shape, float density )
|
||||
{
|
||||
b3Vec3 center = shape->center;
|
||||
float radius = shape->radius;
|
||||
|
||||
float volume = 4.0f / 3.0f * B3_PI * radius * radius * radius;
|
||||
float mass = volume * density;
|
||||
float ixx = 0.4f * mass * radius * radius;
|
||||
|
||||
b3MassData out;
|
||||
out.mass = mass;
|
||||
out.center = center;
|
||||
|
||||
// Inertia about the center of mass
|
||||
out.inertia = b3MakeDiagonalMatrix( ixx, ixx, ixx );
|
||||
return out;
|
||||
}
|
||||
|
||||
b3AABB b3ComputeSphereAABB( const b3Sphere* shape, b3Transform transform )
|
||||
{
|
||||
b3Vec3 center = b3TransformPoint( transform, shape->center );
|
||||
float radius = shape->radius;
|
||||
b3Vec3 extent = { radius, radius, radius };
|
||||
return ( b3AABB ){ b3Sub( center, extent ), b3Add( center, extent ) };
|
||||
}
|
||||
|
||||
b3AABB b3ComputeSweptSphereAABB( const b3Sphere* shape, b3Transform xf1, b3Transform xf2 )
|
||||
{
|
||||
b3Vec3 r = { shape->radius, shape->radius, shape->radius };
|
||||
b3Vec3 center1 = b3TransformPoint( xf1, shape->center );
|
||||
b3Vec3 center2 = b3TransformPoint( xf2, shape->center );
|
||||
return ( b3AABB ){ b3Sub( b3Min( center1, center2 ), r ), b3Add( b3Max( center1, center2 ), r ) };
|
||||
}
|
||||
|
||||
bool b3OverlapSphere( const b3Sphere* shape, b3Transform shapeTransform, const b3ShapeProxy* proxy )
|
||||
{
|
||||
b3DistanceInput input;
|
||||
input.proxyA = ( b3ShapeProxy ){ &shape->center, 1, shape->radius };
|
||||
input.proxyB = *proxy;
|
||||
input.transform = b3InvMulTransforms( shapeTransform, b3Transform_identity );
|
||||
input.useRadii = true;
|
||||
|
||||
b3SimplexCache cache = { 0 };
|
||||
b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 );
|
||||
return output.distance < B3_OVERLAP_SLOP;
|
||||
}
|
||||
|
||||
// Precision Improvements for Ray / Sphere Intersection - Ray Tracing Gems 2019
|
||||
// http://www.codercorner.com/blog/?p=321
|
||||
b3CastOutput b3RayCastSphere(const b3Sphere* shape, const b3RayCastInput* input )
|
||||
{
|
||||
B3_ASSERT( b3IsValidRay( input ) );
|
||||
b3CastOutput output = { 0 };
|
||||
|
||||
b3Vec3 p = shape->center;
|
||||
|
||||
// Shift ray so sphere center is the origin
|
||||
b3Vec3 s = b3Sub( input->origin, p );
|
||||
|
||||
float r = shape->radius;
|
||||
float rr = r * r;
|
||||
|
||||
float length;
|
||||
b3Vec3 d = b3GetLengthAndNormalize( &length, input->translation );
|
||||
if ( length == 0.0f )
|
||||
{
|
||||
// zero length ray
|
||||
|
||||
if ( b3LengthSquared( s ) < rr )
|
||||
{
|
||||
// initial overlap
|
||||
output.point = input->origin;
|
||||
output.hit = true;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Find closest point on ray to origin
|
||||
|
||||
// solve: dot(s + t * d, d) = 0
|
||||
float t = -b3Dot( s, d );
|
||||
|
||||
// c is the closest point on the line to the origin
|
||||
b3Vec3 c = b3MulAdd( s, t, d );
|
||||
|
||||
float cc = b3Dot( c, c );
|
||||
|
||||
if ( cc > rr )
|
||||
{
|
||||
// closest point is outside the sphere
|
||||
return output;
|
||||
}
|
||||
|
||||
// Pythagoras
|
||||
float h = sqrtf( rr - cc );
|
||||
|
||||
float fraction = t - h;
|
||||
|
||||
if ( fraction < 0.0f || input->maxFraction * length < fraction )
|
||||
{
|
||||
// intersection is point outside the range of the ray segment
|
||||
|
||||
if ( b3LengthSquared( s ) < rr )
|
||||
{
|
||||
// initial overlap
|
||||
output.point = input->origin;
|
||||
output.hit = true;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
b3Vec3 hitPoint = b3MulAdd( s, fraction, d );
|
||||
|
||||
output.fraction = fraction / length;
|
||||
|
||||
if ( output.fraction > input->maxFraction )
|
||||
{
|
||||
b3Log( "sphere input fraction = %g, output fraction = %g", input->maxFraction, output.fraction );
|
||||
output.fraction = input->maxFraction;
|
||||
}
|
||||
|
||||
output.normal = b3Normalize( hitPoint );
|
||||
output.point = b3MulAdd( p, shape->radius, output.normal );
|
||||
output.hit = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Precision Improvements for Ray / Sphere Intersection - Ray Tracing Gems 2019
|
||||
// http://www.codercorner.com/blog/?p=321
|
||||
// This will do interior hits.
|
||||
b3CastOutput b3RayCastHollowSphere( const b3Sphere* sphere, const b3RayCastInput* input )
|
||||
{
|
||||
b3Vec3 p = sphere->center;
|
||||
|
||||
b3CastOutput output = { 0 };
|
||||
|
||||
// Shift ray so sphere center is the origin
|
||||
b3Vec3 s = b3Sub( input->origin, p );
|
||||
b3Vec3 d = b3Normalize( input->translation );
|
||||
|
||||
// Find closest point on ray to origin
|
||||
|
||||
// solve: dot(s + t * d, d) = 0
|
||||
float t = -b3Dot( s, d );
|
||||
|
||||
// c is the closest point on the line to the origin
|
||||
b3Vec3 c = b3MulAdd( s, t, d );
|
||||
|
||||
float cc = b3Dot( c, c );
|
||||
float r = sphere->radius;
|
||||
float rr = r * r;
|
||||
|
||||
if ( cc > rr )
|
||||
{
|
||||
// closest point is outside the sphere
|
||||
return output;
|
||||
}
|
||||
|
||||
// Pythagoras
|
||||
float h = sqrtf( rr - cc );
|
||||
|
||||
float fraction = t - h;
|
||||
|
||||
if ( fraction < 0.0f )
|
||||
{
|
||||
fraction = t + h;
|
||||
}
|
||||
|
||||
if ( fraction < 0.0f )
|
||||
{
|
||||
// behind the ray
|
||||
return output;
|
||||
}
|
||||
|
||||
if (fraction > input->maxFraction)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
b3Vec3 hitPoint = b3MulAdd( s, fraction, d );
|
||||
|
||||
output.fraction = fraction;
|
||||
output.normal = b3Normalize( hitPoint );
|
||||
output.point = b3MulAdd( p, sphere->radius, output.normal );
|
||||
output.hit = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
b3CastOutput b3ShapeCastSphere( const b3Sphere* sphere, const b3ShapeCastInput* input )
|
||||
{
|
||||
b3ShapeCastPairInput pairInput;
|
||||
pairInput.proxyA = ( b3ShapeProxy ){ &sphere->center, 1, sphere->radius };
|
||||
pairInput.proxyB = input->proxy;
|
||||
pairInput.transform = b3Transform_identity;
|
||||
pairInput.translationB = input->translation;
|
||||
pairInput.maxFraction = input->maxFraction;
|
||||
pairInput.canEncroach = input->canEncroach;
|
||||
|
||||
b3CastOutput output = b3ShapeCast( &pairInput );
|
||||
return output;
|
||||
}
|
||||
|
||||
int b3CollideMoverAndSphere( b3PlaneResult* result, const b3Sphere* shape, const b3Capsule* mover )
|
||||
{
|
||||
float totalRadius = mover->radius + shape->radius;
|
||||
b3Vec3 closest = b3PointToSegmentDistance( mover->center1, mover->center2, shape->center );
|
||||
|
||||
// The normal points from the sphere toward the mover.
|
||||
float distance;
|
||||
b3Vec3 normal = b3GetLengthAndNormalize( &distance, b3Sub( closest, shape->center ) );
|
||||
|
||||
if ( distance > totalRadius )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
float linearSlop = B3_LINEAR_SLOP;
|
||||
if ( distance < linearSlop )
|
||||
{
|
||||
// Deep overlap: the mover axis passes through the sphere center, so no
|
||||
// direction is preferred. Push perpendicular to the mover axis.
|
||||
float length;
|
||||
b3Vec3 axis = b3GetLengthAndNormalize( &length, b3Sub( mover->center2, mover->center1 ) );
|
||||
normal = length > linearSlop ? b3Perp( axis ) : b3Vec3_axisY;
|
||||
distance = 0.0f;
|
||||
}
|
||||
|
||||
b3Plane plane = { normal, totalRadius - distance };
|
||||
*result = ( b3PlaneResult ){ plane, shape->center };
|
||||
return 1;
|
||||
}
|
||||
745
vendor/box3d/src/src/spherical_joint.c
vendored
Normal file
745
vendor/box3d/src/src/spherical_joint.c
vendored
Normal file
@@ -0,0 +1,745 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "body.h"
|
||||
#include "core.h"
|
||||
#include "joint.h"
|
||||
#include "math_internal.h"
|
||||
#include "physics_world.h"
|
||||
#include "solver.h"
|
||||
#include "solver_set.h"
|
||||
#include "recording.h"
|
||||
|
||||
// needed for dll export
|
||||
#include "box3d/box3d.h"
|
||||
|
||||
void b3SphericalJoint_EnableConeLimit( b3JointId jointId, bool enableLimit )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, SphericalJointEnableConeLimit, jointId, enableLimit );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
if ( enableLimit != base->sphericalJoint.enableConeLimit )
|
||||
{
|
||||
base->sphericalJoint.swingImpulse = 0.0f;
|
||||
}
|
||||
base->sphericalJoint.enableConeLimit = enableLimit;
|
||||
}
|
||||
|
||||
bool b3SphericalJoint_IsConeLimitEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.enableConeLimit;
|
||||
}
|
||||
|
||||
float b3SphericalJoint_GetConeLimit( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.coneAngle;
|
||||
}
|
||||
|
||||
void b3SphericalJoint_SetConeLimit( b3JointId jointId, float angleRadians )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( angleRadians ) && 0 <= angleRadians && angleRadians <= 0.5f * B3_PI );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, SphericalJointSetConeLimit, jointId, angleRadians );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
base->sphericalJoint.coneAngle = angleRadians;
|
||||
}
|
||||
|
||||
float b3SphericalJoint_GetConeAngle( b3JointId jointId )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA );
|
||||
b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB );
|
||||
|
||||
b3Quat quatA = b3MulQuat( transformA.q, base->localFrameA.q );
|
||||
b3Quat quatB = b3MulQuat( transformB.q, base->localFrameB.q );
|
||||
|
||||
if ( b3DotQuat( quatA, quatB ) < 0.0f )
|
||||
{
|
||||
// this keeps the swing angle in the range [0, pi]
|
||||
quatB = b3NegateQuat( quatB );
|
||||
}
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( quatA, quatB );
|
||||
|
||||
return b3GetSwingAngle( relQ );
|
||||
}
|
||||
|
||||
void b3SphericalJoint_EnableTwistLimit( b3JointId jointId, bool enableLimit )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, SphericalJointEnableTwistLimit, jointId, enableLimit );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
if ( enableLimit != base->sphericalJoint.enableTwistLimit )
|
||||
{
|
||||
base->sphericalJoint.lowerTwistImpulse = 0.0f;
|
||||
base->sphericalJoint.upperTwistImpulse = 0.0f;
|
||||
}
|
||||
base->sphericalJoint.enableTwistLimit = enableLimit;
|
||||
}
|
||||
|
||||
bool b3SphericalJoint_IsTwistLimitEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.enableTwistLimit;
|
||||
}
|
||||
|
||||
float b3SphericalJoint_GetLowerTwistLimit( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.lowerTwistAngle;
|
||||
}
|
||||
|
||||
float b3SphericalJoint_GetUpperTwistLimit( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.upperTwistAngle;
|
||||
}
|
||||
|
||||
void b3SphericalJoint_SetTwistLimits( b3JointId jointId, float lowerLimitRadians, float upperLimitRadians )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( lowerLimitRadians ) && b3IsValidFloat( upperLimitRadians ) );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, SphericalJointSetTwistLimits, jointId, lowerLimitRadians, upperLimitRadians );
|
||||
|
||||
float lowerAngle = b3MinFloat( lowerLimitRadians, upperLimitRadians );
|
||||
float upperAngle = b3MaxFloat( lowerLimitRadians, upperLimitRadians );
|
||||
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
base->sphericalJoint.lowerTwistAngle = b3ClampFloat( lowerAngle, -0.99f * B3_PI, 0.99f * B3_PI );
|
||||
base->sphericalJoint.upperTwistAngle = b3ClampFloat( upperAngle, -0.99f * B3_PI, 0.99f * B3_PI );
|
||||
}
|
||||
|
||||
float b3SphericalJoint_GetTwistAngle( b3JointId jointId )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA );
|
||||
b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB );
|
||||
|
||||
b3Quat quatA = b3MulQuat( transformA.q, base->localFrameA.q );
|
||||
b3Quat quatB = b3MulQuat( transformB.q, base->localFrameB.q );
|
||||
|
||||
if ( b3DotQuat( quatA, quatB ) < 0.0f )
|
||||
{
|
||||
// this keeps the twist angle in the range [-pi, pi]
|
||||
quatB = b3NegateQuat( quatB );
|
||||
}
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( quatA, quatB );
|
||||
|
||||
return b3GetTwistAngle( relQ );
|
||||
}
|
||||
|
||||
void b3SphericalJoint_EnableSpring( b3JointId jointId, bool enableSpring )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, SphericalJointEnableSpring, jointId, enableSpring );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
if ( enableSpring != base->sphericalJoint.enableSpring )
|
||||
{
|
||||
base->sphericalJoint.springImpulse = b3Vec3_zero;
|
||||
}
|
||||
base->sphericalJoint.enableSpring = enableSpring;
|
||||
}
|
||||
|
||||
bool b3SphericalJoint_IsSpringEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.enableSpring;
|
||||
}
|
||||
|
||||
void b3SphericalJoint_SetTargetRotation( b3JointId jointId, b3Quat targetRotation )
|
||||
{
|
||||
B3_ASSERT( b3IsValidQuat( targetRotation ) );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, SphericalJointSetTargetRotation, jointId, targetRotation );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
base->sphericalJoint.targetRotation = targetRotation;
|
||||
}
|
||||
|
||||
b3Quat b3SphericalJoint_GetTargetRotation( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.targetRotation;
|
||||
}
|
||||
|
||||
void b3SphericalJoint_SetSpringHertz( b3JointId jointId, float hertz )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, SphericalJointSetSpringHertz, jointId, hertz );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
base->sphericalJoint.hertz = hertz;
|
||||
}
|
||||
|
||||
float b3SphericalJoint_GetSpringHertz( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.hertz;
|
||||
}
|
||||
|
||||
void b3SphericalJoint_SetSpringDampingRatio( b3JointId jointId, float dampingRatio )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, SphericalJointSetSpringDampingRatio, jointId, dampingRatio );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
base->sphericalJoint.dampingRatio = dampingRatio;
|
||||
}
|
||||
|
||||
float b3SphericalJoint_GetSpringDampingRatio( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.dampingRatio;
|
||||
}
|
||||
|
||||
void b3SphericalJoint_EnableMotor( b3JointId jointId, bool enableMotor )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, SphericalJointEnableMotor, jointId, enableMotor );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
if ( enableMotor != base->sphericalJoint.enableMotor )
|
||||
{
|
||||
base->sphericalJoint.motorImpulse = b3Vec3_zero;
|
||||
}
|
||||
base->sphericalJoint.enableMotor = enableMotor;
|
||||
}
|
||||
|
||||
bool b3SphericalJoint_IsMotorEnabled( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.enableMotor;
|
||||
}
|
||||
|
||||
void b3SphericalJoint_SetMotorVelocity( b3JointId jointId, b3Vec3 motorVelocity )
|
||||
{
|
||||
B3_ASSERT( b3IsValidVec3( motorVelocity ) );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, SphericalJointSetMotorVelocity, jointId, motorVelocity );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
base->sphericalJoint.motorVelocity = motorVelocity;
|
||||
}
|
||||
|
||||
b3Vec3 b3SphericalJoint_GetMotorVelocity( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.motorVelocity;
|
||||
}
|
||||
|
||||
void b3SphericalJoint_SetMaxMotorTorque( b3JointId jointId, float maxForce )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( maxForce ) && maxForce >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, SphericalJointSetMaxMotorTorque, jointId, maxForce );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
base->sphericalJoint.maxMotorTorque = maxForce;
|
||||
}
|
||||
|
||||
float b3SphericalJoint_GetMaxMotorTorque( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return base->sphericalJoint.maxMotorTorque;
|
||||
}
|
||||
|
||||
b3Vec3 b3SphericalJoint_GetMotorTorque( b3JointId jointId )
|
||||
{
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint );
|
||||
return b3MulSV( world->inv_h, base->sphericalJoint.motorImpulse );
|
||||
}
|
||||
|
||||
b3Vec3 b3GetSphericalJointForce( b3World* world, b3JointSim* base )
|
||||
{
|
||||
b3Vec3 force = b3MulSV( world->inv_h, base->sphericalJoint.linearImpulse );
|
||||
return force;
|
||||
}
|
||||
|
||||
b3Vec3 b3GetSphericalJointTorque( b3World* world, b3JointSim* base )
|
||||
{
|
||||
b3WorldTransform xfA = b3GetBodyTransform( world, base->bodyIdA );
|
||||
b3WorldTransform xfB = b3GetBodyTransform( world, base->bodyIdB );
|
||||
b3Quat qA = b3MulQuat( xfA.q, base->localFrameA.q );
|
||||
b3Quat qB = b3MulQuat( xfB.q, base->localFrameB.q );
|
||||
|
||||
// Cone axis is the z-axis of body A.
|
||||
b3Vec3 coneAxis = b3RotateVector( qA, b3Vec3_axisZ );
|
||||
b3Vec3 twistAxis = b3RotateVector( qB, b3Vec3_axisZ );
|
||||
b3Vec3 swingAxis = b3Normalize( b3Cross( coneAxis, twistAxis ) );
|
||||
|
||||
b3SphericalJoint* joint = &base->sphericalJoint;
|
||||
b3Vec3 impulse = b3Add( joint->springImpulse, joint->motorImpulse );
|
||||
impulse = b3MulAdd( impulse, joint->lowerTwistImpulse - joint->upperTwistImpulse, twistAxis );
|
||||
impulse = b3MulAdd( impulse, joint->swingImpulse, swingAxis );
|
||||
b3Vec3 torque = b3MulSV( world->inv_h, impulse );
|
||||
return torque;
|
||||
}
|
||||
|
||||
// Point-to-point constraint
|
||||
// C = p2 - p1
|
||||
// Cdot = v2 - v1
|
||||
// = v2 + cross(w2, r2) - v1 - cross(w1, r1)
|
||||
// J = [-I r1_skew I -r2_skew ]
|
||||
// K = J * invM * transpose(J)
|
||||
// transpose(skew(r)) = -skew(r)
|
||||
// K = diag(1/m1 + 1/m2) - r1_skew * invI1 * r1_skew - r2_skew * invI2 * r2_skew
|
||||
|
||||
// r_skew = R * skew(r_local) * RT
|
||||
// invI = R * invI_local * RT
|
||||
// r_skew * invI * r_skew = R * skew(r_local) * RT * R * invI_local * RT * R * r_skew * RT
|
||||
// = R * ( skew(r_local) * invI_local * skew(r_local) ) * RT
|
||||
|
||||
void b3PrepareSphericalJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_sphericalJoint );
|
||||
|
||||
b3World* world = context->world;
|
||||
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB );
|
||||
|
||||
B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet );
|
||||
b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex );
|
||||
b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex );
|
||||
|
||||
int localIndexA = bodyA->localIndex;
|
||||
int localIndexB = bodyB->localIndex;
|
||||
|
||||
b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA );
|
||||
b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB );
|
||||
|
||||
base->invMassA = bodySimA->invMass;
|
||||
base->invMassB = bodySimB->invMass;
|
||||
base->invIA = bodySimA->invInertiaWorld;
|
||||
base->invIB = bodySimB->invInertiaWorld;
|
||||
|
||||
b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB );
|
||||
base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN;
|
||||
|
||||
b3SphericalJoint* joint = &base->sphericalJoint;
|
||||
joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX;
|
||||
joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX;
|
||||
|
||||
// Compute joint anchor frames with world space rotation, relative to center of mass
|
||||
joint->frameA.q = b3MulQuat( bodySimA->transform.q, base->localFrameA.q );
|
||||
joint->frameA.p = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) );
|
||||
joint->frameB.q = b3MulQuat( bodySimB->transform.q, base->localFrameB.q );
|
||||
joint->frameB.p = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) );
|
||||
|
||||
joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center );
|
||||
|
||||
// Cone axis is the z-axis of body A.
|
||||
b3Vec3 coneAxis = b3RotateVector( joint->frameA.q, b3Vec3_axisZ );
|
||||
|
||||
// Twist axis is the z-axis of body B.
|
||||
b3Vec3 twistAxis = b3RotateVector( joint->frameB.q, b3Vec3_axisZ );
|
||||
|
||||
if ( joint->enableConeLimit )
|
||||
{
|
||||
// Swing axis may be zero
|
||||
b3Vec3 swingAxis = b3Normalize( b3Cross( coneAxis, twistAxis ) );
|
||||
float k = b3Dot( swingAxis, b3MulMV( invInertiaSum, swingAxis ) );
|
||||
joint->swingMass = k > 0.0f ? 1.0f / k : 0.0f;
|
||||
joint->swingAxis = swingAxis;
|
||||
}
|
||||
|
||||
if ( joint->enableTwistLimit )
|
||||
{
|
||||
b3Quat relQ = b3InvMulQuat( joint->frameA.q, joint->frameB.q );
|
||||
float tanThetaOver2 = sqrtf( ( relQ.v.x * relQ.v.x + relQ.v.y * relQ.v.y ) / ( relQ.v.z * relQ.v.z + relQ.s * relQ.s ) );
|
||||
|
||||
// todo verify this Jacobian using a finite difference, unit test?
|
||||
b3Vec3 swingAxis = b3Normalize( b3Cross( coneAxis, twistAxis ) );
|
||||
b3Vec3 perpAxis = b3Cross( swingAxis, coneAxis );
|
||||
b3Vec3 twistJacobian = b3MulAdd( coneAxis, tanThetaOver2, perpAxis );
|
||||
float k = b3Dot( twistJacobian, b3MulMV( invInertiaSum, twistJacobian ) );
|
||||
joint->twistMass = k > 0.0f ? 1.0f / k : 0.0f;
|
||||
joint->twistJacobian = twistJacobian;
|
||||
}
|
||||
|
||||
if ( base->fixedRotation == false )
|
||||
{
|
||||
joint->rotationMass = b3InvertMatrix( invInertiaSum );
|
||||
}
|
||||
else
|
||||
{
|
||||
joint->rotationMass = b3Mat3_zero;
|
||||
}
|
||||
|
||||
joint->springSoftness = b3MakeSoft( joint->hertz, joint->dampingRatio, context->h );
|
||||
|
||||
if ( context->enableWarmStarting == false )
|
||||
{
|
||||
joint->linearImpulse = b3Vec3_zero;
|
||||
joint->motorImpulse = b3Vec3_zero;
|
||||
joint->springImpulse = b3Vec3_zero;
|
||||
joint->swingImpulse = 0.0f;
|
||||
joint->lowerTwistImpulse = 0.0f;
|
||||
joint->upperTwistImpulse = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void b3WarmStartSphericalJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_sphericalJoint );
|
||||
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3SphericalJoint* joint = &base->sphericalJoint;
|
||||
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 vA = stateA->linearVelocity;
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 vB = stateB->linearVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p );
|
||||
|
||||
b3Vec3 angularImpulse = b3Add( joint->springImpulse, joint->motorImpulse );
|
||||
angularImpulse = b3MulSub( angularImpulse, joint->swingImpulse, joint->swingAxis );
|
||||
angularImpulse = b3MulAdd( angularImpulse, joint->lowerTwistImpulse - joint->upperTwistImpulse, joint->twistJacobian );
|
||||
|
||||
vA = b3MulSub( vA, mA, joint->linearImpulse );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Add( b3Cross( rA, joint->linearImpulse ), angularImpulse ) ) );
|
||||
|
||||
vB = b3MulAdd( vB, mB, joint->linearImpulse );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Add( b3Cross( rB, joint->linearImpulse ), angularImpulse ) ) );
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->linearVelocity = vA;
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->linearVelocity = vB;
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
|
||||
void b3SolveSphericalJoint( b3JointSim* base, b3StepContext* context, bool useBias )
|
||||
{
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3SphericalJoint* joint = &base->sphericalJoint;
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 vA = stateA->linearVelocity;
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 vB = stateB->linearVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
|
||||
bool fixedRotation = base->fixedRotation;
|
||||
b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q );
|
||||
b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q );
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( quatA, quatB );
|
||||
|
||||
// Solve spring
|
||||
if ( joint->enableSpring && fixedRotation == false )
|
||||
{
|
||||
// Rotation constraint error
|
||||
b3Vec3 deltaRotation = b3DeltaQuatToRotation( relQ, joint->targetRotation );
|
||||
b3Vec3 c = b3Neg( b3RotateVector( quatA, deltaRotation ) );
|
||||
|
||||
b3Vec3 bias = b3MulSV( joint->springSoftness.biasRate, c );
|
||||
float massScale = joint->springSoftness.massScale;
|
||||
float impulseScale = joint->springSoftness.impulseScale;
|
||||
b3Vec3 cdot = b3Sub( wB, wA );
|
||||
|
||||
b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b3MulMV( joint->rotationMass, b3Add( cdot, bias ) ) ),
|
||||
impulseScale, joint->springImpulse );
|
||||
joint->springImpulse = b3Add( joint->springImpulse, impulse );
|
||||
|
||||
wA = b3Sub( wA, b3MulMV( iA, impulse ) );
|
||||
wB = b3Add( wB, b3MulMV( iB, impulse ) );
|
||||
}
|
||||
|
||||
if ( joint->enableMotor && fixedRotation == false )
|
||||
{
|
||||
b3Vec3 cdot = b3Sub( wB, wA );
|
||||
|
||||
b3Vec3 lambda = b3Neg( b3MulMV( joint->rotationMass, b3Sub( cdot, joint->motorVelocity ) ) );
|
||||
b3Vec3 newImpulse = b3Add( joint->motorImpulse, lambda );
|
||||
float length = b3Length( newImpulse );
|
||||
float maxImpulse = joint->maxMotorTorque * context->h;
|
||||
if ( length > maxImpulse )
|
||||
{
|
||||
newImpulse = b3MulSV( maxImpulse / length, newImpulse );
|
||||
}
|
||||
|
||||
lambda = b3Sub( newImpulse, joint->motorImpulse );
|
||||
joint->motorImpulse = newImpulse;
|
||||
|
||||
wA = b3Sub( wA, b3MulMV( iA, lambda ) );
|
||||
wB = b3Add( wB, b3MulMV( iB, lambda ) );
|
||||
}
|
||||
|
||||
if ( joint->enableTwistLimit && fixedRotation == false )
|
||||
{
|
||||
float twistAngle = b3GetTwistAngle( relQ );
|
||||
|
||||
// todo does an updated twist axis help?
|
||||
|
||||
b3Vec3 twistJacobian = joint->twistJacobian;
|
||||
|
||||
// Lower limit
|
||||
{
|
||||
float c = twistAngle - joint->lowerTwistAngle;
|
||||
float bias = 0.0f;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( c > 0.0f )
|
||||
{
|
||||
// speculation
|
||||
bias = c * context->inv_h;
|
||||
}
|
||||
else if ( useBias )
|
||||
{
|
||||
bias = base->constraintSoftness.biasRate * c;
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
float cdot = b3Dot( b3Sub( wB, wA ), twistJacobian );
|
||||
float oldImpulse = joint->lowerTwistImpulse;
|
||||
float deltaImpulse = -massScale * joint->twistMass * ( cdot + bias ) - impulseScale * oldImpulse;
|
||||
joint->lowerTwistImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f );
|
||||
deltaImpulse = joint->lowerTwistImpulse - oldImpulse;
|
||||
|
||||
wA = b3MulSub( wA, deltaImpulse, b3MulMV( iA, twistJacobian ) );
|
||||
wB = b3MulAdd( wB, deltaImpulse, b3MulMV( iB, twistJacobian ) );
|
||||
}
|
||||
|
||||
// Upper limit
|
||||
{
|
||||
float c = joint->upperTwistAngle - twistAngle;
|
||||
float bias = 0.0f;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( c > 0.0f )
|
||||
{
|
||||
// speculation
|
||||
bias = c * context->inv_h;
|
||||
}
|
||||
else if ( useBias )
|
||||
{
|
||||
bias = base->constraintSoftness.biasRate * c;
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
// sign flipped on Cdot
|
||||
float cdot = b3Dot( b3Sub( wA, wB ), twistJacobian );
|
||||
float oldImpulse = joint->upperTwistImpulse;
|
||||
float deltaImpulse = -massScale * joint->twistMass * ( cdot + bias ) - impulseScale * oldImpulse;
|
||||
joint->upperTwistImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f );
|
||||
deltaImpulse = joint->upperTwistImpulse - oldImpulse;
|
||||
|
||||
// sign flipped on applied impulse
|
||||
wA = b3MulAdd( wA, deltaImpulse, b3MulMV( iA, twistJacobian ) );
|
||||
wB = b3MulSub( wB, deltaImpulse, b3MulMV( iB, twistJacobian ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( joint->enableConeLimit && fixedRotation == false )
|
||||
{
|
||||
float swingAngle = b3GetSwingAngle( relQ );
|
||||
|
||||
// todo does an updated swing axis help?
|
||||
// b3Vec3 axisA = b3RotateVector( quatA, b3Vec3_axisZ );
|
||||
// b3Vec3 axisB = b3RotateVector( quatB, b3Vec3_axisZ );
|
||||
// b3Vec3 swingAxis = b3Normalize( b3Cross( axisA, axisB ) );
|
||||
// joint->swingAxis = swingAxis;
|
||||
|
||||
b3Vec3 swingAxis = joint->swingAxis;
|
||||
|
||||
float c = joint->coneAngle - swingAngle;
|
||||
float bias = 0.0f;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( c > 0.0f )
|
||||
{
|
||||
// speculation
|
||||
bias = c * context->inv_h;
|
||||
}
|
||||
else if ( useBias )
|
||||
{
|
||||
bias = base->constraintSoftness.biasRate * c;
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
// sign flipped on Cdot
|
||||
float cdot = b3Dot( b3Sub( wA, wB ), swingAxis );
|
||||
float oldImpulse = joint->swingImpulse;
|
||||
float deltaImpulse = -massScale * joint->swingMass * ( cdot + bias ) - impulseScale * oldImpulse;
|
||||
joint->swingImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f );
|
||||
deltaImpulse = joint->swingImpulse - oldImpulse;
|
||||
|
||||
// sign flipped on applied impulse
|
||||
wA = b3MulAdd( wA, deltaImpulse, b3MulMV( iA, swingAxis ) );
|
||||
wB = b3MulSub( wB, deltaImpulse, b3MulMV( iB, swingAxis ) );
|
||||
}
|
||||
|
||||
// Solve point-to-point constraint
|
||||
{
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p );
|
||||
|
||||
b3Vec3 cdot = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, rA ) );
|
||||
|
||||
b3Vec3 bias = b3Vec3_zero;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( useBias )
|
||||
{
|
||||
b3Vec3 dcA = stateA->deltaPosition;
|
||||
b3Vec3 dcB = stateB->deltaPosition;
|
||||
|
||||
b3Vec3 separation = b3Add( b3Sub( dcB, dcA ), b3Sub( rB, rA ) );
|
||||
separation = b3Add( separation, joint->deltaCenter );
|
||||
|
||||
bias = b3MulSV( base->constraintSoftness.biasRate, separation );
|
||||
massScale = base->constraintSoftness.massScale;
|
||||
impulseScale = base->constraintSoftness.impulseScale;
|
||||
}
|
||||
|
||||
//// K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)]
|
||||
b3Matrix3 sA = b3Skew( rA );
|
||||
b3Matrix3 sB = b3Skew( rB );
|
||||
b3Matrix3 kA = b3MulMM( sA, b3MulMM( base->invIA, sA ) );
|
||||
b3Matrix3 kB = b3MulMM( sB, b3MulMM( base->invIB, sB ) );
|
||||
b3Matrix3 k = b3NegateMat3( b3AddMM( kA, kB ) );
|
||||
k.cx.x += mA + mB;
|
||||
k.cy.y += mA + mB;
|
||||
k.cz.z += mA + mB;
|
||||
|
||||
b3Vec3 b = b3Solve3( k, b3Add( cdot, bias ) );
|
||||
|
||||
b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b ), impulseScale, joint->linearImpulse );
|
||||
joint->linearImpulse = b3Add( joint->linearImpulse, impulse );
|
||||
|
||||
vA = b3MulSub( vA, mA, impulse );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) );
|
||||
vB = b3MulAdd( vB, mB, impulse );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) );
|
||||
}
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->linearVelocity = vA;
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->linearVelocity = vB;
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
|
||||
void b3DrawSphericalJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale )
|
||||
{
|
||||
b3WorldTransform frameA = b3MulWorldTransforms( transformA, base->localFrameA );
|
||||
|
||||
float length1 = 0.1f * scale;
|
||||
draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisX ) ) ), b3_colorRed,
|
||||
draw->context );
|
||||
draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisY ) ) ), b3_colorGreen,
|
||||
draw->context );
|
||||
draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisZ ) ) ), b3_colorBlue,
|
||||
draw->context );
|
||||
|
||||
b3WorldTransform frameB = b3MulWorldTransforms( transformB, base->localFrameB );
|
||||
|
||||
float length2 = 0.2f * scale;
|
||||
draw->DrawSegmentFcn( frameB.p, b3OffsetPos( frameB.p, b3MulSV( length2, b3RotateVector( frameB.q, b3Vec3_axisZ ) ) ), b3_colorOrange,
|
||||
draw->context );
|
||||
|
||||
b3SphericalJoint* joint = &base->sphericalJoint;
|
||||
enum { kSliceCount = 16 };
|
||||
|
||||
// Twist limit
|
||||
if ( joint->enableTwistLimit )
|
||||
{
|
||||
b3Quat quatA = frameA.q;
|
||||
b3Quat quatB = frameB.q;
|
||||
|
||||
if ( b3DotQuat( quatA, quatB ) < 0.0f )
|
||||
{
|
||||
// this keeps the twist angle in the range [-pi, pi]
|
||||
quatB = b3NegateQuat( quatB );
|
||||
}
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( quatA, quatB );
|
||||
|
||||
const float wedgeRadius = 0.1f * scale;
|
||||
for ( int index = 0; index < kSliceCount; ++index )
|
||||
{
|
||||
float t1 = (float)( index + 0 ) / kSliceCount;
|
||||
float alpha1 = ( 1.0f - t1 ) * joint->lowerTwistAngle + t1 * joint->upperTwistAngle;
|
||||
float t2 = (float)( index + 1 ) / kSliceCount;
|
||||
float alpha2 = ( 1.0f - t2 ) * joint->lowerTwistAngle + t2 * joint->upperTwistAngle;
|
||||
|
||||
b3Vec3 vertex1 = { wedgeRadius * b3Cos( alpha1 ), wedgeRadius * b3Sin( alpha1 ), 0.0f };
|
||||
b3Vec3 vertex2 = { wedgeRadius * b3Cos( alpha2 ), wedgeRadius * b3Sin( alpha2 ), 0.0f };
|
||||
|
||||
if ( index == 0 )
|
||||
{
|
||||
draw->DrawSegmentFcn( frameA.p, b3TransformWorldPoint( frameA, vertex1 ), b3_colorCyan, draw->context );
|
||||
}
|
||||
|
||||
if ( index == kSliceCount - 1 )
|
||||
{
|
||||
draw->DrawSegmentFcn( b3TransformWorldPoint( frameA, vertex2 ), frameA.p, b3_colorCyan, draw->context );
|
||||
}
|
||||
draw->DrawSegmentFcn( b3TransformWorldPoint( frameA, vertex1 ), b3TransformWorldPoint( frameA, vertex2 ), b3_colorCyan,
|
||||
draw->context );
|
||||
}
|
||||
|
||||
float twistAngle = b3GetTwistAngle( relQ );
|
||||
b3Vec3 p2 = { wedgeRadius * b3Cos( twistAngle ), wedgeRadius * b3Sin( twistAngle ), 0.0f };
|
||||
draw->DrawSegmentFcn( frameA.p, b3TransformWorldPoint( frameA, p2 ), b3_colorYellow, draw->context );
|
||||
}
|
||||
|
||||
// Swing limit
|
||||
if ( joint->enableConeLimit )
|
||||
{
|
||||
const float radius = 0.1f * scale;
|
||||
float coneRadius = radius * b3Sin( joint->coneAngle );
|
||||
float coneHeight = radius * b3Cos( joint->coneAngle );
|
||||
|
||||
for ( int index = 0; index < kSliceCount; ++index )
|
||||
{
|
||||
float phi1 = 2.0f * ( index + 0 ) / kSliceCount * B3_PI;
|
||||
float phi2 = 2.0f * ( index + 1 ) / kSliceCount * B3_PI;
|
||||
|
||||
b3Vec3 vertex1 = { coneRadius * b3Cos( phi1 ), coneRadius * b3Sin( phi1 ), coneHeight };
|
||||
b3Vec3 vertex2 = { coneRadius * b3Cos( phi2 ), coneRadius * b3Sin( phi2 ), coneHeight };
|
||||
|
||||
draw->DrawSegmentFcn( frameA.p, b3TransformWorldPoint( frameA, vertex1 ), b3_colorCyan, draw->context );
|
||||
draw->DrawSegmentFcn( b3TransformWorldPoint( frameA, vertex1 ), b3TransformWorldPoint( frameA, vertex2 ), b3_colorCyan,
|
||||
draw->context );
|
||||
}
|
||||
}
|
||||
}
|
||||
248
vendor/box3d/src/src/table.c
vendored
Normal file
248
vendor/box3d/src/src/table.c
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "table.h"
|
||||
|
||||
#include "bitset.h"
|
||||
#include "core.h"
|
||||
#include "ctz.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#if B3_DEBUG
|
||||
b3AtomicInt b3_probeCount;
|
||||
#endif
|
||||
|
||||
_Static_assert( 2 * B3_SHAPE_POWER + B3_CHILD_POWER == 64, "compound power" );
|
||||
_Static_assert( B3_CHILD_POWER > 8, "compound child power" );
|
||||
|
||||
b3HashSet b3CreateSet( int32_t capacity )
|
||||
{
|
||||
b3HashSet set = { 0 };
|
||||
|
||||
// Capacity must be a power of 2
|
||||
if ( capacity > 16 )
|
||||
{
|
||||
set.capacity = b3RoundUpPowerOf2( capacity );
|
||||
}
|
||||
else
|
||||
{
|
||||
set.capacity = 16;
|
||||
}
|
||||
|
||||
set.count = 0;
|
||||
set.items = (b3SetItem*)b3Alloc( set.capacity * sizeof( b3SetItem ) );
|
||||
memset( set.items, 0, set.capacity * sizeof( b3SetItem ) );
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
void b3DestroySet( b3HashSet* set )
|
||||
{
|
||||
b3Free( set->items, set->capacity * sizeof( b3SetItem ) );
|
||||
set->items = NULL;
|
||||
set->count = 0;
|
||||
set->capacity = 0;
|
||||
}
|
||||
|
||||
void b3ClearSet( b3HashSet* set )
|
||||
{
|
||||
set->count = 0;
|
||||
memset( set->items, 0, set->capacity * sizeof( b3SetItem ) );
|
||||
}
|
||||
|
||||
// I need a good hash because the keys are built from pairs of increasing integers.
|
||||
// A simple hash like hash = (integer1 XOR integer2) has many collisions.
|
||||
// https://lemire.me/blog/2018/08/15/fast-strongly-universal-64-bit-hashing-everywhere/
|
||||
// https://preshing.com/20130107/this-hash-set-is-faster-than-a-judy-array/
|
||||
// todo try: https://www.jandrewrogers.com/2019/02/12/fast-perfect-hashing/
|
||||
// todo try:
|
||||
// https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/
|
||||
static inline uint32_t b3KeyHash( uint64_t key )
|
||||
{
|
||||
uint64_t h = key;
|
||||
h ^= h >> 33;
|
||||
h *= 0xff51afd7ed558ccdL;
|
||||
h ^= h >> 33;
|
||||
h *= 0xc4ceb9fe1a85ec53L;
|
||||
h ^= h >> 33;
|
||||
|
||||
return (uint32_t)h;
|
||||
}
|
||||
|
||||
static int32_t b3FindSlot( const b3HashSet* set, uint64_t key, uint32_t hash )
|
||||
{
|
||||
uint32_t capacity = set->capacity;
|
||||
int32_t index = hash & ( capacity - 1 );
|
||||
const b3SetItem* items = set->items;
|
||||
while ( items[index].hash != 0 && items[index].key != key )
|
||||
{
|
||||
#if B3_DEBUG
|
||||
b3AtomicFetchAddInt( &b3_probeCount, 1 );
|
||||
#endif
|
||||
index = ( index + 1 ) & ( capacity - 1 );
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static void b3AddKeyHaveCapacity( b3HashSet* set, uint64_t key, uint32_t hash )
|
||||
{
|
||||
int32_t index = b3FindSlot( set, key, hash );
|
||||
b3SetItem* items = set->items;
|
||||
B3_ASSERT( items[index].hash == 0 );
|
||||
|
||||
items[index].key = key;
|
||||
items[index].hash = hash;
|
||||
set->count += 1;
|
||||
}
|
||||
|
||||
static void b3GrowTable( b3HashSet* set )
|
||||
{
|
||||
uint32_t oldCount = set->count;
|
||||
B3_UNUSED( oldCount );
|
||||
|
||||
uint32_t oldCapacity = set->capacity;
|
||||
b3SetItem* oldItems = set->items;
|
||||
|
||||
set->count = 0;
|
||||
// Capacity must be a power of 2
|
||||
set->capacity = 2 * oldCapacity;
|
||||
set->items = (b3SetItem*)b3Alloc( set->capacity * sizeof( b3SetItem ) );
|
||||
memset( set->items, 0, set->capacity * sizeof( b3SetItem ) );
|
||||
|
||||
// Transfer items into new array
|
||||
for ( uint32_t i = 0; i < oldCapacity; ++i )
|
||||
{
|
||||
b3SetItem* item = oldItems + i;
|
||||
if ( item->hash == 0 )
|
||||
{
|
||||
// this item was empty
|
||||
continue;
|
||||
}
|
||||
|
||||
b3AddKeyHaveCapacity( set, item->key, item->hash );
|
||||
}
|
||||
|
||||
B3_ASSERT( set->count == oldCount );
|
||||
|
||||
b3Free( oldItems, oldCapacity * sizeof( b3SetItem ) );
|
||||
}
|
||||
|
||||
bool b3ContainsKey( const b3HashSet* set, uint64_t key )
|
||||
{
|
||||
// key of zero is a sentinel
|
||||
B3_ASSERT( key != 0 );
|
||||
uint32_t hash = b3KeyHash( key );
|
||||
int32_t index = b3FindSlot( set, key, hash );
|
||||
return set->items[index].key == key;
|
||||
}
|
||||
|
||||
int b3GetHashSetBytes( b3HashSet* set )
|
||||
{
|
||||
return set->capacity * (int)sizeof( b3SetItem );
|
||||
}
|
||||
|
||||
bool b3AddKey( b3HashSet* set, uint64_t key )
|
||||
{
|
||||
// key of zero is a sentinel
|
||||
B3_ASSERT( key != 0 );
|
||||
|
||||
uint32_t hash = b3KeyHash( key );
|
||||
B3_ASSERT( hash != 0 );
|
||||
|
||||
int32_t index = b3FindSlot( set, key, hash );
|
||||
if ( set->items[index].hash != 0 )
|
||||
{
|
||||
// Already in set
|
||||
B3_ASSERT( set->items[index].hash == hash && set->items[index].key == key );
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( 2 * set->count >= set->capacity )
|
||||
{
|
||||
b3GrowTable( set );
|
||||
}
|
||||
|
||||
b3AddKeyHaveCapacity( set, key, hash );
|
||||
return false;
|
||||
}
|
||||
|
||||
// See https://en.wikipedia.org/wiki/Open_addressing
|
||||
bool b3RemoveKey( b3HashSet* set, uint64_t key )
|
||||
{
|
||||
uint32_t hash = b3KeyHash( key );
|
||||
int32_t i = b3FindSlot( set, key, hash );
|
||||
b3SetItem* items = set->items;
|
||||
if ( items[i].hash == 0 )
|
||||
{
|
||||
// Not in set
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark item i as unoccupied
|
||||
items[i].key = 0;
|
||||
items[i].hash = 0;
|
||||
|
||||
B3_ASSERT( set->count > 0 );
|
||||
set->count -= 1;
|
||||
|
||||
// Attempt to fill item i
|
||||
int32_t j = i;
|
||||
uint32_t capacity = set->capacity;
|
||||
for ( ;; )
|
||||
{
|
||||
j = ( j + 1 ) & ( capacity - 1 );
|
||||
if ( items[j].hash == 0 )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// k is the first item for the hash of j
|
||||
int32_t k = items[j].hash & ( capacity - 1 );
|
||||
|
||||
// determine if k lies cyclically in (i,j]
|
||||
// i <= j: | i..k..j |
|
||||
// i > j: |.k..j i....| or |....j i..k.|
|
||||
if ( i <= j )
|
||||
{
|
||||
if ( i < k && k <= j )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( i < k || k <= j )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Move j into i
|
||||
items[i] = items[j];
|
||||
|
||||
// Mark item j as unoccupied
|
||||
items[j].key = 0;
|
||||
items[j].hash = 0;
|
||||
|
||||
i = j;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is here because ctz.h is included by
|
||||
// this file but not in bitset.c
|
||||
int b3CountSetBits( b3BitSet* bitSet )
|
||||
{
|
||||
int popCount = 0;
|
||||
uint32_t blockCount = bitSet->blockCount;
|
||||
for ( uint32_t i = 0; i < blockCount; ++i )
|
||||
{
|
||||
popCount += b3PopCount64( bitSet->bits[i] );
|
||||
}
|
||||
|
||||
return popCount;
|
||||
}
|
||||
53
vendor/box3d/src/src/table.h
vendored
Normal file
53
vendor/box3d/src/src/table.h
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "box3d/constants.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct b3SetItem
|
||||
{
|
||||
uint64_t key;
|
||||
uint32_t hash;
|
||||
} b3SetItem;
|
||||
|
||||
typedef struct b3HashSet
|
||||
{
|
||||
b3SetItem* items;
|
||||
uint32_t capacity;
|
||||
uint32_t count;
|
||||
} b3HashSet;
|
||||
|
||||
#define B3_SHAPE_MASK ( B3_MAX_SHAPES - 1 )
|
||||
#define B3_CHILD_MASK ( B3_MAX_CHILD_SHAPES - 1 )
|
||||
|
||||
static inline uint64_t b3ShapePairKey( int s1, int s2, int c )
|
||||
{
|
||||
if (s1 < s2)
|
||||
{
|
||||
return ( (uint64_t)( B3_SHAPE_MASK & s1 ) << ( 64 - B3_SHAPE_POWER ) ) |
|
||||
( (uint64_t)( B3_SHAPE_MASK & s2 ) << ( 64 - 2 * B3_SHAPE_POWER ) ) | ( (uint64_t)( B3_CHILD_MASK & c ) );
|
||||
}
|
||||
|
||||
return ( (uint64_t)( B3_SHAPE_MASK & s2 ) << ( 64 - B3_SHAPE_POWER ) ) |
|
||||
( (uint64_t)( B3_SHAPE_MASK & s1 ) << ( 64 - 2 * B3_SHAPE_POWER ) ) |
|
||||
( (uint64_t)( B3_CHILD_MASK & c ) );
|
||||
}
|
||||
|
||||
b3HashSet b3CreateSet( int32_t capacity );
|
||||
void b3DestroySet( b3HashSet* set );
|
||||
|
||||
void b3ClearSet( b3HashSet* set );
|
||||
|
||||
// Returns true if key was already in set
|
||||
bool b3AddKey( b3HashSet* set, uint64_t key );
|
||||
|
||||
// Returns true if the key was found
|
||||
bool b3RemoveKey( b3HashSet* set, uint64_t key );
|
||||
|
||||
bool b3ContainsKey( const b3HashSet* set, uint64_t key );
|
||||
|
||||
int b3GetHashSetBytes( b3HashSet* set );
|
||||
700
vendor/box3d/src/src/timer.c
vendored
Normal file
700
vendor/box3d/src/src/timer.c
vendored
Normal file
@@ -0,0 +1,700 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Required on Linux to expose pthread_setname_np. Must be defined before any
|
||||
// system header is included.
|
||||
#if defined( __linux__ ) && !defined( _GNU_SOURCE )
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include "box3d/base.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NAME_LENGTH 16
|
||||
|
||||
#if defined( _WIN32 )
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#endif
|
||||
|
||||
#include <Windows.h>
|
||||
#include <limits.h>
|
||||
|
||||
static double s_invFrequency = 0.0;
|
||||
|
||||
uint64_t b3GetTicks( void )
|
||||
{
|
||||
LARGE_INTEGER counter;
|
||||
QueryPerformanceCounter( &counter );
|
||||
return (uint64_t)counter.QuadPart;
|
||||
}
|
||||
|
||||
float b3GetMilliseconds( uint64_t ticks )
|
||||
{
|
||||
if ( s_invFrequency == 0.0 )
|
||||
{
|
||||
LARGE_INTEGER frequency;
|
||||
QueryPerformanceFrequency( &frequency );
|
||||
|
||||
s_invFrequency = (double)frequency.QuadPart;
|
||||
if ( s_invFrequency > 0.0 )
|
||||
{
|
||||
s_invFrequency = 1000.0 / s_invFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t ticksNow = b3GetTicks();
|
||||
return (float)( s_invFrequency * ( ticksNow - ticks ) );
|
||||
}
|
||||
|
||||
float b3GetMillisecondsAndReset( uint64_t* ticks )
|
||||
{
|
||||
if ( s_invFrequency == 0.0 )
|
||||
{
|
||||
LARGE_INTEGER frequency;
|
||||
QueryPerformanceFrequency( &frequency );
|
||||
|
||||
s_invFrequency = (double)frequency.QuadPart;
|
||||
if ( s_invFrequency > 0.0 )
|
||||
{
|
||||
s_invFrequency = 1000.0 / s_invFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t ticksNow = b3GetTicks();
|
||||
float ms = (float)( s_invFrequency * ( ticksNow - *ticks ) );
|
||||
*ticks = ticksNow;
|
||||
return ms;
|
||||
}
|
||||
|
||||
void b3Yield( void )
|
||||
{
|
||||
SwitchToThread();
|
||||
}
|
||||
|
||||
void b3Sleep( int milliseconds )
|
||||
{
|
||||
Sleep( (DWORD)milliseconds );
|
||||
}
|
||||
|
||||
typedef struct b3Mutex
|
||||
{
|
||||
CRITICAL_SECTION cs;
|
||||
} b3Mutex;
|
||||
|
||||
b3Mutex* b3CreateMutex( void )
|
||||
{
|
||||
b3Mutex* m = b3Alloc( sizeof( b3Mutex ) );
|
||||
InitializeCriticalSection( &m->cs );
|
||||
return m;
|
||||
}
|
||||
|
||||
void b3DestroyMutex( b3Mutex* m )
|
||||
{
|
||||
DeleteCriticalSection( &m->cs );
|
||||
*m = (b3Mutex){ 0 };
|
||||
b3Free( m, sizeof( b3Mutex ) );
|
||||
}
|
||||
|
||||
void b3LockMutex( b3Mutex* m )
|
||||
{
|
||||
EnterCriticalSection( &m->cs );
|
||||
}
|
||||
|
||||
void b3UnlockMutex( b3Mutex* m )
|
||||
{
|
||||
LeaveCriticalSection( &m->cs );
|
||||
}
|
||||
|
||||
typedef struct b3Semaphore
|
||||
{
|
||||
HANDLE semaphore;
|
||||
} b3Semaphore;
|
||||
|
||||
b3Semaphore* b3CreateSemaphore( int initCount )
|
||||
{
|
||||
b3Semaphore* s = b3Alloc( sizeof( b3Semaphore ) );
|
||||
s->semaphore = CreateSemaphoreExW( NULL, initCount, INT_MAX, NULL, 0, SEMAPHORE_ALL_ACCESS );
|
||||
return s;
|
||||
}
|
||||
|
||||
void b3DestroySemaphore( b3Semaphore* s )
|
||||
{
|
||||
CloseHandle( s->semaphore );
|
||||
*s = (b3Semaphore){ 0 };
|
||||
b3Free( s, sizeof( b3Semaphore ) );
|
||||
}
|
||||
|
||||
void b3WaitSemaphore( b3Semaphore* s )
|
||||
{
|
||||
WaitForSingleObjectEx( s->semaphore, INFINITE, FALSE );
|
||||
}
|
||||
|
||||
void b3SignalSemaphore( b3Semaphore* s )
|
||||
{
|
||||
ReleaseSemaphore( s->semaphore, 1, NULL );
|
||||
}
|
||||
|
||||
typedef struct b3Thread
|
||||
{
|
||||
HANDLE thread;
|
||||
b3ThreadFunction* function;
|
||||
void* context;
|
||||
char name[NAME_LENGTH];
|
||||
} b3Thread;
|
||||
|
||||
typedef HRESULT( WINAPI* b3SetThreadDescriptionFn )( HANDLE, PCWSTR );
|
||||
|
||||
// SetThreadDescription exists on Windows 10 1607+. Resolve it dynamically so
|
||||
// older Windows versions still link. Resolved once, cached for subsequent calls.
|
||||
static void b3SetCurrentThreadName( const char* name )
|
||||
{
|
||||
if ( name == NULL || name[0] == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static b3SetThreadDescriptionFn pfn = NULL;
|
||||
static int resolved = 0;
|
||||
|
||||
if ( resolved == 0 )
|
||||
{
|
||||
HMODULE kernel = GetModuleHandleW( L"kernel32.dll" );
|
||||
if ( kernel != NULL )
|
||||
{
|
||||
// MSVC /Wall warns C4191 on every FARPROC function-pointer cast.
|
||||
// This is the intended use of GetProcAddress, so suppress locally.
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 4191 )
|
||||
pfn = (b3SetThreadDescriptionFn)GetProcAddress( kernel, "SetThreadDescription" );
|
||||
#pragma warning( pop )
|
||||
}
|
||||
resolved = 1;
|
||||
}
|
||||
|
||||
if ( pfn == NULL )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
wchar_t wide[NAME_LENGTH];
|
||||
int n = MultiByteToWideChar( CP_UTF8, 0, name, -1, wide, (int)( sizeof( wide ) / sizeof( wide[0] ) ) );
|
||||
if ( n > 0 )
|
||||
{
|
||||
pfn( GetCurrentThread(), wide );
|
||||
}
|
||||
}
|
||||
|
||||
static DWORD WINAPI b3ThreadStart( LPVOID param )
|
||||
{
|
||||
b3Thread* t = (b3Thread*)param;
|
||||
b3SetCurrentThreadName( t->name );
|
||||
t->function( t->context );
|
||||
return 0;
|
||||
}
|
||||
|
||||
b3Thread* b3CreateThread( b3ThreadFunction* function, void* context, const char* name )
|
||||
{
|
||||
b3Thread* t = b3Alloc( sizeof( b3Thread ) );
|
||||
t->function = function;
|
||||
t->context = context;
|
||||
if ( name != NULL )
|
||||
{
|
||||
snprintf( t->name, sizeof( t->name ), "%s", name );
|
||||
}
|
||||
else
|
||||
{
|
||||
t->name[0] = 0;
|
||||
}
|
||||
t->thread = CreateThread( NULL, 0, b3ThreadStart, t, 0, NULL );
|
||||
return t;
|
||||
}
|
||||
|
||||
void b3JoinThread( b3Thread* t )
|
||||
{
|
||||
WaitForSingleObject( t->thread, INFINITE );
|
||||
CloseHandle( t->thread );
|
||||
*t = (b3Thread){ 0 };
|
||||
b3Free( t, sizeof( b3Thread ) );
|
||||
}
|
||||
|
||||
#elif defined( __linux__ ) || defined( __EMSCRIPTEN__ )
|
||||
|
||||
#include <sched.h>
|
||||
#include <time.h>
|
||||
|
||||
uint64_t b3GetTicks( void )
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime( CLOCK_MONOTONIC, &ts );
|
||||
return ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
||||
}
|
||||
|
||||
float b3GetMilliseconds( uint64_t ticks )
|
||||
{
|
||||
uint64_t ticksNow = b3GetTicks();
|
||||
return (float)( ( ticksNow - ticks ) / 1000000.0 );
|
||||
}
|
||||
|
||||
float b3GetMillisecondsAndReset( uint64_t* ticks )
|
||||
{
|
||||
uint64_t ticksNow = b3GetTicks();
|
||||
float ms = (float)( ( ticksNow - *ticks ) / 1000000.0 );
|
||||
*ticks = ticksNow;
|
||||
return ms;
|
||||
}
|
||||
|
||||
void b3Yield( void )
|
||||
{
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
void b3Sleep( int milliseconds )
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = milliseconds / 1000;
|
||||
ts.tv_nsec = ( milliseconds % 1000 ) * 1000000L;
|
||||
nanosleep( &ts, NULL );
|
||||
}
|
||||
|
||||
#include <pthread.h>
|
||||
typedef struct b3Mutex
|
||||
{
|
||||
pthread_mutex_t mtx;
|
||||
} b3Mutex;
|
||||
|
||||
b3Mutex* b3CreateMutex( void )
|
||||
{
|
||||
b3Mutex* m = b3Alloc( sizeof( b3Mutex ) );
|
||||
pthread_mutex_init( &m->mtx, NULL );
|
||||
return m;
|
||||
}
|
||||
|
||||
void b3DestroyMutex( b3Mutex* m )
|
||||
{
|
||||
pthread_mutex_destroy( &m->mtx );
|
||||
*m = (b3Mutex){ 0 };
|
||||
b3Free( m, sizeof( b3Mutex ) );
|
||||
}
|
||||
|
||||
void b3LockMutex( b3Mutex* m )
|
||||
{
|
||||
pthread_mutex_lock( &m->mtx );
|
||||
}
|
||||
|
||||
void b3UnlockMutex( b3Mutex* m )
|
||||
{
|
||||
pthread_mutex_unlock( &m->mtx );
|
||||
}
|
||||
|
||||
#include <semaphore.h>
|
||||
|
||||
typedef struct b3Semaphore
|
||||
{
|
||||
sem_t semaphore;
|
||||
} b3Semaphore;
|
||||
|
||||
b3Semaphore* b3CreateSemaphore( int initCount )
|
||||
{
|
||||
b3Semaphore* s = b3Alloc( sizeof( b3Semaphore ) );
|
||||
sem_init( &s->semaphore, 0, (unsigned int)initCount );
|
||||
return s;
|
||||
}
|
||||
|
||||
void b3DestroySemaphore( b3Semaphore* s )
|
||||
{
|
||||
sem_destroy( &s->semaphore );
|
||||
*s = (b3Semaphore){ 0 };
|
||||
b3Free( s, sizeof( b3Semaphore ) );
|
||||
}
|
||||
|
||||
void b3WaitSemaphore( b3Semaphore* s )
|
||||
{
|
||||
sem_wait( &s->semaphore );
|
||||
}
|
||||
|
||||
void b3SignalSemaphore( b3Semaphore* s )
|
||||
{
|
||||
sem_post( &s->semaphore );
|
||||
}
|
||||
|
||||
typedef struct b3Thread
|
||||
{
|
||||
pthread_t thread;
|
||||
b3ThreadFunction* function;
|
||||
void* context;
|
||||
char name[NAME_LENGTH];
|
||||
} b3Thread;
|
||||
|
||||
static void b3SetCurrentThreadName( const char* name )
|
||||
{
|
||||
if ( name == NULL || name[0] == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined( __linux__ )
|
||||
// Linux caps thread names at 15 chars + null terminator.
|
||||
char truncated[NAME_LENGTH];
|
||||
snprintf( truncated, sizeof( truncated ), "%s", name );
|
||||
pthread_setname_np( pthread_self(), truncated );
|
||||
#else
|
||||
(void)name;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void* b3ThreadStart( void* param )
|
||||
{
|
||||
b3Thread* t = (b3Thread*)param;
|
||||
b3SetCurrentThreadName( t->name );
|
||||
t->function( t->context );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b3Thread* b3CreateThread( b3ThreadFunction* function, void* context, const char* name )
|
||||
{
|
||||
b3Thread* t = b3Alloc( sizeof( b3Thread ) );
|
||||
t->function = function;
|
||||
t->context = context;
|
||||
if ( name != NULL )
|
||||
{
|
||||
snprintf( t->name, sizeof( t->name ), "%s", name );
|
||||
}
|
||||
else
|
||||
{
|
||||
t->name[0] = 0;
|
||||
}
|
||||
pthread_create( &t->thread, NULL, b3ThreadStart, t );
|
||||
return t;
|
||||
}
|
||||
|
||||
void b3JoinThread( b3Thread* t )
|
||||
{
|
||||
pthread_join( t->thread, NULL );
|
||||
*t = (b3Thread){ 0 };
|
||||
b3Free( t, sizeof( b3Thread ) );
|
||||
}
|
||||
|
||||
#elif defined( __APPLE__ )
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
#include <sched.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
static double s_invFrequency = 0.0;
|
||||
|
||||
uint64_t b3GetTicks( void )
|
||||
{
|
||||
return mach_absolute_time();
|
||||
}
|
||||
|
||||
float b3GetMilliseconds( uint64_t ticks )
|
||||
{
|
||||
if ( s_invFrequency == 0 )
|
||||
{
|
||||
mach_timebase_info_data_t timebase;
|
||||
mach_timebase_info( &timebase );
|
||||
|
||||
// convert to ns then to ms
|
||||
s_invFrequency = 1e-6 * (double)timebase.numer / (double)timebase.denom;
|
||||
}
|
||||
|
||||
uint64_t ticksNow = b3GetTicks();
|
||||
return (float)( s_invFrequency * ( ticksNow - ticks ) );
|
||||
}
|
||||
|
||||
float b3GetMillisecondsAndReset( uint64_t* ticks )
|
||||
{
|
||||
if ( s_invFrequency == 0 )
|
||||
{
|
||||
mach_timebase_info_data_t timebase;
|
||||
mach_timebase_info( &timebase );
|
||||
|
||||
// convert to ns then to ms
|
||||
s_invFrequency = 1e-6 * (double)timebase.numer / (double)timebase.denom;
|
||||
}
|
||||
|
||||
uint64_t ticksNow = b3GetTicks();
|
||||
float ms = (float)( s_invFrequency * ( ticksNow - *ticks ) );
|
||||
*ticks = ticksNow;
|
||||
return ms;
|
||||
}
|
||||
|
||||
void b3Yield( void )
|
||||
{
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
void b3Sleep( int milliseconds )
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = milliseconds / 1000;
|
||||
ts.tv_nsec = ( milliseconds % 1000 ) * 1000000L;
|
||||
nanosleep( &ts, NULL );
|
||||
}
|
||||
|
||||
#include <pthread.h>
|
||||
typedef struct b3Mutex
|
||||
{
|
||||
pthread_mutex_t mtx;
|
||||
} b3Mutex;
|
||||
|
||||
b3Mutex* b3CreateMutex( void )
|
||||
{
|
||||
b3Mutex* m = b3Alloc( sizeof( b3Mutex ) );
|
||||
pthread_mutex_init( &m->mtx, NULL );
|
||||
return m;
|
||||
}
|
||||
|
||||
void b3DestroyMutex( b3Mutex* m )
|
||||
{
|
||||
pthread_mutex_destroy( &m->mtx );
|
||||
*m = (b3Mutex){ 0 };
|
||||
b3Free( m, sizeof( b3Mutex ) );
|
||||
}
|
||||
|
||||
void b3LockMutex( b3Mutex* m )
|
||||
{
|
||||
pthread_mutex_lock( &m->mtx );
|
||||
}
|
||||
|
||||
void b3UnlockMutex( b3Mutex* m )
|
||||
{
|
||||
pthread_mutex_unlock( &m->mtx );
|
||||
}
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
typedef struct b3Semaphore
|
||||
{
|
||||
dispatch_semaphore_t semaphore;
|
||||
int initialCount;
|
||||
} b3Semaphore;
|
||||
|
||||
b3Semaphore* b3CreateSemaphore( int initCount )
|
||||
{
|
||||
b3Semaphore* s = b3Alloc( sizeof( b3Semaphore ) );
|
||||
s->semaphore = dispatch_semaphore_create( (long)initCount );
|
||||
s->initialCount = initCount;
|
||||
return s;
|
||||
}
|
||||
|
||||
void b3DestroySemaphore( b3Semaphore* s )
|
||||
{
|
||||
// libdispatch aborts if the current count is less than the initial count at release time.
|
||||
// Pad with signals so the invariant always holds; no one is waiting at this point.
|
||||
for ( int i = 0; i < s->initialCount; ++i )
|
||||
{
|
||||
dispatch_semaphore_signal( s->semaphore );
|
||||
}
|
||||
dispatch_release( s->semaphore );
|
||||
*s = (b3Semaphore){ 0 };
|
||||
b3Free( s, sizeof( b3Semaphore ) );
|
||||
}
|
||||
|
||||
void b3WaitSemaphore( b3Semaphore* s )
|
||||
{
|
||||
dispatch_semaphore_wait( s->semaphore, DISPATCH_TIME_FOREVER );
|
||||
}
|
||||
|
||||
void b3SignalSemaphore( b3Semaphore* s )
|
||||
{
|
||||
dispatch_semaphore_signal( s->semaphore );
|
||||
}
|
||||
|
||||
typedef struct b3Thread
|
||||
{
|
||||
pthread_t thread;
|
||||
b3ThreadFunction* function;
|
||||
void* context;
|
||||
char name[NAME_LENGTH];
|
||||
} b3Thread;
|
||||
|
||||
// macOS pthread_setname_np takes only the name — it always names the calling thread.
|
||||
static void b3SetCurrentThreadName( const char* name )
|
||||
{
|
||||
if ( name == NULL || name[0] == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
pthread_setname_np( name );
|
||||
}
|
||||
|
||||
static void* b3ThreadStart( void* param )
|
||||
{
|
||||
b3Thread* t = (b3Thread*)param;
|
||||
b3SetCurrentThreadName( t->name );
|
||||
t->function( t->context );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b3Thread* b3CreateThread( b3ThreadFunction* function, void* context, const char* name )
|
||||
{
|
||||
b3Thread* t = b3Alloc( sizeof( b3Thread ) );
|
||||
t->function = function;
|
||||
t->context = context;
|
||||
if ( name != NULL )
|
||||
{
|
||||
snprintf( t->name, sizeof( t->name ), "%s", name );
|
||||
}
|
||||
else
|
||||
{
|
||||
t->name[0] = 0;
|
||||
}
|
||||
pthread_create( &t->thread, NULL, b3ThreadStart, t );
|
||||
return t;
|
||||
}
|
||||
|
||||
void b3JoinThread( b3Thread* t )
|
||||
{
|
||||
pthread_join( t->thread, NULL );
|
||||
*t = (b3Thread){ 0 };
|
||||
b3Free( t, sizeof( b3Thread ) );
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
uint64_t b3GetTicks( void )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
float b3GetMilliseconds( uint64_t ticks )
|
||||
{
|
||||
( (void)( ticks ) );
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float b3GetMillisecondsAndReset( uint64_t* ticks )
|
||||
{
|
||||
( (void)( ticks ) );
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void b3Yield( void )
|
||||
{
|
||||
}
|
||||
|
||||
void b3Sleep( int milliseconds )
|
||||
{
|
||||
( (void)( milliseconds ) );
|
||||
}
|
||||
|
||||
typedef struct b3Mutex
|
||||
{
|
||||
int dummy;
|
||||
} b3Mutex;
|
||||
|
||||
b3Mutex* b3CreateMutex( void )
|
||||
{
|
||||
b3Mutex* m = b3Alloc( sizeof( b3Mutex ) );
|
||||
m->dummy = 42;
|
||||
return m;
|
||||
}
|
||||
|
||||
void b3DestroyMutex( b3Mutex* m )
|
||||
{
|
||||
*m = (b3Mutex){ 0 };
|
||||
b3Free( m, sizeof( b3Mutex ) );
|
||||
}
|
||||
|
||||
void b3LockMutex( b3Mutex* m )
|
||||
{
|
||||
(void)m;
|
||||
}
|
||||
|
||||
void b3UnlockMutex( b3Mutex* m )
|
||||
{
|
||||
(void)m;
|
||||
}
|
||||
|
||||
typedef struct b3Semaphore
|
||||
{
|
||||
int dummy;
|
||||
} b3Semaphore;
|
||||
|
||||
b3Semaphore* b3CreateSemaphore( int initCount )
|
||||
{
|
||||
b3Semaphore* s = b3Alloc( sizeof( b3Semaphore ) );
|
||||
(void)initCount;
|
||||
s->dummy = 42;
|
||||
return s;
|
||||
}
|
||||
|
||||
void b3DestroySemaphore( b3Semaphore* s )
|
||||
{
|
||||
*s = (b3Semaphore){ 0 };
|
||||
b3Free( s, sizeof( b3Semaphore ) );
|
||||
}
|
||||
|
||||
void b3WaitSemaphore( b3Semaphore* s )
|
||||
{
|
||||
(void)s;
|
||||
}
|
||||
|
||||
void b3SignalSemaphore( b3Semaphore* s )
|
||||
{
|
||||
(void)s;
|
||||
}
|
||||
|
||||
typedef struct b3Thread
|
||||
{
|
||||
int dummy;
|
||||
} b3Thread;
|
||||
|
||||
b3Thread* b3CreateThread( b3ThreadFunction* function, void* context, const char* name )
|
||||
{
|
||||
(void)name;
|
||||
function( context );
|
||||
b3Thread* t = b3Alloc( sizeof( b3Thread ) );
|
||||
t->dummy = 42;
|
||||
return t;
|
||||
}
|
||||
|
||||
void b3JoinThread( b3Thread* t )
|
||||
{
|
||||
*t = (b3Thread){ 0 };
|
||||
b3Free( t, sizeof( b3Thread ) );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// djb2 hash, folded 8 bytes per iteration to shorten the dependency chain.
|
||||
// memcpy lowers to a single load on most targets; on big-endian we byte-swap so
|
||||
// the hash value is identical across endianness (preserving cross-platform determinism).
|
||||
// Equivalent to byte-wise djb2 only in spirit; values differ from the original recurrence.
|
||||
uint32_t b3Hash( uint32_t hash, const uint8_t* data, int count )
|
||||
{
|
||||
uint32_t result = hash;
|
||||
int i = 0;
|
||||
|
||||
while ( i + 8 <= count )
|
||||
{
|
||||
uint64_t word;
|
||||
memcpy( &word, data + i, sizeof( word ) );
|
||||
#if defined( __BYTE_ORDER__ ) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
word = ( ( word & 0x00000000000000FFULL ) << 56 ) | ( ( word & 0x000000000000FF00ULL ) << 40 ) |
|
||||
( ( word & 0x0000000000FF0000ULL ) << 24 ) | ( ( word & 0x00000000FF000000ULL ) << 8 ) |
|
||||
( ( word & 0x000000FF00000000ULL ) >> 8 ) | ( ( word & 0x0000FF0000000000ULL ) >> 24 ) |
|
||||
( ( word & 0x00FF000000000000ULL ) >> 40 ) | ( ( word & 0xFF00000000000000ULL ) >> 56 );
|
||||
#endif
|
||||
result = ( result << 5 ) + result + (uint32_t)word;
|
||||
result = ( result << 5 ) + result + (uint32_t)( word >> 32 );
|
||||
i += 8;
|
||||
}
|
||||
|
||||
while ( i < count )
|
||||
{
|
||||
result = ( result << 5 ) + result + data[i];
|
||||
i++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
1274
vendor/box3d/src/src/triangle_manifold.c
vendored
Normal file
1274
vendor/box3d/src/src/triangle_manifold.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
155
vendor/box3d/src/src/types.c
vendored
Normal file
155
vendor/box3d/src/src/types.c
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "box3d/types.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include "box3d/constants.h"
|
||||
|
||||
b3WorldDef b3DefaultWorldDef( void )
|
||||
{
|
||||
float lengthUnits = b3GetLengthUnitsPerMeter();
|
||||
|
||||
b3WorldDef def = { 0 };
|
||||
def.gravity.x = 0.0f;
|
||||
def.gravity.y = -10.0f;
|
||||
def.hitEventThreshold = 1.0f * lengthUnits;
|
||||
def.restitutionThreshold = 1.0f * lengthUnits;
|
||||
def.contactSpeed = 3.0f * lengthUnits;
|
||||
def.contactHertz = 30.0f;
|
||||
def.contactDampingRatio = 10.0f;
|
||||
|
||||
// 400 meters per second, faster than the speed of sound
|
||||
def.maximumLinearSpeed = 400.0f * lengthUnits;
|
||||
|
||||
def.enableSleep = true;
|
||||
def.enableContinuous = true;
|
||||
def.internalValue = B3_SECRET_COOKIE;
|
||||
return def;
|
||||
}
|
||||
|
||||
b3BodyDef b3DefaultBodyDef( void )
|
||||
{
|
||||
b3BodyDef def = { 0 };
|
||||
def.type = b3_staticBody;
|
||||
def.rotation = b3Quat_identity;
|
||||
def.sleepThreshold = 0.05f * b3GetLengthUnitsPerMeter();
|
||||
def.gravityScale = 1.0f;
|
||||
def.enableSleep = true;
|
||||
def.isAwake = true;
|
||||
def.isEnabled = true;
|
||||
def.enableContactRecycling = true;
|
||||
def.internalValue = B3_SECRET_COOKIE;
|
||||
return def;
|
||||
}
|
||||
|
||||
b3Filter b3DefaultFilter( void )
|
||||
{
|
||||
b3Filter filter = { B3_DEFAULT_CATEGORY_BITS, B3_DEFAULT_MASK_BITS, 0 };
|
||||
return filter;
|
||||
}
|
||||
|
||||
b3QueryFilter b3DefaultQueryFilter( void )
|
||||
{
|
||||
b3QueryFilter filter = { B3_DEFAULT_CATEGORY_BITS, B3_DEFAULT_MASK_BITS, 0, NULL };
|
||||
return filter;
|
||||
}
|
||||
|
||||
b3SurfaceMaterial b3DefaultSurfaceMaterial( void )
|
||||
{
|
||||
b3SurfaceMaterial surfaceMaterial = { 0 };
|
||||
surfaceMaterial.friction = 0.6f;
|
||||
return surfaceMaterial;
|
||||
}
|
||||
|
||||
b3ShapeDef b3DefaultShapeDef( void )
|
||||
{
|
||||
float lengthUnits = b3GetLengthUnitsPerMeter();
|
||||
|
||||
b3ShapeDef def = { 0 };
|
||||
def.baseMaterial = b3DefaultSurfaceMaterial();
|
||||
// density of water
|
||||
def.density = 1000.0f / ( lengthUnits * lengthUnits * lengthUnits );
|
||||
def.explosionScale = 1.0f;
|
||||
def.filter = b3DefaultFilter();
|
||||
def.updateBodyMass = true;
|
||||
def.invokeContactCreation = true;
|
||||
def.enableSpeculativeContact = true;
|
||||
def.internalValue = B3_SECRET_COOKIE;
|
||||
return def;
|
||||
}
|
||||
|
||||
static bool b3EmptyDrawShape( void* userShape, b3WorldTransform transform, b3HexColor color, void* context )
|
||||
{
|
||||
B3_UNUSED( userShape, transform, color, context );
|
||||
return false;
|
||||
}
|
||||
|
||||
static void b3EmptyDrawSegment( b3Pos p1, b3Pos p2, b3HexColor color, void* context )
|
||||
{
|
||||
B3_UNUSED( p1, p2, color, context );
|
||||
}
|
||||
|
||||
static void b3EmptyDrawTransform( b3WorldTransform transform, void* context )
|
||||
{
|
||||
B3_UNUSED( transform, context );
|
||||
}
|
||||
|
||||
static void b3EmptyDrawPoint( b3Pos p, float size, b3HexColor color, void* context )
|
||||
{
|
||||
B3_UNUSED( p, size, color, context );
|
||||
}
|
||||
|
||||
static void b3EmptyDrawSphere( b3Pos p, float radius, b3HexColor color, float alpha, void* context )
|
||||
{
|
||||
B3_UNUSED( p, radius, color, alpha, context );
|
||||
}
|
||||
|
||||
static void b3EmptyDrawCapsule( b3Pos p1, b3Pos p2, float radius, b3HexColor color, float alpha, void* context )
|
||||
{
|
||||
B3_UNUSED( p1, p2, radius, color, alpha, context );
|
||||
}
|
||||
|
||||
static void b3EmptyDrawBounds( b3AABB aabb, b3HexColor color, void* context )
|
||||
{
|
||||
B3_UNUSED( aabb, color, context );
|
||||
}
|
||||
|
||||
static void b3EmptyDrawBox( b3Vec3 extents, b3WorldTransform transform, b3HexColor color, void* context )
|
||||
{
|
||||
B3_UNUSED( extents, transform, color, context );
|
||||
}
|
||||
|
||||
static void b3EmptyDrawString( b3Pos p, const char* s, b3HexColor color, void* context )
|
||||
{
|
||||
B3_UNUSED( p, s, color, context );
|
||||
}
|
||||
|
||||
b3DebugDraw b3DefaultDebugDraw( void )
|
||||
{
|
||||
b3DebugDraw draw = { 0 };
|
||||
|
||||
// These allow the user to skip some implementations and not hit null exceptions.
|
||||
draw.DrawShapeFcn = b3EmptyDrawShape;
|
||||
draw.DrawSegmentFcn = b3EmptyDrawSegment;
|
||||
draw.DrawTransformFcn = b3EmptyDrawTransform;
|
||||
draw.DrawPointFcn = b3EmptyDrawPoint;
|
||||
draw.DrawSphereFcn = b3EmptyDrawSphere;
|
||||
draw.DrawCapsuleFcn = b3EmptyDrawCapsule;
|
||||
draw.DrawBoundsFcn = b3EmptyDrawBounds;
|
||||
draw.DrawBoxFcn = b3EmptyDrawBox;
|
||||
draw.DrawStringFcn = b3EmptyDrawString;
|
||||
|
||||
// Not too small, not too big.
|
||||
float h = 100.0f * b3GetLengthUnitsPerMeter();
|
||||
draw.drawingBounds = (b3AABB){
|
||||
.lowerBound = { -h, -h, -h },
|
||||
.upperBound = { h, h, h },
|
||||
};
|
||||
|
||||
draw.jointScale = 1.0f;
|
||||
draw.forceScale = 1.0f;
|
||||
|
||||
return draw;
|
||||
}
|
||||
2069
vendor/box3d/src/src/verstable.h
vendored
Normal file
2069
vendor/box3d/src/src/verstable.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
314
vendor/box3d/src/src/weld_joint.c
vendored
Normal file
314
vendor/box3d/src/src/weld_joint.c
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "body.h"
|
||||
#include "joint.h"
|
||||
#include "physics_world.h"
|
||||
#include "solver.h"
|
||||
#include "solver_set.h"
|
||||
#include "recording.h"
|
||||
|
||||
// needed for dll export
|
||||
#include "box3d/box3d.h"
|
||||
|
||||
void b3WeldJoint_SetLinearHertz( b3JointId jointId, float hertz )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, WeldJointSetLinearHertz, jointId, hertz );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint );
|
||||
base->weldJoint.linearHertz = hertz;
|
||||
}
|
||||
|
||||
float b3WeldJoint_GetLinearHertz( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint );
|
||||
return base->weldJoint.linearHertz;
|
||||
}
|
||||
|
||||
void b3WeldJoint_SetLinearDampingRatio( b3JointId jointId, float dampingRatio )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, WeldJointSetLinearDampingRatio, jointId, dampingRatio );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint );
|
||||
base->weldJoint.linearDampingRatio = dampingRatio;
|
||||
}
|
||||
|
||||
float b3WeldJoint_GetLinearDampingRatio( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint );
|
||||
return base->weldJoint.linearDampingRatio;
|
||||
}
|
||||
|
||||
void b3WeldJoint_SetAngularHertz( b3JointId jointId, float hertz )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, WeldJointSetAngularHertz, jointId, hertz );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint );
|
||||
base->weldJoint.angularHertz = hertz;
|
||||
}
|
||||
|
||||
float b3WeldJoint_GetAngularHertz( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint );
|
||||
return base->weldJoint.angularHertz;
|
||||
}
|
||||
|
||||
void b3WeldJoint_SetAngularDampingRatio( b3JointId jointId, float dampingRatio )
|
||||
{
|
||||
B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f );
|
||||
b3World* world = b3GetWorld( jointId.world0 );
|
||||
B3_REC( world, WeldJointSetAngularDampingRatio, jointId, dampingRatio );
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint );
|
||||
base->weldJoint.angularDampingRatio = dampingRatio;
|
||||
}
|
||||
|
||||
float b3WeldJoint_GetAngularDampingRatio( b3JointId jointId )
|
||||
{
|
||||
b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint );
|
||||
return base->weldJoint.angularDampingRatio;
|
||||
}
|
||||
|
||||
b3Vec3 b3GetWeldJointForce( b3World* world, b3JointSim* base )
|
||||
{
|
||||
b3Vec3 force = b3MulSV( world->inv_h, base->weldJoint.linearImpulse );
|
||||
return force;
|
||||
}
|
||||
|
||||
b3Vec3 b3GetWeldJointTorque( b3World* world, b3JointSim* base )
|
||||
{
|
||||
return b3MulSV( world->inv_h, base->weldJoint.angularImpulse );
|
||||
}
|
||||
|
||||
void b3PrepareWeldJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_weldJoint );
|
||||
|
||||
b3World* world = context->world;
|
||||
|
||||
b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA );
|
||||
b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB );
|
||||
|
||||
B3_ASSERT( bodyB->setIndex == b3_awakeSet );
|
||||
b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex );
|
||||
b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex );
|
||||
|
||||
int localIndexA = bodyA->localIndex;
|
||||
int localIndexB = bodyB->localIndex;
|
||||
|
||||
b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA );
|
||||
b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB );
|
||||
|
||||
base->invMassA = bodySimA->invMass;
|
||||
base->invMassB = bodySimB->invMass;
|
||||
base->invIA = bodySimA->invInertiaWorld;
|
||||
base->invIB = bodySimB->invInertiaWorld;
|
||||
|
||||
b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB );
|
||||
base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN;
|
||||
|
||||
b3WeldJoint* joint = &base->weldJoint;
|
||||
joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX;
|
||||
joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX;
|
||||
|
||||
// Compute joint anchor frames with world space rotation, relative to center of mass
|
||||
joint->frameA.q = b3MulQuat( bodySimA->transform.q, base->localFrameA.q );
|
||||
joint->frameA.p = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) );
|
||||
joint->frameB.q = b3MulQuat( bodySimB->transform.q, base->localFrameB.q );
|
||||
joint->frameB.p = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) );
|
||||
|
||||
joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center );
|
||||
joint->angularMass = b3InvertMatrix( invInertiaSum );
|
||||
|
||||
if ( joint->linearHertz == 0.0f )
|
||||
{
|
||||
joint->linearSpring = base->constraintSoftness;
|
||||
}
|
||||
else
|
||||
{
|
||||
joint->linearSpring = b3MakeSoft( joint->linearHertz, joint->linearDampingRatio, context->h );
|
||||
}
|
||||
|
||||
if ( joint->angularHertz == 0.0f )
|
||||
{
|
||||
joint->angularSpring = base->constraintSoftness;
|
||||
}
|
||||
else
|
||||
{
|
||||
joint->angularSpring = b3MakeSoft( joint->angularHertz, joint->angularDampingRatio, context->h );
|
||||
}
|
||||
|
||||
if ( context->enableWarmStarting == false )
|
||||
{
|
||||
joint->linearImpulse = b3Vec3_zero;
|
||||
joint->angularImpulse = b3Vec3_zero;
|
||||
}
|
||||
}
|
||||
|
||||
void b3WarmStartWeldJoint( b3JointSim* base, b3StepContext* context )
|
||||
{
|
||||
B3_ASSERT( base->type == b3_weldJoint );
|
||||
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3WeldJoint* joint = &base->weldJoint;
|
||||
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 vA = stateA->linearVelocity;
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 vB = stateB->linearVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p );
|
||||
|
||||
vA = b3MulSub( vA, mA, joint->linearImpulse );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Add( b3Cross( rA, joint->linearImpulse ), joint->angularImpulse ) ) );
|
||||
|
||||
vB = b3MulAdd( vB, mB, joint->linearImpulse );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Add( b3Cross( rB, joint->linearImpulse ), joint->angularImpulse ) ) );
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->linearVelocity = vA;
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->linearVelocity = vB;
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
|
||||
void b3SolveWeldJoint( b3JointSim* base, b3StepContext* context, bool useBias )
|
||||
{
|
||||
float mA = base->invMassA;
|
||||
float mB = base->invMassB;
|
||||
b3Matrix3 iA = base->invIA;
|
||||
b3Matrix3 iB = base->invIB;
|
||||
|
||||
// dummy state for static bodies
|
||||
b3BodyState dummyState = b3_identityBodyState;
|
||||
|
||||
b3WeldJoint* joint = &base->weldJoint;
|
||||
b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA;
|
||||
b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB;
|
||||
|
||||
b3Vec3 vA = stateA->linearVelocity;
|
||||
b3Vec3 wA = stateA->angularVelocity;
|
||||
b3Vec3 vB = stateB->linearVelocity;
|
||||
b3Vec3 wB = stateB->angularVelocity;
|
||||
|
||||
bool fixedRotation = base->fixedRotation;
|
||||
b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q );
|
||||
b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q );
|
||||
|
||||
if ( b3DotQuat( quatA, quatB ) < 0.0f )
|
||||
{
|
||||
// this keeps the rotation angle in the range [-pi, pi]
|
||||
quatB = b3NegateQuat( quatB );
|
||||
}
|
||||
|
||||
b3Quat relQ = b3InvMulQuat( quatA, quatB );
|
||||
|
||||
// angular constraint
|
||||
if ( fixedRotation == false )
|
||||
{
|
||||
b3Vec3 bias = b3Vec3_zero;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( useBias || joint->angularHertz > 0.0f )
|
||||
{
|
||||
b3Quat targetQuat = b3Quat_identity;
|
||||
b3Vec3 deltaRotation = b3DeltaQuatToRotation( relQ, targetQuat );
|
||||
b3Vec3 c = b3Neg( b3RotateVector( quatA, deltaRotation ) );
|
||||
|
||||
bias = b3MulSV( joint->angularSpring.biasRate, c );
|
||||
massScale = joint->angularSpring.massScale;
|
||||
impulseScale = joint->angularSpring.impulseScale;
|
||||
}
|
||||
|
||||
b3Vec3 cdot = b3Sub( wB, wA );
|
||||
b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b3MulMV( joint->angularMass, b3Add( cdot, bias ) ) ), impulseScale, joint->angularImpulse );
|
||||
joint->angularImpulse = b3Add( joint->angularImpulse, impulse );
|
||||
|
||||
wA = b3Sub( wA, b3MulMV( iA, impulse ) );
|
||||
wB = b3Add( wB, b3MulMV( iB, impulse ) );
|
||||
}
|
||||
|
||||
// linear constraint
|
||||
{
|
||||
b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p );
|
||||
b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p );
|
||||
|
||||
b3Vec3 cdot = b3Sub( b3Add( vB, b3Cross( wB, rB ) ), b3Add( vA, b3Cross( wA, rA ) ) );
|
||||
|
||||
b3Vec3 bias = b3Vec3_zero;
|
||||
float massScale = 1.0f;
|
||||
float impulseScale = 0.0f;
|
||||
if ( useBias || joint->linearHertz > 0.0f )
|
||||
{
|
||||
b3Vec3 dcA = stateA->deltaPosition;
|
||||
b3Vec3 dcB = stateB->deltaPosition;
|
||||
|
||||
b3Vec3 separation = b3Add( b3Add( b3Sub( dcB, dcA ), b3Sub( rB, rA ) ), joint->deltaCenter );
|
||||
|
||||
bias = b3MulSV( joint->linearSpring.biasRate, separation );
|
||||
massScale = joint->linearSpring.massScale;
|
||||
impulseScale = joint->linearSpring.impulseScale;
|
||||
}
|
||||
|
||||
//// K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)]
|
||||
b3Matrix3 sA = b3Skew( rA );
|
||||
b3Matrix3 sB = b3Skew( rB );
|
||||
b3Matrix3 kA = b3MulMM( sA, b3MulMM( base->invIA, sA ) );
|
||||
b3Matrix3 kB = b3MulMM( sB, b3MulMM( base->invIB, sB ) );
|
||||
b3Matrix3 k = b3NegateMat3( b3AddMM( kA, kB ) );
|
||||
k.cx.x += mA + mB;
|
||||
k.cy.y += mA + mB;
|
||||
k.cz.z += mA + mB;
|
||||
|
||||
b3Vec3 b = b3Solve3( k, b3Add( cdot, bias ) );
|
||||
|
||||
b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b ), impulseScale, joint->linearImpulse );
|
||||
joint->linearImpulse = b3Add( joint->linearImpulse, impulse );
|
||||
|
||||
vA = b3MulSub( vA, mA, impulse );
|
||||
wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) );
|
||||
vB = b3MulAdd( vB, mB, impulse );
|
||||
wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) );
|
||||
}
|
||||
|
||||
if ( stateA->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateA->linearVelocity = vA;
|
||||
stateA->angularVelocity = wA;
|
||||
}
|
||||
|
||||
if ( stateB->flags & b3_dynamicFlag )
|
||||
{
|
||||
stateB->linearVelocity = vB;
|
||||
stateB->angularVelocity = wB;
|
||||
}
|
||||
}
|
||||
|
||||
void b3DrawWeldJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale )
|
||||
{
|
||||
b3WorldTransform frameA = b3MulWorldTransforms( transformA, base->localFrameA );
|
||||
b3WorldTransform frameB = b3MulWorldTransforms( transformB, base->localFrameB );
|
||||
|
||||
b3Vec3 extents = { 0.1f * scale, 0.05f * scale, 0.025f * scale };
|
||||
draw->DrawBoxFcn( extents, frameA, b3_colorDarkOrange, draw->context );
|
||||
draw->DrawBoxFcn( extents, frameB, b3_colorDarkCyan, draw->context );
|
||||
}
|
||||
1105
vendor/box3d/src/src/wheel_joint.c
vendored
Normal file
1105
vendor/box3d/src/src/wheel_joint.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1335
vendor/box3d/src/src/world_snapshot.c
vendored
Normal file
1335
vendor/box3d/src/src/world_snapshot.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
21
vendor/box3d/src/src/world_snapshot.h
vendored
Normal file
21
vendor/box3d/src/src/world_snapshot.h
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// SPDX-FileCopyrightText: 2026 Erin Catto
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "recording.h"
|
||||
#include "recording_replay.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct b3World b3World;
|
||||
|
||||
// Serialize the live world into buf, interning shape geometry into rec->registry.
|
||||
// On success buf holds a self-contained snapshot image. Returns the byte count.
|
||||
int b3SerializeWorld( b3World* world, b3RecBuffer* buf, b3Recording* rec );
|
||||
|
||||
// Overwrite a freshly-created (shell) world with the simulation state held in the
|
||||
// snapshot image [data, size). Geometry references are resolved via the shared
|
||||
// registry slots in rdr. Returns false on a corrupt or incompatible image.
|
||||
bool b3DeserializeIntoShell( const uint8_t* data, int size, b3World* world, b3RecReader* rdr );
|
||||
Reference in New Issue
Block a user