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