123 lines
4.1 KiB
GDScript3
123 lines
4.1 KiB
GDScript3
|
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())
|