Cogs.Core
EntityEditor.cpp
1#include "EntityEditor.h"
2
3#include "Renderer/IRenderer.h"
4#include "Renderer/InspectorGui/InspectorGuiHelper.h"
5#include "Renderer/InspectorGui/EntityInspector.h"
6
7#include "MaterialEditor.h"
8
9#include "Components/Core/PropertiesComponent.h"
10
11#include "Systems/Core/TransformSystem.h"
12#include "Systems/Core/CameraSystem.h"
13
14#include "Resources/MaterialInstance.h"
15#include "Resources/Mesh.h"
16
17#include "Bridge/Bridge.h"
18#include "Bridge/SceneFunctions.h"
19
20#include "Commands/FieldCommands.h"
21
22#include "Types.h"
23
24#include "Foundation/Geometry/BoundingBox.hpp"
25#include "Foundation/Reflection/TypeDatabase.h"
26
27#include "imgui.h"
28#include "ImGuizmo.h"
29
30#include <bit>
31#include <span>
32
33using namespace Cogs::Reflection;
34
35namespace
36{
37 void showPropertyStore(const Cogs::Core::Editor& editor, const Cogs::Core::PropertyStore& props) {
38 std::span<const Cogs::Core::PropertyInfo> headers = props.getHeaders();
39
40 for (const Cogs::Core::PropertyInfo& header : headers) {
41 const std::string propName = props.getKey(header).to_string();
42
43 switch(header.type) {
44 case Cogs::Core::PropertyType::Bool: {
45 editor.showText("%s: %s", propName.c_str(), header.boolValue ? "true" : "false");
46 break;
47 }
48 case Cogs::Core::PropertyType::Integer: {
49 editor.showText("%s: %d", propName.c_str(), header.intValue);
50 break;
51 }
52 case Cogs::Core::PropertyType::Int2: {
53 editor.showText("%s: (%d, %d)", propName.c_str(), header.int2Value[0], header.int2Value[1]);
54 break;
55 }
56 case Cogs::Core::PropertyType::UnsignedInteger: {
57 editor.showText("%s: %u", propName.c_str(), header.uintValue);
58 break;
59 }
60 case Cogs::Core::PropertyType::UInt2: {
61 editor.showText("%s: (%u, %u)", propName.c_str(), header.uint2Value[0], header.uint2Value[1]);
62 break;
63 }
64 case Cogs::Core::PropertyType::Float: {
65 editor.showText("%s: %f", propName.c_str(), header.floatValue);
66 break;
67 }
68 case Cogs::Core::PropertyType::Float2: {
69 editor.showText("%s: (%f, %f)", propName.c_str(), header.float2Value[0], header.float2Value[1]);
70 break;
71 }
72 case Cogs::Core::PropertyType::Double: {
73 editor.showText("%s: %f", propName.c_str(), header.doubleValue);
74 break;
75 }
76 case Cogs::Core::PropertyType::StringRef: {
77 editor.showText("%s: %s", propName.c_str(), Cogs::Core::Strings::getC(header.stringRefValue));
78 break;
79 }
80 case Cogs::Core::PropertyType::String: {
81 editor.showText("%s: %s", propName.c_str(), props.getString(header).to_string().c_str());
82 break;
83 }
84 case Cogs::Core::PropertyType::FloatArray: {
85 std::span<const float> floats = props.getFloatArray(header);
86 if (floats.empty()) {
87 editor.showText("%s: Empty", propName.c_str());
88 }
89 else {
90 if (ImGui::TreeNode((propName + " [size=" + std::to_string(floats.size()) + "]").c_str())) {
91 for (size_t i = 0; i < floats.size(); i++) {
92 editor.showText("%zu: %f", i, floats[i]);
93 }
94 ImGui::TreePop();
95 }
96 }
97 break;
98 }
99 case Cogs::Core::PropertyType::IntArray: {
100 std::span<const int32_t> ints = props.getIntArray(header);
101 if (ints.empty()) {
102 editor.showText("%s: Empty", propName.c_str());
103 }
104 else {
105 if (ImGui::TreeNode((propName + " [size=" + std::to_string(ints.size()) + "]").c_str())) {
106 for (size_t i = 0; i < ints.size(); i++) {
107 editor.showText("%zu: %d", i, ints[i]);
108 }
109 ImGui::TreePop();
110 }
111 }
112 break;
113 }
114 case Cogs::Core::PropertyType::UIntArray: {
115 std::span<const uint32_t> uints = props.getUIntArray(header);
116 if (uints.empty()) {
117 editor.showText("%s: Empty", propName.c_str());
118 }
119 else {
120 if (ImGui::TreeNode((propName + " [size=" + std::to_string(uints.size()) + "]").c_str())) {
121 for (size_t i = 0; i < uints.size(); i++) {
122 editor.showText("%zu: %u", i, uints[i]);
123 }
124 ImGui::TreePop();
125 }
126 }
127 break;
128 }
129 case Cogs::Core::PropertyType::DoubleArray: {
130 std::span<const double> doubles = props.getDoubleArray(header);
131 if (doubles.empty()) {
132 editor.showText("%s: Empty", propName.c_str());
133 }
134 else {
135 if (ImGui::TreeNode((propName + " [size=" + std::to_string(doubles.size()) + "]").c_str())) {
136 for (size_t i = 0; i < doubles.size(); i++) {
137 editor.showText("%zu: %f", i, doubles[i]);
138 }
139 ImGui::TreePop();
140 }
141 }
142 break;
143 }
144 default: {
145 assert(false && "Unsupported PropertyType");
146 }
147 }
148 }
149 }
150}
151
152void Cogs::Core::EntityEditor::showEntityEditor(Context* context, Editor* editor, ComponentModel::Entity* entity)
153{
154 editor->showText("%s", entity->getName().c_str());
155 const char* entityTemplate = ::getEntityTemplate(context, entity->getId());
156 if (entityTemplate) {
157 editor->showText("Type: %s", entityTemplate);
158 }
159
160 editor->showText("Id: %zd", entity->getId());
161
162 auto& components = entity->getComponents();
163
164 std::string componentHeader;
165 componentHeader.reserve(128);
166
167 for (auto& c : components) {
168 Cogs::ComponentModel::Component* component = c.resolve();
169 showComponentEditor(context, editor, entity, component);
170 }
171}
172
173void Cogs::Core::EntityEditor::showComponentEditor(Context* context, Editor* editor, ComponentModel::Entity* entity, Cogs::ComponentModel::Component* component)
174{
175 const Cogs::Reflection::Type& componentType = component->getType();
176 const Name& typeName = componentType.getName();
177
178 if (ImGui::TreeNodeEx(typeName.c_str(), ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog)) {
179 ScopedIndent componentIndent;
180
181 ImGui::PushID(component);
182
183 const size_t numFields = componentType.getNumHierarchyFields();
184
185 if (typeName == "TransformComponent") {
186 const TransformComponent* transform = static_cast<const TransformComponent*>(component);
187 for (FieldId fieldId = 0; fieldId < numFields; ++fieldId) {
188 const Cogs::Reflection::Field* field = componentType.getField(fieldId);
189 const Cogs::Reflection::Name& fieldName = field->getName();
190 if (fieldName == "transformFlags") {
191 // Do not display - user shouldn't change.
192 continue;
193 } else if (fieldName == "transform") {
194 // Inactive unless transformFlags = 0.
195 if (!transform->transformFlags) {
196 continue;
197 }
198 }
199 else {
200 // If transformFlags set position/rotation etc not in use
201 if (transform->transformFlags) {
202 continue;
203 }
204 }
205
206 // coordinates field only used for root entities. Hide if child and zero coord.
207 if (fieldName == "coordinates" && context->store->getEntityParent(entity) && transform->coordinates == glm::dvec3(0, 0, 0)) {
208 continue;
209 }
210
211 showFieldEditor(context, editor, component, fieldId);
212 }
213 }
214 else {
215 for (FieldId fieldId = 0; fieldId < numFields; ++fieldId) {
216 showFieldEditor(context, editor, component, fieldId);
217 }
218 }
219
220 ImGui::PopID();
221 }
222}
223
224void Cogs::Core::EntityEditor::showFieldEditor(Context* context, Editor* editor, Cogs::ComponentModel::Component* component, FieldId fieldId)
225{
226 const Cogs::Reflection::Type* componentType = &component->getType();
227
228 const Cogs::Reflection::Field* field = componentType->getField(fieldId);
229 const Cogs::Reflection::Name & fieldName = field->getName();
230 FieldInfo fieldInfo{ componentType->getTypeId(), fieldId };
231
232 const std::string& fieldHeader = fieldName.getName();
233
234 const Cogs::Reflection::Type& fieldType = TypeDatabase::getType(field->getTypeId());
235
236 if (fieldType == TypeDatabase::getType<int32_t>()) {
237 auto value = *field->getPtr<int32_t>(component);
238 const auto* range = field->get<ComponentModel::RangeAttribute<int>>();
239 if (range) {
240 const int32_t min = std::min(range->getMin(), value);
241 const int32_t max = std::max(range->getMax(), value);
242 if (ImGui::SliderInt(fieldHeader.c_str(), &value, min, max)) {
243 editor->applySelected<SetFieldCommand<int32_t>>(fieldInfo, value);
244 }
245 }
246 else {
247 if (ImGui::DragInt(fieldHeader.c_str(), &value)) {
248 editor->applySelected<SetFieldCommand<int32_t>>(fieldInfo, value);
249 }
250 }
251 }
252 else if (fieldType == TypeDatabase::getType<float>()) {
253 auto value = *field->getPtr<float>(component);
254
255 const auto* range = field->get<ComponentModel::RangeAttribute<float>>();
256 if (range) {
257 const float min = std::min(range ? range->getMin() : 0.0f, value);
258 const float max = std::max(range ? range->getMax() : 100.0f, value);
259 const float stepValue = (max - min) / 100.f;
260
261 if (ImGui::DragFloat(fieldHeader.c_str(), &value, stepValue, min, max)) {
262 editor->applySelected<SetFieldCommand<float>>(fieldInfo, value);
263 }
264 }
265 else {
266 if (ImGui::DragFloat(fieldHeader.c_str(), &value)) {
267 editor->applySelected<SetFieldCommand<float>>(fieldInfo, value);
268 }
269 }
270 }
271 else if (fieldType == TypeDatabase::getType<glm::vec2>()) {
272 auto value = *field->getPtr<glm::vec2>(component);
273
274 if (ImGui::DragFloat2(fieldHeader.c_str(), glm::value_ptr(value))) {
275 editor->applySelected<SetFieldCommand<glm::vec2>>(fieldInfo, value);
276 }
277 }
278 else if (fieldType == TypeDatabase::getType<glm::vec3>()) {
279 auto value = *field->getPtr<glm::vec3>(component);
280
281 if (ImGui::DragFloat3(fieldHeader.c_str(), glm::value_ptr(value))) {
282 editor->applySelected<SetFieldCommand<glm::vec3>>(fieldInfo, value);
283 }
284 }
285 else if (fieldType == TypeDatabase::getType<glm::vec4>()) {
286 auto value = *field->getPtr<glm::vec4>(component);
287
288 auto ca = field->get<ComponentModel::ColorAttribute>();
289
290 if (ca || (fieldName.getName().find("olor") != std::string::npos)) {
291 if (ImGui::ColorEdit4(fieldHeader.c_str(), glm::value_ptr(value))) {
292 editor->applySelected<SetFieldCommand<glm::vec4>>(fieldInfo, value);
293 }
294 } else {
295 if (ImGui::DragFloat4(fieldHeader.c_str(), glm::value_ptr(value))) {
296 editor->applySelected<SetFieldCommand<glm::vec4>>(fieldInfo, value);
297 }
298 }
299 }
300 else if (fieldType == TypeDatabase::getType<glm::ivec2>()) {
301 auto value = *field->getPtr<glm::ivec2>(component);
302 if (ImGui::DragInt2(fieldHeader.c_str(), glm::value_ptr(value))) {
303 editor->applySelected<SetFieldCommand<glm::ivec2>>(fieldInfo, value);
304 }
305 }
306 else if (fieldType == TypeDatabase::getType<glm::ivec3>()) {
307 auto value = *field->getPtr<glm::ivec3>(component);
308 if (ImGui::DragInt3(fieldHeader.c_str(), glm::value_ptr(value))) {
309 editor->applySelected<SetFieldCommand<glm::ivec3>>(fieldInfo, value);
310 }
311 }
312 else if (fieldType == TypeDatabase::getType<glm::ivec4>()) {
313 auto value = *field->getPtr<glm::ivec4>(component);
314 if (ImGui::DragInt4(fieldHeader.c_str(), glm::value_ptr(value))) {
315 editor->applySelected<SetFieldCommand<glm::ivec4>>(fieldInfo, value);
316 }
317 }
318 else if (fieldType == TypeDatabase::getType<glm::uvec2>()) {
319 auto fieldValue = field->getPtr<glm::uvec2>(component);
320 glm::ivec2 tmp = *fieldValue;
321 if (ImGui::DragInt2(fieldHeader.c_str(), glm::value_ptr(tmp))) {
322 editor->applySelected<SetFieldCommand<glm::uvec2>>(fieldInfo, tmp);
323 }
324 }
325 else if (fieldType == TypeDatabase::getType<glm::uvec3>()) {
326 auto fieldValue = field->getPtr<glm::uvec3>(component);
327 glm::ivec3 tmp = *fieldValue;
328 if (ImGui::DragInt3(fieldHeader.c_str(), glm::value_ptr(tmp))) {
329 editor->applySelected<SetFieldCommand<glm::uvec3>>(fieldInfo, tmp);
330 }
331 }
332 else if (fieldType == TypeDatabase::getType<glm::uvec4>()) {
333 auto fieldValue = field->getPtr<glm::uvec4>(component);
334 glm::ivec4 tmp = *fieldValue;
335 if (ImGui::DragInt4(fieldHeader.c_str(), glm::value_ptr(tmp))) {
336 editor->applySelected<SetFieldCommand<glm::uvec4>>(fieldInfo, tmp);
337 }
338 }
339 else if (fieldType == TypeDatabase::getType<glm::dvec2>()) {
340 auto fieldValue = field->getPtr<glm::dvec2>(component);
341
342 glm::vec2 fValues(*fieldValue);
343
344 if (ImGui::DragFloat2(fieldHeader.c_str(), glm::value_ptr(fValues))) {
345 editor->applySelected<SetFieldCommand<glm::dvec2>>(fieldInfo, fValues);
346 }
347 }
348 else if (fieldType == TypeDatabase::getType<glm::dvec3>()) {
349 auto fieldValue = field->getPtr<glm::dvec3>(component);
350
351 glm::vec3 fValues(*fieldValue);
352
353 if (ImGui::DragFloat3(fieldHeader.c_str(), glm::value_ptr(fValues))) {
354 editor->applySelected<SetFieldCommand<glm::dvec3>>(fieldInfo, fValues);
355 }
356 }
357 else if (fieldType == TypeDatabase::getType<glm::quat>()) {
358 auto value = *field->getPtr<glm::quat>(component);
359 // Euler Angles
360 glm::vec3 euler = glm::eulerAngles(value);
361 if (ImGui::DragFloat3("Euler", glm::value_ptr(euler), 0.01f, -glm::pi<float>(), glm::pi<float>())) {
362 float eps = 0.00001f;
363 if (euler.y >= glm::pi<float>() / 2.0f - eps)
364 euler.y = glm::pi<float>() / 2.0f - eps;
365 else if (euler.y <= -glm::pi<float>() / 2.0f + eps)
366 euler.y = -glm::pi<float>() / 2.0f + eps;
367
368 value = glm::quat(euler);
369 editor->applySelected<SetFieldCommand<glm::quat>>(fieldInfo, value);
370 }
371
372 if (ImGui::DragFloat4(fieldHeader.c_str(), glm::value_ptr(value), 0.01f, 0, 1)) {
373 editor->applySelected<SetFieldCommand<glm::quat>>(fieldInfo, value);
374 }
375 }
376 else if (fieldType == TypeDatabase::getType<glm::mat4>()) {
377 auto fieldValue = field->getPtr<glm::mat4>(component);
378 auto value = *fieldValue;
379
380 ImGui::PushID(field);
381
382 if (showMatrix(fieldHeader, *fieldValue)) {
383 editor->applySelected<SetFieldCommand<glm::mat4>>(fieldInfo, value);
384 }
385 ImGui::PopID();
386 }
387 else if (fieldType == TypeDatabase::getType<bool>()) {
388 auto value = *field->getPtr<bool>(component);
389
390 if (ImGui::Checkbox(fieldHeader.c_str(), &value)) {
391 editor->applySelected<SetFieldCommand<bool>>(fieldInfo, value);
392 }
393 }
394 else if (fieldType == TypeDatabase::getType<uint32_t>()) {
395 auto fieldValue = field->getPtr<uint32_t>(component);
396 editor->showText("%s: 0x%08x", fieldHeader.c_str(), *fieldValue);
397
398 int tmp = static_cast<int>(*fieldValue);
399 const auto* range = field->get<ComponentModel::RangeAttribute<uint32_t>>();
400 if (range) {
401 const int min = std::min(tmp, static_cast<int>(range->getMin()));
402 const int max = std::max(tmp, static_cast<int>(range->getMax()));
403 if (ImGui::SliderInt(fieldHeader.c_str(), &tmp, min, max)) {
404 editor->applySelected<SetFieldCommand<uint32_t>>(fieldInfo, static_cast<uint32_t>(tmp));
405 }
406 }
407 else {
408 if (ImGui::DragInt(fieldHeader.c_str(), &tmp)) {
409 editor->applySelected<SetFieldCommand<uint32_t>>(fieldInfo, static_cast<uint32_t>(tmp));
410 }
411 }
412 }
413 else if (fieldType.isEnum()) {
414 auto fieldValue = field->getPtr<uint32_t>(component);
415 auto value = *fieldValue;
416
417 if (fieldType.isEnumFlags()) {
418 editor->showText("%s:", fieldHeader.c_str());
419 ScopedIndent si;
420
421 ImGui::PushID(field);
422
423 auto numEnums = fieldType.getNumEnumerators();
424
425 for (size_t i = 0; i < numEnums; ++i) {
426 auto enumerator = fieldType.getEnumerator(i);
427 auto enumeratorValue = enumerator->getValue();
428
429 if (std::popcount(static_cast<uint32_t>(enumeratorValue)) != 1) continue;
430
431 if (ImGui::CheckboxFlags(enumerator->getName().c_str(), &value, enumeratorValue)) {
432 editor->applySelected<SetFieldCommand<uint32_t>>(fieldInfo, value);
433 }
434 }
435
436 ImGui::PopID();
437 }
438 else {
439 size_t enums = fieldType.getNumEnumerators();
440
441 std::vector<const char *> enumNames(enums);
442 std::vector<int> enumValues(enums);
443 int index = 0;
444 for (size_t i = 0; i < enums; ++i) {
445 enumNames[i] = fieldType.getEnumerator(i)->getName().c_str();
446 enumValues[i] = fieldType.getEnumerator(i)->getValue();
447 if (enumValues[i] == static_cast<int>(*fieldValue)) index = static_cast<int>(i);
448 }
449
450 if (ImGui::Combo(fieldHeader.c_str(), &index, enumNames.data(), static_cast<int>(enumNames.size()))) {
451 editor->applySelected<SetFieldCommand<uint32_t>>(fieldInfo, enumValues[index]);
452 }
453 }
454 }
455 else if (fieldType == TypeDatabase::getType<MeshHandle>()) {
456 auto & fieldValue = *field->getPtr<MeshHandle>(component);
457
458 if (fieldValue) {
459 showMesh(context, fieldValue.resolve(), fieldHeader);
460 } else {
461 editor->showText("None");
462 }
463 }
464 else if (fieldType == TypeDatabase::getType<TextureHandle>()) {
465 auto fieldValue = field->getPtr<TextureHandle>(component);
466
467 if (editor->showTexture(field->getName().c_str(), *fieldValue)) {
468 component->setFieldChanged(fieldId);
469 }
470 }
471 else if (fieldType == TypeDatabase::getType<WeakEntityPtr>()) {
472 auto fieldValue = field->getPtr<WeakEntityPtr>(component);
473 if (auto spt = fieldValue->lock())
474 {
475 showEntityEditor(context, editor, &(*spt));
476 } else {
477 editor->showText("%s: NULL", field->getName().c_str());
478 }
479 }
480 else if (fieldType == TypeDatabase::getType<std::string>()) {
481 auto fieldValue = field->getPtr<std::string>(component);
482 std::string buffer = *fieldValue;
483 buffer.resize(256);
484 if (ImGui::InputText(field->getName().c_str(), &buffer[0], buffer.size())) {
485 for (size_t i = 0; i < buffer.size(); i++) {
486 if (buffer[i] == '\0') {
487 buffer.resize(i);
488 break;
489 }
490 }
491
492 ImguiRenderer* guiRenderer = context->renderer->getGuiRenderer();
493 if (guiRenderer) {
494 guiRenderer->addTextToGlyphBuilder(buffer.c_str());
495 }
496
497 *fieldValue = std::move(buffer);
498 }
499 }
500 else if (fieldType == TypeDatabase::getType<std::vector<std::string>>()) {
501 auto fieldValue = field->getPtr<std::vector<std::string>>(component);
502 if (fieldValue->empty())
503 {
504 editor->showText("%s: Empty", field->getName().c_str());
505 }
506 else {
507 ScopedIndent si;
508 if (ImGui::CollapsingHeader(std::string(fieldHeader + " [size=" + std::to_string(fieldValue->size()) + "]").c_str()))
509 {
510 int i = 0;
511 for (auto& s : *fieldValue) {
512 auto label = std::to_string(i++);
513 std::string buffer = s;
514 buffer.resize(256);
515 if (ImGui::InputText(label.c_str(), &buffer[0], buffer.size())) {
516
517 ImguiRenderer* guiRenderer = context->renderer->getGuiRenderer();
518 if (guiRenderer) {
519 guiRenderer->addTextToGlyphBuilder(buffer.c_str());
520 }
521
522 s = buffer;
523 }
524 }
525 }
526 }
527 }
528 else if (fieldType == TypeDatabase::getType<std::vector<float>>()) {
529 auto fieldValue = field->getPtr<std::vector<float>>(component);
530 if (showArray1D(field->getName().getName(), std::span<float>(*fieldValue))) {
531 component->setFieldChanged(fieldId);
532 }
533 }
534 else if (fieldType == TypeDatabase::getType<std::vector<glm::vec3>>()) {
535 auto fieldValue = field->getPtr<std::vector<glm::vec3>>(component);
536 if (showArray3D(fieldHeader, *fieldValue)) {
537 component->setFieldChanged(fieldId);
538 }
539 }
540 else if (fieldType == TypeDatabase::getType<std::vector<glm::vec2>>()) {
541 auto fieldValue = field->getPtr<std::vector<glm::vec2>>(component);
542 if (fieldValue->empty())
543 {
544 editor->showText("%s: Empty", field->getName().c_str());
545 }
546 else if (ImGui::CollapsingHeader(fieldHeader.c_str())) {
547 ScopedIndent si;
548 int i = 0;
549 for (auto& v : *fieldValue) {
550 std::string label = std::to_string(i++);
551 if (ImGui::DragFloat2(label.c_str(), glm::value_ptr(v))) {
552 component->setFieldChanged(fieldId);
553 }
554 }
555 }
556 }
557 else if (fieldType == TypeDatabase::getType<std::vector<glm::vec4>>()) {
558 auto fieldValue = field->getPtr<std::vector<glm::vec4>>(component);
559 if (fieldValue->empty()) {
560 editor->showText("%s: Empty", field->getName().c_str());
561 }
562 else if (ImGui::CollapsingHeader(fieldHeader.c_str())) {
563 ScopedIndent si;
564 int i = 0;
565 for (auto& v : *fieldValue) {
566 std::string label = std::to_string(i++);
567 if (ImGui::DragFloat4(label.c_str(), glm::value_ptr(v))) {
568 component->setFieldChanged(fieldId);
569 }
570 }
571 }
572 }
573 else if (fieldType == TypeDatabase::getType<MaterialInstanceHandle>()) {
574 auto fieldValue = field->getPtr<MaterialInstanceHandle>(component);
575
576 editor->showText("%s:", fieldHeader.c_str());
577
578 ScopedIndent indent;
579 editor->showMaterialInstance(fieldInfo, *fieldValue);
580 }
581 else if (fieldType == TypeDatabase::getType<EntityPtr>()) {
582 auto fieldValue = field->getPtr<EntityPtr>(component);
583 if (fieldValue->get()) {
584 if (ImGui::CollapsingHeader(fieldHeader.c_str())) {
585 ScopedIndent si;
586 showEntityEditor(context, editor, fieldValue->get());
587 }
588 }
589 else {
590 editor->showText("%s: NULL", field->getName().c_str());
591 }
592 }
593 else if (fieldType == TypeDatabase::getType<Cogs::Core::FontHandle>()) {
594 auto fieldValue = field->getPtr<Cogs::Core::FontHandle>(component);
595 if (fieldValue->get()) {
596 editor->showText("%s:", field->getName().c_str());
597 ImGui::SameLine();
598 editor->showText("%d", fieldValue->getId());
599 }
600 else {
601 editor->showText("%s: NULL", field->getName().c_str());
602 }
603 }
604 else if (fieldType == TypeDatabase::getType<ModelHandle>()) {
605 auto fieldValue = field->getPtr<ModelHandle>(component);
606 editor->showModel(*fieldValue);
607 }
608 else if (fieldType == TypeDatabase::getType<PropertyStore>()) {
609 PropertyStore* propStore = field->getPtr<PropertyStore>(component);
610 assert(propStore != nullptr);
611 showPropertyStore(*editor, *propStore);
612 }
613 else {
614 // Fallback to default inspector for unhandled type.
615 // TypeDatabase::getType<std::vector<EntityPtr>>()
616 // TypeDatabase::getType<glm::ivecN>()
617 // TypeDatabase::getType<glm::uvecN>()
618 // .. ?
619 Cogs::Core::showFieldInspector(context, component, fieldId, std::string_view(), std::string_view());
620 }
621}
622
623
624void Cogs::Core::EntityEditor::showEntityGizmo(Context * context, Editor * editor)
625{
626 auto state = editor->getState();
627
628 if (state->mode == EditingMode::Select) {
629 // No editing
630 state->wasUsingGizmo = false;
631 } else if (state->hasSelected()) {
632 ImGuizmo::OPERATION currentOperation = ImGuizmo::TRANSLATE;
633 ImGuizmo::MODE currentGizmoMode = ImGuizmo::WORLD;
634
635 float snap = state->translateSnap;
636
637 if (state->mode == EditingMode::Rotate) {
638 currentOperation = ImGuizmo::ROTATE;
639 currentGizmoMode = ImGuizmo::LOCAL;
640 snap = state->rotateSnap;
641 } else if (state->mode == EditingMode::Scale) {
642 currentOperation = ImGuizmo::SCALE;
643 currentGizmoMode = ImGuizmo::LOCAL;
644 snap = state->scaleSnap;
645 }
646
647 const Entity* entity = state->getSelected();
648 if (!entity) return;
649
650 const TransformComponent* transformComponent = entity->getComponent<TransformComponent>();
651 if (!transformComponent) return;
652
653 if (!state->wasUsingGizmo) {
654 Geometry::DBoundingBox bounds;
655 ::calculateBoundingBoxWorld(context, entity->getId(), bounds.data());
656 if (Cogs::Geometry::isEmpty(bounds)) return;
657
658 glm::dvec3 center = 0.5 * (bounds.min + bounds.max);
659
660 // Capture transforms
661 if (state->pivot == EditingPivot::ObjectPivot) {
662 state->startTranslation = transformComponent->position;
663 } else {
664 state->startTranslation = center;
665 }
666 state->endTranslation = state->startTranslation;
667
668 state->startRotation = transformComponent->rotation;
669 state->endRotation = state->startRotation;
670
671 state->startScale = transformComponent->scale;
672 state->endScale = state->startScale;
673
674 state->startTranslations.resize(state->selected.size());
675 state->startRotations.resize(state->selected.size());
676 state->startScales.resize(state->selected.size());
677
678 for (size_t i = 0; i < state->selected.size(); ++i) {
679 const Entity* e = context->store->getEntityPtr(state->selected[i]);
680 auto t = e->getComponent<TransformComponent>();
681
682 state->startTranslations[i] = t->position;
683 state->startRotations[i] = t->rotation;
684 state->startScales[i] = t->scale;
685 }
686
687 const EntityData* entityData = static_cast<EntityData*>(state->getSelected()->getUserData());
688
689 glm::mat4 localTransform;
690 if (entityData->entityContext) {
691 localTransform = entityData->entityContext->transformSystem->getLocalTransform(transformComponent);
692 } else {
693 localTransform = context->transformSystem->getLocalTransform(transformComponent);
694 }
695
696 glm::mat4 invLocal = glm::inverse(localTransform);
697
698 state->startMin = glm::vec3(invLocal * glm::vec4(bounds.min, 1));
699 state->startMax = glm::vec3(invLocal * glm::vec4(bounds.max, 1));
700 }
701
702 const glm::vec3 rads = glm::eulerAngles(state->endRotation);
703 const glm::vec3 euler = glm::degrees(rads);
704
705 float transform[16];
706 ImGuizmo::RecomposeMatrixFromComponents(glm::value_ptr(state->endTranslation), glm::value_ptr(euler), glm::value_ptr(state->endScale), transform);
707
708 const auto cameraHandle = state->editorCamera.lock()->getComponentHandle<CameraComponent>();
709 const auto camera = cameraHandle.resolveComponent<CameraComponent>();
710 const auto & cameraData = context->cameraSystem->getData(camera);
711
712 ImGuizmo::SetRect(camera->viewportOrigin.x, camera->viewportOrigin.y, camera->viewportSize.x, camera->viewportSize.y);
713
714 glm::vec3 snapValue(snap);
715 ImGuizmo::Manipulate(glm::value_ptr(cameraData.viewMatrix), glm::value_ptr(cameraData.rawProjectionMatrix), currentOperation, currentGizmoMode, transform, nullptr, glm::value_ptr(snapValue));
716
717 glm::vec3 rotate;
718 ImGuizmo::DecomposeMatrixToComponents(transform, glm::value_ptr(state->endTranslation), glm::value_ptr(rotate), glm::value_ptr(state->endScale));
719
720 if (!ImGuizmo::IsUsing()) {
721 state->wasUsingGizmo = false;
722 return;
723 }
724
725 if (!state->wasUsingGizmo) {
726 // First
727 state->wasUsingGizmo = true;
728 }
729
730 if (state->mode == EditingMode::Translate) {
731 glm::vec3 diff = state->endTranslation - state->startTranslation;
732 editor->apply<TranslateCommand>(state->selected, diff);
733 } else if (state->mode == EditingMode::Rotate) {
734 glm::vec3 angles = glm::radians(rotate);
735 state->endRotation = glm::quat(angles);
736 glm::quat diff = state->endRotation * glm::inverse(state->startRotation);
737 editor->apply<RotateCommand>(state->selected, diff);
738 } else if (state->mode == EditingMode::Scale) {
739 glm::vec3 diff = state->endScale - state->startScale;
740 editor->apply<ScaleCommand>(state->selected, diff);
741 }
742 }
743}
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
T * getComponent() const
Get a pointer to the first component implementing the given type in the entity.
Definition: Entity.h:35
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
void showText(const char *format,...) const
Show the same type of label as ImGui::Text, but with the added ability to highlight and copy text....
Definition: Editor.cpp:1831
Field definition describing a single data member of a data structure.
Definition: Field.h:68
const Name & getName() const
Get the name of the field.
Definition: Field.h:136
const T * get() const
Retrieve an attribute of the given type from storage, if present.
Definition: Field.h:194
TypeId getTypeId() const
Get the type id of the field.
Definition: Field.h:140
FieldValueType * getPtr(void *container) const
Get a pointer to this field on the given container.
Definition: Field.h:112
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 Enumerator * getEnumerator(const Name &name) const
Get a pointer to the enumerator with the given name.
Definition: Type.cpp:145
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
std::string to_string() const
String conversion method.
Definition: StringView.cpp:9
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
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