world can be edited

This commit is contained in:
Guillaume Vern 2026-02-14 02:31:25 +01:00
parent bc58e13a61
commit f4438c7ad0
12 changed files with 142 additions and 42 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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]

View File

@ -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")

View File

@ -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

13
signals_service.gd Normal file
View File

@ -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

1
signals_service.gd.uid Normal file
View File

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

View File

@ -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"]