feat: Improve dungeon canvas rendering and object interaction

- Updated DungeonCanvasViewer and DungeonEditor to render room objects as primitives temporarily for debugging purposes.
- Enhanced object interaction with new context menu options for deleting, copying, and pasting selected objects.
- Implemented drag-and-drop functionality for moving selected objects within the dungeon.
- Added texture processing for background layers and deferred texture commands to optimize rendering performance.
- Refactored code for clarity and maintainability, ensuring better handling of object interactions and rendering logic.
This commit is contained in:
scawful
2025-10-09 08:37:03 -04:00
parent 557b89dac4
commit 48faee6711
5 changed files with 246 additions and 21 deletions

View File

@@ -117,40 +117,49 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
}
// Render the room's background layers
// This already includes objects drawn by ObjectDrawer in Room::RenderObjectsToBackground()
RenderRoomBackgroundLayers(room_id);
// Render room objects with proper graphics (old system as fallback)
// TEMPORARY: Render all objects as primitives until proper rendering is fixed
// This ensures we can see objects while debugging the texture pipeline
if (current_palette_id_ < current_palette_group_.size()) {
auto room_palette = current_palette_group_[current_palette_id_];
// Render regular objects with proper graphics
// Render regular objects as colored rectangles (FALLBACK)
for (const auto& object : room.GetTileObjects()) {
RenderObjectInCanvas(object, room_palette);
}
// Render special objects with primitive shapes
// Render special objects with primitive shapes (overlays)
RenderStairObjects(room, room_palette);
RenderChests(room);
RenderDoorObjects(room);
RenderWallObjects(room);
RenderPotObjects(room);
// Render sprites as simple 16x16 squares with labels
RenderSprites(room);
}
// Render sprites as simple 16x16 squares with labels
// (Sprites are not part of the background buffers)
RenderSprites(room);
// Handle object interaction if enabled
if (object_interaction_enabled_) {
object_interaction_.HandleCanvasMouseInput();
object_interaction_.CheckForObjectSelection();
object_interaction_.DrawSelectBox();
object_interaction_.DrawSelectionHighlights(); // Draw selection highlights on top
object_interaction_.ShowContextMenu(); // Show dungeon-aware context menu
}
}
canvas_.DrawGrid();
canvas_.DrawOverlay();
// Process queued texture commands
if (rom_ && rom_->is_loaded()) {
gfx::Arena::Get().ProcessTextureQueue(nullptr); // Will use default renderer
}
// Draw layer information overlay
if (rooms_ && rom_->is_loaded()) {
auto& room = (*rooms_)[room_id];

View File

@@ -715,20 +715,42 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
}
// Render background layers with proper positioning
renderer_.RenderRoomBackgroundLayers(room_id);
// Render room objects and sprites with improved graphics
// This uses per-room buffers which already include objects drawn by ObjectDrawer
auto& room = rooms_[room_id];
auto& bg1_bitmap = room.bg1_buffer().bitmap();
auto& bg2_bitmap = room.bg2_buffer().bitmap();
if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0) {
if (!bg1_bitmap.texture()) {
// Queue texture creation for background layer 1
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg1_bitmap);
}
canvas_.DrawBitmap(bg1_bitmap, 0, 0, 1.0f, 255);
}
if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0) {
if (!bg2_bitmap.texture()) {
// Queue texture creation for background layer 2
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg2_bitmap);
}
canvas_.DrawBitmap(bg2_bitmap, 0, 0, 1.0f, 200);
}
// TEMPORARY: Render all objects as primitives until proper rendering is fixed
if (current_palette_id_ < current_palette_group_.size()) {
auto room_palette = current_palette_group_[current_palette_id_];
// Render regular objects with improved fallback
for (const auto& object : rooms_[room_id].GetTileObjects()) {
// Render regular objects with primitive fallback
for (const auto& object : room.GetTileObjects()) {
renderer_.RenderObjectInCanvas(object, room_palette);
}
// Render sprites as simple 16x16 squares with labels
renderer_.RenderSprites(rooms_[room_id]);
}
// Render sprites as simple 16x16 squares with labels
// (Sprites are not part of the background buffers)
renderer_.RenderSprites(rooms_[room_id]);
}
// Phase 5: Render with integrated object editor
@@ -740,6 +762,9 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
canvas_.DrawGrid();
canvas_.DrawOverlay();
// Process queued texture commands
ProcessDeferredTextures();
}
// ============================================================================
@@ -847,4 +872,10 @@ absl::Status DungeonEditor::UpdateRoomBackgroundLayers(int /*room_id*/) {
return absl::OkStatus();
}
void DungeonEditor::ProcessDeferredTextures() {
// Process queued texture commands via Arena's deferred system
// Note: Arena will use its stored renderer reference
gfx::Arena::Get().ProcessTextureQueue(nullptr);
}
} // namespace yaze::editor

View File

@@ -110,6 +110,9 @@ class DungeonEditor : public Editor {
void RenderRoomWithObjects(int room_id);
void UpdateObjectEditor();
// Texture processing
void ProcessDeferredTextures();
// Room selection management
void OnRoomSelected(int room_id);

View File

@@ -1,6 +1,7 @@
#include "dungeon_object_interaction.h"
#include "app/gui/color.h"
#include <algorithm>
#include "imgui/imgui.h"
namespace yaze::editor {
@@ -67,7 +68,34 @@ void DungeonObjectInteraction::HandleCanvasMouseInput() {
}
if (is_dragging_) {
is_dragging_ = false;
// TODO: Apply drag transformation to selected objects
// Apply drag transformation to selected objects
if (!selected_object_indices_.empty() && rooms_ && current_room_id_ >= 0 && current_room_id_ < 296) {
auto& room = (*rooms_)[current_room_id_];
ImVec2 drag_delta = ImVec2(drag_current_pos_.x - drag_start_pos_.x,
drag_current_pos_.y - drag_start_pos_.y);
// Convert pixel delta to tile delta
int tile_delta_x = static_cast<int>(drag_delta.x) / 8;
int tile_delta_y = static_cast<int>(drag_delta.y) / 8;
// Move all selected objects
auto& objects = room.GetTileObjects();
for (size_t index : selected_object_indices_) {
if (index < objects.size()) {
objects[index].x_ += tile_delta_x;
objects[index].y_ += tile_delta_y;
// Clamp to room bounds (64x64 tiles)
objects[index].x_ = std::clamp(static_cast<int>(objects[index].x_), 0, 63);
objects[index].y_ = std::clamp(static_cast<int>(objects[index].y_), 0, 63);
}
}
// Trigger cache invalidation and re-render
if (cache_invalidation_callback_) {
cache_invalidation_callback_();
}
}
}
}
}
@@ -252,7 +280,8 @@ void DungeonObjectInteraction::DrawSelectBox() {
}
void DungeonObjectInteraction::DrawDragPreview() {
if (!is_dragging_) return;
if (!is_dragging_ || selected_object_indices_.empty() || !rooms_) return;
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
// Draw drag preview for selected objects
ImDrawList* draw_list = ImGui::GetWindowDrawList();
@@ -260,11 +289,30 @@ void DungeonObjectInteraction::DrawDragPreview() {
ImVec2 drag_delta = ImVec2(drag_current_pos_.x - drag_start_pos_.x,
drag_current_pos_.y - drag_start_pos_.y);
auto& room = (*rooms_)[current_room_id_];
const auto& objects = room.GetTileObjects();
// Draw preview of where objects would be moved
for (int obj_id : selected_objects_) {
// TODO: Draw preview of object at new position
// This would require getting the object's current position and drawing it
// offset by drag_delta
for (size_t index : selected_object_indices_) {
if (index < objects.size()) {
const auto& object = objects[index];
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
// Calculate object size
int obj_width = 8 + (object.size_ & 0x0F) * 4;
int obj_height = 8 + ((object.size_ >> 4) & 0x0F) * 4;
obj_width = std::min(obj_width, 64);
obj_height = std::min(obj_height, 64);
// Draw semi-transparent preview at new position
ImVec2 preview_start(canvas_pos.x + canvas_x + drag_delta.x,
canvas_pos.y + canvas_y + drag_delta.y);
ImVec2 preview_end(preview_start.x + obj_width, preview_start.y + obj_height);
// Draw ghosted object
draw_list->AddRectFilled(preview_start, preview_end, IM_COL32(0, 255, 255, 64));
draw_list->AddRect(preview_start, preview_end, IM_COL32(0, 255, 255, 255), 0.0f, 0, 1.5f);
}
}
}
@@ -341,4 +389,128 @@ void DungeonObjectInteraction::ClearSelection() {
is_dragging_ = false;
}
void DungeonObjectInteraction::ShowContextMenu() {
if (!canvas_->IsMouseHovering()) return;
// Show context menu on right-click when not dragging
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !is_dragging_) {
ImGui::OpenPopup("DungeonObjectContextMenu");
}
if (ImGui::BeginPopup("DungeonObjectContextMenu")) {
// Show different options based on current state
if (!selected_object_indices_.empty()) {
if (ImGui::MenuItem("Delete Selected", "Del")) {
HandleDeleteSelected();
}
if (ImGui::MenuItem("Copy Selected", "Ctrl+C")) {
HandleCopySelected();
}
ImGui::Separator();
}
if (has_clipboard_data_) {
if (ImGui::MenuItem("Paste Objects", "Ctrl+V")) {
HandlePasteObjects();
}
ImGui::Separator();
}
if (object_loaded_) {
ImGui::Text("Placing: Object 0x%02X", preview_object_.id_);
if (ImGui::MenuItem("Cancel Placement", "Esc")) {
object_loaded_ = false;
}
} else {
ImGui::Text("Right-click + drag to select");
ImGui::Text("Left-click + drag to move");
}
ImGui::EndPopup();
}
}
void DungeonObjectInteraction::HandleDeleteSelected() {
if (selected_object_indices_.empty() || !rooms_) return;
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
auto& room = (*rooms_)[current_room_id_];
// Sort indices in descending order to avoid index shifts during deletion
std::vector<size_t> sorted_indices = selected_object_indices_;
std::sort(sorted_indices.rbegin(), sorted_indices.rend());
// Delete selected objects using Room's RemoveTileObject method
for (size_t index : sorted_indices) {
room.RemoveTileObject(index);
}
// Clear selection
ClearSelection();
// Trigger cache invalidation and re-render
if (cache_invalidation_callback_) {
cache_invalidation_callback_();
}
}
void DungeonObjectInteraction::HandleCopySelected() {
if (selected_object_indices_.empty() || !rooms_) return;
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
auto& room = (*rooms_)[current_room_id_];
const auto& objects = room.GetTileObjects();
// Copy selected objects to clipboard
clipboard_.clear();
for (size_t index : selected_object_indices_) {
if (index < objects.size()) {
clipboard_.push_back(objects[index]);
}
}
has_clipboard_data_ = !clipboard_.empty();
}
void DungeonObjectInteraction::HandlePasteObjects() {
if (!has_clipboard_data_ || !rooms_) return;
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
auto& room = (*rooms_)[current_room_id_];
// Get mouse position for paste location
const ImGuiIO& io = ImGui::GetIO();
ImVec2 mouse_pos = io.MousePos;
ImVec2 canvas_pos = canvas_->zero_point();
ImVec2 canvas_mouse_pos =
ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
auto [paste_x, paste_y] = CanvasToRoomCoordinates(
static_cast<int>(canvas_mouse_pos.x),
static_cast<int>(canvas_mouse_pos.y));
// Calculate offset from first object in clipboard
if (!clipboard_.empty()) {
int offset_x = paste_x - clipboard_[0].x_;
int offset_y = paste_y - clipboard_[0].y_;
// Paste all objects with offset
for (const auto& obj : clipboard_) {
auto new_obj = obj;
new_obj.x_ = obj.x_ + offset_x;
new_obj.y_ = obj.y_ + offset_y;
// Clamp to room bounds
new_obj.x_ = std::clamp(static_cast<int>(new_obj.x_), 0, 63);
new_obj.y_ = std::clamp(static_cast<int>(new_obj.y_), 0, 63);
room.AddTileObject(new_obj);
}
// Trigger cache invalidation and re-render
if (cache_invalidation_callback_) {
cache_invalidation_callback_();
}
}
}
} // namespace yaze::editor

View File

@@ -52,6 +52,12 @@ class DungeonObjectInteraction {
bool IsObjectSelectActive() const { return object_select_active_; }
void ClearSelection();
// Context menu
void ShowContextMenu();
void HandleDeleteSelected();
void HandleCopySelected();
void HandlePasteObjects();
// Callbacks
void SetObjectPlacedCallback(std::function<void(const zelda3::RoomObject&)> callback) {
object_placed_callback_ = callback;
@@ -87,6 +93,10 @@ class DungeonObjectInteraction {
// Callbacks
std::function<void(const zelda3::RoomObject&)> object_placed_callback_;
std::function<void()> cache_invalidation_callback_;
// Clipboard for copy/paste
std::vector<zelda3::RoomObject> clipboard_;
bool has_clipboard_data_ = false;
};
} // namespace editor