Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
EntityPrefabGraphDocument.cpp
Go to the documentation of this file.
2#include "PrefabLoader.h"
3#include "../../system/system_utils.h"
4#include <fstream>
5
6namespace Olympe
7{
8 EntityPrefabGraphDocument::EntityPrefabGraphDocument() : m_canvasZoom(1.0f), m_nextNodeId(1) { }
10
12 { return CreateComponentNode(componentType, ""); }
13
14 NodeId EntityPrefabGraphDocument::CreateComponentNode(const std::string& componentType, const std::string& componentName)
15 {
16 ComponentNode node(componentType);
17 node.nodeId = m_nextNodeId++;
18 node.componentName = componentName;
19 node.InitializePorts(1, 1);
20
21 // Initialize node properties from parameter schema
23
24 m_nodes.push_back(node);
25 m_isDirty = true;
26 return node.nodeId;
27 }
28
30 {
31 for (size_t i = 0; i < m_nodes.size(); ++i)
32 { if (m_nodes[i].nodeId == nodeId) { m_nodes.erase(m_nodes.begin() + i); m_isDirty = true; break; } }
33 }
34
36 { for (size_t i = 0; i < m_nodes.size(); ++i) { if (m_nodes[i].nodeId == nodeId) { return true; } } return false; }
37
39 { for (size_t i = 0; i < m_nodes.size(); ++i) { if (m_nodes[i].nodeId == nodeId) { return &m_nodes[i]; } } return nullptr; }
40
42 { for (size_t i = 0; i < m_nodes.size(); ++i) { if (m_nodes[i].nodeId == nodeId) { return &m_nodes[i]; } } return nullptr; }
43
44 const std::vector<ComponentNode>& EntityPrefabGraphDocument::GetAllNodes() const { return m_nodes; }
45
47 {
48 for (auto it = m_selectedNodes.begin(); it != m_selectedNodes.end(); ++it)
49 { if (*it == nodeId) { return; } }
50 m_selectedNodes.push_back(nodeId);
51 ComponentNode* node = GetNode(nodeId);
52 if (node != nullptr) { node->selected = true; }
53 }
54
56 {
57 for (auto it = m_selectedNodes.begin(); it != m_selectedNodes.end(); ++it)
58 { if (*it == nodeId) { m_selectedNodes.erase(it); break; } }
59 ComponentNode* node = GetNode(nodeId);
60 if (node != nullptr) { node->selected = false; }
61 }
62
64 {
65 for (size_t i = 0; i < m_selectedNodes.size(); ++i)
66 { ComponentNode* node = GetNode(m_selectedNodes[i]); if (node != nullptr) { node->selected = false; } }
67 m_selectedNodes.clear();
68 }
69
72
73 const std::vector<NodeId>& EntityPrefabGraphDocument::GetSelectedNodes() const { return m_selectedNodes; }
74
76
78 {
79 if (gridWidth < 1) gridWidth = 3;
80 if (spacing < 50.0f) spacing = 200.0f;
81
82 for (size_t i = 0; i < m_nodes.size(); ++i)
83 {
84 int row = i / gridWidth;
85 int col = i % gridWidth;
86 m_nodes[i].position.x = col * spacing;
87 m_nodes[i].position.y = row * spacing;
88 }
89 }
91
93 {
94 // Validate the connection first
96 {
97 return false;
98 }
99 m_connections.push_back(std::make_pair(sourceId, targetId));
100 m_isDirty = true;
101 return true;
102 }
103
105 {
106 for (auto it = m_connections.begin(); it != m_connections.end(); ++it)
107 { if (it->first == sourceId && it->second == targetId) { m_connections.erase(it); m_isDirty = true; return true; } }
108 return false;
109 }
110
111 const std::vector<std::pair<NodeId, NodeId>>& EntityPrefabGraphDocument::GetConnections() const { return m_connections; }
112
114 {
115 // Prevent self-connections
116 if (sourceId == targetId)
117 {
118 return false;
119 }
120
121 // Check if both nodes exist
122 if (!HasNode(sourceId) || !HasNode(targetId))
123 {
124 return false;
125 }
126
127 // Prevent duplicate connections
129 {
130 return false;
131 }
132
133 return true;
134 }
135
137 {
138 for (const auto& connection : m_connections)
139 {
140 if (connection.first == sourceId && connection.second == targetId)
141 {
142 return true;
143 }
144 }
145 return false;
146 }
147
148 json EntityPrefabGraphDocument::ToJson() const { return json::object(); }
150
151 bool EntityPrefabGraphDocument::LoadFromFile(const std::string& filePath)
152 {
153 try
154 {
155 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] Starting load from: " << filePath << "\n";
156
157 // Load JSON from file
158 json data = PrefabLoader::LoadJsonFromFile(filePath);
159 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] JSON loaded successfully\n";
160
161 // Verify structure
162 if (!data.contains("data"))
163 {
164 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] ERROR: Missing 'data' section\n";
165 return false;
166 }
167
168 // Clear existing data
169 Clear();
170
171 // Load parameter schemas for component properties
172 LoadParameterSchemas("Gamedata\\EntityPrefab\\ComponentsParameters.json");
173 if (m_parameterSchemas.empty())
174 {
175 LoadParameterSchemas("Gamedata/EntityPrefab/ComponentsParameters.json");
176 }
177
178 int nodesLoaded = 0;
179 int connectionsLoaded = 0;
180
181 // Parse nodes from JSON
182 if (data["data"].contains("nodes") && data["data"]["nodes"].is_array())
183 {
184 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] Parsing nodes...\n";
185
186 for (const auto& nodeJson : data["data"]["nodes"])
187 {
188 try
189 {
190 // Extract node data
191 std::string componentType = nodeJson.value("componentType", "");
192 std::string componentName = nodeJson.value("componentName", "");
193
194 if (componentType.empty())
195 {
196 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] WARNING: Node missing componentType\n";
197 continue;
198 }
199
200 // Create node
201 NodeId id = CreateComponentNode(componentType, componentName);
203
204 if (node)
205 {
206 // Set position
207 if (nodeJson.contains("position") && nodeJson["position"].is_object())
208 {
209 float x = nodeJson["position"].value("x", 0.0f);
210 float y = nodeJson["position"].value("y", 0.0f);
211 float z = nodeJson["position"].value("z", 0.0f);
212 node->position = Vector(x, y, z);
213 }
214
215 // Set size
216 if (nodeJson.contains("size") && nodeJson["size"].is_object())
217 {
218 float x = nodeJson["size"].value("x", 150.0f);
219 float y = nodeJson["size"].value("y", 80.0f);
220 float z = nodeJson["size"].value("z", 0.0f);
221 node->size = Vector(x, y, z);
222 }
223
224 // Set enabled flag
225 node->enabled = nodeJson.value("enabled", true);
226
227 // Set selected flag
228 node->selected = nodeJson.value("selected", false);
229
230 // Set properties
231 if (nodeJson.contains("properties") && nodeJson["properties"].is_object())
232 {
233 for (auto it = nodeJson["properties"].begin(); it != nodeJson["properties"].end(); ++it)
234 {
235 std::string key = it.key();
236 const json& value = it.value();
237
238 if (value.is_string())
239 {
240 node->properties[key] = value.get<std::string>();
241 }
242 else if (value.is_number())
243 {
244 node->properties[key] = std::to_string(value.get<double>());
245 }
246 else if (value.is_boolean())
247 {
248 node->properties[key] = value.get<bool>() ? "true" : "false";
249 }
250 }
251 }
252
253 // Fill missing properties with schema defaults
254 // This handles cases where JSON properties are empty but schema provides defaults
255 auto schemaIt = m_parameterSchemas.find(componentType);
256 if (schemaIt != m_parameterSchemas.end())
257 {
258 for (const auto& paramPair : schemaIt->second)
259 {
260 // Only add if not already loaded from JSON
261 if (node->properties.find(paramPair.first) == node->properties.end())
262 {
263 node->properties[paramPair.first] = paramPair.second;
264 }
265 }
266 }
267
268 nodesLoaded++;
269 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] Loaded node: " << componentName
270 << " (type=" << componentType << ", id=" << id << ")\n";
271 }
272 }
273 catch (const std::exception& e)
274 {
275 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] ERROR parsing node: " << e.what() << "\n";
276 }
277 }
278 }
279 else
280 {
281 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] WARNING: 'nodes' array not found or invalid\n";
282 }
283
284 // Parse connections from JSON
285 if (data["data"].contains("connections") && data["data"]["connections"].is_array())
286 {
287 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] Parsing connections...\n";
288
289 for (const auto& connJson : data["data"]["connections"])
290 {
291 try
292 {
293 NodeId sourceId = connJson.value("sourceNodeId", InvalidNodeId);
294 NodeId targetId = connJson.value("targetNodeId", InvalidNodeId);
295
297 {
299 {
301 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] Loaded connection: "
302 << sourceId << " -> " << targetId << "\n";
303 }
304 }
305 }
306 catch (const std::exception& e)
307 {
308 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] ERROR parsing connection: " << e.what() << "\n";
309 }
310 }
311 }
312 else
313 {
314 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] WARNING: 'connections' array not found or invalid\n";
315 }
316
317 // Restore canvas state
318 if (data["data"].contains("canvasState") && data["data"]["canvasState"].is_object())
319 {
320 try
321 {
322 float zoom = data["data"]["canvasState"].value("zoom", 1.0f);
323 float offsetX = data["data"]["canvasState"].value("offsetX", 0.0f);
324 float offsetY = data["data"]["canvasState"].value("offsetY", 0.0f);
325
326 SetCanvasZoom(zoom);
327 SetCanvasOffset(Vector(offsetX, offsetY, 0.0f));
328
329 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] Canvas state restored: zoom="
330 << zoom << ", offset=(" << offsetX << ", " << offsetY << ")\n";
331 }
332 catch (const std::exception& e)
333 {
334 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] ERROR restoring canvas state: " << e.what() << "\n";
335 }
336 }
337
338 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] SUCCESS: Loaded " << nodesLoaded
339 << " nodes and " << connectionsLoaded << " connections\n";
340
341 // Fallback : si AIBehavior_data existe mais sans behaviorTreePath, lire depuis la racine
342 if (data.contains("data") && data["data"].contains("behaviorTreeRef"))
343 {
344 std::string btRef = data["data"]["behaviorTreeRef"].get<std::string>();
345 for (auto& node : m_nodes)
346 {
347 if (node.componentType == "AIBehavior_data")
348 {
349 if (node.properties.find("behaviorTreePath") == node.properties.end()
350 || node.properties["behaviorTreePath"].empty())
351 {
352 node.properties["behaviorTreePath"] = btRef;
353 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] Fallback: Set behaviorTreePath from root behaviorTreeRef\n";
354 }
355 break;
356 }
357 }
358 }
359
360 // Auto-layout nodes if they're all at Y=0 (not properly positioned)
361 bool needsLayout = true;
362 for (size_t i = 0; i < m_nodes.size(); ++i)
363 {
364 if (m_nodes[i].position.y != 0.0f)
365 {
366 needsLayout = false;
367 break;
368 }
369 }
370
371 if (needsLayout && m_nodes.size() > 1)
372 {
373 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] Auto-arranging nodes in grid layout\n";
374 ArrangeNodesInGrid(3, 200.0f); // 3 columns, 200 pixel spacing
375 }
376
377 m_isDirty = false;
378 return true;
379 }
380 catch (const std::exception& e)
381 {
382 SYSTEM_LOG << "[EntityPrefabGraphDocument::LoadFromFile] EXCEPTION: " << e.what() << "\n";
383 return false;
384 }
385 }
386
387 bool EntityPrefabGraphDocument::SaveToFile(const std::string& filePath)
388 {
389 try
390 {
391 SYSTEM_LOG << "[EntityPrefabGraphDocument::SaveToFile] Starting save to: " << filePath << "\n";
392
393 // Create JSON structure matching the load format
394 json data = json::object();
395 data["blueprintType"] = "EntityPrefab";
396 data["schemaVersion"] = 4;
397
398 json dataObj = json::object();
399
400 // Serialize nodes
401 json nodesArray = json::array();
402 for (size_t i = 0; i < m_nodes.size(); ++i)
403 {
404 const ComponentNode& node = m_nodes[i];
405 json nodeJson = json::object();
406
407 nodeJson["nodeId"] = (int)node.nodeId;
408 nodeJson["componentType"] = node.componentType;
409 nodeJson["componentName"] = node.componentName;
410 nodeJson["enabled"] = node.enabled;
411 nodeJson["selected"] = node.selected;
412
413 // Serialize position
414 json posJson = json::object();
415 posJson["x"] = node.position.x;
416 posJson["y"] = node.position.y;
417 posJson["z"] = node.position.z;
418 nodeJson["position"] = posJson;
419
420 // Serialize size
421 json sizeJson = json::object();
422 sizeJson["x"] = node.size.x;
423 sizeJson["y"] = node.size.y;
424 sizeJson["z"] = node.size.z;
425 nodeJson["size"] = sizeJson;
426
427 // Serialize properties
428 json propsJson = json::object();
429 for (auto it = node.properties.begin(); it != node.properties.end(); ++it)
430 {
431 propsJson[it->first] = it->second;
432 }
433 nodeJson["properties"] = propsJson;
434
435 nodesArray.push_back(nodeJson);
436 }
437 dataObj["nodes"] = nodesArray;
438
439 // Serialize connections
440 json connectionsArray = json::array();
441 for (size_t i = 0; i < m_connections.size(); ++i)
442 {
443 json connJson = json::object();
444 connJson["sourceNodeId"] = (int)m_connections[i].first;
445 connJson["targetNodeId"] = (int)m_connections[i].second;
446 connectionsArray.push_back(connJson);
447 }
448 dataObj["connections"] = connectionsArray;
449
450 // Serialize canvas state
451 json canvasStateJson = json::object();
453 canvasStateJson["offsetX"] = m_canvasOffset.x;
454 canvasStateJson["offsetY"] = m_canvasOffset.y;
455 dataObj["canvasState"] = canvasStateJson;
456
457 // Expose behaviorTreePath au niveau racine pour faciliter la lecture runtime
458 for (const auto& node : m_nodes)
459 {
460 if (node.componentType == "AIBehavior_data")
461 {
462 auto it = node.properties.find("behaviorTreePath");
463 if (it != node.properties.end() && !it->second.empty())
464 dataObj["behaviorTreeRef"] = it->second;
465 break;
466 }
467 }
468
469 data["data"] = dataObj;
470
471 // Save JSON to file
472 PrefabLoader::SaveJsonToFile(filePath, data);
473
474 SYSTEM_LOG << "[EntityPrefabGraphDocument::SaveToFile] SUCCESS: Saved " << m_nodes.size()
475 << " nodes and " << m_connections.size() << " connections\n";
476
477 m_isDirty = false;
478 return true;
479 }
480 catch (const std::exception& e)
481 {
482 SYSTEM_LOG << "[EntityPrefabGraphDocument::SaveToFile] EXCEPTION: " << e.what() << "\n";
483 return false;
484 }
485 }
486
487 void EntityPrefabGraphDocument::SetDocumentName(const std::string& name) { m_documentName = name; }
489
492
495
497 size_t EntityPrefabGraphDocument::GetNodeCount() const { return m_nodes.size(); }
498
501
503 {
504 using nlohmann::json;
505
506 SYSTEM_LOG << "[EntityPrefabGraphDocument] Loading parameter schemas from: " << schemasFilePath << "\n";
507
508 try
509 {
510 std::ifstream file(schemasFilePath);
511 if (!file.is_open())
512 {
513 SYSTEM_LOG << "[EntityPrefabGraphDocument] WARNING: Could not open schemas file\n";
514 return;
515 }
516
518 file >> jsonData;
519 file.close();
520
521 if (!jsonData.contains("schemas") || !jsonData["schemas"].is_array())
522 {
523 SYSTEM_LOG << "[EntityPrefabGraphDocument] WARNING: Schemas file missing 'schemas' array\n";
524 return;
525 }
526
527 const json& schemasArray = jsonData["schemas"];
528 for (const auto& schemaJson : schemasArray)
529 {
530 if (!schemaJson.contains("componentType"))
531 continue;
532
533 std::string componentType = schemaJson["componentType"].get<std::string>();
534 std::map<std::string, std::string> params;
535
536 if (schemaJson.contains("parameters") && schemaJson["parameters"].is_array())
537 {
538 for (const auto& paramJson : schemaJson["parameters"])
539 {
540 if (!paramJson.contains("name") || !paramJson.contains("defaultValue"))
541 continue;
542
543 std::string paramName = paramJson["name"].get<std::string>();
544
545 // Convert default value to string
546 std::string defaultValue;
547 if (paramJson["defaultValue"].is_string())
548 {
549 defaultValue = paramJson["defaultValue"].get<std::string>();
550 }
551 else if (paramJson["defaultValue"].is_boolean())
552 {
553 defaultValue = paramJson["defaultValue"].get<bool>() ? "true" : "false";
554 }
555 else if (paramJson["defaultValue"].is_number_integer())
556 {
557 defaultValue = std::to_string(paramJson["defaultValue"].get<int>());
558 }
559 else if (paramJson["defaultValue"].is_number_float())
560 {
561 defaultValue = std::to_string(paramJson["defaultValue"].get<float>());
562 }
563 else if (paramJson["defaultValue"].is_array())
564 {
565 // For arrays, store as JSON string
566 defaultValue = paramJson["defaultValue"].dump();
567 }
568
569 params[paramName] = defaultValue;
570 }
571 }
572
573 m_parameterSchemas[componentType] = params;
574 SYSTEM_LOG << "[EntityPrefabGraphDocument] Loaded " << params.size() << " parameters for " << componentType << "\n";
575 }
576
577 SYSTEM_LOG << "[EntityPrefabGraphDocument] Successfully loaded " << m_parameterSchemas.size() << " component schemas\n";
578 }
579 catch (const std::exception& e)
580 {
581 SYSTEM_LOG << "[EntityPrefabGraphDocument] ERROR loading schemas: " << e.what() << "\n";
582 }
583 }
584
586 {
587 // Look up component schema and populate node properties with defaults
588 auto it = m_parameterSchemas.find(node.componentType);
589 if (it != m_parameterSchemas.end())
590 {
591 for (const auto& paramPair : it->second)
592 {
593 node.SetProperty(paramPair.first, paramPair.second);
594 }
595 }
596 }
597
598 std::vector<LayoutNode> EntityPrefabGraphDocument::CalculateLayout() { return std::vector<LayoutNode>(); }
599
600} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
const std::vector< NodeId > & GetSelectedNodes() const
void LoadParameterSchemas(const std::string &schemasFilePath)
std::vector< std::pair< NodeId, NodeId > > m_connections
bool SaveToFile(const std::string &filePath)
bool ValidateConnection(NodeId sourceId, NodeId targetId) const
std::map< std::string, std::map< std::string, std::string > > m_parameterSchemas
bool HasConnection(NodeId sourceId, NodeId targetId) const
void SetDocumentName(const std::string &name)
bool LoadFromFile(const std::string &filePath)
bool ConnectNodes(NodeId sourceId, NodeId targetId)
NodeId CreateComponentNode(const std::string &componentType)
const std::vector< ComponentNode > & GetAllNodes() const
bool DisconnectNodes(NodeId sourceId, NodeId targetId)
static EntityPrefabGraphDocument FromJson(const json &data)
const std::vector< std::pair< NodeId, NodeId > > & GetConnections() const
void ArrangeNodesInGrid(int gridWidth=4, float spacing=200.0f)
static void SaveJsonToFile(const std::string &filePath, const json &data)
static json LoadJsonFromFile(const std::string &filePath)
float y
Definition vector.h:25
float x
Definition vector.h:25
< Provides AssetID and INVALID_ASSET_ID
nlohmann::json json
@ Vector
3-component vector (Vector from vector.h)
uint32_t NodeId
const NodeId InvalidNodeId
nlohmann::json json
#define SYSTEM_LOG