Drastic code reduction, now handled by the C++ module

master
Marc Gilleron 2016-05-10 02:05:58 +02:00
parent f7a7ab404e
commit e9e9522bd6
6 changed files with 312 additions and 407 deletions

160
project/generator.gd Normal file
View File

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

76
project/grid.gd Normal file
View File

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

15
project/launcher.gd Normal file
View File

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

22
project/launcher.tscn Normal file
View File

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

View File

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

View File

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