From e9e9522bd6f82679225040b4403deb6ea154cd1f Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Tue, 10 May 2016 02:05:58 +0200 Subject: [PATCH] Drastic code reduction, now handled by the C++ module --- project/generator.gd | 160 ++++++++++++++++ project/grid.gd | 76 ++++++++ project/launcher.gd | 15 ++ project/launcher.tscn | 22 +++ project/new_scene.tscn | 26 ++- project/voxel_map.gd | 420 ++--------------------------------------- 6 files changed, 312 insertions(+), 407 deletions(-) create mode 100644 project/generator.gd create mode 100644 project/grid.gd create mode 100644 project/launcher.gd create mode 100644 project/launcher.tscn diff --git a/project/generator.gd b/project/generator.gd new file mode 100644 index 0000000..f58fcc9 --- /dev/null +++ b/project/generator.gd @@ -0,0 +1,160 @@ + +class _Base: + var _noise = OsnNoise.new() + func _init(): + _noise.set_seed(131183) + + +class Flat extends _Base: + func generate(voxels, offset): + if offset.y <= 0: + voxels.fill(1) + +class Grid extends _Base: + var step = 4 + + func generate(voxels, offset): + + for z in range(0, voxels.get_size_z()): + for x in range(0, voxels.get_size_x()): + for y in range(0, voxels.get_size_y()): + + var v = 0 + + if (y/step)%2 == 0: + if (x/step)%2 == 0: + if (z/step)%2 == 0: + v = 1 + else: + if (z/step)%2 != 0: + v = 1 + else: + if (x/step)%2 == 0: + if (z/step)%2 != 0: + v = 1 + else: + if (z/step)%2 == 0: + v = 1 + + voxels.set_voxel(v, x,y,z) + + +class Heightmap extends _Base: + func generate(voxels, offset): + var ox = offset.x + var oy = offset.y + var oz = offset.z + var empty = true + var ns1 = 0.01 + var ns2 = 0.05 + + var dirt = 1 + if oy < 0: + dirt = 2 + + var bs = voxels.get_size_x() + + var noise1 = OsnFractalNoise.new() + noise1.set_source_noise(_noise) + noise1.set_period(128) + noise1.set_octaves(4) + + for z in range(0, bs): + for x in range(0, bs): + + var h = 16.0 * noise1.get_noise_2d(ox+x, oz+z) - oy + + if h >= 0: + if h < bs: + empty = false + for y in range(0, h): + voxels.set_voxel(dirt, x,y,z) + #voxels[z][y][x] = dirt + for y in range(h, bs): + voxels.set_voxel(0, x,y,z) + #voxels[z][y][x] = air + # if oy == -BLOCK_SIZE: + # voxels[z][bs-1][x] = 0 + # if oy >= 0 and randf() < 0.2: + # voxels[z][h][x] = 2 + # if randf() < 0.01: + # var th = h+1+randi()%8 + # if th > bs: + # th = bs + # for y in range(h, th): + # voxels[z][y][x] = 3 + else: + empty = false + for y in range(0, bs): + voxels.set_voxel(dirt, x,y,z) + else: + for y in range(0, bs): + voxels.set_voxel(0, x,y,z) + + return empty + + +class Volume extends _Base: + func generate(voxels, offset): + var ox = offset.x + var oy = offset.y + var oz = offset.z + var empty = true + var bs = voxels.get_size_x() + + var noise1 = OsnFractalNoise.new() + noise1.set_source_noise(_noise) + noise1.set_period(100) + noise1.set_octaves(4) + + var dirt = 1 + if oy < 0: + dirt = 2 + + for z in range(0, bs): + for x in range(0, bs): + for y in range(0, bs): + var gy = y+oy + var h = noise1.get_noise_3d(x+ox+2, gy, z+oz) + if h < 1-gy*0.01 - 1: + voxels.set_voxel(dirt, x, y, z) + empty = false + else: + if gy < 0: + voxels.set_voxel(4, x, y, z) + else: + voxels.set_voxel(0, x, y, z) + empty = false + + return empty + + +class Test extends _Base: + func generate(voxels, offset): + voxels.set_voxel(1, 1,1,1) + + voxels.set_voxel(1, 3,1,1) + voxels.set_voxel(1, 3,1,2) + + voxels.set_voxel(1, 5,1,1) + voxels.set_voxel(1, 5,1,2) + voxels.set_voxel(1, 5,2,1) + + voxels.set_voxel(1, 8,1,1) + voxels.set_voxel(1, 8,2,1) + voxels.set_voxel(1, 7,1,1) + + voxels.set_voxel(1, 11,1,1) + voxels.set_voxel(1, 11,2,1) + voxels.set_voxel(1, 10,1,1) + voxels.set_voxel(1, 10,1,2) + + for x in range(4,7): + for z in range(4,7): + voxels.set_voxel(1, x, 2, z) + voxels.set_voxel(1, x+5, 2, z) + voxels.set_voxel(1, 5,3,5) + voxels.set_voxel(1, 5,1,5) + + return false + diff --git a/project/grid.gd b/project/grid.gd new file mode 100644 index 0000000..7d4ed29 --- /dev/null +++ b/project/grid.gd @@ -0,0 +1,76 @@ + +extends MeshInstance + +var step = 16 + + +func _ready(): + + var st = SurfaceTool.new() + + st.begin(Mesh.PRIMITIVE_LINES) + + st.add_color(Color(0,0,0)) + + var r = 4 + var rv = 4 * step + + for i in range(-r, r): + for j in range(-r, r): + + var x = i * step + var y = j * step + + st.add_vertex(Vector3(x, -rv, y)) + st.add_vertex(Vector3(x, rv, y)) + + st.add_vertex(Vector3(x, y, -rv)) + st.add_vertex(Vector3(x, y, rv)) + + st.add_vertex(Vector3(-rv, x, y)) + st.add_vertex(Vector3(rv, x, y)) + + var mesh = st.commit() + set_mesh(mesh) + + + +#func _add_wireframe_cube(st, pos): +# +# st.add_vertex(pos) +# st.add_vertex(pos + Vector3(step, 0, 0)) +# +# st.add_vertex(pos + Vector3(step, 0, 0)) +# st.add_vertex(pos + Vector3(step, 0, step)) +# +# st.add_vertex(pos + Vector3(step, 0, step)) +# st.add_vertex(pos + Vector3(0, 0, step)) +# +# st.add_vertex(pos + Vector3(0, 0, step)) +# st.add_vertex(pos) +# +# +# st.add_vertex(pos + Vector3(0, step, 0)) +# st.add_vertex(pos + Vector3(step, step, 0)) +# +# st.add_vertex(pos + Vector3(step, step, 0)) +# st.add_vertex(pos + Vector3(step, step, step)) +# +# st.add_vertex(pos + Vector3(step, step, step)) +# st.add_vertex(pos + Vector3(0, step, step)) +# +# st.add_vertex(pos + Vector3(0, step, step)) +# st.add_vertex(pos + Vector3(0, step, 0)) +# +# +# st.add_vertex(pos) +# st.add_vertex(pos + Vector3(0, step, 0)) +# +# st.add_vertex(pos + Vector3(step, 0, 0)) +# st.add_vertex(pos + Vector3(step, step, 0)) +# +# st.add_vertex(pos + Vector3(step, 0, step)) +# st.add_vertex(pos + Vector3(step, step, step)) +# +# st.add_vertex(pos + Vector3(0, 0, step)) +# st.add_vertex(pos + Vector3(0, step, step)) diff --git a/project/launcher.gd b/project/launcher.gd new file mode 100644 index 0000000..9b78dac --- /dev/null +++ b/project/launcher.gd @@ -0,0 +1,15 @@ + +extends Node + +# member variables here, example: +# var a=2 +# var b="textvar" + +func _ready(): + pass + + + + +func _on_Node_pressed(): + get_tree().change_scene("res://new_scene.tscn") diff --git a/project/launcher.tscn b/project/launcher.tscn new file mode 100644 index 0000000..d519d9e --- /dev/null +++ b/project/launcher.tscn @@ -0,0 +1,22 @@ +[gd_scene load_steps=2 format=1] + +[ext_resource path="res://launcher.gd" type="Script" id=1] + +[node name="Node" type="Button"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 66.0 +margin/top = 116.0 +margin/right = 394.0 +margin/bottom = 156.0 +toggle_mode = false +text = "Start" +flat = false +script/script = ExtResource( 1 ) + +[connection signal="pressed" from="." to="." method="_on_Node_pressed"] + + diff --git a/project/new_scene.tscn b/project/new_scene.tscn index 20e5902..f327871 100644 --- a/project/new_scene.tscn +++ b/project/new_scene.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=7 format=1] +[gd_scene load_steps=8 format=1] [ext_resource path="res://debug_camera.gd" type="Script" id=1] [ext_resource path="res://voxel_map.gd" type="Script" id=2] [ext_resource path="res://terrain.png" type="Texture" id=3] +[ext_resource path="res://grid.gd" type="Script" id=4] [sub_resource type="FixedMaterial" id=1] @@ -214,7 +215,7 @@ phase_2/color = Color( 0, 0, 0, 1 ) phase_3/pos = 1.0 phase_3/color = Color( 0, 0, 0, 1 ) -[node name="VoxelMap" type="Node" parent="."] +[node name="VoxelTerrain" type="VoxelTerrain" parent="."] script/script = ExtResource( 2 ) solid_material = SubResource( 1 ) @@ -249,4 +250,25 @@ shadow/max_distance = 32.0 shadow/split_weight = 0.5 shadow/zoffset_scale = 2.0 +[node name="Grid" type="MeshInstance" parent="."] + +_import_transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 ) +layers = 1 +geometry/visible = true +geometry/material_override = null +geometry/cast_shadow = 1 +geometry/receive_shadows = true +geometry/range_begin = 0.0 +geometry/range_end = 0.0 +geometry/extra_cull_margin = 0.0 +geometry/billboard = false +geometry/billboard_y = false +geometry/depth_scale = false +geometry/visible_in_all_rooms = false +geometry/use_baked_light = false +geometry/baked_light_tex_id = 0 +mesh/mesh = null +mesh/skeleton = NodePath("..") +script/script = ExtResource( 4 ) + diff --git a/project/voxel_map.gd b/project/voxel_map.gd index dcffcc2..01eb083 100644 --- a/project/voxel_map.gd +++ b/project/voxel_map.gd @@ -1,426 +1,36 @@ -# Infinite terrain of voxels. -# Voxels are divided in blocks in all directions (like octants in Gridmap). -# It is a lot faster than Gridmap because geometry is merged in one mesh per block. -# Voxels are usually cubes, but they can of any shape (see voxel_type.gd). -# One thread is used to generate and bake geometry. - -# TODO Immerge blocks that are too far away (they currently flood the memory at some point) -# TODO Voxel edition -# TODO Physics -# TODO Generate structures (trees, caves, old buildings... everything that is not made of a single voxel) -# TODO Move data crunching to a C++ module for faster generation and mesh baking -# TODO Ambient occlusion with vertex colors -# TODO Import .obj to voxel types -# TODO Move to a 2D Chunk-based generation system? More convenient for terrains (but keep Blocks for graphics) - -extends Node - -const BLOCK_SIZE = 16 -const SORT_TIME = 1 -#const TILE_SIZE = 16 +extends VoxelTerrain export(Material) var solid_material = null export(Material) var transparent_material = null -var view_radius = 4 -var min_y = -4 -var max_y = 4 -var _blocks = {} -var _generating_blocks = {} -#var _chunks = {} - -var _pending_blocks = [] -var _thread = Thread.new() -var _time_before_sort = SORT_TIME -var _camera = null -var _voxel_types = [] -var _priority_positions = [] -var _outer_positions = [] -var _precalc_neighboring = [] - -var _noise = OsnNoise.new() -var _mesh_builder = VoxelMeshBuilder.new() var _library = VoxelLibrary.new() - - -class Block: - var voxel_map = null - var voxels = VoxelBuffer.new() - var pos = Vector3(0,0,0) - var mesh = null - var node = null - var gen_time = 0 - var has_generated = false - var has_structures = false - var need_update = false - - func _init(): - voxels.create(BLOCK_SIZE+2,BLOCK_SIZE+2,BLOCK_SIZE+2) - - func is_generated(): - return has_generated and has_structures - - func is_surrounded(): - var blocks = voxel_map._blocks - var ngb = voxel_map._precalc_neighboring - for v in ngb: - if not blocks.has(pos + v): - return false - return false - - func get_ground_y(x,z): - var types = voxel_map._voxel_types - for y in range(BLOCK_SIZE-1, 0, -1): - if not types[voxels[z][y][x]].is_transparent: - return y - return 0 - - func local_to_map(vpos): - return vpos + pos * BLOCK_SIZE - - -class BlockRequest: - const TYPE_GENERATE = 0 - const TYPE_UPDATE = 0 - - var type = 0 - var block_pos = Vector3(0,0,0) - - func _init(pos, type=TYPE_GENERATE): - self.block_pos = pos - self.type = type - - -#class Chunk: -# var heightmap = [] -# -# func _init(): -# heightmap.resize(BLOCK_SIZE+2) -# for y in range(0, heightmap.size()): -# var line = [] -# line.resize(BLOCK_SIZE+2) -# heightmap[y] = line -# for x in range(0, line.size()): -# line[x] = 0 +var _generator = null func _ready(): - _noise.set_seed(131183) + var gen = preload("generator.gd") + _generator = gen.Heightmap.new() _library.set_atlas_size(4) - - _camera = get_parent().get_node("Camera") - - _load_voxel_types() - _precalculate_priority_positions() - _precalculate_neighboring() - _update_pending_blocks() - - set_process(true) - - -func _precalculate_neighboring(): - for z in range(-1, 2): - for y in range(-1, 2): - for x in range(-1, 2): - if x != 0 and y != 0 and z != 0: - _precalc_neighboring.append(Vector3(x,y,z)) - - -func _load_voxel_types(): _library.create_voxel(0, "air").set_transparent() _library.create_voxel(1, "grass_dirt").set_cube_geometry().set_cube_uv_tbs_sides(Vector2(0,0), Vector2(0,1), Vector2(1,0)) _library.create_voxel(2, "dirt").set_cube_geometry().set_cube_uv_all_sides(Vector2(1,0)) _library.create_voxel(3, "log").set_cube_geometry().set_cube_uv_tbs_sides(Vector2(3,0), Vector2(3,0), Vector2(2,0)) _library.create_voxel(4, "water").set_transparent().set_cube_geometry(15.0/16.0).set_cube_uv_all_sides(Vector2(2,1)).set_material_id(1) - _mesh_builder.set_library(_library) - _mesh_builder.set_material(solid_material, 0) - _mesh_builder.set_material(transparent_material, 1) - - -func _precalculate_priority_positions(): - _priority_positions.clear() - for z in range(-view_radius, view_radius): - for x in range(-view_radius, view_radius): - for y in range(min_y, max_y): - _priority_positions.append(Vector3(x,y,z)) - _priority_positions.sort_custom(self, "_compare_priority_positions") - - -func _compare_priority_positions(a, b): - return a.length_squared() > b.length_squared() - - -func set_voxel(pos, id): - # This function only works if the block exists and is surrounded - - var bpos = Vector3(floor(pos.x/BLOCK_SIZE), floor(pos.y/BLOCK_SIZE), floor(pos.z/BLOCK_SIZE)) - var block = _blocks[bpos] - var rx = pos.x%BLOCK_SIZE - var ry = pos.y%BLOCK_SIZE - var rz = pos.z%BLOCK_SIZE - block.voxels[rz+1][ry+1][rx+1] = id - block.need_update = true - - # TODO The following is not needed if the meshing process could just take copies with neighboring, - # So we don't need to keep boundaries information for all the lifetime of blocks - - if rx == 0: - var nblock = _blocks[bpos-Vector3(1,0,0)] - nblock.voxels[BLOCK_SIZE+1][ry+1][rx+1] = id - nblock.need_update = true - elif rx == BLOCK_SIZE-1: - var nblock = _blocks[bpos+Vector3(1,0,0)] - nblock.voxels[0][ry+1][rx+1] = id - nblock.need_update = true - - if ry == 0: - var nblock = _blocks[bpos-Vector3(0,1,0)] - nblock.voxels[rx+1][BLOCK_SIZE+1][rx+1] = id - nblock.need_update = true - elif ry == BLOCK_SIZE-1: - var nblock = _blocks[bpos+Vector3(0,1,0)] - nblock.voxels[rx+1][0][rx+1] = id - nblock.need_update = true - - if rz == 0: - var nblock = _blocks[bpos-Vector3(0,0,1)] - nblock.voxels[rz+1][ry+1][BLOCK_SIZE+1] = id - nblock.need_update = true - elif rz == BLOCK_SIZE-1: - var nblock = _blocks[bpos+Vector3(0,0,1)] - nblock.voxels[rx+1][ry+1][0] = id - nblock.need_update = true - - -func _update_pending_blocks(): - # Using pre-sorted relative vectors is faster than sorting the list directly - var camera_block_pos = _camera.get_translation() / BLOCK_SIZE - camera_block_pos.x = floor(camera_block_pos.x) - camera_block_pos.y = 0#floor(camera_block_pos.y) - camera_block_pos.z = floor(camera_block_pos.z) - _pending_blocks.clear() - for rpos in _priority_positions: - var pos = rpos + camera_block_pos - if pos.y >= min_y and pos.y < max_y and not _generating_blocks.has(pos): - if not _blocks.has(pos): - _pending_blocks.append(pos) -# else: -# var block = _blocks[pos] -# if block.need_update: -# # TODO update mesh -# elif not block.has_structures and block.is_surrounded(): -# # TODO generate structures - - -func _process(delta): - - # TODO Immerge blocks that are too far away - - if _time_before_sort > 0: - _time_before_sort -= delta - if _time_before_sort <= 0: - _time_before_sort = SORT_TIME - _update_pending_blocks() - - if _pending_blocks.size() != 0: - if not _thread.is_active(): - - # Closer blocks are loaded first - var pos = _pending_blocks[_pending_blocks.size()-1] - _pending_blocks.pop_back() - _generating_blocks[pos] = true - var arg = BlockRequest.new(pos, BlockRequest.TYPE_GENERATE) - #_thread.start(self, "generate_block_thread", arg) - #print("generate " + str(pos)) - spawn_block(generate_block(arg.block_pos)) - - # Visible blocks are loaded first -# var hbs = Vector3(0.5, 0.5, 0.5) * BLOCK_SIZE -# for i in range(_pending_blocks.size()-1, 0, -1): -# var pos = _pending_blocks[i] -# var wpos = pos*BLOCK_SIZE + hbs -# if not _camera.is_position_behind(wpos): -# _pending_blocks[i] = _pending_blocks[_pending_blocks.size()-1] -# _pending_blocks.pop_back() -# _thread.start(self, "generate_block_thread", pos) -# break - - -func generate_block_thread(request): - if request.type == BlockRequest.TYPE_GENERATE: - var block = generate_block(request.block_pos) - # Call the main thread to wait - call_deferred("thread_finished") - #_generating_blocks.erase(block.pos) # Enable only without thread! - return block - else: - print("Unknown request type " + str(request.type)) - - -func thread_finished(): - var block = _thread.wait_to_finish() - _generating_blocks.erase(block.pos) - spawn_block(block) - - -func generate_block(pos): - var time_before = OS.get_ticks_msec() - - var block = Block.new() - block.pos = pos - - #time_before = OS.get_ticks_msec() - var empty = generate_3d(block.voxels, pos * BLOCK_SIZE) - #print("Generate: " + str(OS.get_ticks_msec() - time_before) + "ms") - - var mesh = null - if empty: - block.voxels = null - else: - #time_before = OS.get_ticks_msec() - mesh = _mesh_builder.build(block.voxels) - #print("Bake: " + str(OS.get_ticks_msec() - time_before) + "ms") - - block.voxel_map = self - block.mesh = mesh - block.gen_time = OS.get_ticks_msec() - time_before - - return block - - -func spawn_block(block): - if block.mesh != null: - var mesh_instance = preload("res://block.tscn").instance() - mesh_instance.set_translation(block.pos * BLOCK_SIZE) - mesh_instance.spawn() - mesh_instance.set_mesh(block.mesh) - mesh_instance.voxel_map = self - add_child(mesh_instance) - block.node = mesh_instance - _blocks[block.pos] = block - #print("Gen time: " + str(block.gen_time) + " (empty=" + str(block.mesh == null) + ")") - - -func generate_test(cubes, offset): - cubes.set_voxel(1, 1,1,1) - - cubes.set_voxel(1, 3,1,1) - cubes.set_voxel(1, 3,1,2) - - cubes.set_voxel(1, 5,1,1) - cubes.set_voxel(1, 5,1,2) - cubes.set_voxel(1, 5,2,1) - - cubes.set_voxel(1, 8,1,1) - cubes.set_voxel(1, 8,2,1) - cubes.set_voxel(1, 7,1,1) - - cubes.set_voxel(1, 11,1,1) - cubes.set_voxel(1, 11,2,1) - cubes.set_voxel(1, 10,1,1) - cubes.set_voxel(1, 10,1,2) - - for x in range(4,7): - for z in range(4,7): - cubes.set_voxel(1, x, 2, z) - cubes.set_voxel(1, x+5, 2, z) - cubes.set_voxel(1, 5,3,5) - cubes.set_voxel(1, 5,1,5) - - return false - - -func generate_3d(cubes, offset): - var ox = offset.x - var oy = offset.y - var oz = offset.z - var empty = true - var bs = cubes.get_size_x() - - var noise1 = OsnFractalNoise.new() - noise1.set_source_noise(_noise) - noise1.set_period(100) - noise1.set_octaves(4) - - var dirt = 1 - if oy < 0: - dirt = 2 - - for z in range(0, bs): - for x in range(0, bs): - for y in range(0, bs): - var gy = y+oy - var h = noise1.get_noise_3d(x+ox+2, gy, z+oz) - if h < 1-gy*0.01 - 1: - cubes.set_voxel(dirt, x, y, z) - empty = false - else: - if gy < 0: - cubes.set_voxel(4, x, y, z) - else: - cubes.set_voxel(0, x, y, z) - empty = false - - return empty - - -func generate_heightmap(cubes, offset): - var ox = offset.x - var oy = offset.y - var oz = offset.z - var empty = true - var ns1 = 0.01 - var ns2 = 0.05 - - var dirt = 1 - if oy < 0: - dirt = 2 - - var bs = cubes.get_size_x() - - var noise1 = OsnFractalNoise.new() - noise1.set_source_noise(_noise) - noise1.set_period(128) - noise1.set_octaves(4) - - for z in range(0, bs): - for x in range(0, bs): - - var h = 16.0 * noise1.get_noise_2d(ox+x, oz+z) - oy - - if h >= 0: - if h < bs: - empty = false - for y in range(0, h): - cubes.set_voxel(dirt, x,y,z) - #cubes[z][y][x] = dirt - for y in range(h, bs): - cubes.set_voxel(0, x,y,z) - #cubes[z][y][x] = air -# if oy == -BLOCK_SIZE: -# cubes[z][bs-1][x] = 0 -# if oy >= 0 and randf() < 0.2: -# cubes[z][h][x] = 2 -# if randf() < 0.01: -# var th = h+1+randi()%8 -# if th > bs: -# th = bs -# for y in range(h, th): -# cubes[z][y][x] = 3 - else: - empty = false - for y in range(0, bs): - cubes.set_voxel(dirt, x,y,z) - else: - for y in range(0, bs): - cubes.set_voxel(0, x,y,z) - - return empty - + var mesher = get_mesher() + mesher.set_library(_library) + mesher.set_material(solid_material, 0) + mesher.set_material(transparent_material, 1) + + force_load_blocks(Vector3(0,0,0), Vector3(8,3,8)) +# TODO option to execute this method in a thread +func _generate_block(voxels, block_pos): + #print("Generating block " + str(block_pos)) + var offset = block_to_voxel(block_pos) + _generator.generate(voxels, offset)