1183 lines
40 KiB
C++
1183 lines
40 KiB
C++
// ImGuiTexInspect, a texture inspector widget for dear imgui
|
|
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] INCLUDES
|
|
//-------------------------------------------------------------------------
|
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
#include "imgui_tex_inspect.h"
|
|
#include "imgui_tex_inspect_internal.h"
|
|
|
|
#include "imgui.h"
|
|
#include "imgui_internal.h"
|
|
|
|
#if defined(_MSC_VER)
|
|
#pragma warning(disable : 4996) // 'sprintf' considered unsafe
|
|
#endif
|
|
|
|
namespace ImGuiTexInspect
|
|
{
|
|
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] FORWARD DECLARATIONS
|
|
//-------------------------------------------------------------------------
|
|
void UpdateShaderOptions(Inspector *inspector);
|
|
void InspectorDrawCallback(const ImDrawList *parent_list, const ImDrawCmd *cmd);
|
|
bool GetVisibleTexelRegionAndGetData(Inspector *inspector, ImVec2 &texelTL, ImVec2 &texelBR);
|
|
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] GLOBAL STATE
|
|
//-------------------------------------------------------------------------
|
|
|
|
// Input mapping structure, default values listed in the comments.
|
|
struct InputMap
|
|
{
|
|
ImGuiMouseButton PanButton; // LMB enables panning when held
|
|
InputMap();
|
|
};
|
|
|
|
InputMap::InputMap()
|
|
{
|
|
PanButton = ImGuiMouseButton_Left;
|
|
}
|
|
|
|
// Settings configured via SetNextPanelOptions etc.
|
|
struct NextPanelSettings
|
|
{
|
|
InspectorFlags ToSet = 0;
|
|
InspectorFlags ToClear = 0;
|
|
};
|
|
|
|
// Main context / configuration structure for imgui_tex_inspect
|
|
struct Context
|
|
{
|
|
InputMap Input; // Input mapping config
|
|
ImGuiStorage Inspectors; // All the inspectors we've seen
|
|
Inspector * CurrentInspector; // Inspector currently being processed
|
|
NextPanelSettings NextPanelOptions; // Options configured for next inspector panel
|
|
float ZoomRate = 1.3f; // How fast mouse wheel affects zoom
|
|
float DefaultPanelHeight = 600; // Height of panel in pixels
|
|
float DefaultInitialPanelWidth = 600; // Only applies when window first appears
|
|
int MaxAnnotations = 1000; // Limit number of texel annotations for performance
|
|
};
|
|
|
|
Context *GContext = nullptr;
|
|
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] USER FUNCTIONS
|
|
//-------------------------------------------------------------------------
|
|
|
|
void Init()
|
|
{
|
|
// Nothing to do here. But there might be in a later version. So client code should still call it!
|
|
}
|
|
|
|
void Shutdown()
|
|
{
|
|
// Nothing to do here. But there might be in a later version. So client code should still call it!
|
|
}
|
|
|
|
Context *CreateContext()
|
|
{
|
|
GContext = IM_NEW(Context);
|
|
SetCurrentContext(GContext);
|
|
return GContext;
|
|
}
|
|
|
|
void DestroyContext(Context *ctx)
|
|
{
|
|
if (ctx == NULL)
|
|
{
|
|
ctx = GContext;
|
|
}
|
|
|
|
if (ctx == GContext)
|
|
{
|
|
GContext = NULL;
|
|
}
|
|
|
|
for (ImGuiStorage::ImGuiStoragePair &pair : ctx->Inspectors.Data)
|
|
{
|
|
Inspector *inspector = (Inspector *)pair.val_p;
|
|
if (inspector)
|
|
{
|
|
IM_DELETE(inspector);
|
|
}
|
|
}
|
|
|
|
IM_DELETE(ctx);
|
|
}
|
|
|
|
|
|
void SetCurrentContext(Context *context)
|
|
{
|
|
ImGuiTexInspect::GContext = context;
|
|
}
|
|
|
|
void SetNextPanelFlags(InspectorFlags setFlags, InspectorFlags clearFlags)
|
|
{
|
|
SetFlag(GContext->NextPanelOptions.ToSet, setFlags);
|
|
SetFlag(GContext->NextPanelOptions.ToClear, clearFlags);
|
|
}
|
|
|
|
bool BeginInspectorPanel(const char *title, ImTextureID texture, ImVec2 textureSize, InspectorFlags flags,
|
|
SizeIncludingBorder sizeIncludingBorder)
|
|
{
|
|
const int borderWidth = 0;
|
|
// Unpack size param. It's in the SizeIncludingBorder structure just to make sure users know what they're requesting
|
|
ImVec2 size = sizeIncludingBorder.Size;
|
|
|
|
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
|
|
|
Context *ctx = GContext;
|
|
|
|
const ImGuiID ID = window->GetID(title);
|
|
const ImGuiIO &IO = ImGui::GetIO();
|
|
|
|
// Create or find inspector
|
|
bool justCreated = GetByKey(ctx, ID) == NULL;
|
|
ctx->CurrentInspector = GetOrAddByKey(ctx, ID);
|
|
Inspector *inspector = ctx->CurrentInspector;
|
|
justCreated |= !inspector->Initialized;
|
|
|
|
// Cache the basics
|
|
inspector->ID = ID;
|
|
inspector->Texture = texture;
|
|
inspector->TextureSize = textureSize;
|
|
inspector->Initialized = true;
|
|
|
|
// Handle incoming flags. We keep special track of the
|
|
// newly set flags because somethings only take effect
|
|
// the first time the flag is set.
|
|
InspectorFlags newlySetFlags = ctx->NextPanelOptions.ToSet;
|
|
if (justCreated)
|
|
{
|
|
SetFlag(newlySetFlags, flags);
|
|
inspector->MaxAnnotatedTexels = ctx->MaxAnnotations;
|
|
}
|
|
SetFlag(inspector->Flags, newlySetFlags);
|
|
ClearFlag(inspector->Flags, ctx->NextPanelOptions.ToClear);
|
|
ClearFlag(newlySetFlags, ctx->NextPanelOptions.ToClear);
|
|
ctx->NextPanelOptions = NextPanelSettings();
|
|
|
|
// Calculate panel size
|
|
ImVec2 contentRegionAvail = ImGui::GetContentRegionAvail();
|
|
|
|
ImVec2 panelSize;
|
|
// A size value of zero indicates we should use defaults
|
|
if (justCreated)
|
|
{
|
|
panelSize = {size.x == 0 ? ImMax(ctx->DefaultInitialPanelWidth, contentRegionAvail.x) : size.x,
|
|
size.y == 0 ? ctx->DefaultPanelHeight : size.y};
|
|
}
|
|
else
|
|
{
|
|
panelSize = {size.x == 0 ? contentRegionAvail.x : size.x, size.y == 0 ? ctx->DefaultPanelHeight : size.y};
|
|
}
|
|
|
|
inspector->PanelSize = panelSize;
|
|
ImVec2 availablePanelSize = panelSize - ImVec2(borderWidth, borderWidth) * 2;
|
|
|
|
{
|
|
// Possibly update scale
|
|
float newScale = -1;
|
|
|
|
if (HasFlag(newlySetFlags, InspectorFlags_FillVertical))
|
|
{
|
|
newScale = availablePanelSize.y / textureSize.y;
|
|
}
|
|
else if (HasFlag(newlySetFlags, InspectorFlags_FillHorizontal))
|
|
{
|
|
newScale = availablePanelSize.x / textureSize.x;
|
|
}
|
|
else if (justCreated)
|
|
{
|
|
newScale = 1;
|
|
}
|
|
|
|
if (newScale != -1)
|
|
{
|
|
inspector->Scale = ImVec2(newScale, newScale);
|
|
SetPanPos(inspector, ImVec2(0.5f, 0.5f));
|
|
}
|
|
}
|
|
|
|
RoundPanPos(inspector);
|
|
|
|
ImVec2 textureSizePixels = inspector->Scale * textureSize; // Size whole texture would appear on screen
|
|
ImVec2 viewSizeUV = availablePanelSize / textureSizePixels; // Cropped size in terms of UV
|
|
ImVec2 uv0 = inspector->PanPos - viewSizeUV * 0.5;
|
|
ImVec2 uv1 = inspector->PanPos + viewSizeUV * 0.5;
|
|
|
|
ImVec2 drawImageOffset{borderWidth, borderWidth};
|
|
ImVec2 viewSize = availablePanelSize;
|
|
|
|
if ((inspector->Flags & InspectorFlags_ShowWrap) == 0)
|
|
{
|
|
/* Don't crop the texture to UV [0,1] range. What you see outside this
|
|
* range will depend on API and texture properties */
|
|
if (textureSizePixels.x < availablePanelSize.x)
|
|
{
|
|
// Not big enough to horizontally fill view
|
|
viewSize.x = ImFloor(textureSizePixels.x);
|
|
drawImageOffset.x += ImFloor((availablePanelSize.x - textureSizePixels.x) / 2);
|
|
uv0.x = 0;
|
|
uv1.x = 1;
|
|
viewSizeUV.x = 1;
|
|
inspector->PanPos.x = 0.5f;
|
|
}
|
|
if (textureSizePixels.y < availablePanelSize.y)
|
|
{
|
|
// Not big enough to vertically fill view
|
|
viewSize.y = ImFloor(textureSizePixels.y);
|
|
drawImageOffset.y += ImFloor((availablePanelSize.y - textureSizePixels.y) / 2);
|
|
uv0.y = 0;
|
|
uv1.y = 1;
|
|
viewSizeUV.y = 1;
|
|
inspector->PanPos.y = 0.5;
|
|
}
|
|
}
|
|
|
|
if (HasFlag(flags,InspectorFlags_FlipX))
|
|
{
|
|
ImSwap(uv0.x, uv1.x);
|
|
viewSizeUV.x *= -1;
|
|
}
|
|
|
|
if (HasFlag(flags,InspectorFlags_FlipY))
|
|
{
|
|
ImSwap(uv0.y, uv1.y);
|
|
viewSizeUV.y *= -1;
|
|
}
|
|
|
|
inspector->ViewSize = viewSize;
|
|
inspector->ViewSizeUV = viewSizeUV;
|
|
|
|
/* We use mouse scroll to zoom so we don't want scroll to propagate to
|
|
* parent window. For this to happen we must NOT set
|
|
* ImGuiWindowFlags_NoScrollWithMouse. This seems strange but it's the way
|
|
* ImGui works. Also we must ensure the ScrollMax.y is not zero for the
|
|
* child window. */
|
|
if (ImGui::BeginChild(title, panelSize, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove))
|
|
{
|
|
// See comment above
|
|
ImGui::GetCurrentWindow()->ScrollMax.y = 1.0f;
|
|
|
|
// Callback for using our own image shader
|
|
ImGui::GetWindowDrawList()->AddCallback(InspectorDrawCallback, inspector);
|
|
|
|
// Keep track of size of area that we draw for borders later
|
|
inspector->PanelTopLeftPixel = ImGui::GetCursorScreenPos();
|
|
ImGui::SetCursorPos(ImGui::GetCursorPos() + drawImageOffset);
|
|
inspector->ViewTopLeftPixel = ImGui::GetCursorScreenPos();
|
|
|
|
UpdateShaderOptions(inspector);
|
|
inspector->CachedShaderOptions = inspector->ActiveShaderOptions;
|
|
ImGui::Image(texture, viewSize, uv0, uv1);
|
|
ImGui::GetWindowDrawList()->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
|
|
|
|
/* Matrices for going back and forth between texel coordinates in the
|
|
* texture and screen coordinates based on where texture is drawn.
|
|
* Useful for annotations and mouse hover etc. */
|
|
inspector->TexelsToPixels = GetTexelsToPixels(inspector->ViewTopLeftPixel, viewSize, uv0, viewSizeUV, inspector->TextureSize);
|
|
inspector->PixelsToTexels = inspector->TexelsToPixels.Inverse();
|
|
|
|
ImVec2 mousePos = ImGui::GetMousePos();
|
|
ImVec2 mousePosTexel = inspector->PixelsToTexels * mousePos;
|
|
ImVec2 mouseUV = mousePosTexel / textureSize;
|
|
mousePosTexel.x = Modulus(mousePosTexel.x, textureSize.x);
|
|
mousePosTexel.y = Modulus(mousePosTexel.y, textureSize.y);
|
|
|
|
if (ImGui::IsItemHovered() && (inspector->Flags & ImGuiTexInspect::InspectorFlags_NoTooltip) == 0)
|
|
{
|
|
// Show a tooltip for currently hovered texel
|
|
ImVec2 texelTL;
|
|
ImVec2 texelBR;
|
|
if (GetVisibleTexelRegionAndGetData(inspector, texelTL, texelBR))
|
|
{
|
|
ImVec4 color = GetTexel(&inspector->Buffer, (int)mousePosTexel.x, (int)mousePosTexel.y);
|
|
|
|
char buffer[128];
|
|
sprintf(buffer, "UV: (%.5f, %.5f)\nTexel: (%d, %d)", mouseUV.x, mouseUV.y, (int)mousePosTexel.x, (int)mousePosTexel.y);
|
|
|
|
ImGui::ColorTooltip(buffer, &color.x, 0);
|
|
}
|
|
}
|
|
|
|
bool hovered = ImGui::IsWindowHovered();
|
|
|
|
{ //DRAGGING
|
|
|
|
// start drag
|
|
if (!inspector->IsDragging && hovered && IO.MouseClicked[ctx->Input.PanButton])
|
|
{
|
|
inspector->IsDragging = true;
|
|
}
|
|
// carry on dragging
|
|
else if (inspector->IsDragging)
|
|
{
|
|
ImVec2 uvDelta = IO.MouseDelta * viewSizeUV / viewSize;
|
|
inspector->PanPos -= uvDelta;
|
|
RoundPanPos(inspector);
|
|
}
|
|
|
|
// end drag
|
|
if (inspector->IsDragging && (IO.MouseReleased[ctx->Input.PanButton] || !IO.MouseDown[ctx->Input.PanButton]))
|
|
{
|
|
inspector->IsDragging = false;
|
|
}
|
|
}
|
|
|
|
// ZOOM
|
|
if (hovered && IO.MouseWheel != 0)
|
|
{
|
|
float zoomRate = ctx->ZoomRate;
|
|
float scale = inspector->Scale.y;
|
|
float prevScale = scale;
|
|
|
|
bool keepTexelSizeRegular = scale > inspector->MinimumGridSize && !HasFlag(inspector->Flags, InspectorFlags_NoGrid);
|
|
if (IO.MouseWheel > 0)
|
|
{
|
|
scale *= zoomRate;
|
|
if (keepTexelSizeRegular)
|
|
{
|
|
// It looks nicer when all the grid cells are the same size
|
|
// so keep scale integer when zoomed in
|
|
scale = ImCeil(scale);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
scale /= zoomRate;
|
|
if (keepTexelSizeRegular)
|
|
{
|
|
// See comment above. We're doing a floor this time to make
|
|
// sure the scale always changes when scrolling
|
|
scale = ImFloorSigned(scale);
|
|
}
|
|
}
|
|
/* To make it easy to get back to 1:1 size we ensure that we stop
|
|
* here without going straight past it*/
|
|
if ((prevScale < 1 && scale > 1) || (prevScale > 1 && scale < 1))
|
|
{
|
|
scale = 1;
|
|
}
|
|
SetScale(inspector, ImVec2(inspector->PixelAspectRatio * scale, scale));
|
|
SetPanPos(inspector, inspector->PanPos + (mouseUV - inspector->PanPos) * (1 - prevScale / scale));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool BeginInspectorPanel(const char *name, ImTextureID texture, ImVec2 textureSize, InspectorFlags flags)
|
|
{
|
|
return BeginInspectorPanel(name, texture, textureSize, flags, SizeIncludingBorder{{0, 0}});
|
|
}
|
|
|
|
bool BeginInspectorPanel(const char *name, ImTextureID texture, ImVec2 textureSize, InspectorFlags flags, SizeExcludingBorder size)
|
|
{
|
|
// Correct the size to include the border, but preserve 0 which has a special meaning
|
|
return BeginInspectorPanel(name, texture, textureSize, flags,
|
|
SizeIncludingBorder{ImVec2{size.size.x == 0 ? 0 : size.size.x + 2,
|
|
size.size.y == 0 ? 0 : size.size.y + 2}});
|
|
}
|
|
|
|
void EndInspectorPanel()
|
|
{
|
|
const ImU32 innerBorderColour = 0xFFFFFFFF;
|
|
const ImU32 outerBorderColour = 0x00000000;
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
|
|
// Draw out border around whole inspector panel
|
|
ImGui::GetWindowDrawList()->AddRect(inspector->PanelTopLeftPixel, inspector->PanelTopLeftPixel + inspector->PanelSize,
|
|
outerBorderColour);
|
|
|
|
// Draw innder border around texture. If zoomed in this will completely cover the outer border
|
|
ImGui::GetWindowDrawList()->AddRect(inspector->ViewTopLeftPixel - ImVec2(1, 1),
|
|
inspector->ViewTopLeftPixel + inspector->ViewSize + ImVec2(1, 1), innerBorderColour);
|
|
|
|
ImGui::EndChild();
|
|
|
|
// We set this back to false every frame in case the texture is dynamic
|
|
if (!HasFlag(inspector->Flags, InspectorFlags_NoAutoReadTexture))
|
|
{
|
|
inspector->HaveCurrentTexelData = false;
|
|
}
|
|
}
|
|
|
|
void ReleaseInspectorData(ImGuiID ID)
|
|
{
|
|
Inspector *inspector = GetByKey(GContext, ID);
|
|
|
|
if (inspector == NULL)
|
|
return;
|
|
|
|
if (inspector->DataBuffer)
|
|
{
|
|
IM_FREE(inspector->DataBuffer);
|
|
inspector->DataBuffer = NULL;
|
|
inspector->DataBufferSize = 0;
|
|
}
|
|
|
|
/* In a later version we will remove inspector from the inspector table
|
|
* altogether. For now we reset the whole inspector structure to prevent
|
|
* clients relying on persisted data.
|
|
*/
|
|
*inspector = Inspector();
|
|
}
|
|
|
|
|
|
ImGuiID CurrentInspector_GetID()
|
|
{
|
|
return GContext->CurrentInspector->ID;
|
|
}
|
|
|
|
void CurrentInspector_SetColorMatrix(const float (&matrix)[16], const float (&colorOffset)[4])
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions;
|
|
memcpy(shaderOptions->ColorTransform, matrix, sizeof(matrix));
|
|
memcpy(shaderOptions->ColorOffset, colorOffset, sizeof(colorOffset));
|
|
}
|
|
|
|
void CurrentInspector_ResetColorMatrix()
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions;
|
|
shaderOptions->ResetColorTransform();
|
|
}
|
|
|
|
void CurrentInspector_SetAlphaMode(InspectorAlphaMode mode)
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions;
|
|
|
|
inspector->AlphaMode = mode;
|
|
|
|
switch (mode)
|
|
{
|
|
case InspectorAlphaMode_Black:
|
|
shaderOptions->BackgroundColor = ImVec4(0, 0, 0, 1);
|
|
shaderOptions->DisableFinalAlpha = 1;
|
|
shaderOptions->PremultiplyAlpha = 1;
|
|
break;
|
|
case InspectorAlphaMode_White:
|
|
shaderOptions->BackgroundColor = ImVec4(1, 1, 1, 1);
|
|
shaderOptions->DisableFinalAlpha = 1;
|
|
shaderOptions->PremultiplyAlpha = 1;
|
|
break;
|
|
case InspectorAlphaMode_ImGui:
|
|
shaderOptions->BackgroundColor = ImVec4(0, 0, 0, 0);
|
|
shaderOptions->DisableFinalAlpha = 0;
|
|
shaderOptions->PremultiplyAlpha = 0;
|
|
break;
|
|
case InspectorAlphaMode_CustomColor:
|
|
shaderOptions->BackgroundColor = inspector->CustomBackgroundColor;
|
|
shaderOptions->DisableFinalAlpha = 1;
|
|
shaderOptions->PremultiplyAlpha = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CurrentInspector_SetFlags(InspectorFlags toSet, InspectorFlags toClear)
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
SetFlag(inspector->Flags, toSet);
|
|
ClearFlag(inspector->Flags, toClear);
|
|
}
|
|
|
|
void CurrentInspector_SetGridColor(ImU32 color)
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
float alpha = inspector->ActiveShaderOptions.GridColor.w;
|
|
inspector->ActiveShaderOptions.GridColor = ImColor(color);
|
|
inspector->ActiveShaderOptions.GridColor.w = alpha;
|
|
}
|
|
|
|
void CurrentInspector_SetMaxAnnotations(int maxAnnotations)
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
inspector->MaxAnnotatedTexels = maxAnnotations;
|
|
}
|
|
|
|
void CurrentInspector_InvalidateTextureCache()
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
inspector->HaveCurrentTexelData = false;
|
|
}
|
|
|
|
void CurrentInspector_SetCustomBackgroundColor(ImVec4 color)
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
inspector->CustomBackgroundColor = color;
|
|
if (inspector->AlphaMode == InspectorAlphaMode_CustomColor)
|
|
{
|
|
inspector->ActiveShaderOptions.BackgroundColor = color;
|
|
}
|
|
}
|
|
|
|
void CurrentInspector_SetCustomBackgroundColor(ImU32 color)
|
|
{
|
|
CurrentInspector_SetCustomBackgroundColor(ImGui::ColorConvertU32ToFloat4(color));
|
|
}
|
|
|
|
void DrawColorMatrixEditor()
|
|
{
|
|
const char *colorVectorNames[] = {"R", "G", "B", "A", "1"};
|
|
const char *finalColorVectorNames[] = {"R'", "G'", "B'", "A'"};
|
|
const float dragSpeed = 0.02f;
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions;
|
|
|
|
// Left hand side of equation. The final color vector which is the actual drawn color
|
|
TextVector("FinalColorVector", finalColorVectorNames, IM_ARRAYSIZE(finalColorVectorNames));
|
|
|
|
ImGui::SameLine();
|
|
ImGui::TextUnformatted("=");
|
|
ImGui::SameLine();
|
|
|
|
// Right hand side of the equation: the Matrix. This is the editable part
|
|
ImGui::BeginGroup();
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
ImGui::PushID(i);
|
|
for (int j = 0; j < 4; ++j)
|
|
{
|
|
ImGui::PushID(j);
|
|
ImGui::SetNextItemWidth(50);
|
|
ImGui::DragFloat("##f", &shaderOptions->ColorTransform[j * 4 + i], dragSpeed);
|
|
ImGui::PopID();
|
|
ImGui::SameLine();
|
|
}
|
|
ImGui::SetNextItemWidth(50);
|
|
ImGui::DragFloat("##offset", &shaderOptions->ColorOffset[i], dragSpeed);
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::EndGroup();
|
|
ImGui::SameLine();
|
|
ImGui::TextUnformatted("*");
|
|
ImGui::SameLine();
|
|
|
|
// Right hand side of equation. The input vector, the source color of the texel.
|
|
TextVector("ColorVector", colorVectorNames, IM_ARRAYSIZE(colorVectorNames));
|
|
}
|
|
|
|
void DrawGridEditor()
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
|
|
ImGui::BeginGroup();
|
|
bool gridEnabled = !HasFlag(inspector->Flags, InspectorFlags_NoGrid);
|
|
if (ImGui::Checkbox("Grid", &gridEnabled))
|
|
{
|
|
if (gridEnabled)
|
|
{
|
|
CurrentInspector_ClearFlags(InspectorFlags_NoGrid);
|
|
}
|
|
else
|
|
{
|
|
CurrentInspector_SetFlags(InspectorFlags_NoGrid);
|
|
}
|
|
}
|
|
if (gridEnabled)
|
|
{
|
|
ImGui::SameLine();
|
|
ImGui::ColorEdit3("Grid Color", (float *)&inspector->ActiveShaderOptions.GridColor, ImGuiColorEditFlags_NoInputs);
|
|
}
|
|
|
|
ImGui::EndGroup();
|
|
}
|
|
|
|
void DrawColorChannelSelector()
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions;
|
|
|
|
ImGuiStorage *storage = ImGui::GetStateStorage();
|
|
const ImGuiID greyScaleID = ImGui::GetID("greyScale");
|
|
|
|
bool greyScale = storage->GetBool(greyScaleID, false);
|
|
|
|
bool red = shaderOptions->ColorTransform[0] > 0;
|
|
bool green = shaderOptions->ColorTransform[5] > 0;
|
|
bool blue = shaderOptions->ColorTransform[10] > 0;
|
|
|
|
bool changed = false;
|
|
|
|
// In greyScale made we draw the red, green, blue checkboxes as disabled
|
|
if (greyScale)
|
|
{
|
|
PushDisabled();
|
|
}
|
|
ImGui::BeginGroup();
|
|
changed |= ImGui::Checkbox("Red", &red);
|
|
changed |= ImGui::Checkbox("Green", &green);
|
|
changed |= ImGui::Checkbox("Blue", &blue);
|
|
ImGui::EndGroup();
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (greyScale)
|
|
{
|
|
PopDisabled();
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
// Overwrite the color transform matrix with one based on the settings
|
|
shaderOptions->ResetColorTransform();
|
|
shaderOptions->ColorTransform[0] = red ? 1.0f : 0.0f;
|
|
shaderOptions->ColorTransform[5] = green ? 1.0f : 0.0f;
|
|
shaderOptions->ColorTransform[10] = blue ? 1.0f : 0.0f;
|
|
}
|
|
|
|
ImGui::BeginGroup();
|
|
if (ImGui::Checkbox("Grey", &greyScale))
|
|
{
|
|
shaderOptions->ResetColorTransform();
|
|
storage->SetBool(greyScaleID, greyScale);
|
|
if (greyScale)
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
shaderOptions->ColorTransform[i * 4 + j] = 0.333f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::EndGroup();
|
|
}
|
|
|
|
void DrawAlphaModeSelector()
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
|
|
const char *alphaModes[] = {"ImGui Background", "Black", "White", "Custom Color"};
|
|
|
|
ImGui::SetNextItemWidth(200);
|
|
|
|
InspectorAlphaMode currentAlphaMode = inspector->AlphaMode;
|
|
|
|
ImGui::Combo("Alpha Modes", (int *)¤tAlphaMode, alphaModes, IM_ARRAYSIZE(alphaModes));
|
|
|
|
CurrentInspector_SetAlphaMode(currentAlphaMode);
|
|
|
|
if (inspector->AlphaMode == InspectorAlphaMode_CustomColor)
|
|
{
|
|
ImVec4 backgroundColor = inspector->CustomBackgroundColor;
|
|
if (ImGui::ColorEdit3("Background Color", (float *)&backgroundColor, 0))
|
|
{
|
|
CurrentInspector_SetCustomBackgroundColor(backgroundColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetZoomRate(float rate)
|
|
{
|
|
GContext->ZoomRate = rate;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] Life Cycle
|
|
//-------------------------------------------------------------------------
|
|
|
|
Inspector::~Inspector()
|
|
{
|
|
if (DataBuffer)
|
|
{
|
|
IM_FREE(DataBuffer);
|
|
}
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] Scaling and Panning
|
|
//-------------------------------------------------------------------------
|
|
void RoundPanPos(Inspector *inspector)
|
|
{
|
|
if ((inspector->Flags & InspectorFlags_ShowWrap) > 0)
|
|
{
|
|
/* PanPos is the point in the center of the current view. Allow the
|
|
* user to pan anywhere as long as the view center is inside the
|
|
* texture.*/
|
|
inspector->PanPos = ImClamp(inspector->PanPos, ImVec2(0, 0), ImVec2(1, 1));
|
|
}
|
|
else
|
|
{
|
|
/* When ShowWrap mode is disabled the limits are a bit more strict. We
|
|
* try to keep it so that the user cannot pan past the edge of the
|
|
* texture at all.*/
|
|
ImVec2 absViewSizeUV = Abs(inspector->ViewSizeUV);
|
|
inspector->PanPos = ImMax(inspector->PanPos - absViewSizeUV / 2, ImVec2(0, 0)) + absViewSizeUV / 2;
|
|
inspector->PanPos = ImMin(inspector->PanPos + absViewSizeUV / 2, ImVec2(1, 1)) - absViewSizeUV / 2;
|
|
}
|
|
|
|
/* If inspector->scale is 1 then we should ensure that pixels are aligned
|
|
* with texel centers to get pixel-perfect texture rendering*/
|
|
ImVec2 topLeftSubTexel = inspector->PanPos * inspector->Scale * inspector->TextureSize - inspector->ViewSize * 0.5f;
|
|
|
|
if (inspector->Scale.x >= 1)
|
|
{
|
|
topLeftSubTexel.x = Round(topLeftSubTexel.x);
|
|
}
|
|
if (inspector->Scale.y >= 1)
|
|
{
|
|
topLeftSubTexel.y = Round(topLeftSubTexel.y);
|
|
}
|
|
inspector->PanPos = (topLeftSubTexel + inspector->ViewSize * 0.5f) / (inspector->Scale * inspector->TextureSize);
|
|
}
|
|
|
|
void SetPanPos(Inspector *inspector, ImVec2 pos)
|
|
{
|
|
inspector->PanPos = pos;
|
|
RoundPanPos(inspector);
|
|
}
|
|
|
|
void SetScale(Inspector *inspector, ImVec2 scale)
|
|
{
|
|
scale = ImClamp(scale, inspector->ScaleMin, inspector->ScaleMax);
|
|
|
|
inspector->ViewSizeUV *= inspector->Scale / scale;
|
|
|
|
inspector->Scale = scale;
|
|
|
|
// Only force nearest sampling if zoomed in
|
|
inspector->ActiveShaderOptions.ForceNearestSampling =
|
|
(inspector->Scale.x > 1.0f || inspector->Scale.y > 1.0f) && !HasFlag(inspector->Flags, InspectorFlags_NoForceFilterNearest);
|
|
inspector->ActiveShaderOptions.GridWidth = ImVec2(1.0f / inspector->Scale.x, 1.0f / inspector->Scale.y);
|
|
}
|
|
|
|
void SetScale(Inspector *inspector, float scaleY)
|
|
{
|
|
SetScale(inspector, ImVec2(scaleY * inspector->PixelAspectRatio, scaleY));
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] INSPECTOR MAP
|
|
//-------------------------------------------------------------------------
|
|
|
|
Inspector *GetByKey(const Context *ctx, ImGuiID key)
|
|
{
|
|
return (Inspector *)ctx->Inspectors.GetVoidPtr(key);
|
|
}
|
|
|
|
Inspector *GetOrAddByKey(Context *ctx, ImGuiID key)
|
|
{
|
|
Inspector *inspector = GetByKey(ctx, key);
|
|
if (inspector)
|
|
{
|
|
return inspector;
|
|
}
|
|
else
|
|
{
|
|
inspector = IM_NEW(Inspector);
|
|
ctx->Inspectors.SetVoidPtr(key, inspector);
|
|
return inspector;
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] TextureConversion class
|
|
//-------------------------------------------------------------------------
|
|
|
|
void ShaderOptions::ResetColorTransform()
|
|
{
|
|
memset(ColorTransform, 0, sizeof(ColorTransform));
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
ColorTransform[i * 4 + i] = 1;
|
|
}
|
|
}
|
|
|
|
ShaderOptions::ShaderOptions()
|
|
{
|
|
ResetColorTransform();
|
|
memset(ColorOffset, 0, sizeof(ColorOffset));
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] UI and CONFIG
|
|
//-------------------------------------------------------------------------
|
|
|
|
void UpdateShaderOptions(Inspector *inspector)
|
|
{
|
|
if (HasFlag(inspector->Flags, InspectorFlags_NoGrid) == false && inspector->Scale.y > inspector->MinimumGridSize)
|
|
{
|
|
// Enable grid in shader
|
|
inspector->ActiveShaderOptions.GridColor.w = 1;
|
|
SetScale(inspector, Round(inspector->Scale.y));
|
|
}
|
|
else
|
|
{
|
|
// Disable grid in shader
|
|
inspector->ActiveShaderOptions.GridColor.w = 0;
|
|
}
|
|
|
|
inspector->ActiveShaderOptions.ForceNearestSampling =
|
|
(inspector->Scale.x > 1.0f || inspector->Scale.y > 1.0f) && !HasFlag(inspector->Flags, InspectorFlags_NoForceFilterNearest);
|
|
}
|
|
|
|
// Draws a single column ImGui table with one row for each provided string
|
|
void TextVector(const char *title, const char *const *strings, int stringCount)
|
|
{
|
|
ImGui::BeginGroup();
|
|
ImGui::SetNextItemWidth(50);
|
|
if (ImGui::BeginTable(title, 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX))
|
|
{
|
|
for (int i = 0; i < stringCount; ++i)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::SetNextItemWidth(50);
|
|
ImGui::Text("%s", strings[i]);
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
ImGui::EndGroup();
|
|
}
|
|
|
|
const ImGuiCol disabledUIColorIds[] = {ImGuiCol_FrameBg,
|
|
ImGuiCol_FrameBgActive,
|
|
ImGuiCol_FrameBgHovered,
|
|
ImGuiCol_Text,
|
|
ImGuiCol_CheckMark};
|
|
|
|
// Push disabled style for ImGui elements
|
|
void PushDisabled()
|
|
{
|
|
for (ImGuiCol colorId : disabledUIColorIds)
|
|
{
|
|
ImVec4 color = ImGui::GetStyleColorVec4(colorId);
|
|
color = color * ImVec4(0.5f, 0.5f, 0.5f, 0.5f);
|
|
ImGui::PushStyleColor(colorId, color);
|
|
}
|
|
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
|
}
|
|
|
|
// Pop disabled style for ImGui elements
|
|
void PopDisabled()
|
|
{
|
|
for (ImGuiCol colorId : disabledUIColorIds)
|
|
{
|
|
(void)colorId;
|
|
ImGui::PopStyleColor();
|
|
}
|
|
ImGui::PopItemFlag();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] Rendering & Buffer Management
|
|
//-------------------------------------------------------------------------
|
|
|
|
void InspectorDrawCallback(const ImDrawList *parent_list, const ImDrawCmd *cmd)
|
|
{
|
|
// Forward call to API-specific backend
|
|
Inspector *inspector = (Inspector *)cmd->UserCallbackData;
|
|
BackEnd_SetShader(parent_list, cmd, inspector);
|
|
}
|
|
|
|
// Calculate a transform to convert from texel coordinates to screen pixel coordinates
|
|
Transform2D GetTexelsToPixels(ImVec2 screenTopLeft, ImVec2 screenViewSize, ImVec2 uvTopLeft, ImVec2 uvViewSize, ImVec2 textureSize)
|
|
{
|
|
ImVec2 uvToPixel = screenViewSize / uvViewSize;
|
|
|
|
Transform2D transform;
|
|
transform.Scale = uvToPixel / textureSize;
|
|
transform.Translate.x = screenTopLeft.x - uvTopLeft.x * uvToPixel.x;
|
|
transform.Translate.y = screenTopLeft.y - uvTopLeft.y * uvToPixel.y;
|
|
return transform;
|
|
}
|
|
|
|
/* Fills in the AnnotationsDesc structure which provides all necessary
|
|
* information for code which draw annoations. Returns false if no annoations
|
|
* should be drawn. The maxAnnotatedTexels argument provides a way to override
|
|
* the default maxAnnotatedTexels.
|
|
*/
|
|
bool GetAnnotationDesc(AnnotationsDesc *ad, ImU64 maxAnnotatedTexels)
|
|
{
|
|
Inspector *inspector = GContext->CurrentInspector;
|
|
|
|
if (maxAnnotatedTexels == 0)
|
|
{
|
|
maxAnnotatedTexels = inspector->MaxAnnotatedTexels;
|
|
}
|
|
if (maxAnnotatedTexels != 0)
|
|
{
|
|
/* Check if we would draw too many annotations. This is to avoid poor
|
|
* frame rate when too zoomed out. Increase MaxAnnotatedTexels if you
|
|
* want to draw more annotations. Note that we don't use texelTL &
|
|
* texelBR to get total visible texels as this would cause flickering
|
|
* while panning as the exact number of visible texels changes.
|
|
*/
|
|
|
|
ImVec2 screenViewSizeTexels = Abs(inspector->PixelsToTexels.Scale) * inspector->ViewSize;
|
|
ImU64 approxVisibleTexelCount = (ImU64)screenViewSizeTexels.x * (ImU64)screenViewSizeTexels.y;
|
|
if (approxVisibleTexelCount > maxAnnotatedTexels)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// texelTL & texelBL will describe the currently visible texel region
|
|
ImVec2 texelTL;
|
|
ImVec2 texelBR;
|
|
|
|
if (GetVisibleTexelRegionAndGetData(inspector, texelTL, texelBR))
|
|
{
|
|
ad->Buffer= inspector->Buffer;
|
|
ad->DrawList = ImGui::GetWindowDrawList();
|
|
ad->TexelsToPixels = inspector->TexelsToPixels;
|
|
ad->TexelTopLeft = texelTL;
|
|
ad->TexelViewSize = texelBR - texelTL;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Calculates currently visible region of texture (which is returned in texelTL
|
|
* and texelBR) then also actually ensure that that data is in memory. Returns
|
|
* false if fetching data failed.
|
|
*/
|
|
bool GetVisibleTexelRegionAndGetData(Inspector *inspector, ImVec2 &texelTL, ImVec2 &texelBR)
|
|
{
|
|
/* Figure out which texels correspond to the top left and bottom right
|
|
* corners of the texture view. The plus + ImVec2(1,1) is because we
|
|
* want to draw partially visible texels on the bottom and right edges.
|
|
*/
|
|
texelTL = ImFloor(inspector->PixelsToTexels * inspector->ViewTopLeftPixel);
|
|
texelBR = ImFloor(inspector->PixelsToTexels * (inspector->ViewTopLeftPixel + inspector->ViewSize));
|
|
|
|
if (texelTL.x > texelBR.x)
|
|
{
|
|
ImSwap(texelTL.x, texelBR.x);
|
|
}
|
|
if (texelTL.y > texelBR.y)
|
|
{
|
|
ImSwap(texelTL.y, texelBR.y);
|
|
}
|
|
|
|
/* Add ImVec2(1,1) because we want to draw partially visible texels on the
|
|
* bottom and right edges.*/
|
|
texelBR += ImVec2(1,1);
|
|
|
|
texelTL = ImClamp(texelTL, ImVec2(0, 0), inspector->TextureSize);
|
|
texelBR = ImClamp(texelBR, ImVec2(0, 0), inspector->TextureSize);
|
|
|
|
if (inspector->HaveCurrentTexelData)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Now request pixel data for this region from backend
|
|
|
|
ImVec2 texelViewSize = texelBR - texelTL;
|
|
|
|
if (ImMin(texelViewSize.x, texelViewSize.y) > 0)
|
|
{
|
|
if (BackEnd_GetData(inspector, inspector->Texture, (int)texelTL.x, (int)texelTL.y, (int)texelViewSize.x, (int)texelViewSize.y,
|
|
&inspector->Buffer))
|
|
{
|
|
inspector->HaveCurrentTexelData = true;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* This is a function the backends can use to allocate a buffer for storing
|
|
* texture texel data. The buffer is owned by the inpsector so the backend
|
|
* code doesn't need to worry about freeing it.
|
|
*/
|
|
ImU8 *GetBuffer(Inspector *inspector, size_t bytes)
|
|
{
|
|
if (inspector->DataBufferSize < bytes || inspector->DataBuffer == nullptr)
|
|
{
|
|
// We need to allocate a buffer
|
|
|
|
if (inspector->DataBuffer)
|
|
{
|
|
IM_FREE(inspector->DataBuffer);
|
|
}
|
|
|
|
// Allocate slightly more than we need to avoid reallocating
|
|
// very frequently in the case that size is increasing.
|
|
size_t size = bytes * 5 / 4;
|
|
inspector->DataBuffer = (ImU8 *)IM_ALLOC(size);
|
|
inspector->DataBufferSize = size;
|
|
}
|
|
|
|
return inspector->DataBuffer;
|
|
}
|
|
|
|
ImVec4 GetTexel(const BufferDesc *bd, int x, int y)
|
|
{
|
|
if (x < bd->StartX || x >= bd->StartX + bd->Width || y < bd->StartY || y >= bd->StartY + bd->Height)
|
|
{
|
|
// Outside the range of data in the buffer.
|
|
return ImVec4();
|
|
}
|
|
// Calculate position in array
|
|
size_t offset = ((size_t)bd->LineStride * (y - bd->StartY) + bd->Stride * (x - bd->StartX));
|
|
if (bd->Data_float)
|
|
{
|
|
const float *texel = bd->Data_float + offset;
|
|
// It's possible our buffer doesn't have all 4 channels so fill gaps in with zeros
|
|
return ImVec4( texel[bd->Red],
|
|
bd->ChannelCount >= 2 ? texel[bd->Green] : 0,
|
|
bd->ChannelCount >= 3 ? texel[bd->Blue] : 0,
|
|
bd->ChannelCount >= 4 ? texel[bd->Alpha] : 0);
|
|
}
|
|
else if (bd->Data_uint8_t)
|
|
{
|
|
const ImU8 *texel = bd->Data_uint8_t + offset;
|
|
// It's possible our buffer doesn't have all 4 channels so fill gaps in with zeros.
|
|
// Also map from [0,255] to [0,1]
|
|
return ImVec4( (float)texel[bd->Red] / 255.0f,
|
|
bd->ChannelCount >= 2 ? (float)texel[bd->Green] / 255.0f : 0,
|
|
bd->ChannelCount >= 3 ? (float)texel[bd->Blue] / 255.0f : 0,
|
|
bd->ChannelCount >= 4 ? (float)texel[bd->Alpha] / 255.0f : 0);
|
|
}
|
|
else
|
|
{
|
|
return ImVec4();
|
|
}
|
|
}
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] Annotations
|
|
//-------------------------------------------------------------------------
|
|
|
|
ValueText::ValueText(Format format)
|
|
{
|
|
/* The ValueText annotation draws a string inside each texel displaying the
|
|
* values of each channel. We now select a format string based on the enum
|
|
* parameter*/
|
|
switch (format)
|
|
{
|
|
case Format::HexString:
|
|
TextFormatString = "#%02X%02X%02X%02X";
|
|
TextColumnCount = 9;
|
|
TextRowCount = 1;
|
|
FormatAsFloats = false;
|
|
break;
|
|
case Format::BytesHex:
|
|
TextFormatString = "R:#%02X\nG:#%02X\nB:#%02X\nA:#%02X";
|
|
TextColumnCount = 5;
|
|
TextRowCount = 4;
|
|
FormatAsFloats = false;
|
|
break;
|
|
case Format::BytesDec:
|
|
TextFormatString = "R:%3d\nG:%3d\nB:%3d\nA:%3d";
|
|
TextColumnCount = 5;
|
|
TextRowCount = 4;
|
|
FormatAsFloats = false;
|
|
break;
|
|
case Format::Floats:
|
|
TextFormatString = "%5.3f\n%5.3f\n%5.3f\n%5.3f";
|
|
TextColumnCount = 5;
|
|
TextRowCount = 4;
|
|
FormatAsFloats = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ValueText::DrawAnnotation(ImDrawList *drawList, ImVec2 texel, Transform2D texelsToPixels, ImVec4 value)
|
|
{
|
|
char buffer[64];
|
|
|
|
float fontHeight = ImGui::GetFontSize();
|
|
float fontWidth = fontHeight / 2; /* WARNING this is a hack that gets a constant
|
|
* character width from half the height. This work for the default font but
|
|
* won't work on other fonts which may even not be monospace.*/
|
|
|
|
// Calculate size of text and check if it fits
|
|
ImVec2 textSize = ImVec2((float)TextColumnCount * fontWidth, (float)TextRowCount * fontHeight);
|
|
|
|
if (textSize.x > ImAbs(texelsToPixels.Scale.x) || textSize.y > ImAbs(texelsToPixels.Scale.y))
|
|
{
|
|
// Not enough room in texel to fit the text. Don't draw it.
|
|
return;
|
|
}
|
|
|
|
/* Choose black or white text based on how bright the texel. I.e. don't
|
|
* draw black text on a dark background or vice versa. */
|
|
float brightness = (value.x + value.y + value.z) * value.w / 3;
|
|
ImU32 lineColor = brightness > 0.5 ? 0xFF000000 : 0xFFFFFFFF;
|
|
|
|
if (FormatAsFloats)
|
|
{
|
|
sprintf(buffer, TextFormatString, value.x, value.y, value.z, value.w);
|
|
}
|
|
else
|
|
{
|
|
/* Map [0,1] to [0,255]. Also clamp it since input data wasn't
|
|
* necessarily in [0,1] range. */
|
|
ImU8 r = (ImU8)Round((ImClamp(value.x, 0.0f, 1.0f)) * 255);
|
|
ImU8 g = (ImU8)Round((ImClamp(value.y, 0.0f, 1.0f)) * 255);
|
|
ImU8 b = (ImU8)Round((ImClamp(value.z, 0.0f, 1.0f)) * 255);
|
|
ImU8 a = (ImU8)Round((ImClamp(value.w, 0.0f, 1.0f)) * 255);
|
|
sprintf(buffer, TextFormatString, r, g, b, a);
|
|
}
|
|
|
|
// Add text to drawlist!
|
|
ImVec2 pixelCenter = texelsToPixels * texel;
|
|
drawList->AddText(pixelCenter - textSize * 0.5f, lineColor, buffer);
|
|
}
|
|
|
|
Arrow::Arrow(int xVectorIndex, int yVectorIndex, ImVec2 lineScale)
|
|
: VectorIndex_x(xVectorIndex), VectorIndex_y(yVectorIndex), LineScale(lineScale)
|
|
{
|
|
}
|
|
|
|
Arrow &Arrow::UsePreset(Preset preset)
|
|
{
|
|
switch (preset)
|
|
{
|
|
default:
|
|
case Preset::NormalMap:
|
|
VectorIndex_x = 0;
|
|
VectorIndex_y = 1;
|
|
LineScale = ImVec2(1, -1);
|
|
ZeroPoint = ImVec2(128.0f / 255, 128.0f / 255);
|
|
break;
|
|
case Preset::NormalizedFloat:
|
|
VectorIndex_x = 0;
|
|
VectorIndex_y = 1;
|
|
LineScale = ImVec2(0.5f, -0.5f);
|
|
ZeroPoint = ImVec2(0, 0);
|
|
break;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void Arrow::DrawAnnotation(ImDrawList *drawList, ImVec2 texel, Transform2D texelsToPixels, ImVec4 value)
|
|
{
|
|
const float arrowHeadScale = 0.35f;
|
|
const ImU32 lineColor = 0xFFFFFFFF;
|
|
float *vecPtr = &value.x;
|
|
|
|
// Draw an arrow!
|
|
ImVec2 lineDir = (ImVec2(vecPtr[VectorIndex_x], vecPtr[VectorIndex_y]) - ZeroPoint) * LineScale;
|
|
ImVec2 lineStart = texel;
|
|
ImVec2 lineEnd = lineStart + lineDir;
|
|
|
|
ImVec2 arrowHead1 = ImVec2(lineDir.x - lineDir.y, lineDir.x + lineDir.y) * -arrowHeadScale;
|
|
ImVec2 arrowHead2 = ImVec2(lineDir.x + lineDir.y, -lineDir.x + lineDir.y) * -arrowHeadScale;
|
|
|
|
DrawAnnotationLine(drawList, lineStart, lineEnd, texelsToPixels, lineColor);
|
|
DrawAnnotationLine(drawList, lineEnd, lineEnd + arrowHead1, texelsToPixels, lineColor);
|
|
DrawAnnotationLine(drawList, lineEnd, lineEnd + arrowHead2, texelsToPixels, lineColor);
|
|
}
|
|
|
|
void DrawAnnotationLine(ImDrawList *drawList, ImVec2 fromTexel, ImVec2 toTexel, Transform2D texelsToPixels, ImU32 color)
|
|
{
|
|
ImVec2 lineFrom = texelsToPixels * fromTexel;
|
|
ImVec2 lineTo = texelsToPixels * toTexel;
|
|
drawList->AddLine(lineFrom, lineTo, color, 1.0f);
|
|
}
|
|
} // namespace ImGuiTexInspect
|