voxelgame/project/blocky_game/generator/generator.gd

201 lines
5.7 KiB
GDScript

#tool
extends VoxelGeneratorScript
const Structure = preload("./structure.gd")
const TreeGenerator = preload("./tree_generator.gd")
const HeightmapCurve = preload("./heightmap_curve.tres")
# TODO Don't hardcode, get by name from library somehow
const AIR = 0
const DIRT = 1
const GRASS = 2
const WATER_FULL = 14
const WATER_TOP = 13
const LOG = 4
const LEAVES = 25
const TALL_GRASS = 8
const DEAD_SHRUB = 26
#const STONE = 8
const _CHANNEL = VoxelBuffer.CHANNEL_TYPE
const _moore_dirs = [
Vector3(-1, 0, -1),
Vector3(0, 0, -1),
Vector3(1, 0, -1),
Vector3(-1, 0, 0),
Vector3(1, 0, 0),
Vector3(-1, 0, 1),
Vector3(0, 0, 1),
Vector3(1, 0, 1)
]
var _tree_structures := []
var _heightmap_min_y := int(HeightmapCurve.min_value)
var _heightmap_max_y := int(HeightmapCurve.max_value)
var _heightmap_range := 0
var _heightmap_noise := OpenSimplexNoise.new()
var _trees_min_y := 0
var _trees_max_y := 0
func _init():
# TODO Even this must be based on a seed, but I'm lazy
var tree_generator = TreeGenerator.new()
tree_generator.log_type = LOG
tree_generator.leaves_type = LEAVES
for i in 16:
var s = tree_generator.generate()
_tree_structures.append(s)
var tallest_tree_height = 0
for structure in _tree_structures:
var h = int(structure.voxels.get_size().y)
if tallest_tree_height < h:
tallest_tree_height = h
_trees_min_y = _heightmap_min_y
_trees_max_y = _heightmap_max_y + tallest_tree_height
#_heightmap_noise.seed = 131183
_heightmap_noise.period = 128
_heightmap_noise.octaves = 4
# IMPORTANT
# If we don't do this `Curve` could bake itself when interpolated,
# and this causes crashes when used in multiple threads
HeightmapCurve.bake()
func _get_used_channels_mask() -> int:
return 1 << _CHANNEL
func _generate_block(buffer: VoxelBuffer, origin_in_voxels: Vector3, lod: int):
# Saves from this demo used 8-bit, which is no longer the default
# buffer.set_channel_depth(_CHANNEL, VoxelBuffer.DEPTH_8_BIT)
# Assuming input is cubic in our use case (it doesn't have to be!)
var block_size := int(buffer.get_size().x)
var oy := int(origin_in_voxels.y)
# TODO This hardcodes a cubic block size of 16, find a non-ugly way...
# Dividing is a false friend because of negative values
var chunk_pos := Vector3(
int(origin_in_voxels.x) >> 4,
int(origin_in_voxels.y) >> 4,
int(origin_in_voxels.z) >> 4)
_heightmap_range = _heightmap_max_y - _heightmap_min_y
# Ground
if origin_in_voxels.y > _heightmap_max_y:
buffer.fill(AIR, _CHANNEL)
elif origin_in_voxels.y + block_size < _heightmap_min_y:
buffer.fill(DIRT, _CHANNEL)
else:
var rng := RandomNumberGenerator.new()
rng.seed = _get_chunk_seed_2d(chunk_pos)
var gx : int
var gz := int(origin_in_voxels.z)
for z in block_size:
gx = int(origin_in_voxels.x)
for x in block_size:
var height := _get_height_at(gx, gz)
var relative_height := height - oy
# Dirt and grass
if relative_height > block_size:
buffer.fill_area(DIRT,
Vector3(x, 0, z), Vector3(x + 1, block_size, z + 1), _CHANNEL)
elif relative_height > 0:
buffer.fill_area(DIRT,
Vector3(x, 0, z), Vector3(x + 1, relative_height, z + 1), _CHANNEL)
if height >= 0:
buffer.set_voxel(GRASS, x, relative_height - 1, z, _CHANNEL)
if relative_height < block_size and rng.randf() < 0.2:
var foliage = TALL_GRASS
if rng.randf() < 0.1:
foliage = DEAD_SHRUB
buffer.set_voxel(foliage, x, relative_height, z, _CHANNEL)
# Water
if height < 0 and oy < 0:
var start_relative_height := 0
if relative_height > 0:
start_relative_height = relative_height
buffer.fill_area(WATER_FULL,
Vector3(x, start_relative_height, z),
Vector3(x + 1, block_size, z + 1), _CHANNEL)
if oy + block_size == 0:
# Surface block
buffer.set_voxel(WATER_TOP, x, block_size - 1, z, _CHANNEL)
gx += 1
gz += 1
# Trees
if origin_in_voxels.y <= _trees_max_y and origin_in_voxels.y + block_size >= _trees_min_y:
var voxel_tool := buffer.get_voxel_tool()
var structure_instances := []
_get_tree_instances_in_chunk(chunk_pos, origin_in_voxels, block_size, structure_instances)
# Relative to current block
var block_aabb := AABB(Vector3(), buffer.get_size() + Vector3(1, 1, 1))
for dir in _moore_dirs:
var ncpos : Vector3 = (chunk_pos + dir).round()
_get_tree_instances_in_chunk(ncpos, origin_in_voxels, block_size, structure_instances)
for structure_instance in structure_instances:
var pos : Vector3 = structure_instance[0]
var structure : Structure = structure_instance[1]
var lower_corner_pos := pos - structure.offset
var aabb := AABB(lower_corner_pos, structure.voxels.get_size() + Vector3(1, 1, 1))
if aabb.intersects(block_aabb):
voxel_tool.paste(lower_corner_pos,
structure.voxels, 1 << VoxelBuffer.CHANNEL_TYPE, AIR)
buffer.optimize()
func _get_tree_instances_in_chunk(
cpos: Vector3, offset: Vector3, chunk_size: int, tree_instances: Array):
var rng := RandomNumberGenerator.new()
rng.seed = _get_chunk_seed_2d(cpos)
for i in 4:
var pos := Vector3(rng.randi() % chunk_size, 0, rng.randi() % chunk_size)
pos += cpos * chunk_size
pos.y = _get_height_at(pos.x, pos.z)
if pos.y > 0:
pos -= offset
var si := rng.randi() % len(_tree_structures)
var structure : Structure = _tree_structures[si]
tree_instances.append([pos.round(), structure])
#static func get_chunk_seed(cpos: Vector3) -> int:
# return cpos.x ^ (13 * int(cpos.y)) ^ (31 * int(cpos.z))
static func _get_chunk_seed_2d(cpos: Vector3) -> int:
return int(cpos.x) ^ (31 * int(cpos.z))
func _get_height_at(x: int, z: int) -> int:
var t = 0.5 + 0.5 * _heightmap_noise.get_noise_2d(x, z)
return int(HeightmapCurve.interpolate_baked(t))