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.
- checksums.yaml +7 -0
- data/.gitmodules +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +228 -0
- data/Rakefile +180 -0
- data/examples/character_virtual.rb +35 -0
- data/examples/falling_sphere.rb +25 -0
- data/examples/stagecraft_binding.rb +29 -0
- data/ext/jolt_ruby/CMakeLists.txt +42 -0
- data/ext/jolt_ruby/character_helper.cpp +44 -0
- data/ext/jolt_ruby/constraint_helper.cpp +154 -0
- data/ext/jolt_ruby/extconf.rb +56 -0
- data/ext/jolt_ruby/helper.cpp +225 -0
- data/ext/jolt_ruby/helper.h +103 -0
- data/ext/joltc/.github/FUNDING.yml +1 -0
- data/ext/joltc/.github/workflows/build.yml +298 -0
- data/ext/joltc/.gitignore +272 -0
- data/ext/joltc/CMakeLists.txt +431 -0
- data/ext/joltc/LICENSE +21 -0
- data/ext/joltc/README.md +10 -0
- data/ext/joltc/build/cmake_vs2026_arm64.bat +3 -0
- data/ext/joltc/build/cmake_vs2026_clang.bat +10 -0
- data/ext/joltc/build/cmake_vs2026_x64.bat +3 -0
- data/ext/joltc/build/cmake_vs2026_x64_double.bat +3 -0
- data/ext/joltc/include/joltc.h +3166 -0
- data/ext/joltc/samples/01_HelloWorld/main.cpp +170 -0
- data/ext/joltc/samples/CMakeLists.txt +35 -0
- data/ext/joltc/src/joltc.c +4 -0
- data/ext/joltc/src/joltc.cpp +11812 -0
- data/ext/joltc/src/joltc_assert.cpp +271 -0
- data/ext/joltc/tests/CMakeLists.txt +77 -0
- data/ext/joltc/tests/test_character.cpp +149 -0
- data/ext/joltc/tests/test_collision.cpp +146 -0
- data/ext/joltc/tests/test_constraints.cpp +353 -0
- data/ext/joltc/tests/test_core.cpp +94 -0
- data/ext/joltc/tests/test_math.cpp +585 -0
- data/ext/joltc/tests/test_physics_system.cpp +465 -0
- data/ext/joltc/tests/test_shapes.cpp +789 -0
- data/ext/joltc/tests/test_skeleton.cpp +370 -0
- data/ext/joltc/tests/test_vehicle.cpp +319 -0
- data/generator/generate.rb +237 -0
- data/generator/layout_probe.cpp +489 -0
- data/generator/verify_layout.rb +32 -0
- data/lib/jolt/body.rb +147 -0
- data/lib/jolt/body_collection.rb +115 -0
- data/lib/jolt/body_dynamics.rb +93 -0
- data/lib/jolt/character_virtual.rb +162 -0
- data/lib/jolt/constraint.rb +136 -0
- data/lib/jolt/constraint_collection.rb +123 -0
- data/lib/jolt/contact_events.rb +19 -0
- data/lib/jolt/conversions.rb +76 -0
- data/lib/jolt/errors.rb +12 -0
- data/lib/jolt/fixed_stepper.rb +37 -0
- data/lib/jolt/hit.rb +5 -0
- data/lib/jolt/layers.rb +182 -0
- data/lib/jolt/native/character_functions.rb +16 -0
- data/lib/jolt/native/constraint_functions.rb +27 -0
- data/lib/jolt/native/core_functions.rb +17 -0
- data/lib/jolt/native/generated.rb +1995 -0
- data/lib/jolt/native/platform.rb +51 -0
- data/lib/jolt/native/query_functions.rb +43 -0
- data/lib/jolt/native/types.rb +28 -0
- data/lib/jolt/native.rb +94 -0
- data/lib/jolt/shape.rb +90 -0
- data/lib/jolt/shape_builders.rb +155 -0
- data/lib/jolt/system.rb +238 -0
- data/lib/jolt/system_characters.rb +25 -0
- data/lib/jolt/system_constraints.rb +36 -0
- data/lib/jolt/system_contacts.rb +57 -0
- data/lib/jolt/system_queries.rb +111 -0
- data/lib/jolt/transform.rb +5 -0
- data/lib/jolt/version.rb +5 -0
- data/lib/jolt.rb +83 -0
- data/script/smoke_gem.rb +29 -0
- data/script/verify_release.rb +36 -0
- 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
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
|
+
}
|