Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
BehaviorTreeRenderer.cpp
Go to the documentation of this file.
1/**
2 * @file BehaviorTreeRenderer.cpp
3 * @brief IGraphRenderer adapter for BehaviorTree graphs.
4 * @author Olympe Engine
5 * @date 2026-03-11
6 * @updated 2026-04-08 (Phase 2: Added split-panel layout with palette + property tabs)
7 *
8 * @details C++14 compliant.
9 */
10
12#include "BTNodeGraphManager.h"
14#include "ExecutionTestPanel.h"
16#include "../NodeGraphShared/BehaviorTreeGraphAdapter.h"
17#include "../AI/AIGraphPlugin_BT/BTNodePalette.h"
18#include "../AI/BehaviorTree.h"
19#include "../DataManager.h"
20#include "../third_party/imgui/imgui.h"
21#include "../system/system_utils.h"
22
23#include <iostream>
24#include <memory>
25
26namespace Olympe {
27
29 : m_panel(panel)
30 , m_graphId(-1)
31 , m_canvasPanelWidth(0.75f)
32 , m_rightPanelTabSelection(0)
33{
34 // Create BTNodePalette for drag-drop node creation
35 m_palette = std::make_unique<AI::BTNodePalette>();
36
37 // Initialize property panel
39
40 // Initialize execution test panel (Phase 35)
41 m_executionTestPanel = std::make_unique<ExecutionTestPanel>();
42 m_executionTestPanel->Initialize();
43
44 // Initialize tracer
45 m_lastTracer = std::make_unique<GraphExecutionTracer>();
46}
47
56
57bool BehaviorTreeRenderer::CreateNew(const std::string& name)
58{
59 // Close any previously loaded graph
60 if (m_graphId >= 0)
61 {
63 m_graphId = -1;
64 m_filePath.clear();
65 }
66
67 // Create new empty graph and set it active immediately
68 m_graphId = NodeGraphManager::Get().CreateGraph(name, "BehaviorTree");
69 if (m_graphId < 0)
70 {
71 SYSTEM_LOG << "[BehaviorTreeRenderer] Failed to create new BehaviorTree graph\n";
72 return false;
73 }
74
76 SYSTEM_LOG << "[BehaviorTreeRenderer] Created new BehaviorTree graph: '" << name
77 << "' (id=" << m_graphId << ")\n";
78 return true;
79}
80
82{
83 if (m_graphId >= 0)
84 {
86 }
88
89 // Render execution test panel as overlay (displays simulation results)
91 {
92 m_executionTestPanel->Render();
93 }
94
95 // Phase 40: Render centralized file picker modal
97}
98
100{
101 // Suppress NodeGraphPanel's GraphTabs since we manage tabs in BehaviorTreeRenderer
103
104 // Render toolbar with Run Graph button
105 ImGui::SetCursorPosX(10.0f);
106 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f);
107
108 if (ImGui::Button("Run Graph", ImVec2(100, 0)))
109 {
111 }
112
113 ImGui::SameLine();
114 ImGui::Text("(Ctrl+Shift+R to run)");
115
116 ImGui::Separator();
117
118 // Layout: Canvas (left, ~75%) | Resize Handle | Tabbed Right Panel (right, ~25%)
119 float totalWidth = ImGui::GetContentRegionAvail().x;
120 float handleWidth = 4.0f;
123
124 ImVec2 regionMin = ImGui::GetCursorScreenPos();
125
126 // Render canvas on the left
127 ImGui::BeginChild("BTNodeCanvas", ImVec2(canvasWidth, 0), false, ImGuiWindowFlags_NoScrollbar);
128 m_canvasScreenPos = ImGui::GetCursorScreenPos(); // Store canvas position for coordinate transforms
130 ImGui::EndChild();
131
132 ImVec2 canvasEnd = ImGui::GetCursorScreenPos();
133
134 ImGui::SameLine();
135
136 // Resize handle
137 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.35f, 0.35f, 0.35f, 0.5f));
138 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.55f, 0.55f, 0.55f, 0.8f));
139 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.70f, 0.70f, 0.70f, 1.0f));
140 ImGui::Button("##resizeHandle", ImVec2(handleWidth, -1.0f));
141 if (ImGui::IsItemHovered())
142 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
143 if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left))
144 {
145 m_canvasPanelWidth += ImGui::GetIO().MouseDelta.x / totalWidth;
146 if (m_canvasPanelWidth < 0.5f) m_canvasPanelWidth = 0.5f;
147 if (m_canvasPanelWidth > 0.9f) m_canvasPanelWidth = 0.9f;
148 }
149 ImGui::PopStyleColor(3);
150
151 ImGui::SameLine();
152
153 // Render right panel with tabs
154 ImGui::BeginChild("BTRightPanel", ImVec2(rightPanelWidth, 0), true);
156 ImGui::EndChild();
157
158 // Create invisible drop target overlay covering the entire layout area
159 ImVec2 layoutEnd = ImGui::GetCursorScreenPos();
162
163 ImGui::SetCursorScreenPos(overlayMin);
164 ImGui::Dummy(ImVec2(totalWidth, layoutEnd.y - regionMin.y));
165
166 // Accept drag-drop for node palette items
167 if (ImGui::BeginDragDropTarget())
168 {
169 if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("BT_NODE_TYPE"))
170 {
171 const char* nodeTypeStr = (const char*)payload->Data;
172 ImVec2 mousePos = ImGui::GetMousePos();
174 }
175 ImGui::EndDragDropTarget();
176 }
177
178 // Handle keyboard shortcuts for copy/paste/duplicate
180}
181
183{
184 if (ImGui::BeginTabBar("BTRightPanelTabs", ImGuiTabBarFlags_None))
185 {
186 // Tab 0: Node Palette
187 if (ImGui::BeginTabItem("Palette"))
188 {
189 if (m_palette)
190 {
191 bool paletteOpen = true;
192 m_palette->Render(&paletteOpen);
193 }
194 ImGui::EndTabItem();
195 }
196
197 // Tab 1: Node Properties
198 if (ImGui::BeginTabItem("Properties"))
199 {
200 // Wire selected node from canvas to property panel
202 if (selectedNodeId >= 0 && m_graphId >= 0)
203 {
205 {
207 }
208 }
209 else if (selectedNodeId < 0)
210 {
212 }
213
215 ImGui::EndTabItem();
216 }
217
218 ImGui::EndTabBar();
219 }
220}
221
222bool BehaviorTreeRenderer::Load(const std::string& path)
223{
224 if (path.empty())
225 return false;
226
227 // Close the previously loaded graph, if any.
228 if (m_graphId >= 0)
229 {
231 m_graphId = -1;
232 }
233
235 if (newId < 0)
236 {
237 SYSTEM_LOG << "[BehaviorTreeRenderer] Failed to load graph: " << path << "\n";
238 return false;
239 }
240
242 m_filePath = path;
244 SYSTEM_LOG << "[BehaviorTreeRenderer] Loaded BT graph: " << path
245 << " (id=" << m_graphId << ")\n";
246 return true;
247}
248
249bool BehaviorTreeRenderer::Save(const std::string& path)
250{
251 if (m_graphId < 0)
252 return false;
253
254 const std::string savePath = path.empty() ? m_filePath : path;
255 if (savePath.empty())
256 return false;
257
259 if (ok && !path.empty())
260 m_filePath = path;
261 return ok;
262}
263
265{
266 if (m_graphId < 0)
267 return false;
268
270 return (graph != nullptr) && graph->IsDirty();
271}
272
274{
275 return "BehaviorTree";
276}
277
279{
280 return m_filePath;
281}
282
283void BehaviorTreeRenderer::AcceptNodeDrop(const std::string& nodeType, float screenX, float screenY)
284{
285 // Validate graph is active
286 if (m_graphId < 0)
287 {
288 SYSTEM_LOG << "[BehaviorTreeRenderer] Cannot drop node: no active graph\n";
289 return;
290 }
291
293 if (!graph)
294 {
295 SYSTEM_LOG << "[BehaviorTreeRenderer] Cannot drop node: graph not found (id=" << m_graphId << ")\n";
296 return;
297 }
298
299 // Convert nodeType string format (e.g., "BT_Selector") to enum
300 // StringToNodeType expects just the suffix part (e.g., "Selector")
301 std::string typeNameForConversion = nodeType;
302
303 // Remove "BT_" prefix if present for conversion to NodeType enum
304 if (typeNameForConversion.length() > 3 && typeNameForConversion.substr(0, 3) == "BT_")
305 {
307 }
308
310
311 // Transform screen coordinates to canvas coordinates
312 // Formula: canvas = (screen - canvasScreenPos - offset) / zoom
313 // For BehaviorTree, we don't have explicit pan/zoom yet, so use direct transformation
315
316 // Create the new node at the drop position
317 int newNodeId = graph->CreateNode(enumType, relativePos.x, relativePos.y, nodeType);
318
319 if (newNodeId >= 0)
320 {
321 SYSTEM_LOG << "[BehaviorTreeRenderer] Created node from drop: type=" << nodeType
322 << ", id=" << newNodeId << ", pos=(" << relativePos.x << "," << relativePos.y << ")\n";
323
324 // Mark graph as dirty for save tracking
325 graph->MarkDirty();
326 }
327 else
328 {
329 SYSTEM_LOG << "[BehaviorTreeRenderer] Failed to create node from drop: type=" << nodeType << "\n";
330 }
331}
332
334{
335 // Check that we have an active graph
336 if (m_graphId < 0)
337 return;
338
340 if (!graph)
341 return;
342
343 // Get keyboard state
344 bool ctrlPressed = ImGui::GetIO().KeyCtrl;
345
346 // Ctrl+C: Copy selected node
347 if (ctrlPressed && ImGui::IsKeyPressed(ImGuiKey_C))
348 {
350 if (selectedNodeId >= 0)
351 {
352 std::vector<int> nodeIds = { selectedNodeId };
353 graph->CopyNodesToClipboard(nodeIds);
354 SYSTEM_LOG << "[BehaviorTreeRenderer] Copied node " << selectedNodeId << "\n";
355 }
356 }
357
358 // Ctrl+V: Paste nodes from clipboard
359 if (ctrlPressed && ImGui::IsKeyPressed(ImGuiKey_V))
360 {
361 if (!graph->m_clipboardData.empty())
362 {
363 std::vector<int> pastedNodeIds = graph->PasteNodesFromClipboard(30.0f, 30.0f);
364 if (!pastedNodeIds.empty())
365 {
366 SYSTEM_LOG << "[BehaviorTreeRenderer] Pasted " << pastedNodeIds.size() << " node(s)\n";
367 // Select first pasted node for convenience
369 }
370 }
371 }
372
373 // Ctrl+D: Duplicate selected node
374 if (ctrlPressed && ImGui::IsKeyPressed(ImGuiKey_D))
375 {
377 if (selectedNodeId >= 0)
378 {
379 std::vector<int> nodeIds = { selectedNodeId };
380 std::vector<int> duplicatedNodeIds = graph->DuplicateNodes(nodeIds, 30.0f, 30.0f);
381 if (!duplicatedNodeIds.empty())
382 {
383 SYSTEM_LOG << "[BehaviorTreeRenderer] Duplicated node " << selectedNodeId << "\n";
384 // Select first duplicated node
386 }
387 }
388 }
389}
390
392{
393 // Step 1: Validate graph is loaded
394 if (m_graphId < 0)
395 {
396 SYSTEM_LOG << "[BehaviorTreeRenderer] Cannot run simulation: no active graph\n";
397 return;
398 }
399
401 if (!graph)
402 {
403 SYSTEM_LOG << "[BehaviorTreeRenderer] Cannot run simulation: graph not found (id=" << m_graphId << ")\n";
404 return;
405 }
406
407 SYSTEM_LOG << "[BehaviorTreeRenderer] Starting BehaviorTree simulation for: " << graph->name << "\n";
408
409 // Step 2: Convert NodeGraph to BehaviorTreeAsset
412 btAsset.name = graph->name;
413
414 // CRITICAL FIX: Don't use graph->rootNodeId (often -1). Instead, scan for actual Root node.
415 // Find the first node of type BT_Root
416 btAsset.rootNodeId = 0; // Default to 0 if no root found
417 std::vector<GraphNode*> allNodes = graph->GetAllNodes();
418
420 {
422 {
423 btAsset.rootNodeId = static_cast<uint32_t>(candidateNode->id);
424 SYSTEM_LOG << "[BehaviorTreeRenderer] Found Root node at ID: " << candidateNode->id << "\n";
425 break; // Use the first Root node found
426 }
427 }
428
429 if (btAsset.rootNodeId == 0)
430 {
431 SYSTEM_LOG << "[BehaviorTreeRenderer] WARNING: No Root node found in graph. Using ID 0 as fallback.\n";
432 }
433
434 // Convert all GraphNode entries to BTNode entries
435 for (GraphNode* gNode : allNodes)
436 {
437 if (!gNode)
438 continue;
439
441 btNode.id = static_cast<uint32_t>(gNode->id);
442 btNode.name = gNode->name;
443 btNode.editorPosX = gNode->posX;
444 btNode.editorPosY = gNode->posY;
445
446 // Convert childIds from int to uint32_t
447 for (int childId : gNode->childIds)
448 {
449 btNode.childIds.push_back(static_cast<uint32_t>(childId));
450 }
451
452 btNode.decoratorChildId = static_cast<uint32_t>(gNode->decoratorChildId >= 0 ? gNode->decoratorChildId : 0);
453
454 // Map NodeType to BTNodeType
455 switch (gNode->type)
456 {
459 break;
462 break;
465 btNode.conditionTypeString = gNode->conditionType;
466 break;
469 btNode.stringParams["actionType"] = gNode->actionType;
470 break;
474 break;
479 break;
482 break;
485 btNode.eventType = gNode->eventType;
486 btNode.eventMessage = gNode->eventMessage;
487 break;
488 default:
490 break;
491 }
492
493 // Copy parameters
494 for (const auto& param : gNode->parameters)
495 {
496 btNode.stringParams[param.first] = param.second;
497 }
498
499 btAsset.nodes.push_back(btNode);
500 }
501
502 SYSTEM_LOG << "[BehaviorTreeRenderer] Converted NodeGraph to BehaviorTreeAsset: "
503 << btAsset.nodes.size() << " nodes\n";
504
505 // Step 3: Use native BehaviorTree executor (not VisualScript simulator)
506 // Create a tracer for the ExecutionTestPanel to use
508
509 // Execute the BehaviorTree using its native execution model
512
513 SYSTEM_LOG << "[BehaviorTreeRenderer] BehaviorTree execution completed with status: "
514 << (result == BTStatus::Success ? "SUCCESS" : "FAILURE") << "\n";
515
516 // Step 4: Display results in ExecutionTestPanel
518 {
519 // Make panel visible
520 m_executionTestPanel->SetVisible(true);
521
522 // Pass the trace to the panel for display
523 m_executionTestPanel->DisplayTrace(btTracer);
524
525 // Get the execution log for reference
526 std::string executionLog = btTracer.GetTraceLog();
527 SYSTEM_LOG << "[BehaviorTreeRenderer] Execution Trace:\n" << executionLog << "\n";
528
529 SYSTEM_LOG << "[BehaviorTreeRenderer] BehaviorTree simulation completed successfully\n";
530 }
531}
532
533// Phase 35.0: Canvas state management
535{
536 // For BehaviorTree, save the canvas screen position
537 // This helps preserve viewport context
539}
540
542{
543 // Restore previously saved canvas offset
545}
546
548{
549 // Return empty for now - can be extended to persist canvas state in JSON files
550 return "";
551}
552
554{
555 // Parse and restore from JSON - can be extended for persistence
556 (void)json;
557}
558
559} // namespace Olympe
Native BehaviorTree execution for simulation and tracing.
IGraphRenderer adapter for BehaviorTree graphs (wraps BTNodeGraphManager).
BTStatus
Behavior tree node execution status.
@ Success
Node completed successfully.
@ Action
Leaf node - performs an action.
@ Selector
OR node - succeeds if any child succeeds.
@ OnEvent
Phase 38b: OnEvent root - event-driven entry point (orange, event-triggered)
@ Sequence
AND node - succeeds if all children succeed.
@ Inverter
Decorator - inverts child result.
@ Condition
Leaf node - checks a condition.
@ Repeater
Decorator - repeats child N times.
@ Root
Phase 38b: Root node - entry point of behavior tree (green, fixed position)
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Panel for executing and testing blueprint graphs with simulation.
Graph execution tracing for simulation and validation.
static DataManager & Get()
Definition DataManager.h:91
void RenderFilePickerModal()
Renders the file picker modal if one is open.
void Render()
Render the property panel.
void ClearSelection()
Clear the current selection.
bool HasSelectedNode() const
Check if a node is currently selected.
void SetSelectedNode(int graphId, int nodeId)
Set the currently selected node ID.
void Initialize()
Initialize the panel.
Executes a BehaviorTree and collects trace information.
BTStatus ExecuteTree(const BehaviorTreeAsset &btAsset, GraphExecutionTracer &outTracer)
Execute a BehaviorTree and collect trace information.
void SetCanvasStateJSON(const std::string &json) override
Restore canvas state from JSON string.
struct Olympe::BehaviorTreeRenderer::CanvasState m_savedCanvasState
std::string m_filePath
Path that was loaded.
std::string GetGraphType() const override
Returns the graph type string, e.g.
bool CreateNew(const std::string &name="Untitled BehaviorTree")
Create a new empty BehaviorTree graph and set it active.
float m_canvasPanelWidth
Split ratio: 75% canvas, 25% right panel.
BehaviorTreeRenderer(NodeGraphPanel &panel)
std::unique_ptr< GraphExecutionTracer > m_lastTracer
Last simulation trace for results display.
int m_graphId
ID in BTNodeGraphManager; -1 if not loaded.
std::unique_ptr< AI::BTNodePalette > m_palette
BTNodePalette for drag-drop.
void Render() override
Renders the graph canvas into the current ImGui child window.
void AcceptNodeDrop(const std::string &nodeType, float screenX, float screenY)
Handle drop of node type at screen position.
void OnRunGraphClicked()
Run graph simulation via BehaviorTreeGraphAdapter + GraphExecutionSimulator Converts BT to graph form...
void RestoreCanvasState() override
Restore previously saved canvas viewport state Called when tab is reactivated.
NodeGraphPanel & m_panel
Shared panel reference (not owned)
BTNodePropertyPanel m_propertyPanel
Property editor for node properties.
void SaveCanvasState() override
Save the current canvas viewport state (pan, zoom, etc.) Called when tab is deactivated.
std::string GetCurrentPath() const override
Returns the last path successfully loaded/saved, or empty string.
std::string GetCanvasStateJSON() const override
Get canvas state as JSON string for persistence.
bool Save(const std::string &path) override
Saves the current graph state to disk.
bool IsDirty() const override
Returns true when the graph has unsaved changes.
std::unique_ptr< ExecutionTestPanel > m_executionTestPanel
REUSED: Simulation results panel.
ImVec2 m_canvasScreenPos
Screen position of canvas for drag-drop coordinate transformation.
void HandleKeyboardShortcuts()
Handle keyboard shortcuts for copy/paste/duplicate operations Ctrl+C: Copy selected node Ctrl+V: Past...
bool Load(const std::string &path) override
Loads a graph from a file on disk.
Records execution trace during graph simulation.
int LoadGraph(const std::string &filepath)
NodeGraph * GetGraph(int graphId)
int CreateGraph(const std::string &name, const std::string &type)
bool SaveGraph(int graphId, const std::string &filepath)
static NodeGraphManager & Get()
NodeGraphPanel - ImGui/ImNodes panel for node graph editing Provides visual editor for behavior trees...
bool m_SuppressGraphTabs
When true, RenderGraphTabs() is skipped (used by BehaviorTreeRenderer)
< Provides AssetID and INVALID_ASSET_ID
@ BT_OnEvent
Phase 38b: Event-driven root (green, event-triggered)
@ BT_Root
Phase 38b: Root entry point (green, fixed position)
nlohmann::json json
NodeType StringToNodeType(const std::string &str)
Represents a single node in a behavior tree.
uint32_t id
Unique node ID within tree.
#define SYSTEM_LOG