world can be edited

This commit is contained in:
2026-02-14 02:31:25 +01:00
parent bc58e13a61
commit f4438c7ad0
12 changed files with 142 additions and 42 deletions
+58 -8
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)
+24 -9
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:
+19 -2
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
-1
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")
+2 -3
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