Skip to content

Commit 5c115ce

Browse files
committed
Implement collisions with physics objects
Ropes can now collide with Godot physics objects. Closes #2
1 parent fc35b11 commit 5c115ce

6 files changed

Lines changed: 114 additions & 11 deletions

File tree

.clang-tidy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Checks:
88
- -cppcoreguidelines-init-variables # Produces false positves when initialization happens later
99
- -cppcoreguidelines-avoid-magic-numbers # This also triggers with literals like 3.1415
1010
- -cppcoreguidelines-avoid-non-const-global-variables
11+
- -cppcoreguidelines-owning-memory
1112
- misc-*
1213
- -misc-non-private-member-variables-in-classes
1314
- -misc-use-anonymous-namespace
@@ -21,3 +22,4 @@ Checks:
2122
- -readability-redundant-access-specifiers
2223
- -readability-implicit-bool-conversion
2324
- -readability-math-missing-parentheses
25+
- -readability-avoid-unconditional-preprocessor-if

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ compile_commands.json
1010
*.so
1111
*.exe
1212
*.dylib
13+
*.o
1314

1415
# Godot-specific ignores
1516
.godot/

demo/addons/ropesim/Rope.gd

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ signal on_point_count_changed()
7272
## Whether to fixate the first point at the rope's node position.
7373
@export var fixate_begin: bool = true
7474

75-
## Render rope points for debugging purposes.
75+
## Render rope points and collision radius for debugging purposes.
7676
@export var render_debug: bool = false: set = _set_draw_debug
7777

7878
## Render the rope as line.
@@ -87,6 +87,33 @@ signal on_point_count_changed()
8787
## Color gradient along the rendered line.
8888
@export var color_gradient: Gradient: set = _set_gradient
8989

90+
@export_group("Collisions")
91+
92+
## Enable collisions with physics bodies.
93+
## Collision checks are only performed for rope points, not whole segments.[br]
94+
## The performance impact is relatively cheap.[br]
95+
## [br]
96+
## If more precision is required to prevent tunneling or stretching between points, the number of
97+
## segments, the collision radius and the amount of constraint iterations can be tweaked.
98+
## There is also the [member resolve_collisions_while_constraining] option as last resort.
99+
@export var enable_collisions: bool = false
100+
101+
## Collision radius around each rope point.
102+
@export var collision_radius: float = 1.0
103+
104+
## Velocity damping after collisions. Gets applied in addition to regular damping.
105+
## Allows to make the rope appear smoother or rougher, i.e. make it slide more or less.
106+
@export var collision_damping: float = 0.0
107+
108+
## Resolves collisions after each constraint iteration instead of once after the constraint step.
109+
## Increases precision but also drastically increases computation time.
110+
## Negligible with a single rope but expensive with many ropes.
111+
@export var resolve_collisions_while_constraining: bool = false
112+
113+
## Physics layers to collide with.
114+
@export_flags_2d_physics var collision_mask: int = 1
115+
116+
90117
var _registered: bool = false
91118
var _colors := PackedColorArray()
92119
var _seg_lengths := PackedFloat32Array()
@@ -127,6 +154,9 @@ func _draw() -> void:
127154
for i in _points.size():
128155
draw_circle(_points[i], line_width / 2, Color.RED)
129156

157+
if enable_collisions:
158+
draw_circle(_points[i], collision_radius, Color.CYAN, false)
159+
130160

131161
# Logic
132162

demo/rope_examples/benchmark.tscn

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ offset_top = -1.0
1111
offset_right = 509.0
1212
offset_bottom = 22.0
1313
text = "384 Ropes
14-
3.99 ms"
14+
8.30 ms"
1515
script = ExtResource("2_ms00a")
1616

1717
[node name="Node2D9" type="Node2D" parent="."]

src/NativeRopeContext.cpp

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
#include "NativeRopeContext.hpp"
2+
#include "godot_cpp/classes/physics_direct_space_state2d.hpp"
3+
#include "godot_cpp/classes/physics_server2d.hpp"
4+
#include "godot_cpp/classes/physics_shape_query_parameters2d.hpp"
5+
#include "godot_cpp/classes/world2d.hpp"
6+
#include <godot_cpp/classes/window.hpp>
7+
#include <godot_cpp/variant/utility_functions.hpp>
28

39
using namespace godot;
410

@@ -16,6 +22,19 @@ static Vector2 damp_vec(Vector2 value, float damping_factor, double delta)
1622
return value.lerp(VECTOR_ZERO, (float)(1.0 - exp(-damping_factor * delta)));
1723
}
1824

25+
NativeRopeContext::NativeRopeContext() :
26+
shape_query(memnew(PhysicsShapeQueryParameters2D))
27+
{
28+
PhysicsServer2D* physics_server = PhysicsServer2D::get_singleton();
29+
cast_shape_rid = physics_server->circle_shape_create();
30+
shape_query->set_shape_rid(cast_shape_rid);
31+
}
32+
33+
NativeRopeContext::~NativeRopeContext()
34+
{
35+
PhysicsServer2D* physics_server = PhysicsServer2D::get_singleton();
36+
physics_server->free_rid(cast_shape_rid);
37+
}
1938

2039
bool NativeRopeContext::validate() const
2140
{
@@ -42,6 +61,11 @@ void NativeRopeContext::load_context(Node2D* rope)
4261
fixate_begin = rope->get("fixate_begin");
4362
resolve_to_begin = rope->get("resolve_to_begin");
4463
resolve_to_end = rope->get("resolve_to_end");
64+
enable_collisions = rope->get("enable_collisions");
65+
collision_radius = rope->get("collision_radius");
66+
collision_mask = rope->get("collision_mask");
67+
collision_damping = rope->get("collision_damping");
68+
resolve_collisions_while_constraining = rope->get("resolve_collisions_while_constraining");
4569
}
4670

4771
void NativeRopeContext::simulate(double delta)
@@ -58,7 +82,10 @@ void NativeRopeContext::simulate(double delta)
5882
}
5983

6084
_simulate_velocities(delta);
61-
_constraint();
85+
_constraint(delta);
86+
87+
if (!resolve_collisions_while_constraining)
88+
_resolve_collisions(delta);
6289

6390
if (fixate_begin)
6491
simulation_weights[0] = backup_multiplier_begin;
@@ -168,7 +195,7 @@ static void constraint_segment(Vector2* point_a, Vector2* point_b, float weight_
168195
*point_b += (weight_b + weight_b * (1.0 - weight_a)) * dir;
169196
}
170197

171-
void NativeRopeContext::_constraint()
198+
void NativeRopeContext::_constraint(double delta)
172199
{
173200
const bool use_euclid_constraint = max_endpoint_distance > 0;
174201

@@ -205,10 +232,44 @@ void NativeRopeContext::_constraint()
205232
}
206233
}
207234

208-
209235
for (int _ = 0; _ < num_constraint_iterations; ++_)
210236
{
211237
for (int i = 0; i < points.size() - 1; ++i)
212238
constraint_segment(&points[i], &points[i + 1], simulation_weights[i], simulation_weights[i + 1], seg_lengths[i]);
239+
240+
if (resolve_collisions_while_constraining)
241+
_resolve_collisions(delta);
213242
}
214243
}
244+
245+
void NativeRopeContext::_resolve_collisions(double delta)
246+
{
247+
if (!enable_collisions)
248+
return;
249+
250+
PhysicsDirectSpaceState2D* space = rope->get_world_2d()->get_direct_space_state();
251+
PhysicsServer2D* physics_server = PhysicsServer2D::get_singleton();
252+
Transform2D transform;
253+
254+
physics_server->shape_set_data(cast_shape_rid, collision_radius);
255+
shape_query->set_collision_mask(collision_mask);
256+
257+
for (int i = 0; i < points.size(); ++i)
258+
{
259+
transform.set_origin(points[i]);
260+
shape_query->set_transform(transform);
261+
const Dictionary rest_info = space->get_rest_info(shape_query);
262+
263+
if (rest_info.is_empty())
264+
continue;
265+
266+
const Vector2 intersect_point = rest_info["point"];
267+
const Vector2 intersect_normal = rest_info["normal"];
268+
const Vector2 safe_point = intersect_point + intersect_normal * (collision_radius + CMP_EPSILON);
269+
const Vector2 safe_motion = (safe_point - points[i]) * simulation_weights[i];
270+
const Vector2 new_point = points[i] + safe_motion;
271+
272+
points[i] = oldpoints[i] + damp_vec(new_point - oldpoints[i], collision_damping, delta);
273+
}
274+
}
275+

src/NativeRopeContext.hpp

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
#ifndef NATIVE_ROPE_CONTEXT_HPP
2-
#define NATIVE_ROPE_CONTEXT_HPP
1+
#pragma once
32

43
#include "godot_cpp/classes/curve.hpp"
54
#include "godot_cpp/classes/node2d.hpp"
5+
#include "godot_cpp/classes/physics_shape_query_parameters2d.hpp"
66

77
namespace godot
88
{
@@ -11,14 +11,18 @@ namespace godot
1111
class NativeRopeContext
1212
{
1313
public:
14-
void load_context(Node2D* rope);
14+
NativeRopeContext();
15+
~NativeRopeContext();
16+
17+
void load_context(Node2D *rope);
1518
void simulate(double delta);
1619
bool validate() const;
1720

1821
protected:
1922
void _simulate_velocities(double delta);
2023
void _simulate_stiffness(PackedVector2Array* velocities) const;
21-
void _constraint();
24+
void _resolve_collisions(double delta);
25+
void _constraint(double delta);
2226

2327
public:
2428
Node2D* rope = nullptr;
@@ -31,12 +35,17 @@ namespace godot
3135
float damping = 0.0;
3236
float stiffness = 0.0;
3337
float max_endpoint_distance = 0.0;
38+
float collision_radius = 1.0;
39+
float collision_damping = 0.0;
3440
int num_constraint_iterations = 0;
41+
int collision_mask = 0;
3542
Ref<Curve> damping_curve;
43+
Ref<PhysicsShapeQueryParameters2D> shape_query;
44+
RID cast_shape_rid;
3645
bool fixate_begin = true;
3746
bool resolve_to_begin = false;
3847
bool resolve_to_end = false;
48+
bool enable_collisions = false;
49+
bool resolve_collisions_while_constraining = false;
3950
};
4051
}
41-
42-
#endif

0 commit comments

Comments
 (0)