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
data/lib/jolt/body.rb
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jolt
|
|
4
|
+
class Body
|
|
5
|
+
include BodyDynamics
|
|
6
|
+
|
|
7
|
+
attr_reader :id, :system
|
|
8
|
+
|
|
9
|
+
def initialize(system, id)
|
|
10
|
+
@system = system
|
|
11
|
+
@id = id
|
|
12
|
+
@destroyed = false
|
|
13
|
+
transform = current_transform
|
|
14
|
+
@previous_transform = transform
|
|
15
|
+
@current_transform = transform
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def position
|
|
19
|
+
read_vec3(:JPH_BodyInterface_GetPosition)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def position=(value)
|
|
23
|
+
write_vec3(:JPH_BodyInterface_SetPosition, value, activation)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def rotation
|
|
27
|
+
check_alive!
|
|
28
|
+
native = Native::Quat.new
|
|
29
|
+
Native.JPH_BodyInterface_GetRotation(body_interface, @id, native.pointer)
|
|
30
|
+
Conversions.quat(native)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def rotation=(value)
|
|
34
|
+
check_alive!
|
|
35
|
+
native = Conversions.native_quat(value)
|
|
36
|
+
Native.JPH_BodyInterface_SetRotation(body_interface, @id, native.pointer, activation)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def user_data
|
|
40
|
+
check_alive!
|
|
41
|
+
@system.__user_data(@id)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def user_data=(value)
|
|
45
|
+
check_alive!
|
|
46
|
+
@system.__set_user_data(@id, value)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def interpolated(alpha)
|
|
50
|
+
check_alive!
|
|
51
|
+
alpha = Conversions.finite_float(alpha, "alpha")
|
|
52
|
+
raise InvalidArgumentError, "alpha must be between 0 and 1" unless alpha.between?(0.0, 1.0)
|
|
53
|
+
|
|
54
|
+
Transform.new(
|
|
55
|
+
position: @previous_transform.position.lerp(@current_transform.position, alpha),
|
|
56
|
+
rotation: @previous_transform.rotation.slerp(@current_transform.rotation, alpha)
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def destroy
|
|
61
|
+
@system.__destroy_body(self)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def destroyed?
|
|
65
|
+
@destroyed
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def hash
|
|
69
|
+
[@system.object_id, @id].hash
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def eql?(other)
|
|
73
|
+
other.is_a?(Body) && other.system.equal?(@system) && other.id == @id
|
|
74
|
+
end
|
|
75
|
+
alias == eql?
|
|
76
|
+
|
|
77
|
+
def __capture_before_step
|
|
78
|
+
check_alive!
|
|
79
|
+
@previous_transform = @current_transform
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def __capture_after_step
|
|
83
|
+
check_alive!
|
|
84
|
+
@current_transform = current_transform
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def __mark_destroyed
|
|
88
|
+
@destroyed = true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def current_transform
|
|
94
|
+
Transform.new(position: position, rotation: rotation)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def read_vec3(function)
|
|
98
|
+
check_alive!
|
|
99
|
+
native = Native::Vec3.new
|
|
100
|
+
Native.public_send(function, body_interface, @id, native.pointer)
|
|
101
|
+
Conversions.vec3(native)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def write_vec3(function, value, *extra)
|
|
105
|
+
check_alive!
|
|
106
|
+
native = Conversions.native_vec3(value)
|
|
107
|
+
Native.public_send(function, body_interface, @id, native.pointer, *extra)
|
|
108
|
+
value
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def apply_vector_at_point(center_function, point_function, value, point)
|
|
112
|
+
check_alive!
|
|
113
|
+
native_value = Conversions.native_vec3(value)
|
|
114
|
+
if point
|
|
115
|
+
native_point = Conversions.native_vec3(point, name: "point")
|
|
116
|
+
Native.public_send(point_function, body_interface, @id, native_value.pointer, native_point.pointer)
|
|
117
|
+
else
|
|
118
|
+
Native.public_send(center_function, body_interface, @id, native_value.pointer)
|
|
119
|
+
end
|
|
120
|
+
self
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def scalar_property(function)
|
|
124
|
+
check_alive!
|
|
125
|
+
Native.public_send(function, body_interface, @id)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def write_scalar(function, value, name)
|
|
129
|
+
check_alive!
|
|
130
|
+
number = Conversions.non_negative_float(value, name)
|
|
131
|
+
Native.public_send(function, body_interface, @id, number)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def activation
|
|
135
|
+
0
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def body_interface
|
|
139
|
+
@system.__body_interface
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def check_alive!
|
|
143
|
+
raise UseAfterDestroyError, "body #{@id} has been destroyed" if @destroyed
|
|
144
|
+
@system.__check_alive!
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jolt
|
|
4
|
+
class BodyCollection
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
MOTION_TYPES = {static: 0, kinematic: 1, dynamic: 2}.freeze
|
|
8
|
+
INVALID_BODY_ID = 0xffff_ffff
|
|
9
|
+
|
|
10
|
+
def initialize(system)
|
|
11
|
+
@system = system
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create(shape:, position: [0, 0, 0], rotation: [0, 0, 0, 1],
|
|
15
|
+
motion: :dynamic, layer: nil, activate: nil, friction: 0.2,
|
|
16
|
+
restitution: 0.0, linear_damping: 0.05, angular_damping: 0.05,
|
|
17
|
+
gravity_factor: 1.0, mass: nil, ccd: false, sensor: false,
|
|
18
|
+
user_data: nil)
|
|
19
|
+
@system.__check_alive!
|
|
20
|
+
validate_shape!(shape)
|
|
21
|
+
motion_type = MOTION_TYPES.fetch(motion.respond_to?(:to_sym) ? motion.to_sym : nil)
|
|
22
|
+
if !motion_type.zero? && shape.must_be_static?
|
|
23
|
+
raise InvalidArgumentError, "#{shape.kind} shapes can only be used by static bodies"
|
|
24
|
+
end
|
|
25
|
+
layer ||= motion_type.zero? ? :non_moving : :moving
|
|
26
|
+
layer_id = @system.layers.object_layer_id(layer)
|
|
27
|
+
settings = create_settings(shape, position, rotation, motion_type, layer_id)
|
|
28
|
+
configure_settings(
|
|
29
|
+
settings,
|
|
30
|
+
friction:, restitution:, linear_damping:, angular_damping:,
|
|
31
|
+
gravity_factor:, mass:, ccd:, sensor:, motion_type:
|
|
32
|
+
)
|
|
33
|
+
id = Native.JPH_BodyInterface_CreateAndAddBody(
|
|
34
|
+
@system.__body_interface, settings, activation(activate, motion_type)
|
|
35
|
+
)
|
|
36
|
+
raise InitializationError, "failed to create body" if id == INVALID_BODY_ID
|
|
37
|
+
|
|
38
|
+
Body.new(@system, id).tap do |body|
|
|
39
|
+
@system.__register_body(body, shape)
|
|
40
|
+
body.user_data = user_data unless user_data.nil?
|
|
41
|
+
end
|
|
42
|
+
rescue KeyError
|
|
43
|
+
raise InvalidArgumentError, "motion must be one of: #{MOTION_TYPES.keys.join(", ")}"
|
|
44
|
+
ensure
|
|
45
|
+
Native.JPH_BodyCreationSettings_Destroy(settings) if settings && !settings.null?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def each(&block)
|
|
49
|
+
return enum_for(:each) unless block
|
|
50
|
+
|
|
51
|
+
@system.__bodies_snapshot.each(&block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def [](id)
|
|
55
|
+
@system.__body(id)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def size
|
|
59
|
+
@system.__bodies_snapshot.size
|
|
60
|
+
end
|
|
61
|
+
alias length size
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def create_settings(shape, position, rotation, motion_type, layer_id)
|
|
66
|
+
position = Conversions.native_vec3(position, name: "position")
|
|
67
|
+
rotation = Conversions.native_quat(rotation)
|
|
68
|
+
settings = Native.JPH_BodyCreationSettings_Create3(
|
|
69
|
+
shape.native_pointer, position.pointer, rotation.pointer, motion_type, layer_id
|
|
70
|
+
)
|
|
71
|
+
raise InitializationError, "failed to allocate body creation settings" if settings.null?
|
|
72
|
+
|
|
73
|
+
settings
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def configure_settings(settings, friction:, restitution:, linear_damping:, angular_damping:,
|
|
77
|
+
gravity_factor:, mass:, ccd:, sensor:, motion_type:)
|
|
78
|
+
{
|
|
79
|
+
Friction: [friction, "friction"],
|
|
80
|
+
Restitution: [restitution, "restitution"],
|
|
81
|
+
LinearDamping: [linear_damping, "linear_damping"],
|
|
82
|
+
AngularDamping: [angular_damping, "angular_damping"],
|
|
83
|
+
GravityFactor: [gravity_factor, "gravity_factor"]
|
|
84
|
+
}.each do |name, (value, label)|
|
|
85
|
+
number = Conversions.non_negative_float(value, label)
|
|
86
|
+
Native.public_send(:"JPH_BodyCreationSettings_Set#{name}", settings, number)
|
|
87
|
+
end
|
|
88
|
+
Native.JPH_BodyCreationSettings_SetIsSensor(settings, !!sensor)
|
|
89
|
+
Native.JPH_BodyCreationSettings_SetMotionQuality(settings, ccd ? 1 : 0)
|
|
90
|
+
configure_mass(settings, mass, motion_type) unless mass.nil?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def configure_mass(settings, mass, motion_type)
|
|
94
|
+
raise InvalidArgumentError, "mass is only valid for dynamic bodies" unless motion_type == MOTION_TYPES[:dynamic]
|
|
95
|
+
|
|
96
|
+
mass = Conversions.positive_float(mass, "mass")
|
|
97
|
+
properties = Native::MassProperties.new
|
|
98
|
+
Native.JPH_BodyCreationSettings_GetMassPropertiesOverride(settings, properties.pointer)
|
|
99
|
+
properties[:mass] = mass
|
|
100
|
+
Native.JPH_BodyCreationSettings_SetMassPropertiesOverride(settings, properties.pointer)
|
|
101
|
+
Native.JPH_BodyCreationSettings_SetOverrideMassProperties(settings, 1)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def activation(value, motion_type)
|
|
105
|
+
activate = value.nil? ? motion_type == MOTION_TYPES[:dynamic] : !!value
|
|
106
|
+
activate ? 0 : 1
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def validate_shape!(shape)
|
|
110
|
+
raise InvalidArgumentError, "shape must be a Jolt::Shape" unless shape.is_a?(Shape)
|
|
111
|
+
|
|
112
|
+
shape.native_pointer
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jolt
|
|
4
|
+
module BodyDynamics
|
|
5
|
+
def linear_velocity
|
|
6
|
+
read_vec3(:JPH_BodyInterface_GetLinearVelocity)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def linear_velocity=(value)
|
|
10
|
+
write_vec3(:JPH_BodyInterface_SetLinearVelocity, value)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def angular_velocity
|
|
14
|
+
read_vec3(:JPH_BodyInterface_GetAngularVelocity)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def angular_velocity=(value)
|
|
18
|
+
write_vec3(:JPH_BodyInterface_SetAngularVelocity, value)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def apply_impulse(impulse, point: nil)
|
|
22
|
+
apply_vector_at_point(:JPH_BodyInterface_AddImpulse, :JPH_BodyInterface_AddImpulse2, impulse, point)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def apply_angular_impulse(impulse)
|
|
26
|
+
write_vec3(:JPH_BodyInterface_AddAngularImpulse, impulse)
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add_force(force, point: nil)
|
|
31
|
+
apply_vector_at_point(:JPH_BodyInterface_AddForce, :JPH_BodyInterface_AddForce2, force, point)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def add_torque(torque)
|
|
35
|
+
write_vec3(:JPH_BodyInterface_AddTorque, torque)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def active?
|
|
40
|
+
check_alive!
|
|
41
|
+
Native.JPH_BodyInterface_IsActive(body_interface, @id)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def activate
|
|
45
|
+
check_alive!
|
|
46
|
+
Native.JPH_BodyInterface_ActivateBody(body_interface, @id)
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def deactivate
|
|
51
|
+
check_alive!
|
|
52
|
+
Native.JPH_BodyInterface_DeactivateBody(body_interface, @id)
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def kinematic_move_to(position, rotation, delta_time)
|
|
57
|
+
check_alive!
|
|
58
|
+
position = Conversions.native_vec3(position, name: "position")
|
|
59
|
+
rotation = Conversions.native_quat(rotation)
|
|
60
|
+
delta_time = Conversions.positive_float(delta_time, "delta_time")
|
|
61
|
+
Native.JPH_BodyInterface_MoveKinematic(
|
|
62
|
+
body_interface, @id, position.pointer, rotation.pointer, delta_time
|
|
63
|
+
)
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def friction
|
|
68
|
+
scalar_property(:JPH_BodyInterface_GetFriction)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def friction=(value)
|
|
72
|
+
write_scalar(:JPH_BodyInterface_SetFriction, value, "friction")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def restitution
|
|
76
|
+
scalar_property(:JPH_BodyInterface_GetRestitution)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def restitution=(value)
|
|
80
|
+
write_scalar(:JPH_BodyInterface_SetRestitution, value, "restitution")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def sensor?
|
|
84
|
+
check_alive!
|
|
85
|
+
Native.JPH_BodyInterface_IsSensor(body_interface, @id)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def sensor=(value)
|
|
89
|
+
check_alive!
|
|
90
|
+
Native.JPH_BodyInterface_SetIsSensor(body_interface, @id, !!value)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jolt
|
|
4
|
+
class CharacterVirtual
|
|
5
|
+
GROUND_STATES = {
|
|
6
|
+
0 => :on_ground,
|
|
7
|
+
1 => :sliding,
|
|
8
|
+
2 => :in_air,
|
|
9
|
+
3 => :in_air
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :system, :shape, :layer
|
|
13
|
+
|
|
14
|
+
def initialize(system, shape:, position: [0, 0, 0], rotation: [0, 0, 0, 1],
|
|
15
|
+
max_slope: Math::PI / 4, mass: 70, layer: :moving)
|
|
16
|
+
unless system.is_a?(System) && !system.destroyed?
|
|
17
|
+
raise InvalidArgumentError, "system must be a live Jolt::System"
|
|
18
|
+
end
|
|
19
|
+
unless shape.is_a?(Shape) && !shape.released?
|
|
20
|
+
raise InvalidArgumentError, "shape must be a live Jolt::Shape"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
@system = system
|
|
24
|
+
@shape = shape
|
|
25
|
+
layer_id = @system.layers.object_layer_id(layer)
|
|
26
|
+
@layer = layer.to_sym
|
|
27
|
+
native_position = Conversions.native_vec3(position, name: "position")
|
|
28
|
+
native_rotation = Conversions.native_quat(rotation)
|
|
29
|
+
slope = Conversions.positive_float(max_slope, "max_slope")
|
|
30
|
+
unless slope <= Math::PI / 2
|
|
31
|
+
raise InvalidArgumentError, "max_slope must not exceed pi / 2"
|
|
32
|
+
end
|
|
33
|
+
character_mass = Conversions.positive_float(mass, "mass")
|
|
34
|
+
@pointer = Native.JR_CharacterVirtual_Create(
|
|
35
|
+
@system.__native_pointer,
|
|
36
|
+
@shape.native_pointer,
|
|
37
|
+
native_position.pointer,
|
|
38
|
+
native_rotation.pointer,
|
|
39
|
+
slope,
|
|
40
|
+
character_mass
|
|
41
|
+
)
|
|
42
|
+
raise InitializationError, "failed to create virtual character" if @pointer.null?
|
|
43
|
+
|
|
44
|
+
@layer_id = layer_id
|
|
45
|
+
@destroyed = false
|
|
46
|
+
@system.__register_character(self, @shape)
|
|
47
|
+
rescue StandardError
|
|
48
|
+
Native.JPH_CharacterBase_Destroy(@pointer) if @pointer && !@pointer.null?
|
|
49
|
+
raise
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def position
|
|
53
|
+
read_vec3(:JPH_CharacterVirtual_GetPosition)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def position=(value)
|
|
57
|
+
write_vec3(:JPH_CharacterVirtual_SetPosition, value, "position")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def rotation
|
|
61
|
+
check_alive!
|
|
62
|
+
native = Native::Quat.new
|
|
63
|
+
Native.JPH_CharacterVirtual_GetRotation(@pointer, native.pointer)
|
|
64
|
+
Conversions.quat(native)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def rotation=(value)
|
|
68
|
+
check_alive!
|
|
69
|
+
native = Conversions.native_quat(value)
|
|
70
|
+
Native.JPH_CharacterVirtual_SetRotation(@pointer, native.pointer)
|
|
71
|
+
value
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def velocity
|
|
75
|
+
read_vec3(:JPH_CharacterVirtual_GetLinearVelocity)
|
|
76
|
+
end
|
|
77
|
+
alias linear_velocity velocity
|
|
78
|
+
|
|
79
|
+
def velocity=(value)
|
|
80
|
+
write_vec3(:JPH_CharacterVirtual_SetLinearVelocity, value, "velocity")
|
|
81
|
+
end
|
|
82
|
+
alias linear_velocity= velocity=
|
|
83
|
+
|
|
84
|
+
def mass
|
|
85
|
+
check_alive!
|
|
86
|
+
Native.JPH_CharacterVirtual_GetMass(@pointer)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def mass=(value)
|
|
90
|
+
check_alive!
|
|
91
|
+
Native.JPH_CharacterVirtual_SetMass(
|
|
92
|
+
@pointer,
|
|
93
|
+
Conversions.positive_float(value, "mass")
|
|
94
|
+
)
|
|
95
|
+
value
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def update(delta_time)
|
|
99
|
+
check_alive!
|
|
100
|
+
delta_time = Conversions.positive_float(delta_time, "delta_time")
|
|
101
|
+
@system.__with_native_operation("recursive virtual character update is not allowed") do
|
|
102
|
+
Native.JR_CharacterVirtual_ExtendedUpdate(
|
|
103
|
+
@pointer,
|
|
104
|
+
delta_time,
|
|
105
|
+
@layer_id,
|
|
106
|
+
@system.__native_pointer
|
|
107
|
+
)
|
|
108
|
+
self
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def ground_state
|
|
113
|
+
check_alive!
|
|
114
|
+
GROUND_STATES.fetch(Native.JPH_CharacterBase_GetGroundState(@pointer))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def supported?
|
|
118
|
+
check_alive!
|
|
119
|
+
Native.JPH_CharacterBase_IsSupported(@pointer)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def destroy
|
|
123
|
+
@system.__destroy_character(self)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def destroyed?
|
|
127
|
+
@destroyed
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def __native_pointer
|
|
131
|
+
check_alive!
|
|
132
|
+
@pointer
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def __destroy_native
|
|
136
|
+
Native.JPH_CharacterBase_Destroy(@pointer) if @pointer && !@pointer.null?
|
|
137
|
+
@pointer = nil
|
|
138
|
+
@destroyed = true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def read_vec3(function)
|
|
144
|
+
check_alive!
|
|
145
|
+
native = Native::Vec3.new
|
|
146
|
+
Native.public_send(function, @pointer, native.pointer)
|
|
147
|
+
Conversions.vec3(native)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def write_vec3(function, value, name)
|
|
151
|
+
check_alive!
|
|
152
|
+
native = Conversions.native_vec3(value, name:)
|
|
153
|
+
Native.public_send(function, @pointer, native.pointer)
|
|
154
|
+
value
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def check_alive!
|
|
158
|
+
raise UseAfterDestroyError, "virtual character has been destroyed" if @destroyed
|
|
159
|
+
@system.__check_alive!
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jolt
|
|
4
|
+
class Constraint
|
|
5
|
+
attr_reader :kind, :body_a, :body_b, :system
|
|
6
|
+
|
|
7
|
+
def initialize(system, pointer, kind, body_a, body_b)
|
|
8
|
+
@system = system
|
|
9
|
+
@pointer = pointer
|
|
10
|
+
@kind = kind
|
|
11
|
+
@body_a = body_a
|
|
12
|
+
@body_b = body_b
|
|
13
|
+
@destroyed = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def enabled?
|
|
17
|
+
check_alive!
|
|
18
|
+
Native.JPH_Constraint_GetEnabled(@pointer)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def enabled=(value)
|
|
22
|
+
check_alive!
|
|
23
|
+
Native.JPH_Constraint_SetEnabled(@pointer, !!value)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def limits
|
|
27
|
+
check_alive!
|
|
28
|
+
functions = limit_functions
|
|
29
|
+
[
|
|
30
|
+
Native.public_send(functions.fetch(:min), @pointer),
|
|
31
|
+
Native.public_send(functions.fetch(:max), @pointer)
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def limits=(value)
|
|
36
|
+
check_alive!
|
|
37
|
+
minimum, maximum = ConstraintCollection.interval(value, "limits")
|
|
38
|
+
Native.public_send(limit_functions.fetch(:set), @pointer, minimum, maximum)
|
|
39
|
+
value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def current_position
|
|
43
|
+
check_alive!
|
|
44
|
+
function = case @kind
|
|
45
|
+
when :hinge then :JPH_HingeConstraint_GetCurrentAngle
|
|
46
|
+
when :slider then :JPH_SliderConstraint_GetCurrentPosition
|
|
47
|
+
else
|
|
48
|
+
raise InvalidArgumentError, "#{@kind} constraints do not have a scalar position"
|
|
49
|
+
end
|
|
50
|
+
Native.public_send(function, @pointer)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def motor_speed
|
|
54
|
+
check_alive!
|
|
55
|
+
Native.public_send(motor_functions.fetch(:get), @pointer)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def motor_speed=(value)
|
|
59
|
+
check_alive!
|
|
60
|
+
speed = Conversions.finite_float(value, "motor_speed")
|
|
61
|
+
functions = motor_functions
|
|
62
|
+
Native.public_send(functions.fetch(:state), @pointer, 1)
|
|
63
|
+
Native.public_send(functions.fetch(:set), @pointer, speed)
|
|
64
|
+
value
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def disable_motor
|
|
68
|
+
check_alive!
|
|
69
|
+
Native.public_send(motor_functions.fetch(:state), @pointer, 0)
|
|
70
|
+
self
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def destroy
|
|
74
|
+
@system.__destroy_constraint(self)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def destroyed?
|
|
78
|
+
@destroyed
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def __native_pointer
|
|
82
|
+
check_alive!
|
|
83
|
+
@pointer
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def __involves?(body)
|
|
87
|
+
@body_a.equal?(body) || @body_b.equal?(body)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def __mark_destroyed
|
|
91
|
+
@destroyed = true
|
|
92
|
+
@pointer = nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def limit_functions
|
|
98
|
+
prefix = case @kind
|
|
99
|
+
when :distance then "Distance"
|
|
100
|
+
when :hinge then "Hinge"
|
|
101
|
+
when :slider then "Slider"
|
|
102
|
+
else
|
|
103
|
+
raise InvalidArgumentError, "#{@kind} constraints do not have limits"
|
|
104
|
+
end
|
|
105
|
+
{
|
|
106
|
+
set: :"JPH_#{prefix}Constraint_Set#{prefix == "Distance" ? "Distance" : "Limits"}",
|
|
107
|
+
min: :"JPH_#{prefix}Constraint_Get#{prefix == "Distance" ? "MinDistance" : "LimitsMin"}",
|
|
108
|
+
max: :"JPH_#{prefix}Constraint_Get#{prefix == "Distance" ? "MaxDistance" : "LimitsMax"}"
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def motor_functions
|
|
113
|
+
case @kind
|
|
114
|
+
when :hinge
|
|
115
|
+
{
|
|
116
|
+
state: :JPH_HingeConstraint_SetMotorState,
|
|
117
|
+
set: :JPH_HingeConstraint_SetTargetAngularVelocity,
|
|
118
|
+
get: :JPH_HingeConstraint_GetTargetAngularVelocity
|
|
119
|
+
}
|
|
120
|
+
when :slider
|
|
121
|
+
{
|
|
122
|
+
state: :JPH_SliderConstraint_SetMotorState,
|
|
123
|
+
set: :JPH_SliderConstraint_SetTargetVelocity,
|
|
124
|
+
get: :JPH_SliderConstraint_GetTargetVelocity
|
|
125
|
+
}
|
|
126
|
+
else
|
|
127
|
+
raise InvalidArgumentError, "#{@kind} constraints do not have a motor"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def check_alive!
|
|
132
|
+
raise UseAfterDestroyError, "#{@kind} constraint has been destroyed" if @destroyed
|
|
133
|
+
@system.__check_alive!
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|