diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 680ab6cd1e..12417c0d8a 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -97,6 +97,20 @@ This function returns always the map set on the GridMap node and not the map on the NavigationServer. If the map is changed directly with the NavigationServer API the GridMap node will not be aware of the map change. + + + + + Returns the [Vector3i] octant coordinates of the octant that the cell at [param cell_coords] belongs to. + + + + + + + Returns an array of [Vector3i] octant coordinates that are inside the given [param bounds], including octants that have no cells in use. + + @@ -117,6 +131,41 @@ Returns an array of all cells with the given item index specified in [param item]. + + + + + Returns an array of [Vector3i]s with the cell coordinates of non-empty cells inside the octant at [param octant_coords]. + + + + + + + + Returns an array of [Vector3i]s with the cell coordinates of cells inside the octant at [param octant_coords] that use the specified cell [param item]. + + + + + + Returns an array of [Vector3i]s with the octant coordinates of the non-empty octants in the grid map. + + + + + + + Returns an array of [Vector3i]s with the octant coordinates of the octants that use the specified [param item] in the grid map. + + + + + + + Returns an array of [Vector3i]s with the octant coordinates of non-empty octants that are inside the local [param bounds]. + + diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index a5ab441f53..9b293ae72d 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -1266,6 +1266,17 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_used_cells"), &GridMap::get_used_cells); ClassDB::bind_method(D_METHOD("get_used_cells_by_item", "item"), &GridMap::get_used_cells_by_item); + ClassDB::bind_method(D_METHOD("get_used_octants"), &GridMap::get_used_octants); + ClassDB::bind_method(D_METHOD("get_used_octants_by_item", "item"), &GridMap::get_used_octants_by_item); + + ClassDB::bind_method(D_METHOD("get_used_cells_in_octant", "octant_coords"), &GridMap::get_used_cells_in_octant); + ClassDB::bind_method(D_METHOD("get_used_cells_in_octant_by_item", "octant_coords", "item"), &GridMap::get_used_cells_in_octant_by_item); + + ClassDB::bind_method(D_METHOD("get_octants_in_bounds", "bounds"), &GridMap::get_octants_in_bounds); + ClassDB::bind_method(D_METHOD("get_used_octants_in_bounds", "bounds"), &GridMap::get_used_octants_in_bounds); + + ClassDB::bind_method(D_METHOD("get_octant_coords_from_cell_coords", "cell_coords"), &GridMap::get_octant_coords_from_cell_coords); + ClassDB::bind_method(D_METHOD("get_meshes"), &GridMap::get_meshes); ClassDB::bind_method(D_METHOD("get_bake_meshes"), &GridMap::get_bake_meshes); ClassDB::bind_method(D_METHOD("get_bake_mesh_instance", "idx"), &GridMap::get_bake_mesh_instance); @@ -1337,6 +1348,193 @@ TypedArray GridMap::get_used_cells_by_item(int p_item) const { return a; } +TypedArray GridMap::get_used_octants() const { + TypedArray octant_coords; + octant_coords.resize(octant_map.size()); + int i = 0; + for (const KeyValue &octant_kv : octant_map) { + const OctantKey &octant_key = octant_kv.key; + Vector3i octant_coord(octant_key.x, octant_key.y, octant_key.z); + octant_coords[i++] = octant_coord; + } + + return octant_coords; +} + +TypedArray GridMap::get_used_octants_by_item(int p_item) const { + TypedArray octant_coords; + for (const KeyValue &octant_kv : octant_map) { + const OctantKey &octant_key = octant_kv.key; + const Octant &octant = *octant_kv.value; + for (const IndexKey &cell_key : octant.cells) { + const Cell &cell = cell_map[cell_key]; + if ((int)cell.item == p_item) { + Vector3i octant_coord(octant_key.x, octant_key.y, octant_key.z); + octant_coords.push_back(octant_coord); + break; + } + } + } + + return octant_coords; +} + +TypedArray GridMap::get_used_cells_in_octant(const Vector3i &p_octant_coords) const { + TypedArray cell_coords; + + const OctantKey octantkey = get_octant_key_from_cell_coords(p_octant_coords); + if (!octant_map.has(octantkey)) { + return cell_coords; + } + + const Octant &octant = *octant_map[octantkey]; + + for (const IndexKey &cell_key : octant.cells) { + Vector3i cell_coord(cell_key.x, cell_key.y, cell_key.z); + cell_coords.push_back(cell_coord); + } + + return cell_coords; +} + +TypedArray GridMap::get_used_cells_in_octant_by_item(const Vector3i &p_octant_coords, int p_item) const { + TypedArray cell_coords; + + const OctantKey octantkey = get_octant_key_from_cell_coords(p_octant_coords); + if (!octant_map.has(octantkey)) { + return cell_coords; + } + + const Octant &octant = *octant_map[octantkey]; + + for (const IndexKey &cell_key : octant.cells) { + const Cell &cell = cell_map[cell_key]; + if ((int)cell.item == p_item) { + Vector3i cell_coord(cell_key.x, cell_key.y, cell_key.z); + cell_coords.push_back(cell_coord); + } + } + + return cell_coords; +} + +Vector3i GridMap::get_octant_coords_from_cell_coords(const Vector3i &p_cell_coords) const { + return Vector3i( + p_cell_coords.x > 0 ? p_cell_coords.x / octant_size : (p_cell_coords.x - (octant_size - 1)) / octant_size, + p_cell_coords.y > 0 ? p_cell_coords.y / octant_size : (p_cell_coords.y - (octant_size - 1)) / octant_size, + p_cell_coords.z > 0 ? p_cell_coords.z / octant_size : (p_cell_coords.z - (octant_size - 1)) / octant_size); +} + +LocalVector GridMap::get_index_keys_in_bounds(const AABB &p_bounds, bool p_used_only) const { + LocalVector index_keys; + if (!p_bounds.has_volume()) { + return index_keys; + } + + Vector3i cell_coords_start = (p_bounds.position / cell_size).floor(); + // -CMP_EPSILON because we don't want the octants that are just starting at the edge of the bounds. + Vector3i cell_coords_end = ((p_bounds.get_end() - Vector3(CMP_EPSILON, CMP_EPSILON, CMP_EPSILON)) / cell_size).floor(); + + for (int z = cell_coords_start.z; z < cell_coords_end.z + 1; z++) { + for (int y = cell_coords_start.y; y < cell_coords_end.y + 1; y++) { + for (int x = cell_coords_start.x; x < cell_coords_end.x + 1; x++) { + IndexKey index_key; + index_key.x = x; + index_key.y = y; + index_key.z = z; + + if (p_used_only) { + const HashMap::ConstIterator index_key_kv = cell_map.find(index_key); + + if (index_key_kv) { + index_keys.push_back(index_key); + } + } else { + index_keys.push_back(index_key); + } + } + } + } + + return index_keys; +} + +LocalVector GridMap::get_octant_keys_in_bounds(const AABB &p_bounds, bool p_used_only) const { + LocalVector octant_keys; + if (!p_bounds.has_volume()) { + return octant_keys; + } + + Vector3i cell_coords_start = (p_bounds.position / cell_size).floor(); + // -CMP_EPSILON because we don't want the octants that are just starting at the edge of the bounds. + Vector3i cell_coords_end = ((p_bounds.get_end() - Vector3(CMP_EPSILON, CMP_EPSILON, CMP_EPSILON)) / cell_size).floor(); + + OctantKey octant_coords_start = get_octant_key_from_cell_coords(cell_coords_start); + OctantKey octant_coords_end = get_octant_key_from_cell_coords(cell_coords_end); + + for (int z = octant_coords_start.z; z < octant_coords_end.z + 1; z++) { + for (int y = octant_coords_start.y; y < octant_coords_end.y + 1; y++) { + for (int x = octant_coords_start.x; x < octant_coords_end.x + 1; x++) { + OctantKey octant_key; + octant_key.x = x; + octant_key.y = y; + octant_key.z = z; + + if (p_used_only) { + const HashMap::ConstIterator octant_kv = octant_map.find(octant_key); + + if (octant_kv) { + Vector3i octant_coord(x, y, z); + octant_keys.push_back(octant_key); + } + } else { + octant_keys.push_back(octant_key); + } + } + } + } + + return octant_keys; +} + +TypedArray GridMap::get_octants_in_bounds(const AABB &p_bounds) const { + TypedArray octant_coords; + if (!p_bounds.has_volume()) { + return octant_coords; + } + + bool used_only = false; + + const LocalVector &octant_keys = get_octant_keys_in_bounds(p_bounds, used_only); + + octant_coords.resize(octant_keys.size()); + + for (uint32_t i = 0; i < octant_keys.size(); i++) { + octant_coords[i] = Vector3i(octant_keys[i].x, octant_keys[i].y, octant_keys[i].z); + } + + return octant_coords; +} + +TypedArray GridMap::get_used_octants_in_bounds(const AABB &p_bounds) const { + TypedArray octant_coords; + if (!p_bounds.has_volume()) { + return octant_coords; + } + + bool used_only = true; + + const LocalVector &octant_keys = get_octant_keys_in_bounds(p_bounds, used_only); + + octant_coords.resize(octant_keys.size()); + + for (uint32_t i = 0; i < octant_keys.size(); i++) { + octant_coords[i] = Vector3i(octant_keys[i].x, octant_keys[i].y, octant_keys[i].z); + } + + return octant_coords; +} + Array GridMap::get_meshes() const { if (mesh_library.is_null()) { return Array(); diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index d4f022c2bf..dc7b8837fd 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -312,6 +312,21 @@ public: TypedArray get_used_cells() const; TypedArray get_used_cells_by_item(int p_item) const; + TypedArray get_used_octants() const; + TypedArray get_used_octants_by_item(int p_item) const; + + TypedArray get_used_cells_in_octant(const Vector3i &p_octant_coords) const; + TypedArray get_used_cells_in_octant_by_item(const Vector3i &p_octant_coords, int p_item) const; + + // Fastpath functions for native modules that do not use Variant/TypedArray. + LocalVector get_index_keys_in_bounds(const AABB &p_bounds, bool p_used_only = true) const; + LocalVector get_octant_keys_in_bounds(const AABB &p_bounds, bool p_used_only = true) const; + + TypedArray get_octants_in_bounds(const AABB &p_bounds) const; + TypedArray get_used_octants_in_bounds(const AABB &p_bounds) const; + + Vector3i get_octant_coords_from_cell_coords(const Vector3i &p_cell_coords) const; + Array get_meshes() const; void clear_baked_meshes(); diff --git a/modules/gridmap/tests/test_grid_map.h b/modules/gridmap/tests/test_grid_map.h new file mode 100644 index 0000000000..460cf83c16 --- /dev/null +++ b/modules/gridmap/tests/test_grid_map.h @@ -0,0 +1,122 @@ +/**************************************************************************/ +/* test_grid_map.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "../grid_map.h" + +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" +#include "scene/resources/3d/primitive_meshes.h" +#include "tests/test_macros.h" + +namespace TestGridMap { + +TEST_CASE("[SceneTree][GridMap][MeshLibrary] Octant querying") { + Ref mesh_library; + mesh_library.instantiate(); + + int item1 = 0; + int item2 = 1; + + { + Vector item_list = mesh_library->get_item_list(); + CHECK(item_list.size() == 0); + } + + mesh_library->create_item(item1); + mesh_library->create_item(item2); + + int last_unused_item_id = mesh_library->get_last_unused_item_id(); + CHECK(last_unused_item_id == 2); + + mesh_library->set_item_name(item1, "Item1"); + mesh_library->set_item_name(item2, "Item2"); + + Ref box_mesh; + box_mesh.instantiate(); + box_mesh->set_size(Vector3(1.0, 1.0, 1.0)); + + mesh_library->set_item_mesh(item1, box_mesh); + mesh_library->set_item_mesh(item2, box_mesh); + + { + Vector item_list = mesh_library->get_item_list(); + CHECK(item_list.size() == 2); + CHECK(item_list[0] == item1); + CHECK(item_list[1] == item2); + } + + GridMap *grid_map = memnew(GridMap); + grid_map->set_mesh_library(mesh_library); + + SceneTree::get_singleton()->get_root()->add_child(grid_map); + + grid_map->set_octant_size(8); + grid_map->set_cell_size(Vector3(1.0, 1.0, 1.0)); + + grid_map->set_cell_item(Vector3i(0, 0, 0), -1); + + CHECK(grid_map->get_used_cells().size() == 0); + CHECK(grid_map->get_used_octants().size() == 0); + + grid_map->set_cell_item(Vector3i(0, 0, 0), item1); + grid_map->set_cell_item(Vector3i(7, 7, 7), item2); + + CHECK(grid_map->get_used_cells().size() == 2); + CHECK(grid_map->get_used_octants().size() == 1); + CHECK(grid_map->get_used_octants()[0] == Vector3i(0, 0, 0)); + + grid_map->set_cell_item(Vector3i(8, 8, 8), item2); + + CHECK(grid_map->get_used_cells().size() == 3); + CHECK(grid_map->get_used_octants().size() == 2); + CHECK(grid_map->get_used_octants()[0] == Vector3i(0, 0, 0)); + CHECK(grid_map->get_used_octants()[1] == Vector3i(1, 1, 1)); + + AABB bounds; + bounds.position = Vector3(0.0, 0.0, 0.0); + bounds.size = Vector3(8.0, 8.0, 8.0); + + CHECK(grid_map->get_octants_in_bounds(bounds).size() == 1); + CHECK(grid_map->get_used_octants_in_bounds(bounds).size() == 1); + CHECK(grid_map->get_octants_in_bounds(bounds)[0] == Vector3i(0, 0, 0)); + + // Edge margin is -CMP_EPSILON. + bounds.size = Vector3(8.001, 8.001, 8.001); + CHECK(grid_map->get_octants_in_bounds(bounds).size() == 8); + CHECK(grid_map->get_used_octants_in_bounds(bounds).size() == 2); + + SceneTree::get_singleton()->get_root()->remove_child(grid_map); + + memdelete(grid_map); +} + +} // namespace TestGridMap