refactor: Introduce OverworldEntityRenderer for Enhanced Entity Visualization
- Added OverworldEntityRenderer class to separate entity rendering logic from OverworldEditor, improving maintainability and testability. - Updated OverworldEditor to utilize the new entity renderer, removing legacy drawing functions for entrances, exits, items, and sprites. - Enhanced initialization and drawing methods to streamline entity visualization and interaction within the editor. - Updated CMake configuration to include the new renderer files, ensuring proper integration into the build system.
This commit is contained in:
@@ -35,6 +35,7 @@ set(
|
|||||||
app/editor/overworld/map_properties.cc
|
app/editor/overworld/map_properties.cc
|
||||||
app/editor/graphics/gfx_group_editor.cc
|
app/editor/graphics/gfx_group_editor.cc
|
||||||
app/editor/overworld/entity.cc
|
app/editor/overworld/entity.cc
|
||||||
|
app/editor/overworld/overworld_entity_renderer.cc
|
||||||
app/editor/system/settings_editor.cc
|
app/editor/system/settings_editor.cc
|
||||||
app/editor/system/command_manager.cc
|
app/editor/system/command_manager.cc
|
||||||
app/editor/system/extension_manager.cc
|
app/editor/system/extension_manager.cc
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
#include "app/core/asar_wrapper.h"
|
#include "app/core/asar_wrapper.h"
|
||||||
#include "app/core/features.h"
|
#include "app/core/features.h"
|
||||||
#include "app/core/window.h"
|
#include "app/core/window.h"
|
||||||
#include "app/editor/overworld/entity.h"
|
|
||||||
#include "app/editor/overworld/map_properties.h"
|
#include "app/editor/overworld/map_properties.h"
|
||||||
#include "app/editor/overworld/tile16_editor.h"
|
#include "app/editor/overworld/tile16_editor.h"
|
||||||
#include "app/gfx/arena.h"
|
#include "app/gfx/arena.h"
|
||||||
@@ -62,6 +61,10 @@ void OverworldEditor::Initialize() {
|
|||||||
overworld_manager_ =
|
overworld_manager_ =
|
||||||
std::make_unique<OverworldEditorManager>(&overworld_, rom_, this);
|
std::make_unique<OverworldEditorManager>(&overworld_, rom_, this);
|
||||||
|
|
||||||
|
// Initialize OverworldEntityRenderer for entity visualization
|
||||||
|
entity_renderer_ = std::make_unique<OverworldEntityRenderer>(
|
||||||
|
&overworld_, &ow_map_canvas_, &sprite_previews_);
|
||||||
|
|
||||||
// Setup overworld canvas context menu
|
// Setup overworld canvas context menu
|
||||||
SetupOverworldCanvasContextMenu();
|
SetupOverworldCanvasContextMenu();
|
||||||
|
|
||||||
@@ -1100,10 +1103,24 @@ void OverworldEditor::DrawOverworldCanvas() {
|
|||||||
|
|
||||||
if (overworld_.is_loaded()) {
|
if (overworld_.is_loaded()) {
|
||||||
DrawOverworldMaps();
|
DrawOverworldMaps();
|
||||||
DrawOverworldExits(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
|
|
||||||
DrawOverworldEntrances(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
|
// Update entity renderer state
|
||||||
DrawOverworldItems();
|
entity_renderer_->set_current_map(current_map_);
|
||||||
DrawOverworldSprites();
|
|
||||||
|
// Draw all entities using the entity renderer
|
||||||
|
int mode_int = static_cast<int>(current_mode);
|
||||||
|
entity_renderer_->DrawExits(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(),
|
||||||
|
current_world_, mode_int);
|
||||||
|
entity_renderer_->DrawEntrances(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(),
|
||||||
|
current_world_, mode_int);
|
||||||
|
entity_renderer_->DrawItems(current_world_, mode_int);
|
||||||
|
entity_renderer_->DrawSprites(current_world_, game_state_, mode_int);
|
||||||
|
|
||||||
|
// Check if entity renderer wants to jump to a tab (e.g., double-clicked entrance/exit)
|
||||||
|
if (entity_renderer_->jump_to_tab() != -1) {
|
||||||
|
jump_to_tab_ = entity_renderer_->jump_to_tab();
|
||||||
|
entity_renderer_->reset_jump_to_tab();
|
||||||
|
}
|
||||||
|
|
||||||
// Draw overlay preview if enabled
|
// Draw overlay preview if enabled
|
||||||
if (show_overlay_preview_) {
|
if (show_overlay_preview_) {
|
||||||
@@ -1243,237 +1260,8 @@ absl::Status OverworldEditor::DrawAreaGraphics() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DrawTileSelector() removed - replaced by individual card system in Update()
|
// DrawTileSelector() removed - replaced by individual card system in Update()
|
||||||
|
// DrawOverworldEntrances(), DrawOverworldExits(), DrawOverworldItems(), DrawOverworldSprites()
|
||||||
void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling) {
|
// removed - moved to OverworldEntityRenderer
|
||||||
int i = 0;
|
|
||||||
for (auto& each : overworld_.entrances()) {
|
|
||||||
if (each.map_id_ < 0x40 + (current_world_ * 0x40) &&
|
|
||||||
each.map_id_ >= (current_world_ * 0x40) && !each.deleted) {
|
|
||||||
// Use theme-aware color with proper transparency
|
|
||||||
ImVec4 entrance_color = gui::GetEntranceColor();
|
|
||||||
if (each.is_hole_) {
|
|
||||||
// Holes are more opaque for visibility
|
|
||||||
entrance_color.w = 0.78f; // 200/255 alpha
|
|
||||||
}
|
|
||||||
ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, entrance_color);
|
|
||||||
std::string str = util::HexByte(each.entrance_id_);
|
|
||||||
|
|
||||||
if (current_mode == EditingMode::ENTRANCES) {
|
|
||||||
HandleEntityDragging(&each, canvas_p0, scrolling, is_dragging_entity_,
|
|
||||||
dragged_entity_, current_entity_);
|
|
||||||
|
|
||||||
if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
|
|
||||||
ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
|
||||||
jump_to_tab_ = each.entrance_id_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
|
|
||||||
ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
||||||
current_entrance_id_ = i;
|
|
||||||
current_entrance_ = each;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ow_map_canvas_.DrawText(str, each.x_, each.y_);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DrawEntranceInserterPopup()) {
|
|
||||||
// Get the deleted entrance ID and insert it at the mouse position
|
|
||||||
auto deleted_entrance_id = overworld_.deleted_entrances().back();
|
|
||||||
overworld_.deleted_entrances().pop_back();
|
|
||||||
auto& entrance = overworld_.entrances()[deleted_entrance_id];
|
|
||||||
entrance.map_id_ = current_map_;
|
|
||||||
entrance.entrance_id_ = deleted_entrance_id;
|
|
||||||
entrance.x_ = ow_map_canvas_.hover_mouse_pos().x;
|
|
||||||
entrance.y_ = ow_map_canvas_.hover_mouse_pos().y;
|
|
||||||
entrance.deleted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current_mode == EditingMode::ENTRANCES) {
|
|
||||||
const auto is_hovering =
|
|
||||||
IsMouseHoveringOverEntity(current_entrance_, canvas_p0, scrolling);
|
|
||||||
|
|
||||||
if (!is_hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
||||||
ImGui::OpenPopup("Entrance Inserter");
|
|
||||||
} else {
|
|
||||||
if (DrawOverworldEntrancePopup(
|
|
||||||
overworld_.entrances()[current_entrance_id_])) {
|
|
||||||
overworld_.entrances()[current_entrance_id_] = current_entrance_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overworld_.entrances()[current_entrance_id_].deleted) {
|
|
||||||
overworld_.mutable_deleted_entrances()->emplace_back(
|
|
||||||
current_entrance_id_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OverworldEditor::DrawOverworldExits(ImVec2 canvas_p0, ImVec2 scrolling) {
|
|
||||||
int i = 0;
|
|
||||||
for (auto& each : *overworld_.mutable_exits()) {
|
|
||||||
if (each.map_id_ < 0x40 + (current_world_ * 0x40) &&
|
|
||||||
each.map_id_ >= (current_world_ * 0x40) && !each.deleted_) {
|
|
||||||
ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16,
|
|
||||||
gui::GetExitColor());
|
|
||||||
if (current_mode == EditingMode::EXITS) {
|
|
||||||
each.entity_id_ = i;
|
|
||||||
HandleEntityDragging(&each, ow_map_canvas_.zero_point(),
|
|
||||||
ow_map_canvas_.scrolling(), is_dragging_entity_,
|
|
||||||
dragged_entity_, current_entity_, true);
|
|
||||||
|
|
||||||
if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
|
|
||||||
ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
|
||||||
jump_to_tab_ = each.room_id_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
|
|
||||||
ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
||||||
current_exit_id_ = i;
|
|
||||||
current_exit_ = each;
|
|
||||||
current_entity_ = &each;
|
|
||||||
current_entity_->entity_id_ = i;
|
|
||||||
ImGui::OpenPopup("Exit editor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string str = util::HexByte(i);
|
|
||||||
ow_map_canvas_.DrawText(str, each.x_, each.y_);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawExitInserterPopup();
|
|
||||||
if (current_mode == EditingMode::EXITS) {
|
|
||||||
const auto hovering = IsMouseHoveringOverEntity(
|
|
||||||
overworld_.mutable_exits()->at(current_exit_id_),
|
|
||||||
ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
|
|
||||||
|
|
||||||
if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
||||||
ImGui::OpenPopup("Exit Inserter");
|
|
||||||
} else {
|
|
||||||
if (DrawExitEditorPopup(
|
|
||||||
overworld_.mutable_exits()->at(current_exit_id_))) {
|
|
||||||
overworld_.mutable_exits()->at(current_exit_id_) = current_exit_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OverworldEditor::DrawOverworldItems() {
|
|
||||||
int i = 0;
|
|
||||||
for (auto& item : *overworld_.mutable_all_items()) {
|
|
||||||
// Get the item's bitmap and real X and Y positions
|
|
||||||
if (item.room_map_id_ < 0x40 + (current_world_ * 0x40) &&
|
|
||||||
item.room_map_id_ >= (current_world_ * 0x40) && !item.deleted) {
|
|
||||||
ow_map_canvas_.DrawRect(item.x_, item.y_, 16, 16, gui::GetItemColor());
|
|
||||||
|
|
||||||
if (current_mode == EditingMode::ITEMS) {
|
|
||||||
// Check if this item is being clicked and dragged
|
|
||||||
HandleEntityDragging(&item, ow_map_canvas_.zero_point(),
|
|
||||||
ow_map_canvas_.scrolling(), is_dragging_entity_,
|
|
||||||
dragged_entity_, current_entity_);
|
|
||||||
|
|
||||||
const auto hovering = IsMouseHoveringOverEntity(
|
|
||||||
item, ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
|
|
||||||
if (hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
||||||
current_item_id_ = i;
|
|
||||||
current_item_ = item;
|
|
||||||
current_entity_ = &item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::string item_name = "";
|
|
||||||
if (item.id_ < zelda3::kSecretItemNames.size()) {
|
|
||||||
item_name = zelda3::kSecretItemNames[item.id_];
|
|
||||||
} else {
|
|
||||||
item_name = absl::StrFormat("0x%02X", item.id_);
|
|
||||||
}
|
|
||||||
ow_map_canvas_.DrawText(item_name, item.x_, item.y_);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawItemInsertPopup();
|
|
||||||
if (current_mode == EditingMode::ITEMS) {
|
|
||||||
const auto hovering = IsMouseHoveringOverEntity(
|
|
||||||
overworld_.mutable_all_items()->at(current_item_id_),
|
|
||||||
ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
|
|
||||||
|
|
||||||
if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
||||||
ImGui::OpenPopup("Item Inserter");
|
|
||||||
} else {
|
|
||||||
if (DrawItemEditorPopup(
|
|
||||||
overworld_.mutable_all_items()->at(current_item_id_))) {
|
|
||||||
overworld_.mutable_all_items()->at(current_item_id_) = current_item_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OverworldEditor::DrawOverworldSprites() {
|
|
||||||
int i = 0;
|
|
||||||
for (auto& sprite : *overworld_.mutable_sprites(game_state_)) {
|
|
||||||
// Filter sprites by current world - only show sprites for the current world
|
|
||||||
if (!sprite.deleted() && sprite.map_id() < 0x40 + (current_world_ * 0x40) &&
|
|
||||||
sprite.map_id() >= (current_world_ * 0x40)) {
|
|
||||||
// Sprites are already stored with global coordinates (realX, realY from
|
|
||||||
// ROM loading) So we can use sprite.x_ and sprite.y_ directly
|
|
||||||
int sprite_x = sprite.x_;
|
|
||||||
int sprite_y = sprite.y_;
|
|
||||||
|
|
||||||
// Temporarily update sprite coordinates for entity interaction
|
|
||||||
int original_x = sprite.x_;
|
|
||||||
int original_y = sprite.y_;
|
|
||||||
|
|
||||||
ow_map_canvas_.DrawRect(sprite_x, sprite_y, kTile16Size, kTile16Size,
|
|
||||||
gui::GetSpriteColor());
|
|
||||||
if (current_mode == EditingMode::SPRITES) {
|
|
||||||
HandleEntityDragging(&sprite, ow_map_canvas_.zero_point(),
|
|
||||||
ow_map_canvas_.scrolling(), is_dragging_entity_,
|
|
||||||
dragged_entity_, current_entity_);
|
|
||||||
if (IsMouseHoveringOverEntity(sprite, ow_map_canvas_.zero_point(),
|
|
||||||
ow_map_canvas_.scrolling()) &&
|
|
||||||
ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
||||||
current_sprite_id_ = i;
|
|
||||||
current_sprite_ = sprite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) {
|
|
||||||
if (sprite_previews_[sprite.id()].is_active()) {
|
|
||||||
ow_map_canvas_.DrawBitmap(sprite_previews_[sprite.id()], sprite_x,
|
|
||||||
sprite_y, 2.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ow_map_canvas_.DrawText(absl::StrFormat("%s", sprite.name()), sprite_x,
|
|
||||||
sprite_y);
|
|
||||||
|
|
||||||
// Restore original coordinates
|
|
||||||
sprite.x_ = original_x;
|
|
||||||
sprite.y_ = original_y;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawSpriteInserterPopup();
|
|
||||||
if (current_mode == EditingMode::SPRITES) {
|
|
||||||
const auto hovering = IsMouseHoveringOverEntity(
|
|
||||||
overworld_.mutable_sprites(game_state_)->at(current_sprite_id_),
|
|
||||||
ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
|
|
||||||
|
|
||||||
if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
||||||
ImGui::OpenPopup("Sprite Inserter");
|
|
||||||
} else {
|
|
||||||
if (DrawSpriteEditorPopup(overworld_.mutable_sprites(game_state_)
|
|
||||||
->at(current_sprite_id_))) {
|
|
||||||
overworld_.mutable_sprites(game_state_)->at(current_sprite_id_) =
|
|
||||||
current_sprite_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status OverworldEditor::Save() {
|
absl::Status OverworldEditor::Save() {
|
||||||
if (core::FeatureFlags::get().overworld.kSaveOverworldMaps) {
|
if (core::FeatureFlags::get().overworld.kSaveOverworldMaps) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "app/editor/graphics/palette_editor.h"
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
#include "app/editor/overworld/tile16_editor.h"
|
#include "app/editor/overworld/tile16_editor.h"
|
||||||
#include "app/editor/overworld/map_properties.h"
|
#include "app/editor/overworld/map_properties.h"
|
||||||
|
#include "app/editor/overworld/overworld_entity_renderer.h"
|
||||||
#include "app/gfx/bitmap.h"
|
#include "app/gfx/bitmap.h"
|
||||||
#include "app/gfx/snes_palette.h"
|
#include "app/gfx/snes_palette.h"
|
||||||
#include "app/gfx/tilemap.h"
|
#include "app/gfx/tilemap.h"
|
||||||
@@ -123,11 +124,6 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
|
|||||||
void RefreshMapProperties();
|
void RefreshMapProperties();
|
||||||
absl::Status RefreshTile16Blockset();
|
absl::Status RefreshTile16Blockset();
|
||||||
|
|
||||||
void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling);
|
|
||||||
void DrawOverworldExits(ImVec2 zero, ImVec2 scrolling);
|
|
||||||
void DrawOverworldItems();
|
|
||||||
void DrawOverworldSprites();
|
|
||||||
|
|
||||||
void DrawOverworldMaps();
|
void DrawOverworldMaps();
|
||||||
void DrawOverworldEdits();
|
void DrawOverworldEdits();
|
||||||
void RenderUpdatedMapBitmap(const ImVec2& click_position,
|
void RenderUpdatedMapBitmap(const ImVec2& click_position,
|
||||||
@@ -276,6 +272,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
|
|||||||
// Map properties system for UI organization
|
// Map properties system for UI organization
|
||||||
std::unique_ptr<MapPropertiesSystem> map_properties_system_;
|
std::unique_ptr<MapPropertiesSystem> map_properties_system_;
|
||||||
std::unique_ptr<OverworldEditorManager> overworld_manager_;
|
std::unique_ptr<OverworldEditorManager> overworld_manager_;
|
||||||
|
std::unique_ptr<OverworldEntityRenderer> entity_renderer_;
|
||||||
|
|
||||||
// Scratch space for large layouts
|
// Scratch space for large layouts
|
||||||
// Scratch space canvas for tile16 drawing (like a mini overworld)
|
// Scratch space canvas for tile16 drawing (like a mini overworld)
|
||||||
|
|||||||
260
src/app/editor/overworld/overworld_entity_renderer.cc
Normal file
260
src/app/editor/overworld/overworld_entity_renderer.cc
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
#include "overworld_entity_renderer.h"
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "app/core/features.h"
|
||||||
|
#include "app/editor/overworld/entity.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/zelda3/common.h"
|
||||||
|
#include "util/hex.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using namespace ImGui;
|
||||||
|
|
||||||
|
// Entity colors - these could be theme-dependent in the future
|
||||||
|
namespace {
|
||||||
|
ImVec4 GetEntranceColor() { return ImVec4(0.0f, 1.0f, 0.0f, 0.5f); } // Green
|
||||||
|
ImVec4 GetExitColor() { return ImVec4(1.0f, 0.0f, 0.0f, 0.5f); } // Red
|
||||||
|
ImVec4 GetItemColor() { return ImVec4(1.0f, 1.0f, 0.0f, 0.5f); } // Yellow
|
||||||
|
ImVec4 GetSpriteColor() { return ImVec4(1.0f, 0.5f, 0.0f, 0.5f); } // Orange
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void OverworldEntityRenderer::DrawEntrances(ImVec2 canvas_p0, ImVec2 scrolling,
|
||||||
|
int current_world,
|
||||||
|
int current_mode) {
|
||||||
|
int i = 0;
|
||||||
|
for (auto& each : overworld_->entrances()) {
|
||||||
|
if (each.map_id_ < 0x40 + (current_world * 0x40) &&
|
||||||
|
each.map_id_ >= (current_world * 0x40) && !each.deleted) {
|
||||||
|
// Use theme-aware color with proper transparency
|
||||||
|
ImVec4 entrance_color = GetEntranceColor();
|
||||||
|
if (each.is_hole_) {
|
||||||
|
// Holes are more opaque for visibility
|
||||||
|
entrance_color.w = 0.78f; // 200/255 alpha
|
||||||
|
}
|
||||||
|
canvas_->DrawRect(each.x_, each.y_, 16, 16, entrance_color);
|
||||||
|
std::string str = util::HexByte(each.entrance_id_);
|
||||||
|
|
||||||
|
if (current_mode == 1) { // EditingMode::ENTRANCES
|
||||||
|
HandleEntityDragging(&each, canvas_p0, scrolling, is_dragging_entity_,
|
||||||
|
dragged_entity_, current_entity_);
|
||||||
|
|
||||||
|
if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
|
||||||
|
ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||||
|
jump_to_tab_ = each.entrance_id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
|
||||||
|
ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
current_entrance_id_ = i;
|
||||||
|
current_entrance_ = each;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_->DrawText(str, each.x_, each.y_);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DrawEntranceInserterPopup()) {
|
||||||
|
// Get the deleted entrance ID and insert it at the mouse position
|
||||||
|
auto deleted_entrance_id = overworld_->deleted_entrances().back();
|
||||||
|
overworld_->deleted_entrances().pop_back();
|
||||||
|
auto& entrance = overworld_->entrances()[deleted_entrance_id];
|
||||||
|
entrance.map_id_ = current_map_;
|
||||||
|
entrance.entrance_id_ = deleted_entrance_id;
|
||||||
|
entrance.x_ = canvas_->hover_mouse_pos().x;
|
||||||
|
entrance.y_ = canvas_->hover_mouse_pos().y;
|
||||||
|
entrance.deleted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_mode == 1) { // EditingMode::ENTRANCES
|
||||||
|
const auto is_hovering =
|
||||||
|
IsMouseHoveringOverEntity(current_entrance_, canvas_p0, scrolling);
|
||||||
|
|
||||||
|
if (!is_hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
ImGui::OpenPopup("Entrance Inserter");
|
||||||
|
} else {
|
||||||
|
if (DrawOverworldEntrancePopup(
|
||||||
|
overworld_->entrances()[current_entrance_id_])) {
|
||||||
|
overworld_->entrances()[current_entrance_id_] = current_entrance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overworld_->entrances()[current_entrance_id_].deleted) {
|
||||||
|
overworld_->mutable_deleted_entrances()->emplace_back(
|
||||||
|
current_entrance_id_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldEntityRenderer::DrawExits(ImVec2 canvas_p0, ImVec2 scrolling,
|
||||||
|
int current_world,
|
||||||
|
int current_mode) {
|
||||||
|
int i = 0;
|
||||||
|
for (auto& each : *overworld_->mutable_exits()) {
|
||||||
|
if (each.map_id_ < 0x40 + (current_world * 0x40) &&
|
||||||
|
each.map_id_ >= (current_world * 0x40) && !each.deleted_) {
|
||||||
|
canvas_->DrawRect(each.x_, each.y_, 16, 16, GetExitColor());
|
||||||
|
if (current_mode == 2) { // EditingMode::EXITS
|
||||||
|
each.entity_id_ = i;
|
||||||
|
HandleEntityDragging(&each, canvas_->zero_point(),
|
||||||
|
canvas_->scrolling(), is_dragging_entity_,
|
||||||
|
dragged_entity_, current_entity_, true);
|
||||||
|
|
||||||
|
if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
|
||||||
|
ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||||
|
jump_to_tab_ = each.room_id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) &&
|
||||||
|
ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
current_exit_id_ = i;
|
||||||
|
current_exit_ = each;
|
||||||
|
current_entity_ = &each;
|
||||||
|
current_entity_->entity_id_ = i;
|
||||||
|
ImGui::OpenPopup("Exit editor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string str = util::HexByte(i);
|
||||||
|
canvas_->DrawText(str, each.x_, each.y_);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawExitInserterPopup();
|
||||||
|
if (current_mode == 2) { // EditingMode::EXITS
|
||||||
|
const auto hovering = IsMouseHoveringOverEntity(
|
||||||
|
overworld_->mutable_exits()->at(current_exit_id_),
|
||||||
|
canvas_->zero_point(), canvas_->scrolling());
|
||||||
|
|
||||||
|
if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
ImGui::OpenPopup("Exit Inserter");
|
||||||
|
} else {
|
||||||
|
if (DrawExitEditorPopup(
|
||||||
|
overworld_->mutable_exits()->at(current_exit_id_))) {
|
||||||
|
overworld_->mutable_exits()->at(current_exit_id_) = current_exit_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldEntityRenderer::DrawItems(int current_world, int current_mode) {
|
||||||
|
int i = 0;
|
||||||
|
for (auto& item : *overworld_->mutable_all_items()) {
|
||||||
|
// Get the item's bitmap and real X and Y positions
|
||||||
|
if (item.room_map_id_ < 0x40 + (current_world * 0x40) &&
|
||||||
|
item.room_map_id_ >= (current_world * 0x40) && !item.deleted) {
|
||||||
|
canvas_->DrawRect(item.x_, item.y_, 16, 16, GetItemColor());
|
||||||
|
|
||||||
|
if (current_mode == 3) { // EditingMode::ITEMS
|
||||||
|
// Check if this item is being clicked and dragged
|
||||||
|
HandleEntityDragging(&item, canvas_->zero_point(),
|
||||||
|
canvas_->scrolling(), is_dragging_entity_,
|
||||||
|
dragged_entity_, current_entity_);
|
||||||
|
|
||||||
|
const auto hovering = IsMouseHoveringOverEntity(
|
||||||
|
item, canvas_->zero_point(), canvas_->scrolling());
|
||||||
|
if (hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
current_item_id_ = i;
|
||||||
|
current_item_ = item;
|
||||||
|
current_entity_ = &item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string item_name = "";
|
||||||
|
if (item.id_ < zelda3::kSecretItemNames.size()) {
|
||||||
|
item_name = zelda3::kSecretItemNames[item.id_];
|
||||||
|
} else {
|
||||||
|
item_name = absl::StrFormat("0x%02X", item.id_);
|
||||||
|
}
|
||||||
|
canvas_->DrawText(item_name, item.x_, item.y_);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawItemInsertPopup();
|
||||||
|
if (current_mode == 3) { // EditingMode::ITEMS
|
||||||
|
const auto hovering = IsMouseHoveringOverEntity(
|
||||||
|
overworld_->mutable_all_items()->at(current_item_id_),
|
||||||
|
canvas_->zero_point(), canvas_->scrolling());
|
||||||
|
|
||||||
|
if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
ImGui::OpenPopup("Item Inserter");
|
||||||
|
} else {
|
||||||
|
if (DrawItemEditorPopup(
|
||||||
|
overworld_->mutable_all_items()->at(current_item_id_))) {
|
||||||
|
overworld_->mutable_all_items()->at(current_item_id_) = current_item_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldEntityRenderer::DrawSprites(int current_world, int game_state,
|
||||||
|
int current_mode) {
|
||||||
|
int i = 0;
|
||||||
|
for (auto& sprite : *overworld_->mutable_sprites(game_state)) {
|
||||||
|
// Filter sprites by current world - only show sprites for the current world
|
||||||
|
if (!sprite.deleted() && sprite.map_id() < 0x40 + (current_world * 0x40) &&
|
||||||
|
sprite.map_id() >= (current_world * 0x40)) {
|
||||||
|
// Sprites are already stored with global coordinates (realX, realY from
|
||||||
|
// ROM loading) So we can use sprite.x_ and sprite.y_ directly
|
||||||
|
int sprite_x = sprite.x_;
|
||||||
|
int sprite_y = sprite.y_;
|
||||||
|
|
||||||
|
// Temporarily update sprite coordinates for entity interaction
|
||||||
|
int original_x = sprite.x_;
|
||||||
|
int original_y = sprite.y_;
|
||||||
|
|
||||||
|
canvas_->DrawRect(sprite_x, sprite_y, 16, 16, GetSpriteColor());
|
||||||
|
if (current_mode == 4) { // EditingMode::SPRITES
|
||||||
|
HandleEntityDragging(&sprite, canvas_->zero_point(),
|
||||||
|
canvas_->scrolling(), is_dragging_entity_,
|
||||||
|
dragged_entity_, current_entity_);
|
||||||
|
if (IsMouseHoveringOverEntity(sprite, canvas_->zero_point(),
|
||||||
|
canvas_->scrolling()) &&
|
||||||
|
ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
current_sprite_id_ = i;
|
||||||
|
current_sprite_ = sprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) {
|
||||||
|
if ((*sprite_previews_)[sprite.id()].is_active()) {
|
||||||
|
canvas_->DrawBitmap((*sprite_previews_)[sprite.id()], sprite_x,
|
||||||
|
sprite_y, 2.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_->DrawText(absl::StrFormat("%s", sprite.name()), sprite_x,
|
||||||
|
sprite_y);
|
||||||
|
|
||||||
|
// Restore original coordinates
|
||||||
|
sprite.x_ = original_x;
|
||||||
|
sprite.y_ = original_y;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawSpriteInserterPopup();
|
||||||
|
if (current_mode == 4) { // EditingMode::SPRITES
|
||||||
|
const auto hovering = IsMouseHoveringOverEntity(
|
||||||
|
overworld_->mutable_sprites(game_state)->at(current_sprite_id_),
|
||||||
|
canvas_->zero_point(), canvas_->scrolling());
|
||||||
|
|
||||||
|
if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
ImGui::OpenPopup("Sprite Inserter");
|
||||||
|
} else {
|
||||||
|
if (DrawSpriteEditorPopup(overworld_->mutable_sprites(game_state)
|
||||||
|
->at(current_sprite_id_))) {
|
||||||
|
overworld_->mutable_sprites(game_state)->at(current_sprite_id_) =
|
||||||
|
current_sprite_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
90
src/app/editor/overworld/overworld_entity_renderer.h
Normal file
90
src/app/editor/overworld/overworld_entity_renderer.h
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_ENTITY_RENDERER_H
|
||||||
|
#define YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_ENTITY_RENDERER_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
#include "app/zelda3/overworld/overworld_entrance.h"
|
||||||
|
#include "app/zelda3/overworld/overworld_exit.h"
|
||||||
|
#include "app/zelda3/overworld/overworld_item.h"
|
||||||
|
#include "app/zelda3/sprite/sprite.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
class OverworldEditor; // Forward declaration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class OverworldEntityRenderer
|
||||||
|
* @brief Handles visualization of all overworld entities (entrances, exits, items, sprites)
|
||||||
|
*
|
||||||
|
* This class separates entity rendering logic from the main OverworldEditor,
|
||||||
|
* making it easier to maintain and test entity visualization independently.
|
||||||
|
*/
|
||||||
|
class OverworldEntityRenderer {
|
||||||
|
public:
|
||||||
|
OverworldEntityRenderer(zelda3::Overworld* overworld, gui::Canvas* canvas,
|
||||||
|
std::vector<gfx::Bitmap>* sprite_previews)
|
||||||
|
: overworld_(overworld), canvas_(canvas),
|
||||||
|
sprite_previews_(sprite_previews) {}
|
||||||
|
|
||||||
|
// Main rendering methods
|
||||||
|
void DrawEntrances(ImVec2 canvas_p0, ImVec2 scrolling, int current_world,
|
||||||
|
int current_mode);
|
||||||
|
void DrawExits(ImVec2 canvas_p0, ImVec2 scrolling, int current_world,
|
||||||
|
int current_mode);
|
||||||
|
void DrawItems(int current_world, int current_mode);
|
||||||
|
void DrawSprites(int current_world, int game_state, int current_mode);
|
||||||
|
|
||||||
|
// State accessors
|
||||||
|
int current_entrance_id() const { return current_entrance_id_; }
|
||||||
|
int current_exit_id() const { return current_exit_id_; }
|
||||||
|
int current_item_id() const { return current_item_id_; }
|
||||||
|
int current_sprite_id() const { return current_sprite_id_; }
|
||||||
|
int jump_to_tab() const { return jump_to_tab_; }
|
||||||
|
|
||||||
|
zelda3::OverworldEntrance& current_entrance() { return current_entrance_; }
|
||||||
|
zelda3::OverworldExit& current_exit() { return current_exit_; }
|
||||||
|
zelda3::OverworldItem& current_item() { return current_item_; }
|
||||||
|
zelda3::Sprite& current_sprite() { return current_sprite_; }
|
||||||
|
|
||||||
|
void set_current_map(int map) { current_map_ = map; }
|
||||||
|
void set_is_dragging_entity(bool value) { is_dragging_entity_ = value; }
|
||||||
|
void set_dragged_entity(zelda3::GameEntity* entity) { dragged_entity_ = entity; }
|
||||||
|
void set_current_entity(zelda3::GameEntity* entity) { current_entity_ = entity; }
|
||||||
|
|
||||||
|
void reset_jump_to_tab() { jump_to_tab_ = -1; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
zelda3::Overworld* overworld_;
|
||||||
|
gui::Canvas* canvas_;
|
||||||
|
std::vector<gfx::Bitmap>* sprite_previews_;
|
||||||
|
|
||||||
|
// Current entity selections
|
||||||
|
int current_entrance_id_ = 0;
|
||||||
|
int current_exit_id_ = 0;
|
||||||
|
int current_item_id_ = 0;
|
||||||
|
int current_sprite_id_ = 0;
|
||||||
|
int current_map_ = 0;
|
||||||
|
int jump_to_tab_ = -1;
|
||||||
|
|
||||||
|
// Entity interaction state
|
||||||
|
bool is_dragging_entity_ = false;
|
||||||
|
zelda3::GameEntity* dragged_entity_ = nullptr;
|
||||||
|
zelda3::GameEntity* current_entity_ = nullptr;
|
||||||
|
|
||||||
|
// Current entity instances for editing
|
||||||
|
zelda3::OverworldEntrance current_entrance_;
|
||||||
|
zelda3::OverworldExit current_exit_;
|
||||||
|
zelda3::OverworldItem current_item_;
|
||||||
|
zelda3::Sprite current_sprite_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_ENTITY_RENDERER_H
|
||||||
|
|
||||||
Reference in New Issue
Block a user