diff --git a/core/math/math_defs.h b/core/math/math_defs.h index d1bbbd40741..040bc6fcc72 100644 --- a/core/math/math_defs.h +++ b/core/math/math_defs.h @@ -3,12 +3,9 @@ /**************************************************************************/ /* This file is part of: */ /* REDOT ENGINE */ -/* https://redotengine.org */ +/* https://www.redotengine.org/ */ /**************************************************************************/ -/* Copyright (c) 2024-present Redot Engine contributors */ -/* (see REDOT_AUTHORS.md) */ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* Copyright © 2024-present by the Redot community. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -29,6 +26,9 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ +/* Work done by Octetdev2 and BSChad. */ +/* Merging done by Wasabi_Cheetah. */ +/**************************************************************************/ #ifndef MATH_DEFS_H #define MATH_DEFS_H @@ -41,6 +41,7 @@ #define Math_SQRT12 0.7071067811865475244008443621048490 #define Math_SQRT2 1.4142135623730950488016887242 +#define Math_SQRT3 1.7320508075688772935274463415059 #define Math_LN2 0.6931471805599453094172321215 #define Math_TAU 6.2831853071795864769252867666 #define Math_PI 3.1415926535897932384626433833 diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index cf13068efa7..321578f15b2 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -69,6 +69,13 @@ The orientation of the cell at the given grid coordinates. [code]-1[/code] is returned if the cell is empty. + + + + + Returns an array of [Vector3i] map coordinates that neighbor the cell at coordinates [param cell], including unused cells. Use [method get_cell_item] to check if the cell is used. + + @@ -116,6 +123,14 @@ Returns an array of all cells with the given item index specified in [param item]. + + + + + + Returns an array containing map coordinates for all cells within the GridMap's local coordinate space that fall within the axis-aligned bounding box defined by points [param local_position_a] and [param local_position_b]. See also [method local_to_map]. + + @@ -200,8 +215,12 @@ The scale of the cell items. This does not affect the size of the grid cells themselves, only the items in them. This can be used to make cell items overlap their neighbors. + + The shape of the grid's cells. + The dimensions of the grid's cells. + When [member cell_shape] is set to Hexagon, the [code]cell_size.x[/code] value represents the radius of the cell (from center to vertex), and the [code]cell_size.y[/code] value represents the height of the cell. The [code]cell_size.z[/code] value is ignored for hexagonal cells. This does not affect the size of the meshes. See [member cell_scale]. @@ -222,6 +241,11 @@ + + + + + @@ -235,6 +259,15 @@ + + Rectangular cell shape. + + + Hexagonal cell shape. + + + Count of all cell shapes. Not a valid selection. + Invalid cell item that can be used in [method set_cell_item] to clear cells (or represent an empty cell in [method get_cell_item]). diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index b94ce38f725..6dc1c3e5ece 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -29,12 +29,14 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ +/* Work done by Octetdev2 and BSChad. */ +/* Merging done by Wasabi_Cheetah. */ +/**************************************************************************/ #include "grid_map_editor_plugin.h" #ifdef TOOLS_ENABLED -#include "core/input/input.h" #include "core/os/keyboard.h" #include "editor/editor_main_screen.h" #include "editor/editor_node.h" @@ -49,6 +51,7 @@ #include "scene/gui/menu_button.h" #include "scene/gui/separator.h" #include "scene/main/window.h" +#include "scene/resources/3d/primitive_meshes.h" void GridMapEditor::_configure() { if (!node) { @@ -63,132 +66,175 @@ void GridMapEditor::_menu_option(int p_option) { case MENU_OPTION_PREV_LEVEL: { floor->set_value(floor->get_value() - 1); if (selection.active && input_action == INPUT_SELECT) { - selection.current[edit_axis]--; - _validate_selection(); + _update_selection(); } } break; case MENU_OPTION_NEXT_LEVEL: { floor->set_value(floor->get_value() + 1); if (selection.active && input_action == INPUT_SELECT) { - selection.current[edit_axis]++; - _validate_selection(); + _update_selection(); } } break; case MENU_OPTION_X_AXIS: + edit_axis = AXIS_X; + update_grid(); + break; case MENU_OPTION_Y_AXIS: - case MENU_OPTION_Z_AXIS: { - int new_axis = p_option - MENU_OPTION_X_AXIS; - for (int i = 0; i < 3; i++) { - int idx = options->get_popup()->get_item_index(MENU_OPTION_X_AXIS + i); - options->get_popup()->set_item_checked(idx, i == new_axis); + edit_axis = AXIS_Y; + update_grid(); + break; + case MENU_OPTION_Z_AXIS: + edit_axis = AXIS_Z; + update_grid(); + break; + case MENU_OPTION_Q_AXIS: + edit_axis = AXIS_Q; + update_grid(); + break; + case MENU_OPTION_R_AXIS: + edit_axis = AXIS_R; + update_grid(); + break; + case MENU_OPTION_S_AXIS: + edit_axis = AXIS_S; + update_grid(); + break; + case MENU_OPTION_ROTATE_AXIS_CW: + switch (edit_axis) { + case AXIS_R: + edit_axis = AXIS_Q; + break; + case AXIS_Q: + edit_axis = AXIS_X; + break; + case AXIS_X: + edit_axis = AXIS_S; + break; + default: + edit_axis = AXIS_R; + break; } - - if (edit_axis != new_axis) { - int item1 = options->get_popup()->get_item_index(MENU_OPTION_NEXT_LEVEL); - int item2 = options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL); - if (edit_axis == Vector3::AXIS_Y) { - options->get_popup()->set_item_text(item1, TTR("Next Plane")); - options->get_popup()->set_item_text(item2, TTR("Previous Plane")); - spin_box_label->set_text(TTR("Plane:")); - } else if (new_axis == Vector3::AXIS_Y) { - options->get_popup()->set_item_text(item1, TTR("Next Floor")); - options->get_popup()->set_item_text(item2, TTR("Previous Floor")); - spin_box_label->set_text(TTR("Floor:")); - } + update_grid(); + break; + case MENU_OPTION_ROTATE_AXIS_CCW: + switch (edit_axis) { + case AXIS_X: + edit_axis = AXIS_Q; + break; + case AXIS_Q: + edit_axis = AXIS_R; + break; + case AXIS_R: + edit_axis = AXIS_S; + break; + default: + edit_axis = AXIS_X; + break; } - edit_axis = Vector3::Axis(new_axis); update_grid(); + break; - } break; case MENU_OPTION_CURSOR_ROTATE_Y: { Basis r; + real_t rotation = Math_PI / (node->get_cell_shape() == GridMap::CELL_SHAPE_SQUARE ? 2.0 : 3.0); + if (input_action == INPUT_PASTE) { r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); - r.rotate(Vector3(0, 1, 0), -Math_PI / 2.0); + r.rotate(Vector3(0, 1, 0), -rotation); paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } r = node->get_basis_with_orthogonal_index(cursor_rot); - r.rotate(Vector3(0, 1, 0), -Math_PI / 2.0); + r.rotate(Vector3(0, 1, 0), -rotation); cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_ROTATE_X: { Basis r; + real_t rotation = node->get_cell_shape() == GridMap::CELL_SHAPE_SQUARE ? (Math_PI / 2.0) : Math_PI; + if (input_action == INPUT_PASTE) { r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); - r.rotate(Vector3(1, 0, 0), -Math_PI / 2.0); + r.rotate(Vector3(1, 0, 0), -rotation); paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } r = node->get_basis_with_orthogonal_index(cursor_rot); - r.rotate(Vector3(1, 0, 0), -Math_PI / 2.0); + r.rotate(Vector3(1, 0, 0), -rotation); cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_ROTATE_Z: { Basis r; + real_t rotation = node->get_cell_shape() == GridMap::CELL_SHAPE_SQUARE ? (Math_PI / 2.0) : Math_PI; + if (input_action == INPUT_PASTE) { r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); - r.rotate(Vector3(0, 0, 1), -Math_PI / 2.0); + r.rotate(Vector3(0, 0, 1), -rotation); paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } r = node->get_basis_with_orthogonal_index(cursor_rot); - r.rotate(Vector3(0, 0, 1), -Math_PI / 2.0); + r.rotate(Vector3(0, 0, 1), -rotation); cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_BACK_ROTATE_Y: { Basis r; + real_t rotation = Math_PI / (node->get_cell_shape() == GridMap::CELL_SHAPE_SQUARE ? 2.0 : 3.0); + if (input_action == INPUT_PASTE) { r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); - r.rotate(Vector3(0, 1, 0), Math_PI / 2.0); + r.rotate(Vector3(0, 1, 0), rotation); paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } r = node->get_basis_with_orthogonal_index(cursor_rot); - r.rotate(Vector3(0, 1, 0), Math_PI / 2.0); + r.rotate(Vector3(0, 1, 0), rotation); cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_BACK_ROTATE_X: { Basis r; + real_t rotation = node->get_cell_shape() == GridMap::CELL_SHAPE_SQUARE ? (Math_PI / 2.0) : Math_PI; + if (input_action == INPUT_PASTE) { r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); - r.rotate(Vector3(1, 0, 0), Math_PI / 2.0); + r.rotate(Vector3(1, 0, 0), rotation); paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } r = node->get_basis_with_orthogonal_index(cursor_rot); - r.rotate(Vector3(1, 0, 0), Math_PI / 2.0); + r.rotate(Vector3(1, 0, 0), rotation); cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_BACK_ROTATE_Z: { Basis r; + real_t rotation = node->get_cell_shape() == GridMap::CELL_SHAPE_SQUARE ? (Math_PI / 2.0) : Math_PI; + if (input_action == INPUT_PASTE) { r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); - r.rotate(Vector3(0, 0, 1), Math_PI / 2.0); + r.rotate(Vector3(0, 0, 1), rotation); paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } r = node->get_basis_with_orthogonal_index(cursor_rot); - r.rotate(Vector3(0, 0, 1), Math_PI / 2.0); + r.rotate(Vector3(0, 0, 1), rotation); cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; @@ -221,10 +267,7 @@ void GridMapEditor::_menu_option(int p_option) { } input_action = INPUT_PASTE; - paste_indicator.click = selection.begin; - paste_indicator.current = selection.begin; - paste_indicator.begin = selection.begin; - paste_indicator.end = selection.end; + paste_indicator.current_cell = selection.begin; paste_indicator.orientation = 0; _update_paste_indicator(); } break; @@ -252,7 +295,7 @@ void GridMapEditor::_menu_option(int p_option) { void GridMapEditor::_update_cursor_transform() { cursor_transform = Transform3D(); - cursor_transform.origin = cursor_origin; + cursor_transform.origin = node->map_to_local(cursor_cell); cursor_transform.basis = node->get_basis_with_orthogonal_index(cursor_rot); cursor_transform.basis *= node->get_cell_scale(); cursor_transform = node->get_global_transform() * cursor_transform; @@ -269,74 +312,69 @@ void GridMapEditor::_update_cursor_transform() { } } -void GridMapEditor::_update_selection_transform() { - Transform3D xf_zero; - xf_zero.basis.set_zero(); - - if (!selection.active) { - RenderingServer::get_singleton()->instance_set_transform(selection_instance, xf_zero); - for (int i = 0; i < 3; i++) { - RenderingServer::get_singleton()->instance_set_transform(selection_level_instance[i], xf_zero); - } - return; - } - - Transform3D xf; - xf.scale((Vector3(1, 1, 1) + (selection.end - selection.begin)) * node->get_cell_size()); - xf.origin = selection.begin * node->get_cell_size(); - - RenderingServer::get_singleton()->instance_set_transform(selection_instance, node->get_global_transform() * xf); - - for (int i = 0; i < 3; i++) { - if (i != edit_axis || (edit_floor[edit_axis] < selection.begin[edit_axis]) || (edit_floor[edit_axis] > selection.end[edit_axis] + 1)) { - RenderingServer::get_singleton()->instance_set_transform(selection_level_instance[i], xf_zero); - } else { - Vector3 scale = (selection.end - selection.begin + Vector3(1, 1, 1)); - scale[edit_axis] = 1.0; - Vector3 position = selection.begin; - position[edit_axis] = edit_floor[edit_axis]; - - scale *= node->get_cell_size(); - position *= node->get_cell_size(); +void GridMapEditor::_update_selection() { + RenderingServer *rs = RS::get_singleton(); - Transform3D xf2; - xf2.basis.scale(scale); - xf2.origin = position; - - RenderingServer::get_singleton()->instance_set_transform(selection_level_instance[i], xf2); - } + if (selection_instance.is_valid()) { + rs->free(selection_instance); } -} -void GridMapEditor::_validate_selection() { if (!selection.active) { return; } - selection.begin = selection.click; - selection.end = selection.current; - if (selection.begin.x > selection.end.x) { - SWAP(selection.begin.x, selection.end.x); - } - if (selection.begin.y > selection.end.y) { - SWAP(selection.begin.y, selection.end.y); - } - if (selection.begin.z > selection.end.z) { - SWAP(selection.begin.z, selection.end.z); + // general scaling and translation for the cell mesh. The translation is + // necessary because the meshes are centered around the origin, and we + // need to shift the cell up. + // + // XXX need to accommodate center_x/center_z also? + Transform3D cell_transform = Transform3D() + .scaled_local(node->get_cell_size()) + .translated(Vector3(0, node->get_center_y() ? 0 : (node->get_cell_size().y / 2.0), 0)); + + // when performing selection on the Q & S axis (hex shaped cells) we need + // the begin cell index to limit the selection to the desired plane. + Vector3i begin = node->local_to_map(selection.begin); + + // get the cells in our selection area + TypedArray cells = node->local_region_to_map(selection.begin, selection.end); + + // add the cells to our selection multimesh + rs->multimesh_allocate_data(selection_multimesh, cells.size(), RS::MULTIMESH_TRANSFORM_3D); + for (int i = 0; i < cells.size(); i++) { + Vector3i cell = cells[i]; + switch (edit_axis) { + case AXIS_Q: + if (cell.x != begin.x) { + continue; + } + break; + case AXIS_S: + if (-cell.x - cell.z != -begin.x - begin.z) { + continue; + } + break; + + default: + break; + } + rs->multimesh_instance_set_transform(selection_multimesh, i, + cell_transform.translated(node->map_to_local(cell))); } - _update_selection_transform(); + // create an instance of the multimesh with the transform of our node + selection_instance = rs->instance_create2(selection_multimesh, get_tree()->get_root()->get_world_3d()->get_scenario()); + rs->instance_set_transform(selection_instance, node->get_global_transform()); + rs->instance_set_layer_mask(selection_instance, Node3DEditorViewport::MISC_TOOL_LAYER); } void GridMapEditor::_set_selection(bool p_active, const Vector3 &p_begin, const Vector3 &p_end) { selection.active = p_active; selection.begin = p_begin; selection.end = p_end; - selection.click = p_begin; - selection.current = p_end; if (is_visible_in_tree()) { - _update_selection_transform(); + _update_selection(); } options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_CLEAR), !selection.active); @@ -368,12 +406,8 @@ bool GridMapEditor::do_input_action(Camera3D *p_camera, const Point2 &p_point, b from = local_xform.xform(from); normal = local_xform.basis.xform(normal).normalized(); - Plane p; - p.normal[edit_axis] = 1.0; - p.d = edit_floor[edit_axis] * node->get_cell_size()[edit_axis]; - Vector3 inters; - if (!p.intersects_segment(from, from + normal * settings_pick_distance->get_value(), &inters)) { + if (!edit_plane.intersects_segment(from, from + normal * settings_pick_distance->get_value(), &inters)) { return false; } @@ -386,25 +420,10 @@ bool GridMapEditor::do_input_action(Camera3D *p_camera, const Point2 &p_point, b } } - int cell[3]; - Vector3 cell_size = node->get_cell_size(); - - for (int i = 0; i < 3; i++) { - if (i == edit_axis) { - cell[i] = edit_floor[i]; - } else { - cell[i] = inters[i] / cell_size[i]; - if (inters[i] < 0) { - cell[i] -= 1; // Compensate negative. - } - grid_ofs[i] = cell[i] * cell_size[i]; - } - } - - RS::get_singleton()->instance_set_transform(grid_instance[edit_axis], node->get_global_transform() * edit_grid_xform); + Vector3i cell = node->local_to_map(inters); if (cursor_instance.is_valid()) { - cursor_origin = (Vector3(cell[0], cell[1], cell[2]) + Vector3(0.5 * node->get_center_x(), 0.5 * node->get_center_y(), 0.5 * node->get_center_z())) * node->get_cell_size(); + cursor_cell = cell; cursor_visible = true; if (input_action == INPUT_SELECT || input_action == INPUT_PASTE) { @@ -415,20 +434,20 @@ bool GridMapEditor::do_input_action(Camera3D *p_camera, const Point2 &p_point, b } if (input_action == INPUT_PASTE) { - paste_indicator.current = Vector3i(cell[0], cell[1], cell[2]); + paste_indicator.current_cell = cell; _update_paste_indicator(); } else if (input_action == INPUT_SELECT) { - selection.current = Vector3i(cell[0], cell[1], cell[2]); if (p_click) { - selection.click = selection.current; + selection.begin = inters; } + selection.end = inters; selection.active = true; - _validate_selection(); + _update_selection(); return true; } else if (input_action == INPUT_PICK) { - int item = node->get_cell_item(Vector3i(cell[0], cell[1], cell[2])); + int item = node->get_cell_item(cell); if (item >= 0) { selected_palette = item; @@ -448,23 +467,23 @@ bool GridMapEditor::do_input_action(Camera3D *p_camera, const Point2 &p_point, b if (input_action == INPUT_PAINT) { SetItem si; - si.position = Vector3i(cell[0], cell[1], cell[2]); + si.position = cell; si.new_value = selected_palette; si.new_orientation = cursor_rot; - si.old_value = node->get_cell_item(Vector3i(cell[0], cell[1], cell[2])); - si.old_orientation = node->get_cell_item_orientation(Vector3i(cell[0], cell[1], cell[2])); + si.old_value = node->get_cell_item(cell); + si.old_orientation = node->get_cell_item_orientation(cell); set_items.push_back(si); - node->set_cell_item(Vector3i(cell[0], cell[1], cell[2]), selected_palette, cursor_rot); + node->set_cell_item(cell, selected_palette, cursor_rot); return true; } else if (input_action == INPUT_ERASE) { SetItem si; si.position = Vector3i(cell[0], cell[1], cell[2]); si.new_value = -1; si.new_orientation = 0; - si.old_value = node->get_cell_item(Vector3i(cell[0], cell[1], cell[2])); - si.old_orientation = node->get_cell_item_orientation(Vector3i(cell[0], cell[1], cell[2])); + si.old_value = node->get_cell_item(cell); + si.old_orientation = node->get_cell_item_orientation(cell); set_items.push_back(si); - node->set_cell_item(Vector3i(cell[0], cell[1], cell[2]), -1); + node->set_cell_item(cell, -1); return true; } @@ -478,15 +497,13 @@ void GridMapEditor::_delete_selection() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("GridMap Delete Selection")); - for (int i = selection.begin.x; i <= selection.end.x; i++) { - for (int j = selection.begin.y; j <= selection.end.y; j++) { - for (int k = selection.begin.z; k <= selection.end.z; k++) { - Vector3i selected = Vector3i(i, j, k); - undo_redo->add_do_method(node, "set_cell_item", selected, GridMap::INVALID_CELL_ITEM); - undo_redo->add_undo_method(node, "set_cell_item", selected, node->get_cell_item(selected), node->get_cell_item_orientation(selected)); - } - } + + TypedArray cells = node->local_region_to_map(selection.begin, selection.end); + for (const Vector3i cell : cells) { + undo_redo->add_do_method(node, "set_cell_item", cell, GridMap::INVALID_CELL_ITEM); + undo_redo->add_undo_method(node, "set_cell_item", cell, node->get_cell_item(cell), node->get_cell_item_orientation(cell)); } + undo_redo->add_do_method(this, "_set_selection", !selection.active, selection.begin, selection.end); undo_redo->add_undo_method(this, "_set_selection", selection.active, selection.begin, selection.end); undo_redo->commit_action(); @@ -499,15 +516,13 @@ void GridMapEditor::_fill_selection() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("GridMap Fill Selection")); - for (int i = selection.begin.x; i <= selection.end.x; i++) { - for (int j = selection.begin.y; j <= selection.end.y; j++) { - for (int k = selection.begin.z; k <= selection.end.z; k++) { - Vector3i selected = Vector3i(i, j, k); - undo_redo->add_do_method(node, "set_cell_item", selected, selected_palette, cursor_rot); - undo_redo->add_undo_method(node, "set_cell_item", selected, node->get_cell_item(selected), node->get_cell_item_orientation(selected)); - } - } + + TypedArray cells = node->local_region_to_map(selection.begin, selection.end); + for (const Vector3i cell : cells) { + undo_redo->add_do_method(node, "set_cell_item", cell, selected_palette, cursor_rot); + undo_redo->add_undo_method(node, "set_cell_item", cell, node->get_cell_item(cell), node->get_cell_item_orientation(cell)); } + undo_redo->add_do_method(this, "_set_selection", !selection.active, selection.begin, selection.end); undo_redo->add_undo_method(this, "_set_selection", selection.active, selection.begin, selection.end); undo_redo->commit_action(); @@ -526,90 +541,89 @@ void GridMapEditor::_set_clipboard_data() { Ref meshLibrary = node->get_mesh_library(); - for (int i = selection.begin.x; i <= selection.end.x; i++) { - for (int j = selection.begin.y; j <= selection.end.y; j++) { - for (int k = selection.begin.z; k <= selection.end.z; k++) { - Vector3i selected = Vector3i(i, j, k); - int itm = node->get_cell_item(selected); - if (itm == GridMap::INVALID_CELL_ITEM) { - continue; - } - - Ref mesh = meshLibrary->get_item_mesh(itm); + RID root = get_tree()->get_root()->get_world_3d()->get_scenario(); - ClipboardItem item; - item.cell_item = itm; - item.grid_offset = Vector3(selected) - selection.begin; - item.orientation = node->get_cell_item_orientation(selected); - item.instance = RenderingServer::get_singleton()->instance_create2(mesh->get_rid(), get_tree()->get_root()->get_world_3d()->get_scenario()); + Vector3 begin = node->map_to_local(node->local_to_map(selection.begin)); + Vector3 end = node->map_to_local(node->local_to_map(selection.end)); + Vector3 selection_center = (end + begin) / 2.0; + Vector3 offset = node->map_to_local(node->local_to_map(selection_center)); - clipboard_items.push_back(item); - } + TypedArray cells = node->local_region_to_map(selection.begin, selection.end); + for (const Vector3i cell : cells) { + int id = node->get_cell_item(cell); + if (id == GridMap::INVALID_CELL_ITEM) { + continue; } + + RID mesh = meshLibrary->get_item_mesh(id)->get_rid(); + RID instance = RS::get_singleton()->instance_create2(mesh, root); + + ClipboardItem item; + item.cell_item = id; + item.grid_offset = node->map_to_local(cell) - offset; + item.orientation = node->get_cell_item_orientation(cell); + item.instance = instance; + clipboard_items.push_back(item); } } void GridMapEditor::_update_paste_indicator() { if (input_action != INPUT_PASTE) { - Transform3D xf; - xf.basis.set_zero(); - RenderingServer::get_singleton()->instance_set_transform(paste_instance, xf); + _clear_clipboard_data(); return; } - Vector3 center = 0.5 * Vector3(real_t(node->get_center_x()), real_t(node->get_center_y()), real_t(node->get_center_z())); - Vector3 scale = (Vector3(1, 1, 1) + (paste_indicator.end - paste_indicator.begin)) * node->get_cell_size(); - Transform3D xf; - xf.scale(scale); - xf.origin = (paste_indicator.begin + (paste_indicator.current - paste_indicator.click) + center) * node->get_cell_size(); - Basis rot; - rot = node->get_basis_with_orthogonal_index(paste_indicator.orientation); - xf.basis = rot * xf.basis; - xf.translate_local((-center * node->get_cell_size()) / scale); - - RenderingServer::get_singleton()->instance_set_transform(paste_instance, node->get_global_transform() * xf); + Vector3 cursor = node->map_to_local(paste_indicator.current_cell); + Basis paste_rotation = node->get_basis_with_orthogonal_index(paste_indicator.orientation); for (const ClipboardItem &item : clipboard_items) { - xf = Transform3D(); - xf.origin = (paste_indicator.begin + (paste_indicator.current - paste_indicator.click) + center) * node->get_cell_size(); - xf.basis = rot * xf.basis; - xf.translate_local(item.grid_offset * node->get_cell_size()); + // move the item to the cursor, then apply paste rotation, then + // translate by the item's cell offset. + Transform3D xf; + xf.origin = cursor; + xf.basis = paste_rotation * xf.basis; + xf.translate_local(item.grid_offset); - Basis item_rot; - item_rot = node->get_basis_with_orthogonal_index(item.orientation); - xf.basis = item_rot * xf.basis * node->get_cell_scale(); + xf.basis = node->get_basis_with_orthogonal_index(item.orientation) * xf.basis; - RenderingServer::get_singleton()->instance_set_transform(item.instance, node->get_global_transform() * xf); + RS::get_singleton()->instance_set_transform(item.instance, xf); } } void GridMapEditor::_do_paste() { - int idx = options->get_popup()->get_item_index(MENU_OPTION_PASTE_SELECTS); - bool reselect = options->get_popup()->is_item_checked(idx); - - Basis rot; - rot = node->get_basis_with_orthogonal_index(paste_indicator.orientation); + Vector3 cursor = node->map_to_local(paste_indicator.current_cell); + Basis paste_rotation = node->get_basis_with_orthogonal_index(paste_indicator.orientation); - Vector3 ofs = paste_indicator.current - paste_indicator.click; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("GridMap Paste Selection")); - for (const ClipboardItem &item : clipboard_items) { - Vector3 position = rot.xform(item.grid_offset) + paste_indicator.begin + ofs; - - Basis orm; - orm = node->get_basis_with_orthogonal_index(item.orientation); - orm = rot * orm; + // track the bounds of the paste region in case we need to select it below + AABB bounds; + bounds.set_position(cursor); - undo_redo->add_do_method(node, "set_cell_item", position, item.cell_item, node->get_orthogonal_index_from_basis(orm)); - undo_redo->add_undo_method(node, "set_cell_item", position, node->get_cell_item(position), node->get_cell_item_orientation(position)); + for (const ClipboardItem &item : clipboard_items) { + // apply paste rotation & convert it to a cell index + Vector3 position = paste_rotation.xform(item.grid_offset) + cursor; + Vector3i cell = node->local_to_map(position); + bounds.expand_to(position); + + // apply paste rotation to existing cell rotation to get cell orientation + Basis cell_rotation = paste_rotation * + node->get_basis_with_orthogonal_index(item.orientation); + int cell_orientation = node->get_orthogonal_index_from_basis(cell_rotation); + + undo_redo->add_do_method(node, "set_cell_item", cell, item.cell_item, cell_orientation); + undo_redo->add_undo_method(node, "set_cell_item", cell, node->get_cell_item(cell), node->get_cell_item_orientation(cell)); } - if (reselect) { - undo_redo->add_do_method(this, "_set_selection", true, paste_indicator.begin + ofs, paste_indicator.end + ofs); + // if "Paste Selects" option is checked, update the selection to reflect + // the pasted region. + int option_index = options->get_popup()->get_item_index(MENU_OPTION_PASTE_SELECTS); + if (options->get_popup()->is_item_checked(option_index)) { + Vector3 begin = bounds.position, end = bounds.get_end(); + undo_redo->add_do_method(this, "_set_selection", true, begin, end); undo_redo->add_undo_method(this, "_set_selection", selection.active, selection.begin, selection.end); } - undo_redo->commit_action(); _clear_clipboard_data(); @@ -724,9 +738,6 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D Ref mm = p_event; if (mm.is_valid()) { - // Update the grid, to check if the grid needs to be moved to a tile cursor. - update_grid(); - if (do_input_action(p_camera, mm->get_position(), false)) { return EditorPlugin::AFTER_GUI_INPUT_STOP; } @@ -948,6 +959,7 @@ void GridMapEditor::_update_mesh_library() { void GridMapEditor::edit(GridMap *p_gridmap) { if (node) { node->disconnect(SNAME("cell_size_changed"), callable_mp(this, &GridMapEditor::_draw_grids)); + node->disconnect(SNAME("cell_shape_changed"), callable_mp(this, &GridMapEditor::_update_cell_shape)); node->disconnect(CoreStringName(changed), callable_mp(this, &GridMapEditor::_update_mesh_library)); if (mesh_library.is_valid()) { mesh_library->disconnect_changed(callable_mp(this, &GridMapEditor::update_palette)); @@ -959,8 +971,10 @@ void GridMapEditor::edit(GridMap *p_gridmap) { input_action = INPUT_NONE; selection.active = false; - _update_selection_transform(); + _build_selection_meshes(); + _update_selection(); _update_paste_indicator(); + _update_options_menu(); spatial_editor = Object::cast_to(EditorNode::get_singleton()->get_editor_main_screen()->get_selected_plugin()); @@ -982,81 +996,378 @@ void GridMapEditor::edit(GridMap *p_gridmap) { set_process(true); + // load any previous floor values + TypedArray floors = node->get_meta("_editor_floor_", TypedArray()); + for (int i = 0; i < MIN(floors.size(), AXIS_MAX); i++) { + edit_floor[i] = floors[i]; + } _draw_grids(node->get_cell_size()); update_grid(); node->connect(SNAME("cell_size_changed"), callable_mp(this, &GridMapEditor::_draw_grids)); + node->connect(SNAME("cell_shape_changed"), callable_mp(this, &GridMapEditor::_update_cell_shape)); node->connect(CoreStringName(changed), callable_mp(this, &GridMapEditor::_update_mesh_library)); _update_mesh_library(); } void GridMapEditor::update_grid() { - grid_xform.origin.x -= 1; // Force update in hackish way. + RenderingServer *rs = RS::get_singleton(); + Vector3 cell_size = node->get_cell_size(); + bool is_hex = node->get_cell_shape() == GridMap::CELL_SHAPE_HEXAGON; + + // Hex planes Q, R, and S need to offset the grid by half a cell on even + // numbered floors. We calculate this value here to simplify the code + // later. + int is_even_floor = (edit_floor[edit_axis] & 1) == 0; + + // hide the active grid + rs->instance_set_visible(active_grid_instance, false); + + real_t cell_depth; + Transform3D grid_transform; + Menu menu_axis; + + // switch the edit plane and pick the new active grid and rotate if necessary + switch (edit_axis) { + case AXIS_X: + // set which grid to display + active_grid_instance = grid_instance[0]; + // set the edit plane normal, and cell depth (used by the plane) + edit_plane.normal = Vector3(1, 0, 0); + cell_depth = is_hex ? (SQRT3_2 * cell_size.x) : cell_size.x; + // shift the edit grid based on which floor we are on + if (is_hex && !is_even_floor) { + grid_transform.translate_local(Vector3(0, 0, 1.5 * cell_size.x)); + } + // update the menu + menu_axis = MENU_OPTION_X_AXIS; + break; + case AXIS_Y: + active_grid_instance = grid_instance[1]; + edit_plane.normal = Vector3(0, 1, 0); + cell_depth = cell_size.y; + menu_axis = MENU_OPTION_Y_AXIS; + break; + case AXIS_Z: + active_grid_instance = grid_instance[2]; + edit_plane.normal = Vector3(0, 0, 1); + cell_depth = cell_size.z; + menu_axis = MENU_OPTION_Z_AXIS; + break; + case AXIS_Q: // hex plane, northwest to southeast + active_grid_instance = grid_instance[2]; + edit_plane.normal = Vector3(SQRT3_2, 0, -0.5).normalized(); + cell_depth = 1.5 * cell_size.x; + grid_transform.rotate(Vector3(0, 1, 0), -Math_PI / 3.0); + // offset the edit grid on even numbered floors by half a cell + grid_transform.translate_local(Vector3(is_even_floor * SQRT3_2 * cell_size.x, 0, 0)); + menu_axis = MENU_OPTION_Q_AXIS; + break; + case AXIS_R: // hex plane, east to west; same as AXIS_Z, but for hex + active_grid_instance = grid_instance[2]; + edit_plane.normal = Vector3(0, 0, 1); + cell_depth = 1.5 * cell_size.x; + grid_transform.translate_local(Vector3(is_even_floor * SQRT3_2 * cell_size.x, 0, 0)); + menu_axis = MENU_OPTION_R_AXIS; + break; + case AXIS_S: // hex plane, southwest to northeast + active_grid_instance = grid_instance[2]; + edit_plane.normal = Vector3(SQRT3_2, 0, 0.5).normalized(); + cell_depth = 1.5 * cell_size.x; + grid_transform.rotate(Vector3(0, 1, 0), Math_PI / 3.0); + grid_transform.translate_local(Vector3(is_even_floor * SQRT3_2 * cell_size.x, 0, 0)); + menu_axis = MENU_OPTION_S_AXIS; + break; + default: + ERR_PRINT_ED("unsupported edit plane axis"); + return; + } - grid_ofs[edit_axis] = edit_floor[edit_axis] * node->get_cell_size()[edit_axis]; + // update the depth of the edit plane so it matches the floor, and update + // the grid transform for the depth. + edit_plane.d = edit_floor[edit_axis] * cell_depth; + grid_transform.origin += edit_plane.normal * edit_plane.d; + + // shift the edit plane a little into the cell to prevent floating point + // errors from causing the raycast to fall into the lower cell. Note we + // only need to do this when the grid is drawn along the edge of a cell, + // so the Y & X axis, or any square shape cell. Hex cells draw the grid + // through the middle of the cells for Q/R/S. + if (edit_axis == AXIS_Y || edit_axis == AXIS_X || !is_hex) { + edit_plane.d += cell_depth * 0.1; + } - // If there's a valid tile cursor, offset the grid, otherwise move it back to the node. - edit_grid_xform.origin = cursor_instance.is_valid() ? grid_ofs : Vector3(); - edit_grid_xform.basis = Basis(); + // make the editing grid visible + RenderingServer::get_singleton() + ->instance_set_visible(active_grid_instance, true); + RenderingServer::get_singleton()->instance_set_transform(active_grid_instance, + node->get_global_transform() * grid_transform); - for (int i = 0; i < 3; i++) { - RenderingServer::get_singleton()->instance_set_visible(grid_instance[i], i == edit_axis); + // update the UI floor indicator + floor->set_value(edit_floor[edit_axis]); + + // update the option menu to show the correct axis is selected + PopupMenu *popup = options->get_popup(); + for (int i = MENU_OPTION_X_AXIS; i <= MENU_OPTION_S_AXIS; i++) { + int index = popup->get_item_index(i); + if (index != -1) { + popup->set_item_checked(index, menu_axis == i); + } } +} - updating = true; - floor->set_value(edit_floor[edit_axis]); - updating = false; +void GridMapEditor::_draw_hex_grid(RID p_mesh_id, const Vector3 &p_cell_size) { + // create the points that make up the top of a hex cell + Vector shape_points; + shape_points.append(Vector3(0.0, 0, -1.0) * p_cell_size); + shape_points.append(Vector3(-SQRT3_2, 0, -0.5) * p_cell_size); + shape_points.append(Vector3(-SQRT3_2, 0, 0.5) * p_cell_size); + shape_points.append(Vector3(0.0, 0, 1.0) * p_cell_size); + shape_points.append(Vector3(SQRT3_2, 0, 0.5) * p_cell_size); + shape_points.append(Vector3(SQRT3_2, 0, -0.5) * p_cell_size); + + Vector grid_points; + TypedArray cells = node->local_region_to_map( + Vector3i(-GRID_CURSOR_SIZE * Math_SQRT3 * p_cell_size.x, + 0, + -GRID_CURSOR_SIZE * 1.625 * p_cell_size.x), + Vector3i(GRID_CURSOR_SIZE * Math_SQRT3 * p_cell_size.x, + 0, + GRID_CURSOR_SIZE * 1.625 * p_cell_size.x)); + for (const Vector3i cell : cells) { + Vector3 center = node->map_to_local(cell); + + for (int j = 1; j < shape_points.size(); j++) { + grid_points.append(center + shape_points[j - 1]); + grid_points.append(center + shape_points[j]); + } + } + + Array d; + d.resize(RS::ARRAY_MAX); + d[RS::ARRAY_VERTEX] = grid_points; + RenderingServer::get_singleton()->mesh_add_surface_from_arrays(p_mesh_id, RenderingServer::PRIMITIVE_LINES, d); + RenderingServer::get_singleton()->mesh_surface_set_material(p_mesh_id, 0, indicator_mat->get_rid()); } -void GridMapEditor::_draw_grids(const Vector3 &cell_size) { - Vector3 edited_floor = node->get_meta("_editor_floor_", Vector3()); +void GridMapEditor::_draw_hex_x_axis_grid(RID p_mesh_id, const Vector3 &p_cell_size) { + Vector grid_points; - for (int i = 0; i < 3; i++) { - RS::get_singleton()->mesh_clear(grid[i]); - edit_floor[i] = edited_floor[i]; + // draw horizontal lines + for (int y_index = -GRID_CURSOR_SIZE; y_index <= GRID_CURSOR_SIZE; y_index++) { + real_t y = y_index * p_cell_size.y; + grid_points.append(Vector3(0, y, -GRID_CURSOR_SIZE * 1.625 * p_cell_size.x)); + grid_points.append(Vector3(0, y, GRID_CURSOR_SIZE * 1.625 * p_cell_size.x)); + } + + // for vertical lines, we'll need to know where the center of the cell is + // for a line along the Z axis. + TypedArray cells = node->local_region_to_map( + Vector3(0, 0.001, -GRID_CURSOR_SIZE * 1.625 * p_cell_size.x), + Vector3(0, 0.002, GRID_CURSOR_SIZE * 1.625 * p_cell_size.x)); + + // use the cell list to draw the vertical lines + for (const Vector3i cell : cells) { + // grab the z coordinate for the center of the cell + real_t z = node->map_to_local(cell).z; + + // Adjust from the center of the cell to where the line should fall. + // We're drawing lines at 1 radius, then 2 radius apart, alternating. + if ((cell.z & 1) == 0) { + z += p_cell_size.x; + } else { + z += p_cell_size.x / 2; + } + + grid_points.append(Vector3(0, -GRID_CURSOR_SIZE * p_cell_size.y, z)); + grid_points.append(Vector3(0, GRID_CURSOR_SIZE * p_cell_size.y, z)); + } + + Array d; + d.resize(RS::ARRAY_MAX); + d[RS::ARRAY_VERTEX] = grid_points; + RenderingServer::get_singleton()->mesh_add_surface_from_arrays(p_mesh_id, RenderingServer::PRIMITIVE_LINES, d); + RenderingServer::get_singleton()->mesh_surface_set_material(p_mesh_id, 0, indicator_mat->get_rid()); +} + +void GridMapEditor::_draw_plane_grid( + RID p_mesh_id, + const Vector3 &p_axis_n1, + const Vector3 &p_axis_n2, + const Vector3 &cell_size) { + Vector grid_points; + + Vector3 axis_n1 = p_axis_n1 * cell_size; + Vector3 axis_n2 = p_axis_n2 * cell_size; + + for (int j = -GRID_CURSOR_SIZE; j <= GRID_CURSOR_SIZE; j++) { + for (int k = -GRID_CURSOR_SIZE; k <= GRID_CURSOR_SIZE; k++) { + Vector3 p = axis_n1 * j + axis_n2 * k; + + Vector3 pj = axis_n1 * (j + 1) + axis_n2 * k; + + Vector3 pk = axis_n1 * j + axis_n2 * (k + 1); + + grid_points.push_back(p); + grid_points.push_back(pk); + + grid_points.push_back(p); + grid_points.push_back(pj); + } } - Vector grid_points[3]; - Vector grid_colors[3]; + Array d; + d.resize(RS::ARRAY_MAX); + d[RS::ARRAY_VERTEX] = grid_points; + RenderingServer::get_singleton()->mesh_add_surface_from_arrays(p_mesh_id, RenderingServer::PRIMITIVE_LINES, d); + RenderingServer::get_singleton()->mesh_surface_set_material(p_mesh_id, 0, indicator_mat->get_rid()); +} +void GridMapEditor::_draw_grids(const Vector3 &p_cell_size) { for (int i = 0; i < 3; i++) { - Vector3 axis; - axis[i] = 1; - Vector3 axis_n1; - axis_n1[(i + 1) % 3] = cell_size[(i + 1) % 3]; - Vector3 axis_n2; - axis_n2[(i + 2) % 3] = cell_size[(i + 2) % 3]; - - for (int j = -GRID_CURSOR_SIZE; j <= GRID_CURSOR_SIZE; j++) { - for (int k = -GRID_CURSOR_SIZE; k <= GRID_CURSOR_SIZE; k++) { - Vector3 p = axis_n1 * j + axis_n2 * k; - float trans = Math::pow(MAX(0, 1.0 - (Vector2(j, k).length() / GRID_CURSOR_SIZE)), 2); - - Vector3 pj = axis_n1 * (j + 1) + axis_n2 * k; - float transj = Math::pow(MAX(0, 1.0 - (Vector2(j + 1, k).length() / GRID_CURSOR_SIZE)), 2); - - Vector3 pk = axis_n1 * j + axis_n2 * (k + 1); - float transk = Math::pow(MAX(0, 1.0 - (Vector2(j, k + 1).length() / GRID_CURSOR_SIZE)), 2); - - grid_points[i].push_back(p); - grid_points[i].push_back(pk); - grid_colors[i].push_back(Color(1, 1, 1, trans)); - grid_colors[i].push_back(Color(1, 1, 1, transk)); - - grid_points[i].push_back(p); - grid_points[i].push_back(pj); - grid_colors[i].push_back(Color(1, 1, 1, trans)); - grid_colors[i].push_back(Color(1, 1, 1, transj)); - } + RS::get_singleton()->mesh_clear(grid_mesh[i]); + } + + switch (node->get_cell_shape()) { + case GridMap::CELL_SHAPE_SQUARE: + _draw_plane_grid(grid_mesh[0], Vector3(0, 1, 0), Vector3(0, 0, 1), p_cell_size); + _draw_plane_grid(grid_mesh[1], Vector3(1, 0, 0), Vector3(0, 0, 1), p_cell_size); + _draw_plane_grid(grid_mesh[2], Vector3(1, 0, 0), Vector3(0, 1, 0), p_cell_size); + break; + case GridMap::CELL_SHAPE_HEXAGON: { + real_t radius = p_cell_size.x; + Vector3 cell_size = Vector3(Math_SQRT3 * radius, p_cell_size.y, Math_SQRT3 * radius); + _draw_hex_x_axis_grid(grid_mesh[0], p_cell_size); + _draw_hex_grid(grid_mesh[1], p_cell_size); + _draw_plane_grid(grid_mesh[2], Vector3(1, 0, 0), Vector3(0, 1, 0), cell_size); + break; } + default: + ERR_PRINT_ED("unsupported cell shape"); + return; + } +} + +void GridMapEditor::_update_cell_shape(const GridMap::CellShape cell_shape) { + _draw_grids(node->get_cell_size()); + _build_selection_meshes(); + edit_axis = AXIS_Y; + _update_options_menu(); + selection.active = false; + _update_selection(); +} + +void GridMapEditor::_build_selection_meshes() { + if (selection_tile_mesh.is_valid()) { + RS::get_singleton()->free(selection_tile_mesh); + selection_tile_mesh = RID(); + } + if (selection_multimesh.is_valid()) { + RS::get_singleton()->free(selection_multimesh); + selection_multimesh = RID(); + } + + // we can get called when node is null + if (node == NULL) { + return; + } - Array d; - d.resize(RS::ARRAY_MAX); - d[RS::ARRAY_VERTEX] = grid_points[i]; - d[RS::ARRAY_COLOR] = grid_colors[i]; - RenderingServer::get_singleton()->mesh_add_surface_from_arrays(grid[i], RenderingServer::PRIMITIVE_LINES, d); - RenderingServer::get_singleton()->mesh_surface_set_material(grid[i], 0, indicator_mat->get_rid()); + Array mesh_array; + mesh_array.resize(RS::ARRAY_MAX); + Array lines_array; + lines_array.resize(RS::ARRAY_MAX); + + switch (node->get_cell_shape()) { + case GridMap::CELL_SHAPE_SQUARE: { + BoxMesh::create_mesh_array(mesh_array, Vector3(1, 1, 1)); + + /* + * (2)-----(3) Y + * | \ | \ | + * | (1)-----(0) o---X + * | | | | \ + * (6)--|--(7) | Z + * \ | \ | + * (5)-----(4) + */ + lines_array[RS::ARRAY_VERTEX] = Vector({ + Vector3(0.5, 0.5, 0.5), // 0 + Vector3(-0.5, 0.5, 0.5), // 1 + Vector3(-0.5, 0.5, -0.5), // 2 + Vector3(0.5, 0.5, -0.5), // 3 + Vector3(0.5, -0.5, 0.5), // 4 + Vector3(-0.5, -0.5, 0.5), // 5 + Vector3(-0.5, -0.5, -0.5), // 6 + Vector3(0.5, -0.5, -0.5) // 7 + }); + lines_array[RS::ARRAY_INDEX] = Vector({ + 0, 1, 2, 3, // top + 7, 4, 5, 6, // bottom + 7, 3, 0, 4, // right + 5, 1, 2, 6, // left + }); + break; + } + case GridMap::CELL_SHAPE_HEXAGON: + CylinderMesh::create_mesh_array(mesh_array, 1.0, 1.0, 1, 6, 1); + + /* + * (0) Y + * / \ | + * (1) (5) o---X + * | | \ + * (2) (4) Z + * | \ / | + * | (3) | + * | | | + * | (6) | + * | / | \ | + * (7) | (b) + * | | | + * (8) | (a) + * \ | / + * (9) + */ + + lines_array[RS::ARRAY_VERTEX] = Vector({ + Vector3(0.0, 0.5, -1.0), // 0 + Vector3(-SQRT3_2, 0.5, -0.5), // 1 + Vector3(-SQRT3_2, 0.5, 0.5), // 2 + Vector3(0.0, 0.5, 1.0), // 3 + Vector3(SQRT3_2, 0.5, 0.5), // 4 + Vector3(SQRT3_2, 0.5, -0.5), // 5 + Vector3(0.0, -0.5, -1.0), // 6 + Vector3(-SQRT3_2, -0.5, -0.5), // 7 + Vector3(-SQRT3_2, -0.5, 0.5), // 8 + Vector3(0.0, -0.5, 1.0), // 9 + Vector3(SQRT3_2, -0.5, 0.5), // 10 (0xa) + Vector3(SQRT3_2, -0.5, -0.5), // 11 (0xb) + }); + lines_array[RS::ARRAY_INDEX] = Vector({ + 0, 1, 2, 3, 4, 5, // top + 11, 6, 7, 8, 9, 10, // bottom + 11, 5, 0, 6, // northeast face + 7, 1, 2, 8, // west face + 9, 3, 4, 10, // southeast face + }); + break; + default: + ERR_PRINT_ED("unsupported cell shape"); + return; } + + RenderingServer *rs = RS::get_singleton(); + selection_tile_mesh = rs->mesh_create(); + rs->mesh_add_surface_from_arrays(selection_tile_mesh, RS::PRIMITIVE_TRIANGLES, mesh_array); + rs->mesh_surface_set_material(selection_tile_mesh, 0, inner_mat->get_rid()); + + // add lines around the cell + rs->mesh_add_surface_from_arrays(selection_tile_mesh, RS::PRIMITIVE_LINE_STRIP, lines_array); + rs->mesh_surface_set_material(selection_tile_mesh, 1, outer_mat->get_rid()); + + // create the multimesh for rendering the tile mesh in multiple locations. + selection_multimesh = rs->multimesh_create(); + rs->multimesh_set_mesh(selection_multimesh, selection_tile_mesh); } void GridMapEditor::_update_theme() { @@ -1071,21 +1382,15 @@ void GridMapEditor::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { mesh_library_palette->connect(SceneStringName(item_selected), callable_mp(this, &GridMapEditor::_item_selected_cbk)); for (int i = 0; i < 3; i++) { - grid[i] = RS::get_singleton()->mesh_create(); - grid_instance[i] = RS::get_singleton()->instance_create2(grid[i], get_tree()->get_root()->get_world_3d()->get_scenario()); + grid_mesh[i] = RS::get_singleton()->mesh_create(); + grid_instance[i] = RS::get_singleton()->instance_create2(grid_mesh[i], get_tree()->get_root()->get_world_3d()->get_scenario()); RenderingServer::get_singleton()->instance_set_layer_mask(grid_instance[i], 1 << Node3DEditorViewport::MISC_TOOL_LAYER); - selection_level_instance[i] = RenderingServer::get_singleton()->instance_create2(selection_level_mesh[i], get_tree()->get_root()->get_world_3d()->get_scenario()); - RenderingServer::get_singleton()->instance_set_layer_mask(selection_level_instance[i], 1 << Node3DEditorViewport::MISC_TOOL_LAYER); + RenderingServer::get_singleton()->instance_set_visible(grid_instance[i], false); } - - selection_instance = RenderingServer::get_singleton()->instance_create2(selection_mesh, get_tree()->get_root()->get_world_3d()->get_scenario()); - RenderingServer::get_singleton()->instance_set_layer_mask(selection_instance, 1 << Node3DEditorViewport::MISC_TOOL_LAYER); - paste_instance = RenderingServer::get_singleton()->instance_create2(paste_mesh, get_tree()->get_root()->get_world_3d()->get_scenario()); - RenderingServer::get_singleton()->instance_set_layer_mask(paste_instance, 1 << Node3DEditorViewport::MISC_TOOL_LAYER); - - _update_selection_transform(); + _update_selection(); _update_paste_indicator(); _update_theme(); + _update_options_menu(); } break; case NOTIFICATION_EXIT_TREE: { @@ -1093,16 +1398,13 @@ void GridMapEditor::_notification(int p_what) { for (int i = 0; i < 3; i++) { RS::get_singleton()->free(grid_instance[i]); - RS::get_singleton()->free(grid[i]); + RS::get_singleton()->free(grid_mesh[i]); grid_instance[i] = RID(); - grid[i] = RID(); - RenderingServer::get_singleton()->free(selection_level_instance[i]); + grid_mesh[i] = RID(); } - RenderingServer::get_singleton()->free(selection_instance); - RenderingServer::get_singleton()->free(paste_instance); - selection_instance = RID(); - paste_instance = RID(); + selection.active = false; + _update_selection(); } break; case NOTIFICATION_PROCESS: { @@ -1110,13 +1412,13 @@ void GridMapEditor::_notification(int p_what) { return; } - Transform3D xf = node->get_global_transform(); - - if (xf != grid_xform) { - for (int i = 0; i < 3; i++) { - RS::get_singleton()->instance_set_transform(grid_instance[i], xf * edit_grid_xform); - } - grid_xform = xf; + // if the transform of our GridMap node has been changed, update + // the grid. + Transform3D transform = node->get_global_transform(); + if (transform != node_global_transform) { + node_global_transform = transform; + update_grid(); + _update_selection(); } } break; @@ -1157,21 +1459,26 @@ void GridMapEditor::_update_cursor_instance() { } } -void GridMapEditor::_item_selected_cbk(int idx) { - selected_palette = mesh_library_palette->get_item_metadata(idx); +void GridMapEditor::_item_selected_cbk(int p_idx) { + selected_palette = mesh_library_palette->get_item_metadata(p_idx); _update_cursor_instance(); } void GridMapEditor::_floor_changed(float p_value) { - if (updating) { - return; + // update the floor number for the current plane we're editing + edit_floor[edit_axis] = p_value; + + // save off editor floor numbers so the user can jump in and out of the + // gridmap editor without losing their place. + TypedArray floors; + for (int i = 0; i < AXIS_MAX; i++) { + floors.push_back(edit_floor[i]); } + node->set_meta("_editor_floor_", floors); - edit_floor[edit_axis] = p_value; - node->set_meta("_editor_floor_", Vector3(edit_floor[0], edit_floor[1], edit_floor[2])); update_grid(); - _update_selection_transform(); + _update_selection(); } void GridMapEditor::_floor_mouse_exited() { @@ -1183,12 +1490,75 @@ void GridMapEditor::_bind_methods() { ClassDB::bind_method("_set_selection", &GridMapEditor::_set_selection); } +void GridMapEditor::_update_options_menu() { + PopupMenu *popup = options->get_popup(); + + // save off the current settings + bool paste_selects = false; + if (int index = popup->get_item_index(MENU_OPTION_PASTE_SELECTS) != -1) { + popup->is_item_checked(index); + } + + // clear the menu + popup->clear(); + + // rebuild the menu + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/previous_floor"), MENU_OPTION_PREV_LEVEL); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/next_floor"), MENU_OPTION_NEXT_LEVEL); + popup->add_separator(); + + // shape-specific edit axis options + if (node && node->get_cell_shape() == GridMap::CELL_SHAPE_HEXAGON) { + // hex cells have five edit axis; we add shortcuts for Y, two more for + // rotating a vertical plane clockwise and counter clockwise. + popup->add_radio_check_item(TTR("Edit X Axis"), MENU_OPTION_X_AXIS); + popup->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_y_axis"), MENU_OPTION_Y_AXIS); + popup->add_radio_check_item(TTR("Edit Q Axis"), MENU_OPTION_Q_AXIS); + popup->add_radio_check_item(TTR("Edit R Axis (Z Axis)"), MENU_OPTION_R_AXIS); + popup->add_radio_check_item(TTR("Edit S Axis"), MENU_OPTION_S_AXIS); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/edit_plane_rotate_cw"), MENU_OPTION_ROTATE_AXIS_CW); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/edit_plane_rotate_ccw"), MENU_OPTION_ROTATE_AXIS_CCW); + } else { + // square cell shape only uses XYZ with per-plane shortcuts + popup->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_x_axis"), MENU_OPTION_X_AXIS); + popup->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_y_axis"), MENU_OPTION_Y_AXIS); + popup->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_z_axis"), MENU_OPTION_Z_AXIS); + } + popup->set_item_checked(popup->get_item_index(MENU_OPTION_Y_AXIS), true); + + popup->add_separator(); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_x"), MENU_OPTION_CURSOR_ROTATE_X); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_y"), MENU_OPTION_CURSOR_ROTATE_Y); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_z"), MENU_OPTION_CURSOR_ROTATE_Z); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_x"), MENU_OPTION_CURSOR_BACK_ROTATE_X); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_y"), MENU_OPTION_CURSOR_BACK_ROTATE_Y); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_clear_rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION); + popup->add_separator(); + // TRANSLATORS: This is a toggle to select after pasting the new content. + popup->add_check_shortcut(ED_GET_SHORTCUT("grid_map/paste_selects"), MENU_OPTION_PASTE_SELECTS); + popup->set_item_checked(popup->get_item_index(MENU_OPTION_PASTE_SELECTS), paste_selects); + popup->add_separator(); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/duplicate_selection"), MENU_OPTION_SELECTION_DUPLICATE); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/cut_selection"), MENU_OPTION_SELECTION_CUT); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/clear_selection"), MENU_OPTION_SELECTION_CLEAR); + popup->add_shortcut(ED_GET_SHORTCUT("grid_map/fill_selection"), MENU_OPTION_SELECTION_FILL); + + popup->add_separator(); + popup->add_item(TTR("Settings..."), MENU_OPTION_GRIDMAP_SETTINGS); +} + GridMapEditor::GridMapEditor() { ED_SHORTCUT("grid_map/previous_floor", TTR("Previous Floor"), Key::Q, true); ED_SHORTCUT("grid_map/next_floor", TTR("Next Floor"), Key::E, true); ED_SHORTCUT("grid_map/edit_x_axis", TTR("Edit X Axis"), Key::Z, true); ED_SHORTCUT("grid_map/edit_y_axis", TTR("Edit Y Axis"), Key::X, true); ED_SHORTCUT("grid_map/edit_z_axis", TTR("Edit Z Axis"), Key::C, true); + + // TRANSLATORS: These two shortcuts are only used with hex-shaped cells to rotate an edit plane about the Y axis clockwise or counter-clockwise. + ED_SHORTCUT("grid_map/edit_plane_rotate_cw", TTR("Rotate Edit Plane Clockwise"), Key::C, true); + ED_SHORTCUT("grid_map/edit_plane_rotate_ccw", TTR("Rotate Edit Plane Counter-Clockwise"), Key::Z, true); + ED_SHORTCUT("grid_map/cursor_rotate_x", TTR("Cursor Rotate X"), Key::A, true); ED_SHORTCUT("grid_map/cursor_rotate_y", TTR("Cursor Rotate Y"), Key::S, true); ED_SHORTCUT("grid_map/cursor_rotate_z", TTR("Cursor Rotate Z"), Key::D, true); @@ -1234,29 +1604,7 @@ GridMapEditor::GridMapEditor() { spatial_editor_hb->hide(); options->set_text(TTR("Grid Map")); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/previous_floor"), MENU_OPTION_PREV_LEVEL); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/next_floor"), MENU_OPTION_NEXT_LEVEL); - options->get_popup()->add_separator(); - options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_x_axis"), MENU_OPTION_X_AXIS); - options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_y_axis"), MENU_OPTION_Y_AXIS); - options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_z_axis"), MENU_OPTION_Z_AXIS); - options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_Y_AXIS), true); - options->get_popup()->add_separator(); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_x"), MENU_OPTION_CURSOR_ROTATE_X); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_y"), MENU_OPTION_CURSOR_ROTATE_Y); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_z"), MENU_OPTION_CURSOR_ROTATE_Z); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_x"), MENU_OPTION_CURSOR_BACK_ROTATE_X); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_y"), MENU_OPTION_CURSOR_BACK_ROTATE_Y); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_clear_rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION); - options->get_popup()->add_separator(); - // TRANSLATORS: This is a toggle to select after pasting the new content. - options->get_popup()->add_check_shortcut(ED_GET_SHORTCUT("grid_map/paste_selects"), MENU_OPTION_PASTE_SELECTS); - options->get_popup()->add_separator(); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/duplicate_selection"), MENU_OPTION_SELECTION_DUPLICATE); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cut_selection"), MENU_OPTION_SELECTION_CUT); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/clear_selection"), MENU_OPTION_SELECTION_CLEAR); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/fill_selection"), MENU_OPTION_SELECTION_FILL); + _update_options_menu(); options->get_popup()->add_separator(); options->get_popup()->add_item(TTR("Settings..."), MENU_OPTION_GRIDMAP_SETTINGS); @@ -1327,129 +1675,26 @@ GridMapEditor::GridMapEditor() { info_message->set_anchors_and_offsets_preset(PRESET_FULL_RECT, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); mesh_library_palette->add_child(info_message); - edit_axis = Vector3::AXIS_Y; edit_floor[0] = -1; edit_floor[1] = -1; edit_floor[2] = -1; - selection_mesh = RenderingServer::get_singleton()->mesh_create(); - paste_mesh = RenderingServer::get_singleton()->mesh_create(); - - { - // Selection mesh create. - - Vector lines; - Vector triangles; - Vector square[3]; + edit_axis = AXIS_Y; + edit_plane = Plane(); - for (int i = 0; i < 6; i++) { - Vector3 face_points[4]; + inner_mat.instantiate(); + inner_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.2)); + inner_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + inner_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + inner_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); - for (int j = 0; j < 4; j++) { - float v[3]; - v[0] = 1.0; - v[1] = 1 - 2 * ((j >> 1) & 1); - v[2] = v[1] * (1 - 2 * (j & 1)); + outer_mat.instantiate(); + outer_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.8)); + outer_mat->set_on_top_of_alpha(); - for (int k = 0; k < 3; k++) { - if (i < 3) { - face_points[j][(i + k) % 3] = v[k]; - } else { - face_points[3 - j][(i + k) % 3] = -v[k]; - } - } - } - - triangles.push_back(face_points[0] * 0.5 + Vector3(0.5, 0.5, 0.5)); - triangles.push_back(face_points[1] * 0.5 + Vector3(0.5, 0.5, 0.5)); - triangles.push_back(face_points[2] * 0.5 + Vector3(0.5, 0.5, 0.5)); - - triangles.push_back(face_points[2] * 0.5 + Vector3(0.5, 0.5, 0.5)); - triangles.push_back(face_points[3] * 0.5 + Vector3(0.5, 0.5, 0.5)); - triangles.push_back(face_points[0] * 0.5 + Vector3(0.5, 0.5, 0.5)); - } - - for (int i = 0; i < 12; i++) { - AABB base(Vector3(0, 0, 0), Vector3(1, 1, 1)); - Vector3 a, b; - base.get_edge(i, a, b); - lines.push_back(a); - lines.push_back(b); - } - - for (int i = 0; i < 3; i++) { - Vector3 points[4]; - for (int j = 0; j < 4; j++) { - static const bool orderx[4] = { false, true, true, false }; - static const bool ordery[4] = { false, false, true, true }; - - Vector3 sp; - if (orderx[j]) { - sp[(i + 1) % 3] = 1.0; - } - if (ordery[j]) { - sp[(i + 2) % 3] = 1.0; - } - - points[j] = sp; - } - - for (int j = 0; j < 4; j++) { - Vector3 ofs; - ofs[i] += 0.01; - square[i].push_back(points[j] - ofs); - square[i].push_back(points[(j + 1) % 4] - ofs); - square[i].push_back(points[j] + ofs); - square[i].push_back(points[(j + 1) % 4] + ofs); - } - } - - Array d; - d.resize(RS::ARRAY_MAX); - - inner_mat.instantiate(); - inner_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.2)); - inner_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); - inner_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); - inner_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); - - d[RS::ARRAY_VERTEX] = triangles; - RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_TRIANGLES, d); - RenderingServer::get_singleton()->mesh_surface_set_material(selection_mesh, 0, inner_mat->get_rid()); - - outer_mat.instantiate(); - outer_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.8)); - outer_mat->set_on_top_of_alpha(); - - outer_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); - outer_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); - outer_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); - - selection_floor_mat.instantiate(); - selection_floor_mat->set_albedo(Color(0.80, 0.80, 1.0, 1)); - selection_floor_mat->set_on_top_of_alpha(); - selection_floor_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); - selection_floor_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); - - d[RS::ARRAY_VERTEX] = lines; - RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_LINES, d); - RenderingServer::get_singleton()->mesh_surface_set_material(selection_mesh, 1, outer_mat->get_rid()); - - d[RS::ARRAY_VERTEX] = triangles; - RenderingServer::get_singleton()->mesh_add_surface_from_arrays(paste_mesh, RS::PRIMITIVE_TRIANGLES, d); - RenderingServer::get_singleton()->mesh_surface_set_material(paste_mesh, 0, inner_mat->get_rid()); - - d[RS::ARRAY_VERTEX] = lines; - RenderingServer::get_singleton()->mesh_add_surface_from_arrays(paste_mesh, RS::PRIMITIVE_LINES, d); - RenderingServer::get_singleton()->mesh_surface_set_material(paste_mesh, 1, outer_mat->get_rid()); - - for (int i = 0; i < 3; i++) { - d[RS::ARRAY_VERTEX] = square[i]; - selection_level_mesh[i] = RS::get_singleton()->mesh_create(); - RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_level_mesh[i], RS::PRIMITIVE_LINES, d); - RenderingServer::get_singleton()->mesh_surface_set_material(selection_level_mesh[i], 0, selection_floor_mat->get_rid()); - } - } + outer_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + outer_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + outer_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); _set_selection(false); @@ -1467,8 +1712,8 @@ GridMapEditor::~GridMapEditor() { _clear_clipboard_data(); for (int i = 0; i < 3; i++) { - if (grid[i].is_valid()) { - RenderingServer::get_singleton()->free(grid[i]); + if (grid_mesh[i].is_valid()) { + RenderingServer::get_singleton()->free(grid_mesh[i]); } if (grid_instance[i].is_valid()) { RenderingServer::get_singleton()->free(grid_instance[i]); @@ -1476,22 +1721,12 @@ GridMapEditor::~GridMapEditor() { if (cursor_instance.is_valid()) { RenderingServer::get_singleton()->free(cursor_instance); } - if (selection_level_instance[i].is_valid()) { - RenderingServer::get_singleton()->free(selection_level_instance[i]); - } - if (selection_level_mesh[i].is_valid()) { - RenderingServer::get_singleton()->free(selection_level_mesh[i]); - } } - - RenderingServer::get_singleton()->free(selection_mesh); - if (selection_instance.is_valid()) { - RenderingServer::get_singleton()->free(selection_instance); + if (selection_multimesh.is_valid()) { + RenderingServer::get_singleton()->free(selection_multimesh); } - - RenderingServer::get_singleton()->free(paste_mesh); - if (paste_instance.is_valid()) { - RenderingServer::get_singleton()->free(paste_instance); + if (selection_tile_mesh.is_valid()) { + RenderingServer::get_singleton()->free(selection_tile_mesh); } } diff --git a/modules/gridmap/editor/grid_map_editor_plugin.h b/modules/gridmap/editor/grid_map_editor_plugin.h index 3d2eecd4fae..efb291a3584 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.h +++ b/modules/gridmap/editor/grid_map_editor_plugin.h @@ -3,6 +3,10 @@ /**************************************************************************/ /* This file is part of: */ /* REDOT ENGINE */ +/* https://www.redotengine.org/ */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ /* https://redotengine.org */ /**************************************************************************/ /* Copyright (c) 2024-present Redot Engine contributors */ @@ -29,6 +33,9 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ +/* Work done by Octetdev2 and BSChad. */ +/* Merging done by Wasabi_Cheetah. */ +/**************************************************************************/ #ifndef GRID_MAP_EDITOR_PLUGIN_H #define GRID_MAP_EDITOR_PLUGIN_H @@ -94,23 +101,30 @@ class GridMapEditor : public VBoxContainer { List set_items; GridMap *node = nullptr; + // caching the node global transform to detect when the node has been + // moved/scaled/rotated. + Transform3D node_global_transform; Ref mesh_library = nullptr; - Transform3D grid_xform; - Transform3D edit_grid_xform; - Vector3::Axis edit_axis; - int edit_floor[3]; - Vector3 grid_ofs; + // plane we're editing cells on; depth comes from edit_floor + Plane edit_plane; + + enum EditAxis { + AXIS_X = 0, + AXIS_Y, + AXIS_Z, + AXIS_Q, // axial hex coordinates northwest/southeast + AXIS_R, // axial hex coordinates east/west + AXIS_S, // axial hex coordinates northeast/southwest + AXIS_MAX, + }; + EditAxis edit_axis; + int edit_floor[AXIS_MAX]; - RID grid[3]; + RID active_grid_instance; + RID grid_mesh[3]; RID grid_instance[3]; RID cursor_instance; - RID selection_mesh; - RID selection_instance; - RID selection_level_mesh[3]; - RID selection_level_instance[3]; - RID paste_mesh; - RID paste_instance; struct ClipboardItem { int cell_item = 0; @@ -124,24 +138,19 @@ class GridMapEditor : public VBoxContainer { Ref indicator_mat; Ref inner_mat; Ref outer_mat; - Ref selection_floor_mat; - - bool updating = false; struct Selection { - Vector3 click; - Vector3 current; Vector3 begin; Vector3 end; bool active = false; } selection; Selection last_selection; + RID selection_tile_mesh; + RID selection_multimesh; + RID selection_instance; struct PasteIndicator { - Vector3 click; - Vector3 current; - Vector3 begin; - Vector3 end; + Vector3i current_cell; int orientation = 0; }; PasteIndicator paste_indicator; @@ -149,7 +158,7 @@ class GridMapEditor : public VBoxContainer { bool cursor_visible = false; Transform3D cursor_transform; - Vector3 cursor_origin; + Vector3i cursor_cell; int display_mode = DISPLAY_THUMBNAIL; int selected_palette = -1; @@ -162,6 +171,11 @@ class GridMapEditor : public VBoxContainer { MENU_OPTION_X_AXIS, MENU_OPTION_Y_AXIS, MENU_OPTION_Z_AXIS, + MENU_OPTION_Q_AXIS, + MENU_OPTION_R_AXIS, + MENU_OPTION_S_AXIS, + MENU_OPTION_ROTATE_AXIS_CW, + MENU_OPTION_ROTATE_AXIS_CCW, MENU_OPTION_CURSOR_ROTATE_Y, MENU_OPTION_CURSOR_ROTATE_X, MENU_OPTION_CURSOR_ROTATE_Z, @@ -189,7 +203,13 @@ class GridMapEditor : public VBoxContainer { Label *info_message = nullptr; void update_grid(); // Change which and where the grid is displayed - void _draw_grids(const Vector3 &cell_size); + void _draw_hex_grid(RID p_grid, const Vector3 &p_cell_size); + void _draw_hex_x_axis_grid(RID p_grid, const Vector3 &p_cell_size); + void _draw_plane_grid(RID p_grid, const Vector3 &p_axis_n1, const Vector3 &p_axis_n2, const Vector3 &p_cell_size); + void _draw_grids(const Vector3 &p_cell_size); + void _update_cell_shape(const GridMap::CellShape cell_shape); + void _update_options_menu(); + void _build_selection_meshes(); void _configure(); void _menu_option(int); void update_palette(); @@ -210,8 +230,7 @@ class GridMapEditor : public VBoxContainer { void _set_clipboard_data(); void _update_paste_indicator(); void _do_paste(); - void _update_selection_transform(); - void _validate_selection(); + void _update_selection(); void _set_selection(bool p_active, const Vector3 &p_begin = Vector3(), const Vector3 &p_end = Vector3()); void _floor_changed(float p_value); diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 70e845ee76c..f9924b04561 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -1,5 +1,9 @@ /**************************************************************************/ -/* grid_map.cpp */ +/* grid_map.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://www.redotengine.org/ */ /**************************************************************************/ /* This file is part of: */ /* REDOT ENGINE */ @@ -29,13 +33,14 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ +/* Work done by Octetdev2 and BSChad. */ +/* Merging done by Wasabi_Cheetah. */ +/**************************************************************************/ #include "grid_map.h" #include "core/io/marshalls.h" -#include "scene/3d/light_3d.h" #include "scene/resources/3d/mesh_library.h" -#include "scene/resources/3d/primitive_meshes.h" #include "scene/resources/physics_material.h" #include "scene/resources/surface_tool.h" #include "servers/navigation_server_3d.h" @@ -273,9 +278,36 @@ Ref GridMap::get_mesh_library() const { return mesh_library; } +void GridMap::set_cell_shape(CellShape p_shape) { + ERR_FAIL_INDEX(p_shape, CELL_SHAPE_MAX); + if (cell_shape == p_shape) { + return; + } + cell_shape = p_shape; + + // if we switch to hex cells, make sure to change z & x coords to match + if (cell_shape == CELL_SHAPE_HEXAGON) { + cell_size.z = cell_size.x; + } + + notify_property_list_changed(); + _recreate_octant_data(); + emit_signal(SNAME("cell_shape_changed"), cell_shape); +} + +GridMap::CellShape GridMap::get_cell_shape() const { + return cell_shape; +} + void GridMap::set_cell_size(const Vector3 &p_size) { ERR_FAIL_COND(p_size.x < 0.001 || p_size.y < 0.001 || p_size.z < 0.001); cell_size = p_size; + // hex cells have a radius stored in x, and height stored in y. To make + // it clear that irregular hexagons are not supported, the z value of hex + // cells will always be updated to be the same as x. + if (cell_shape == CELL_SHAPE_HEXAGON) { + cell_size.z = cell_size.x; + } _recreate_octant_data(); emit_signal(SNAME("cell_size_changed"), cell_size); } @@ -433,7 +465,8 @@ int GridMap::get_cell_item_orientation(const Vector3i &p_position) const { return cell_map[key].rot; } -static const Basis _ortho_bases[24] = { +#define ORTHO_BASES_SQUARE_LEN 24 +static const Basis _ortho_bases_square[ORTHO_BASES_SQUARE_LEN] = { Basis(1, 0, 0, 0, 1, 0, 0, 0, 1), Basis(0, -1, 0, 1, 0, 0, 0, 0, 1), Basis(-1, 0, 0, 0, -1, 0, 0, 0, 1), @@ -460,6 +493,27 @@ static const Basis _ortho_bases[24] = { Basis(0, -1, 0, 0, 0, -1, 1, 0, 0) }; +// hex cells can be rotated six different ways around the y-axis. Additionally, +// they can be flipped upside down. +#define ORTHO_BASES_HEX_LEN 12 +static const Basis _ortho_bases_hex[ORTHO_BASES_HEX_LEN] = { + // rotate around y axis, y up + Basis(1, 0, 0, 0, 1, 0, 0, 0, 1), + Basis(0.5, 0, SQRT3_2, 0, 1, 0, -SQRT3_2, 0, 0.5), + Basis(-0.5, 0, SQRT3_2, 0, 1, 0, -SQRT3_2, 0, -0.5), + Basis(-1, 0, 0, 0, 1, 0, 0, 0, -1), + Basis(0.5, 0, -SQRT3_2, 0, 1, 0, SQRT3_2, 0, 0.5), + Basis(-0.5, 0, -SQRT3_2, 0, 1, 0, SQRT3_2, 0, -0.5), + + // rotate 180 degrees about the x-axis to make y down, and rotate about y + Basis(1, 0, 0, 0, -1, 0, 0, 0, -1), + Basis(0.5, 0, -SQRT3_2, 0, -1, 0, -SQRT3_2, 0, -0.5), + Basis(-0.5, 0, -SQRT3_2, 0, -1, 0, -SQRT3_2, 0, 0.5), + Basis(-1, 0, 0, 0, -1, 0, 0, 0, 1), + Basis(-0.5, 0, SQRT3_2, 0, -1, 0, SQRT3_2, 0, 0.5), + Basis(0.5, 0, SQRT3_2, 0, -1, 0, SQRT3_2, 0, -0.5), +}; + Basis GridMap::get_cell_item_basis(const Vector3i &p_position) const { int orientation = get_cell_item_orientation(p_position); @@ -471,49 +525,284 @@ Basis GridMap::get_cell_item_basis(const Vector3i &p_position) const { } Basis GridMap::get_basis_with_orthogonal_index(int p_index) const { - ERR_FAIL_INDEX_V(p_index, 24, Basis()); + if (cell_shape == CELL_SHAPE_SQUARE) { + ERR_FAIL_INDEX_V(p_index, ORTHO_BASES_SQUARE_LEN, Basis()); + return _ortho_bases_square[p_index]; + } else { + ERR_FAIL_INDEX_V(p_index, ORTHO_BASES_HEX_LEN, Basis()); + return _ortho_bases_hex[p_index]; + } +} - return _ortho_bases[p_index]; +// for hex cells, round a value within a Basis to -sqrt(3)/2, 0.0, sqrt(3)/2 +static inline real_t round_sqrt3_2(real_t v) { + if (v < -(SQRT3_2 / 2)) { + return -SQRT3_2; + } else if (v < SQRT3_2 / 2) { + return 0; + } else { + return SQRT3_2; + } +} + +// for hex cells, round a value within a Basis to -1.0, -0.5, 0.5, 1.0 +static inline real_t round_one_or_half(real_t v) { + if (v < -0.75) { + return -1.0; + } else if (v < 0.0) { + return -0.5; + } else if (v < 0.75) { + return 0.50; + } else { + return 1.0; + } } int GridMap::get_orthogonal_index_from_basis(const Basis &p_basis) const { Basis orth = p_basis; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - real_t v = orth[i][j]; - if (v > 0.5) { - v = 1.0; - } else if (v < -0.5) { - v = -1.0; - } else { - v = 0; - } + if (cell_shape == CELL_SHAPE_SQUARE) { + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + real_t v = orth[i][j]; + if (v > 0.5) { + v = 1.0; + } else if (v < -0.5) { + v = -1.0; + } else { + v = 0; + } - orth[i][j] = v; + orth[i][j] = v; + } } - } + for (int i = 0; i < ORTHO_BASES_SQUARE_LEN; i++) { + if (_ortho_bases_square[i] == orth) { + return i; + } + } + } else { + orth[0][0] = round_one_or_half(orth[0][0]); + orth[0][1] = 0; + orth[0][2] = round_sqrt3_2(orth[0][2]); - for (int i = 0; i < 24; i++) { - if (_ortho_bases[i] == orth) { - return i; + orth[1][0] = 0; + orth[1][1] = orth[1][1] < 0 ? -1 : 1; + orth[1][2] = 0; + + orth[2][0] = round_sqrt3_2(orth[2][0]); + orth[2][1] = 0; + orth[2][2] = round_one_or_half(orth[2][2]); + + for (int i = 0; i < ORTHO_BASES_HEX_LEN; i++) { + if (_ortho_bases_hex[i] == orth) { + return i; + } } } return 0; } -Vector3i GridMap::local_to_map(const Vector3 &p_world_position) const { - Vector3 map_position = (p_world_position / cell_size).floor(); - return Vector3i(map_position); +TypedArray GridMap::get_cell_neighbors(const Vector3i cell) const { + TypedArray out; + switch (cell_shape) { + case CELL_SHAPE_SQUARE: + out.push_back(cell + Vector3(1, 0, 0)); + out.push_back(cell + Vector3(-1, 0, 0)); + out.push_back(cell + Vector3(0, 1, 0)); + out.push_back(cell + Vector3(0, -1, 0)); + out.push_back(cell + Vector3(0, 0, 1)); + out.push_back(cell + Vector3(0, 0, -1)); + break; + case CELL_SHAPE_HEXAGON: + // each of the six horizontal directions + out.push_back(cell + Vector3(1, 0, 0)); + out.push_back(cell + Vector3(1, 0, -1)); + out.push_back(cell + Vector3(0, 0, -1)); + out.push_back(cell + Vector3(-1, 0, 0)); + out.push_back(cell + Vector3(-1, 0, 1)); + out.push_back(cell + Vector3(0, 0, 1)); + // and up and down + out.push_back(cell + Vector3(0, 1, 0)); + out.push_back(cell + Vector3(0, -1, 0)); + break; + default: + ERR_PRINT_ED("unsupported cell shape"); + } + return out; +} + +// based on blog post https://observablehq.com/@jrus/hexround +static inline Vector2i axial_round(real_t q_in, real_t r_in) { + int q = round(q_in); + int r = round(r_in); + + real_t q_rem = q_in - q; + real_t r_rem = r_in - r; + + if (abs(q_rem) >= abs(r_rem)) { + q += round(0.5 * r_rem + q_rem); + } else { + r += round(0.5 * q_rem + r_rem); + } + + return Vector2i(q, r); +} + +// convert axial hex coordinates to offset coordinates +// https://www.redblobgames.com/grids/hexagons/#conversions-offset +static inline Vector3i axial_to_oddr(Vector3i axial) { + int x = axial.x + (axial.z - (axial.z & 1)) / 2; + return Vector3i(x, axial.y, axial.z); +} + +static inline Vector3i oddr_to_axial(Vector3i oddr) { + int q = oddr.x - (oddr.z - (oddr.z & 1)) / 2; + return Vector3i(q, oddr.y, oddr.z); +} + +Vector3i GridMap::local_to_map(const Vector3 &p_local_position) const { + if (cell_shape != CELL_SHAPE_HEXAGON) { + Vector3 map_position = (p_local_position / cell_size).floor(); + return Vector3i(map_position); + } + + // convert x/z point into axial hex coordinates + // https://www.redblobgames.com/grids/hexagons/#pixel-to-hex + real_t q = (Math_SQRT3 / 3 * p_local_position.x - 1.0 / 3 * p_local_position.z) / cell_size.x; + real_t r = (2.0 / 3 * p_local_position.z) / cell_size.x; + Vector2i hex = axial_round(q, r); + + // map index for hex cells using (q, r) axial coordinates for the cell are: + // (q, level, r). We do it this way as q and r best map to x and z + // respectively. + return Vector3i(hex.x, floor(p_local_position.y / cell_size.y), hex.y); } Vector3 GridMap::map_to_local(const Vector3i &p_map_position) const { Vector3 offset = _get_offset(); - Vector3 local_position( - p_map_position.x * cell_size.x + offset.x, - p_map_position.y * cell_size.y + offset.y, - p_map_position.z * cell_size.z + offset.z); - return local_position; + if (cell_shape != CELL_SHAPE_HEXAGON) { + Vector3 local_position( + p_map_position.x * cell_size.x + offset.x, + p_map_position.y * cell_size.y + offset.y, + p_map_position.z * cell_size.z + offset.z); + return local_position; + } + + // convert axial hex coordinates to a point + // https://www.redblobgames.com/grids/hexagons/#hex-to-pixel + Vector3 local; + local.x = cell_size.x * (Math_SQRT3 * p_map_position.x + SQRT3_2 * p_map_position.z); + local.y = p_map_position.y * cell_size.y + offset.y; + local.z = cell_size.x * (3.0 / 2 * p_map_position.z); + return local; +} + +TypedArray GridMap::local_region_to_map(Vector3 p_a, Vector3 p_b) const { + TypedArray out; + + // shuffle the fields of a & b around so that a is bottom-left, b is + // top-right + if (p_a.x > p_b.x) { + SWAP(p_a.x, p_b.x); + } + if (p_a.y > p_b.y) { + SWAP(p_a.y, p_b.y); + } + if (p_a.z > p_b.z) { + SWAP(p_a.z, p_b.z); + } + Vector3i bottom_left = local_to_map(p_a); + Vector3i top_right = local_to_map(p_b); + + switch (cell_shape) { + case CELL_SHAPE_SQUARE: + for (int z = bottom_left.z; z <= top_right.z; z++) { + for (int y = bottom_left.y; y <= top_right.y; y++) { + for (int x = bottom_left.x; x <= top_right.x; x++) { + out.push_back(Vector3i(x, y, z)); + } + } + } + break; + + case CELL_SHAPE_HEXAGON: { + // we need the x coordinate of the center of the corner cells later. + // grab them now before we switch coordinate systems. + real_t left_x_center = map_to_local(bottom_left).x; + real_t right_x_center = map_to_local(top_right).x; + + // we're going to use a different coordinate system for this + // operation. It's much easier to walk the region when we use + // offset coordinates. So let's map our corners from axial to + // offset, then walk the region the same as the square region. + // We'll convert the coordinates back to axial before putting them + // in the array. + bottom_left = axial_to_oddr(bottom_left); + top_right = axial_to_oddr(top_right); + + // Also, unlike square cells, the location of the corner of the + // region within a cell matters for hex cells, specifically the x + // coordinate. If you pick a point anywhere within a hex cell, + // and draw a line down along the z-axis, that line will intercept + // either the cell to the southwest or southeast of the clicked + // cell. + // + // For both the left and right sides of the region, we need to + // determine which of the southwest/southeast cells fall within + // the region. We do this by adjusting the x-min and x-max for the + // even and odd rows independently. We use the following table to + // determine the modifier for the rows for both the minimum x + // value (in bottom_left.x), and the maximum x value (in + // top_right.x). + // + // Given an x coordinate in local space: + // | cell z coord | x > cell_center.x | odd mod | even mod | + // | even | false | -1 | 0 | + // | even | true | 0 | 0 | + // | odd | false | 0 | 0 | + // | odd | true | 0 | 1 | + + // adjustment applied to the min x value for odd and even cells + int x_min_delta[2] = { 0, 0 }; + + // if we start on an odd row, and the region starts to the right + // of center, we want to skip the even cells at x == a.x. + if ((bottom_left.z & 1) == 1 && p_a.x > left_x_center) { + x_min_delta[0] = 1; + } + // if we start on an even row, and the region starts to the left + // of center, we want to include the odd cells at x = a.x - 1. + else if ((bottom_left.z & 1) == 0 && p_a.x <= left_x_center) { + x_min_delta[1] = -1; + } + + // same as above, but for the max x values + int x_max_delta[2] = { 0, 0 }; + if ((top_right.z & 1) == 1 && p_b.x > right_x_center) { + x_max_delta[0] = 1; + } else if ((top_right.z & 1) == 0 && p_b.x <= right_x_center) { + x_max_delta[1] = -1; + } + for (int z = bottom_left.z; z <= top_right.z; z++) { + for (int y = bottom_left.y; y <= top_right.y; y++) { + int min_x = bottom_left.x + x_min_delta[z & 1]; + int max_x = top_right.x + x_max_delta[z & 1]; + for (int x = min_x; x <= max_x; x++) { + Vector3i oddr = Vector3i(x, y, z); + Vector3i axial = oddr_to_axial(oddr); + out.push_back(axial); + } + } + } + break; + } + + default: + ERR_PRINT_ED("unsupported cell shape"); + } + + return out; } void GridMap::_octant_transform(const OctantKey &p_key) { @@ -602,13 +891,15 @@ bool GridMap::_octant_update(const OctantKey &p_key) { continue; } - Vector3 cellpos = Vector3(E.x, E.y, E.z); - Vector3 ofs = _get_offset(); - + Vector3 map_pos = Vector3(E.x, E.y, E.z); Transform3D xform; - xform.basis = _ortho_bases[c.rot]; - xform.set_origin(cellpos * cell_size + ofs); + if (cell_shape == CELL_SHAPE_SQUARE) { + xform.basis = _ortho_bases_square[c.rot]; + } else { + xform.basis = _ortho_bases_hex[c.rot]; + } + xform.set_origin(map_to_local(map_pos)); xform.basis.scale(Vector3(cell_scale, cell_scale, cell_scale)); if (baked_meshes.size() == 0) { if (mesh_library->get_item_mesh(c.item).is_valid()) { @@ -1075,6 +1366,9 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mesh_library", "mesh_library"), &GridMap::set_mesh_library); ClassDB::bind_method(D_METHOD("get_mesh_library"), &GridMap::get_mesh_library); + ClassDB::bind_method(D_METHOD("set_cell_shape", "shape"), &GridMap::set_cell_shape); + ClassDB::bind_method(D_METHOD("get_cell_shape"), &GridMap::get_cell_shape); + ClassDB::bind_method(D_METHOD("set_cell_size", "size"), &GridMap::set_cell_size); ClassDB::bind_method(D_METHOD("get_cell_size"), &GridMap::get_cell_size); @@ -1090,9 +1384,11 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_cell_item_basis", "position"), &GridMap::get_cell_item_basis); ClassDB::bind_method(D_METHOD("get_basis_with_orthogonal_index", "index"), &GridMap::get_basis_with_orthogonal_index); ClassDB::bind_method(D_METHOD("get_orthogonal_index_from_basis", "basis"), &GridMap::get_orthogonal_index_from_basis); + ClassDB::bind_method(D_METHOD("get_cell_neighbors", "cell"), &GridMap::get_cell_neighbors); ClassDB::bind_method(D_METHOD("local_to_map", "local_position"), &GridMap::local_to_map); ClassDB::bind_method(D_METHOD("map_to_local", "map_position"), &GridMap::map_to_local); + ClassDB::bind_method(D_METHOD("local_region_to_map", "local_position_a", "local_position_b"), &GridMap::local_region_to_map); #ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("resource_changed", "resource"), &GridMap::resource_changed); @@ -1120,6 +1416,7 @@ void GridMap::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh_library", PROPERTY_HINT_RESOURCE_TYPE, "MeshLibrary"), "set_mesh_library", "get_mesh_library"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), "set_physics_material", "get_physics_material"); ADD_GROUP("Cell", "cell_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_shape", PROPERTY_HINT_ENUM, "Square,Hexagon"), "set_cell_shape", "get_cell_shape"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "cell_size", PROPERTY_HINT_NONE, "suffix:m"), "set_cell_size", "get_cell_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_octant_size", PROPERTY_HINT_RANGE, "1,1024,1"), "set_octant_size", "get_octant_size"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cell_center_x"), "set_center_x", "get_center_x"); @@ -1133,9 +1430,14 @@ void GridMap::_bind_methods() { ADD_GROUP("Navigation", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bake_navigation"), "set_bake_navigation", "is_baking_navigation"); + BIND_ENUM_CONSTANT(CELL_SHAPE_SQUARE); + BIND_ENUM_CONSTANT(CELL_SHAPE_HEXAGON); + BIND_ENUM_CONSTANT(CELL_SHAPE_MAX); + BIND_CONSTANT(INVALID_CELL_ITEM); ADD_SIGNAL(MethodInfo("cell_size_changed", PropertyInfo(Variant::VECTOR3, "cell_size"))); + ADD_SIGNAL(MethodInfo("cell_shape_changed", PropertyInfo(Variant::INT, "cell_shape", PROPERTY_HINT_ENUM, "Square,Hexagon"))); ADD_SIGNAL(MethodInfo(CoreStringName(changed))); } @@ -1177,7 +1479,6 @@ Array GridMap::get_meshes() const { return Array(); } - Vector3 ofs = _get_offset(); Array meshes; for (const KeyValue &E : cell_map) { @@ -1192,13 +1493,17 @@ Array GridMap::get_meshes() const { IndexKey ik = E.key; - Vector3 cellpos = Vector3(ik.x, ik.y, ik.z); + Vector3 cellpos = map_to_local(Vector3(ik.x, ik.y, ik.z)); Transform3D xform; - xform.basis = _ortho_bases[E.value.rot]; + if (cell_shape == CELL_SHAPE_SQUARE) { + xform.basis = _ortho_bases_square[E.value.rot]; + } else { + xform.basis = _ortho_bases_hex[E.value.rot]; + } - xform.set_origin(cellpos * cell_size + ofs); + xform.set_origin(cellpos); xform.basis.scale(Vector3(cell_scale, cell_scale, cell_scale)); meshes.push_back(xform * mesh_library->get_item_mesh_transform(id)); @@ -1251,7 +1556,11 @@ void GridMap::make_baked_meshes(bool p_gen_lightmap_uv, float p_lightmap_uv_texe Transform3D xform; - xform.basis = _ortho_bases[E.value.rot]; + if (cell_shape == CELL_SHAPE_SQUARE) { + xform.basis = _ortho_bases_square[E.value.rot]; + } else { + xform.basis = _ortho_bases_hex[E.value.rot]; + } xform.set_origin(cellpos * cell_size + ofs); xform.basis.scale(Vector3(cell_scale, cell_scale, cell_scale)); diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index 1b0016608b3..78c6c5d430e 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -3,6 +3,10 @@ /**************************************************************************/ /* This file is part of: */ /* REDOT ENGINE */ +/* https://www.redotengine.org/ */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ /* https://redotengine.org */ /**************************************************************************/ /* Copyright (c) 2024-present Redot Engine contributors */ @@ -29,13 +33,19 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ +/* Work done by Octetdev2 and BSChad. */ +/* Merging done by Wasabi_Cheetah. */ +/**************************************************************************/ #ifndef GRID_MAP_H #define GRID_MAP_H #include "scene/3d/node_3d.h" #include "scene/resources/3d/mesh_library.h" -#include "scene/resources/multimesh.h" + +// SQRT(3)/2; used both in the editor and the GridMap. Due to the division, it +// didn't fit the pattern of other Math_SQRTN defines, so I'm putting it here. +#define SQRT3_2 0.8660254037844386 //heh heh, godotsphir!! this shares no code and the design is completely different with previous projects i've done.. //should scale better with hardware that supports instancing @@ -45,6 +55,14 @@ class PhysicsMaterial; class GridMap : public Node3D { GDCLASS(GridMap, Node3D); +public: + enum CellShape { + CELL_SHAPE_SQUARE, + CELL_SHAPE_HEXAGON, + CELL_SHAPE_MAX, + }; + +private: enum { MAP_DIRTY_TRANSFORMS = 1, MAP_DIRTY_INSTANCES = 2, @@ -161,6 +179,7 @@ class GridMap : public Node3D { Transform3D last_transform; bool _in_tree = false; + CellShape cell_shape = CELL_SHAPE_SQUARE; Vector3 cell_size = Vector3(2, 2, 2); int octant_size = 8; bool center_x = true; @@ -263,6 +282,9 @@ class GridMap : public Node3D { void set_mesh_library(const Ref &p_mesh_library); Ref get_mesh_library() const; + void set_cell_shape(CellShape p_shape); + CellShape get_cell_shape() const; + void set_cell_size(const Vector3 &p_size); Vector3 get_cell_size() const; @@ -282,10 +304,13 @@ class GridMap : public Node3D { Basis get_cell_item_basis(const Vector3i &p_position) const; Basis get_basis_with_orthogonal_index(int p_index) const; int get_orthogonal_index_from_basis(const Basis &p_basis) const; + TypedArray get_cell_neighbors(const Vector3i) const; Vector3i local_to_map(const Vector3 &p_local_position) const; Vector3 map_to_local(const Vector3i &p_map_position) const; + TypedArray local_region_to_map(Vector3 p_a, Vector3 p_b) const; + void set_cell_scale(float p_scale); float get_cell_scale() const; @@ -306,4 +331,6 @@ class GridMap : public Node3D { ~GridMap(); }; +VARIANT_ENUM_CAST(GridMap::CellShape); + #endif // GRID_MAP_H