From 42c3a53b24ebe40bc353cb486a10542ff7385e69 Mon Sep 17 00:00:00 2001 From: kayomn Date: Fri, 27 Jan 2023 14:15:59 +0000 Subject: [PATCH] Refactor code quality and squash bugs in MeshGrid --- map_editor.scn | 4 +- map_editor/camera_boundary_mesh.res | 4 +- mesh_grid.gd | 189 ++++++++++++++++++---------- project.godot | 5 + 4 files changed, 133 insertions(+), 69 deletions(-) diff --git a/map_editor.scn b/map_editor.scn index e31b394..d597e3d 100644 --- a/map_editor.scn +++ b/map_editor.scn @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37ec7d06fc6cdc7a3d5a3fb8b4821c47e7265f1d781fcca50a7bae3bc1cc28e6 -size 8629 +oid sha256:b8de9c0cb07eadedbfd24cfa384a72d55c4f44d26665163e8de8e147d97e5df9 +size 9103 diff --git a/map_editor/camera_boundary_mesh.res b/map_editor/camera_boundary_mesh.res index 673c51f..c478941 100644 --- a/map_editor/camera_boundary_mesh.res +++ b/map_editor/camera_boundary_mesh.res @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce40e96135058eaaf87b5188862dc31e71c6e11a53f5afce3cffccf4320208d6 -size 645 +oid sha256:28046c63a066c0a2119714133b8aae4216b3dbd694749d3e8467d362c2cf74c5 +size 643 diff --git a/mesh_grid.gd b/mesh_grid.gd index aca0ea2..950e0ae 100644 --- a/mesh_grid.gd +++ b/mesh_grid.gd @@ -1,25 +1,78 @@ class_name MeshGrid extends Node3D -const _CELL_COUNT := 8 +const _CHUNK_SIZE := 32 -class _Chunk: - var multimesh_instances: Array[_MultimeshInstance] = [] +const _GRID_ORIGIN := Vector2(0.5, 0.5) + +## +## +## +class Chunk: + var multimesh_instances: Array[MultiMeshInstance] = [] var meshes: Array[Mesh] = [] func _init() -> void: - self.meshes.resize(_CELL_COUNT * _CELL_COUNT) + meshes.resize(_CHUNK_SIZE * _CHUNK_SIZE) -class _MultimeshInstance: + ## + ## + ## + func invalidate(mesh_grid: MeshGrid, coordinate: Vector2i) -> void: + # TODO: Once this is all lowered into native code, look for ways to parallelize the loops. + for multimesh_instance in multimesh_instances: + RenderingServer.free_rid(multimesh_instance._instance_rid) + RenderingServer.free_rid(multimesh_instance._multimesh_rid) + + multimesh_instances.clear() + + # Normalize mesh instance data for the chunk. + var transforms_by_mesh := {} + var grid_size := mesh_grid.size + + for i in meshes.size(): + var mesh := meshes[i] + + if mesh != null: + if not(mesh in transforms_by_mesh): + transforms_by_mesh[mesh] = [] + + transforms_by_mesh[mesh].append(Transform3D(Basis.IDENTITY, Vector3( + (float(coordinate.x * _CHUNK_SIZE) + (i % _CHUNK_SIZE)) - + (float(grid_size.x) * _GRID_ORIGIN.x), 0.0, + (float(coordinate.y * _CHUNK_SIZE) + (i / _CHUNK_SIZE)) - + (float(grid_size.y) * _GRID_ORIGIN.y)))) + + # Bake into multimesh instances for the chunk. + var scenario_rid := mesh_grid.get_world_3d().scenario + var global_transform := mesh_grid.global_transform + + for chunk_mesh in transforms_by_mesh: + var multimesh_instance := MultiMeshInstance.new( + scenario_rid, chunk_mesh.get_rid(), transforms_by_mesh[chunk_mesh]) + + multimesh_instance.set_offset(global_transform) + multimesh_instances.append(multimesh_instance) + + ## + ## + ## + func set_mesh(coordinate: Vector2i, mesh: Mesh) -> void: + meshes[(_CHUNK_SIZE * coordinate.y) + coordinate.x] = mesh + +## +## +## +class MultiMeshInstance: var _instance_rid := RID() var _multimesh_rid := RID() - func _init(scenario_rid: RID, mesh: Mesh, transforms: Array) -> void: + func _init(scenario_rid: RID, mesh_rid: RID, 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()) + RenderingServer.multimesh_set_mesh(_multimesh_rid, mesh_rid) var transform_count := transforms.size() @@ -29,88 +82,94 @@ class _MultimeshInstance: for i in transform_count: RenderingServer.multimesh_instance_set_transform(_multimesh_rid, i, transforms[i]) -var _chunks: Array[_Chunk] = [] + ## + ## + ## + func set_offset(offset: Transform3D) -> void: + RenderingServer.instance_set_transform(_instance_rid, offset) -var _grid_origin := Vector2(0.5, 0.5) +var _chunks: Array[Chunk] = [] + +var _chunks_size := Vector2i.ZERO ## -## 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)))) + var chunk_size_factor := float(_CHUNK_SIZE) - for i in self._chunks.size(): - self._chunks[i] = _Chunk.new() + _chunks.resize(int(ceilf(value.x / chunk_size_factor) * ceilf(value.y / chunk_size_factor))) + for i in _chunks.size(): + _chunks[i] = Chunk.new() + + _chunks_size = Vector2i((value / float(_CHUNK_SIZE)).ceil()) size = value +func _get_chunk(chunk_coordinate: Vector2i) -> Chunk: + return _chunks[(_chunks_size.x * chunk_coordinate.y) + chunk_coordinate.x] + 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) + multimesh_instance.set_offset_transform(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 clear_mesh(mesh: Mesh) -> void: + for y in size.y: + for x in size.x: + var coordinate := Vector2i(x, y) + + _get_chunk(coordinate / _CHUNK_SIZE).set_mesh(coordinate % _CHUNK_SIZE, mesh) + + for y in _chunks_size.y: + for x in _chunks_size.x: + var chunk_coordinate := Vector2i(x, y) + + _get_chunk(chunk_coordinate).invalidate(self, chunk_coordinate) + ## -func set_mesh(coordinate: Vector2i, mesh: Mesh) -> void: +## +## +func fill_mesh(area: Rect2i, mesh: Mesh) -> void: + assert(Rect2i(Vector2i.ZERO, size).encloses(area), "area must be within grid") + + var filled_chunks := BitMap.new() + + filled_chunks.resize(_chunks_size) + + for y in range(area.position.y, area.end.y): + for x in range(area.position.x, area.end.x): + var coordinate := Vector2i(x, y) + var chunk_coordinate := coordinate / _CHUNK_SIZE + + _get_chunk(chunk_coordinate).set_mesh(coordinate % _CHUNK_SIZE, mesh) + filled_chunks.set_bitv(chunk_coordinate, true) + + for y in _chunks_size.y: + for x in _chunks_size.x: + if filled_chunks.get_bit(x, y): + var chunk_coordinate := Vector2i(x, y) + + _get_chunk(chunk_coordinate).invalidate(self, chunk_coordinate) + +## +## +## +func plot_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 + var chunk_coordinate := coordinate / _CHUNK_SIZE + var chunk := _get_chunk(chunk_coordinate) - 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) + chunk.set_mesh(coordinate % _CHUNK_SIZE, mesh) + chunk.invalidate(self, chunk_coordinate) ## ## Returns [code]world_position[/code] converted into a coordinate aligned with the [MeshGrid]. @@ -119,4 +178,4 @@ func set_mesh(coordinate: Vector2i, mesh: Mesh) -> void: ## 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()) + return Vector2i((world_position + (Vector2(size) * _GRID_ORIGIN)).floor()) diff --git a/project.godot b/project.godot index 986754a..c52661e 100644 --- a/project.godot +++ b/project.godot @@ -23,6 +23,7 @@ LocalPlayer="*res://local_player.scn" [debug] +gdscript/warnings/integer_division=0 gdscript/warnings/assert_always_true=0 gdscript/warnings/assert_always_false=0 @@ -80,6 +81,10 @@ editor_paint={ ] } +[physics] + +common/physics_ticks_per_second=30 + [rendering] lights_and_shadows/directional_shadow/soft_shadow_filter_quality=3