class_name InteriorMap extends Node3D const _CHUNK_SIZE := 32 const _HALF_PI := PI / 2 const _GRID_ORIGIN := Vector2(0.5, 0.5) ## ## ## enum FloorTile { ISLAND, ENDCAP_WEST, ENDCAP_NORTH, CORNER_NORTH_WEST, ENDCAP_EAST, PATH_HORIZONTAL, CORNER_NORTH_EAST, EDGE_NORTH, ENDCAP_SOUTH, CORNER_SOUTH_WEST, PATH_VERTICAL, EDGE_WEST, CORNER_SOUTH_EAST, EDGE_SOUTH, EDGE_EAST, FILL, } enum Orientation { EAST = 2 ** 0, SOUTH = 2 ** 1, WEST = 2 ** 2, NORTH = 2 ** 3, } ## ## Baked block of meshes making up a segment of the grid. ## class Chunk: var _coordinates := Vector2i.ZERO var _interior_map: InteriorMap = null var _renderables_by_mesh := {} func _init(interior_map: InteriorMap, coordinates: Vector2i) -> void: _interior_map = interior_map _coordinates = coordinates ## ## Invalidates the mesh block, re-baking its contents from the current mesh data set. ## func invalidate() -> void: _renderables_by_mesh.clear() var floor_map := _interior_map._floor_map var map_size := _interior_map.size var offset := _coordinates * _CHUNK_SIZE var positions_by_mesh := {} var map_area := Rect2i(Vector2.ZERO, map_size) for y in _CHUNK_SIZE: var map_row_offset := (map_size.y * y) for x in _CHUNK_SIZE: var map_coordinates := offset + Vector2i(x, y) if map_area.has_point(map_coordinates): var interior_floor := floor_map[map_row_offset + map_coordinates.x] if interior_floor != null: var tile := 0 for i in Orientation.values().size(): var map_neighbor := map_coordinates + Vector2i( roundi(cos(i * PI / 2.0)), roundi(sin(i * PI / 2.0))) if map_area.has_point(map_neighbor): tile += int(floor_map[(map_size.y * map_neighbor.y) + map_neighbor.x] == interior_floor) * 2 ** i var mesh: Mesh = null match tile: FloorTile.ISLAND: mesh = interior_floor.island_mesh FloorTile.ENDCAP_WEST: mesh = interior_floor.endcap_west_mesh FloorTile.ENDCAP_NORTH: mesh = interior_floor.endcap_north_mesh FloorTile.CORNER_NORTH_WEST: mesh = interior_floor.corner_north_west_mesh FloorTile.ENDCAP_EAST: mesh = interior_floor.endcap_east_mesh FloorTile.PATH_HORIZONTAL: mesh = interior_floor.path_horizontal_mesh FloorTile.CORNER_NORTH_EAST: mesh = interior_floor.corner_north_east_mesh FloorTile.EDGE_NORTH: mesh = interior_floor.edge_north_mesh FloorTile.ENDCAP_SOUTH: mesh = interior_floor.endcap_south_mesh FloorTile.CORNER_SOUTH_WEST: mesh = interior_floor.corner_south_west_mesh FloorTile.PATH_VERTICAL: mesh = interior_floor.path_vertical_mesh FloorTile.EDGE_WEST: mesh = interior_floor.edge_west_mesh FloorTile.CORNER_SOUTH_EAST: mesh = interior_floor.corner_south_east_mesh FloorTile.EDGE_SOUTH: mesh = interior_floor.edge_south_mesh FloorTile.EDGE_EAST: mesh = interior_floor.edge_east_mesh FloorTile.FILL: mesh = interior_floor.fill_mesh if mesh != null: if not(mesh in positions_by_mesh): positions_by_mesh[mesh] = PackedVector2Array() positions_by_mesh[mesh].append(Vector2(x, y)) # (Re)-bake tiles into multimesh instances for the chunk. var scenario_rid := _interior_map.get_world_3d().scenario for mesh in positions_by_mesh: var positions: PackedVector2Array = positions_by_mesh[mesh] if not(positions.is_empty()): var position_count := positions.size() var renderable := Renderable.new(scenario_rid, mesh.get_rid(), position_count) for i in position_count: var position := positions[i] renderable.set_instance_transform(i, Transform3D(Basis.IDENTITY, Vector3( ((float(offset.x) + position.x) - (float(map_size.x) * _GRID_ORIGIN.x)) + 0.5, 0.0, ((float(offset.y) + position.y) - (float(map_size.y) * _GRID_ORIGIN.y)) + 0.5))) _renderables_by_mesh[mesh] = renderable func set_transform(transform: Transform3D) -> void: for mesh in _renderables_by_mesh: _renderables_by_mesh[mesh].set_transform(transform) func set_visible(visible: bool) -> void: for mesh in _renderables_by_mesh: _renderables_by_mesh[mesh].set_visible(visible) ## ## ## class Renderable: var _instance_rid := RID() var _multimesh_rid := RID() func _init(scenario_rid: RID, mesh_rid: RID, transform_count: int) -> void: _multimesh_rid = RenderingServer.multimesh_create() _instance_rid = RenderingServer.instance_create2(_multimesh_rid, scenario_rid) RenderingServer.multimesh_set_mesh(_multimesh_rid, mesh_rid) RenderingServer.multimesh_allocate_data(_multimesh_rid, transform_count, RenderingServer.MULTIMESH_TRANSFORM_3D, true) func _notification(what: int) -> void: match what: NOTIFICATION_PREDELETE: RenderingServer.free_rid(_instance_rid) RenderingServer.free_rid(_multimesh_rid) ## ## ## func set_instance_transform(instance_index: int, transform: Transform3D) -> void: RenderingServer.multimesh_instance_set_transform(_multimesh_rid, instance_index, transform) ## ## ## func set_visible(visible: bool) -> void: RenderingServer.instance_set_visible(_instance_rid, visible) ## ## ## func set_transform(transform: Transform3D) -> void: RenderingServer.instance_set_transform(_instance_rid, transform) var _chunks: Array[Chunk] = [] var _chunks_size := Vector2i.ZERO var _floor_map: Array[InteriorFloor] = [] ## ## Size of the tile grid (in engine units). ## @export var size: Vector2i: set(value): _chunks_size = Vector2i((value / float(_CHUNK_SIZE)).ceil()) _floor_map.resize(value.x * value.y) _chunks.resize(_chunks_size.x * _chunks_size.y) for y in _chunks_size.y: for x in _chunks_size.x: _chunks[(y * _CHUNK_SIZE) + x] = Chunk.new(self, Vector2i(x, y)) 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: chunk.set_transform(global_transform) NOTIFICATION_VISIBILITY_CHANGED: for chunk in _chunks: chunk.set_visible(visible) ## ## Clears the entirety of the tile grid to only contain tiles of instance [code]tile[/code] pointing ## in the orientation of [code]orientation[/code]. ## ## For clearing a specific region of the mesh grid, see [method fill_mesh]. ## func clear() -> void: for i in _floor_map.size(): _floor_map[i] = null for y in _chunks_size.y: for x in _chunks_size.x: _get_chunk(Vector2i(x, y)).invalidate() ## ## Clears the region of the tile grid at [code]area[/code] to only contain instances of ## [code]interior_floor[/code]. ## ## *Note* that [code]area[/code] *must* be within the area of the of the mesh grid, where ## [code]Vector2i.ZERO[/code] is the top-left and [member size] [code]- 1[/code] is the bottom- ## right. ## ## For clearing the whole of the mesh grid, see [method clear_mesh]. ## func fill_floor(area: Rect2i, interior_floor: InteriorFloor) -> 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): _floor_map[(size.y * (y / _CHUNK_SIZE)) + (x / _CHUNK_SIZE)] = interior_floor for y in _chunks_size.y: for x in _chunks_size.x: if filled_chunks.get_bit(x, y): _get_chunk(Vector2i(x, y)).invalidate() ## ## Plots a single tile at [code]coordinates[/code] to be an instance of [code]interior_floor[/code], ## overwriting whatever it previously contained. ## ## *Note* that [code]coordinates[/code] *must* be within the area of the of the mesh grid, where ## [code]Vector2i.ZERO[/code] is the top-left and [member size] [code]- 1[/code] is the bottom- ## right. ## ## For bulk-setting many meshes at once, see [method fill_mesh] and [method clear_mesh]. ## func plot_floor(coordinates: Vector2i, interior_floor: InteriorFloor) -> void: assert(Rect2i(Vector2i.ZERO, size).has_point(coordinates), "coordinate must be within grid") _floor_map[(size.y * coordinates.y) + coordinates.x] = interior_floor _get_chunk(coordinates / _CHUNK_SIZE).invalidate()