Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
BTGraphLayoutEngine.cpp
Go to the documentation of this file.
1/**
2 * @file BTGraphLayoutEngine.cpp
3 * @brief Implementation of graph layout engine for behavior tree visualization
4 */
5
7#include "../system/system_utils.h"
8#include <queue>
9#include <algorithm>
10
11namespace Olympe
12{
14 {
15 }
16
17 std::vector<BTNodeLayout> BTGraphLayoutEngine::ComputeLayout(
19 float nodeSpacingX,
20 float nodeSpacingY,
21 float zoomFactor)
22 {
23 // Suppress unused parameter warnings (fixed grid is used)
24 static_cast<void>(nodeSpacingX);
25 static_cast<void>(nodeSpacingY);
26 static_cast<void>(zoomFactor);
27
28 if (!tree || tree->nodes.empty())
29 {
30 return {};
31 }
32
33 // Clear previous state
34 m_layouts.clear();
35 m_nodeIdToIndex.clear();
36 m_layers.clear();
37 m_parentMap.clear();
38
39 // Phase 1: Assign nodes to layers via BFS
41
42 // Phase 2: Place nodes on fixed 400x200 grid (horizontal layout)
43 // layers become columns (x-axis), order within layer becomes rows (y-axis)
44 const float gridColumnSpacing = 400.0f; // pixels between adjacent layers (columns)
45 const float gridRowSpacing = 200.0f; // pixels between nodes in the same layer (rows)
46
47 for (auto& layout : m_layouts)
48 {
49 layout.position.x = layout.layer * gridColumnSpacing;
50 layout.position.y = layout.orderInLayer * gridRowSpacing;
51 }
52
53 SYSTEM_LOG << "[BTGraphLayout] Layout complete: " << m_layouts.size()
54 << " nodes positioned" << std::endl;
55
56 return m_layouts;
57 }
58
59 const BTNodeLayout* BTGraphLayoutEngine::GetNodeLayout(uint32_t nodeId) const
60 {
61 auto it = m_nodeIdToIndex.find(nodeId);
62 if (it != m_nodeIdToIndex.end())
63 {
64 return &m_layouts[it->second];
65 }
66 return nullptr;
67 }
68
69 bool BTGraphLayoutEngine::UpdateNodePosition(uint32_t nodeId, float x, float y)
70 {
71 auto it = m_nodeIdToIndex.find(nodeId);
72 if (it == m_nodeIdToIndex.end())
73 return false;
74 m_layouts[it->second].position.x = x;
75 m_layouts[it->second].position.y = y;
76 return true;
77 }
78
80 {
81 // BFS from root to assign layers
82 std::queue<std::pair<uint32_t, int>> queue; // (nodeId, layer)
83 std::map<uint32_t, int> visitedLayers;
84
85 queue.push({tree->rootNodeId, 0});
86 visitedLayers[tree->rootNodeId] = 0;
87
88 int maxLayer = 0;
89
90 while (!queue.empty())
91 {
92 std::pair<uint32_t, int> front = queue.front();
93 uint32_t nodeId = front.first;
94 int layer = front.second;
95 queue.pop();
96
97 const BTNode* node = tree->GetNode(nodeId);
98 if (!node)
99 continue;
100
101 // Create layout for this node
102 BTNodeLayout layout;
103 layout.nodeId = nodeId;
104 layout.layer = layer;
105 layout.orderInLayer = 0; // Will be set below
106
107 size_t idx = m_layouts.size();
108 m_layouts.push_back(layout);
109 m_nodeIdToIndex[nodeId] = idx;
110
111 maxLayer = std::max(maxLayer, layer);
112
113 // Get children and add to queue
114 auto children = GetChildren(node);
115 for (uint32_t childId : children)
116 {
117 // Only visit each node once (shortest path from root)
118 if (visitedLayers.find(childId) == visitedLayers.end())
119 {
120 visitedLayers[childId] = layer + 1;
121 queue.push({childId, layer + 1});
122 }
123 }
124 }
125
126 // Organize nodes into layers and set orderInLayer
127 m_layers.resize(maxLayer + 1);
128 for (const auto& layout : m_layouts)
129 {
130 m_layers[layout.layer].push_back(layout.nodeId);
131 }
132
133 for (size_t layerIdx = 0; layerIdx < m_layers.size(); ++layerIdx)
134 {
135 const auto& layer = m_layers[layerIdx];
136 for (size_t i = 0; i < layer.size(); ++i)
137 {
138 auto it = m_nodeIdToIndex.find(layer[i]);
139 if (it != m_nodeIdToIndex.end())
140 {
141 m_layouts[it->second].orderInLayer = static_cast<int>(i);
142 }
143 }
144 }
145
146 // Build parent map for reference
148 }
149
150 std::vector<uint32_t> BTGraphLayoutEngine::GetChildren(const BTNode* node) const
151 {
152 if (!node)
153 return {};
154
155 std::vector<uint32_t> children;
156
157 // Composite nodes (Selector, Sequence)
158 if (node->type == BTNodeType::Selector || node->type == BTNodeType::Sequence)
159 {
160 children = node->childIds;
161 }
162 // Decorator nodes (Inverter, Repeater)
163 else if (node->type == BTNodeType::Inverter || node->type == BTNodeType::Repeater)
164 {
165 if (node->decoratorChildId != 0)
166 {
167 children.push_back(node->decoratorChildId);
168 }
169 }
170
171 return children;
172 }
173
175 {
176 m_parentMap.clear();
177
178 for (const auto& node : tree->nodes)
179 {
180 auto children = GetChildren(&node);
181 for (uint32_t childId : children)
182 {
183 m_parentMap[childId].push_back(node.id);
184 }
185 }
186 }
187
188} // namespace Olympe
Graph layout engine for behavior tree visualization.
@ Selector
OR node - succeeds if any child succeeds.
@ Sequence
AND node - succeeds if all children succeed.
@ Inverter
Decorator - inverts child result.
@ Repeater
Decorator - repeats child N times.
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
bool UpdateNodePosition(uint32_t nodeId, float x, float y)
Update the stored position for a node (e.g.
const BTNodeLayout * GetNodeLayout(uint32_t nodeId) const
Get computed layout for a specific node.
std::vector< BTNodeLayout > m_layouts
std::vector< uint32_t > GetChildren(const BTNode *node) const
void BuildParentMap(const BehaviorTreeAsset *tree)
std::map< uint32_t, size_t > m_nodeIdToIndex
std::map< uint32_t, std::vector< uint32_t > > m_parentMap
void AssignLayers(const BehaviorTreeAsset *tree)
std::vector< BTNodeLayout > ComputeLayout(const BehaviorTreeAsset *tree, float nodeSpacingX=320.0f, float nodeSpacingY=180.0f, float zoomFactor=1.0f)
Compute layout for a behavior tree.
std::vector< std::vector< uint32_t > > m_layers
float x
Definition vector.h:25
< Provides AssetID and INVALID_ASSET_ID
Represents a single node in a behavior tree.
Vector position
Final position (x, y)
#define SYSTEM_LOG