fx2d 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. fx2d-0.1.0/.gitattributes +3 -0
  2. fx2d-0.1.0/.gitignore +28 -0
  3. fx2d-0.1.0/CITATION.cff +21 -0
  4. fx2d-0.1.0/CMakeLists.txt +582 -0
  5. fx2d-0.1.0/LICENSE +28 -0
  6. fx2d-0.1.0/PKG-INFO +119 -0
  7. fx2d-0.1.0/README.md +265 -0
  8. fx2d-0.1.0/Scene.yml +190 -0
  9. fx2d-0.1.0/include/Fx2D/AABBTree.h +254 -0
  10. fx2d-0.1.0/include/Fx2D/Core.h +10 -0
  11. fx2d-0.1.0/include/Fx2D/Entity.h +240 -0
  12. fx2d-0.1.0/include/Fx2D/Execution.h +19 -0
  13. fx2d-0.1.0/include/Fx2D/Geometry.h +485 -0
  14. fx2d-0.1.0/include/Fx2D/Input.h +120 -0
  15. fx2d-0.1.0/include/Fx2D/Joints.h +247 -0
  16. fx2d-0.1.0/include/Fx2D/Math.h +1192 -0
  17. fx2d-0.1.0/include/Fx2D/Profile.h +77 -0
  18. fx2d-0.1.0/include/Fx2D/Registry.h +484 -0
  19. fx2d-0.1.0/include/Fx2D/Renderer.h +78 -0
  20. fx2d-0.1.0/include/Fx2D/Scene.h +358 -0
  21. fx2d-0.1.0/include/Fx2D/Solver.h +467 -0
  22. fx2d-0.1.0/include/Fx2D/YamlUtils.h +64 -0
  23. fx2d-0.1.0/pyproject.toml +62 -0
  24. fx2d-0.1.0/python/README.md +93 -0
  25. fx2d-0.1.0/python/fx2d/__init__.py +45 -0
  26. fx2d-0.1.0/python/fx2d/envs/__init__.py +28 -0
  27. fx2d-0.1.0/python/fx2d/envs/base.py +120 -0
  28. fx2d-0.1.0/python/fx2d/envs/cartpole.py +76 -0
  29. fx2d-0.1.0/python/fx2d/envs/pendulum.py +64 -0
  30. fx2d-0.1.0/python/fx2d/envs/push.py +97 -0
  31. fx2d-0.1.0/python/src/module.cpp +844 -0
  32. fx2d-0.1.0/python/tests/test_builder.py +160 -0
  33. fx2d-0.1.0/python/tests/test_envs.py +118 -0
  34. fx2d-0.1.0/python/tests/test_scene.py +154 -0
  35. fx2d-0.1.0/scripts/.clang-format +80 -0
  36. fx2d-0.1.0/scripts/.clang-tidy +29 -0
  37. fx2d-0.1.0/scripts/bench.cpp +107 -0
  38. fx2d-0.1.0/scripts/bench_scenes.h +131 -0
  39. fx2d-0.1.0/scripts/cppcheck-suppressions.txt +17 -0
  40. fx2d-0.1.0/scripts/format.sh +27 -0
  41. fx2d-0.1.0/scripts/gen_compile_db.sh +59 -0
  42. fx2d-0.1.0/scripts/lint.sh +79 -0
  43. fx2d-0.1.0/scripts/list_sources.sh +14 -0
  44. fx2d-0.1.0/src/Collisions.cpp +893 -0
  45. fx2d-0.1.0/src/Constraints.cpp +693 -0
  46. fx2d-0.1.0/src/ContactGraph.cpp +122 -0
  47. fx2d-0.1.0/src/Entity.cpp +318 -0
  48. fx2d-0.1.0/src/Joints.cpp +557 -0
  49. fx2d-0.1.0/src/Profile.cpp +51 -0
  50. fx2d-0.1.0/src/Query.cpp +222 -0
  51. fx2d-0.1.0/src/Renderer.cpp +587 -0
  52. fx2d-0.1.0/src/Scene.cpp +739 -0
  53. fx2d-0.1.0/src/WindowIcon.inc +110 -0
  54. fx2d-0.1.0/src/YamlUtils.cpp +477 -0
  55. fx2d-0.1.0/src/main.cpp +12 -0
  56. fx2d-0.1.0/tests/goldens_solver_regression.inc +55 -0
  57. fx2d-0.1.0/tests/main.cpp +114 -0
  58. fx2d-0.1.0/tests/test_aabb_tree.cpp +88 -0
  59. fx2d-0.1.0/tests/test_adversarial.cpp +501 -0
  60. fx2d-0.1.0/tests/test_angle_precision.cpp +199 -0
  61. fx2d-0.1.0/tests/test_capsule_collision.cpp +257 -0
  62. fx2d-0.1.0/tests/test_ccd.cpp +89 -0
  63. fx2d-0.1.0/tests/test_chain.cpp +305 -0
  64. fx2d-0.1.0/tests/test_collisions_edge.cpp +228 -0
  65. fx2d-0.1.0/tests/test_contact_events.cpp +329 -0
  66. fx2d-0.1.0/tests/test_contact_graph.cpp +315 -0
  67. fx2d-0.1.0/tests/test_distance_joint.cpp +356 -0
  68. fx2d-0.1.0/tests/test_groups.cpp +175 -0
  69. fx2d-0.1.0/tests/test_harness.h +21 -0
  70. fx2d-0.1.0/tests/test_input.cpp +329 -0
  71. fx2d-0.1.0/tests/test_joints.cpp +341 -0
  72. fx2d-0.1.0/tests/test_mouse_joint.cpp +215 -0
  73. fx2d-0.1.0/tests/test_queries.cpp +274 -0
  74. fx2d-0.1.0/tests/test_resting_stability.cpp +364 -0
  75. fx2d-0.1.0/tests/test_scene_builders.h +79 -0
  76. fx2d-0.1.0/tests/test_slingshot.cpp +353 -0
  77. fx2d-0.1.0/tests/test_solver_regression.cpp +261 -0
  78. fx2d-0.1.0/tests/test_yaml_inertia.cpp +221 -0
@@ -0,0 +1,3 @@
1
+ # Shell scripts must keep LF endings, otherwise they fail to run after a
2
+ # checkout on Windows (bash reports "bad interpreter" on the CR).
3
+ *.sh text eol=lf
fx2d-0.1.0/.gitignore ADDED
@@ -0,0 +1,28 @@
1
+ build
2
+ /cmake/
3
+ build-headless
4
+ build-web
5
+ build-lint
6
+ # Example textures live under examples/*/assets/ (committed, downscaled).
7
+ # Ignore only a leftover root-level assets/ folder used for local extras.
8
+ /assets/
9
+ bin
10
+ lib/imgui/*
11
+ !lib/imgui/.gitkeep
12
+ lib/rlImGui/*
13
+ !lib/rlImGui/.gitkeep
14
+ imgui.ini
15
+ *.exe
16
+ .vscode
17
+ .vscode/*
18
+ *.json
19
+ !docs/package.json
20
+ !docs/package-lock.json
21
+ ./Makefile
22
+ ./Scene.yml
23
+ # Python package builds
24
+ __pycache__/
25
+ *.egg-info/
26
+ /dist/
27
+ /wheelhouse/
28
+ .pytest_cache/
@@ -0,0 +1,21 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use Fx2D in your work, please cite it as below."
3
+ title: "Fx2D: A 2D rigid-body physics engine in C++20"
4
+ type: software
5
+ authors:
6
+ - family-names: Irigireddy
7
+ given-names: Bharath Chandra
8
+ repository-code: "https://github.com/Bharath2/fx2d-physics-engine"
9
+ url: "https://bharath2.github.io/fx2d-physics-engine/"
10
+ license: BSD-3-Clause
11
+ keywords:
12
+ - physics engine
13
+ - reinforcement learning
14
+ - gymnasium
15
+ - python
16
+ - rigid body dynamics
17
+ - XPBD
18
+ - collision detection
19
+ - C++20
20
+ - simulation
21
+ date-released: 2025-06-16
@@ -0,0 +1,582 @@
1
+ # ============================================================
2
+ # Project Definition
3
+ # ============================================================
4
+ cmake_minimum_required(VERSION 3.16)
5
+ project(Fx2DProject LANGUAGES CXX)
6
+
7
+ set(CMAKE_CXX_STANDARD 20)
8
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
9
+
10
+ set(TARGET_NAME Fx2D)
11
+
12
+ # Built on its own, Fx2D defaults to the full build: viewer, tests and the YAML loader. Added to
13
+ # another project with add_subdirectory() or FetchContent, it defaults to the headless physics
14
+ # library, without tests.
15
+ if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
16
+ set(FX2D_IS_TOP_LEVEL ON)
17
+ set(FX2D_HEADLESS_DEFAULT OFF)
18
+ else()
19
+ set(FX2D_IS_TOP_LEVEL OFF)
20
+ set(FX2D_HEADLESS_DEFAULT ON)
21
+ endif()
22
+
23
+ # Options
24
+ option(FX2D_WARNINGS "Enable extra warnings" ON)
25
+ option(FX2D_WERROR "Treat warnings as errors" OFF)
26
+ option(FX2D_BUILD_TESTS "Build unit tests" ${FX2D_IS_TOP_LEVEL})
27
+
28
+ # Headless builds the physics library and tests without the viewer: no raylib, no Dear ImGui.
29
+ # Renderer.cpp, the viewer executable and the visual examples are left out.
30
+ option(FX2D_HEADLESS "Build without the viewer (no raylib/ImGui)" ${FX2D_HEADLESS_DEFAULT})
31
+
32
+ # The YAML scene loader (FxYAML) is the only part of the library that uses a third-party
33
+ # package. With it off, the library builds from the C++ standard library alone.
34
+ option(FX2D_YAML "Build the YAML scene loader (needs yaml-cpp)" ON)
35
+
36
+ # Anything needed but not installed is downloaded and built with the project, so a fresh clone
37
+ # configures without installing packages first. Turn this off to require installed packages
38
+ # instead, for distribution packaging or offline builds.
39
+ option(FX2D_FETCH_DEPS "Download yaml-cpp, raylib, Dear ImGui and rlImGui when not installed" ON)
40
+
41
+ # The Python module in python/, which scikit-build-core builds from pyproject.toml. Everything it
42
+ # links is static, so all of it has to be position-independent.
43
+ option(FX2D_PYTHON "Build the Python module" OFF)
44
+ if (FX2D_PYTHON)
45
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
46
+ endif()
47
+
48
+ # ============================================================
49
+ # Dependencies: use what is installed, otherwise download it
50
+ # ============================================================
51
+ include(FetchContent)
52
+ if (POLICY CMP0135)
53
+ cmake_policy(SET CMP0135 NEW) # extracted files get the extraction time as their timestamp
54
+ endif()
55
+
56
+ # What is downloaded when a package is not installed. Pinned with checksums, so every fresh
57
+ # build compiles the same sources.
58
+ set(FX2D_YAMLCPP_URL "https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.9.0.tar.gz")
59
+ set(FX2D_YAMLCPP_SHA256 "25cb043240f828a8c51beb830569634bc7ac603978e0f69d6b63558dadefd49a")
60
+ set(FX2D_RAYLIB_URL "https://github.com/raysan5/raylib/archive/refs/tags/5.5.tar.gz")
61
+ set(FX2D_RAYLIB_SHA256 "aea98ecf5bc5c5e0b789a76de0083a21a70457050ea4cc2aec7566935f5e258e")
62
+ set(FX2D_IMGUI_URL "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.9b.tar.gz")
63
+ set(FX2D_IMGUI_SHA256 "21d8a0a565e85dce943e375db00812c2f3f0ab21f3f0f7964e364a63422d7f99")
64
+ set(FX2D_RLIMGUI_URL "https://github.com/raylib-extras/rlImGui/archive/1550009359ad975927f7f0e4a3f47e3f27123ea9.tar.gz")
65
+ set(FX2D_RLIMGUI_SHA256 "7d04ff0c996a89190e1050d3c4448ad784479723c76893efea2d731bd5eb839f")
66
+
67
+ function(fx2d_require_fetch what)
68
+ if (NOT FX2D_FETCH_DEPS)
69
+ message(FATAL_ERROR "Fx2D: ${what} was not found and FX2D_FETCH_DEPS is OFF. Install it, "
70
+ "or configure with -DFX2D_FETCH_DEPS=ON to download it.")
71
+ endif()
72
+ message(STATUS "Fx2D: ${what} not found; downloading it")
73
+ endfunction()
74
+
75
+ # A downloaded dependency's headers are third-party code: include them as system headers, so the
76
+ # project's warning flags, and -Werror, stop at its own code.
77
+ function(fx2d_system_includes tgt)
78
+ get_target_property(dirs ${tgt} INTERFACE_INCLUDE_DIRECTORIES)
79
+ if (dirs)
80
+ set_target_properties(${tgt} PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${dirs}")
81
+ endif()
82
+ endfunction()
83
+
84
+ # --- yaml-cpp ---
85
+ if (FX2D_YAML)
86
+ find_package(yaml-cpp CONFIG QUIET)
87
+ if (TARGET yaml-cpp::yaml-cpp)
88
+ set(YAMLCPP_TARGET yaml-cpp::yaml-cpp)
89
+ elseif (TARGET yaml-cpp) # MSYS2 exports the target without a namespace
90
+ set(YAMLCPP_TARGET yaml-cpp)
91
+ else()
92
+ fx2d_require_fetch("yaml-cpp")
93
+ set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
94
+ set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
95
+ set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "" FORCE)
96
+ set(YAML_CPP_INSTALL OFF CACHE BOOL "" FORCE)
97
+ set(YAML_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
98
+ FetchContent_Declare(fx2d_yamlcpp URL ${FX2D_YAMLCPP_URL} URL_HASH SHA256=${FX2D_YAMLCPP_SHA256})
99
+ FetchContent_MakeAvailable(fx2d_yamlcpp)
100
+ fx2d_system_includes(yaml-cpp)
101
+ set(YAMLCPP_TARGET yaml-cpp::yaml-cpp)
102
+ endif()
103
+ endif()
104
+
105
+ # --- raylib ---
106
+ if (NOT FX2D_HEADLESS)
107
+ find_package(raylib CONFIG QUIET)
108
+ if (TARGET raylib)
109
+ set(RAYLIB_TARGET raylib)
110
+ elseif (TARGET raylib::raylib)
111
+ set(RAYLIB_TARGET raylib::raylib)
112
+ else()
113
+ find_package(PkgConfig QUIET)
114
+ if (PkgConfig_FOUND)
115
+ pkg_check_modules(RAYLIB QUIET raylib)
116
+ endif()
117
+ if (RAYLIB_FOUND)
118
+ add_library(raylib_pkg INTERFACE)
119
+ target_include_directories(raylib_pkg INTERFACE ${RAYLIB_INCLUDE_DIRS})
120
+ target_link_libraries(raylib_pkg INTERFACE ${RAYLIB_LINK_LIBRARIES})
121
+ set(RAYLIB_TARGET raylib_pkg)
122
+ else()
123
+ # raylib is written in C. On Linux it still needs the system's X11 and OpenGL development
124
+ # packages; the installation guide lists them.
125
+ fx2d_require_fetch("raylib")
126
+ enable_language(C)
127
+ set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
128
+ FetchContent_Declare(fx2d_raylib URL ${FX2D_RAYLIB_URL} URL_HASH SHA256=${FX2D_RAYLIB_SHA256})
129
+ FetchContent_MakeAvailable(fx2d_raylib)
130
+ fx2d_system_includes(raylib)
131
+ set(RAYLIB_TARGET raylib)
132
+ endif()
133
+ endif()
134
+ endif()
135
+
136
+ # ============================================================
137
+ # ImGui + rlImGui
138
+ # Sources placed in lib/imgui and lib/rlImGui are used when present; otherwise they are
139
+ # downloaded.
140
+ # ============================================================
141
+ if (NOT FX2D_HEADLESS)
142
+ set(FX2D_IMGUI_DIR "${CMAKE_CURRENT_SOURCE_DIR}/lib/imgui" CACHE PATH "Path to Dear ImGui sources")
143
+ set(FX2D_RLIMGUI_DIR "${CMAKE_CURRENT_SOURCE_DIR}/lib/rlImGui" CACHE PATH "Path to rlImGui sources")
144
+
145
+ if (EXISTS "${FX2D_IMGUI_DIR}/imgui.cpp")
146
+ set(FX2D_IMGUI_SOURCE_DIR "${FX2D_IMGUI_DIR}")
147
+ else()
148
+ fx2d_require_fetch("Dear ImGui")
149
+ FetchContent_Declare(fx2d_imgui URL ${FX2D_IMGUI_URL} URL_HASH SHA256=${FX2D_IMGUI_SHA256})
150
+ FetchContent_MakeAvailable(fx2d_imgui)
151
+ set(FX2D_IMGUI_SOURCE_DIR "${fx2d_imgui_SOURCE_DIR}")
152
+ endif()
153
+ if (EXISTS "${FX2D_RLIMGUI_DIR}/rlImGui.cpp")
154
+ set(FX2D_RLIMGUI_SOURCE_DIR "${FX2D_RLIMGUI_DIR}")
155
+ else()
156
+ fx2d_require_fetch("rlImGui")
157
+ FetchContent_Declare(fx2d_rlimgui URL ${FX2D_RLIMGUI_URL} URL_HASH SHA256=${FX2D_RLIMGUI_SHA256})
158
+ FetchContent_MakeAvailable(fx2d_rlimgui)
159
+ set(FX2D_RLIMGUI_SOURCE_DIR "${fx2d_rlimgui_SOURCE_DIR}")
160
+ endif()
161
+
162
+ add_library(rlimgui_library STATIC
163
+ "${FX2D_IMGUI_SOURCE_DIR}/imgui.cpp"
164
+ "${FX2D_IMGUI_SOURCE_DIR}/imgui_draw.cpp"
165
+ "${FX2D_IMGUI_SOURCE_DIR}/imgui_tables.cpp"
166
+ "${FX2D_IMGUI_SOURCE_DIR}/imgui_widgets.cpp"
167
+ "${FX2D_RLIMGUI_SOURCE_DIR}/rlImGui.cpp"
168
+ )
169
+
170
+ target_include_directories(rlimgui_library SYSTEM PUBLIC
171
+ "${FX2D_IMGUI_SOURCE_DIR}"
172
+ "${FX2D_RLIMGUI_SOURCE_DIR}"
173
+ )
174
+
175
+ # rlImGui needs raylib headers/types
176
+ target_link_libraries(rlimgui_library PUBLIC ${RAYLIB_TARGET})
177
+ endif()
178
+
179
+ # ============================================================
180
+ # Fx2D Library
181
+ # ============================================================
182
+ set(FX2D_CORE_SOURCES
183
+ src/Entity.cpp
184
+ src/Profile.cpp
185
+ src/Scene.cpp
186
+ src/Collisions.cpp
187
+ src/ContactGraph.cpp
188
+ src/Constraints.cpp
189
+ src/Joints.cpp
190
+ src/Query.cpp
191
+ )
192
+
193
+ if (FX2D_YAML)
194
+ list(APPEND FX2D_CORE_SOURCES src/YamlUtils.cpp)
195
+ endif()
196
+ if (NOT FX2D_HEADLESS)
197
+ list(APPEND FX2D_CORE_SOURCES src/Renderer.cpp)
198
+ endif()
199
+
200
+ add_library(Fx2Dlib STATIC ${FX2D_CORE_SOURCES})
201
+ add_library(Fx2D::Fx2D ALIAS Fx2Dlib)
202
+
203
+ target_include_directories(Fx2Dlib PUBLIC
204
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
205
+ $<INSTALL_INTERFACE:include>
206
+ )
207
+
208
+ # The physics depends on nothing outside the C++ standard library.
209
+ if (FX2D_YAML)
210
+ target_link_libraries(Fx2Dlib PUBLIC ${YAMLCPP_TARGET})
211
+ target_compile_definitions(Fx2Dlib PUBLIC YAML_CPP_STATIC_DEFINE)
212
+ else()
213
+ target_compile_definitions(Fx2Dlib PUBLIC FX2D_NO_YAML)
214
+ endif()
215
+
216
+ if (NOT FX2D_HEADLESS)
217
+ target_link_libraries(Fx2Dlib PUBLIC rlimgui_library ${RAYLIB_TARGET})
218
+ endif()
219
+
220
+ # When TBB's headers are installed, libstdc++ builds std::execution on TBB, and unoptimised builds
221
+ # reference it even for the sequential policy the engine uses. Link it whenever it is found; it is
222
+ # also what std::execution::par passed to for_each_entity()/transform_entities() runs on.
223
+ find_package(TBB QUIET)
224
+ if (TARGET TBB::tbb)
225
+ target_link_libraries(Fx2Dlib PUBLIC TBB::tbb)
226
+ endif()
227
+
228
+ # Compile definitions for the library
229
+ target_compile_definitions(Fx2Dlib PUBLIC
230
+ NO_FONT_AWESOME
231
+ $<$<CONFIG:Debug>:DEBUG>
232
+ )
233
+
234
+ # ============================================================
235
+ # Main executable
236
+ # ============================================================
237
+ # The viewer and the visual examples load YAML scenes, so they need FX2D_YAML.
238
+ if (NOT FX2D_HEADLESS AND FX2D_YAML)
239
+ set(FX2D_BUILD_VIEWER ON)
240
+ else()
241
+ set(FX2D_BUILD_VIEWER OFF)
242
+ endif()
243
+
244
+ if (FX2D_BUILD_VIEWER)
245
+ add_executable(${TARGET_NAME} src/main.cpp)
246
+ target_link_libraries(${TARGET_NAME} PRIVATE Fx2Dlib)
247
+ endif()
248
+
249
+ # ============================================================
250
+ # Build configuration options
251
+ # ============================================================
252
+ option(FX2D_WARNINGS "Enable high warning levels" ON)
253
+ option(FX2D_WERROR "Treat warnings as errors" OFF)
254
+ option(FX2D_NATIVE "Enable native CPU tuning (SIMD etc.)" OFF)
255
+ option(FX2D_IPO "Enable interprocedural optimization (LTO/IPO) in Release" ON)
256
+
257
+ # Floating-point contraction (a*b+c fused into one FMA) is a compiler's choice by default:
258
+ # GCC and Clang both contract, MSVC does not, and whether a given expression fuses depends on
259
+ # the target ISA. That makes a bench A/B between two builds, or between x86 and ARM, compare
260
+ # two different sets of arithmetic. Pinning it off costs a little speed and buys results that
261
+ # only move when the code moves. It is PUBLIC on Fx2Dlib because the maths lives in headers,
262
+ # so a consumer compiling Math.h with contraction on would not match the library it links.
263
+ option(FX2D_PIN_FP_CONTRACT "Pin off FP contraction (FMA) so results are build-independent" ON)
264
+
265
+ # The per-phase step profiler. Off by default: the timers are cheap but not free, and a
266
+ # release build should not pay for them. Fx2DBench prints the split when this is on.
267
+ option(FX2D_PROFILE "Build with the per-phase step profiler (FxProfile)" OFF)
268
+
269
+ # FX2D_NATIVE tunes for the machine doing the build, which is right for development and wrong
270
+ # for anything you ship, so it is off by default; results are the same bits either way. Set
271
+ # FX2D_ARCH_BASELINE to pin a fixed instruction set instead; it takes precedence when non-empty.
272
+ # Accepted: sse2, avx2, avx512 (x86) and armv8-a, armv8.2-a (arm64). Empty means "follow
273
+ # FX2D_NATIVE".
274
+ set(FX2D_ARCH_BASELINE "" CACHE STRING "Fixed ISA baseline for distributable builds")
275
+ set_property(CACHE FX2D_ARCH_BASELINE PROPERTY STRINGS "" sse2 avx2 avx512 armv8-a armv8.2-a)
276
+
277
+ # ============================================================
278
+ # Target architecture
279
+ # ============================================================
280
+ # CMAKE_SYSTEM_PROCESSOR spells the same architecture differently on every platform -- x86_64
281
+ # on Linux, AMD64 on Windows, arm64 on macOS, aarch64 on Linux ARM. Normalise it once so the
282
+ # tuning flags below can be chosen by architecture rather than by guessing from the compiler.
283
+ if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
284
+ set(FX2D_ARCH "arm64")
285
+ elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
286
+ set(FX2D_ARCH "x86_64")
287
+ elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(i[3-6]86|x86|X86)$")
288
+ set(FX2D_ARCH "x86")
289
+ else()
290
+ set(FX2D_ARCH "other")
291
+ endif()
292
+ message(STATUS "Fx2D target architecture: ${FX2D_ARCH} (CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR})")
293
+
294
+ include(CheckCXXCompilerFlag)
295
+
296
+ # Adds a compile flag only if this compiler accepts it. Every architecture flag goes through
297
+ # here: -march=native is rejected by Apple Clang on arm64, /arch: does not exist for MSVC ARM64,
298
+ # and -mcpu=native is missing from older Apple Clang -- all of which are hard errors, not
299
+ # warnings, so probing is the only way to stay portable.
300
+ function(fx2d_add_flag_if_supported tgt flag)
301
+ string(MAKE_C_IDENTIFIER "FX2D_HAS_FLAG_${flag}" probe)
302
+ check_cxx_compiler_flag("${flag}" ${probe})
303
+ if (${probe})
304
+ target_compile_options(${tgt} PRIVATE "${flag}")
305
+ endif()
306
+ endfunction()
307
+
308
+ # ============================================================
309
+ # Helper function to apply common compile options
310
+ # ============================================================
311
+ function(fx2d_tune_target tgt)
312
+ # --- Warnings ---
313
+ if(FX2D_WARNINGS)
314
+ if (MSVC)
315
+ target_compile_options(${tgt} PRIVATE /W4)
316
+ if (FX2D_WERROR)
317
+ target_compile_options(${tgt} PRIVATE /WX)
318
+ endif()
319
+ else()
320
+ target_compile_options(${tgt} PRIVATE -Wall -Wextra -Wconversion -Wshadow -Wpedantic)
321
+ if (FX2D_WERROR)
322
+ target_compile_options(${tgt} PRIVATE -Werror)
323
+ endif()
324
+ endif()
325
+ endif()
326
+
327
+ # --- Optimization per-config (don't overwrite CMake defaults) ---
328
+ if (MSVC)
329
+ target_compile_options(${tgt} PRIVATE $<$<CONFIG:Release>:/O2>)
330
+ else()
331
+ target_compile_options(${tgt} PRIVATE $<$<CONFIG:Release>:-O3>)
332
+ endif()
333
+
334
+ # --- Vectoriser budget ---
335
+ # GCC abandons a loop that would need more runtime alias checks than this budget allows
336
+ # (default 10), and the batched contact solve reads and writes around twenty columns. Without
337
+ # this the two hot solve loops silently fall back to scalar; with it they vectorise to 32-byte
338
+ # vectors, measured at 26% off the `stacks` step and 14% off `pile`.
339
+ #
340
+ # Gated on the compiler being GCC rather than probed: Clang *accepts* the spelling and then
341
+ # warns that it went unused, which -Werror turns into an error. Clang has its own budget and
342
+ # vectorises these loops without help.
343
+ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
344
+ target_compile_options(${tgt} PRIVATE "--param=vect-max-version-for-alias-checks=200")
345
+ endif()
346
+
347
+ # --- CPU features (auto-vectorization benefits) ---
348
+ if (EMSCRIPTEN)
349
+ # WebAssembly has no host CPU to tune for, so the browser build takes no ISA flags.
350
+ elseif (NOT FX2D_ARCH_BASELINE STREQUAL "")
351
+ # A fixed baseline: the binary may assume this ISA wherever it runs.
352
+ if (MSVC)
353
+ if (FX2D_ARCH_BASELINE STREQUAL "sse2" AND FX2D_ARCH STREQUAL "x86")
354
+ target_compile_options(${tgt} PRIVATE /arch:SSE2)
355
+ elseif (FX2D_ARCH_BASELINE STREQUAL "avx2")
356
+ target_compile_options(${tgt} PRIVATE /arch:AVX2)
357
+ elseif (FX2D_ARCH_BASELINE STREQUAL "avx512")
358
+ target_compile_options(${tgt} PRIVATE /arch:AVX512)
359
+ endif()
360
+ # x86-64 MSVC is SSE2 by default and ARM64 MSVC is NEON by default, so those two
361
+ # baselines need no flag at all.
362
+ elseif (FX2D_ARCH_BASELINE STREQUAL "sse2")
363
+ fx2d_add_flag_if_supported(${tgt} "-msse2")
364
+ elseif (FX2D_ARCH_BASELINE STREQUAL "avx2")
365
+ fx2d_add_flag_if_supported(${tgt} "-mavx2")
366
+ fx2d_add_flag_if_supported(${tgt} "-mfma")
367
+ elseif (FX2D_ARCH_BASELINE STREQUAL "avx512")
368
+ fx2d_add_flag_if_supported(${tgt} "-mavx512f")
369
+ elseif (FX2D_ARCH_BASELINE MATCHES "^armv8")
370
+ fx2d_add_flag_if_supported(${tgt} "-march=${FX2D_ARCH_BASELINE}")
371
+ else()
372
+ message(WARNING "Fx2D: unknown FX2D_ARCH_BASELINE '${FX2D_ARCH_BASELINE}', ignoring")
373
+ endif()
374
+ elseif (FX2D_NATIVE)
375
+ # Tune for the machine doing the build. Never use this for a binary you ship.
376
+ if (MSVC)
377
+ if (FX2D_ARCH STREQUAL "x86_64" OR FX2D_ARCH STREQUAL "x86")
378
+ # AVX2 is a solid baseline; bump to AVX512 if you know your CPUs
379
+ target_compile_options(${tgt} PRIVATE /arch:AVX2)
380
+ endif()
381
+ # MSVC ARM64 rejects /arch: entirely. NEON is mandatory in ARMv8, so there is nothing
382
+ # to ask for -- the vector unit is already assumed.
383
+ elseif (FX2D_ARCH STREQUAL "arm64")
384
+ # -march=native is an error on Apple Clang for arm64; -mcpu=native is the spelling that
385
+ # works on both GCC and Clang, and the probe covers Apple Clang versions predating it.
386
+ fx2d_add_flag_if_supported(${tgt} "-mcpu=native")
387
+ else()
388
+ fx2d_add_flag_if_supported(${tgt} "-march=native")
389
+ fx2d_add_flag_if_supported(${tgt} "-mtune=native")
390
+ endif()
391
+ endif()
392
+
393
+ # --- Determinism: no silent FMA contraction (see FX2D_PIN_FP_CONTRACT) ---
394
+ if (FX2D_PIN_FP_CONTRACT)
395
+ if (MSVC)
396
+ target_compile_options(${tgt} PRIVATE /fp:precise)
397
+ else()
398
+ target_compile_options(${tgt} PRIVATE -ffp-contract=off)
399
+ endif()
400
+ endif()
401
+
402
+ # --- Sanitizers in Debug (helpful; skip on MSVC by default) ---
403
+ if (NOT MSVC)
404
+ target_compile_options(${tgt} PRIVATE $<$<CONFIG:Debug>:-fsanitize=address,undefined>)
405
+ target_link_options(${tgt} PRIVATE $<$<CONFIG:Debug>:-fsanitize=address,undefined>)
406
+ endif()
407
+ endfunction()
408
+
409
+ # ============================================================
410
+ # Apply tuning to targets
411
+ # ============================================================
412
+ fx2d_tune_target(Fx2Dlib)
413
+ if (FX2D_BUILD_VIEWER)
414
+ fx2d_tune_target(${TARGET_NAME})
415
+ endif()
416
+
417
+ # Both of these have to reach anything that includes the headers, not just the library's own
418
+ # translation units: FxProfile's storage is only defined when the library was built with the
419
+ # profiler on, and the FP-contract pin is meaningless if a consumer compiles Math.h differently.
420
+ if (FX2D_PROFILE)
421
+ target_compile_definitions(Fx2Dlib PUBLIC FX2D_PROFILE)
422
+ endif()
423
+ if (FX2D_PIN_FP_CONTRACT)
424
+ if (MSVC)
425
+ target_compile_options(Fx2Dlib PUBLIC /fp:precise)
426
+ else()
427
+ target_compile_options(Fx2Dlib PUBLIC -ffp-contract=off)
428
+ endif()
429
+ endif()
430
+
431
+ # ============================================================
432
+ # Examples
433
+ # ============================================================
434
+ # Each example resolves its Scene.yml relative to the working directory, so run
435
+ # the resulting binaries from the repo root.
436
+ # Headless builds get the two renderer-free examples; full builds get all of them.
437
+ option(FX2D_BUILD_EXAMPLES "Build the example programs" OFF)
438
+
439
+ if (FX2D_BUILD_EXAMPLES AND NOT FX2D_YAML)
440
+ message(STATUS "Fx2D: the examples load YAML scenes; skipping them because FX2D_YAML is OFF")
441
+ endif()
442
+
443
+ if (FX2D_BUILD_EXAMPLES AND FX2D_YAML)
444
+ add_executable(truck_headless examples/truck/main_headless.cpp)
445
+ add_executable(joint_control_headless examples/joint_control_demo/main.cpp)
446
+ foreach(example_target truck_headless joint_control_headless)
447
+ target_link_libraries(${example_target} PRIVATE Fx2Dlib)
448
+ fx2d_tune_target(${example_target})
449
+ endforeach()
450
+ endif()
451
+
452
+ if (FX2D_BUILD_EXAMPLES AND FX2D_BUILD_VIEWER)
453
+ add_executable(example_truck examples/truck/main.cpp)
454
+ add_executable(example_joint_control examples/joint_control_demo/main_visual.cpp)
455
+ add_executable(example_stacked_boxes examples/stacked_boxes/main.cpp)
456
+ add_executable(example_angry_boxes examples/angry_boxes/main.cpp)
457
+ add_executable(example_chain_terrain examples/chain_terrain/main.cpp)
458
+ add_executable(example_bucket_fill examples/bucket_fill/main.cpp)
459
+ add_executable(example_playground examples/playground/main.cpp)
460
+
461
+ foreach(example_target example_truck example_joint_control example_stacked_boxes example_angry_boxes
462
+ example_chain_terrain example_bucket_fill example_playground)
463
+ target_link_libraries(${example_target} PRIVATE Fx2Dlib)
464
+ fx2d_tune_target(${example_target})
465
+ endforeach()
466
+ endif()
467
+
468
+ # ============================================================
469
+ # Browser playground (Emscripten)
470
+ # ============================================================
471
+ # Configure through emcmake; scripts/build_web.sh does the whole thing, dependencies included.
472
+ option(FX2D_BUILD_WEB "Build the browser playground (needs the Emscripten toolchain)" OFF)
473
+
474
+ if (FX2D_BUILD_WEB)
475
+ if (NOT EMSCRIPTEN)
476
+ message(FATAL_ERROR "FX2D_BUILD_WEB needs the Emscripten toolchain: configure with emcmake.")
477
+ endif()
478
+ add_executable(playground_web examples/playground/main.cpp)
479
+ target_link_libraries(playground_web PRIVATE Fx2Dlib)
480
+ fx2d_tune_target(playground_web)
481
+ set_target_properties(playground_web PROPERTIES OUTPUT_NAME playground SUFFIX ".js")
482
+ # The scene file is read at the same relative path the desktop build uses.
483
+ target_link_options(playground_web PRIVATE
484
+ -sUSE_GLFW=3 -sALLOW_MEMORY_GROWTH=1 -sASSERTIONS=0 -sSTACK_SIZE=1MB
485
+ "--preload-file=${CMAKE_CURRENT_SOURCE_DIR}/examples/playground/Scene.yml@examples/playground/Scene.yml")
486
+ endif()
487
+
488
+ # ============================================================
489
+ # Benchmark
490
+ # ============================================================
491
+ # Renderer-free, so it builds headless. Threading changes need an A/B measurement before they
492
+ # land (see docs/engineering-notes.md); this is what produces it.
493
+ option(FX2D_BUILD_BENCH "Build the step-time benchmark" OFF)
494
+
495
+ if (FX2D_BUILD_BENCH)
496
+ add_executable(Fx2DBench scripts/bench.cpp)
497
+ target_link_libraries(Fx2DBench PRIVATE Fx2Dlib)
498
+ fx2d_tune_target(Fx2DBench)
499
+ endif()
500
+
501
+ # ============================================================
502
+ # Python module
503
+ # ============================================================
504
+ if (FX2D_PYTHON)
505
+ find_package(Python 3.10 REQUIRED COMPONENTS Interpreter Development.Module)
506
+ execute_process(COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
507
+ OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT)
508
+ find_package(nanobind CONFIG REQUIRED)
509
+ nanobind_add_module(_fx2d NB_STATIC python/src/module.cpp)
510
+ target_link_libraries(_fx2d PRIVATE Fx2Dlib)
511
+ install(TARGETS _fx2d LIBRARY DESTINATION fx2d)
512
+ endif()
513
+
514
+ if (FX2D_BUILD_TESTS)
515
+ enable_testing()
516
+
517
+ set(FX2D_TEST_SOURCES
518
+ tests/main.cpp
519
+ tests/test_aabb_tree.cpp
520
+ tests/test_ccd.cpp
521
+ tests/test_angle_precision.cpp
522
+ tests/test_resting_stability.cpp
523
+ tests/test_contact_events.cpp
524
+ tests/test_adversarial.cpp
525
+ tests/test_input.cpp
526
+ tests/test_queries.cpp
527
+ tests/test_groups.cpp
528
+ tests/test_slingshot.cpp
529
+ tests/test_contact_graph.cpp
530
+ tests/test_solver_regression.cpp
531
+ )
532
+ # These suites load YAML scenes. Without FX2D_YAML they are left out, and tests/main.cpp
533
+ # skips them, so the dependency-free build still runs the rest.
534
+ if (FX2D_YAML)
535
+ list(APPEND FX2D_TEST_SOURCES
536
+ tests/test_joints.cpp
537
+ tests/test_capsule_collision.cpp
538
+ tests/test_collisions_edge.cpp
539
+ tests/test_yaml_inertia.cpp
540
+ tests/test_chain.cpp
541
+ tests/test_mouse_joint.cpp
542
+ tests/test_distance_joint.cpp
543
+ )
544
+ endif()
545
+
546
+ add_executable(Fx2DTests ${FX2D_TEST_SOURCES})
547
+
548
+ target_link_libraries(Fx2DTests PRIVATE Fx2Dlib)
549
+ # test_slingshot.cpp drives the example's FxSlingshot directly, so the mechanic that ships
550
+ # in examples/angry_boxes is the one under test.
551
+ target_include_directories(Fx2DTests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/examples)
552
+ fx2d_tune_target(Fx2DTests)
553
+ add_test(NAME Fx2DTests COMMAND Fx2DTests)
554
+ endif()
555
+
556
+ # ============================================================
557
+ # Interprocedural Optimization (LTO/IPO)
558
+ # ============================================================
559
+ if (FX2D_IPO)
560
+ cmake_policy(SET CMP0069 NEW)
561
+ include(CheckIPOSupported)
562
+ check_ipo_supported(RESULT ipo_ok OUTPUT ipo_msg)
563
+ if (ipo_ok)
564
+ set_property(TARGET Fx2Dlib PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
565
+ if (TARGET ${TARGET_NAME})
566
+ set_property(TARGET ${TARGET_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
567
+ endif()
568
+ else()
569
+ message(STATUS "IPO not supported: ${ipo_msg}")
570
+ endif()
571
+ endif()
572
+
573
+ # ============================================================
574
+ # Build status messages
575
+ # ============================================================
576
+ message(STATUS "Raylib target: ${RAYLIB_TARGET}")
577
+ message(STATUS "yaml-cpp target: ${YAMLCPP_TARGET}")
578
+ message(STATUS "ImGui source dir: ${FX2D_IMGUI_SOURCE_DIR}")
579
+ message(STATUS "rlImGui source dir: ${FX2D_RLIMGUI_SOURCE_DIR}")
580
+ if (FX2D_BUILD_VIEWER)
581
+ message(STATUS "Executable will be: ${CMAKE_BINARY_DIR}/${TARGET_NAME}")
582
+ endif()
fx2d-0.1.0/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Bharath Chandra Irigireddy
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.