game/mesh_grid.gd

123 lines
4.1 KiB
GDScript

class_name MeshGrid extends Node3D
const _CELL_COUNT := 8
class _Chunk:
var multimesh_instances: Array[_MultimeshInstance] = []
var meshes: Array[Mesh] = []
func _init() -> void:
self.meshes.resize(_CELL_COUNT * _CELL_COUNT)
class _MultimeshInstance:
var _instance_rid := RID()
var _multimesh_rid := RID()
func _init(scenario_rid: RID, mesh: Mesh, transforms: Array) -> void:
_multimesh_rid = RenderingServer.multimesh_create()
_instance_rid = RenderingServer.instance_create2(_multimesh_rid, scenario_rid)
RenderingServer.multimesh_set_mesh(_multimesh_rid, mesh.get_rid())
var transform_count := transforms.size()
RenderingServer.multimesh_allocate_data(_multimesh_rid,
transform_count, RenderingServer.MULTIMESH_TRANSFORM_3D, true)
for i in transform_count:
RenderingServer.multimesh_instance_set_transform(_multimesh_rid, i, transforms[i])
var _chunks: Array[_Chunk] = []
var _grid_origin := Vector2(0.5, 0.5)
##
## The number of horizontal and vertical cells in the current grid.
##
## Setting this value will result in the current grid data being completely overwritten to return
## it to a sensible default.
##
@export
var size: Vector2i:
set(value):
self._chunks.resize(int(ceil((value.x * value.y) / float(_CELL_COUNT))))
for i in self._chunks.size():
self._chunks[i] = _Chunk.new()
size = value
func _notification(what: int) -> void:
match what:
NOTIFICATION_TRANSFORM_CHANGED:
for chunk in _chunks:
for multimesh_instance in chunk.multimesh_instances:
RenderingServer.instance_set_transform(
multimesh_instance._instance_rid, multimesh_instance.global_transform)
##
## Sets the cell at [code]coordinate[/code] to display [code]mesh[/code].
##
## Note that this function assumes [code]coordinate[/code] is within the bounds of the grid
## coordinate space.
##
## Note that changes to a cell result in the chunk it resides in being completely regenerated as
## part of its modification, and therefore has an implicitly higher overhead to other setter
## operations defined by [MeshGrid].
##
func set_mesh(coordinate: Vector2i, mesh: Mesh) -> void:
assert(Rect2i(Vector2i.ZERO, size).has_point(coordinate), "coordinate must be within grid")
# TODO: Once this is all lowered into native code, look for ways to parallelize the loops.
var chunk_coordinate := coordinate / _CELL_COUNT
var cell_coordinate := coordinate % _CELL_COUNT
var chunk := _chunks[(size.x * chunk_coordinate.y) + chunk_coordinate.x]
var chunk_meshes := chunk.meshes
chunk_meshes[(_CELL_COUNT * cell_coordinate.y) + cell_coordinate.x] = mesh
# Invalide any existing baked multimeshes in the chunk.
for multimesh_instance in chunk.multimesh_instances:
RenderingServer.free_rid(multimesh_instance._instance_rid)
RenderingServer.free_rid(multimesh_instance._multimesh_rid)
chunk.multimesh_instances.clear()
# Normalize mesh instance data for the chunk.
var mesh_transform_sets := {}
for i in chunk_meshes.size():
var chunk_mesh := chunk_meshes[i]
if chunk_mesh != null:
if not(chunk_mesh in mesh_transform_sets):
mesh_transform_sets[chunk_mesh] = []
mesh_transform_sets[chunk_mesh].push_back(Transform3D(Basis.IDENTITY, Vector3(
(float(chunk_coordinate.x * _CELL_COUNT) + (i % _CELL_COUNT)) -
(float(size.x) * _grid_origin.x), 0.0,
(float(chunk_coordinate.y * _CELL_COUNT) + (i / _CELL_COUNT)) -
(float(size.y) * _grid_origin.y))))
# Bake into multimesh instances for the chunk.
var scenario_rid := get_world_3d().scenario
for chunk_mesh in mesh_transform_sets:
var multimesh_instance :=\
_MultimeshInstance.new(scenario_rid, chunk_mesh, mesh_transform_sets[chunk_mesh])
RenderingServer.instance_set_transform(multimesh_instance._instance_rid, global_transform)
chunk.multimesh_instances.push_back(multimesh_instance)
##
## Returns [code]world_position[/code] converted into a coordinate aligned with the [MeshGrid].
##
## Note that [code]world_position[/code] values not within the [MeshGrid] will produce grid
## coordinates outside of the [MeshGrid] bounds as well.
##
func world_to_grid(world_position: Vector2) -> Vector2i:
return Vector2i((world_position + (Vector2(size) * _grid_origin)).floor())