Cogs.Core
InspectorGuiHelper.cpp
1#include "InspectorGuiHelper.h"
2
3#include "Context.h"
4
5#include "Components/Core/SceneComponent.h"
6
7#include "Renderer/Renderer.h"
8
9#include "Resources/TextureManager.h"
10#include "Renderer/RenderTexture.h"
11
12#include "Resources/Mesh.h"
13#include "Resources/MeshManager.h"
14#include "Resources/MaterialOptions.h"
15#include "Resources/MaterialInstance.h"
16#include "Resources/Model.h"
17#include "Resources/Animation.h"
18
19#include "Types.h"
20
21#include <glm/vec2.hpp>
22#include <glm/vec3.hpp>
23#include <glm/vec4.hpp>
24#include <glm/mat4x4.hpp>
25#include <glm/gtc/type_ptr.hpp>
26
27#include <algorithm>
28#include <cctype>
29#include <cstdio>
30
31namespace
32{
33 bool lc_eq(unsigned char l, unsigned char r)
34 {
35 return (std::tolower(l) == std::tolower(r));
36 }
37}
38
39std::string Cogs::Core::guiWindowTitle;
40std::vector<size_t> Cogs::Core::guiIdsStack;
41
42enum struct EMeshDataType : uint8_t
43{
44 Integer = 0,
45 UnsignedInt,
46 Float,
47 Normalized,
48 UnsignedNorm,
49 Other
50};
51
53{
54 EMeshDataType type = EMeshDataType::Other;
55 uint16_t size = 0;
56 uint16_t offset = 0;
57 std::string header;
58 ImU32 color = IM_COL32(255, 255, 255, 255);
59};
60
61
62// Per https://www.khronos.org/opengl/wiki/Normalized_Integer
63template <typename T>
64constexpr float normalizeNumber(T value) {
65 return std::max(static_cast<float>(value) / static_cast<float>(std::numeric_limits<T>::max()), -1.0f);
66}
67
68void Cogs::Core::guiBegin(const std::string & title, bool * show)
69{
70 ImGui::Begin(title.c_str(), show, 0);
71 guiWindowTitle = title;
72
73 assert(guiIdsStack.empty());
74}
75
76void Cogs::Core::guiEnd()
77{
78 ImGui::End();
79 guiIdsStack.clear();
80}
81
82std::string Cogs::Core::getUniqueHeader(const std::string & header)
83{
84 if (guiIdsStack.empty()) {
85 guiIdsStack.push_back(0);
86 } else {
87 guiIdsStack.back()++;
88 }
89
90 assert(!guiWindowTitle.empty());
91 std::string id = guiWindowTitle;
92 for (auto top : guiIdsStack) {
93 if (!id.empty()) {
94 id += "_";
95 }
96 id += std::to_string(top);
97 }
98
99 std::string res;
100 res.reserve(header.size() + 2 + id.size());
101 res.append(header);
102 res.append("##");
103 res.append(id);
104
105 return res;
106}
107
108
109void Cogs::Core::appendBytesize(std::string& out, size_t byteSize)
110{
111 std::array<char, 32> tmp;
112
113 constexpr size_t k = static_cast<size_t>(1024);
114 constexpr size_t m = k * k;
115 constexpr size_t g = k * k * k;
116
117 if (byteSize < k) {
118 out.append(std::to_string(byteSize));
119 out.append("b");
120 }
121 else if (byteSize < m) {
122 if (int n = snprintf(tmp.data(), tmp.size(), "%.1f", double(byteSize) / double(k)); 0 <= n && size_t(n) < tmp.size()) {
123 out.append(std::string_view(tmp.data(), n));
124 }
125 out.append("kb");
126 }
127 else if (byteSize < g) {
128 if (int n = snprintf(tmp.data(), tmp.size(), "%.1f", double(byteSize) / double(m)); 0 <= n && size_t(n) < tmp.size()) {
129 out.append(std::string_view(tmp.data(), n));
130 }
131 out.append("mb");
132 }
133 else {
134 if (int n = snprintf(tmp.data(), tmp.size(), "%.1f", double(byteSize) / double(g)); 0 <= n && size_t(n) < tmp.size()) {
135 out.append(std::string_view(tmp.data(), n));
136 }
137 out.append("gb");
138 }
139}
140
141
142bool Cogs::Core::containsInvariantCase(std::string_view str, std::string_view substr)
143{
144 return std::search(str.begin(), str.end(), substr.begin(), substr.end(), lc_eq) != str.end();
145}
146
147
148bool Cogs::Core::findHierarchyWithMatch(const Cogs::ComponentModel::Entity& entity, std::string_view entityNamePattern, std::string_view componentNamePattern)
149{
150 if (entityNamePattern.empty() && componentNamePattern.empty()) {
151 return true;
152 }
153
154 bool showEntity = true;
155 if (!entityNamePattern.empty() && !containsInvariantCase(entity.getName(), entityNamePattern)) {
156 showEntity = false;
157 }
158
159 if (showEntity && !componentNamePattern.empty()) {
160 showEntity = false;
161 for (const Cogs::ComponentModel::ComponentHandle& c : entity.getComponents()) {
162 Cogs::ComponentModel::Component* component = c.resolve();
163 if (component) {
164 showEntity = showEntity || containsInvariantCase(component->getType().getName().c_str(), componentNamePattern);
165 }
166 }
167 }
168
169 if (!showEntity) {
170 // Search children for matching. Must show parent to navigate to child
172 if (sceneComponent) {
173 for (const Cogs::Core::EntityPtr& child : sceneComponent->children) {
174 showEntity = findHierarchyWithMatch(*child, entityNamePattern, componentNamePattern);
175 if (showEntity) {
176 break;
177 }
178 }
179 }
180 }
181 return showEntity;
182}
183
184void Cogs::Core::showTexture(const Context * context, Renderer * renderer, Texture * texture, bool showTitle)
185{
186 if (showTitle) {
187 if (!texture->getName().empty()) {
188 ImGui::Text("Name: %.*s", StringViewFormat(texture->getName()));
189 }
190 else if (texture->getId() != NoResourceId) {
191 ImGui::Text("Id: %d", texture->getId());
192 }
193 }
194
195 ImGui::Text("Refs: %u", texture->referenceCount());
196 ImGui::SameLine();
197 ImGui::Text("Target: %s", getResourceDimensionsName(texture->description.target));
198 ImGui::SameLine();
199 switch(texture->description.target){
200 case ResourceDimensions::Unknown:
201 ImGui::Text("Size: Unknown");
202 break;
203 case ResourceDimensions::Buffer:
204 ImGui::Text("Size: Buffer");
205 break;
206 case ResourceDimensions::Texture1D:
207 ImGui::Text("Size: %d", texture->description.width);
208 break;
209 case ResourceDimensions::Texture1DArray:
210 ImGui::Text("Size: %d Layers %d", texture->description.width, texture->description.layers);
211 break;
212 case ResourceDimensions::Texture2D:
213 ImGui::Text("Size: %dx%d", texture->description.width, texture->description.height);
214 break;
215 case ResourceDimensions::Texture2DArray:
216 ImGui::Text("Size: %dx%d Layers %d", texture->description.width, texture->description.height, texture->description.layers);
217 break;
218 case ResourceDimensions::Texture2DMS:
219 ImGui::Text("Size: %dx%d Samples %d", texture->description.width, texture->description.height, texture->description.samples);
220 break;
221 case ResourceDimensions::Texture2DMSArray:
222 ImGui::Text("Size: %dx%d Layers %d Samples %d", texture->description.width, texture->description.height, texture->description.layers, texture->description.samples);
223 break;
224 case ResourceDimensions::Texture3D:
225 ImGui::Text("Size: %dx%dx%d", texture->description.width, texture->description.height, texture->description.depth);
226 break;
227 case ResourceDimensions::Texture3DArray:
228 ImGui::Text("Size: %dx%dx%d Layers %d", texture->description.width, texture->description.height, texture->description.depth, texture->description.layers);
229 break;
230 case ResourceDimensions::TextureCube:
231 ImGui::Text("Size: %dx%d Faces %d", texture->description.width, texture->description.height, texture->description.faces);
232 break;
233 case ResourceDimensions::TextureCubeArray:
234 ImGui::Text("Size: %dx%d Faces %d Layers %d", texture->description.width, texture->description.height, texture->description.faces, texture->description.layers);
235 break;
236 case ResourceDimensions::RenderBuffer:
237 ImGui::Text("Size: %dx%d", texture->description.width, texture->description.height);
238 break;
239 case ResourceDimensions::ResourceDimensions_Size:
240 break;
241 }
242 ImGui::SameLine();
243 ImGui::Text("Mips %d", texture->description.levels);
244 ImGui::SameLine();
245 auto tmp = getFormatInfo(texture->description.format);
246 if(tmp && tmp->name)
247 ImGui::Text("Format: %s", tmp->name);
248
249 RenderTexture * renderTexture = renderer->getRenderResources().getRenderTexture(context->textureManager->generateHandle(texture));
250
251 if (renderTexture && HandleIsValid(renderTexture->textureHandle)) {
252 float aspectRatio = 1;
253 if (texture->description.width > 0 && texture->description.height > 0) {
254 aspectRatio = static_cast<float>(texture->description.width) / static_cast<float>(texture->description.height);
255 }
256
257 auto size = ImVec2(256, 256);
258 if (aspectRatio < 1) {
259 size.x *= aspectRatio;
260 } else {
261 size.y /= aspectRatio;
262 }
263
264 uint64_t mode = GUI_MODE_TEX_TYPE_2D;
265 if (texture->description.target == ResourceDimensions::Texture2DMS) {
266 mode = GUI_MODE_TEX_TYPE_2DMS;
267 }
268 else if (texture->description.target == ResourceDimensions::Texture1DArray ||
269 texture->description.target == ResourceDimensions::Texture2DArray) {
270 mode = GUI_MODE_TEX_TYPE_ARRAY;
271 }
272 else if (texture->description.target == ResourceDimensions::TextureCube ||
273 texture->description.target == ResourceDimensions::TextureCubeArray) {
274 mode = GUI_MODE_TEX_TYPE_CUBE;
275 }
276
277 auto dl = ImGui::GetWindowDrawList();
278 dl->AddCallback(setGuiMode, reinterpret_cast<void*>(mode));
279 ImGui::Image(ImTextureID(renderTexture->textureHandle.handle), size);
280 ImGui::SameLine();
281 dl->AddCallback(setGuiMode, reinterpret_cast<void*>(mode | GUI_MODE_TEX_CHANNELS_ALPHA)); // show only alpha channel for the next texture
282 ImGui::Image(ImTextureID(renderTexture->textureHandle.handle), size);
283 dl->AddCallback(setGuiMode, reinterpret_cast<void*>(GUI_MODE_DEFAULT)); // restore the default texture rendering state
284 }
285}
286
287void Cogs::Core::showRenderTexture(const Context * /*context*/, Renderer * /*renderer*/, const RenderTexture * renderTexture, bool showTitle)
288{
289 auto texture = renderTexture->getResource();
290 float aspectRatio = 1;
291
292 if (texture) {
293 if (showTitle) {
294 if (!texture->getName().empty()) {
295 ImGui::Text("Name: %.*s", StringViewFormat(texture->getName()));
296 }
297 else if (texture->getId() != NoResourceId) {
298 ImGui::Text("Id: %d", texture->getId());
299 }
300 }
301
302 ImGui::Text("Refs: %u", texture->referenceCount());
303 ImGui::Text("Size: %d x %d", renderTexture->description.width, renderTexture->description.height);
304
305 if (renderTexture->description.width > 0 && renderTexture->description.height > 0) {
306 aspectRatio = static_cast<float>(renderTexture->description.width) / static_cast<float>(renderTexture->description.height);
307 }
308 }
309
310 if (renderTexture && HandleIsValid(renderTexture->textureHandle)) {
311 ImVec2 size(256, 256);
312 ImDrawList* dl = ImGui::GetWindowDrawList();
313
314 if (aspectRatio < 1) {
315 size.x *= aspectRatio;
316 }
317 else {
318 size.y /= aspectRatio;
319 }
320
321 if (renderTexture->description.target == ResourceDimensions::Texture2DMS) {
322 dl->AddCallback(setGuiMode, reinterpret_cast<void*>(GUI_MODE_TEX_TYPE_2DMS)); // Use the Texture2DMS slot for the next texture
323 ImGui::Image(ImTextureID(renderTexture->textureHandle.handle), size);
324 ImGui::SameLine();
325 dl->AddCallback(setGuiMode, reinterpret_cast<void*>(GUI_MODE_TEX_TYPE_2DMS | GUI_MODE_TEX_CHANNELS_ALPHA)); // Show only alpha channel for the next texture
326 ImGui::Image(ImTextureID(renderTexture->textureHandle.handle), size);
327 dl->AddCallback(setGuiMode, reinterpret_cast<void*>(GUI_MODE_DEFAULT)); // Restore the default texture rendering state
328 }
329 else {
330 ImGui::Image(ImTextureID(renderTexture->textureHandle.handle), size);
331 ImGui::SameLine();
332 dl->AddCallback(setGuiMode, reinterpret_cast<void*>(GUI_MODE_TEX_CHANNELS_ALPHA)); // Show only alpha channel for the next texture
333 ImGui::Image(ImTextureID(renderTexture->textureHandle.handle), size);
334 dl->AddCallback(setGuiMode, reinterpret_cast<void*>(GUI_MODE_DEFAULT)); // Restore the default texture rendering state
335 }
336 }
337}
338
339bool Cogs::Core::showMatrix(const std::string & header, glm::mat4& m)
340{
341 bool changed = false;
342 if (ImGui::TreeNode(header.c_str())) {
343 ScopedIndent si;
344
345 if (ImGui::DragFloat4("row_0", glm::value_ptr(m))) {
346 changed = true;
347 }
348 if (ImGui::DragFloat4("row_1", glm::value_ptr(m) + 4)) {
349 changed = true;
350 }
351 if (ImGui::DragFloat4("row_2", glm::value_ptr(m) + 8)) {
352 changed = true;
353 }
354 if (ImGui::DragFloat4("row_3", glm::value_ptr(m) + 12)) {
355 changed = true;
356 }
357
358 ImGui::TreePop();
359 }
360
361 return changed;
362}
363
364bool Cogs::Core::showArray3D(const std::string & header, std::span<glm::vec3> array)
365{
366 bool changed = false;
367
368 if (array.empty()) {
369 ImGui::Text("%s: Empty", header.c_str());
370 } else {
371 if (ImGui::CollapsingHeader(getUniqueHeader(std::string(header + " [size=" + std::to_string(array.size()) + "]")).c_str())) {
372 ScopedIndent si;
373
374 int i = 0;
375 for (auto& v : array) {
376 auto label = std::to_string(i++);
377
378 if (ImGui::DragFloat3(getUniqueHeader(label).c_str(), glm::value_ptr(v))) {
379 changed = true;
380 }
381 }
382 }
383 }
384
385 return changed;
386}
387
388void Cogs::Core::showMesh(const Context * /*context*/, Mesh * mesh, const std::string & header)
389{
390 ImGui::PushID(mesh);
391
392 if (ImGui::TreeNode(header.c_str())) {
393
394 if (ImGui::TreeNode("Bounding box")) {
395 ScopedIndent si;
396 bool changed = ImGui::DragFloat3("min", glm::value_ptr(mesh->boundingBox.min));
397 if (ImGui::DragFloat3("max", glm::value_ptr(mesh->boundingBox.max))) changed = true;
398 if (changed) {
399 mesh->setMeshFlag(MeshFlags::BoundingBoxSet);
400 }
401 ImGui::TreePop();
402 }
403
404 showEnum<Cogs::PrimitiveType::EPrimitiveType>("Primitive type", mesh->primitiveType);
405
406 ImGui::Text("Number of primitives: %d", mesh->getCount());
407
408 if (mesh->isIndexed()) {
409 if (mesh->hasIndexesU16()) {
410 showArray1D("IndexesU16", mesh->getIndexesU16());
411 }
412 else {
413 showArray1D("IndexesU32", mesh->getIndexes());
414 }
415 }
416
417 if (!mesh->streams.empty()) {
418 if (ImGui::TreeNode(("Streams [size=" + std::to_string(mesh->streams.size()) + "]").c_str())) {
419 ScopedIndent si;
420 int streamNum = 0;
421 for (DataStream& stream : mesh->streams)
422 {
423 std::string typeAsString;
424 switch (stream.type)
425 {
426 case VertexDataType::Positions:
427 typeAsString = "Positions";
428 break;
429 case VertexDataType::Normals:
430 typeAsString = "Normals";
431 break;
432 case VertexDataType::Tangents:
433 typeAsString = "Tangents";
434 break;
435 case VertexDataType::Bitangents:
436 typeAsString = "Bitangents";
437 break;
438 case VertexDataType::TexCoords0:
439 typeAsString = "TexCoords0";
440 break;
441 case VertexDataType::TexCoords1:
442 typeAsString = "TexCoords1";
443 break;
444 case VertexDataType::TexCoords2:
445 typeAsString = "TexCoords2";
446 break;
447 case VertexDataType::Colors0:
448 typeAsString = "Colors0";
449 break;
450 case VertexDataType::Colors1:
451 typeAsString = "Colors1";
452 break;
453 case VertexDataType::Colors2:
454 typeAsString = "Colors2";
455 break;
456 case VertexDataType::Colors3:
457 typeAsString = "Colors3";
458 break;
459 case VertexDataType::Interleaved0:
460 typeAsString = "Interleaved0";
461 break;
462 case VertexDataType::Interleaved1:
463 typeAsString = "Interleaved1";
464 break;
465 case VertexDataType::Indexes:
466 typeAsString = "Indexes";
467 break;
468 case VertexDataType::PoseIndexes:
469 typeAsString = "PoseIndexes";
470 break;
471 case VertexDataType::SubMeshes:
472 typeAsString = "SubMeshes";
473 break;
474 case VertexDataType::Reserved:
475 typeAsString = "Reserved";
476 break;
477 default:
478 break;
479 }
480
481 ImGui::PushID(&stream);
482
483 std::string stream_header;
484 stream_header.reserve(50);
485 stream_header.append("Stream ");
486 stream_header.append(!typeAsString.empty() ? typeAsString : std::to_string(streamNum++));
487 stream_header.append(" [elements=");
488 stream_header.append(std::to_string(stream.numElements));
489 stream_header.append(", stride=");
490 stream_header.append(std::to_string(stream.stride));
491 stream_header.append(", size=");
492 appendBytesize(stream_header, stream.size());
493 stream_header.append("]");
494
495 if (ImGui::TreeNode(stream_header.c_str())) {
496 ScopedIndent si2;
497
498 static constexpr std::array colors = { IM_COL32(255,255,128,255), IM_COL32(255,128,255,255), IM_COL32(128,255,255,255),
499 IM_COL32(255,200,200,255), IM_COL32(200,255,200,255), IM_COL32(200,200,255,255) };
500
501 // Show Vertex Elements information
502 const Cogs::VertexFormat* format = HandleIsValid(stream.format) ? Cogs::VertexFormats::getVertexFormat(stream.format) : nullptr;
503 if (format && ImGui::TreeNode(("Vertex Format Elements [size=" + std::to_string(format->elements.size()) + "]").c_str())) {
504 ScopedIndent si3;
505 int colorElement = 0;
506 for (uint16_t i = 0; i < format->elements.size(); ++i) {
507 const Cogs::VertexElement& element = format->elements[i];
508 const Cogs::FormatInfo* formatInfo = Cogs::getFormatInfo(element.format);
509
510 ImGui::PushStyleColor(ImGuiCol_Text, colors[colorElement]);
511 colorElement = (colorElement + 1) % colors.size();
512
513 std::string elementHeader;
514 elementHeader.reserve(127);
515 elementHeader += std::to_string(i);
516 elementHeader += ": [offset=";
517 elementHeader += std::to_string(element.offset);
518 elementHeader += ", size=";
519 elementHeader += std::to_string(formatInfo->blockSize);
520 elementHeader += "]";
521
522 if (ImGui::TreeNode(elementHeader.c_str())) {
523 ImGui::Text("DataFormat: %s", formatInfo->name);
524
525 std::string flagsStr;
526 const FormatFlags flags = formatInfo->flags;
527 if ((flags & FormatFlags::Alpha) != FormatFlags::None) { flagsStr += "Alpha "; }
528 if ((flags & FormatFlags::VertexFormat) != FormatFlags::None) { flagsStr += "VertexFormat "; }
529 if ((flags & FormatFlags::Depth) != FormatFlags::None) { flagsStr += "Depth "; }
530 if ((flags & FormatFlags::Typeless) != FormatFlags::None) { flagsStr += "Typeless "; }
531 if ((flags & FormatFlags::Integer) != FormatFlags::None) { flagsStr += "Integer "; }
532 if ((flags & FormatFlags::Unsigned) != FormatFlags::None) { flagsStr += "Unsigned "; }
533 ImGui::Text("Flags: %s", flagsStr.c_str());
534
535 ImGui::Text("Semantic: %.*s%u", StringViewFormat(Cogs::getElementSemanticName(element.semantic)), element.semanticIndex);
536 const char* inputType = element.inputType == InputType::VertexData ? "VertexData" : "InstanceData";
537 ImGui::Text("InputType: %s [step=%u]", inputType, element.instanceStep);
538
539 ImGui::TreePop();
540 }
541 ImGui::PopStyleColor();
542 }
543 ImGui::TreePop();
544 }
545
546 // Try to show formated Data for this stream
547 if (format && ImGui::TreeNode("Data")) {
548 std::vector<ColumnInfo> columns;
549 int colorIndex = 0;
550
551 // Assemble Columns info for all Vertex Elements
552 // Assume for now that all channels in one Vertex Element are of the same type and size
553 for (const Cogs::VertexElement& element : format->elements) {
554 const Cogs::FormatInfo* formatInfo = Cogs::getFormatInfo(element.format);
555 ColumnInfo column;
556 column.size = formatInfo->blockSize / formatInfo->elements;
557 column.color = colors[colorIndex];
558 colorIndex = (colorIndex + 1) % colors.size();
559
560 std::string prefix;
561 if (element.semantic == ElementSemantic::Position) { prefix = "V"; }
562 else if (element.semantic == ElementSemantic::Normal) { prefix = "N"; }
563 else if (element.semantic == ElementSemantic::Color) { prefix = "C"; }
564 else if (element.semantic == ElementSemantic::TextureCoordinate) { prefix = "T"; }
565 else if (element.semantic == ElementSemantic::Tangent) { prefix = "Tg"; }
566 else if (element.semantic == ElementSemantic::InstanceVector) { prefix = "IV"; }
567 else if (element.semantic == ElementSemantic::InstanceMatrix) { prefix = "IM"; }
568 else if (element.semantic == ElementSemantic::Semantic_Size) { prefix = "S"; }
569
570 std::string_view name(formatInfo->name);
571 if (name.find("UINT") != std::string_view::npos) {
572 column.type = EMeshDataType::UnsignedInt;
573 }
574 else if (name.find("SINT") != std::string_view::npos) {
575 column.type = EMeshDataType::Integer;
576 }
577 else if (name.find("FLOAT") != std::string_view::npos) {
578 column.type = EMeshDataType::Float;
579 }
580 else if (name.find("UNORM") != std::string_view::npos) {
581 column.type = EMeshDataType::UnsignedNorm;
582 }
583 else if (name.find("SNORM") != std::string_view::npos) {
584 column.type = EMeshDataType::Normalized;
585 }
586
587 for (uint16_t j = 0; j < formatInfo->elements; ++j) {
588 column.offset = element.offset + j * column.size;
589 // Assume for header that Vertex Elements are sequential (ascending offsets)
590 column.header = prefix + "." + std::to_string(j);
591 columns.push_back(column);
592 }
593 }
594
595 // Show formatted table for all data elements
596 const char* p = static_cast<const char*>(stream.data());
597 ImGui::Columns(static_cast<int>(columns.size() + 1));
598 // Headers row
599 ImGui::NextColumn();
600 for (const ColumnInfo& column : columns) {
601 ImGui::Text("%s", column.header.c_str());
602 ImGui::NextColumn();
603 }
604 // Data table, one row per Element
605 for (size_t k = 0; k < stream.numElements; k++) {
606 ImGui::Text("%zu", k);
607 ImGui::NextColumn();
608
609 for (const ColumnInfo& column: columns) {
610 const void* data = p + column.offset;
611 bool other = true;
612 ImGui::PushStyleColor(ImGuiCol_Text, column.color);
613
614 if (column.type == EMeshDataType::Float) {
615 if (column.size == 4) {
616 ImGui::Text("%.2f", *(static_cast<const float*>(data)));
617 other = false;
618 }
619 else if (column.size == 2) {
620 ImGui::Text("%.2f", glm::detail::toFloat32(*static_cast<const uint16_t*>(data)));
621 other = false;
622 }
623 }
624 else if (column.type == EMeshDataType::Integer) {
625 if (column.size == 1) {
626 ImGui::Text("%d", *static_cast<const int8_t*>(data));
627 other = false;
628 }
629 else if (column.size == 2) {
630 ImGui::Text("%d", *static_cast<const int16_t*>(data));
631 other = false;
632 }
633 else if (column.size == 4) {
634 ImGui::Text("%d", *static_cast<const int32_t*>(data));
635 other = false;
636 }
637 }
638 else if (column.type == EMeshDataType::UnsignedInt) {
639 if (column.size == 1) {
640 ImGui::Text("%d", *static_cast<const uint8_t*>(data));
641 other = false;
642 }
643 else if (column.size == 2) {
644 ImGui::Text("%d", *static_cast<const uint16_t*>(data));
645 other = false;
646 }
647 else if (column.size == 4) {
648 ImGui::Text("%d", *static_cast<const uint32_t*>(data));
649 other = false;
650 }
651 }
652 else if (column.type == EMeshDataType::Normalized) {
653 if (column.size == 1) {
654 ImGui::Text("%.2f", normalizeNumber<int8_t>(*static_cast<const int8_t*>(data)));
655 other = false;
656 }
657 else if (column.size == 2) {
658 ImGui::Text("%.2f", normalizeNumber<int16_t>(*static_cast<const int16_t*>(data)));
659 other = false;
660 }
661 else if (column.size == 4) {
662 ImGui::Text("%.2f", normalizeNumber<int32_t>(*static_cast<const int32_t*>(data)));
663 other = false;
664 }
665 }
666 else if (column.type == EMeshDataType::UnsignedNorm) {
667 if (column.size == 1) {
668 ImGui::Text("%.2f", normalizeNumber<uint8_t>(*static_cast<const uint8_t*>(data)));
669 other = false;
670 }
671 else if (column.size == 2) {
672 ImGui::Text("%.2f", normalizeNumber<uint16_t>(*static_cast<const uint16_t*>(data)));
673 other = false;
674 }
675 else if (column.size == 4) {
676 ImGui::Text("%.2f", normalizeNumber<uint32_t>(*static_cast<const uint32_t*>(data)));
677 other = false;
678 }
679 }
680 if (other) {
681 // Data Format that we cannot parse, for now
682 ImGui::Text("-");
683 }
684 ImGui::NextColumn();
685 ImGui::PopStyleColor();
686 }
687 p += stream.stride;
688 }
689 ImGui::Columns(1);
690
691 ImGui::TreePop();
692 }
693
694 // Use writable streams to allow changing stream data.
695 const size_t size = stream.size() / sizeof(float);
696 const size_t intSize = stream.size() / sizeof(int);
697 const std::span<float> data(static_cast<float*>(stream.data()), size);
698 const std::span<int> intData(static_cast<int*>(stream.data()), intSize);
699 bool streamChanged = false;
700
701 if(stream.format == VertexFormats::Pos2f)
702 streamChanged = showArray2D<float>("Data", data);
703 else if(stream.format == VertexFormats::Pos3f)
704 streamChanged = showArray3D<float>("Data", data);
705 else if(stream.format == VertexFormats::Pos4f)
706 streamChanged = showArray4D<float>("Data", data);
707 else if(stream.format == VertexFormats::Norm3f)
708 streamChanged = showArray3D<float>("Data", data);
709 else if(stream.format == VertexFormats::Tang3f)
710 streamChanged = showArray3D<float>("Data", data);
711 else if(stream.format == VertexFormats::Tex2f)
712 streamChanged = showArray2D<float>("Data", data);
713 else if(stream.format == VertexFormats::Color4f)
714 streamChanged = showArray4D<float>("Data", data);
715 else if(stream.format == VertexFormats::BoneIndex4i)
716 streamChanged = showArray4D<int>("Data", intData);
717 else if(stream.format == VertexFormats::BoneWeight4f)
718 streamChanged = showArray4D<float>("Data", data);
719 else {
720 // Unsupported format. Can be mixed: VertexFormats::Pos3fNorm3f etc.
721 }
722
723 if (streamChanged) {
724 mesh->markStreamChanged(stream.type);
725 }
726
727 ImGui::TreePop();
728 }
729
730 ImGui::PopID();
731 }
732
733 ImGui::TreePop();
734 }
735 } else {
736 ImGui::Text("Streams: Empty");
737 }
738
739 if (!mesh->getSubMeshes().empty()) {
740 if (ImGui::TreeNode(("Sub-meshes [size=" + std::to_string(mesh->getSubMeshes().size()) + "]").c_str())) {
741 ScopedIndent si;
742 int id = 0;
743 for (SubMesh& subMesh : mesh->getSubMeshes()) {
744 if (ImGui::TreeNode(("Sub-meshes " + std::to_string(id++)).c_str())) {
745 ScopedIndent si2;
746 ImGui::Text("Start index: %d", subMesh.startIndex);
747 ImGui::Text("Number of indexes: %d", subMesh.numIndexes);
748 showEnum<Cogs::PrimitiveType::EPrimitiveType>("Primitive type", subMesh.primitiveType);
749
750 ImGui::TreePop();
751 }
752 }
753
754 ImGui::TreePop();
755 }
756 } else {
757 ImGui::Text("Sub-meshes: Empty");
758 }
759
760 ImGui::TreePop();
761 }
762
763 ImGui::PopID();
764}
765
766void Cogs::Core::showMaterialOptions(const Context * /*context*/, MaterialOptions * options, const std::string & header)
767{
768 ImGui::PushID(options);
769
770 if (ImGui::TreeNode(header.c_str())) {
771 showEnum("Cull mode", options->cullMode);
772
773 ImGui::Checkbox("Depth write", &options->depthWriteEnabled);
774 ImGui::Checkbox("Depth test", &options->depthTestEnabled);
775 ImGui::Checkbox("Depth bias", &options->depthBiasEnabled);
776
777 if (ImGui::CollapsingHeader("Depth bias##DepthBiasHeader")) {
778 ImGui::DragFloat("Constant", &options->depthBias.constant);
779 ImGui::DragFloat("Slope", &options->depthBias.slope);
780 ImGui::DragFloat("Clamp", &options->depthBias.clamp);
781 }
782
783 showEnum("Depth func", options->depthFunc);
784 showEnum("Transparent", options->transparencyMode);
785 showEnum("Blend mode", options->blendMode);
786
787 ImGui::TreePop();
788 }
789
790 ImGui::PopID();
791}
792
793void Cogs::Core::showMaterialVariants(MaterialInstance* material)
794{
795 if (ImGui::TreeNode("Variants")) {
796 for (const ShaderVariantDefinition& v : material->material->definition.variants) {
797
798 size_t value = v.isShared ? v.defaultValue : material->variantSelectors[v.index].value;
799
800 switch (v.type) {
801 case ShaderVariantType::Enum:
802 {
803 std::vector<const char*> enumNames(v.values.size());
804
805 for (size_t i = 0; i < v.values.size(); ++i) {
806 enumNames[i] = v.values[i].key.c_str();
807 }
808
809 int currentIndex = static_cast<int>(value);
810 if (ImGui::Combo(v.name.c_str(), &currentIndex, enumNames.data(), static_cast<int>(enumNames.size()))) {
811 if (!v.isShared) material->setVariant(v.name, enumNames[currentIndex]);
812 }
813 }
814 break;
815
816 case ShaderVariantType::Bool:
817 if (bool boolean = value != 0; ImGui::Checkbox(v.name.c_str(), &boolean)) {
818 if (!v.isShared) material->setVariant(v.name, boolean);
819 }
820 break;
821
822 case ShaderVariantType::Int:
823 if (int integer = static_cast<int>(value); ImGui::InputInt(v.name.c_str(), &integer, 1)) {
824 if (!v.isShared) material->setVariant(v.name, integer);
825 }
826 break;
827
828 // Don't show: VertexStreamPosition0": { "type": "format", "value" : "BASE_MATERIAL_VERTEX_POSITION0",..
829 case ShaderVariantType::Format:
830 break;
831
832 default:
833 ImGui::Text("Unsupported: %s", v.name.c_str());
834 break;
835 }
836 }
837
838 ImGui::TreePop();
839 }
840}
841
842void Cogs::Core::showMaterialProperties(MaterialInstance* material)
843{
844 for (const MaterialProperty& prop : material->material->constantBuffers.variables) {
845
846 switch (prop.type) {
847 case MaterialDataType::Float:
848 {
849 float value = {};
850 material->getProperty(prop.key, &value, sizeof(value));
851 if (ImGui::DragFloat(prop.name.c_str(), &value)) {
852 material->setFloatProperty(prop.key, value);
853 }
854 }
855 break;
856
857 case MaterialDataType::Float2:
858 {
859 glm::vec2 value = {};
860 material->getProperty(prop.key, &value, sizeof(value));
861 if (ImGui::DragFloat2(prop.name.c_str(), glm::value_ptr(value))) {
862 material->setVec2Property(prop.key, value);
863 }
864 }
865 break;
866
867 case MaterialDataType::Float3:
868 {
869 glm::vec3 value = {};
870 material->getProperty(prop.key, &value, sizeof(value));
871 if ((prop.flags & MaterialPropertyFlags::sRGB) == MaterialPropertyFlags::sRGB) {
872 if (ImGui::ColorEdit3(prop.name.c_str(), glm::value_ptr(value))) {
873 material->setVec3Property(prop.key, value);
874 }
875 }
876 else {
877 if (ImGui::DragFloat3(prop.name.c_str(), glm::value_ptr(value))) {
878 material->setVec3Property(prop.key, value);
879 }
880 }
881 }
882 break;
883
884 case MaterialDataType::Float4:
885 {
886 glm::vec4 value = {};
887 material->getProperty(prop.key, &value, sizeof(value));
888 if ((prop.flags & MaterialPropertyFlags::sRGB) == MaterialPropertyFlags::sRGB) {
889 if (ImGui::ColorEdit4(prop.name.c_str(), glm::value_ptr(value))) {
890 material->setVec4Property(prop.key, value);
891 }
892 }
893 else {
894 if (ImGui::DragFloat4(prop.name.c_str(), glm::value_ptr(value))) {
895 material->setVec4Property(prop.key, value);
896 }
897 }
898 }
899 break;
900
901 case MaterialDataType::Float4x4:
902 {
903 glm::mat4 value = {};
904 material->getProperty(prop.key, &value, sizeof(value));
905 if (showMatrix(prop.name, value)) {
906 material->setMat4Property(prop.key, value);
907 }
908 }
909 break;
910
911 case MaterialDataType::Int:
912 {
913 int32_t value = {};
914 material->getProperty(prop.key, &value, sizeof(value));
915 if (ImGui::DragInt(prop.name.c_str(), &value, 1.0f, 0, 100)) {
916 material->setIntProperty(prop.key, value);
917 }
918 break;
919 }
920
921 case MaterialDataType::UInt:
922 {
923 int32_t value = {};
924 material->getProperty(prop.key, &value, sizeof(value));
925 if (ImGui::DragInt(prop.name.c_str(), &value, 1.0f, 0, 100)) {
926 material->setUIntProperty(prop.key, static_cast<uint32_t>(value));
927 }
928 }
929 break;
930
931 case MaterialDataType::Bool:
932 {
933 bool value = {};
934 material->getProperty(prop.key, &value, sizeof(value));
935 if (ImGui::Checkbox(prop.name.c_str(), &value)) {
936 material->setBoolProperty(prop.key, value);
937 }
938 }
939 break;
940
941 default:
942 ImGui::Text("Unsupported: %d", static_cast<int>(prop.type));
943 break;
944 }
945 }
946}
947
948void Cogs::Core::showMaterialInstance(const Context * context, MaterialInstance * material, const std::string & header)
949{
950 ImGui::PushID(material);
951
952 if (ImGui::TreeNodeEx(header.c_str(), ImGuiTreeNodeFlags_NoAutoOpenOnLog)) {
953 std::string flags;
954 flags += material->isBackdrop() ? "Backdrop " : "";
955 flags += material->hasTransparency() ? "Transparent " : "";
956 flags += material->isDefaultMaterial() ? "Default " : "";
957 flags = flags.size() ? "Flags: " + flags : "Flags: None";
958
959 ImGui::Text("%s", flags.c_str());
960
961 int permutationIndex = static_cast<int>(material->permutationIndex);
962 std::vector<const char*> permutations;
963
964 for (auto& permutation : material->material->definition.permutations) {
965 permutations.push_back(permutation.permutationName.c_str());
966 }
967
968 if (ImGui::Combo("Permutation", &permutationIndex, permutations.data(), static_cast<int>(permutations.size()))) {
969 material->setPermutation(permutations[permutationIndex]);
970 }
971
972 Cogs::Core::showMaterialVariants(material);
973
974 Cogs::Core::showMaterialOptions(context, &material->options, "Options");
975
976 Cogs::Core::showMaterialProperties(material);
977
978 if (!material->textureVariables.empty()) {
979 if (ImGui::CollapsingHeader(("Textures [size=" + std::to_string(material->textureVariables.size()) + "]").c_str())) {
980 ScopedIndent si;
981 for (VariableKey texVarKey = 0; texVarKey < material->textureVariables.size(); ++texVarKey) {
982 TextureValue& texVar = material->textureVariables[texVarKey];
983 const std::string& tex_header = texVar.property->name;
984
985 if (HandleIsValid(texVar.texture.handle)) {
986 Texture* texture = texVar.texture.handle.resolve();
987
988 if (ImGui::CollapsingHeader(tex_header.c_str())) {
989 ScopedIndent si2;
990 Cogs::Core::Renderer* renderer = dynamic_cast<Cogs::Core::Renderer*>(context->renderer);
991 showTexture(context, renderer, texture, true);
992
993 // Make copy of current state and change copy - then use methods to update MaterialInstance.
994 TextureWithSampler textureCopy = texVar.texture;
995 ImGui::PushItemWidth(ImGui::CalcTextSize("Mirror").x + ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetTextLineHeightWithSpacing());
996 if (showEnum("##sAddressMode", textureCopy.sMode)) {
997 material->setTextureAddressMode(texVarKey, textureCopy.sMode, textureCopy.tMode, textureCopy.uMode);
998 }
999 ImGui::SameLine();
1000 if (showEnum("##tAddressMode", textureCopy.tMode)) {
1001 material->setTextureAddressMode(texVarKey, textureCopy.sMode, textureCopy.tMode, textureCopy.uMode);
1002 }
1003 ImGui::SameLine();
1004 if (showEnum("##uAddressMode", textureCopy.uMode)) {
1005 material->setTextureAddressMode(texVarKey, textureCopy.sMode, textureCopy.tMode, textureCopy.uMode);
1006 }
1007 ImGui::PopItemWidth();
1008
1009 ImGui::SameLine();
1010 ImGui::SetNextItemWidth(ImGui::CalcTextSize("ComparisonMinMagMipLinear").x + ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetTextLineHeightWithSpacing());
1011 if (showEnum("##filterMode", textureCopy.filterMode)) {
1012 material->setTextureFilterMode(texVarKey, textureCopy.filterMode);
1013 }
1014 }
1015 }
1016 else {
1017 ImGui::Text("%s: Empty", tex_header.c_str());
1018 }
1019
1020 }
1021 }
1022 } else {
1023 ImGui::Text("Textures: Empty");
1024 }
1025
1026 ImGui::TreePop();
1027 }
1028
1029 ImGui::PopID();
1030}
1031
1032void Cogs::Core::showModel(const Context * context, Model * model, const std::string & header)
1033{
1034 ImGui::PushID(model);
1035
1036 if (ImGui::TreeNode(header.c_str())) {
1037 ScopedIndent si;
1038
1039 ImGui::Text("Loaded: %s", model->isLoaded() ? "true" : "false");
1040
1041 uint32_t maxProp = model->parts.empty() ? model->properties.size() : model->parts[0].firstProperty;
1042 if (uint32_t ix = model->properties.findProperty(0, maxProp, Strings::add("modelOrigin")); ix != PropertyStore::NoProperty) {
1043
1044 if (std::span<const double> view = model->properties.getDoubleArray(ix); view.size() == 3) {
1045 ImGui::Text("Model origin: [%.2f %.2f %.2f]", view[0], view[1], view[2]);
1046 }
1047 }
1048
1049
1050 if (ImGui::TreeNode("Parts", "Parts: %zd", model->parts.size())) {
1051 size_t index = 0;
1052 for (auto & part : model->parts) {
1053 ImGui::PushID(&part);
1054
1055 std::string name(model->getPartName(part));
1056
1057 std::string partName = name.empty() ? (std::string("Part ") + std::to_string(index)) : name;
1058
1059 if (ImGui::TreeNode(partName.c_str())) {
1060 if (part.meshIndex != uint32_t(-1)) {
1061 ImGui::Text("Mesh: %u", part.meshIndex);
1062 } else {
1063 ImGui::Text("Mesh: None");
1064 }
1065
1066 if (part.materialIndex != uint32_t(-1)) {
1067 ImGui::Text("Material: %u", part.materialIndex);
1068 } else {
1069 ImGui::Text("Material: None");
1070 }
1071
1072 ImGui::Text("StartIndex: %ud", part.startIndex);
1073 ImGui::Text("VertexCount: %ud", part.vertexCount);
1074
1075 // Allow editing xform.
1076 glm::mat4 matrix = model->getPartTransform(part);
1077 if (showMatrix("Transform", matrix)) {
1078 model->setPartTransform(part, matrix);
1079 }
1080
1081 ImGui::TreePop();
1082 }
1083
1084 ImGui::PopID();
1085 }
1086
1087 ImGui::TreePop();
1088 }
1089
1090 if (ImGui::TreeNode("Meshes", "Meshes: %zd", model->meshes.size())) {
1091 size_t index = 0;
1092 for (auto & mesh : model->meshes) {
1093 std::string meshName = mesh->getName().empty() ? (std::string("Mesh ") + std::to_string(index)) : std::string(mesh->getName());
1094 ++index;
1095
1096 showMesh(context, mesh.resolve(), meshName);
1097 }
1098
1099 ImGui::TreePop();
1100 }
1101
1102 if (ImGui::TreeNode("Materials", "Materials: %zd", model->materials.size())) {
1103 size_t index = 0;
1104 for (auto & material : model->materials) {
1105 std::string materialName = material->getName().empty() ? (std::string("Material ") + std::to_string(index)) : std::string(material->getName());
1106 ++index;
1107
1108 showMaterialInstance(context, material.resolve(), materialName);
1109 }
1110
1111 ImGui::TreePop();
1112 }
1113
1114 if (model->skeleton.bones.size()) {
1115 if (ImGui::TreeNode("Skeleton")) {
1116 if (ImGui::TreeNode("Bones")) {
1117 for (auto & bone : model->skeleton.bones) {
1118 if (ImGui::TreeNode(bone.name.c_str())) {
1119 ImGui::Text("Name: %s", bone.name.c_str());
1120 if (bone.parentBone < model->skeleton.bones.size()) {
1121 ImGui::Text("Parent: %zd (%s)", bone.parentBone, model->skeleton.bones[bone.parentBone].name.c_str());
1122 } else {
1123 ImGui::Text("Parent: None");
1124 }
1125
1126 ImGui::TreePop();
1127 }
1128 }
1129
1130 ImGui::TreePop();
1131 }
1132
1133 ImGui::TreePop();
1134 }
1135 }
1136
1137 if (model->animation) {
1138 if (ImGui::TreeNode("Animation")) {
1139 auto animation = model->animation.resolve();
1140
1141 if (ImGui::TreeNode("Clips")) {
1142 for (auto & clip : animation->clips) {
1143 if (ImGui::TreeNode(clip.name.c_str())) {
1144 ImGui::Text("Duration: %f", clip.duration);
1145 ImGui::Text("Resolution: %f", clip.resolution);
1146
1147 if (ImGui::TreeNode("Tracks")) {
1148 for (auto& track : clip.tracks) {
1149 std::string trackName = "Bone index " + std::to_string(track.boneIndex);
1150 if (ImGui::TreeNode(trackName.c_str())) {
1151 if (track.boneIndex < animation->skeleton.bones.size()) {
1152 ImGui::Text("Bone: %s", animation->skeleton.bones[track.boneIndex].name.c_str());
1153 }
1154 ImGui::Text("Keyframes: %zd", track.translations.size());
1155
1156 ImGui::TreePop();
1157 }
1158 }
1159
1160 ImGui::TreePop();
1161 }
1162
1163 ImGui::TreePop();
1164 }
1165 }
1166
1167 ImGui::TreePop();
1168 }
1169
1170 ImGui::TreePop();
1171 }
1172 }
1173
1174 ImGui::TreePop();
1175 }
1176
1177 ImGui::PopID();
1178}
Base class for Component instances.
Definition: Component.h:143
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
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
Core renderer system.
Definition: Renderer.h:28
Contains information on how the entity behaves in the scene.
std::vector< EntityPtr > children
Contains all child entities owned by this component.
constexpr const Name & getName() const
Get the unique name of the type.
Definition: Type.h:198
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
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
bool showEnum(const std::string &header, T &currentItem)
Show enumerator with edit option.
bool showArray1D(const std::string &header, std::span< T > array)
Handle to a Component instance.
Definition: Component.h:67
uint16_t elements
Number of channels in a data item.
Definition: DataFormat.h:259
uint8_t blockSize
Bytesize of one block of data.
Definition: DataFormat.h:257
const char * name
Name as a color format (using RGBA as channels).
Definition: DataFormat.h:260
FormatFlags flags
See FormatFlags.
Definition: DataFormat.h:262
const char * c_str() const
Gets the name as a null-terminated string.
Definition: Name.h:116
Vertex element structure used to describe a single data element in a vertex for the input assembler.
Definition: VertexFormat.h:38
InputType inputType
Input type of the element, vertex or instance data.
Definition: VertexFormat.h:43
DataFormat format
Format of the element.
Definition: VertexFormat.h:40
uint16_t offset
Offset in bytes from the vertex position in memory.
Definition: VertexFormat.h:39
uint16_t semanticIndex
Index for the semantic mapping.
Definition: VertexFormat.h:42
ElementSemantic semantic
Semantic mapping of the element (position, normal, etc...).
Definition: VertexFormat.h:41
uint16_t instanceStep
Instance step factor.
Definition: VertexFormat.h:44
Vertex format structure used to describe a single vertex for the input assembler.
Definition: VertexFormat.h:60
std::vector< VertexElement > elements
Vector containing all vertex elements of this format.
Definition: VertexFormat.h:62