#include "terrain_mesh_editor.h" #include "core/input/input_event.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/os/keyboard.h" #include "scene/3d/node_3d.h" #include "scene/gui/file_dialog.h" #include "scene/resources/packed_scene.h" #include "terrain_editor/edit_history.h" #include "terrain_editor/macros.h" #include "terrain_editor/point_primitive_node.h" #include "terrain_editor/terrain_primitive.h" void SaveData::_bind_methods() { BIND_HPROPERTY(Variant::ARRAY, primitives, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:TerrainPrimitive", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE)); } void SaveData::write_to_file() { ResourceSaver::save(this, this->save_file_path, ResourceSaver::FLAG_OMIT_EDITOR_PROPERTIES | ResourceSaver::FLAG_BUNDLE_RESOURCES); } void SaveData::set_save_path(String value) { this->save_file_path = value; } String SaveData::get_save_path() const { return this->save_file_path; } void SaveData::set_primitives(Array array) { this->primitives = array.duplicate(true); // do a full deep copy of the data, to avoid modification before saving } Array SaveData::get_primitives() const { return this->primitives; } String const TerrainMeshEditor::sig_selection_changed{ "selection_changed" }; void TerrainMeshEditor::_bind_methods() { BIND_HPROPERTY(Variant::OBJECT, point_primitive_object, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"); ClassDB::bind_method(D_METHOD("save_data"), &self_type::save_data); BIND_HPROPERTY(Variant::OBJECT, current_selected, PROPERTY_HINT_RESOURCE_TYPE, "TerrainPrimitive"); ADD_SIGNAL(MethodInfo(sig_selection_changed, PropertyInfo(Variant::OBJECT, "new_selection", PROPERTY_HINT_RESOURCE_TYPE, "TerrainPrimitive"))); } void TerrainMeshEditor::ready() { connect(sig_primitive_list_changed, callable_mp(this, &self_type::on_primitive_list_changed)); on_primitive_list_changed(get_primitives()); if (FileDialog * dialog{ memnew(FileDialog) }) { this->file_dialog = dialog; add_child(dialog); dialog->set_filters({ "*.terrain.tres;World Resource Files;", "*.terrain.res;Binary World Resource Files;" }); dialog->set_access(FileDialog::ACCESS_FILESYSTEM); dialog->connect("file_selected", callable_mp(this, &self_type::on_save_file_selected)); } } void TerrainMeshEditor::on_primitive_list_changed(Array primitives) { for (Node3D *existing : this->primitive_nodes) { existing->queue_free(); } this->primitive_nodes.clear(); for (Variant var : primitives) { if (this->point_primitive_object.is_valid() && this->point_primitive_object->get_state()->get_node_type(0) == PointPrimitiveNode::get_class_static()) { Ref point{ var }; if (point.is_valid()) { PointPrimitiveNode *primitive_node{ cast_to(this->point_primitive_object->instantiate()) }; primitive_node->set_primitive(point); this->add_child(primitive_node); this->primitive_nodes.push_back(primitive_node); } } } } void TerrainMeshEditor::on_save_file_selected(String path) { switch (this->file_dialog->get_file_mode()) { default: print_error("Attempt to open or save with invalid file dialog mode"); break; case FileDialog::FILE_MODE_SAVE_FILE: this->data->set_save_path(path); this->data->set_primitives(get_primitives()); this->data->write_to_file(); break; case FileDialog::FILE_MODE_OPEN_FILE: Ref resource{ ResourceLoader::load(path, "SaveData", ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP) }; Ref loaded_data{ resource }; if (loaded_data.is_valid()) { this->data = loaded_data; this->set_primitives(this->data->get_primitives().duplicate(true)); this->data->set_save_path(path); } break; } } void TerrainMeshEditor::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { return; } switch (what) { default: return; case NOTIFICATION_READY: set_process_unhandled_input(true); ready(); return; } } void TerrainMeshEditor::unhandled_input(Ref const &event) { Ref key{ event }; if (!key.is_valid() || !key->is_pressed()) { return; } switch (key->get_key_label()) { default: return; case Key::S: if (key->is_shift_pressed() && key->is_command_or_control_pressed()) { get_viewport()->set_input_as_handled(); this->file_dialog->set_file_mode(FileDialog::FILE_MODE_SAVE_FILE); this->file_dialog->set_ok_button_text("Save"); this->file_dialog->popup_file_dialog(); } else if (key->is_command_or_control_pressed()) { get_viewport()->set_input_as_handled(); save_data(); } break; case Key::O: if (key->is_command_or_control_pressed()) { get_viewport()->set_input_as_handled(); this->file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE); this->file_dialog->set_ok_button_text("Open"); this->file_dialog->popup_file_dialog(); } break; case Key::Z: if (key->is_shift_pressed() && key->is_command_or_control_pressed()) { get_viewport()->set_input_as_handled(); EditHistory::get_singleton()->redo(); } else if (key->is_command_or_control_pressed()) { get_viewport()->set_input_as_handled(); EditHistory::get_singleton()->undo(); } break; } } void TerrainMeshEditor::save_data() { // TODO: figure out if this is the correct way to see if the popup is popped up if (this->file_dialog->is_visible()) { return; } if (this->data->get_save_path().is_empty()) { this->file_dialog->set_file_mode(FileDialog::FILE_MODE_SAVE_FILE); this->file_dialog->set_ok_button_text("Save"); this->file_dialog->popup_file_dialog(); } else { this->data->set_primitives(get_primitives()); this->data->write_to_file(); } } void TerrainMeshEditor::set_current_selected(Ref primitive) { this->current_selected = primitive; emit_signal(sig_selection_changed, primitive); } Ref TerrainMeshEditor::get_current_selected() const { return this->current_selected; } void TerrainMeshEditor::set_point_primitive_object(Ref scene) { this->point_primitive_object = scene; } Ref TerrainMeshEditor::get_point_primitive_object() const { return this->point_primitive_object; }