jolt-ruby 1.0.0

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 (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitmodules +3 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +228 -0
  5. data/Rakefile +180 -0
  6. data/examples/character_virtual.rb +35 -0
  7. data/examples/falling_sphere.rb +25 -0
  8. data/examples/stagecraft_binding.rb +29 -0
  9. data/ext/jolt_ruby/CMakeLists.txt +42 -0
  10. data/ext/jolt_ruby/character_helper.cpp +44 -0
  11. data/ext/jolt_ruby/constraint_helper.cpp +154 -0
  12. data/ext/jolt_ruby/extconf.rb +56 -0
  13. data/ext/jolt_ruby/helper.cpp +225 -0
  14. data/ext/jolt_ruby/helper.h +103 -0
  15. data/ext/joltc/.github/FUNDING.yml +1 -0
  16. data/ext/joltc/.github/workflows/build.yml +298 -0
  17. data/ext/joltc/.gitignore +272 -0
  18. data/ext/joltc/CMakeLists.txt +431 -0
  19. data/ext/joltc/LICENSE +21 -0
  20. data/ext/joltc/README.md +10 -0
  21. data/ext/joltc/build/cmake_vs2026_arm64.bat +3 -0
  22. data/ext/joltc/build/cmake_vs2026_clang.bat +10 -0
  23. data/ext/joltc/build/cmake_vs2026_x64.bat +3 -0
  24. data/ext/joltc/build/cmake_vs2026_x64_double.bat +3 -0
  25. data/ext/joltc/include/joltc.h +3166 -0
  26. data/ext/joltc/samples/01_HelloWorld/main.cpp +170 -0
  27. data/ext/joltc/samples/CMakeLists.txt +35 -0
  28. data/ext/joltc/src/joltc.c +4 -0
  29. data/ext/joltc/src/joltc.cpp +11812 -0
  30. data/ext/joltc/src/joltc_assert.cpp +271 -0
  31. data/ext/joltc/tests/CMakeLists.txt +77 -0
  32. data/ext/joltc/tests/test_character.cpp +149 -0
  33. data/ext/joltc/tests/test_collision.cpp +146 -0
  34. data/ext/joltc/tests/test_constraints.cpp +353 -0
  35. data/ext/joltc/tests/test_core.cpp +94 -0
  36. data/ext/joltc/tests/test_math.cpp +585 -0
  37. data/ext/joltc/tests/test_physics_system.cpp +465 -0
  38. data/ext/joltc/tests/test_shapes.cpp +789 -0
  39. data/ext/joltc/tests/test_skeleton.cpp +370 -0
  40. data/ext/joltc/tests/test_vehicle.cpp +319 -0
  41. data/generator/generate.rb +237 -0
  42. data/generator/layout_probe.cpp +489 -0
  43. data/generator/verify_layout.rb +32 -0
  44. data/lib/jolt/body.rb +147 -0
  45. data/lib/jolt/body_collection.rb +115 -0
  46. data/lib/jolt/body_dynamics.rb +93 -0
  47. data/lib/jolt/character_virtual.rb +162 -0
  48. data/lib/jolt/constraint.rb +136 -0
  49. data/lib/jolt/constraint_collection.rb +123 -0
  50. data/lib/jolt/contact_events.rb +19 -0
  51. data/lib/jolt/conversions.rb +76 -0
  52. data/lib/jolt/errors.rb +12 -0
  53. data/lib/jolt/fixed_stepper.rb +37 -0
  54. data/lib/jolt/hit.rb +5 -0
  55. data/lib/jolt/layers.rb +182 -0
  56. data/lib/jolt/native/character_functions.rb +16 -0
  57. data/lib/jolt/native/constraint_functions.rb +27 -0
  58. data/lib/jolt/native/core_functions.rb +17 -0
  59. data/lib/jolt/native/generated.rb +1995 -0
  60. data/lib/jolt/native/platform.rb +51 -0
  61. data/lib/jolt/native/query_functions.rb +43 -0
  62. data/lib/jolt/native/types.rb +28 -0
  63. data/lib/jolt/native.rb +94 -0
  64. data/lib/jolt/shape.rb +90 -0
  65. data/lib/jolt/shape_builders.rb +155 -0
  66. data/lib/jolt/system.rb +238 -0
  67. data/lib/jolt/system_characters.rb +25 -0
  68. data/lib/jolt/system_constraints.rb +36 -0
  69. data/lib/jolt/system_contacts.rb +57 -0
  70. data/lib/jolt/system_queries.rb +111 -0
  71. data/lib/jolt/transform.rb +5 -0
  72. data/lib/jolt/version.rb +5 -0
  73. data/lib/jolt.rb +83 -0
  74. data/script/smoke_gem.rb +29 -0
  75. data/script/verify_release.rb +36 -0
  76. metadata +147 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 509d377cf15f0161227805d8d345983bf07dec680a2ed7aecf38c73ace3590f5
4
+ data.tar.gz: d7a89c5121b6cecabcb41b1c936c24c169cac11baf4929c1cda9bd7976ad2bb3
5
+ SHA512:
6
+ metadata.gz: afe48cf43dc719295618c46950959280bc13e61005846b5b28d2dadaef963156da74a4ba29340d078471dc6dc5f29eea7cdfd440e4a8562d87f0ce971917ce33
7
+ data.tar.gz: 76ecbdfb1a0b21decd3f55f627f31e910f716c81d3d3370fb0bc76e033d372d0800539bb840d41f8384c6faf6a2e9f9215498b0db424e5fd67204bf5b966e9a0
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "ext/joltc"]
2
+ path = ext/joltc
3
+ url = https://github.com/amerkoleci/joltc.git
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Yudai Takada
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # jolt-ruby
2
+
3
+ Ruby bindings for [Jolt Physics](https://github.com/jrouwe/JoltPhysics), built on
4
+ the `joltc` C API, `ffi`, and `larb`.
5
+
6
+ > jolt-ruby uses SI units in a right-handed, Y-up coordinate system: metres,
7
+ > kilograms, seconds, and radians. Passing centimetres or degrees without
8
+ > conversion will produce incorrect simulation results.
9
+
10
+ ## Installation
11
+
12
+ ```ruby
13
+ gem "jolt-ruby"
14
+ ```
15
+
16
+ ```sh
17
+ bundle install
18
+ ```
19
+
20
+ Release builds use platform gems for x86-64/aarch64 Linux, arm64/x86-64 macOS,
21
+ and x64-mingw-ucrt. On another platform RubyGems falls back to the source gem,
22
+ which requires CMake 3.16 or newer and a C++17 compiler.
23
+
24
+ The 1.x public API follows semantic versioning. Native declarations mirror the
25
+ vendored `joltc` header; higher-level Ruby APIs are added deliberately rather
26
+ than exposing unsafe ownership or callback behavior.
27
+
28
+ ## First simulation
29
+
30
+ ```ruby
31
+ require "jolt"
32
+
33
+ system = Jolt::System.new(gravity: [0, -9.81, 0])
34
+ floor = system.bodies.create(
35
+ shape: Jolt::Shape.box([50, 0.5, 50]),
36
+ position: [0, -0.5, 0],
37
+ motion: :static,
38
+ layer: :non_moving
39
+ )
40
+ ball = system.bodies.create(
41
+ shape: Jolt::Shape.sphere(0.5),
42
+ position: [0, 5, 0],
43
+ mass: 2,
44
+ restitution: 0.6,
45
+ ccd: true
46
+ )
47
+
48
+ system.update(1.0 / 60)
49
+ puts ball.position
50
+
51
+ system.destroy
52
+ Jolt.shutdown
53
+ ```
54
+
55
+ `Jolt.init` is called automatically. A `System` owns its bodies, constraints,
56
+ characters, and retained shapes. Destroying the system invalidates their Ruby
57
+ handles; subsequent access raises `Jolt::UseAfterDestroyError`. Explicit
58
+ `destroy`/`release` is supported, and shutdown is also registered with
59
+ `at_exit`.
60
+
61
+ ## Layers
62
+
63
+ `Layers.default` provides `:non_moving` and `:moving`. Custom collision matrices
64
+ remain entirely native during simulation:
65
+
66
+ ```ruby
67
+ layers = Jolt::Layers.define do |layer|
68
+ layer.broad_phase :static_bp, :dynamic_bp
69
+ layer.object :ground, broad_phase: :static_bp
70
+ layer.object :player, :enemy, broad_phase: :dynamic_bp
71
+ layer.collide :player, with: %i[ground enemy]
72
+ layer.collide :enemy, with: %i[ground player enemy]
73
+ end
74
+
75
+ system = Jolt::System.new(layers:)
76
+ ```
77
+
78
+ ## Shapes and bodies
79
+
80
+ Primitive shapes:
81
+
82
+ ```ruby
83
+ Jolt::Shape.box([1, 0.5, 1])
84
+ Jolt::Shape.sphere(0.5)
85
+ Jolt::Shape.capsule(half_height: 0.8, radius: 0.3)
86
+ Jolt::Shape.cylinder(half_height: 1, radius: 0.5)
87
+ ```
88
+
89
+ The advanced builders are `convex_hull`, `mesh`, `heightfield`, `compound`,
90
+ `scaled`, and `offset`. Hull vertices and mesh vertices accept nested arrays or
91
+ packed little-endian f32 strings; mesh indices also accept packed u32 strings.
92
+ Mesh and heightfield shapes are static-only.
93
+
94
+ Bodies support `:static`, `:kinematic`, and `:dynamic` motion. Common operations
95
+ include:
96
+
97
+ ```ruby
98
+ body.linear_velocity = [1, 0, 0]
99
+ body.angular_velocity = [0, 1, 0]
100
+ body.apply_impulse([0, 10, 0])
101
+ body.add_force([5, 0, 0], point: [0, 1, 0])
102
+ body.friction = 0.4
103
+ body.sensor = true
104
+ body.kinematic_move_to([2, 0, 0], [0, 0, 0, 1], 1.0 / 60)
105
+ ```
106
+
107
+ Arbitrary Ruby objects can be stored in `body.user_data`. They never cross the
108
+ native boundary.
109
+
110
+ ## Contacts and queries
111
+
112
+ Jolt invokes contact listeners from worker threads. jolt-ruby records those
113
+ calls into a bounded native lock-free queue, then creates immutable Ruby
114
+ snapshots after `System#update`:
115
+
116
+ ```ruby
117
+ system.update(1.0 / 60)
118
+ system.contact_events.added.each do |contact|
119
+ puts [contact.body_a, contact.body_b, contact.point, contact.normal].inspect
120
+ end
121
+
122
+ warn "contact queue overflow" if system.contact_events.dropped_count.positive?
123
+ ```
124
+
125
+ Synchronous queries return registered `Jolt::Body` handles:
126
+
127
+ ```ruby
128
+ hit = system.raycast(
129
+ origin: [0, 10, 0],
130
+ direction: [0, -20, 0],
131
+ layer_mask: %i[non_moving moving]
132
+ )
133
+
134
+ nearby = system.overlap_sphere([0, 0, 0], 2, layer_mask: :moving)
135
+ system.overlap_point([0, 0, 0]) { |body| puts body.user_data }
136
+ ```
137
+
138
+ Overlap queries use Jolt's broad phase and therefore report bodies whose bounds
139
+ overlap the query.
140
+
141
+ ## Constraints
142
+
143
+ The collection supports fixed, point, distance, hinge, and slider constraints:
144
+
145
+ ```ruby
146
+ hinge = system.constraints.hinge(
147
+ body_a: door,
148
+ body_b: frame,
149
+ anchor: [0, 1, 0],
150
+ axis: [0, 1, 0],
151
+ limits: (-Math::PI / 2)..(Math::PI / 2)
152
+ )
153
+ hinge.motor_speed = 1.5
154
+ hinge.disable_motor
155
+ hinge.destroy
156
+ ```
157
+
158
+ Destroying either body destroys its attached constraints first.
159
+
160
+ ## Virtual characters
161
+
162
+ `CharacterVirtual#update` uses Jolt's extended update with stair walking and
163
+ stick-to-floor enabled:
164
+
165
+ ```ruby
166
+ character = Jolt::CharacterVirtual.new(
167
+ system,
168
+ shape: Jolt::Shape.capsule(half_height: 0.8, radius: 0.3),
169
+ position: [0, 2, 0],
170
+ max_slope: Math::PI / 4,
171
+ mass: 70
172
+ )
173
+
174
+ character.velocity = [3, character.velocity.y - 9.81 / 60, 0]
175
+ character.update(1.0 / 60)
176
+ puts character.ground_state # :on_ground, :sliding, or :in_air
177
+ ```
178
+
179
+ ## Fixed stepping and rendering
180
+
181
+ `System#update` releases Ruby's GVL while Jolt runs. Use `FixedStepper` to keep
182
+ simulation stable and interpolate rendering transforms:
183
+
184
+ ```ruby
185
+ stepper = Jolt::FixedStepper.new(hz: 60, max_substeps: 5)
186
+
187
+ alpha = stepper.advance(frame_delta) { |step| system.update(step) }
188
+ render_transform = ball.interpolated(alpha)
189
+ ```
190
+
191
+ Each system permits one native operation at a time. Do not read, mutate, or
192
+ destroy a system or one of its handles from another thread while
193
+ `System#update` or `CharacterVirtual#update` is running. Such access raises
194
+ `Jolt::ConcurrentAccessError`.
195
+
196
+ The returned transform exposes `position` and `rotation` as `Larb::Vec3` and
197
+ `Larb::Quat`, matching the stagecraft binding contract. See
198
+ [`examples/stagecraft_binding.rb`](examples/stagecraft_binding.rb).
199
+
200
+ ## Development
201
+
202
+ Initialize the submodule and run the complete build:
203
+
204
+ ```sh
205
+ git submodule update --init
206
+ bundle install
207
+ bundle exec rake
208
+ ```
209
+
210
+ Useful tasks:
211
+
212
+ ```sh
213
+ bundle exec rake native:compile # build joltc and the helper library
214
+ bundle exec rake generator:generate # regenerate all joltc FFI declarations
215
+ bundle exec rake native:verify_layout # compare Clang, C++, and FFI layouts
216
+ bundle exec rake native:gem # build a precompiled platform gem
217
+ bundle exec rake native:smoke_gem # install and run the platform gem
218
+ bundle exec rake package:smoke_source # compile, install, and run the source gem
219
+ JOLT_STRESS=1 bundle exec rspec spec/stress
220
+ ```
221
+
222
+ The generator reads `joltc.h` through `ffi-clang`. CI rejects stale output and
223
+ compares all generated struct sizes and field offsets against a C++ probe.
224
+
225
+ ## License
226
+
227
+ The gem is available under the [MIT License](LICENSE.txt). Jolt Physics and
228
+ `joltc` retain their respective upstream licenses.
data/Rakefile ADDED
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "fileutils"
5
+ require "open3"
6
+ require "rspec/core/rake_task"
7
+ require "rbconfig"
8
+ require "shellwords"
9
+ require_relative "lib/jolt/native/platform"
10
+ require_relative "lib/jolt/version"
11
+
12
+ RSpec::Core::RakeTask.new(:spec)
13
+
14
+ module NativeBuild
15
+ extend Rake::FileUtilsExt
16
+ module_function
17
+
18
+ ROOT = __dir__
19
+ PLATFORM = Jolt::Native::Platform.tag
20
+ BUILD_DIR = File.join(ROOT, "tmp", "native", PLATFORM)
21
+
22
+ def configure
23
+ arch = RbConfig::CONFIG.fetch("host_cpu")
24
+ command = [
25
+ "cmake",
26
+ "-S", File.join(ROOT, "ext", "jolt_ruby"),
27
+ "-B", BUILD_DIR,
28
+ "-DCMAKE_BUILD_TYPE=Release"
29
+ ]
30
+ command << "-DCMAKE_OSX_ARCHITECTURES=#{arch}" if RbConfig::CONFIG.fetch("host_os").include?("darwin")
31
+ sh(*command)
32
+ end
33
+
34
+ def build
35
+ sh("cmake", "--build", BUILD_DIR, "--config", "Release", "--parallel")
36
+ end
37
+
38
+ def library_names
39
+ case RbConfig::CONFIG.fetch("host_os")
40
+ when /darwin/
41
+ %w[libjoltc.dylib libjolt_ruby_helper.dylib]
42
+ when /mswin|mingw/
43
+ %w[joltc.dll jolt_ruby_helper.dll]
44
+ else
45
+ %w[libjoltc.so libjolt_ruby_helper.so]
46
+ end
47
+ end
48
+
49
+ def library_directory
50
+ [
51
+ File.join(BUILD_DIR, "lib"),
52
+ File.join(BUILD_DIR, "lib", "Release"),
53
+ File.join(BUILD_DIR, "Release")
54
+ ].find { |directory| library_names.all? { |name| File.file?(File.join(directory, name)) } } ||
55
+ raise("native libraries were not found in #{BUILD_DIR}")
56
+ end
57
+
58
+ def stage
59
+ destination = File.join(ROOT, "lib", "jolt", "native", PLATFORM)
60
+ FileUtils.rm_rf(destination)
61
+ FileUtils.mkdir_p(destination)
62
+ library_names.each do |name|
63
+ FileUtils.cp(File.join(library_directory, name), destination)
64
+ end
65
+ end
66
+
67
+ def verify_layout
68
+ directory = File.join(ROOT, "tmp", "layout")
69
+ FileUtils.mkdir_p(directory)
70
+ executable = File.join(directory, "layout_probe#{RbConfig::CONFIG.fetch("EXEEXT")}")
71
+ compiler = Shellwords.split(ENV.fetch("CXX", RbConfig::CONFIG.fetch("CXX")))
72
+ sh(
73
+ *compiler,
74
+ "-std=c++17",
75
+ "-I#{File.join(ROOT, "ext", "joltc", "include")}",
76
+ File.join(ROOT, "generator", "layout_probe.cpp"),
77
+ "-o", executable
78
+ )
79
+ output, status = Open3.capture2e(executable)
80
+ raise "layout probe failed:\n#{output}" unless status.success?
81
+
82
+ output_path = File.join(directory, "layouts.tsv")
83
+ File.binwrite(output_path, output)
84
+ sh(
85
+ RbConfig.ruby,
86
+ File.join(ROOT, "generator", "verify_layout.rb"),
87
+ output_path
88
+ )
89
+ end
90
+ end
91
+
92
+ module PackageSmoke
93
+ extend Rake::FileUtilsExt
94
+ module_function
95
+
96
+ ROOT = __dir__
97
+
98
+ def run(gem_path, label)
99
+ raise "gem was not built: #{gem_path}" unless File.file?(gem_path)
100
+
101
+ install_dir = File.join(ROOT, "tmp", "gem-smoke", label)
102
+ FileUtils.rm_rf(install_dir)
103
+ FileUtils.mkdir_p(install_dir)
104
+ sh(
105
+ "gem", "install", "--local", "--ignore-dependencies", "--no-document",
106
+ "--install-dir", install_dir, gem_path
107
+ )
108
+
109
+ gem_path_entries = [install_dir, *Gem.path].uniq.join(File::PATH_SEPARATOR)
110
+ environment = {
111
+ "GEM_HOME" => install_dir,
112
+ "GEM_PATH" => gem_path_entries,
113
+ "JOLT_RUBY_SMOKE_HOME" => install_dir
114
+ }
115
+ Bundler.with_unbundled_env do
116
+ sh(environment, RbConfig.ruby, File.join(ROOT, "script", "smoke_gem.rb"))
117
+ end
118
+ end
119
+ end
120
+
121
+ namespace :generator do
122
+ desc "Regenerate FFI declarations and the native layout probe"
123
+ task :generate do
124
+ sh(RbConfig.ruby, File.join(__dir__, "generator", "generate.rb"))
125
+ end
126
+
127
+ desc "Fail if generated FFI declarations are stale"
128
+ task :check do
129
+ sh(RbConfig.ruby, File.join(__dir__, "generator", "generate.rb"), "--check")
130
+ end
131
+ end
132
+
133
+ namespace :native do
134
+ desc "Configure the native joltc build"
135
+ task :configure do
136
+ NativeBuild.configure
137
+ end
138
+
139
+ desc "Build joltc and the jolt-ruby support library"
140
+ task compile: :configure do
141
+ NativeBuild.build
142
+ end
143
+
144
+ desc "Verify every generated FFI struct against the C compiler"
145
+ task verify_layout: "generator:check" do
146
+ NativeBuild.verify_layout
147
+ end
148
+
149
+ desc "Build a platform gem containing the native libraries"
150
+ task gem: :compile do
151
+ NativeBuild.stage
152
+ sh({ "JOLT_RUBY_PLATFORM_GEM" => "1" }, "gem", "build", "jolt.gemspec")
153
+ end
154
+
155
+ desc "Build, install, and execute the current platform gem"
156
+ task smoke_gem: :gem do
157
+ platform = Gem::Platform.new(NativeBuild::PLATFORM)
158
+ gem_path = File.join(__dir__, "jolt-ruby-#{Jolt::VERSION}-#{platform}.gem")
159
+ PackageSmoke.run(gem_path, "platform")
160
+ end
161
+ end
162
+
163
+ namespace :package do
164
+ desc "Build the source gem"
165
+ task :source_gem do
166
+ sh("gem", "build", "jolt.gemspec")
167
+ end
168
+
169
+ desc "Build, install, compile, and execute the source gem fallback"
170
+ task smoke_source: :source_gem do
171
+ gem_path = File.join(__dir__, "jolt-ruby-#{Jolt::VERSION}.gem")
172
+ PackageSmoke.run(gem_path, "source")
173
+ end
174
+ end
175
+
176
+ desc "Build the native libraries"
177
+ task compile: "native:compile"
178
+
179
+ task spec: [:compile, "native:verify_layout"]
180
+ task default: :spec
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jolt"
4
+
5
+ begin
6
+ system = Jolt::System.new
7
+ system.bodies.create(
8
+ shape: Jolt::Shape.box([10, 0.5, 10]),
9
+ position: [0, -0.5, 0],
10
+ motion: :static
11
+ )
12
+ character = Jolt::CharacterVirtual.new(
13
+ system,
14
+ shape: Jolt::Shape.capsule(half_height: 0.8, radius: 0.3),
15
+ position: [0, 2, 0]
16
+ )
17
+
18
+ 180.times do |frame|
19
+ velocity = character.velocity
20
+ character.velocity = [1, velocity.y - 9.81 / 60, 0]
21
+ system.update(1.0 / 60)
22
+ character.update(1.0 / 60)
23
+ if (frame % 15).zero?
24
+ puts format(
25
+ "%3d position=%s ground=%s",
26
+ frame,
27
+ character.position.to_a.map { |value| value.round(3) },
28
+ character.ground_state
29
+ )
30
+ end
31
+ end
32
+ ensure
33
+ system&.destroy
34
+ Jolt.shutdown
35
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jolt"
4
+
5
+ begin
6
+ system = Jolt::System.new
7
+ system.bodies.create(
8
+ shape: Jolt::Shape.box([10, 0.5, 10]),
9
+ position: [0, -0.5, 0],
10
+ motion: :static
11
+ )
12
+ ball = system.bodies.create(
13
+ shape: Jolt::Shape.sphere(0.5),
14
+ position: [0, 5, 0],
15
+ restitution: 0.6
16
+ )
17
+
18
+ 120.times do |frame|
19
+ system.update(1.0 / 60)
20
+ puts format("%3d y=% .3f", frame, ball.position.y) if (frame % 10).zero?
21
+ end
22
+ ensure
23
+ system&.destroy
24
+ Jolt.shutdown
25
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jolt"
4
+ require "stagecraft"
5
+
6
+ def run_stagecraft_physics(app:, mesh_node:)
7
+ begin
8
+ system = Jolt::System.new
9
+ ball = system.bodies.create(
10
+ shape: Jolt::Shape.sphere(0.5),
11
+ position: [0, 5, 0]
12
+ )
13
+
14
+ binding = Stagecraft::PhysicsBinding.new
15
+ binding.bind(mesh_node, ball)
16
+ stepper = Jolt::FixedStepper.new(hz: 60)
17
+
18
+ app.run do |delta_time|
19
+ alpha = stepper.advance(delta_time) { |step| system.update(step) }
20
+ binding.sync!(alpha)
21
+ end
22
+ ensure
23
+ system&.destroy
24
+ Jolt.shutdown
25
+ end
26
+ end
27
+
28
+ # Call from a stagecraft scene with:
29
+ # run_stagecraft_physics(app:, mesh_node:)
@@ -0,0 +1,42 @@
1
+ cmake_minimum_required(VERSION 3.16)
2
+
3
+ project(jolt_ruby_native LANGUAGES C CXX)
4
+
5
+ set(CMAKE_CXX_STANDARD 17)
6
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
7
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
8
+ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
9
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
10
+
11
+ set(JPH_MASTER_PROJECT OFF CACHE BOOL "" FORCE)
12
+ set(JPH_BUILD_SHARED ON CACHE BOOL "" FORCE)
13
+ set(JPH_INSTALL OFF CACHE BOOL "" FORCE)
14
+ set(JPH_SAMPLES OFF CACHE BOOL "" FORCE)
15
+ set(JPH_TESTS OFF CACHE BOOL "" FORCE)
16
+ set(DEBUG_RENDERER_IN_DEBUG_AND_RELEASE ON CACHE BOOL "" FORCE)
17
+ set(DEBUG_RENDERER_IN_DISTRIBUTION OFF CACHE BOOL "" FORCE)
18
+ set(CROSS_PLATFORM_DETERMINISTIC ON CACHE BOOL "" FORCE)
19
+ set(PROFILER_IN_DEBUG_AND_RELEASE OFF CACHE BOOL "" FORCE)
20
+ set(PROFILER_IN_DISTRIBUTION OFF CACHE BOOL "" FORCE)
21
+ set(ENABLE_OBJECT_STREAM OFF CACHE BOOL "" FORCE)
22
+ set(JPH_USE_DX12 OFF CACHE BOOL "" FORCE)
23
+ set(JPH_USE_VK OFF CACHE BOOL "" FORCE)
24
+ set(JPH_USE_MTL OFF CACHE BOOL "" FORCE)
25
+
26
+ set(JOLTC_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../joltc" CACHE PATH "Path to joltc")
27
+ if(NOT EXISTS "${JOLTC_ROOT}/CMakeLists.txt")
28
+ message(FATAL_ERROR "joltc submodule is missing; run git submodule update --init")
29
+ endif()
30
+
31
+ add_subdirectory("${JOLTC_ROOT}" "${CMAKE_BINARY_DIR}/joltc")
32
+
33
+ add_library(jolt_ruby_helper SHARED helper.cpp constraint_helper.cpp character_helper.cpp)
34
+ target_include_directories(jolt_ruby_helper PRIVATE "${JOLTC_ROOT}/include")
35
+ target_compile_definitions(jolt_ruby_helper PRIVATE JPH_SHARED_LIBRARY_INCLUDE=1)
36
+ target_link_libraries(jolt_ruby_helper PRIVATE joltc)
37
+
38
+ if(APPLE)
39
+ set_target_properties(jolt_ruby_helper PROPERTIES INSTALL_RPATH "@loader_path")
40
+ elseif(UNIX)
41
+ set_target_properties(jolt_ruby_helper PROPERTIES INSTALL_RPATH "$ORIGIN")
42
+ endif()
@@ -0,0 +1,44 @@
1
+ #include "helper.h"
2
+
3
+ #include <joltc.h>
4
+
5
+ #include <cmath>
6
+
7
+ JPH_CharacterVirtual* JR_CharacterVirtual_Create(
8
+ JPH_PhysicsSystem* system,
9
+ const JPH_Shape* shape,
10
+ const float* position,
11
+ const float* rotation,
12
+ float max_slope_angle,
13
+ float mass) {
14
+ if (!system || !shape || !position || !rotation) return nullptr;
15
+
16
+ JPH_CharacterVirtualSettings settings{};
17
+ JPH_CharacterVirtualSettings_Init(&settings);
18
+ settings.base.shape = shape;
19
+ settings.base.maxSlopeAngle = max_slope_angle;
20
+ settings.mass = mass;
21
+
22
+ const JPH_RVec3 native_position{position[0], position[1], position[2]};
23
+ const JPH_Quat native_rotation{
24
+ rotation[0], rotation[1], rotation[2], rotation[3]};
25
+ return JPH_CharacterVirtual_Create(
26
+ &settings, &native_position, &native_rotation, 0, system);
27
+ }
28
+
29
+ void JR_CharacterVirtual_ExtendedUpdate(
30
+ JPH_CharacterVirtual* character,
31
+ float delta_time,
32
+ uint32_t layer,
33
+ JPH_PhysicsSystem* system) {
34
+ JPH_ExtendedUpdateSettings settings{};
35
+ settings.stickToFloorStepDown = JPH_Vec3{0.0f, -0.5f, 0.0f};
36
+ settings.walkStairsStepUp = JPH_Vec3{0.0f, 0.4f, 0.0f};
37
+ settings.walkStairsMinStepForward = 0.02f;
38
+ settings.walkStairsStepForwardTest = 0.15f;
39
+ settings.walkStairsCosAngleForwardContact =
40
+ std::cos(75.0f * JPH_M_PI / 180.0f);
41
+ settings.walkStairsStepDownExtra = JPH_Vec3{0.0f, 0.0f, 0.0f};
42
+ JPH_CharacterVirtual_ExtendedUpdate(
43
+ character, delta_time, &settings, layer, system, nullptr, nullptr);
44
+ }