Botcraft 1.21.4
Loading...
Searching...
No Matches
BehaviourRenderer.cpp
Go to the documentation of this file.
1#if USE_IMGUI
4
7
8#include <imgui_node_editor.h>
9
10static constexpr float TREE_SPACING_VERTICAL = 50.0f;
11static constexpr float TREE_SPACING_HORIZONTAL = 150.0f;
12static constexpr float MIN_NODE_WIDTH = 200.0f;
13static constexpr float PIN_RADIUS = 5.0f;
14static constexpr float LINK_THICKNESS = 4.0f;
15
16static constexpr double BLACKBOARD_HIGHLIGHT_DURATION = 0.5;
17static const ImVec4 BLACKBOARD_HIGHLIGHT_COLOR = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
18
19namespace Botcraft
20{
21 namespace Renderer
22 {
23 enum class ImNodeStatus
24 {
25 Idle,
26 Success,
27 Failure,
29 };
30
31 /// @brief A class to hold data to be drawn on ImNode context
32 struct ImNode
33 {
34 const int id;
36 const std::string name;
37 const std::string classname;
38 int in_attr_id = -1;
39 ImNode* parent = nullptr;
40 std::vector<int> out_attr_ids;
41 std::vector<ImNode*> children;
42
43 float width = 0.0f;
44 float height = 0.0f;
45 float x = 0.0f;
46 float y = 0.0f;
47
49 bool visible = true;
50
51 ImNode(const int id_, const BehaviourNodeType t,
52 const std::string& name_, const std::string& classname_) :
53 id(id_), type(t), name(name_), classname(classname_) {}
54 };
55
56 /// @brief Convert a tree to a vector of ImNode with proper data
57 /// @param node Root node of the tree
58 /// @param index Index to give to the next node/pin
59 /// @return A vector of all the nodes
60 std::vector<std::unique_ptr<ImNode>> UnrollTreeStructure(const BaseNode* node, const int index = 1);
61
62 /// @brief Apply Buchheim & al. algorithm to position all nodes in the tree
63 /// (position is directly set inside the nodes)
64 /// Christoph Buchheim, Michael Jünger, and Sebastian Leipert, "Improving Walker’s algorithm to run in linear time", 2002
65 void PlaceNodesBuchheim(ImNode* root);
66
67 /// @brief Helper struct used in Buchheim algorithm
68 struct TreePosNode;
69
70 // Helper functions used in Buchheim algorithm
71 void FirstWalk(TreePosNode* v);
72 void SecondWalk(TreePosNode* v, const float m = 0.0f);
73 TreePosNode* Apportion(TreePosNode* v, TreePosNode* default_ancestor);
74 TreePosNode* Ancestor(TreePosNode* vim, TreePosNode* v, TreePosNode* default_ancestor);
75 void ExecuteShifts(TreePosNode* v);
76 void MoveSubtree(TreePosNode* wm, TreePosNode* wp, const float shift);
77 void GetMaxWidthPerDepthLevel(TreePosNode* node, std::vector<float>& max_width_depth);
78 void SetRealNodePos(TreePosNode* node, std::vector<float>& max_width_depth);
79 ImColor GetNodeColor(const BehaviourNodeType type);
80 ImColor GetStatusColor(const ImNodeStatus s);
81 void ToggleNodeVisibility(ImNode* node, const bool b);
82
84 context(nullptr), config(nullptr), active_node(nullptr),
85 recompute_node_position(false), paused(false), step(false)
86 {
87
88 }
89
94
96 {
97 {
98 std::scoped_lock<std::mutex> lock(nodes_mutex);
99 config = std::make_unique<ax::NodeEditor::Config>();
100 config->SettingsFile = nullptr;
101 config->DragButtonIndex = 3;
102 context = ax::NodeEditor::CreateEditor(config.get());
103 }
104
105 // Reset blackboard is already thread safe
106 {
108 }
109 }
110
112 {
113 std::scoped_lock<std::mutex> lock(nodes_mutex);
114 if (context == nullptr)
115 {
116 return;
117 }
118
119 ax::NodeEditor::SetCurrentEditor(context);
120
121 if (paused)
122 {
123 if (ImGui::ArrowButton("Play button", ImGuiDir_Right))
124 {
125 paused = false;
126 }
127 }
128 else
129 {
130 if (ImGui::Button("##Pause button", ImVec2(ImGui::GetFrameHeight(), ImGui::GetFrameHeight())))
131 {
132 paused = true;
133 }
134 // Draw pause icon (two rects) on button
135 const ImVec2 button_min_rect = ImGui::GetItemRectMin();
136 const ImVec2 button_size = ImGui::GetItemRectSize();
137
138 ImDrawList* draw_list = ImGui::GetWindowDrawList();
139 draw_list->AddRectFilled(
140 ImVec2(button_min_rect.x + 0.2f * button_size.x, button_min_rect.y + 0.1f * button_size.y),
141 ImVec2(button_min_rect.x + 0.4f * button_size.x, button_min_rect.y + 0.9f * button_size.y),
142 ImGui::GetColorU32(ImGuiCol_Text)
143 );
144 draw_list->AddRectFilled(
145 ImVec2(button_min_rect.x + 0.6f * button_size.x, button_min_rect.y + 0.1f * button_size.y),
146 ImVec2(button_min_rect.x + 0.8f * button_size.x, button_min_rect.y + 0.9f * button_size.y),
147 ImGui::GetColorU32(ImGuiCol_Text)
148 );
149 }
150 const bool disabled = !paused;
151 if (disabled)
152 {
153 ImGui::BeginDisabled();
154 }
155 ImGui::SameLine();
156 if (ImGui::Button("##Step button", ImVec2(ImGui::GetFrameHeight(), ImGui::GetFrameHeight())))
157 {
158 paused = false;
159 step = true;
160 }
161 // Draw step icon (rect + triangle) on button
162 const ImVec2 button_min_rect = ImGui::GetItemRectMin();
163 const ImVec2 button_size = ImGui::GetItemRectSize();
164 ImDrawList* draw_list = ImGui::GetWindowDrawList();
165 draw_list->AddRectFilled(
166 ImVec2(button_min_rect.x + 0.15f * button_size.x, button_min_rect.y + 0.2f * button_size.y),
167 ImVec2(button_min_rect.x + 0.3f * button_size.x, button_min_rect.y + 0.8f * button_size.y),
168 ImGui::GetColorU32(ImGuiCol_Text)
169 );
170
171 const ImVec2 triangle_center = ImVec2(button_min_rect.x + 0.7f * button_size.x, button_min_rect.y + 0.5f * button_size.y);
172
173 draw_list->AddTriangleFilled(
174 ImVec2(triangle_center.x + 0.205f * button_size.x, triangle_center.y),
175 ImVec2(triangle_center.x - 0.205f * button_size.x, triangle_center.y + 0.237f * button_size.y),
176 ImVec2(triangle_center.x - 0.205f * button_size.x, triangle_center.y - 0.237f * button_size.y),
177 ImGui::GetColorU32(ImGuiCol_Text)
178 );
179 if (disabled)
180 {
181 ImGui::EndDisabled();
182 }
183
184 ax::NodeEditor::PushStyleVar(ax::NodeEditor::StyleVar_FlowDuration, 0.5f);
185 ax::NodeEditor::PushStyleVar(ax::NodeEditor::StyleVar_SelectedNodeBorderWidth, 10.0f);
186 ax::NodeEditor::PushStyleColor(ax::NodeEditor::StyleColor_Flow, GetStatusColor(ImNodeStatus::Running));
187 ax::NodeEditor::PushStyleColor(ax::NodeEditor::StyleColor_FlowMarker, GetStatusColor(ImNodeStatus::Running));
188 ax::NodeEditor::PushStyleColor(ax::NodeEditor::StyleColor_PinRect, ImColor(0, 0, 0, 0));
189 ax::NodeEditor::PushStyleColor(ax::NodeEditor::StyleColor_Bg, ImColor(60, 60, 70, 100));
190
191 ax::NodeEditor::Begin("Behaviour");
192
193 // Render all nodes
194 for (size_t i = 0; i < nodes.size(); ++i)
195 {
196 RenderNode(i);
197
198 // If thats the first time we render this node,
199 // update its size (needed for placing it correctly)
200 if (nodes[i]->width == 0.0f && nodes[i]->height == 0.0f)
201 {
202 const ImVec2 size = ax::NodeEditor::GetNodeSize(nodes[i]->id);
203 nodes[i]->width = size.x;
204 nodes[i]->height = size.y;
205 }
206 }
207
209 {
210 if (nodes.size() > 1)
211 {
212 PlaceNodesBuchheim(nodes.front().get());
213 }
215 }
216
217 // Draw all links between nodes
218 int current_link_id = 0;
219 for (const std::unique_ptr<ImNode>& node : nodes)
220 {
221 for (size_t i = 0; i < node->out_attr_ids.size(); ++i)
222 {
223 if (!node->children[i]->visible)
224 {
225 continue;
226 }
227 ax::NodeEditor::Link(current_link_id++, node->out_attr_ids[i], node->children[i]->in_attr_id, GetStatusColor(node->children[i]->status), LINK_THICKNESS);
228 if (node->children[i]->status == ImNodeStatus::Running)
229 {
230 ax::NodeEditor::Flow(current_link_id - 1);
231 }
232 }
233 }
234
235 ax::NodeEditor::End();
236 if (paused)
237 {
238 const ImVec2 node_editor_min_rect = ImGui::GetItemRectMin();
239 const ImVec2 node_editor_max_rect = ImGui::GetItemRectMax();
240 ImDrawList* draw_list = ImGui::GetWindowDrawList();
241 draw_list->AddRect(
242 node_editor_min_rect,
243 node_editor_max_rect,
244 ImColor(5, 195, 221)
245 );
246 }
247
248 ax::NodeEditor::PopStyleColor();
249 ax::NodeEditor::PopStyleColor();
250 ax::NodeEditor::PopStyleColor();
251 ax::NodeEditor::PopStyleColor();
252 ax::NodeEditor::PopStyleVar();
253 ax::NodeEditor::PopStyleVar();
254
255 if (paused && ImGui::IsKeyPressed(ImGuiKey_F5))
256 {
257 paused = false;
258 step = false;
259 }
260 if (paused && ImGui::IsKeyPressed(ImGuiKey_F10))
261 {
262 paused = false;
263 step = true;
264 }
265
266 ax::NodeEditor::SetCurrentEditor(nullptr);
267 }
268
269 ImVec4 ColorInterpolation(const ImVec4& a, const ImVec4& b, const float t)
270 {
271 if (t < 0.0f)
272 {
273 return a;
274 }
275 if (t >= 1.0f)
276 {
277 return b;
278 }
279
280 return ImVec4(
281 (1 - t) * a.x + t * b.x,
282 (1 - t) * a.y + t * b.y,
283 (1 - t) * a.z + t * b.z,
284 (1 - t) * a.w + t * b.w
285 );
286 }
287
289 {
290 ImGui::PushID(&node);
291 if (node.contains("children"))
292 {
293 // If expanded, display children
294 ImGui::PushStyleColor(ImGuiCol_Text, ColorInterpolation(ImGui::GetStyle().Colors[ImGuiCol_Text], BLACKBOARD_HIGHLIGHT_COLOR, node["timer"].get<double>() / BLACKBOARD_HIGHLIGHT_DURATION));
295 const bool item_clicked = ImGui::TreeNodeEx(name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth);
296 ImGui::PopStyleColor();
297 if (item_clicked)
298 {
299 ProtocolCraft::Json::Object& children = node["children"].get_object();
300 for (auto& [k, v] : children)
301 {
303 }
304 ImGui::TreePop();
305 }
306 }
307 else
308 {
309 ImGui::PushStyleColor(ImGuiCol_Text, ColorInterpolation(ImGui::GetStyle().Colors[ImGuiCol_Text], BLACKBOARD_HIGHLIGHT_COLOR, node["timer"].get<double>() / BLACKBOARD_HIGHLIGHT_DURATION));
310 if (ImGui::TreeNodeEx(name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth))
311 {
312 if (ImGui::TreeNodeEx(node["content"].get_string().c_str(), ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_SpanFullWidth))
313 {
314 // Do something when value is clicked?
315 }
316 ImGui::TreePop();
317 }
318 ImGui::PopStyleColor();
319 }
320 ImGui::PopID();
321 }
322
324 {
325 node["timer"] = std::max(0.0, node["timer"].get<double>() - ImGui::GetIO().DeltaTime);
326 if (node.contains("children"))
327 {
328 ProtocolCraft::Json::Object& children = node["children"].get_object();
329 for (auto& [_, v] : children)
330 {
332 }
333 }
334 }
335
342
344 {
345 {
346 std::scoped_lock<std::mutex> lock(nodes_mutex);
347 ax::NodeEditor::DestroyEditor(context);
348 context = nullptr;
349 config.reset();
350 nodes.clear();
351 active_node = nullptr;
353 paused = false;
354 step = false;
355 }
356
358 }
359
361 {
362 std::scoped_lock<std::mutex> lock(nodes_mutex);
365 }
366
368 {
369 std::scoped_lock<std::mutex> lock(nodes_mutex);
370 for (const auto& node : nodes)
371 {
372 node->status = ImNodeStatus::Idle;
373 }
374
375 active_node = nodes[0].get();
376 }
377
379 {
380 std::scoped_lock<std::mutex> lock(nodes_mutex);
381 if (active_node != nullptr)
382 {
384
385 if (context != nullptr && active_node->visible)
386 {
387 ax::NodeEditor::SetCurrentEditor(context);
388 if (ax::NodeEditor::IsNodeSelected(active_node->id))
389 {
390 paused = true;
392 }
393 else if (paused || step)
394 {
396 }
397 ax::NodeEditor::SetCurrentEditor(nullptr);
398
399 if (step)
400 {
401 paused = true;
402 step = false;
403 }
404 }
405 // If current node is not visible, don't stop to it
406 else
407 {
408 step = paused || step;
409 paused = false;
410 }
411 }
412 }
413
415 {
416 std::scoped_lock<std::mutex> lock(nodes_mutex);
417 if (active_node != nullptr)
418 {
420 if (active_node->parent != nullptr)
421 {
423 }
424 }
425 }
426
428 {
429 std::scoped_lock<std::mutex> lock(nodes_mutex);
430 if (active_node != nullptr)
431 {
433 }
434 }
435
437 {
438 std::scoped_lock<std::mutex> lock(nodes_mutex);
439 return paused;
440 }
441
442 void BehaviourRenderer::RenderNode(const size_t index)
443 {
444 ImNode* node = nodes[index].get();
445
446 if (!node->visible)
447 {
448 return;
449 }
450
451 const ImVec2 mouse_pos = ImGui::GetMousePos();
452 const bool mouse_left_click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
453
454 ax::NodeEditor::PushStyleVar(ax::NodeEditor::StyleVar::StyleVar_NodePadding, ImVec4(0, 4, 0, 8));
455 ax::NodeEditor::PushStyleVar(ax::NodeEditor::StyleVar::StyleVar_NodeRounding, 4.0f);
456 ax::NodeEditor::PushStyleVar(ax::NodeEditor::StyleVar::StyleVar_NodeBorderWidth, 0.0f);
457
458 ax::NodeEditor::BeginNode(node->id);
459
460 // Header
461 ImGui::BeginGroup();
462 ImGui::Dummy(ImVec2(1, 0));
463 ImGui::SameLine();
464 ImGui::TextUnformatted(node->name.c_str());
465 ImGui::SameLine();
466 ImGui::Dummy(ImVec2(1, 0));
467 ImGui::Dummy(ImVec2(0, ax::NodeEditor::GetStyle().NodePadding.y));
468 ImGui::EndGroup();
469 const float title_bar_max_y = ImGui::GetItemRectMax().y;
470 const float title_bar_width = ImGui::GetItemRectSize().x;
471
472 // In Pin
473 ImGui::BeginGroup();
474 if (node->in_attr_id != -1)
475 {
476 ax::NodeEditor::PushStyleVar(ax::NodeEditor::StyleVar_PivotAlignment, ImVec2(0.0f, 0.5f));
477 ax::NodeEditor::PushStyleVar(ax::NodeEditor::StyleVar_PivotSize, ImVec2(0, 0));
478 ax::NodeEditor::BeginPin(node->in_attr_id, ax::NodeEditor::PinKind::Input);
479
480 ImGui::Dummy(ImVec2(1, ImGui::GetTextLineHeight()));
481
482 const ImVec2 size = ImGui::GetItemRectSize();
483 ImDrawList* draw_list = ImGui::GetWindowDrawList();
484 const ImVec2 p = ImGui::GetCursorScreenPos();
485 draw_list->AddCircleFilled(ImVec2(p.x, p.y - size.y + 0.5f * PIN_RADIUS), PIN_RADIUS, GetStatusColor(node->status), 8);
486
487 ax::NodeEditor::EndPin();
488 ax::NodeEditor::PopStyleVar();
489 ax::NodeEditor::PopStyleVar();
490 }
491 ImGui::EndGroup();
492 float pins_width = ImGui::GetItemRectSize().x;
493
494 ImGui::SameLine();
495 ImGui::TextUnformatted(node->classname.c_str());
496 ImGui::SameLine();
497 pins_width += ImGui::GetItemRectSize().x;
498
499
500 ImGui::BeginGroup();
501 for (int i = 0; i < node->out_attr_ids.size(); ++i)
502 {
503 ax::NodeEditor::PushStyleVar(ax::NodeEditor::StyleVar_PivotAlignment, ImVec2(1.0f, 0.5f));
504 ax::NodeEditor::PushStyleVar(ax::NodeEditor::StyleVar_PivotSize, ImVec2(0, 0));
505 ax::NodeEditor::BeginPin(node->out_attr_ids[i], ax::NodeEditor::PinKind::Output);
506
507 ImGui::Dummy(ImVec2(std::max(1.0f, std::max(MIN_NODE_WIDTH - pins_width, title_bar_width - pins_width)), ImGui::GetTextLineHeight()));
508 const ImVec2 size = ImGui::GetItemRectSize();
509
510 ImDrawList* draw_list = ImGui::GetWindowDrawList();
511 ImVec2 p = ImGui::GetCursorScreenPos();
512 const ImVec2 circle_center(p.x + size.x, p.y - size.y + 0.5f * PIN_RADIUS);
513 // If click in the pin, then toggle on/off the visibility of this child
514 if (mouse_left_click &&
515 (
516 (mouse_pos.x - circle_center.x) * (mouse_pos.x - circle_center.x) +
517 (mouse_pos.y - circle_center.y) * (mouse_pos.y - circle_center.y)
519 {
520 ToggleNodeVisibility(node->children[i], !node->children[i]->visible);
522 }
523
524 if (node->children[i]->visible)
525 {
526 draw_list->AddCircleFilled(circle_center, PIN_RADIUS, GetStatusColor(node->children[i]->status), 8);
527 }
528 else
529 {
530 draw_list->AddCircle(circle_center, PIN_RADIUS, GetStatusColor(node->children[i]->status), 8);
531 }
532
533 ax::NodeEditor::EndPin();
534 ax::NodeEditor::PopStyleVar();
535 ax::NodeEditor::PopStyleVar();
536 }
537 if (node->out_attr_ids.size() == 0)
538 {
539 ImGui::Dummy(ImVec2(std::max(1.0f, std::max(MIN_NODE_WIDTH - pins_width, title_bar_width - pins_width)), ImGui::GetTextLineHeight()));
540 }
541 ImGui::EndGroup();
542
543 ax::NodeEditor::EndNode();
544
545 ImVec2 node_rect_min = ImGui::GetItemRectMin();
546 ImVec2 node_rect_max = ImGui::GetItemRectMax();
547
548
549 ImDrawList* draw_list = ax::NodeEditor::GetNodeBackgroundDrawList(node->id);
550 //Draw header background
551 draw_list->AddRectFilled(
552 ImVec2(node_rect_min.x, node_rect_min.y),
553 ImVec2(node_rect_max.x, title_bar_max_y),
554 GetNodeColor(node->type),
555 ax::NodeEditor::GetStyle().NodeRounding,
556 ImDrawFlags_RoundCornersTop);
557
558 // Custom node border
559 draw_list->AddRect(
560 node_rect_min,
561 node_rect_max,
562 GetStatusColor(node->status),
563 ax::NodeEditor::GetStyle().NodeRounding,
564 ImDrawFlags_RoundCornersAll,
565 2.5f);
566
567
568 ax::NodeEditor::PopStyleVar();
569 ax::NodeEditor::PopStyleVar();
570 ax::NodeEditor::PopStyleVar();
571
572 ax::NodeEditor::SetNodePosition(node->id, ImVec2(node->x, node->y));
573 }
574
576 {
577 // Get all currently selected elements
578 std::vector<ax::NodeEditor::NodeId> selected_nodes;
579 std::vector<ax::NodeEditor::LinkId> selected_links;
580
581 selected_nodes.resize(ax::NodeEditor::GetSelectedObjectCount());
582 selected_links.resize(ax::NodeEditor::GetSelectedObjectCount());
583
584 const int node_count = ax::NodeEditor::GetSelectedNodes(selected_nodes.data(), static_cast<int>(selected_nodes.size()));
585 const int link_count = ax::NodeEditor::GetSelectedLinks(selected_links.data(), static_cast<int>(selected_links.size()));
586
587 selected_nodes.resize(node_count);
588 selected_links.resize(link_count);
589
590 // Deselect everything
591 for (const auto& nid : selected_nodes)
592 {
593 ax::NodeEditor::DeselectNode(nid);
594 }
595 for (const auto& lid : selected_links)
596 {
597 ax::NodeEditor::DeselectLink(lid);
598 }
599
600 // select active node
601 ax::NodeEditor::SelectNode(active_node->id);
602 ax::NodeEditor::NavigateToSelection();
603 ax::NodeEditor::DeselectNode(active_node->id);
604
605 // Reselect everything
606 for (const auto& nid : selected_nodes)
607 {
608 ax::NodeEditor::SelectNode(nid, true);
609 }
610 for (const auto& lid : selected_links)
611 {
612 ax::NodeEditor::SelectLink(lid, true);
613 }
614 }
615
616
617 std::vector<std::unique_ptr<ImNode>> UnrollTreeStructure(const BaseNode* node, const int index)
618 {
619 std::vector<std::unique_ptr<ImNode>> output;
620
621 if (node == nullptr)
622 {
623 return output;
624 }
625
626 int current_index = index;
627
628 output.push_back(std::make_unique<ImNode>(current_index++, node->GetNodeType(), node->GetName(), node->GetClassName()));
629
630 // We are no longer processing the root, so add an input attr
631 if (index != 1)
632 {
633 output.back()->in_attr_id = current_index++;
634 }
635
636 // We need to remember this node place because after the
637 // first child it won't be located at output.back() anymore
638 const size_t this_node_index = output.size() - 1;
639
640 for (size_t i = 0; i < node->GetNumChildren(); ++i)
641 {
642 // Recursive call to get the tree representation of the child
643 std::vector<std::unique_ptr<ImNode>> child_tree = UnrollTreeStructure(node->GetChild(i), current_index);
644
645 // Update current indices
646 current_index += static_cast<int>(child_tree.size());
647 for (size_t j = 0; j < child_tree.size(); ++j)
648 {
649 current_index += 1 + static_cast<int>(child_tree[j]->children.size());
650 }
651
652 // Create one out attribute for this child
653 output[this_node_index]->out_attr_ids.push_back(current_index++);
654
655 // Set parent/child relationship
656 child_tree.front()->parent = output[this_node_index].get();
657 output[this_node_index]->children.push_back(child_tree.front().get());
658
659 // Copy the child tree node in this output
660 output.insert(
661 output.end(),
662 std::make_move_iterator(child_tree.begin()),
663 std::make_move_iterator(child_tree.end())
664 );
665 }
666
667 return output;
668 }
669
671 {
672 TreePosNode(ImNode* real_node_, TreePosNode* parent_ = nullptr, int depth_ = 0, int sibling_order_number_ = 1)
673 {
674 prelim = 0.0f;
675 depth = depth_;
676 real_node = real_node_;
677 ancestor = this;
678
679 children.reserve(real_node->children.size());
680 int sibling_index = 1;
681 for (int i = 0; i < real_node->children.size(); ++i)
682 {
683 if (real_node->children[i]->visible)
684 {
685 children.push_back(std::make_unique<TreePosNode>(real_node->children[i], this, depth + 1, sibling_index++));
686 }
687 }
688 parent = parent_;
689 thread = nullptr;
690 mod = 0.0f;
691 change = 0;
692 shift = 0;
693 number = sibling_order_number_;
694 }
695
696 bool IsLeaf() const
697 {
698 return children.size() == 0;
699 }
700
702 {
703 return IsLeaf() ? thread : children[0].get();
704 }
705
707 {
708 return IsLeaf() ? thread : children[children.size() - 1].get();
709 }
710
712 {
713 TreePosNode* output = nullptr;
714 if (parent != nullptr)
715 {
716 for (const auto& c : parent->children)
717 {
718 if (c.get() == this)
719 {
720 return output;
721 }
722 output = c.get();
723 }
724 }
725 return output;
726 }
727
728 bool IsLeftmostSibling() const
729 {
730 if (parent != nullptr && parent->children[0].get() == this)
731 {
732 return true;
733 }
734 return false;
735 }
736
738 {
739 if (parent != nullptr && this != parent->children[0].get())
740 {
741 return parent->children[0].get();
742 }
743 return nullptr;
744 }
745
746 float prelim;
747 int depth;
750 std::vector<std::unique_ptr<TreePosNode>> children;
753 float mod;
754 float change;
755 float shift;
757 };
758
760 {
761 TreePosNode root_pos_node(root);
762
763 FirstWalk(&root_pos_node);
764 SecondWalk(&root_pos_node, -root_pos_node.prelim);
765
766 std::vector<float> max_width_depth;
767 GetMaxWidthPerDepthLevel(&root_pos_node, max_width_depth);
768
769 SetRealNodePos(&root_pos_node, max_width_depth);
770 }
771
773 {
774 if (v->IsLeaf())
775 {
776 if (v->IsLeftmostSibling())
777 {
778 v->prelim = 0.0f;
779 }
780 else
781 {
783 }
784 }
785 else
786 {
787 TreePosNode* default_ancestor = v->children[0].get();
788 for (const auto& w : v->children)
789 {
790 FirstWalk(w.get());
791 default_ancestor = Apportion(w.get(), default_ancestor);
792 }
793 ExecuteShifts(v);
794
795 const float midpoint = 0.5f * (
796 v->children[0].get()->prelim +
797 v->children[v->children.size() - 1].get()->prelim +
798 v->children[v->children.size() - 1].get()->real_node->height -
799 v->children[0].get()->real_node->height
800 );
801 if (TreePosNode* w = v->LeftBrother())
802 {
803 v->prelim = w->prelim + w->real_node->height + TREE_SPACING_VERTICAL;
804 v->mod = v->prelim - midpoint;
805 }
806 else
807 {
808 v->prelim = midpoint;
809 }
810 }
811 }
812
813 void SecondWalk(TreePosNode* v, const float m)
814 {
815 for (const auto& w : v->children)
816 {
817 SecondWalk(w.get(), m + v->mod);
818 }
819 v->prelim += m;
820 }
821
823 {
824 TreePosNode* output = default_ancestor;
825
826 TreePosNode* w = v->LeftBrother();
827
828 if (w == nullptr)
829 {
830 return output;
831 }
832
833 TreePosNode* vip = v;
834 TreePosNode* vop = v;
835 TreePosNode* vim = w;
836 TreePosNode* vom = vip->GetLeftMostSibling();
837 float sip = vip->mod;
838 float sop = vop->mod;
839 float sim = vim->mod;
840 float som = vom->mod;
841
842 while (vim->NextRight() != nullptr && vip->NextLeft() != nullptr)
843 {
844 vim = vim->NextRight();
845 vip = vip->NextLeft();
846 vom = vom->NextLeft();
847 vop = vop->NextRight();
848 vop->ancestor = v;
849 const float shift = (vim->prelim + sim) - (vip->prelim + sip) + TREE_SPACING_VERTICAL + vim->real_node->height;
850 if (shift > 0.0f)
851 {
852 MoveSubtree(Ancestor(vim, v, default_ancestor), v, shift);
853 sip += shift;
854 sop += shift;
855 }
856 sim += vim->mod;
857 sip += vip->mod;
858 som += vom->mod;
859 sop += vop->mod;
860 }
861 if (vim->NextRight() != nullptr && vop->NextRight() == nullptr)
862 {
863 vop->thread = vim->NextRight();
864 vop->mod += sim - sop;
865 }
866 else
867 {
868 if (vip->NextLeft() != nullptr && vom->NextLeft() == nullptr)
869 {
870 vom->thread = vip->NextLeft();
871 vom->mod += sip - som;
872 }
873 output = v;
874 }
875 return output;
876 }
877
879 {
880 if (v->parent == nullptr)
881 {
882 return default_ancestor;
883 }
884
885 for (const auto& c : v->parent->children)
886 {
887 if (vim->ancestor == c.get())
888 {
889 return vim->ancestor;
890 }
891 }
892 return default_ancestor;
893 }
894
896 {
897 float shift = 0.0f;
898 float change = 0.0f;
899 for (int i = static_cast<int>(v->children.size()) - 1; i >= 0; --i)
900 {
901 TreePosNode* w = v->children[i].get();
902 w->prelim += shift;
903 w->mod += shift;
904 change += w->change;
905 shift += w->shift + change;
906 }
907 }
908
909 void MoveSubtree(TreePosNode* wm, TreePosNode* wp, const float shift)
910 {
911 const int num_subtrees = wp->number - wm->number;
912 const float shift_per_subtree = shift / num_subtrees;
913 wp->change -= shift_per_subtree;
914 wp->shift += shift;
915 wm->change += shift_per_subtree;
916 wp->prelim += shift;
917 wp->mod += shift;
918 }
919
920 void GetMaxWidthPerDepthLevel(TreePosNode* node, std::vector<float>& max_width_depth)
921 {
922 if (max_width_depth.size() < node->depth + 1)
923 {
924 max_width_depth.resize(node->depth + 1, 0.0f);
925 }
926
927 max_width_depth[node->depth] = std::max(node->real_node->width, max_width_depth[node->depth]);
928
929 for (const auto& c : node->children)
930 {
931 GetMaxWidthPerDepthLevel(c.get(), max_width_depth);
932 }
933 }
934
935 void SetRealNodePos(TreePosNode* node, std::vector<float>& max_width_depth)
936 {
937 float depth_offset = 0.0f;
938 for (size_t i = 0; i < node->depth; ++i)
939 {
940 depth_offset += max_width_depth[i] + TREE_SPACING_HORIZONTAL;
941 }
942
943 node->real_node->x = depth_offset + 0.5f * (max_width_depth[node->depth] - node->real_node->width);
944 node->real_node->y = node->prelim;
945 for (const auto& c : node->children)
946 {
947 SetRealNodePos(c.get(), max_width_depth);
948 }
949 }
950
951 ImColor GetNodeColor(const BehaviourNodeType type)
952 {
953 switch (type)
954 {
956 return ImColor(60, 40, 0);
958 return ImColor(0, 50, 0);
960 return ImColor(40, 75, 125);
962 return ImColor(100, 40, 125);
963 }
964
965 // Never reached
966 return ImColor();
967 }
968
970 {
971 switch (s)
972 {
974 return ImColor(225, 225, 225);
976 return ImColor(25, 225, 25);
978 return ImColor(225, 25, 25);
980 return ImColor(255, 255, 0);
981 }
982
983 // Never reached
984 return ImColor();
985 }
986
987 void ToggleNodeVisibility(ImNode* node, const bool b)
988 {
989 node->visible = b;
990 for (size_t i = 0; i < node->children.size(); ++i)
991 {
992 ToggleNodeVisibility(node->children[i], b);
993 }
994 }
995
997 {
998 std::scoped_lock<std::mutex> lock(blackboard_mutex);
999 blackboard = { { "children", ProtocolCraft::Json::Object() }, { "timer", BLACKBOARD_HIGHLIGHT_DURATION } };
1000 }
1001
1002 void BehaviourRenderer::UpdateBlackboardValue(const std::string& key, const std::any& value)
1003 {
1004 std::vector<std::string> splitted = Utilities::SplitString(key, '.');
1005 // Empty key
1006 if (splitted.size() == 0)
1007 {
1008 return;
1009 }
1010
1011 std::scoped_lock<std::mutex> lock(blackboard_mutex);
1012
1013 bool changed = false;
1014 ProtocolCraft::Json::Value* current_json = &blackboard;
1015 for (size_t i = 0; i < splitted.size(); ++i)
1016 {
1017 // Create intermediary nodes
1018 if (!(*current_json)["children"].contains(splitted[i]))
1019 {
1020 changed = true;
1021 if (i < splitted.size() - 1)
1022 {
1023 (*current_json)["children"][splitted[i]] = { { "children", {} }, { "timer", 0.0 } };
1024 }
1025 else
1026 {
1027 (*current_json)["children"][splitted[i]] = { { "content", ""}, { "timer", 0.0 }};
1028 }
1029 }
1030 current_json = &(*current_json)["children"][splitted[i]];
1031 }
1032 const std::string new_content = Utilities::AnyParser::ToString(value);
1033 changed = changed || (*current_json)["content"].get_string() != new_content;
1034
1035 if (changed)
1036 {
1037 // Go down all values to update timers
1038 current_json = &blackboard;
1039 for (size_t i = 0; i < splitted.size(); ++i)
1040 {
1041 (*current_json)["timer"] = BLACKBOARD_HIGHLIGHT_DURATION;
1042 current_json = &(*current_json)["children"][splitted[i]];
1043 }
1044
1045 // Update leaf value
1046 (*current_json)["timer"] = BLACKBOARD_HIGHLIGHT_DURATION;
1047 (*current_json)["content"] = new_content;
1048 }
1049 }
1050
1051 void BehaviourRenderer::RemoveBlackboardValue(const std::string& key)
1052 {
1053 std::vector<std::string> splitted = Utilities::SplitString(key, '.');
1054 // Empty key
1055 if (splitted.size() == 0)
1056 {
1057 return;
1058 }
1059
1060 std::scoped_lock<std::mutex> lock(blackboard_mutex);
1061
1062 // From bottom to top, update the timer and remove the child
1063 // if no more siblings, mark parent for removal too
1064 bool remove = true;
1065 for (int depth = static_cast<int>(splitted.size()) - 1; depth > -1; --depth)
1066 {
1067 // Go down depth level
1068 ProtocolCraft::Json::Value* json_element = &blackboard;
1069 for (size_t i = 0; i < depth; ++i)
1070 {
1071 json_element = &(*json_element)["children"][splitted[i]];
1072 }
1073 (*json_element)["timer"] = BLACKBOARD_HIGHLIGHT_DURATION;
1074 if (remove)
1075 {
1076 (*json_element)["children"].get_object().erase(splitted[depth]);
1077 remove = (*json_element)["children"].size() == 0;
1078 }
1079 }
1080 }
1081 }
1082}
1083#endif
static constexpr float LINK_THICKNESS
static constexpr float MIN_NODE_WIDTH
static constexpr float PIN_RADIUS
static constexpr float TREE_SPACING_HORIZONTAL
static constexpr double BLACKBOARD_HIGHLIGHT_DURATION
static const ImVec4 BLACKBOARD_HIGHLIGHT_COLOR
static constexpr float TREE_SPACING_VERTICAL
virtual const BaseNode * GetChild(const size_t index) const =0
const std::string & GetName() const
Definition BaseNode.cpp:24
virtual size_t GetNumChildren() const =0
std::string GetClassName() const
Definition BaseNode.cpp:29
virtual BehaviourNodeType GetNodeType() const =0
ax::NodeEditor::EditorContext * context
void SetCurrentBehaviourTree(const BaseNode *root)
std::vector< std::unique_ptr< ImNode > > nodes
std::unique_ptr< ax::NodeEditor::Config > config
void UpdateBlackboardValue(const std::string &key, const std::any &value)
void RemoveBlackboardValue(const std::string &key)
static std::string ToString(const std::any &value)
Give a string representation of a std::any value.
Real class declaration, just a derived class of std::map<std::string, Value>
Definition Json.hpp:186
Main class, basically a JsonVariant with extra utility functions it doesn't inherit JsonVariant direc...
Definition Json.hpp:45
bool contains(const std::string &s) const
Definition Json.cpp:232
void ExecuteShifts(TreePosNode *v)
void RecursiveRenderBlackboardJsonNode(const std::string &name, ProtocolCraft::Json::Value &node)
void MoveSubtree(TreePosNode *wm, TreePosNode *wp, const float shift)
ImVec4 ColorInterpolation(const ImVec4 &a, const ImVec4 &b, const float t)
void PlaceNodesBuchheim(ImNode *root)
Apply Buchheim & al.
std::vector< std::unique_ptr< ImNode > > UnrollTreeStructure(const BaseNode *node, const int index=1)
Convert a tree to a vector of ImNode with proper data.
void FirstWalk(TreePosNode *v)
void GetMaxWidthPerDepthLevel(TreePosNode *node, std::vector< float > &max_width_depth)
TreePosNode * Apportion(TreePosNode *v, TreePosNode *default_ancestor)
void SetRealNodePos(TreePosNode *node, std::vector< float > &max_width_depth)
ImColor GetStatusColor(const ImNodeStatus s)
void SecondWalk(TreePosNode *v, const float m=0.0f)
TreePosNode * Ancestor(TreePosNode *vim, TreePosNode *v, TreePosNode *default_ancestor)
void ToggleNodeVisibility(ImNode *node, const bool b)
void RecursiveUpdateTimerBlackboardJsonNode(ProtocolCraft::Json::Value &node)
ImColor GetNodeColor(const BehaviourNodeType type)
std::vector< std::string > SplitString(const std::string &s, const char delimiter)
BehaviourNodeType
Definition BaseNode.hpp:8
A class to hold data to be drawn on ImNode context.
std::vector< ImNode * > children
const BehaviourNodeType type
ImNode(const int id_, const BehaviourNodeType t, const std::string &name_, const std::string &classname_)
TreePosNode(ImNode *real_node_, TreePosNode *parent_=nullptr, int depth_=0, int sibling_order_number_=1)
std::vector< std::unique_ptr< TreePosNode > > children
TreePosNode * GetLeftMostSibling() const