Add a 3D soft body physics demo (#1228)

This commit is contained in:
Hugo Locurcio
2025-09-30 20:16:47 +02:00
committed by GitHub
parent 49718c4b16
commit 0540894062
27 changed files with 2828 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
# 3D Soft Body Physics
An example of [soft body physics](https://docs.godotengine.org/en/latest/tutorials/physics/soft_body.html)
(deformable objects) such as cloth, boxes, and spheres.
Soft bodies support pinning points as well as applying impulses/forces to specific points,
which can be useful for effects such as wind.
Soft bodies can interact with static, rigid, and character bodies (and will be aware of rigid bodies'
weight when doing so). However, soft bodies currently do **not** interact with other soft bodies,
which means they will pass through each other.
The per-point impulse timer example also showcases how to attach nodes to specific points of a SoftBody3D.
This can be used to make particles, meshes, or even rigid bodies follow specific points.
The cloth soft bodies in this demo use a BaseMaterial3D with the **Grow** property enabled to prevent
visible clipping into surfaces.
Controls:
- <kbd>R</kbd>: Reset soft body simulation and user-placed objects
- <kbd>C</kbd>: Place cloth at cursor position
- <kbd>V</kbd>: Place light box at cursor position
- <kbd>B</kbd>: Place heavy box at cursor position
For performance reasons, only 10 user-placed objects can be present at a time in this demo.
The oldest objects will be removed when attempting to place more than 10 objects.
Language: GDScript
Renderer: Forward+
## Screenshots
![Screenshot](screenshots/soft_body_physics.webp)

View File

@@ -0,0 +1,36 @@
[gd_scene load_steps=7 format=3 uid="uid://inwnbl1ufttq"]
[ext_resource type="ArrayMesh" uid="uid://d2aecsqru2t1r" path="res://rounded_cube.obj" id="1_g2tbl"]
[sub_resource type="Gradient" id="Gradient_cbx31"]
colors = PackedColorArray(0.619608, 0.227451, 0, 1, 1, 0.564706, 0, 1)
[sub_resource type="FastNoiseLite" id="FastNoiseLite_x8fbi"]
fractal_lacunarity = 3.47
fractal_gain = 1.105
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_41u45"]
noise = SubResource("FastNoiseLite_x8fbi")
color_ramp = SubResource("Gradient_cbx31")
seamless = true
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_cbx31"]
noise = SubResource("FastNoiseLite_x8fbi")
color_ramp = SubResource("Gradient_cbx31")
seamless = true
as_normal_map = true
bump_strength = 4.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cbx31"]
albedo_texture = SubResource("NoiseTexture2D_41u45")
normal_enabled = true
normal_texture = SubResource("NoiseTexture2D_cbx31")
uv1_scale = Vector3(2, 2, 2)
texture_filter = 5
[node name="SoftBody3D" type="SoftBody3D"]
mesh = ExtResource("1_g2tbl")
surface_material_override/0 = SubResource("StandardMaterial3D_cbx31")
simulation_precision = 100
total_mass = 10.01
pressure_coefficient = 100.0

View File

@@ -0,0 +1,26 @@
[gd_scene load_steps=6 format=3 uid="uid://ditdn410656"]
[ext_resource type="Texture2D" uid="uid://csp3kbg6r4rwd" path="res://textures/polyhaven/fabric_pattern_07_col_1_1k.jpg" id="1_pir5u"]
[ext_resource type="Texture2D" uid="uid://hh6i17naw1qx" path="res://textures/polyhaven/fabric_pattern_07_nor_gl_1k.jpg" id="2_q5utd"]
[ext_resource type="Texture2D" uid="uid://clyte8pu0nwom" path="res://textures/polyhaven/fabric_pattern_07_arm_1k.jpg" id="3_eo47f"]
[sub_resource type="ORMMaterial3D" id="ORMMaterial3D_g14j6"]
cull_mode = 2
albedo_texture = ExtResource("1_pir5u")
orm_texture = ExtResource("3_eo47f")
normal_enabled = true
normal_texture = ExtResource("2_q5utd")
uv1_scale = Vector3(4, 4, 4)
texture_filter = 5
grow = true
grow_amount = 0.02
[sub_resource type="PlaneMesh" id="PlaneMesh_6uqi0"]
material = SubResource("ORMMaterial3D_g14j6")
size = Vector2(1.6, 1.6)
subdivide_width = 63
subdivide_depth = 63
[node name="Cloth" type="SoftBody3D"]
mesh = SubResource("PlaneMesh_6uqi0")
skeleton = NodePath("../..")

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><path fill="#fc7f7f" d="M16.003 8s-24 40 0 56.001-8 56.002-8 56.002h104.002s24-48.002 0-64.002 8-48.001 8-48.001Zm16 16h56.002s-16 24 8 40.001 0 40.001 0 40.001H40.003s16-32-8-48c-24-16.001 0-32.002 0-32.002" style="fill:gray;fill-opacity:1;stroke-width:8.00018"/></svg>

After

Width:  |  Height:  |  Size: 336 B

View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c5uks4gy25jah"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1,85 @@
[gd_scene load_steps=12 format=3 uid="uid://da7gfvk1e0p6i"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_g14j6"]
albedo_color = Color(1, 0.27, 0.27, 1)
emission_enabled = true
emission = Color(1, 0, 0, 1)
[sub_resource type="SphereMesh" id="SphereMesh_g14j6"]
material = SubResource("StandardMaterial3D_g14j6")
radius = 0.02
height = 0.04
radial_segments = 16
rings = 8
[sub_resource type="Gradient" id="Gradient_c85u6"]
colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_nikp6"]
gradient = SubResource("Gradient_c85u6")
[sub_resource type="Curve" id="Curve_gflbs"]
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
point_count = 2
[sub_resource type="CurveTexture" id="CurveTexture_h4t62"]
curve = SubResource("Curve_gflbs")
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_sw3mb"]
spread = 180.0
initial_velocity_max = 0.25
gravity = Vector3(0, 0, 0)
scale_curve = SubResource("CurveTexture_h4t62")
color_ramp = SubResource("GradientTexture1D_nikp6")
[sub_resource type="Gradient" id="Gradient_jfmli"]
interpolation_mode = 2
colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_reuqr"]
gradient = SubResource("Gradient_jfmli")
fill = 1
fill_from = Vector2(0.5, 0.5)
fill_to = Vector2(0.5, 0)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_c85u6"]
transparency = 1
blend_mode = 1
shading_mode = 0
vertex_color_use_as_albedo = true
albedo_color = Color(1.8247963, 0.60199416, 0.49019507, 1)
albedo_texture = SubResource("GradientTexture2D_reuqr")
billboard_mode = 3
billboard_keep_scale = true
particles_anim_h_frames = 1
particles_anim_v_frames = 1
particles_anim_loop = false
[sub_resource type="QuadMesh" id="QuadMesh_nikp6"]
material = SubResource("StandardMaterial3D_c85u6")
size = Vector2(0.05, 0.05)
[node name="Pin" type="MeshInstance3D"]
cast_shadow = 0
visibility_range_end = 10.0
visibility_range_end_margin = 5.0
visibility_range_fade_mode = 1
mesh = SubResource("SphereMesh_g14j6")
[node name="GPUParticles3D" type="GPUParticles3D" parent="."]
visibility_range_end = 10.0
visibility_range_end_margin = 5.0
visibility_range_fade_mode = 1
amount = 100
process_material = SubResource("ParticleProcessMaterial_sw3mb")
draw_pass_1 = SubResource("QuadMesh_nikp6")
[node name="OmniLight3D" type="OmniLight3D" parent="."]
light_color = Color(1, 0.3137255, 0.2509804, 1)
light_energy = 0.05
light_size = 0.25
distance_fade_enabled = true
distance_fade_begin = 10.0
distance_fade_length = 5.0
omni_range = 0.25
omni_attenuation = 2.0

View File

@@ -0,0 +1,67 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Soft Body Physics"
config/description="An example of soft body physics (deformable objects) such as cloth, boxes and spheres."
config/tags=PackedStringArray("3d", "demo", "official", "rendering")
run/main_scene="res://test.tscn"
config/features=PackedStringArray("4.5")
config/icon="uid://c5uks4gy25jah"
[debug]
settings/physics_interpolation/enable_warnings=false
gdscript/warnings/untyped_declaration=1
[display]
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"
[filesystem]
import/blender/enabled=false
[input]
reset_physics_simulation={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":82,"physical_keycode":0,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
]
}
place_cloth={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":67,"physical_keycode":0,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null)
]
}
place_light_box={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":86,"physical_keycode":0,"key_label":0,"unicode":118,"location":0,"echo":false,"script":null)
]
}
place_heavy_box={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":66,"physical_keycode":0,"key_label":0,"unicode":98,"location":0,"echo":false,"script":null)
]
}
[physics]
3d/physics_engine="Jolt Physics"
common/physics_interpolation=true
[rendering]
lights_and_shadows/directional_shadow/soft_shadow_filter_quality=3
textures/default_filters/anisotropic_filtering_level=4
anti_aliasing/quality/msaa_3d=2
textures/decals/filter=5

View File

@@ -0,0 +1,38 @@
[gd_scene load_steps=7 format=3 uid="uid://hlyf6l5pbqgu"]
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_ppyta"]
friction = 0.75
bounce = 0.4
[sub_resource type="Gradient" id="Gradient_jgx4i"]
interpolation_mode = 2
colors = PackedColorArray(1, 1, 1, 1, 0.5, 0.5, 0.5, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_jypa8"]
gradient = SubResource("Gradient_jgx4i")
fill = 2
fill_from = Vector2(0.5, 0.5)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ppyta"]
albedo_color = Color(1, 0.298039, 0.74902, 1)
albedo_texture = SubResource("GradientTexture2D_jypa8")
uv1_scale = Vector3(3, 2, 1)
texture_filter = 5
[sub_resource type="BoxMesh" id="BoxMesh_ykrsh"]
material = SubResource("StandardMaterial3D_ppyta")
size = Vector3(0.25, 0.25, 0.25)
[sub_resource type="BoxShape3D" id="BoxShape3D_ppyta"]
size = Vector3(0.25, 0.25, 0.25)
[node name="RigidBoxHeavy" type="RigidBody3D"]
collision_mask = 3
mass = 1000.0
physics_material_override = SubResource("PhysicsMaterial_ppyta")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
mesh = SubResource("BoxMesh_ykrsh")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("BoxShape3D_ppyta")

View File

@@ -0,0 +1,38 @@
[gd_scene load_steps=7 format=3 uid="uid://b6uelvj3b82bf"]
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_ppyta"]
friction = 0.75
bounce = 0.4
[sub_resource type="Gradient" id="Gradient_jgx4i"]
interpolation_mode = 2
colors = PackedColorArray(1, 1, 1, 1, 0.5, 0.5, 0.5, 1)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_nmss4"]
gradient = SubResource("Gradient_jgx4i")
fill = 2
fill_from = Vector2(0.5, 0.5)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ppyta"]
albedo_color = Color(1.8771e-06, 0.751954, 0.25936, 1)
albedo_texture = SubResource("GradientTexture2D_nmss4")
uv1_scale = Vector3(3, 2, 1)
texture_filter = 5
[sub_resource type="BoxMesh" id="BoxMesh_ykrsh"]
material = SubResource("StandardMaterial3D_ppyta")
size = Vector3(0.25, 0.25, 0.25)
[sub_resource type="BoxShape3D" id="BoxShape3D_ppyta"]
size = Vector3(0.25, 0.25, 0.25)
[node name="RigidBoxLight" type="RigidBody3D"]
collision_mask = 3
mass = 10.0
physics_material_override = SubResource("PhysicsMaterial_ppyta")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
mesh = SubResource("BoxMesh_ykrsh")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("BoxShape3D_ppyta")

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
[remap]
importer="wavefront_obj"
importer_version=1
type="Mesh"
uid="uid://d2aecsqru2t1r"
path="res://.godot/imported/rounded_cube.obj-8ac39ddda691347e7c52fd05f36f907f.mesh"
[deps]
files=["res://.godot/imported/rounded_cube.obj-8ac39ddda691347e7c52fd05f36f907f.mesh"]
source_file="res://rounded_cube.obj"
dest_files=["res://.godot/imported/rounded_cube.obj-8ac39ddda691347e7c52fd05f36f907f.mesh", "res://.godot/imported/rounded_cube.obj-8ac39ddda691347e7c52fd05f36f907f.mesh"]
[params]
generate_tangents=true
generate_lods=true
generate_shadow_mesh=true
generate_lightmap_uv2=false
generate_lightmap_uv2_texel_size=0.2
scale_mesh=Vector3(1, 1, 1)
offset_mesh=Vector3(0, 0, 0)
force_disable_mesh_compression=false

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

View File

@@ -0,0 +1,37 @@
[gd_scene load_steps=7 format=3 uid="uid://nn4wqmqujm26"]
[sub_resource type="Gradient" id="Gradient_cbx31"]
colors = PackedColorArray(0.619608, 0.227451, 0, 1, 1, 0.564706, 0, 1)
[sub_resource type="FastNoiseLite" id="FastNoiseLite_x8fbi"]
fractal_lacunarity = 3.47
fractal_gain = 1.105
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_41u45"]
noise = SubResource("FastNoiseLite_x8fbi")
color_ramp = SubResource("Gradient_cbx31")
seamless = true
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_cbx31"]
noise = SubResource("FastNoiseLite_x8fbi")
color_ramp = SubResource("Gradient_cbx31")
seamless = true
as_normal_map = true
bump_strength = 4.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cbx31"]
albedo_texture = SubResource("NoiseTexture2D_41u45")
normal_enabled = true
normal_texture = SubResource("NoiseTexture2D_cbx31")
texture_filter = 5
[sub_resource type="SphereMesh" id="SphereMesh_6uqi0"]
material = SubResource("StandardMaterial3D_cbx31")
radius = 0.25
height = 0.5
radial_segments = 32
rings = 16
[node name="SoftBody3D" type="SoftBody3D"]
mesh = SubResource("SphereMesh_6uqi0")
pressure_coefficient = 1400.0

View File

@@ -0,0 +1,492 @@
[gd_scene load_steps=21 format=3 uid="uid://bks13fos3trrs"]
[ext_resource type="PackedScene" uid="uid://ditdn410656" path="res://cloth.tscn" id="3_6uqi0"]
[ext_resource type="PackedScene" uid="uid://nn4wqmqujm26" path="res://sphere.tscn" id="4_ppyta"]
[ext_resource type="PackedScene" uid="uid://hlyf6l5pbqgu" path="res://rigid_box_heavy.tscn" id="5_ykrsh"]
[ext_resource type="PackedScene" uid="uid://b6uelvj3b82bf" path="res://rigid_box_light.tscn" id="6_g14j6"]
[ext_resource type="PackedScene" uid="uid://inwnbl1ufttq" path="res://box.tscn" id="6_vbegm"]
[ext_resource type="PackedScene" uid="uid://da7gfvk1e0p6i" path="res://pin.tscn" id="8_ehkex"]
[ext_resource type="Texture2D" uid="uid://chjqieyps5n5r" path="res://textures/checker.png" id="14"]
[ext_resource type="Script" uid="uid://bi2o4fooys1dc" path="res://tester.gd" id="18"]
[sub_resource type="ProceduralSkyMaterial" id="9"]
[sub_resource type="Sky" id="10"]
sky_material = SubResource("9")
[sub_resource type="Environment" id="11"]
background_mode = 2
sky = SubResource("10")
tonemap_mode = 4
tonemap_exposure = 1.3
[sub_resource type="Animation" id="12"]
resource_name = "move"
length = 6.0
loop_mode = 1
[sub_resource type="AnimationLibrary" id="AnimationLibrary_ecfcr"]
_data = {
&"move": SubResource("12")
}
[sub_resource type="StandardMaterial3D" id="13"]
diffuse_mode = 1
albedo_texture = ExtResource("14")
uv1_scale = Vector3(32, 32, 1)
texture_filter = 5
[sub_resource type="PlaneMesh" id="14"]
material = SubResource("13")
size = Vector2(64, 64)
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_g14j6"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6uqi0"]
albedo_color = Color(0.396078, 0.52549, 1, 1)
albedo_texture = ExtResource("14")
uv1_scale = Vector3(3, 2, 1)
texture_filter = 5
[sub_resource type="BoxMesh" id="BoxMesh_ppyta"]
material = SubResource("StandardMaterial3D_6uqi0")
[sub_resource type="BoxShape3D" id="BoxShape3D_ykrsh"]
[sub_resource type="BoxShape3D" id="BoxShape3D_g14j6"]
size = Vector3(3, 3, 0.5)
[node name="WorldEnvironment" type="WorldEnvironment"]
environment = SubResource("11")
script = ExtResource("18")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
&"": SubResource("AnimationLibrary_ecfcr")
}
autoplay = "move"
[node name="StaticBody3D" type="StaticBody3D" parent="."]
[node name="Plane" type="MeshInstance3D" parent="StaticBody3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -6)
layers = 2
mesh = SubResource("14")
skeleton = NodePath("../..")
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -6)
shape = SubResource("WorldBoundaryShape3D_g14j6")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.770897, 0.365782, -0.52146, -7.63748e-10, 0.818671, 0.574263, 0.63696, 0.442697, -0.631111, 3.9506, 3.39961, 3.54442)
shadow_enabled = true
shadow_bias = 0.04
shadow_blur = 1.5
directional_shadow_mode = 0
directional_shadow_fade_start = 1.0
directional_shadow_max_distance = 20.0
[node name="CameraHolder" type="Node3D" parent="."]
transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 0, 0.125, 12)
[node name="RotationX" type="Node3D" parent="CameraHolder"]
[node name="Camera3D" type="Camera3D" parent="CameraHolder/RotationX"]
fov = 70.0
[node name="Testers" type="Node3D" parent="."]
[node name="ClothPhysics" type="Node3D" parent="Testers"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 12)
[node name="Cloth" parent="Testers/ClothPhysics" instance=ExtResource("3_6uqi0")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
[node name="StaticBody3D" type="StaticBody3D" parent="Testers/ClothPhysics"]
transform = Transform3D(0.851651, 0.309976, 0.422618, -0.34202, 0.939693, 0, -0.397131, -0.144544, 0.906308, 0, 0, 0)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/ClothPhysics/StaticBody3D"]
mesh = SubResource("BoxMesh_ppyta")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/ClothPhysics/StaticBody3D"]
shape = SubResource("BoxShape3D_ykrsh")
[node name="StaticBody3D2" type="StaticBody3D" parent="Testers/ClothPhysics"]
transform = Transform3D(0.212913, 0.0774939, 0.105655, -0.085505, 0.234923, -3.0889e-10, -0.0992828, -0.036136, 0.226577, 0.193735, 0.587308, -0.09034)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/ClothPhysics/StaticBody3D2"]
mesh = SubResource("BoxMesh_ppyta")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/ClothPhysics/StaticBody3D2"]
shape = SubResource("BoxShape3D_ykrsh")
[node name="SoftBoxes" type="Node3D" parent="Testers"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 8)
[node name="Box" parent="Testers/SoftBoxes" instance=ExtResource("6_vbegm")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 2, 0)
[node name="Box2" parent="Testers/SoftBoxes" instance=ExtResource("6_vbegm")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.90000004, 1, 0)
[node name="CSGPolygon3D" type="CSGPolygon3D" parent="Testers/SoftBoxes"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.6, -2.62268e-08)
use_collision = true
polygon = PackedVector2Array(0.458662, -0.458186, 0.222452, -0.352707, 0.261384, -0.169098, 0.403376, -0.143968, 0.627135, -0.181435, 0.737105, -0.303279, 0.648884, -0.455454)
[node name="CSGPolygon3D2" type="CSGPolygon3D" parent="Testers/SoftBoxes"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.726544, 0.6, -0.572491)
use_collision = true
polygon = PackedVector2Array(0.458662, -0.458186, 0.222452, -0.352707, 0.261384, -0.169098, 0.403376, -0.143968, 0.627135, -0.181435, 0.737105, -0.303279, 0.648884, -0.455454)
[node name="CSGPolygon3D3" type="CSGPolygon3D" parent="Testers/SoftBoxes"]
transform = Transform3D(1, 0, 0, 0, -0.992629, 0.121194, 0, -0.121194, -0.992629, -0.726544, 0.6, -0.572491)
use_collision = true
polygon = PackedVector2Array(0.458662, -0.458186, 0.222452, -0.352707, 0.261384, -0.169098, 0.403376, -0.143968, 0.627135, -0.181435, 0.737105, -0.303279, 0.648884, -0.455454)
[node name="CSGPolygon3D4" type="CSGPolygon3D" parent="Testers/SoftBoxes"]
transform = Transform3D(1, 0, 0, 0, -0.992629, 0.121194, 0, -0.121194, -0.992629, -0.216217, -0.0966039, -0.68374)
use_collision = true
polygon = PackedVector2Array(0.458662, -0.458186, 0.222452, -0.352707, 0.261384, -0.169098, 0.403376, -0.143968, 0.627135, -0.181435, 0.737105, -0.303279, 0.648884, -0.455454)
[node name="CSGBox3D" type="CSGBox3D" parent="Testers/SoftBoxes"]
use_collision = true
size = Vector3(2.5, 1, 2.5)
material = SubResource("StandardMaterial3D_6uqi0")
[node name="CSGSphere3D" type="CSGSphere3D" parent="Testers/SoftBoxes/CSGBox3D"]
transform = Transform3D(1.5, 0, 0, 0, 1, 0, 0, 0, 1.5, 0, 0.9, 0)
operation = 2
radius = 0.9
radial_segments = 96
rings = 32
material = SubResource("StandardMaterial3D_6uqi0")
[node name="RigidBoxHeavy" parent="Testers/SoftBoxes" instance=ExtResource("5_ykrsh")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0.4)
[node name="RigidBoxHeavy2" parent="Testers/SoftBoxes" instance=ExtResource("5_ykrsh")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.2, 2, -0.1)
[node name="RigidBoxLight" parent="Testers/SoftBoxes" instance=ExtResource("6_g14j6")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.1, 1.5, -0.0999997)
[node name="RigidBoxLight2" parent="Testers/SoftBoxes" instance=ExtResource("6_g14j6")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.7, 1.5, 0.2)
[node name="RigidBoxLight3" parent="Testers/SoftBoxes" instance=ExtResource("6_g14j6")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.4, 2.6, -0.6)
[node name="InvisibleWalls" type="StaticBody3D" parent="Testers/SoftBoxes"]
collision_layer = 2
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/SoftBoxes/InvisibleWalls"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.25, 1.5, 1.5)
shape = SubResource("BoxShape3D_g14j6")
[node name="CollisionShape3D2" type="CollisionShape3D" parent="Testers/SoftBoxes/InvisibleWalls"]
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -1.5, 1.5, -0.25)
shape = SubResource("BoxShape3D_g14j6")
[node name="CollisionShape3D3" type="CollisionShape3D" parent="Testers/SoftBoxes/InvisibleWalls"]
transform = Transform3D(-1, 0, 8.74228e-08, 0, 1, 0, -8.74228e-08, 0, -1, 0.25, 1.5, -1.5)
shape = SubResource("BoxShape3D_g14j6")
[node name="CollisionShape3D4" type="CollisionShape3D" parent="Testers/SoftBoxes/InvisibleWalls"]
transform = Transform3D(1.31134e-07, 0, 1, 0, 1, 0, -1, 0, 1.31134e-07, 1.5, 1.5, 0.25)
shape = SubResource("BoxShape3D_g14j6")
[node name="SoftSpheres" type="Node3D" parent="Testers"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 4)
[node name="Sphere" parent="Testers/SoftSpheres" instance=ExtResource("4_ppyta")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
[node name="Sphere2" parent="Testers/SoftSpheres" instance=ExtResource("4_ppyta")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 1.6, 0)
[node name="CSGPolygon3D" type="CSGPolygon3D" parent="Testers/SoftSpheres"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.6, -2.62268e-08)
use_collision = true
polygon = PackedVector2Array(0.458662, -0.458186, 0.222452, -0.352707, 0.261384, -0.169098, 0.403376, -0.143968, 0.627135, -0.181435, 0.737105, -0.303279, 0.648884, -0.455454)
[node name="CSGPolygon3D2" type="CSGPolygon3D" parent="Testers/SoftSpheres"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.726544, 0.6, -0.572491)
use_collision = true
polygon = PackedVector2Array(0.458662, -0.458186, 0.222452, -0.352707, 0.261384, -0.169098, 0.403376, -0.143968, 0.627135, -0.181435, 0.737105, -0.303279, 0.648884, -0.455454)
[node name="CSGPolygon3D3" type="CSGPolygon3D" parent="Testers/SoftSpheres"]
transform = Transform3D(1, 0, 0, 0, -0.992629, 0.121194, 0, -0.121194, -0.992629, -0.726544, 0.6, -0.572491)
use_collision = true
polygon = PackedVector2Array(0.458662, -0.458186, 0.222452, -0.352707, 0.261384, -0.169098, 0.403376, -0.143968, 0.627135, -0.181435, 0.737105, -0.303279, 0.648884, -0.455454)
[node name="CSGPolygon3D4" type="CSGPolygon3D" parent="Testers/SoftSpheres"]
transform = Transform3D(1, 0, 0, 0, -0.992629, 0.121194, 0, -0.121194, -0.992629, -0.216217, -0.0966039, -0.68374)
use_collision = true
polygon = PackedVector2Array(0.458662, -0.458186, 0.222452, -0.352707, 0.261384, -0.169098, 0.403376, -0.143968, 0.627135, -0.181435, 0.737105, -0.303279, 0.648884, -0.455454)
[node name="CSGBox3D" type="CSGBox3D" parent="Testers/SoftSpheres"]
use_collision = true
size = Vector3(2.5, 1, 2.5)
material = SubResource("StandardMaterial3D_6uqi0")
[node name="CSGSphere3D" type="CSGSphere3D" parent="Testers/SoftSpheres/CSGBox3D"]
transform = Transform3D(1.5, 0, 0, 0, 1, 0, 0, 0, 1.5, 0, 0.9, 0)
operation = 2
radius = 0.9
radial_segments = 96
rings = 32
material = SubResource("StandardMaterial3D_6uqi0")
[node name="RigidBoxHeavy" parent="Testers/SoftSpheres" instance=ExtResource("5_ykrsh")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0.4)
[node name="RigidBoxHeavy2" parent="Testers/SoftSpheres" instance=ExtResource("5_ykrsh")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.2, 2, -0.1)
[node name="RigidBoxLight" parent="Testers/SoftSpheres" instance=ExtResource("6_g14j6")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.1, 1.5, -0.0999997)
[node name="RigidBoxLight2" parent="Testers/SoftSpheres" instance=ExtResource("6_g14j6")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.7, 1.5, 0.2)
[node name="RigidBoxLight3" parent="Testers/SoftSpheres" instance=ExtResource("6_g14j6")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.4, 2.6, -0.6)
[node name="InvisibleWalls" type="StaticBody3D" parent="Testers/SoftSpheres"]
collision_layer = 2
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/SoftSpheres/InvisibleWalls"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.25, 1.5, 1.5)
shape = SubResource("BoxShape3D_g14j6")
[node name="CollisionShape3D2" type="CollisionShape3D" parent="Testers/SoftSpheres/InvisibleWalls"]
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -1.5, 1.5, -0.25)
shape = SubResource("BoxShape3D_g14j6")
[node name="CollisionShape3D3" type="CollisionShape3D" parent="Testers/SoftSpheres/InvisibleWalls"]
transform = Transform3D(-1, 0, 8.74228e-08, 0, 1, 0, -8.74228e-08, 0, -1, 0.25, 1.5, -1.5)
shape = SubResource("BoxShape3D_g14j6")
[node name="CollisionShape3D4" type="CollisionShape3D" parent="Testers/SoftSpheres/InvisibleWalls"]
transform = Transform3D(1.31134e-07, 0, 1, 0, 1, 0, -1, 0, 1.31134e-07, 1.5, 1.5, 0.25)
shape = SubResource("BoxShape3D_g14j6")
[node name="CentralImpulseTimer" type="Node3D" parent="Testers"]
[node name="Cloth" parent="Testers/CentralImpulseTimer" instance=ExtResource("3_6uqi0")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
[node name="StaticBody3D" type="StaticBody3D" parent="Testers/CentralImpulseTimer"]
transform = Transform3D(0.851651, 0.309976, 0.422618, -0.34202, 0.939693, 0, -0.397131, -0.144544, 0.906308, 0, 0, 0)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/CentralImpulseTimer/StaticBody3D"]
mesh = SubResource("BoxMesh_ppyta")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/CentralImpulseTimer/StaticBody3D"]
shape = SubResource("BoxShape3D_ykrsh")
[node name="StaticBody3D2" type="StaticBody3D" parent="Testers/CentralImpulseTimer"]
transform = Transform3D(0.212913, 0.0774939, 0.105655, -0.085505, 0.234923, -3.0889e-10, -0.0992828, -0.036136, 0.226577, 0.193735, 0.587308, -0.09034)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/CentralImpulseTimer/StaticBody3D2"]
mesh = SubResource("BoxMesh_ppyta")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/CentralImpulseTimer/StaticBody3D2"]
shape = SubResource("BoxShape3D_ykrsh")
[node name="Timer" type="Timer" parent="Testers/CentralImpulseTimer"]
wait_time = 1.5
autostart = true
[node name="RigidBoxHeavy" parent="Testers/CentralImpulseTimer" instance=ExtResource("5_ykrsh")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.8, 0.3, -0.6)
[node name="RigidBoxHeavy2" parent="Testers/CentralImpulseTimer" instance=ExtResource("5_ykrsh")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6, 1.3, 0.6)
[node name="RigidBoxLight" parent="Testers/CentralImpulseTimer" instance=ExtResource("6_g14j6")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 1.5, 0.1)
[node name="CentralForceWind" type="Node3D" parent="Testers"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -4)
[node name="Cloth" parent="Testers/CentralForceWind" instance=ExtResource("3_6uqi0")]
transform = Transform3D(0.965926, -0.258819, 0, 0.258819, 0.965926, 0, 0, 0, 1, -0.38637, 0.896472, 0)
[node name="StaticBody3D" type="StaticBody3D" parent="Testers/CentralForceWind"]
transform = Transform3D(0.851651, 0.309976, 0.422618, -0.34202, 0.939693, 0, -0.397131, -0.144544, 0.906308, 0, 0, 0)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/CentralForceWind/StaticBody3D"]
mesh = SubResource("BoxMesh_ppyta")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/CentralForceWind/StaticBody3D"]
shape = SubResource("BoxShape3D_ykrsh")
[node name="StaticBody3D2" type="StaticBody3D" parent="Testers/CentralForceWind"]
transform = Transform3D(0.212913, 0.0774939, 0.105655, -0.085505, 0.234923, -3.0889e-10, -0.0992828, -0.036136, 0.226577, 0.193735, 0.587308, -0.09034)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/CentralForceWind/StaticBody3D2"]
mesh = SubResource("BoxMesh_ppyta")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/CentralForceWind/StaticBody3D2"]
shape = SubResource("BoxShape3D_ykrsh")
[node name="PerPointImpulseTimer" type="Node3D" parent="Testers"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -8)
[node name="PointTrackers" type="Node3D" parent="Testers/PerPointImpulseTimer"]
[node name="PointTracker" parent="Testers/PerPointImpulseTimer/PointTrackers" instance=ExtResource("8_ehkex")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.8, 1.02, 0.79999995)
skeleton = NodePath("../..")
metadata/point = 0
[node name="PointTracker2" parent="Testers/PerPointImpulseTimer/PointTrackers" instance=ExtResource("8_ehkex")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.8, 1.02, 0.79999995)
skeleton = NodePath("../..")
metadata/point = 64
[node name="PointTracker3" parent="Testers/PerPointImpulseTimer/PointTrackers" instance=ExtResource("8_ehkex")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.8, 1.02, -0.8000002)
skeleton = NodePath("../..")
metadata/point = 4160
[node name="PointTracker4" parent="Testers/PerPointImpulseTimer/PointTrackers" instance=ExtResource("8_ehkex")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.8, 1.02, -0.8000002)
skeleton = NodePath("../..")
metadata/point = 4224
[node name="Cloth" parent="Testers/PerPointImpulseTimer" instance=ExtResource("3_6uqi0")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
[node name="StaticBody3D" type="StaticBody3D" parent="Testers/PerPointImpulseTimer"]
transform = Transform3D(0.851651, 0.309976, 0.422618, -0.34202, 0.939693, 0, -0.397131, -0.144544, 0.906308, 0, 0, 0)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/PerPointImpulseTimer/StaticBody3D"]
mesh = SubResource("BoxMesh_ppyta")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/PerPointImpulseTimer/StaticBody3D"]
shape = SubResource("BoxShape3D_ykrsh")
[node name="StaticBody3D2" type="StaticBody3D" parent="Testers/PerPointImpulseTimer"]
transform = Transform3D(0.212913, 0.0774939, 0.105655, -0.085505, 0.234923, -3.0889e-10, -0.0992828, -0.036136, 0.226577, 0.193735, 0.587308, -0.09034)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/PerPointImpulseTimer/StaticBody3D2"]
mesh = SubResource("BoxMesh_ppyta")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/PerPointImpulseTimer/StaticBody3D2"]
shape = SubResource("BoxShape3D_ykrsh")
[node name="Timer" type="Timer" parent="Testers/PerPointImpulseTimer"]
wait_time = 1.5
autostart = true
[node name="PinnedPoints" type="Node3D" parent="Testers"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -12)
[node name="Pin" parent="Testers/PinnedPoints" instance=ExtResource("8_ehkex")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.8, 1.02, 0.8)
[node name="Pin2" parent="Testers/PinnedPoints" instance=ExtResource("8_ehkex")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.8000001, 1.02, 0.8000002)
[node name="Pin3" parent="Testers/PinnedPoints" instance=ExtResource("8_ehkex")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.8, 1.02, -0.8000002)
[node name="Pin4" parent="Testers/PinnedPoints" instance=ExtResource("8_ehkex")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.8000001, 1.02, -0.8000002)
[node name="PinnedCloth" parent="Testers/PinnedPoints" instance=ExtResource("3_6uqi0")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
pinned_points = [0, 4160, 64, 4224]
attachments/0/point_index = 0
attachments/0/spatial_attachment_path = NodePath("")
attachments/0/offset = Vector3(0, 0, 0)
attachments/1/point_index = 4160
attachments/1/spatial_attachment_path = NodePath("")
attachments/1/offset = Vector3(0, 0, 0)
attachments/2/point_index = 64
attachments/2/spatial_attachment_path = NodePath("")
attachments/2/offset = Vector3(0, 0, 0)
attachments/3/point_index = 4224
attachments/3/spatial_attachment_path = NodePath("")
attachments/3/offset = Vector3(0, 0, 0)
[node name="StaticBody3D" type="StaticBody3D" parent="Testers/PinnedPoints"]
transform = Transform3D(0.851651, 0.309976, 0.422618, -0.34202, 0.939693, 0, -0.397131, -0.144544, 0.906308, 0, 0, 0)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/PinnedPoints/StaticBody3D"]
mesh = SubResource("BoxMesh_ppyta")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/PinnedPoints/StaticBody3D"]
shape = SubResource("BoxShape3D_ykrsh")
[node name="StaticBody3D2" type="StaticBody3D" parent="Testers/PinnedPoints"]
transform = Transform3D(0.212913, 0.0774939, 0.105655, -0.085505, 0.234923, -3.0889e-10, -0.0992828, -0.036136, 0.226577, 0.193735, 0.587308, -0.09034)
[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/PinnedPoints/StaticBody3D2"]
mesh = SubResource("BoxMesh_ppyta")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Testers/PinnedPoints/StaticBody3D2"]
shape = SubResource("BoxShape3D_ykrsh")
[node name="TestName" type="Label" parent="."]
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -192.0
offset_top = -58.0
offset_right = 192.0
offset_bottom = -24.0
grow_horizontal = 2
grow_vertical = 0
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 5
theme_override_font_sizes/font_size = 24
horizontal_alignment = 1
[node name="Previous" type="Button" parent="."]
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 24.0
offset_top = -55.0
offset_right = 135.0
offset_bottom = -24.0
grow_vertical = 0
text = "« Previous"
[node name="Next" type="Button" parent="."]
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -107.0
offset_top = -55.0
offset_right = -24.0
offset_bottom = -24.0
grow_horizontal = 0
grow_vertical = 0
text = "Next »"
[node name="Help" type="Label" parent="."]
offset_left = 24.0
offset_top = 24.0
offset_right = 312.0
offset_bottom = 125.0
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 3
text = "R: Reset Soft Body Physics Simulation
C: Place Cloth at Cursor Position
V: Place Light Box at Cursor Position
B: Place Heavy Box at Cursor Position"
[connection signal="timeout" from="Testers/CentralImpulseTimer/Timer" to="." method="_on_central_impulse_timer_timeout"]
[connection signal="timeout" from="Testers/PerPointImpulseTimer/Timer" to="." method="_on_per_point_impulse_timer_timeout"]
[connection signal="pressed" from="Previous" to="." method="_on_previous_pressed"]
[connection signal="pressed" from="Next" to="." method="_on_next_pressed"]

View File

@@ -0,0 +1,213 @@
extends WorldEnvironment
const ROT_SPEED: float = 0.003
const ZOOM_SPEED: float = 0.125
const MAIN_BUTTONS: int = MOUSE_BUTTON_MASK_LEFT | MOUSE_BUTTON_MASK_RIGHT | MOUSE_BUTTON_MASK_MIDDLE
## The maximum number of additional cloths and boxes that can be spawned at a given time
## (for performance reasons).
const MAX_ADDITIONAL_ITEMS: int = 10
const Cloth: PackedScene = preload("res://cloth.tscn")
const RigidBoxLight: PackedScene = preload("res://rigid_box_light.tscn")
const RigidBoxHeavy: PackedScene = preload("res://rigid_box_heavy.tscn")
const Box: PackedScene = preload("res://box.tscn")
const Sphere: PackedScene = preload("res://sphere.tscn")
var tester_index: int = 0
var rot_x: float = deg_to_rad(-22.5) # This must be kept in sync with RotationX.
var rot_y: float = deg_to_rad(90.0) # This must be kept in sync with CameraHolder.
var zoom: float = 1.5
var base_height := int(ProjectSettings.get_setting("display/window/size/viewport_height"))
var additional_items: Array[Node3D] = []
@onready var testers: Node3D = $Testers
@onready var camera_holder: Node3D = $CameraHolder # Has a position and rotates around Y.
@onready var rotation_x: Node3D = $CameraHolder/RotationX
@onready var camera: Camera3D = $CameraHolder/RotationX/Camera3D
@onready var nodes_to_reset: Array[SoftBody3D] = [
$Testers/ClothPhysics/Cloth,
$Testers/SoftBoxes/Box,
$Testers/SoftBoxes/Box2,
$Testers/SoftSpheres/Sphere,
$Testers/SoftSpheres/Sphere2,
$Testers/CentralImpulseTimer/Cloth,
$Testers/PerPointImpulseTimer/Cloth,
$Testers/CentralForceWind/Cloth,
$Testers/PinnedPoints/PinnedCloth,
]
@onready var nodes_to_reset_types: Array[PackedScene] = [
Cloth,
Box,
Box,
Sphere,
Sphere,
Cloth,
Cloth,
Cloth,
Cloth,
]
@onready var nodes_to_reset_global_positions: PackedVector3Array
func _ready() -> void:
for node: Node3D in nodes_to_reset:
nodes_to_reset_global_positions.push_back(node.global_position)
camera_holder.transform.basis = Basis.from_euler(Vector3(0, rot_y, 0))
rotation_x.transform.basis = Basis.from_euler(Vector3(rot_x, 0, 0))
update_gui()
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed(&"ui_left"):
_on_previous_pressed()
if event.is_action_pressed(&"ui_right"):
_on_next_pressed()
if event.is_action_pressed(&"reset_physics_simulation"):
# Remove all additional (player-spawned) items.
for additional_item in additional_items:
additional_item.queue_free()
additional_items.clear()
# Reset everything to its base state by removing existing nodes and reinstancing new scenes to replace them.
for idx in nodes_to_reset.size():
var previous_name: String = nodes_to_reset[idx].name
var previous_parent: Node = nodes_to_reset[idx].get_parent()
nodes_to_reset[idx].queue_free()
nodes_to_reset[idx] = nodes_to_reset_types[idx].instantiate()
previous_parent.add_child(nodes_to_reset[idx])
nodes_to_reset[idx].name = previous_name
nodes_to_reset[idx].global_position = nodes_to_reset_global_positions[idx]
if "PinnedCloth" in nodes_to_reset[idx].name:
# Pin the four corners of the cloth.
# These vertex IDs are valid with the PlaneMesh subdivision level set to 63 on both axes.
for point: int in [0, 64, 4160, 4224]:
nodes_to_reset[idx].set_point_pinned(point, true)
if (
event.is_action_pressed(&"place_cloth") or
event.is_action_pressed(&"place_light_box") or
event.is_action_pressed(&"place_heavy_box")
):
# Place a new item and track it in an additional items array, so we can limit
# the number of player-spawned items present in the scene at a given time.
var origin: Vector3 = camera.global_position
var target: Vector3 = camera.project_position(get_viewport().get_mouse_position(), 100)
var query := PhysicsRayQueryParameters3D.create(origin, target)
# Ignore layer 2 which contains invisible walls.
query.collision_mask = 1
var result := camera.get_world_3d().direct_space_state.intersect_ray(query)
if not result.is_empty():
if additional_items.size() >= MAX_ADDITIONAL_ITEMS:
additional_items.pop_front().queue_free()
var node: Node3D
if event.is_action_pressed(&"place_cloth"):
node = Cloth.instantiate()
# Make user-placed cloth translucent to distinguish from the scene's own cloths.
node.transparency = 0.35
elif event.is_action_pressed(&"place_light_box"):
node = RigidBoxLight.instantiate()
else:
node = RigidBoxHeavy.instantiate()
node.position = result["position"] + Vector3(0, 0.5, 0)
add_child(node)
additional_items.push_back(node)
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
zoom -= ZOOM_SPEED
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
zoom += ZOOM_SPEED
zoom = clampf(zoom, 1.5, 4)
if event is InputEventMouseMotion and event.button_mask & MAIN_BUTTONS:
# Compensate motion speed to be resolution-independent (based on the window height).
var relative_motion: Vector2 = event.relative * DisplayServer.window_get_size().y / base_height
rot_y -= relative_motion.x * ROT_SPEED
rot_x -= relative_motion.y * ROT_SPEED
rot_x = clampf(rot_x, deg_to_rad(-90), 0)
camera_holder.transform.basis = Basis.from_euler(Vector3(0, rot_y, 0))
rotation_x.transform.basis = Basis.from_euler(Vector3(rot_x, 0, 0))
func _process(delta: float) -> void:
var current_tester: Node3D = testers.get_child(tester_index)
# This code assumes CameraHolder's X and Y coordinates are already correct.
var current_position_z: float = camera_holder.global_transform.origin.z
var target_position_z: float = current_tester.global_transform.origin.z
camera_holder.global_transform.origin.z = lerpf(current_position_z, target_position_z, 3 * delta)
camera.position.z = lerpf(camera.position.z, zoom, 10 * delta)
func _physics_process(delta: float) -> void:
# Strong sideways wind force, which pushes the cloth while airborne and
# makes it wave around once it has landed.
const WIND_FORCE: float = 2_450_000.0
for node in $Testers/CentralForceWind.get_children():
if node is SoftBody3D:
node.apply_central_force(Vector3(1.0, 0.0, 0.0) * WIND_FORCE * delta)
# Use a loop to determine the right node to follow
# (we need to do this as the node will have a different name after resetting).
for cloth in $Testers/PerPointImpulseTimer.get_children():
if cloth is SoftBody3D:
for node in $Testers/PerPointImpulseTimer/PointTrackers.get_children():
# Make the point trackers follow specific points stored in node metadata.
# This can be used to make any node type (such as particles, a mesh, or even a rigid body)
# follow a point. It's also possible to average the position of several points
# to make an object follow an edge or a face in a best-effort manner.
#
# We slightly shift the tracker upwards to account for the cloth's thickness
# (specified in the cloth material's Grow property).
node.global_position = cloth.get_point_transform(node.get_meta(&"point")) + Vector3(0.0, 0.01, 0.0)
func _on_previous_pressed() -> void:
tester_index = max(0, tester_index - 1)
update_gui()
func _on_next_pressed() -> void:
tester_index = min(tester_index + 1, testers.get_child_count() - 1)
update_gui()
func update_gui() -> void:
$TestName.text = str(testers.get_child(tester_index).name).capitalize()
$Previous.disabled = tester_index == 0
$Next.disabled = tester_index == testers.get_child_count() - 1
func _on_central_impulse_timer_timeout() -> void:
# When using `apply_central_impulse()` instead of `apply_impulse()`,
# we have to use a much larger value to get significant movement as the impulse is distributed
# across all points.
const INTENSITY: float = 8000.0
for node in $Testers/CentralImpulseTimer.get_children():
if node is SoftBody3D:
var random_unit_vector := Vector3(randf_range(-1.0, 1.0), randf_range(-0.0, 1.0), randf_range(-1.0, 1.0)).normalized()
node.apply_central_impulse(random_unit_vector * INTENSITY)
func _on_per_point_impulse_timer_timeout() -> void:
const INTENSITY: float = 600.0
for node in $Testers/PerPointImpulseTimer.get_children():
if node is SoftBody3D:
# Apply impulse on the four corners of the cloth.
# These vertex IDs are valid with the PlaneMesh subdivision level set to 63 on both axes.
for point: int in [0, 64, 4160, 4224]:
var random_unit_vector := Vector3(randf_range(-1.0, 1.0), randf_range(-0.0, 1.0), randf_range(-1.0, 1.0)).normalized()
node.apply_impulse(point, random_unit_vector * INTENSITY)

View File

@@ -0,0 +1 @@
uid://bi2o4fooys1dc

View File

@@ -0,0 +1,7 @@
# License for `checker.png`
Copyright (c) 2020 Kenney
Licensed under CC0 1.0 Universal.
Downloaded from https://kenney.nl/assets/prototype-textures

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://chjqieyps5n5r"
path="res://.godot/imported/checker.png-d334a8ae07de292fd4162f184b9dd7bc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://textures/checker.png"
dest_files=["res://.godot/imported/checker.png-d334a8ae07de292fd4162f184b9dd7bc.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://clyte8pu0nwom"
path="res://.godot/imported/fabric_pattern_07_arm_1k.jpg-b8047b6218bab9daca6e63f423a1a56e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://textures/polyhaven/fabric_pattern_07_arm_1k.jpg"
dest_files=["res://.godot/imported/fabric_pattern_07_arm_1k.jpg-b8047b6218bab9daca6e63f423a1a56e.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://csp3kbg6r4rwd"
path="res://.godot/imported/fabric_pattern_07_col_1_1k.jpg-cf99f064f36aed618123054eda9ac220.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://textures/polyhaven/fabric_pattern_07_col_1_1k.jpg"
dest_files=["res://.godot/imported/fabric_pattern_07_col_1_1k.jpg-cf99f064f36aed618123054eda9ac220.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://hh6i17naw1qx"
path="res://.godot/imported/fabric_pattern_07_nor_gl_1k.jpg-34d7a8b5cb474713d82a29b35675d447.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://textures/polyhaven/fabric_pattern_07_nor_gl_1k.jpg"
dest_files=["res://.godot/imported/fabric_pattern_07_nor_gl_1k.jpg-34d7a8b5cb474713d82a29b35675d447.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=1
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=1
roughness/src_normal="res://textures/polyhaven/fabric_pattern_07_nor_gl_1k.jpg"
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0