Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
ComponentNodeRenderer.cpp
Go to the documentation of this file.
3#include "./../../third_party/imgui/imgui.h"
4#include "../../system/system_utils.h"
5
6namespace Olympe
7{
8 ComponentNodeRenderer::ComponentNodeRenderer() : m_showLabels(true), m_showProperties(true), m_nodeScale(1.0f)
9 {
10 m_style.normalColor = Vector(0.3f, 0.3f, 0.3f);
11 m_style.selectedColor = Vector(0.0f, 0.7f, 1.0f);
12 m_style.hoverColor = Vector(0.5f, 0.5f, 0.5f);
13 m_style.disabledColor = Vector(0.2f, 0.2f, 0.2f);
14 m_style.textColor = Vector(1.0f, 1.0f, 1.0f);
15 m_style.borderWidth = 2.0f;
16 m_style.cornerRadius = 5.0f;
17 }
19
22
24 {
26 m_canvasZoom = (zoom > 0.1f) ? zoom : 0.1f;
27 }
28
31
34
42
52
54 {
55 if (document == nullptr) { return; }
56
57 // Get the ImGui child window's screen position (top-left corner)
58 ImVec2 canvasScreenPos = ImGui::GetCursorScreenPos();
59
60 const std::vector<ComponentNode>& nodes = document->GetAllNodes();
61 for (size_t i = 0; i < nodes.size(); ++i)
62 {
63 RenderNode(nodes[i]);
64 }
65 }
66
68 {
69 if (document == nullptr) { return; }
70 const std::vector<std::pair<NodeId, NodeId>>& connections = document->GetConnections();
71 for (size_t i = 0; i < connections.size(); ++i)
72 {
75 if (sourceNode != nullptr && targetNode != nullptr)
76 {
77 Vector from = sourceNode->position;
78 from.x += sourceNode->size.x * 0.5f;
79 Vector to = targetNode->position;
80 to.x -= targetNode->size.x * 0.5f;
81
82 bool isHovered = (hoveredConnectionIndex == static_cast<int>(i));
84 }
85 }
86 }
87
90
98
100 {
101 Vector min = node.position;
102 min.x -= node.size.x * 0.5f;
103 min.y -= node.size.y * 0.5f;
104 Vector max = node.position;
105 max.x += node.size.x * 0.5f;
106 max.y += node.size.y * 0.5f;
107 return point.x >= min.x && point.x <= max.x && point.y >= min.y && point.y <= max.y;
108 }
109
111 {
112 outMin = node.position;
113 outMin.x -= node.size.x * 0.5f;
114 outMin.y -= node.size.y * 0.5f;
115 outMax = node.position;
116 outMax.x += node.size.x * 0.5f;
117 outMax.y += node.size.y * 0.5f;
118 return true;
119 }
120
123
126
129
131 {
132 ImDrawList* drawList = ImGui::GetWindowDrawList();
133 if (drawList == nullptr) { return; }
134
135 // Transform node position from canvas space to screen space
137
138 // Apply scaled node size
139 float scaledWidth = node.size.x * 0.5f * m_nodeScale * m_canvasZoom;
140 float scaledHeight = node.size.y * 0.5f * m_nodeScale * m_canvasZoom;
141
143 min.x -= scaledWidth;
144 min.y -= scaledHeight;
145
147 max.x += scaledWidth;
148 max.y += scaledHeight;
149
150 Vector color = GetNodeColor(node);
151 ImU32 bgColor = ImGui::GetColorU32(ImVec4(color.x, color.y, color.z, 1.0f));
152 ImU32 borderColor = ImGui::GetColorU32(ImVec4(
153 (color.x * 1.3f > 1.0f) ? 1.0f : color.x * 1.3f,
154 (color.y * 1.3f > 1.0f) ? 1.0f : color.y * 1.3f,
155 (color.z * 1.3f > 1.0f) ? 1.0f : color.z * 1.3f,
156 1.0f
157 ));
158
159 // Draw selection glow if node is selected
160 if (node.selected)
161 {
162 ImU32 glowColor = ImGui::GetColorU32(ImVec4(0.0f, 0.8f, 1.0f, 0.3f));
163 float glowSize = 4.0f * m_nodeScale * m_canvasZoom;
164 drawList->AddRectFilled(
165 ImVec2(min.x - glowSize, min.y - glowSize),
166 ImVec2(max.x + glowSize, max.y + glowSize),
167 glowColor,
169 );
170 }
171
172 drawList->AddRectFilled(
173 ImVec2(min.x, min.y),
174 ImVec2(max.x, max.y),
175 bgColor,
177 );
178
179 float borderWidth = node.selected ? m_style.borderWidth * 2.0f : m_style.borderWidth;
180 drawList->AddRect(
181 ImVec2(min.x, min.y),
182 ImVec2(max.x, max.y),
186 borderWidth
187 );
188
189 ImU32 titleBgColor = ImGui::GetColorU32(ImVec4(
190 (color.x * 0.8f > 1.0f) ? 1.0f : color.x * 0.8f,
191 (color.y * 0.8f > 1.0f) ? 1.0f : color.y * 0.8f,
192 (color.z * 0.8f > 1.0f) ? 1.0f : color.z * 0.8f,
193 1.0f
194 ));
195 float titleHeight = 25.0f * m_nodeScale * m_canvasZoom;
196 drawList->AddRectFilled(
197 ImVec2(min.x, min.y),
198 ImVec2(max.x, min.y + titleHeight),
201 );
202 }
203
205 {
206 ImDrawList* drawList = ImGui::GetWindowDrawList();
207 if (drawList == nullptr) { return; }
208
209 // Transform node position from canvas space to screen space
211
212 // Calculate node bounds in screen space
213 float scaledWidth = node.size.x * 0.5f * m_nodeScale * m_canvasZoom;
214 float scaledHeight = node.size.y * 0.5f * m_nodeScale * m_canvasZoom;
215
217 min.x -= scaledWidth;
218 min.y -= scaledHeight;
219
220 Vector textColor = m_style.textColor;
221 ImU32 textColorU32 = ImGui::GetColorU32(ImVec4(textColor.x, textColor.y, textColor.z, 1.0f));
222
224
225 const char* label = node.componentType.c_str();
226 drawList->AddText(textPos, textColorU32, label);
227
228 if (m_showProperties && node.properties.size() > 0)
229 {
231 size_t propCount = 0;
232 for (auto it = node.properties.begin(); it != node.properties.end() && propCount < 3; ++it, ++propCount)
233 {
234 std::string propText = " " + it->first + ": " + it->second;
235 drawList->AddText(propPos, textColorU32, propText.c_str());
236 propPos.y += 16.0f * m_nodeScale * m_canvasZoom;
237 }
238 if (node.properties.size() > 3)
239 {
241 drawList->AddText(morePos, textColorU32, " ...");
242 }
243 }
244 }
245
247 {
248 ImDrawList* drawList = ImGui::GetWindowDrawList();
249 if (drawList == nullptr) { return; }
250
251 // Transform canvas coordinates to screen coordinates
254
257
258 // Calculate bezier control point offset based on horizontal distance
259 // This creates smooth curves that scale with the connection distance
260 float dx = p2.x - p1.x;
261 float horizontalDistance = (dx > 0.0f) ? dx : -dx;
262
263 // Use a proportional offset: 40% of horizontal distance, minimum 50 pixels
264 float controlOffset = (horizontalDistance * 0.4f > 50.0f) ? horizontalDistance * 0.4f : 50.0f;
265
266 // Color depends on hover state
268 float lineWidth = 2.0f;
269
270 if (isHovered)
271 {
272 lineColor = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 0.0f, 1.0f)); // Bright yellow
273 lineWidth = 3.0f; // Thicker when hovered
274 }
275 else
276 {
277 lineColor = ImGui::GetColorU32(ImVec4(0.7f, 0.7f, 0.7f, 0.8f)); // Normal gray
278 }
279
280 // Draw bezier curve with symmetric control points
281 drawList->AddBezierCubic(
282 p1,
283 ImVec2(p1.x + controlOffset, p1.y),
284 ImVec2(p2.x - controlOffset, p2.y),
285 p2,
286 lineColor,
287 lineWidth,
288 20
289 );
290
291 // Draw connection endpoints
292 float endpointRadius = isHovered ? 5.0f : 4.0f;
293 drawList->AddCircleFilled(p1, endpointRadius, lineColor);
294 drawList->AddCircleFilled(p2, endpointRadius, lineColor);
295 }
296
298 {
299 return node.GetCurrentColor();
300 }
301
303 {
304 const std::vector<NodePort>& ports = node.GetPorts();
305 for (const auto& port : ports)
306 {
308 }
309 }
310
312 {
313 ImDrawList* drawList = ImGui::GetWindowDrawList();
314 if (drawList == nullptr) { return; }
315
316 // Calculate port position on node edge
318 float scaledWidth = node.size.x * 0.5f * m_nodeScale * m_canvasZoom;
319 float scaledHeight = node.size.y * 0.5f * m_nodeScale * m_canvasZoom;
320
321 Vector portPos = node.position;
322
323 std::vector<NodePort> inputPorts;
324 std::vector<NodePort> outputPorts;
325 for (const auto& p : node.GetPorts())
326 {
327 if (p.isOutput)
328 outputPorts.push_back(p);
329 else
330 inputPorts.push_back(p);
331 }
332
333 uint32_t portCountInType = port.isOutput ? outputPorts.size() : inputPorts.size();
335 if (port.isOutput)
336 {
337 for (size_t i = 0; i < outputPorts.size(); ++i)
338 {
339 if (outputPorts[i].portId == port.portId)
340 {
342 break;
343 }
344 }
345 }
346 else
347 {
348 for (size_t i = 0; i < inputPorts.size(); ++i)
349 {
350 if (inputPorts[i].portId == port.portId)
351 {
353 break;
354 }
355 }
356 }
357
358 // Distribute ports vertically on the edge
359 if (portCountInType > 0)
360 {
361 float spacing = (2.0f * scaledHeight) / (portCountInType + 1);
362 float yOffset = -scaledHeight + spacing * (portIndexInType + 1);
363
364 if (port.isOutput)
365 {
367 }
368 else
369 {
371 }
373 }
374
376 // FIX #4: Ensure port visual size is always visible even when zoomed out
377 // Minimum visual size is 4.0 pixels, maximum is scaled with zoom
378 float portRadius = port.radius * m_canvasZoom;
379 if (portRadius < 4.0f) { portRadius = 4.0f; } // Minimum visual size
380
381 ImU32 portColor = ImGui::GetColorU32(ImVec4(0.8f, 0.8f, 0.0f, 1.0f));
382 ImU32 portBorderColor = ImGui::GetColorU32(ImVec4(0.6f, 0.6f, 0.0f, 1.0f));
383
384 drawList->AddCircleFilled(ImVec2(screenPort.x, screenPort.y), portRadius, portColor);
385 drawList->AddCircle(ImVec2(screenPort.x, screenPort.y), portRadius, portBorderColor, 0, 1.5f);
386 }
387
389 {
390 const std::vector<NodePort>& ports = node.GetPorts();
391
392 for (const auto& port : ports)
393 {
394 // Calculate port position on node edge
396 float scaledWidth = node.size.x * 0.5f * m_nodeScale * m_canvasZoom;
397 float scaledHeight = node.size.y * 0.5f * m_nodeScale * m_canvasZoom;
398
399 std::vector<NodePort> inputPorts;
400 std::vector<NodePort> outputPorts;
401 for (const auto& p : node.GetPorts())
402 {
403 if (p.isOutput)
404 outputPorts.push_back(p);
405 else
406 inputPorts.push_back(p);
407 }
408
409 uint32_t portCountInType = port.isOutput ? outputPorts.size() : inputPorts.size();
411 if (port.isOutput)
412 {
413 for (size_t i = 0; i < outputPorts.size(); ++i)
414 {
415 if (outputPorts[i].portId == port.portId)
416 {
418 break;
419 }
420 }
421 }
422 else
423 {
424 for (size_t i = 0; i < inputPorts.size(); ++i)
425 {
426 if (inputPorts[i].portId == port.portId)
427 {
429 break;
430 }
431 }
432 }
433
434 Vector portPos = node.position;
435 if (portCountInType > 0)
436 {
437 float spacing = (2.0f * scaledHeight) / (portCountInType + 1);
438 float yOffset = -scaledHeight + spacing * (portIndexInType + 1);
439
440 if (port.isOutput)
441 {
443 }
444 else
445 {
447 }
449 }
450
451 float dx = point.x - portPos.x;
452 float dy = point.y - portPos.y;
453 float distance = sqrtf(dx * dx + dy * dy);
454
455 // FIX #4: Use larger detection radius for hitbox while visual stays small
456 // Visual port radius: 4.0 pixels (kept small via RenderPort)
457 // Detection radius: 4.0 * 3.0 = 12.0 canvas units (generous for usability)
458 // This decouples visual appearance from interaction size
459 float detectionRadius = port.radius * 3.0f;
460
462 {
463 outPortId = port.portId;
464 return true;
465 }
466 }
467
469 return false;
470 }
471
473 {
474 // This can be used to update port positions if needed
475 // Ports positions are calculated on-the-fly during rendering
476 }
477
479 const Vector& testPoint,
480 const Vector& connectionStart,
481 const Vector& connectionEnd,
482 Vector* outClosestPoint /*= nullptr*/
483 ) const
484 {
485 // Transform connection endpoints from canvas space to screen space
488
489 // Calculate bezier control points (same formula as RenderConnectionLine)
492 float dx = p2.x - p1.x;
493 float horizontalDistance = (dx > 0.0f) ? dx : -dx;
494 float controlOffset = (horizontalDistance * 0.4f > 50.0f) ? horizontalDistance * 0.4f : 50.0f;
495
496 ImVec2 cp1(p1.x + controlOffset, p1.y); // Control point 1
497 ImVec2 cp2(p2.x - controlOffset, p2.y); // Control point 2
498
499 // Sample the bezier curve at multiple points to find closest distance
500 // Using parametric cubic bezier: B(t) = (1-t)^3*P0 + 3(1-t)^2*t*P1 + 3(1-t)*t^2*P2 + t^3*P3
501 float minDistance = FLT_MAX;
503
504 const int numSamples = 32; // Sample the curve at 32 points
505 for (int i = 0; i <= numSamples; ++i)
506 {
507 float t = static_cast<float>(i) / static_cast<float>(numSamples);
508 float mt = 1.0f - t;
509
510 // Cubic bezier formula
511 float coeffA = mt * mt * mt; // (1-t)^3
512 float coeffB = 3.0f * mt * mt * t; // 3(1-t)^2*t
513 float coeffC = 3.0f * mt * t * t; // 3(1-t)*t^2
514 float coeffD = t * t * t; // t^3
515
516 float curveX = coeffA * p1.x + coeffB * cp1.x + coeffC * cp2.x + coeffD * p2.x;
517 float curveY = coeffA * p1.y + coeffB * cp1.y + coeffC * cp2.y + coeffD * p2.y;
518
519 // Calculate distance from test point to this sample point
520 float dx = testPoint.x - curveX;
521 float dy = testPoint.y - curveY;
522 float distance = sqrtf(dx * dx + dy * dy);
523
524 if (distance < minDistance)
525 {
528 }
529 }
530
531 if (outClosestPoint != nullptr)
532 {
534 }
535
536 return minDistance;
537 }
538
539} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
void SetTextColor(const Vector &color)
float GetDistanceToConnection(const Vector &testPoint, const Vector &connectionStart, const Vector &connectionEnd, Vector *outClosestPoint=nullptr) const
const ComponentNodeStyle & GetNodeStyle() const
void RenderConnectionLine(const Vector &from, const Vector &to, bool isHovered=false)
void SetNodeStyle(const ComponentNodeStyle &style)
void SetCanvasScreenPos(const ImVec2 &screenPos)
void RenderNodeLabel(const ComponentNode &node)
void RenderNodePorts(const ComponentNode &node)
void SetNormalColor(const Vector &color)
Vector CanvasToScreen(const Vector &canvasPos) const
bool GetNodeBounds(const ComponentNode &node, Vector &outMin, Vector &outMax) const
void RenderConnections(const EntityPrefabGraphDocument *document, int hoveredConnectionIndex=-1)
bool IsPointInNode(const Vector &point, const ComponentNode &node) const
void SetCanvasTransform(const Vector &offset, float zoom)
void RenderPort(const ComponentNode &node, const NodePort &port)
void SetDisabledColor(const Vector &color)
void UpdatePortPositions(ComponentNode &node) const
Vector GetNodeColor(const ComponentNode &node) const
void RenderNodes(const EntityPrefabGraphDocument *document)
void RenderNode(const ComponentNode &node)
void RenderNodeBox(const ComponentNode &node)
bool IsPointInPort(const Vector &point, const ComponentNode &node, PortId &outPortId) const
void SetSelectedColor(const Vector &color)
void SetHoverColor(const Vector &color)
float z
Definition vector.h:25
float y
Definition vector.h:25
float x
Definition vector.h:25
< Provides AssetID and INVALID_ASSET_ID
@ Vector
3-component vector (Vector from vector.h)
const PortId InvalidPortId
uint32_t PortId