From f4438c7ad0736e6c8090d46a5618a160f80f294c Mon Sep 17 00:00:00 2001 From: Guillaume Vern Date: Sat, 14 Feb 2026 02:31:25 +0100 Subject: [PATCH] world can be edited --- NoclipCamera.gd | 19 +++--- SurfaceNetsWorld/chunk.gd | 66 +++++++++++++++++--- SurfaceNetsWorld/compute_samples.gd | 33 +++++++--- SurfaceNetsWorld/compute_surface_points.glsl | 21 ++++++- SurfaceNetsWorld/generate_mesh.tres | 1 - SurfaceNetsWorld/smooth_world.tscn | 5 +- chunk.tscn | 8 +-- player.tscn | 11 ++-- project.godot | 4 ++ signals_service.gd | 13 ++++ signals_service.gd.uid | 1 + world.tscn | 2 +- 12 files changed, 142 insertions(+), 42 deletions(-) create mode 100644 signals_service.gd create mode 100644 signals_service.gd.uid diff --git a/NoclipCamera.gd b/NoclipCamera.gd index 678e223..3ccab4f 100644 --- a/NoclipCamera.gd +++ b/NoclipCamera.gd @@ -18,7 +18,6 @@ var delta_speed = 0 # var a = 2 # var b = "text" - # Called when the node enters the scene tree for the first time. func _ready(): Input.mouse_mode = Input.MOUSE_MODE_CAPTURED @@ -50,22 +49,22 @@ func _input(event): camera.rotate_object_local(Vector3(-1, 0, 0), rot_y) # then rotate in X # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(_delta): - delta_speed = PLAYER_SPEED * _delta * 500 + delta_speed = PLAYER_SPEED * _delta * 500000 if NOCLIP: process_noclip() else: process_physics_movements() - #for hit_pos in ray_hit_positions: - #DebugDraw3D.draw_sphere(hit_pos) + for hit_pos in ray_hit_positions: + DebugDraw3D.draw_sphere(hit_pos) func process_physics_movements(): if Input.is_action_pressed("MoveCamUp"): - move_y = 1 + move_y = 5 if Input.is_action_pressed("MoveCamDown"): - pass + move_y = -1 if Input.is_action_pressed("MoveCamLeft"): move_x = -1 if Input.is_action_pressed("MoveCamRight"): @@ -102,10 +101,12 @@ func raycast_from_center(): var raycast_result = space.intersect_ray(ray_query) if len(raycast_result) > 0: - var hit_pos = raycast_result.position - (raycast_result.normal * 0.5) - hit_pos = Vector3i(round(hit_pos.x), round(hit_pos.y), round(hit_pos.z)) + var hit_pos: Vector3 = raycast_result.position - (raycast_result.normal * 0.5) + hit_pos = Vector3(hit_pos.x, hit_pos.y, hit_pos.z) ray_hit_positions.append(hit_pos) - world.SetBlockAtPosition(hit_pos, Block.BlockType.Air) + print("ray hit at pos:", hit_pos) + signals_service.add_matter.emit(hit_pos, 0.2) + #world.SetBlockAtPosition(hit_pos, Block.BlockType.Air) diff --git a/SurfaceNetsWorld/chunk.gd b/SurfaceNetsWorld/chunk.gd index f183bb0..8ece6ae 100644 --- a/SurfaceNetsWorld/chunk.gd +++ b/SurfaceNetsWorld/chunk.gd @@ -4,7 +4,7 @@ const CENTER := Vector3.ZERO var generate_mesh_shader = preload("res://SurfaceNetsWorld/generate_mesh.tres") -@export var regenerate_mesh = false +var regenerate_mesh = true @export var chunk_size = 16 @@ -12,7 +12,7 @@ var generate_mesh_shader = preload("res://SurfaceNetsWorld/generate_mesh.tres") @export var show_surface_points = false @export var show_surface = true -var mesh +var mesh: ArrayMesh var color = Color.RED @@ -21,25 +21,33 @@ var material = ShaderMaterial.new() var gpu_sdf +var staticBody: StaticBody3D = StaticBody3D.new() +var collisionShape3D: CollisionShape3D = CollisionShape3D.new() + + func generate_chunk(pgpu_sdf) -> void: + signals_service.regenerate_mesh_sig.connect(_on_regenerate_mesh_sig) + signals_service.add_matter.connect(_on_player_add_matter) gpu_sdf = pgpu_sdf material.shader = generate_mesh_shader meshinstance.material_override = material add_child(meshinstance) - meshinstance.mesh = gpu_sdf.compute_mesh(chunk_size, threshold, self.position) func _input(event): if event is InputEventKey and event.is_action_released("RegenerateMesh"): - if regenerate_mesh: - regenerate_mesh = false - else: regenerate_mesh = true func _process(_delta: float) -> void: - if show_surface && regenerate_mesh: + if regenerate_mesh: regenerate_mesh = false clear() - meshinstance.mesh = gpu_sdf.compute_mesh(chunk_size, threshold, self.position) + var array_mesh = gpu_sdf.compute_mesh(chunk_size, threshold, self.position) + var trimesh_collision = array_mesh.create_trimesh_shape() + collisionShape3D.set_shape(trimesh_collision) + staticBody = StaticBody3D.new() + staticBody.add_child(collisionShape3D) + add_child(staticBody) + meshinstance.mesh = array_mesh if show_surface_points: var idx = 0 var text_id = 0 @@ -56,8 +64,50 @@ func _process(_delta: float) -> void: func clear(): + if staticBody.get_children().has(collisionShape3D): + staticBody.remove_child(collisionShape3D) mesh = ArrayMesh.new() func get_index_from_coords(coords: Vector3i): return coords.x + coords.y * chunk_size + coords.z * chunk_size * chunk_size + +var chunks_to_regenerate = [ + Vector3i(0,0,0), + Vector3i(0,1,0), + Vector3i(0,0,1), + Vector3i(0,1,1), + Vector3i(0,-1,0), + Vector3i(0,0,-1), + Vector3i(0,-1,-1), + Vector3i(0,-1,1), + Vector3i(0,1,-1), + + Vector3i(1,0,0), + Vector3i(1,0,1), + Vector3i(-1,0,0), + Vector3i(-1,0,-1), + Vector3i(-1,0,1), + Vector3i(1,0,-1), + + Vector3i(1,1,0), + Vector3i(-1,-1,0), + Vector3i(-1,1,0), + Vector3i(1,-1,0), + Vector3i(1,1,1), + Vector3i(-1,-1,-1), +] + +func _on_regenerate_mesh_sig(pposition: Vector3i): + if Vector3i(self.global_position) == pposition: + regenerate_mesh = true + +func _on_player_add_matter(pposition: Vector3, size: float) -> void: + var intpposition = Vector3i(pposition.x - int(pposition.x) % chunk_size, pposition.y - int(pposition.y) % chunk_size, pposition.z - int(pposition.z) % chunk_size) + var intposition = Vector3i(self.global_position) + if intpposition.x == intposition.x && intpposition.y == intposition.y && intpposition.z == intposition.z: + for chunk_pos_to_regenerate in chunks_to_regenerate: + chunk_pos_to_regenerate = Vector3i(intpposition.x + chunk_pos_to_regenerate.x * chunk_size, intpposition.y + chunk_pos_to_regenerate.y * chunk_size, intpposition.z + chunk_pos_to_regenerate.z * chunk_size) + signals_service.regenerate_mesh_sig.emit(chunk_pos_to_regenerate) + + diff --git a/SurfaceNetsWorld/compute_samples.gd b/SurfaceNetsWorld/compute_samples.gd index 042e2d3..a8c7188 100644 --- a/SurfaceNetsWorld/compute_samples.gd +++ b/SurfaceNetsWorld/compute_samples.gd @@ -9,6 +9,7 @@ var shader_pass2: RID var start_time := Time.get_ticks_msec() / 1000.0 var color = Color.CORAL @export var iout_surface_points = [] +var player_edits = PackedFloat32Array() var pipeline1 var pipeline2 @@ -20,9 +21,11 @@ var idx_buffer var counter_buffer var chunk_position_buffer var params_buffer +var player_edits_buffer + func create_device(world_size: int): - + signals_service.add_matter.connect(_on_player_add_matter) rd = RenderingServer.create_local_rendering_device() # 1. Load Shaders @@ -36,7 +39,7 @@ func create_device(world_size: int): pipeline2 = rd.compute_pipeline_create(shader_pass2) # 3. Pre-allocate Buffers (assuming world_size is constant) - # add 1 to look at adjacent voxels for chunk boundaries + # add 2 to look at adjacent voxels for chunk boundaries var total = (world_size + 2) ** 3 # We create them once with empty/zero data of the correct size @@ -48,6 +51,7 @@ func create_device(world_size: int): counter_buffer = rd.storage_buffer_create(4) chunk_position_buffer = rd.storage_buffer_create(16) # vec4 params_buffer = rd.uniform_buffer_create(16) # world_size, threshold, time, etc + player_edits_buffer = rd.storage_buffer_create(256) func compute_mesh(world_size: int, threshold: float, chunk_pos: Vector3) -> ArrayMesh: world_size += 2 @@ -70,6 +74,11 @@ func compute_mesh(world_size: int, threshold: float, chunk_pos: Vector3) -> Arra var chunk_position_bytes := chunk_position_peer.to_byte_array() rd.buffer_update(chunk_position_buffer, 0, chunk_position_bytes.size(), chunk_position_bytes) + var player_edits_bytes = player_edits.to_byte_array() + if player_edits_bytes.size() > 0: + player_edits_buffer = rd.storage_buffer_create(player_edits_bytes.size()) + rd.buffer_update(player_edits_buffer, 0, player_edits_bytes.size(), player_edits_bytes) + var u_time := Time.get_ticks_msec() / 1000.0 - start_time var peer := StreamPeerBuffer.new() peer.put_32(world_size) @@ -116,20 +125,23 @@ func compute_mesh(world_size: int, threshold: float, chunk_pos: Vector3) -> Arra chunk_position_uniform.binding = 7 chunk_position_uniform.add_id(chunk_position_buffer) - + var player_edits_uniform := RDUniform.new() + player_edits_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER + player_edits_uniform.binding = 8 + player_edits_uniform.add_id(player_edits_buffer) var uniform_params := RDUniform.new() uniform_params.uniform_type = RenderingDevice.UNIFORM_TYPE_UNIFORM_BUFFER uniform_params.binding = 1 uniform_params.add_id(params_buffer) - var uniform_set1 := rd.uniform_set_create([uniform_buf, uniform_params, surface_uniform_buf, normal_uniform, uv_uniform, idx_uniform, counter_uniform, chunk_position_uniform], shader_pass1, 0) # the last parameter (the 0) needs to match the "set" in our shader file + var uniform_set1 := rd.uniform_set_create([uniform_buf, uniform_params, surface_uniform_buf, normal_uniform, uv_uniform, idx_uniform, counter_uniform, chunk_position_uniform, player_edits_uniform], shader_pass1, 0) # the last parameter (the 0) needs to match the "set" in our shader file var uniform_set2 := rd.uniform_set_create([uniform_buf, uniform_params, surface_uniform_buf, normal_uniform, uv_uniform, idx_uniform, counter_uniform], shader_pass2, 0) var dispatch_count = int(ceil(world_size / 4.0)) # 1. Dispatch PASS 1 (Calculate Points) - var pipeline1 := rd.compute_pipeline_create(shader_pass1) # Points only + pipeline1 = rd.compute_pipeline_create(shader_pass1) # Points only var compute_list = rd.compute_list_begin() rd.compute_list_bind_compute_pipeline(compute_list, pipeline1) rd.compute_list_bind_uniform_set(compute_list, uniform_set1, 0) @@ -137,7 +149,7 @@ func compute_mesh(world_size: int, threshold: float, chunk_pos: Vector3) -> Arra rd.compute_list_end() # 2. Dispatch PASS 2 (Generate Indices) - var pipeline2 := rd.compute_pipeline_create(shader_pass2) # Indices only + pipeline2 = rd.compute_pipeline_create(shader_pass2) # Indices only compute_list = rd.compute_list_begin() rd.compute_list_bind_compute_pipeline(compute_list, pipeline2) rd.compute_list_bind_uniform_set(compute_list, uniform_set2, 0) @@ -159,9 +171,6 @@ func compute_mesh(world_size: int, threshold: float, chunk_pos: Vector3) -> Arra var arrays = [] arrays.resize(Mesh.ARRAY_MAX) - # We need to reshape the flat float array into Vector3s - var verts := PackedVector3Array() - var normals := PackedVector3Array() # Instead of blindly appending every voxel, we check if the voxel was "active" # Or, even better, map the original voxel indices to new packed indices var active_map = {} @@ -234,6 +243,12 @@ func build_surface_dict(world_size: int, flat_buffer: PackedFloat32Array) -> Dic return dict +func _on_player_add_matter(pposition: Vector3, size: float) -> void: + player_edits.append(pposition.x) + player_edits.append(pposition.y) + player_edits.append(pposition.z) + player_edits.append(size) + func _exit_tree(): # If the rendering device wasn't initialized, we have nothing to free if not rd: diff --git a/SurfaceNetsWorld/compute_surface_points.glsl b/SurfaceNetsWorld/compute_surface_points.glsl index ba2a243..7a8629e 100644 --- a/SurfaceNetsWorld/compute_surface_points.glsl +++ b/SurfaceNetsWorld/compute_surface_points.glsl @@ -39,6 +39,11 @@ layout(set = 0, binding = 7, std430) buffer ChunkPos { float position_array[]; } chunk; +layout(set = 0, binding = 8, std430) buffer PlayerEdits { + float position_size_array[]; +} edits; + + uint index3(uint x, uint y, uint z) { return x + y * params.world_size + z * params.world_size * params.world_size; } @@ -79,6 +84,10 @@ vec4 taylorInvSqrt(vec4 r) return 1.79284291400159 - 0.85373472095314 * r; } +float sdsphere(vec3 p, float radius) { + return length(p) - radius; +} + float snoise(vec3 v) { const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; @@ -185,13 +194,21 @@ const ivec3 CORNERS[8] = ivec3[]( ); float get_noise_at(vec3 p) { - float chunk_x = chunk.position_array[0] * -.5; float chunk_y = chunk.position_array[1] * -.5; float chunk_z = chunk.position_array[2] * -.5; p = p - vec3(chunk_x, chunk_y, chunk_z); p = p / 20.0; - return snoise(p); + + float return_value = 10000.0; + + for (int i = 0; i < edits.position_size_array.length(); i += 4){ + return_value = min(return_value, sdsphere(p + vec3(edits.position_size_array[i] * -0.025, edits.position_size_array[i + 1] * -0.025, edits.position_size_array[i + 2] * -0.025), edits.position_size_array[i + 3])); + } + + return_value = min(return_value, snoise(p)); + + return return_value; } // Calculate normal using central difference diff --git a/SurfaceNetsWorld/generate_mesh.tres b/SurfaceNetsWorld/generate_mesh.tres index f6047a2..80663c1 100644 --- a/SurfaceNetsWorld/generate_mesh.tres +++ b/SurfaceNetsWorld/generate_mesh.tres @@ -4,7 +4,6 @@ constant = Color(0.30202293, 0.6060653, 0.9109474, 1) [resource] -modes/cull = 2 nodes/vertex/0/position = Vector2(360, 220) nodes/fragment/0/position = Vector2(660, 140) nodes/fragment/2/node = SubResource("VisualShaderNodeColorConstant_sxi40") diff --git a/SurfaceNetsWorld/smooth_world.tscn b/SurfaceNetsWorld/smooth_world.tscn index b9371ab..2b7a250 100644 --- a/SurfaceNetsWorld/smooth_world.tscn +++ b/SurfaceNetsWorld/smooth_world.tscn @@ -4,6 +4,5 @@ [node name="SmoothWorld" type="Node3D" unique_id=113243680] script = ExtResource("1_4h467") -chunk_size = 64 -world_size = 4 -threshold = 0.045 +world_size = 8 +threshold = 0.46 diff --git a/chunk.tscn b/chunk.tscn index df767c9..a4ad22d 100644 --- a/chunk.tscn +++ b/chunk.tscn @@ -1,13 +1,13 @@ -[gd_scene load_steps=3 format=3 uid="uid://da568t2u1olhb"] +[gd_scene format=3 uid="uid://da568t2u1olhb"] [ext_resource type="Material" uid="uid://d2eg0eaiyrt6p" path="res://grass/grass_material.tres" id="1_eat54"] [ext_resource type="Script" uid="uid://civdn1k6g4dhy" path="res://chunk.gd" id="1_kdh3y"] -[node name="Chunk" type="MeshInstance3D"] +[node name="Chunk" type="MeshInstance3D" unique_id=1056588591] material_override = ExtResource("1_eat54") script = ExtResource("1_kdh3y") backface_culling = true -[node name="StaticBody3D" type="StaticBody3D" parent="."] +[node name="StaticBody3D" type="StaticBody3D" parent="." unique_id=1003201572] -[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"] +[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" unique_id=232522921] diff --git a/player.tscn b/player.tscn index 8f8bbbd..f8c89ab 100644 --- a/player.tscn +++ b/player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=5 format=3 uid="uid://chbs3naovk63w"] +[gd_scene format=3 uid="uid://chbs3naovk63w"] [ext_resource type="Script" uid="uid://c1jlypyykqvfl" path="res://NoclipCamera.gd" id="1_4flbx"] @@ -8,17 +8,18 @@ [sub_resource type="SphereShape3D" id="SphereShape3D_tlwt5"] -[node name="Player" type="RigidBody3D"] +[node name="Player" type="RigidBody3D" unique_id=50574317] physics_material_override = SubResource("PhysicsMaterial_fj7yv") gravity_scale = 0.0 lock_rotation = true linear_damp = 1.0 script = ExtResource("1_4flbx") -[node name="PlayerCamera" type="Camera3D" parent="."] +[node name="PlayerCamera" type="Camera3D" parent="." unique_id=703597908] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.1223998, 0) -[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1204761983] mesh = SubResource("SphereMesh_fj7yv") -[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=703714189] shape = SubResource("SphereShape3D_tlwt5") diff --git a/project.godot b/project.godot index fa8e03d..533ad08 100644 --- a/project.godot +++ b/project.godot @@ -19,6 +19,10 @@ run/main_scene="res://world.tscn" config/features=PackedStringArray("4.6") config/icon="res://icon.png" +[autoload] + +signals_service="*uid://c7m0o52cbtdb4" + [debug] settings/stdout/print_fps=true diff --git a/signals_service.gd b/signals_service.gd new file mode 100644 index 0000000..2f5e697 --- /dev/null +++ b/signals_service.gd @@ -0,0 +1,13 @@ +extends Node + +signal add_matter(position: Vector3, size: float) +signal regenerate_mesh_sig(position: Vector3i) + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass diff --git a/signals_service.gd.uid b/signals_service.gd.uid new file mode 100644 index 0000000..cd907f2 --- /dev/null +++ b/signals_service.gd.uid @@ -0,0 +1 @@ +uid://c7m0o52cbtdb4 diff --git a/world.tscn b/world.tscn index e007fad..06ded1a 100644 --- a/world.tscn +++ b/world.tscn @@ -29,7 +29,7 @@ shadow_enabled = true [node name="Player" parent="." unique_id=1950519856 instance=ExtResource("3_036b0")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.182447, 0.6673207, 0.0726881) +gravity_scale = 2.0 PLAYER_SPEED = 0.035 -NOCLIP = true [connection signal="change_world" from="." to="." method="_on_change_world"]