Shadows: Added initial version of convex shape shadow code.

(+stripped out of original polygon generation demo, moved to imgui_dev)
This commit is contained in:
Ben Carter 2020-06-12 14:45:30 +09:00 committed by ocornut
parent f882102374
commit 542f1da1e2
4 changed files with 441 additions and 93 deletions

View File

@ -6832,7 +6832,7 @@ void ImGui::SetCurrentFont(ImFont* font)
g.DrawListSharedData.TexUvLines = atlas->TexUvLines;
g.DrawListSharedData.Font = g.Font;
g.DrawListSharedData.FontSize = g.FontSize;
g.DrawListSharedData.ShadowRectId = atlas->ShadowRectId;
g.DrawListSharedData.ShadowRectIds = &atlas->ShadowRectIds[0];
g.DrawListSharedData.ShadowRectUvs = &atlas->ShadowRectUvs[0];
}

19
imgui.h
View File

@ -2569,10 +2569,16 @@ struct ImDrawList
// Shadows primitives
// [BETA] API
// - Add a shadow for a rectangular object, with min-max giving the object extents, and offset shifting the shadow. Rounding parameters refer to the object itself, not the shadow.
// - In the vast majority of cases, filled shadows are unnecessary and wasteful. We still provide the primitives for consistency and flexibility.
// - The filled parameter causes the shadow "under" the object to be drawn as well. In the vast majority of cases, filled shadows are unnecessary and wasteful. We still provide the primitives for consistency and flexibility.
#define IMGUI_HAS_SHADOWS 1
IMGUI_API void AddShadowRect(const ImVec2& p_min, const ImVec2& p_max, float shadow_thickness, const ImVec2& offset, ImU32 col, float rounding = 0.0f, ImDrawCornerFlags rounding_corners = ImDrawCornerFlags_All);
IMGUI_API void AddShadowRectFilled(const ImVec2& p_min, const ImVec2& p_max, float shadow_thickness, const ImVec2& offset, ImU32 col);
IMGUI_API void AddShadowCircle(const ImVec2& center, float radius, float shadow_thickness, const ImVec2& offset, ImU32 col, int num_segments = 12); // Offset not currently supported
IMGUI_API void AddShadowCircleFilled(const ImVec2& center, float radius, float shadow_thickness, const ImVec2& offset, ImU32 col, int num_segments = 12); // Offset not currently supported
IMGUI_API void AddShadowNGon(const ImVec2& center, float radius, float shadow_thickness, const ImVec2& offset, ImU32 col, int num_segments); // Offset not currently supported
IMGUI_API void AddShadowNGonFilled(const ImVec2& center, float radius, float shadow_thickness, const ImVec2& offset, ImU32 col, int num_segments); // Offset not currently supported
IMGUI_API void AddShadowConvexPoly(const ImVec2* points, int num_points, float shadow_thickness, const ImVec2& offset, ImU32 col); // Offset not currently supported
IMGUI_API void AddShadowConvexPolyFilled(const ImVec2* points, int num_points, float shadow_thickness, const ImVec2& offset, ImU32 col); // Offset not currently supported
// Stateful path API, add points then finish with PathFillConvex() or PathStroke()
inline void PathClear() { _Path.Size = 0; }
@ -2666,8 +2672,11 @@ struct ImFontAtlasShadowTexConfig
bool TexBlur; // Do we want to Gaussian blur the shadow texture?
IMGUI_API ImFontAtlasShadowTexConfig();
int GetPadding() const { return 2; } // Number of pixels of padding to add to avoid sampling artifacts at the edges.
int CalcTexSize() const { return TexCornerSize + TexEdgeSize + GetPadding(); } // The size of the texture area required for the actual 2x2 shadow texture (after the redundant corners have been removed). Padding is required here to avoid sampling artifacts at the edge adjoining the removed corners.
int GetRectTexPadding() const { return 2; } // Number of pixels of padding to add to the rectangular texture to avoid sampling artifacts at the edges.
int CalcRectTexSize() const { return TexCornerSize + TexEdgeSize + GetRectTexPadding(); } // The size of the texture area required for the actual 2x2 rectangle shadow texture (after the redundant corners have been removed). Padding is required here to avoid sampling artifacts at the edge adjoining the removed corners. int CalcConvexTexWidth() const; // The width of the texture area required for the convex shape shadow texture.
int GetConvexTexPadding() const { return 8; } // Number of pixels of padding to add to the convex shape texture to avoid sampling artifacts at the edges. This also acts as padding for the expanded corner triangles.
int CalcConvexTexWidth() const; // The width of the texture area required for the convex shape shadow texture.
int CalcConvexTexHeight() const; // The height of the texture area required for the convex shape shadow texture.
};
struct ImFontConfig
@ -2859,8 +2868,8 @@ struct ImFontAtlas
int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines
// [Internal] Shadow data
int ShadowRectId; // ID of rect for shadow texture
ImVec4 ShadowRectUvs[9]; // UV coordinates for shadow texture.
int ShadowRectIds[2]; // IDs of rect for shadow textures
ImVec4 ShadowRectUvs[10]; // UV coordinates for shadow textures, 9 for the rectangle shadows and the final entry for the convex shape shadows
ImFontAtlasShadowTexConfig ShadowTexConfig; // Shadow texture baking config
// [Obsolete]

View File

@ -373,6 +373,31 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst)
colors[ImGuiCol_WindowShadow] = ImVec4(0.08f, 0.08f, 0.08f, 0.35f);
}
//-----------------------------------------------------------------------------
// [SECTION] ImFontAtlasShadowTexConfig
//-----------------------------------------------------------------------------
ImFontAtlasShadowTexConfig::ImFontAtlasShadowTexConfig()
{
TexCornerSize = 16;
TexEdgeSize = 1;
TexFalloffPower = 4.8f;
TexDistanceFieldOffset = 3.8f;
TexBlur = true;
}
int ImFontAtlasShadowTexConfig::CalcConvexTexWidth() const
{
// We have to pad the texture enough that we don't go off the edges when we expand the corner triangles
return (int)((TexCornerSize / ImCos(IM_PI * 0.25f)) + (GetConvexTexPadding() * 2));
}
int ImFontAtlasShadowTexConfig::CalcConvexTexHeight() const
{
// We have to pad the texture enough that we don't go off the edges when we expand the corner triangles
return (int)((TexCornerSize / ImCos(IM_PI * 0.25f)) + (GetConvexTexPadding() * 2));
}
//-----------------------------------------------------------------------------
// [SECTION] ImDrawList
//-----------------------------------------------------------------------------
@ -386,7 +411,6 @@ ImDrawListSharedData::ImDrawListSharedData()
ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a));
}
ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError);
ShadowRectId = -1;
}
void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error)
@ -2089,7 +2113,7 @@ static void AddShadowRectEx(ImDrawList* draw_list, const ImVec2& p_min, const Im
}
if (is_filled)
draw_list->PrimReserve(6 * 9, 4 * 9);
draw_list->PrimReserve(6 * 9, 4 * 9); // Reserve space for adding unclipped chunks
// Draw the relevant chunks of the texture (the texture is split into a 3x3 grid)
for (int x = 0; x < 3; x++)
@ -2116,7 +2140,7 @@ static void AddShadowRectEx(ImDrawList* draw_list, const ImVec2& p_min, const Im
ImVec2 uv_min(uvs.x, uvs.y);
ImVec2 uv_max(uvs.z, uvs.w);
if (is_filled)
draw_list->PrimRectUV(draw_min + offset, draw_max + offset, uv_min, uv_max, col);
draw_list->PrimRectUV(draw_min + offset, draw_max + offset, uv_min, uv_max, col); // No clipping path (draw entire shadow)
else if (is_rounded)
AddSubtractedRect(draw_list, draw_min + offset, draw_max + offset, uv_min, uv_max, inner_rect_points, inner_rect_points_count, col); // Complex path for rounded rectangles
else
@ -2141,6 +2165,260 @@ void ImDrawList::AddShadowRect(const ImVec2& p_min, const ImVec2& p_max, float s
AddShadowRectEx(this, p_min, p_max, shadow_thickness, offset, col, rounding, rounding_corners, false);
}
// Add a shadow for a convex shape described by points and num_points
static void AddShadowConvexShapeEx(ImDrawList* draw_list, const ImVec2* points, int num_points, float shadow_thickness, const ImVec2& offset, ImU32 col, bool is_filled)
{
IM_UNUSED(offset); // Offset not currently supported
const ImVec4 uvs = draw_list->_Data->ShadowRectUvs[9];
int tex_width = draw_list->_Data->Font->ContainerAtlas->TexWidth;
int tex_height = draw_list->_Data->Font->ContainerAtlas->TexHeight;
float inv_tex_width = 1.0f / (float)tex_width;
float inv_tex_height = 1.0f / (float)tex_height;
ImVec2 solid_uv = ImVec2(uvs.z, uvs.w); // UV at the inside of an edge
ImVec2 edge_uv = ImVec2(uvs.x, uvs.w); // UV at the outside of an edge
ImVec2 solid_to_edge_delta_texels = edge_uv - solid_uv; // Delta between the solid/edge points in texel-space (we need this in pixels - or, to be more precise, to be at a 1:1 aspect ratio - for the rotation to work)
solid_to_edge_delta_texels.x *= (float)tex_width;
solid_to_edge_delta_texels.y *= (float)tex_height;
// Our basic algorithm here is that we generate a straight section along each edge, and then either one or two curved corner triangles at the corners,
// which use an appropriate chunk of the texture to generate a smooth curve.
const int num_edges = num_points;
// Normalize a vector
#define NORMALIZE(vec) ((vec) / ImLength((vec), 0.001f))
// Calculate edge normals
ImVec2* edge_normals = (ImVec2*)alloca(num_edges * sizeof(ImVec2));
for (int edge_index = 0; edge_index < num_edges; edge_index++)
{
ImVec2 edge_start = points[edge_index];
ImVec2 edge_end = points[(edge_index + 1) % num_edges];
ImVec2 edge_normal = NORMALIZE(ImVec2(edge_end.y - edge_start.y, -(edge_end.x - edge_start.x)));
edge_normals[edge_index] = edge_normal;
}
const int max_vertices = (4 + (3 * 2) + (is_filled ? 1 : 0)) * num_edges; // 4 vertices per edge plus 3*2 for potentially two corner triangles, plus one per vertex for fill
const int max_indices = ((6 + (3 * 2)) * num_edges) + (is_filled ? ((num_edges - 2) * 3) : 0); // 2 tris per edge plus up to two corner triangles, plus fill triangles
draw_list->PrimReserve(max_indices, max_vertices);
ImDrawIdx* idx_write = draw_list->_IdxWritePtr;
ImDrawVert* vtx_write = draw_list->_VtxWritePtr;
ImDrawIdx current_idx = (ImDrawIdx)draw_list->_VtxCurrentIdx;
ImVec2 previous_edge_start = points[0];
ImVec2 edge_start = points[0];
ImVec2 prev_edge_normal = edge_normals[num_edges - 1];
for (int edge_index = 0; edge_index < num_edges; edge_index++)
{
ImVec2 edge_end = points[(edge_index + 1) % num_edges];
ImVec2 edge_normal = edge_normals[edge_index];
// Add corner section
float cos_angle_coverage = ImDot(edge_normal, prev_edge_normal);
if (cos_angle_coverage < 0.999999f) // Don't fill if the corner is actually straight
{
// If we are covering more than 90 degrees we need an intermediate vertex to stop the required expansion tending towards infinity
int num_steps = (cos_angle_coverage <= 0.0f) ? 2 : 1;
for (int step = 0; step < num_steps; step++)
{
if (num_steps > 1)
{
if (step == 0)
edge_normal = NORMALIZE(edge_normal + prev_edge_normal); // Use half-way normal for first step
else
edge_normal = edge_normals[edge_index]; // Then use the "real" next edge normal for the second
cos_angle_coverage = ImDot(edge_normal, prev_edge_normal); // Recalculate angle
}
// Calculate UV for the section of the curved texture
float angle_coverage = ImAcos(cos_angle_coverage);
float sin_angle_coverage = ImSin(angle_coverage);
float size_scale = 1.0f / ImCos(angle_coverage * 0.5f); // How much we need to expand our size by to avoid clipping the corner of the texture off
ImVec2 edge_delta = solid_to_edge_delta_texels;
edge_delta *= size_scale;
ImVec2 rotated_edge_delta = ImVec2((edge_delta.x * cos_angle_coverage) + (edge_delta.y * sin_angle_coverage), (edge_delta.x * sin_angle_coverage) + (edge_delta.y * cos_angle_coverage));
// Convert from texels back into UV space
edge_delta.x *= inv_tex_width;
edge_delta.y *= inv_tex_height;
rotated_edge_delta.x *= inv_tex_width;
rotated_edge_delta.y *= inv_tex_height;
ImVec2 expanded_edge_uv = solid_uv + edge_delta;
ImVec2 other_edge_uv = solid_uv + rotated_edge_delta; // Rotated UV to encompass the necessary section of the curve
float expanded_thickness = shadow_thickness * size_scale;
// Add a triangle to fill the corner
ImVec2 outer_edge_start = edge_start + (prev_edge_normal * expanded_thickness);
ImVec2 outer_edge_end = edge_start + (edge_normal * expanded_thickness);
vtx_write->pos = edge_start; vtx_write->col = col; vtx_write->uv = solid_uv; vtx_write++;
vtx_write->pos = outer_edge_end; vtx_write->col = col; vtx_write->uv = expanded_edge_uv; vtx_write++;
vtx_write->pos = outer_edge_start; vtx_write->col = col; vtx_write->uv = other_edge_uv; vtx_write++;
*(idx_write++) = current_idx;
*(idx_write++) = current_idx + 1;
*(idx_write++) = current_idx + 2;
current_idx += 3;
prev_edge_normal = edge_normal;
}
}
// Add section along edge
const float edge_length = ImLength(edge_end - edge_start, 0.0f);
if (edge_length > 0.00001f) // Don't try and process degenerate edges
{
ImVec2 outer_edge_start = edge_start + (edge_normal * shadow_thickness);
ImVec2 outer_edge_end = edge_end + (edge_normal * shadow_thickness);
// Write vertices, inner first, then outer
vtx_write->pos = edge_start; vtx_write->col = col; vtx_write->uv = solid_uv; vtx_write++;
vtx_write->pos = edge_end; vtx_write->col = col; vtx_write->uv = solid_uv; vtx_write++;
vtx_write->pos = outer_edge_end; vtx_write->col = col; vtx_write->uv = edge_uv; vtx_write++;
vtx_write->pos = outer_edge_start; vtx_write->col = col; vtx_write->uv = edge_uv; vtx_write++;
*(idx_write++) = current_idx;
*(idx_write++) = current_idx + 1;
*(idx_write++) = current_idx + 2;
*(idx_write++) = current_idx;
*(idx_write++) = current_idx + 2;
*(idx_write++) = current_idx + 3;
current_idx += 4;
}
edge_start = edge_end;
}
if (is_filled) // Fill if requested
{
// Add vertices
for (int edge_index = 0; edge_index < num_edges; edge_index++)
{
vtx_write->pos = points[edge_index]; vtx_write->col = col; vtx_write->uv = solid_uv; vtx_write++;
}
// Add tris
for (int edge_index = 2; edge_index < num_edges; edge_index++)
{
*(idx_write++) = current_idx;
*(idx_write++) = (ImDrawIdx)(current_idx + edge_index - 1);
*(idx_write++) = (ImDrawIdx)(current_idx + edge_index);
}
current_idx += (ImDrawIdx)num_edges;
}
// Release any unused vertices/indices
int used_indices = (int)(idx_write - draw_list->_IdxWritePtr);
int used_vertices = (int)(vtx_write - draw_list->_VtxWritePtr);
draw_list->_IdxWritePtr = idx_write;
draw_list->_VtxWritePtr = vtx_write;
draw_list->_VtxCurrentIdx = current_idx;
draw_list->PrimUnreserve(max_indices - used_indices, max_vertices - used_vertices);
#undef NORMALIZE
}
// Draw a shadow for a circular object
// Uses the draw path and so wipes any existing data there
static void AddShadowCircleEx(ImDrawList* draw_list, const ImVec2& center, float radius, float shadow_thickness, const ImVec2& offset, ImU32 col, int num_segments, bool is_filled)
{
// Obtain segment count
if (num_segments <= 0)
{
// Automatic segment count
const int radius_idx = (int)radius - 1;
if (radius_idx < IM_ARRAYSIZE(draw_list->_Data->CircleSegmentCounts))
num_segments = draw_list->_Data->CircleSegmentCounts[radius_idx]; // Use cached value
else
num_segments = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, draw_list->_Data->CircleSegmentMaxError);
}
else
{
// Explicit segment count (still clamp to avoid drawing insanely tessellated shapes)
num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX);
}
// Generate a path describing the inner circle and copy it to our buffer
IM_ASSERT(draw_list->_Path.Size == 0);
const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments;
if (num_segments == 12)
draw_list->PathArcToFast(center, radius, 0, 11);
else
draw_list->PathArcTo(center, radius, 0.0f, a_max, num_segments - 1);
// Draw the shadow using the convex shape code
AddShadowConvexShapeEx(draw_list, draw_list->_Path.Data, draw_list->_Path.Size, shadow_thickness, offset, col, is_filled);
// Clear the path we generated
draw_list->_Path.Size = 0;
}
void ImDrawList::AddShadowCircle(const ImVec2& center, float radius, float shadow_thickness, const ImVec2& offset, ImU32 col, int num_segments)
{
if (((col & IM_COL32_A_MASK) == 0) || (radius <= 0))
return;
AddShadowCircleEx(this, center, radius, shadow_thickness, offset, col, num_segments, false);
}
void ImDrawList::AddShadowCircleFilled(const ImVec2& center, float radius, float shadow_thickness, const ImVec2& offset, ImU32 col, int num_segments)
{
if (((col & IM_COL32_A_MASK) == 0) || (radius <= 0))
return;
AddShadowCircleEx(this, center, radius, shadow_thickness, offset, col, num_segments, true);
}
void ImDrawList::AddShadowNGon(const ImVec2& center, float radius, float shadow_thickness, const ImVec2& offset, ImU32 col, int num_segments)
{
if (((col & IM_COL32_A_MASK) == 0) || (radius <= 0))
return;
AddShadowCircleEx(this, center, radius, shadow_thickness, offset, col, num_segments, false);
}
void ImDrawList::AddShadowNGonFilled(const ImVec2& center, float radius, float shadow_thickness, const ImVec2& offset, ImU32 col, int num_segments)
{
if (((col & IM_COL32_A_MASK) == 0) || (radius <= 0))
return;
AddShadowCircleEx(this, center, radius, shadow_thickness, offset, col, num_segments, true);
}
void ImDrawList::AddShadowConvexPoly(const ImVec2* points, int num_points, float shadow_thickness, const ImVec2& offset, ImU32 col)
{
if ((col & IM_COL32_A_MASK) == 0)
return;
AddShadowConvexShapeEx(this, points, num_points, shadow_thickness, offset, col, false);
}
void ImDrawList::AddShadowConvexPolyFilled(const ImVec2* points, int num_points, float shadow_thickness, const ImVec2& offset, ImU32 col)
{
if ((col & IM_COL32_A_MASK) == 0)
return;
AddShadowConvexShapeEx(this, points, num_points, shadow_thickness, offset, col, true);
}
//-----------------------------------------------------------------------------
// [SECTION] ImDrawListSplitter
@ -2379,19 +2657,6 @@ void ImGui::ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int ve
}
}
//-----------------------------------------------------------------------------
// [SECTION] ImFontAtlasShadowTexConfig
//-----------------------------------------------------------------------------
ImFontAtlasShadowTexConfig::ImFontAtlasShadowTexConfig()
{
TexCornerSize = 16;
TexEdgeSize = 1;
TexFalloffPower = 4.8f;
TexDistanceFieldOffset = 3.8f;
TexBlur = true;
}
//-----------------------------------------------------------------------------
// [SECTION] ImFontConfig
//-----------------------------------------------------------------------------
@ -2466,7 +2731,7 @@ ImFontAtlas::ImFontAtlas()
memset(this, 0, sizeof(*this));
TexGlyphPadding = 1;
PackIdMouseCursors = PackIdLines = -1;
ShadowRectId = -1;
ShadowRectIds[0] = ShadowRectIds[1] = -1;
}
ImFontAtlas::~ImFontAtlas()
@ -2495,7 +2760,7 @@ void ImFontAtlas::ClearInputData()
ConfigData.clear();
CustomRects.clear();
PackIdMouseCursors = PackIdLines = -1;
ShadowRectId = -1;
ShadowRectIds[0] = ShadowRectIds[1] = -1;
// Important: we leave TexReady untouched
}
@ -3249,13 +3514,17 @@ static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas)
// Register the rectangles we need for the rounded corner images
static void ImFontAtlasBuildRegisterShadowCustomRects(ImFontAtlas* atlas)
{
if (atlas->ShadowRectId >= 0)
if (atlas->ShadowRectIds[0] >= 0)
return;
// ShadowRectIds[0] is the rectangle for rectangular shadows
// ShadowRectIds[1] is the rectangle for convex shadows
// The actual size we want to reserve, including padding
const ImFontAtlasShadowTexConfig* shadow_cfg = &atlas->ShadowTexConfig;
const unsigned int effective_size = shadow_cfg->CalcTexSize() + shadow_cfg->GetPadding();
atlas->ShadowRectId = atlas->AddCustomRectRegular(effective_size, effective_size);
const unsigned int effective_size = shadow_cfg->CalcRectTexSize() + shadow_cfg->GetRectTexPadding();
atlas->ShadowRectIds[0] = atlas->AddCustomRectRegular(effective_size, effective_size);
atlas->ShadowRectIds[1] = atlas->AddCustomRectRegular(shadow_cfg->CalcConvexTexWidth() + shadow_cfg->GetConvexTexPadding(), shadow_cfg->CalcConvexTexHeight() + shadow_cfg->GetConvexTexPadding());
}
// Calculates the signed distance from samplePos to the nearest point on the rectangle defined by rectMin-rectMax
@ -3274,6 +3543,12 @@ static float DistanceFromRectangle(ImVec2 samplePos, ImVec2 rectMin, ImVec2 rect
return out_dist + in_dist;
}
// Calculates the signed distance from samplePos to the point given
static float DistanceFromPoint(ImVec2 samplePos, ImVec2 point)
{
return ImLength(samplePos - point, 0.0f);
}
// Perform a single Gaussian blur pass with a fixed kernel size and sigma
static void GaussianBlurPass(float* src, float* dest, int size, bool horizontal)
{
@ -3318,82 +3593,146 @@ static void GaussianBlur(float* data, int size)
static void ImFontAtlasBuildRenderShadowTexData(ImFontAtlas* atlas)
{
IM_ASSERT(atlas->TexPixelsAlpha8 != NULL);
IM_ASSERT(atlas->ShadowRectId >= 0);
IM_ASSERT(atlas->ShadowRectIds[0] >= 0);
IM_ASSERT(atlas->ShadowRectIds[1] >= 0);
// Because of the blur, we have to generate the full 3x3 texture here, and then we chop that down to just the 2x2 section we need later.
// 'size' correspond to the our 3x3 size, whereas 'shadow_tex_size' correspond to our 2x2 version where duplicate mirrored corners are not stored.
const ImFontAtlasShadowTexConfig* shadow_cfg = &atlas->ShadowTexConfig;
const int size = shadow_cfg->TexCornerSize + shadow_cfg->TexEdgeSize + shadow_cfg->TexCornerSize;
const int corner_size = shadow_cfg->TexCornerSize;
const int edge_size = shadow_cfg->TexEdgeSize;
// The bounds of the rectangle we are generating the shadow from
const ImVec2 shadow_rect_min((float)corner_size, (float)corner_size);
const ImVec2 shadow_rect_max((float)(corner_size + edge_size), (float)(corner_size + edge_size));
// Render the texture
ImFontAtlasCustomRect r = atlas->CustomRects[atlas->ShadowRectId];
// Remove the padding we added
const int padding = shadow_cfg->GetPadding();
r.X += (unsigned short)padding;
r.Y += (unsigned short)padding;
r.Width -= (unsigned short)padding * 2;
r.Height -= (unsigned short)padding * 2;
// We draw the actual texture content by evaluating the distance field for the inner rectangle
// Generate distance field
float* tex_data = (float*)alloca(size * size * sizeof(float));
for (int y = 0; y < size; y++)
for (int x = 0; x < size; x++)
{
float dist = DistanceFromRectangle(ImVec2((float)x, (float)y), shadow_rect_min, shadow_rect_max);
float alpha = 1.0f - ImMin(ImMax(dist + shadow_cfg->TexDistanceFieldOffset, 0.0f) / ImMax(shadow_cfg->TexCornerSize + shadow_cfg->TexDistanceFieldOffset, 0.001f), 1.0f);
alpha = ImPow(alpha, shadow_cfg->TexFalloffPower); // Apply power curve to give a nicer falloff
tex_data[x + (y * size)] = alpha;
}
// Blur
if (shadow_cfg->TexBlur)
GaussianBlur(tex_data, size);
// Copy to texture, truncating to the actual required texture size (the bottom/right of the source data is chopped off, as we don't need it - see below). The truncated size is essentially the top 2x2 of our data, plus a little bit of padding for sampling.
const int tex_w = atlas->TexWidth;
const int shadow_tex_size = shadow_cfg->CalcTexSize();
for (int y = 0; y < shadow_tex_size; y++)
for (int x = 0; x < shadow_tex_size; x++)
{
const float alpha = tex_data[x + (y * size)];
const unsigned int offset = (int)(r.X + x) + (int)(r.Y + y) * tex_w;
atlas->TexPixelsAlpha8[offset] = (unsigned char)(0xFF * alpha);
}
// Generate UVs for each of the nine sections, which are arranged in a 3x3 grid starting from 0 in the top-left and going across then down
for (int i = 0; i < 9; i++)
// The rectangular shadow texture
{
ImFontAtlasCustomRect sub_rect = r;
const int size = shadow_cfg->TexCornerSize + shadow_cfg->TexEdgeSize + shadow_cfg->TexCornerSize;
const int corner_size = shadow_cfg->TexCornerSize;
const int edge_size = shadow_cfg->TexEdgeSize;
// The third row/column of the 3x3 grid are generated by flipping the appropriate chunks of the upper 2x2 grid.
bool flip_h = false; // Do we need to flip the UVs horizontally?
bool flip_v = false; // Do we need to flip the UVs vertically?
// The bounds of the rectangle we are generating the shadow from
const ImVec2 shadow_rect_min((float)corner_size, (float)corner_size);
const ImVec2 shadow_rect_max((float)(corner_size + edge_size), (float)(corner_size + edge_size));
switch (i % 3)
// Render the texture
ImFontAtlasCustomRect r = atlas->CustomRects[atlas->ShadowRectIds[0]];
// Remove the padding we added
const int padding = shadow_cfg->GetRectTexPadding();
r.X += (unsigned short)padding;
r.Y += (unsigned short)padding;
r.Width -= (unsigned short)padding * 2;
r.Height -= (unsigned short)padding * 2;
// We draw the actual texture content by evaluating the distance field for the inner rectangle
// Generate distance field
float* tex_data = (float*)alloca(size * size * sizeof(float));
for (int y = 0; y < size; y++)
for (int x = 0; x < size; x++)
{
float dist = DistanceFromRectangle(ImVec2((float)x, (float)y), shadow_rect_min, shadow_rect_max);
float alpha = 1.0f - ImMin(ImMax(dist + shadow_cfg->TexDistanceFieldOffset, 0.0f) / ImMax(shadow_cfg->TexCornerSize + shadow_cfg->TexDistanceFieldOffset, 0.001f), 1.0f);
alpha = ImPow(alpha, shadow_cfg->TexFalloffPower); // Apply power curve to give a nicer falloff
tex_data[x + (y * size)] = alpha;
}
// Blur
if (shadow_cfg->TexBlur)
GaussianBlur(tex_data, size);
// Copy to texture, truncating to the actual required texture size (the bottom/right of the source data is chopped off, as we don't need it - see below). The truncated size is essentially the top 2x2 of our data, plus a little bit of padding for sampling.
const int tex_w = atlas->TexWidth;
const int shadow_tex_size = shadow_cfg->CalcRectTexSize();
for (int y = 0; y < shadow_tex_size; y++)
for (int x = 0; x < shadow_tex_size; x++)
{
const float alpha = tex_data[x + (y * size)];
const unsigned int offset = (int)(r.X + x) + (int)(r.Y + y) * tex_w;
atlas->TexPixelsAlpha8[offset] = (unsigned char)(0xFF * alpha);
}
// Generate UVs for each of the nine sections, which are arranged in a 3x3 grid starting from 0 in the top-left and going across then down
for (int i = 0; i < 9; i++)
{
case 0: sub_rect.Width = (unsigned short)corner_size; break;
case 1: sub_rect.X += (unsigned short)corner_size; sub_rect.Width = (unsigned short)edge_size; break;
case 2: sub_rect.Width = (unsigned short)corner_size; flip_h = true; break;
}
ImFontAtlasCustomRect sub_rect = r;
switch (i / 3)
{
case 0: sub_rect.Height = (unsigned short)corner_size; break;
case 1: sub_rect.Y += (unsigned short)corner_size; sub_rect.Height = (unsigned short)edge_size; break;
case 2: sub_rect.Height = (unsigned short)corner_size; flip_v = true; break;
}
// The third row/column of the 3x3 grid are generated by flipping the appropriate chunks of the upper 2x2 grid.
bool flip_h = false; // Do we need to flip the UVs horizontally?
bool flip_v = false; // Do we need to flip the UVs vertically?
switch (i % 3)
{
case 0: sub_rect.Width = (unsigned short)corner_size; break;
case 1: sub_rect.X += (unsigned short)corner_size; sub_rect.Width = (unsigned short)edge_size; break;
case 2: sub_rect.Width = (unsigned short)corner_size; flip_h = true; break;
}
switch (i / 3)
{
case 0: sub_rect.Height = (unsigned short)corner_size; break;
case 1: sub_rect.Y += (unsigned short)corner_size; sub_rect.Height = (unsigned short)edge_size; break;
case 2: sub_rect.Height = (unsigned short)corner_size; flip_v = true; break;
}
ImVec2 uv0, uv1;
atlas->CalcCustomRectUV(&sub_rect, &uv0, &uv1);
atlas->ShadowRectUvs[i] = ImVec4(flip_h ? uv1.x : uv0.x, flip_v ? uv1.y : uv0.y, flip_h ? uv0.x : uv1.x, flip_v ? uv0.y : uv1.y);
}
}
// The convex shape shadow texture
{
const int size = shadow_cfg->TexCornerSize * 2;
const int padding = shadow_cfg->GetConvexTexPadding();
// Render the texture
ImFontAtlasCustomRect r = atlas->CustomRects[atlas->ShadowRectIds[1]];
// We draw the actual texture content by evaluating the distance field for the distance from a center point
// Generate distance field
ImVec2 center_point(size * 0.5f, size * 0.5f);
float* tex_data = (float*)alloca(size * size * sizeof(float));
for (int y = 0; y < size; y++)
for (int x = 0; x < size; x++)
{
float dist = DistanceFromPoint(ImVec2((float)x, (float)y), center_point);
float alpha = 1.0f - ImMin(ImMax((float)dist /*+ shadow_cfg->TexDistanceFieldOffset*/, 0.0f) / ImMax((float)shadow_cfg->TexCornerSize /*+ shadow_cfg->TexDistanceFieldOffset*/, 0.001f), 1.0f);
alpha = ImPow(alpha, shadow_cfg->TexFalloffPower); // Apply power curve to give a nicer falloff
tex_data[x + (y * size)] = alpha;
}
// Blur
if (shadow_cfg->TexBlur)
GaussianBlur(tex_data, size);
// Copy to texture, truncating to the actual required texture size (the bottom/right of the source data is chopped off, as we don't need it - see below)
// We push the data down and right by the amount we padded the top of the texture (see CalcConvexTexWidth/CalcConvexTexHeight) for details
const int padded_size = (int)(shadow_cfg->TexCornerSize / ImCos(IM_PI * 0.25f));
const int src_x_offset = padding + (padded_size - shadow_cfg->TexCornerSize);
const int src_y_offset = padding + (padded_size - shadow_cfg->TexCornerSize);
const int tex_width = shadow_cfg->CalcConvexTexWidth();
const int tex_height = shadow_cfg->CalcConvexTexHeight();
const int tex_w = atlas->TexWidth;
for (int y = 0; y < tex_height; y++)
for (int x = 0; x < tex_width; x++)
{
int srcX = ImClamp(x - src_x_offset, 0, size - 1);
int srcY = ImClamp(y - src_y_offset, 0, size - 1);
const float alpha = tex_data[srcX + (srcY * size)];
const unsigned int offset = (int)(r.X + x) + (int)(r.Y + y) * tex_w;
atlas->TexPixelsAlpha8[offset] = (unsigned char)(0xFF * alpha);
}
// Remove the padding we added
r.X += (unsigned short)padding;
r.Y += (unsigned short)padding;
r.Width = (unsigned short)(tex_width - (padding * 2));
r.Height = (unsigned short)(tex_height - (padding * 2));
// Generate UVs
ImVec2 uv0, uv1;
atlas->CalcCustomRectUV(&sub_rect, &uv0, &uv1);
atlas->ShadowRectUvs[i] = ImVec4(flip_h ? uv1.x : uv0.x, flip_v ? uv1.y : uv0.y, flip_h ? uv0.x : uv1.x, flip_v ? uv0.y : uv1.y);
atlas->CalcCustomRectUV(&r, &uv0, &uv1);
atlas->ShadowRectUvs[9] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y);
}
}

View File

@ -727,8 +727,8 @@ struct IMGUI_API ImDrawListSharedData
ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead)
const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas
int ShadowRectId; // ID of rect for shadow texture
const ImVec4* ShadowRectUvs; // UV coordinates for shadow texture (9 entries)
int* ShadowRectIds; // IDs of rects for shadow texture (2 entries)
const ImVec4* ShadowRectUvs; // UV coordinates for shadow texture (10 entries)
ImDrawListSharedData();
void SetCircleTessellationMaxError(float max_error);