Cogs.Core
EntityInspector.cpp
1#include "EntityInspector.h"
2
3#include "Context.h"
4#include "Engine.h"
5#include "EntityStore.h"
6#include "Types.h"
7
8#include "Editor/IEditor.h"
9
10#include "InspectorGuiHelper.h"
11#include "Inspectors.h"
12
13#include "Components/Core/PropertiesComponent.h"
14#include "Components/Core/SceneComponent.h"
15
16#include "Resources/Resources.h"
17#include "Resources/TextureManager.h"
18#include "Renderer/Renderer.h"
19#include "Renderer/RenderTexture.h"
20#include "Resources/MaterialInstance.h"
21#include "Resources/Model.h"
22#include "Resources/Mesh.h"
23
24#include "Foundation/ComponentModel/Entity.h"
25#include "Foundation/Reflection/TypeDatabase.h"
26
27// Add a button to the Entity Inspector that allows you to export the entity tree as XML.
28// (Handy for debugging.)
29#define ENABLE_XML_EXPORT
30#if defined(ENABLE_XML_EXPORT)
31 #include "Foundation/Platform/IO.h"
32#endif
33
34#include "imgui.h"
35
36#include <glm/vec2.hpp>
37#include <glm/vec3.hpp>
38#include <glm/vec4.hpp>
39#include <glm/mat4x4.hpp>
40#include <glm/ext/quaternion_float.hpp>
41
42#include <algorithm>
43#include <sstream>
44#include <iomanip>
45#include <array>
46#include <bit>
47#include <cstdio>
48
49using namespace Cogs::Reflection;
50using namespace Cogs::ComponentModel;
51
52#if defined(ENABLE_XML_EXPORT)
53namespace Cogs::Core
54{
55 void exportEntity(Context* context, Entity* entity, std::string& dest) {
56 char buffer[1024];
57
58 sprintf(buffer, "<entity name='%s' id='%zu'>", entity->getName().c_str(), entity->getId());
59 dest += buffer;
60
61 for (const ComponentHandle& componentHandle : entity->getComponents()) {
62 const Component* component = componentHandle.resolve();
63 const Reflection::Type* type = &TypeDatabase::getType(componentHandle.typeId);
64 std::string children;
65
66 dest += "<component name='";
67 dest += type->getName().getName();
68 dest += "'";
69
70 if (type == &TypeDatabase::getType<PropertiesComponent>()) {
71 const PropertiesComponent* propertiesComp = static_cast<const PropertiesComponent*>(component);
72
73 for (const PropertyInfo& header : propertiesComp->properties.getHeaders()) {
74 StringView name = propertiesComp->properties.getKey(header);
75
76 if (!name.empty()) {
77 dest += " ";
78 dest += name.to_string();
79 dest += "='";
80
81 switch (header.type) {
82 case PropertyType::Bool:
83 dest += header.boolValue ? "true" : "false";
84 break;
85 case PropertyType::Integer:
86 sprintf(buffer, "%d", header.intValue);
87 dest += buffer;
88 break;
89 case PropertyType::UnsignedInteger:
90 sprintf(buffer, "%u", header.uintValue);
91 dest += buffer;
92 break;
93 case PropertyType::Float:
94 sprintf(buffer, "%f", header.floatValue);
95 dest += buffer;
96 break;
97 case PropertyType::Float2:
98 sprintf(buffer, "%f,%f", header.float2Value[0], header.float2Value[1]);
99 dest += buffer;
100 break;
101 case PropertyType::StringRef:
102 case PropertyType::String:
103 dest += propertiesComp->properties.getString(header).to_string();
104 break;
105 case PropertyType::FloatArray:
106 for (float value : propertiesComp->properties.getFloatArray(header.index)) {
107 sprintf(buffer, "%f,", value);
108 dest += buffer;
109 }
110 dest.resize(dest.size() - 1);
111 break;
112 }
113 dest += "'";
114 }
115 }
116 }
117
118
119
120 for (; type; type = type->getBase()) {
121 for (size_t fieldIdx = 0, fieldCount = type->getNumFields(); fieldIdx < fieldCount; ++fieldIdx) {
122 const Reflection::Field* field = type->getField(static_cast<FieldId>(fieldIdx));
123 const Reflection::Type& fieldType = TypeDatabase::getType(field->getTypeId());
124 const std::string& name = field->getName().getName();
125
126 if (!name.empty()) {
127 dest += " ";
128 dest += name;
129 dest += "='";
130
131 if (fieldType == TypeDatabase::getType<bool>()) {
132 dest += field->getPtr<bool>(component) ? "true" : "false";
133 }
134 else if (fieldType == TypeDatabase::getType<int32_t>()) {
135 sprintf(buffer, "%d", *field->getPtr<int32_t>(component));
136 dest += buffer;
137 }
138 else if (fieldType == TypeDatabase::getType<uint32_t>()) {
139 sprintf(buffer, "%u", *field->getPtr<uint32_t>(component));
140 dest += buffer;
141 }
142 else if (fieldType == TypeDatabase::getType<float>()) {
143 sprintf(buffer, "%f", *field->getPtr<float>(component));
144 dest += buffer;
145 }
146 else if (fieldType == TypeDatabase::getType<glm::vec2>()) {
147 const glm::vec2* value = field->getPtr<glm::vec2>(component);
148 sprintf(buffer, "%f,%f", value->x, value->y);
149 dest += buffer;
150 }
151 else if (fieldType == TypeDatabase::getType<glm::vec3>()) {
152 const glm::vec3* value = field->getPtr<glm::vec3>(component);
153 sprintf(buffer, "%f,%f,%f", value->x, value->y, value->z);
154 dest += buffer;
155 }
156 else if (fieldType == TypeDatabase::getType<glm::vec4>()) {
157 const glm::vec4* value = field->getPtr<glm::vec4>(component);
158 sprintf(buffer, "%f,%f,%f,%f", value->x, value->y, value->z, value->w);
159 dest += buffer;
160 }
161 else if (fieldType == TypeDatabase::getType<glm::dvec2>()) {
162 const glm::dvec2* value = field->getPtr<glm::dvec2>(component);
163 sprintf(buffer, "%f,%f", value->x, value->y);
164 dest += buffer;
165 }
166 else if (fieldType == TypeDatabase::getType<glm::dvec3>()) {
167 const glm::dvec3* value = field->getPtr<glm::dvec3>(component);
168 sprintf(buffer, "%f,%f,%f", value->x, value->y, value->z);
169 dest += buffer;
170 }
171 else if (fieldType == TypeDatabase::getType<glm::dvec4>()) {
172 const glm::dvec4* value = field->getPtr<glm::dvec4>(component);
173 sprintf(buffer, "%f,%f,%f,%f", value->x, value->y, value->z, value->w);
174 dest += buffer;
175 }
176 else if (fieldType == TypeDatabase::getType<glm::ivec2>()) {
177 const glm::ivec2* value = field->getPtr<glm::ivec2>(component);
178 sprintf(buffer, "%d,%d", value->x, value->y);
179 dest += buffer;
180 }
181 else if (fieldType == TypeDatabase::getType<glm::ivec3>()) {
182 const glm::ivec3* value = field->getPtr<glm::ivec3>(component);
183 sprintf(buffer, "%d,%d,%d", value->x, value->y, value->z);
184 dest += buffer;
185 }
186 else if (fieldType == TypeDatabase::getType<glm::ivec4>()) {
187 const glm::ivec4* value = field->getPtr<glm::ivec4>(component);
188 sprintf(buffer, "%d,%d,%d,%d", value->x, value->y, value->z, value->w);
189 dest += buffer;
190 }
191 else if (fieldType == TypeDatabase::getType<glm::uvec2>()) {
192 const glm::uvec2* value = field->getPtr<glm::uvec2>(component);
193 sprintf(buffer, "%u,%u", value->x, value->y);
194 dest += buffer;
195 }
196 else if (fieldType == TypeDatabase::getType<glm::uvec3>()) {
197 const glm::uvec3* value = field->getPtr<glm::uvec3>(component);
198 sprintf(buffer, "%u,%u,%u", value->x, value->y, value->z);
199 dest += buffer;
200 }
201 else if (fieldType == TypeDatabase::getType<glm::uvec4>()) {
202 const glm::uvec4* value = field->getPtr<glm::uvec4>(component);
203 sprintf(buffer, "%d,%d,%d,%d", value->x, value->y, value->z, value->w);
204 dest += buffer;
205 }
206 else if (fieldType == TypeDatabase::getType<glm::mat4>()) {
207 const float* values = glm::value_ptr(*field->getPtr<glm::mat4>(component));
208 sprintf(buffer, "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f", values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], values[8], values[9], values[10], values[11], values[12], values[13], values[14], values[15]);
209 dest += buffer;
210 }
211 else if (fieldType == TypeDatabase::getType<glm::quat>()) {
212 const glm::quat* value = field->getPtr<glm::quat>(component);
213 sprintf(buffer, "%f,%f,%f,%f", value->x, value->y, value->z, value->w);
214 dest += buffer;
215 }
216 else if (fieldType.isEnumFlags()) {
217 sprintf(buffer, "0x%08X", *field->getPtr<uint32_t>(component));
218 dest += buffer;
219 }
220 else if (fieldType.isEnum()) {
221 uint32_t value = *field->getPtr<uint32_t>(component);
222
223 for (size_t idx = 0, enumCount = fieldType.getNumEnumerators(); idx < enumCount; ++idx) {
224 if (value == static_cast<uint32_t>(fieldType.getEnumerator(idx)->getValue())) {
225 dest += fieldType.getEnumerator(idx)->getName().getName();
226 break;
227 }
228 }
229 }
230 else if (fieldType == TypeDatabase::getType<std::vector<EntityPtr>>()) {
231 dest.resize(dest.size() - name.size() - 3);
232
233 const std::vector<EntityPtr>* childEntities = field->getPtr<std::vector<EntityPtr>>(component);
234
235 if (!childEntities->empty()) {
236 children.reserve(1048576);
237 children += "<";
238 children += name;
239 children += ">";
240 for (const EntityPtr& child : *childEntities) {
241 exportEntity(context, child.get(), children);
242 }
243 children += "</";
244 children += name;
245 children += ">";
246 }
247 continue;
248 }
249 else if (fieldType == TypeDatabase::getType<std::string>()) {
250 dest += *field->getPtr<std::string>(component);
251 }
252 else if (fieldType == TypeDatabase::getType<std::vector<std::string>>()) {
253 const std::vector<std::string>* values = field->getPtr<std::vector<std::string>>(component);
254
255 if (!values->empty()) {
256 for (const std::string& str : *values) {
257 dest += str;
258 dest += ",";
259 }
260 dest.resize(dest.size() - 1);
261 }
262 }
263 else {
264 dest += "UNSUPPORTED";
265 }
266 dest += "'";
267 }
268 }
269 }
270 if (children.empty()) {
271 dest += "/>";
272 }
273 else {
274 dest += ">";
275 dest += children;
276 dest += "</component>";
277 }
278 }
279 dest += "</entity>";
280 }
281}
282#endif //ENABLE_XML_EXPORT
283
284void Cogs::Core::showCollapsableEntityInspector(Cogs::Core::Context* context, const ComponentModel::Entity* entity, std::string_view entityNamePattern, std::string_view componentNamePattern, bool showBorder)
285{
286 if (Cogs::Core::findHierarchyWithMatch(*entity, entityNamePattern, componentNamePattern)) {
287 std::array<char, 128> header = {};
288 std::snprintf(header.data(), header.size(), "%s Id: %zu", entity->getName().c_str(), entity->getId());
289
290 ImGui::PushID(entity);
291
292 if (ImGui::TreeNodeEx(header.data(), ImGuiTreeNodeFlags_NoAutoOpenOnLog | (showBorder ? ImGuiTreeNodeFlags_Framed : 0))) {
293 ScopedIndent si;
294 showEntityInspector(context, entity, entityNamePattern, componentNamePattern, showBorder);
295
296 ImGui::TreePop();
297 }
298
299 ImGui::PopID();
300 }
301}
302
303void Cogs::Core::showEntities(Context * context, std::span<const EntityPtr> entities, std::string_view entityNamePattern, std::string_view componentNamePattern)
304{
305 for (const EntityPtr & e : entities) {
306 showCollapsableEntityInspector(context, e.get(), entityNamePattern, componentNamePattern);
307 }
308}
309
310void Cogs::Core::showEntityInspector(Context* context, const ComponentModel::Entity* entity, std::string_view entityNamePattern, std::string_view componentNamePattern, bool showBorder)
311{
312 ImGui::Text("Id: %zd", entity->getId());
313 ImGui::SameLine();
314 const float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
315 static std::array<char, 65> buffer;
316 ImGui::SameLine(0.0f, spacing);
317 constexpr int widthInputText = 120;
318 ImGui::PushItemWidth(widthInputText);
319 ImGui::Text("Filter: ");
320 ImGui::SameLine(0.0f, spacing);
321 if (ImGui::InputText(" ", buffer.data(), buffer.size(), ImGuiInputTextFlags_None)) {
322 ImguiRenderer* guiRenderer = context->renderer->getGuiRenderer();
323 if (guiRenderer) {
324 guiRenderer->addTextToGlyphBuilder(buffer.data());
325 }
326 }
327 ImGui::PopItemWidth();
328
329 std::string_view findPattern = buffer.data();
330
331 for (const Cogs::ComponentModel::ComponentHandle& c : entity->getComponents()) {
332 Cogs::ComponentModel::Component* component = c.resolve();
333
334 bool showComponent = (bool)component;
335 if(showComponent && !findPattern.empty()) {
336 showComponent = containsInvariantCase(component->getType().getName().c_str(), findPattern);
337 }
338
339 if(showComponent) {
340 showComponentInspector(context, component, entityNamePattern, componentNamePattern, showBorder);
341 }
342 }
343}
344
345void Cogs::Core::showComponentInspector(Context* context, ComponentModel::Component* component, std::string_view entityNamePattern, std::string_view componentNamePattern, bool showBorder)
346{
347 const Cogs::Reflection::Type* componentType = &component->getType();
348
349 if (ImGui::TreeNodeEx(componentType->getName().getName().c_str(), ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog | (showBorder ? ImGuiTreeNodeFlags_Framed : 0))) {
350 ImGui::PushID(component);
351 ImGui::Text(" ");
352 const float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
353 static std::array<char, 65> buffer;
354 ImGui::SameLine(0.0f, spacing);
355 constexpr int widthInputText = 120;
356 ImGui::PushItemWidth(widthInputText);
357 ImGui::Text("Filter: ");
358 ImGui::SameLine(0.0f, spacing);
359 if (ImGui::InputText(" ", buffer.data(), buffer.size(), ImGuiInputTextFlags_None)) {
360 ImguiRenderer* guiRenderer = context->renderer->getGuiRenderer();
361 if (guiRenderer) {
362 guiRenderer->addTextToGlyphBuilder(buffer.data());
363 }
364 }
365 ImGui::PopItemWidth();
366 auto updateGui = componentType->getMethod("updateGui");
367 if (updateGui) {
368 updateGui->call(component);
369 ImGui::PopID();
370 return;
371 }
372
373 std::string_view findPattern = buffer.data();
374
375 // Show fields
376 ScopedIndent si_type;
377 size_t numFields = componentType->getNumHierarchyFields();
378
379 for (FieldId fieldId = 0; fieldId < numFields; ++fieldId) {
380 bool showField = true;
381 if(!findPattern.empty()) {
382 showField = containsInvariantCase(componentType->getField(fieldId)->getName().c_str(), findPattern);
383 }
384
385 if(showField) {
386 showFieldInspector(context, component, fieldId, entityNamePattern, componentNamePattern, showBorder);
387 }
388 }
389
390 ImGui::PopID();
391 }
392}
393
394void Cogs::Core::showFieldInspector(Context* context, ComponentModel::Component* component, FieldId fieldId, std::string_view entityNamePattern, std::string_view componentNamePattern, bool showBorder)
395{
396 const Cogs::Reflection::Type* componentType = &component->getType();
397
398 const Cogs::Reflection::Field* field = componentType->getField(fieldId);
399 const Cogs::Reflection::Name& fieldName = field->getName();
400
401 const std::string & fieldHeader = fieldName.getName();
402
403 const Cogs::Reflection::Type & fieldType = TypeDatabase::getType(field->getTypeId());
404
405 if (fieldType == TypeDatabase::getType<int32_t>()) {
406 auto fieldValue = field->getPtr<int32_t>(component);
407 const auto* range = field->get<ComponentModel::RangeAttribute<int>>();
408 if (range) {
409 const int32_t min = std::min(range->getMin(), *fieldValue);
410 const int32_t max = std::max(range->getMax(), *fieldValue);
411 if (ImGui::SliderInt(fieldHeader.c_str(), fieldValue, min, max)) {
412 component->setFieldChanged(fieldId);
413 }
414 }
415 else {
416 if (ImGui::DragInt(fieldHeader.c_str(), fieldValue)) {
417 component->setFieldChanged(fieldId);
418 }
419 }
420 }
421 else if (fieldType == TypeDatabase::getType<float>()) {
422 auto fieldValue = field->getPtr<float>(component);
423
424 const auto* range = field->get<ComponentModel::RangeAttribute<float>>();
425 if (range) {
426 const float min = std::min(range ? range->getMin() : 0.0f, *fieldValue);
427 const float max = std::max(range ? range->getMax() : 100.0f, *fieldValue);
428 const float stepValue = (max - min) / 100.f;
429
430 if (ImGui::DragFloat(fieldHeader.c_str(), fieldValue, stepValue, min, max)) {
431 component->setFieldChanged(fieldId);
432 }
433 }
434 else {
435 if (ImGui::DragFloat(fieldHeader.c_str(), fieldValue)) {
436 component->setFieldChanged(fieldId);
437 }
438 }
439 }
440 else if (fieldType == TypeDatabase::getType<glm::vec2>()) {
441 auto fieldValue = field->getPtr<glm::vec2>(component);
442
443 if (ImGui::DragFloat2(fieldHeader.c_str(), glm::value_ptr(*fieldValue))) {
444 component->setFieldChanged(fieldId);
445 }
446 }
447 else if (fieldType == TypeDatabase::getType<glm::vec3>()) {
448 auto fieldValue = field->getPtr<glm::vec3>(component);
449
450 if (ImGui::DragFloat3(fieldHeader.c_str(), glm::value_ptr(*fieldValue))) {
451 component->setFieldChanged(fieldId);
452 }
453 }
454 else if (fieldType == TypeDatabase::getType<glm::vec4>()) {
455 auto fieldValue = field->getPtr<glm::vec4>(component);
456
457 auto ca = field->get<ComponentModel::ColorAttribute>();
458
459 if (ca || (fieldName.getName().find("olor") != std::string::npos)) {
460 if (ImGui::ColorEdit4(fieldHeader.c_str(), glm::value_ptr(*fieldValue))) {
461 component->setFieldChanged(fieldId);
462 }
463 } else {
464 if (ImGui::DragFloat4(fieldHeader.c_str(), glm::value_ptr(*fieldValue))) {
465 component->setFieldChanged(fieldId);
466 }
467 }
468 }
469 else if (fieldType == TypeDatabase::getType<glm::ivec2>()) {
470 auto fieldValue = field->getPtr<glm::ivec2>(component);
471 if (ImGui::DragInt2(fieldHeader.c_str(), glm::value_ptr(*fieldValue))) {
472 component->setFieldChanged(fieldId);
473 }
474 }
475 else if (fieldType == TypeDatabase::getType<glm::ivec3>()) {
476 auto fieldValue = field->getPtr<glm::ivec3>(component);
477 if (ImGui::DragInt3(fieldHeader.c_str(), glm::value_ptr(*fieldValue))) {
478 component->setFieldChanged(fieldId);
479 }
480 }
481 else if (fieldType == TypeDatabase::getType<glm::ivec4>()) {
482 auto fieldValue = field->getPtr<glm::ivec4>(component);
483 if (ImGui::DragInt4(fieldHeader.c_str(), glm::value_ptr(*fieldValue))) {
484 component->setFieldChanged(fieldId);
485 }
486 }
487 else if (fieldType == TypeDatabase::getType<glm::uvec2>()) {
488 auto fieldValue = field->getPtr<glm::uvec2>(component);
489 glm::ivec2 tmp = *fieldValue;
490 if (ImGui::DragInt2(fieldHeader.c_str(), glm::value_ptr(tmp))) {
491 *fieldValue = tmp;
492 component->setFieldChanged(fieldId);
493 }
494 }
495 else if (fieldType == TypeDatabase::getType<glm::uvec3>()) {
496 auto fieldValue = field->getPtr<glm::uvec3>(component);
497 glm::ivec3 tmp = *fieldValue;
498 if (ImGui::DragInt3(fieldHeader.c_str(), glm::value_ptr(tmp))) {
499 *fieldValue = tmp;
500 component->setFieldChanged(fieldId);
501 }
502 }
503 else if (fieldType == TypeDatabase::getType<glm::uvec4>()) {
504 auto fieldValue = field->getPtr<glm::uvec4>(component);
505 glm::ivec4 tmp = *fieldValue;
506 if (ImGui::DragInt4(fieldHeader.c_str(), glm::value_ptr(tmp))) {
507 *fieldValue = tmp;
508 component->setFieldChanged(fieldId);
509 }
510 }
511 else if (fieldType == TypeDatabase::getType<glm::dvec2>()) {
512 auto fieldValue = field->getPtr<glm::dvec2>(component);
513
514 glm::vec2 fValues(*fieldValue);
515
516 if (ImGui::DragFloat2(fieldHeader.c_str(), glm::value_ptr(fValues))) {
517 *fieldValue = glm::dvec2(fValues);
518 component->setFieldChanged(fieldId);
519 }
520 }
521 else if (fieldType == TypeDatabase::getType<glm::dvec3>()) {
522 auto fieldValue = field->getPtr<glm::dvec3>(component);
523
524 glm::vec3 fValues(*fieldValue);
525
526 if (ImGui::DragFloat3(fieldHeader.c_str(), glm::value_ptr(fValues))) {
527 *fieldValue = glm::dvec3(fValues);
528 component->setFieldChanged(fieldId);
529 }
530 }
531 else if (fieldType == TypeDatabase::getType<glm::quat>()) {
532 auto fieldValue = field->getPtr<glm::quat>(component);
533 // Euler Angles
534 glm::vec3 euler = glm::eulerAngles(*fieldValue);
535 if (ImGui::DragFloat3("Euler", glm::value_ptr(euler), 0.01f, -3.14f, 3.14f)) {
536 float eps = 0.00001f;
537 if (euler.y >= glm::pi<float>() / 2.0f - eps)
538 euler.y = glm::pi<float>() / 2.0f - eps;
539 else if (euler.y <= -glm::pi<float>() / 2.0f + eps)
540 euler.y = -glm::pi<float>() / 2.0f + eps;
541 *fieldValue = glm::quat(euler);
542
543 component->setFieldChanged(fieldId);
544 }
545
546 if (ImGui::DragFloat4(fieldHeader.c_str(), glm::value_ptr(*fieldValue), 0.01f, 0, 1)) {
547 component->setFieldChanged(fieldId);
548 }
549 }
550 else if (fieldType == TypeDatabase::getType<glm::mat4>()) {
551 auto fieldValue = field->getPtr<glm::mat4>(component);
552
553 ImGui::PushID(field);
554
555 if (showMatrix(fieldHeader, *fieldValue)) {
556 component->setFieldChanged(fieldId);
557 }
558 ImGui::PopID();
559 }
560 else if (fieldType == TypeDatabase::getType<bool>()) {
561 auto fieldValue = field->getPtr<bool>(component);
562
563 if (ImGui::Checkbox(fieldHeader.c_str(), fieldValue)) {
564 component->setFieldChanged(fieldId);
565 }
566 }
567 else if (fieldType == TypeDatabase::getType<uint32_t>()) {
568 auto fieldValue = field->getPtr<uint32_t>(component);
569 ImGui::Text("%s: 0x%08x", fieldHeader.c_str(), *fieldValue);
570
571 int tmp = static_cast<int>(*fieldValue);
572 const auto* range = field->get<ComponentModel::RangeAttribute<uint32_t>>();
573 if (range) {
574 const int min = std::min(tmp, static_cast<int>(range->getMin()));
575 const int max = std::max(tmp, static_cast<int>(range->getMax()));
576 if (ImGui::SliderInt(fieldHeader.c_str(), &tmp, min, max)) {
577 *fieldValue = static_cast<uint32_t>(tmp);
578 component->setFieldChanged(fieldId);
579 }
580 }
581 else {
582 if (ImGui::DragInt(fieldHeader.c_str(), &tmp)) {
583 *fieldValue = static_cast<uint32_t>(tmp);
584 component->setFieldChanged(fieldId);
585 }
586 }
587 }
588 else if (fieldType.isEnum()) {
589 auto fieldValue = field->getPtr<uint32_t>(component);
590 auto value = *fieldValue;
591
592 if (fieldType.isEnumFlags()) {
593 ImGui::Text("%s (%d = 0x%08X):", fieldHeader.c_str(), value, value);
594 ScopedIndent si;
595
596 ImGui::PushID(field);
597
598 auto numEnums = fieldType.getNumEnumerators();
599
600 for (size_t i = 0; i < numEnums; ++i) {
601 auto enumerator = fieldType.getEnumerator(i);
602 auto enumeratorValue = enumerator->getValue();
603
604 if (std::popcount(static_cast<uint32_t>(enumeratorValue)) != 1) continue;
605
606 if (ImGui::CheckboxFlags(enumerator->getName().c_str(), &value, enumeratorValue)) {
607 *fieldValue = value;
608 }
609 }
610
611 ImGui::PopID();
612 }
613 else {
614 size_t enums = fieldType.getNumEnumerators();
615
616 std::vector<const char *> enumNames(enums);
617 std::vector<int> enumValues(enums);
618 int index = 0;
619 for (size_t i = 0; i < enums; ++i) {
620 enumNames[i] = fieldType.getEnumerator(i)->getName().c_str();
621 enumValues[i] = fieldType.getEnumerator(i)->getValue();
622 if (enumValues[i] == static_cast<int>(*fieldValue)) index = static_cast<int>(i);
623 }
624
625 if (ImGui::Combo(fieldHeader.c_str(), &index, enumNames.data(), static_cast<int>(enumNames.size()))) {
626 *fieldValue = enumValues[index];
627 component->setFieldChanged(fieldId);
628 }
629 }
630 }
631 else if (fieldType == TypeDatabase::getType<std::vector<EntityPtr>>()) {
632 auto fieldValue = field->getPtr<std::vector<EntityPtr>>(component);
633 showEntities(context, *fieldValue, entityNamePattern, componentNamePattern);
634 }
635 else if (fieldType == TypeDatabase::getType<MeshHandle>()) {
636 auto & fieldValue = *field->getPtr<MeshHandle>(component);
637
638 if (fieldValue) {
639 showMesh(context, fieldValue.resolve(), fieldHeader);
640 } else {
641 ImGui::Text("None");
642 }
643 }
644 else if (fieldType == TypeDatabase::getType<TextureHandle>()) {
645 auto fieldValue = field->getPtr<TextureHandle>(component);
646
647 auto editor = context->engine->getEditor();
648 if (editor && editor->isActive()) {
649 if (editor->showTexture(field->getName().c_str(), *fieldValue)) {
650 component->setFieldChanged(fieldId);
651 }
652 }
653 else {
654 if (*fieldValue) {
655 if (ImGui::CollapsingHeader(field->getName().c_str())) {
656 Texture* texture = fieldValue->resolve();
657 auto renderer = dynamic_cast<Cogs::Core::Renderer*>(context->renderer);
658 showTexture(context, renderer, texture);
659 }
660 }
661 else {
662 ImGui::Text("%s: NULL", field->getName().c_str());
663 }
664 }
665 }
666 else if (fieldType == TypeDatabase::getType<WeakEntityPtr>()) {
667 auto fieldValue = field->getPtr<WeakEntityPtr>(component);
668 if (auto spt = fieldValue->lock())
669 {
670 showEntityInspector(context, &(*spt), entityNamePattern, componentNamePattern, showBorder);
671 } else {
672 ImGui::Text("%s: NULL", field->getName().c_str());
673 }
674 }
675 else if (fieldType == TypeDatabase::getType<std::string>()) {
676 auto fieldValue = field->getPtr<std::string>(component);
677 std::string buffer = *fieldValue;
678 buffer.resize(256);
679 if (ImGui::InputText(field->getName().c_str(), &buffer[0], buffer.size())) {
680 for (size_t i = 0; i < buffer.size(); i++) {
681 if (buffer[i] == '\0') {
682 buffer.resize(i);
683 break;
684 }
685 }
686
687 ImguiRenderer* guiRenderer = context->renderer->getGuiRenderer();
688 if (guiRenderer) {
689 guiRenderer->addTextToGlyphBuilder(buffer.c_str());
690 }
691
692 *fieldValue = std::move(buffer);
693 }
694 }
695 else if (fieldType == TypeDatabase::getType<std::vector<std::string>>()) {
696 auto fieldValue = field->getPtr<std::vector<std::string>>(component);
697 if (fieldValue->empty())
698 {
699 ImGui::Text("%s: Empty", field->getName().c_str());
700 }
701 else {
702 ScopedIndent si;
703 if (ImGui::CollapsingHeader(std::string(fieldHeader + " [size=" + std::to_string(fieldValue->size()) + "]").c_str()))
704 {
705 int i = 0;
706 for (auto& s : *fieldValue) {
707 auto label = std::to_string(i++);
708 std::string buffer = s;
709 buffer.resize(256);
710 if (ImGui::InputText(label.c_str(), &buffer[0], buffer.size())) {
711
712 ImguiRenderer* guiRenderer = context->renderer->getGuiRenderer();
713 if (guiRenderer) {
714 guiRenderer->addTextToGlyphBuilder(buffer.c_str());
715 }
716
717 s = buffer;
718 }
719 }
720 }
721 }
722 }
723 else if (fieldType == TypeDatabase::getType<std::vector<int32_t>>()) {
724 auto fieldValue = field->getPtr<std::vector<int32_t>>(component);
725 if (showArray1D(field->getName().getName(), std::span<int32_t>(*fieldValue))) {
726 component->setFieldChanged(fieldId);
727 }
728 }
729 else if (fieldType == TypeDatabase::getType<std::vector<uint32_t>>()) {
730 auto fieldValue = field->getPtr<std::vector<uint32_t>>(component);
731 if (showArray1D(field->getName().getName(), std::span<uint32_t>(*fieldValue))) {
732 component->setFieldChanged(fieldId);
733 }
734 }
735 else if (fieldType == TypeDatabase::getType<std::vector<float>>()) {
736 auto fieldValue = field->getPtr<std::vector<float>>(component);
737 if (showArray1D(field->getName().getName(), std::span<float>(*fieldValue))) {
738 component->setFieldChanged(fieldId);
739 }
740 }
741 else if (fieldType == TypeDatabase::getType<std::vector<glm::vec3>>()) {
742 auto fieldValue = field->getPtr<std::vector<glm::vec3>>(component);
743 if (showArray3D(fieldHeader, *fieldValue)) {
744 component->setFieldChanged(fieldId);
745 }
746 }
747 else if (fieldType == TypeDatabase::getType<std::vector<glm::vec2>>()) {
748 auto fieldValue = field->getPtr<std::vector<glm::vec2>>(component);
749 if (fieldValue->empty())
750 {
751 ImGui::Text("%s: Empty", field->getName().c_str());
752 }
753 else if (ImGui::CollapsingHeader(fieldHeader.c_str())) {
754 ScopedIndent si;
755 int i = 0;
756 for (auto& v : *fieldValue) {
757 auto label = std::to_string(i++);
758 if (ImGui::DragFloat2(label.c_str(), glm::value_ptr(v))) {
759 component->setFieldChanged(fieldId);
760 }
761 }
762 }
763 }
764 else if (fieldType == TypeDatabase::getType<std::vector<glm::vec4>>()) {
765 auto fieldValue = field->getPtr<std::vector<glm::vec4>>(component);
766 if (fieldValue->empty()) {
767 ImGui::Text("%s: Empty", field->getName().c_str());
768 }
769 else if (ImGui::CollapsingHeader(fieldHeader.c_str())) {
770 ScopedIndent si;
771 int i = 0;
772 for (auto& v : *fieldValue) {
773 auto label = std::to_string(i++);
774 if (ImGui::DragFloat4(label.c_str(), glm::value_ptr(v))) {
775 component->setFieldChanged(fieldId);
776 }
777 }
778 }
779 }
780 else if (fieldType == TypeDatabase::getType<MaterialInstanceHandle>()) {
781 auto fieldValue = field->getPtr<MaterialInstanceHandle>(component);
782 auto header = (!field->getName().getName().empty() ? field->getName().getName() : "Material");
783 auto * material = fieldValue->resolve();
784 if (material) {
785 showMaterialInstance(context, material, header);
786 }
787 else {
788 ImGui::Text("%s: NULL", header.c_str());
789 }
790 }
791 else if (fieldType == TypeDatabase::getType<std::vector<MaterialInstanceHandle>>()) {
792 auto fieldValue = field->getPtr<std::vector<MaterialInstanceHandle>>(component);
793 if (fieldValue->empty()) {
794 ImGui::Text("%s: Empty", field->getName().c_str());
795 }
796 else if (ImGui::CollapsingHeader(fieldHeader.c_str())) {
797 ScopedIndent si;
798 int i = 0;
799 for (auto& v : *fieldValue) {
800 auto* material = v.resolve();
801
802 auto label = "Mat: " + std::to_string(i++);
803 if (material) {
804 showMaterialInstance(context, material, label);
805 }
806 else {
807 ImGui::Text("%s: NULL", label.c_str());
808 }
809 }
810 }
811 }
812 else if (fieldType == TypeDatabase::getType<EntityPtr>()) {
813 auto fieldValue = field->getPtr<EntityPtr>(component);
814 if (fieldValue->get()) {
815 showEntityInspector(context, fieldValue->get(), entityNamePattern, componentNamePattern, showBorder);
816 }
817 else {
818 ImGui::Text("%s: NULL", field->getName().c_str());
819 }
820 }
821 else if (fieldType == TypeDatabase::getType<Cogs::Core::FontHandle>()) {
822 auto fieldValue = field->getPtr<Cogs::Core::FontHandle>(component);
823 if (fieldValue->get()) {
824 ImGui::Text("%s:", field->getName().c_str());
825 ImGui::SameLine();
826 ImGui::Text("%d", fieldValue->getId());
827 }
828 else {
829 ImGui::Text("%s: NULL", field->getName().c_str());
830 }
831 }
832 else if (fieldType == TypeDatabase::getType<ModelHandle>()) {
833 auto fieldValue = field->getPtr<ModelHandle>(component);
834
835 Model * model = fieldValue->resolve();
836 if (model) {
837 auto header = (!model->getName().empty() ? model->getName() : "Model");
838 showModel(context, model, std::string(header));
839 } else {
840 ImGui::Text("%s: NULL", field->getName().c_str());
841 }
842 }
843 else if (fieldType == TypeDatabase::getType<PropertyStore>()) {
844 PropertyStore* propStore = field->getPtr<PropertyStore>(component);
845 assert(propStore != nullptr);
846
847 for (const Cogs::Core::PropertyInfo& header : propStore->getHeaders()) {
848 const std::string propName(propStore->getKey(header));
849
850 switch(header.type) {
851 case Cogs::Core::PropertyType::Bool: {
852 ImGui::Text("%s: %s", propName.c_str(), header.boolValue ? "true" : "false");
853 break;
854 }
855 case Cogs::Core::PropertyType::Integer: {
856 ImGui::Text("%s: %d", propName.c_str(), header.intValue);
857 break;
858 }
859 case Cogs::Core::PropertyType::Int2: {
860 ImGui::Text("%s: (%d, %d)", propName.c_str(), header.int2Value[0], header.int2Value[1]);
861 break;
862 }
863 case Cogs::Core::PropertyType::UnsignedInteger: {
864 ImGui::Text("%s: %u", propName.c_str(), header.uintValue);
865 break;
866 }
867 case Cogs::Core::PropertyType::UInt2: {
868 ImGui::Text("%s: (%u, %u)", propName.c_str(), header.uint2Value[0], header.uint2Value[1]);
869 break;
870 }
871 case Cogs::Core::PropertyType::Float: {
872 ImGui::Text("%s: %f", propName.c_str(), header.floatValue);
873 break;
874 }
875 case Cogs::Core::PropertyType::Float2: {
876 ImGui::Text("%s: (%f, %f)", propName.c_str(), header.float2Value[0], header.float2Value[1]);
877 break;
878 }
879 case Cogs::Core::PropertyType::Double: {
880 ImGui::Text("%s: %f", propName.c_str(), header.doubleValue);
881 break;
882 }
883 case Cogs::Core::PropertyType::StringRef: {
884 ImGui::Text("%s: %s", propName.c_str(), Cogs::Core::Strings::getC(header.stringRefValue));
885 break;
886 }
887 case Cogs::Core::PropertyType::String: {
888 ImGui::Text("%s: %.*s", propName.c_str(), StringViewFormat(propStore->getString(header)));
889 break;
890 }
891 case Cogs::Core::PropertyType::FloatArray: {
892 showArray1D(propName, propStore->getFloatArray(header));
893 break;
894 }
895 case Cogs::Core::PropertyType::IntArray: {
896 showArray1D(propName, propStore->getIntArray(header));
897 break;
898 }
899 case Cogs::Core::PropertyType::UIntArray: {
900 showArray1D(propName, propStore->getUIntArray(header));
901 break;
902 }
903 case Cogs::Core::PropertyType::DoubleArray: {
904 showArray1D(propName, propStore->getDoubleArray(header));
905 break;
906 }
907 default: {
908 assert(false && "Unsupported PropertyType");
909 }
910 }
911 }
912 }
913 else {
914 ImGui::Text("%s: %s (Type: %d)", field->getName().c_str(), fieldType.getName().c_str(), static_cast<int>(fieldType.getTypeId()));
915 }
916}
917
918void Cogs::Core::entityInspector(Context * context, bool * show)
919{
920 if (*show) {
921 ImGui::SetNextWindowSize(ImVec2(400, 1000), ImGuiCond_Once);
922
923 guiBegin("Entities", show);
924
925 const std::unordered_map<EntityId, EntityPtr>& entities = context->store->getEntities();
926
927#if defined(ENABLE_XML_EXPORT)
928 static char filename[1024];
929
930 ImGui::SameLine();
931 if (ImGui::Button("Export XML to >")) {
932 if (filename[0]) {
933 std::string dest;
934
935 dest.reserve(1048576);
936 dest = "<?xml version='1.0'?><entities>";
937 for (const auto& [id, entity] : entities) {
938 if (entity) {
939 exportEntity(context, entity.get(), dest);
940 }
941 }
942 dest += "</entities>";
943 IO::writeBinaryFile(filename, dest.data(), dest.size());
944 }
945 }
946 ImGui::SameLine();
947 ImGui::InputText("Filename", filename, sizeof(filename));
948#endif
949
950 // Filter out entities in store, but child of other.
951 static bool onlyRootEntities = true;
952 size_t numEntities = 0;
953 if (onlyRootEntities) {
954 for (const auto& [id, entity] : entities) {
955 if (context->store->getEntityParent(entity.get()) == nullptr) {
956 numEntities++;
957 }
958 }
959 }
960 else {
961 numEntities = entities.size();
962 }
963
964 ImGui::Text("Entities: %zu", numEntities);
965
966 const float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
967 ImGui::SameLine(0.0f, spacing);
968 ImGui::Text(" ");
969 ImGui::SameLine(0.0f, spacing);
970 if (ImGui::RadioButton("Root Entities", onlyRootEntities == true)) { onlyRootEntities = true; }
971 ImGui::SameLine(0.0f, spacing);
972 if (ImGui::RadioButton("All User", onlyRootEntities == false)) { onlyRootEntities = false; }
973
974 // Add text-box allowing filtering by entity name. Simple String containsInvariantCase.
975 std::string_view entityNamePattern;
976 {
977 static std::array<char, 33> nameFilterBuffer;
978 constexpr int widthInputText = 120;
979 ImGui::PushItemWidth(widthInputText);
980 ImGui::Text("Name:");
981 ImGui::SameLine(0.0f, spacing);
982 if (ImGui::InputText(" ", nameFilterBuffer.data(), nameFilterBuffer.size(), ImGuiInputTextFlags_None)) {
983 ImguiRenderer* guiRenderer = context->renderer->getGuiRenderer();
984 if (guiRenderer) {
985 guiRenderer->addTextToGlyphBuilder(nameFilterBuffer.data());
986 }
987 }
988 entityNamePattern = nameFilterBuffer.data();
989 }
990
991 std::string_view componentNamePattern;
992 {
993 static std::array<char, 33> componentFilterBuffer;
994 ImGui::SameLine(0.0f, spacing);
995 ImGui::Text("Componen:");
996 ImGui::SameLine(0.0f, spacing);
997 if (ImGui::InputText(" ", componentFilterBuffer.data(), componentFilterBuffer.size(), ImGuiInputTextFlags_None)) {
998 ImguiRenderer* guiRenderer = context->renderer->getGuiRenderer();
999 if (guiRenderer) {
1000 guiRenderer->addTextToGlyphBuilder(componentFilterBuffer.data());
1001 }
1002 }
1003 componentNamePattern = componentFilterBuffer.data();
1004 }
1005
1006 ImGui::PopItemWidth();
1007
1008 // Get entity search. Special: startes with '+' only show entities with several refs.
1009 // Possible leaked refs. E.g. both a child and stored in store->entities.
1010 bool showOnlyMultiRefs = false;
1011 if (!entityNamePattern.empty() && entityNamePattern[0] == '+') {
1012 showOnlyMultiRefs = true;
1013 entityNamePattern = entityNamePattern.substr(1);
1014 }
1015
1016
1017 ImGui::BeginChild("Child");
1018 for (const auto& [id, entity] : entities) {
1019 bool showEntity = entity.operator bool();
1020 if (showOnlyMultiRefs) {
1021 showEntity = entity.use_count() > 1;
1022 }
1023
1024 if (showEntity && (!entityNamePattern.empty() || !componentNamePattern.empty())) {
1025 showEntity = findHierarchyWithMatch(*entity, entityNamePattern, componentNamePattern);
1026 }
1027
1028 if (showEntity && onlyRootEntities) {
1029 showEntity = context->store->getEntityParent(entity.get()) == nullptr;
1030 }
1031
1032 if (showEntity) {
1033 showCollapsableEntityInspector(context, entity.get(), entityNamePattern, componentNamePattern);
1034 }
1035 }
1036 ImGui::EndChild();
1037
1038 guiEnd();
1039 }
1040}
Base class for Component instances.
Definition: Component.h:143
void setFieldChanged(const Reflection::FieldId fieldId)
Sets the component to the ComponentFlags::Changed state without carry.
Definition: Component.h:218
COGSFOUNDATION_API const Reflection::Type & getType() const
Get the full Reflection::Type of the component.
Definition: Component.cpp:27
Container for components, providing composition of dynamic entities.
Definition: Entity.h:18
constexpr size_t getId() const noexcept
Get the unique identifier of this entity.
Definition: Entity.h:113
void getComponents(ComponentCollection< T > &collection) const
Get all the components implementing the templated type.
Definition: Entity.h:52
const std::string & getName() const noexcept
Get the name of this entity.
Definition: Entity.h:120
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
Core renderer system.
Definition: Renderer.h:28
Field definition describing a single data member of a data structure.
Definition: Field.h:70
const Name & getName() const
Get the name of the field.
Definition: Field.h:154
const T * get() const
Retrieve an attribute of the given type from storage, if present.
Definition: Field.h:225
TypeId getTypeId() const
Get the type id of the field.
Definition: Field.h:158
FieldValueType * getPtr(void *container) const
Get a pointer to this field on the given container.
Definition: Field.h:130
void call(Class *object, Arg... arg) const
Call the method named name on the given object, with the given arguments.
Definition: Method.h:144
static const Type & getType()
Get the Type of the given template argument.
Definition: TypeDatabase.h:168
Represents a discrete type definition, describing a native type class.
Definition: Type.h:89
const Type * getBase() const
Get the base type.
Definition: Type.cpp:32
size_t getNumFields() const
Get the number of fields in the type.
Definition: Type.h:230
const Enumerator * getEnumerator(const Name &name) const
Get a pointer to the enumerator with the given name.
Definition: Type.cpp:145
const Method * getMethod(const Name &name) const
Get a pointer to the method with the given name.
Definition: Type.cpp:133
bool isEnum() const
Get if the type is an enumeration type.
Definition: Type.h:315
constexpr const Name & getName() const
Get the unique name of the type.
Definition: Type.h:198
bool isEnumFlags() const
Get if the type is a flag enumeration type.
Definition: Type.h:334
size_t getNumHierarchyFields() const
Get the number of fields in the type + types in all base types.
Definition: Type.cpp:96
size_t getNumEnumerators() const
Get the number of enumerators in the type.
Definition: Type.h:322
const Field * getField(const Name &name) const
Get a pointer to the field info of the field with the given name.
Definition: Type.cpp:53
constexpr TypeId getTypeId() const
Get the unique Reflection::TypeId of this instance.
Definition: Type.h:325
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:50
constexpr bool empty() const noexcept
Check if the string is empty.
Definition: StringView.h:148
std::string to_string() const
String conversion method.
Definition: StringView.cpp:9
Contains code for composing and managing entities built from components.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
bool findHierarchyWithMatch(const Cogs::ComponentModel::Entity &entity, std::string_view entityNamePattern, std::string_view componentNamePattern)
std::shared_ptr< ComponentModel::Entity > EntityPtr
Smart pointer for Entity access.
Definition: EntityPtr.h:12
std::weak_ptr< ComponentModel::Entity > WeakEntityPtr
Weak Smart pointer for Entity access.
Definition: EntityPtr.h:18
bool showArray1D(const std::string &header, std::span< T > array)
Contains reflection support.
Definition: Component.h:11
Tags an object as being able to represent a color.
Definition: Attributes.h:122
Handle to a Component instance.
Definition: Component.h:67
Adds range information to an object.
Definition: Attributes.h:38
const Name & getName() const
Get the name of the enumerator.
Definition: Type.h:68
int getValue() const
Get the value of the enumerator.
Definition: Type.h:71
Represents an unique name.
Definition: Name.h:70
const char * c_str() const
Gets the name as a null-terminated string.
Definition: Name.h:116
const std::string & getName() const
Get the string name. This can be empty, even in valid instances of Name.
Definition: Name.h:112