Geometry Nodes: improve Viewer Attribute Text Readability

- Corner Domain Text Overlap: Text is now offset along the angle bisector of
  each corner. The offset distance is dynamically adjusted based on the view
  zoom and corner angle sharpness.
- Wrap Long Line: Split Vectors, Colors, and Quaternions into labeled line for
  each component.

Pull Request: https://projects.blender.org/blender/blender/pulls/145290
This commit is contained in:
W_Cloud
2025-09-05 13:46:03 +02:00
committed by Jacques Lucke
parent 4f4104d047
commit 43fdf067ab

View File

@@ -19,9 +19,13 @@
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_instances.hh"
#include "BKE_mesh.hh"
#include "DRW_render.hh"
#include "ED_view3d.hh"
#include "UI_interface_c.hh"
#include "UI_resources.hh"
#include "draw_manager_text.hh"
@@ -72,7 +76,7 @@ class AttributeTexts : Overlay {
switch (object.type) {
case OB_MESH: {
const Mesh &mesh = DRW_object_get_data_for_drawing<Mesh>(object);
add_attributes_to_text_cache(dt, mesh.attributes(), object_to_world);
add_mesh_attributes_to_text_cache(state, mesh, object_to_world);
break;
}
case OB_POINTCLOUD: {
@@ -113,6 +117,63 @@ class AttributeTexts : Overlay {
add_values_to_text_cache(dt, attribute.varray, positions, object_to_world);
}
void add_mesh_attributes_to_text_cache(const State &state,
const Mesh &mesh,
const float4x4 &object_to_world)
{
const bke::AttributeAccessor attributes = mesh.attributes();
if (!attributes.contains(".viewer")) {
return;
}
const bke::GAttributeReader attribute = attributes.lookup(".viewer");
const bke::AttrDomain domain = attribute.domain;
const VArraySpan<float3> positions = *attributes.lookup<float3>("position", domain);
if (domain == bke::AttrDomain::Corner) {
const CPPType &type = attribute.varray.type();
float offset_by_type = 1.0f;
if (type.is<int2>() || type.is<float2>() || type.is<float3>() ||
type.is<ColorGeometry4b>() || type.is<ColorGeometry4f>() || type.is<math::Quaternion>())
{
offset_by_type = 1.5f;
}
else if (type.is<float4x4>()) {
offset_by_type = 3.0f;
}
Array<float3> corner_positions(positions.size());
const Span<float3> positions = mesh.vert_positions();
const OffsetIndices<int> faces = mesh.faces();
const Span<int> corner_verts = mesh.corner_verts();
const Span<float3> face_normals = mesh.face_normals();
threading::parallel_for(faces.index_range(), 512, [&](const IndexRange range) {
for (const int face_index : range) {
const float3 &face_normal = face_normals[face_index];
const IndexRange face = faces[face_index];
for (const int corner : face) {
const int corner_prev = bke::mesh::face_corner_prev(face, corner);
const int corner_next = bke::mesh::face_corner_next(face, corner);
corner_positions[corner] = calc_corner_text_position(
positions[corner_verts[corner]],
positions[corner_verts[corner_prev]],
positions[corner_verts[corner_next]],
face_normal,
state.rv3d,
object_to_world,
offset_by_type);
}
}
});
add_values_to_text_cache(
state.dt, attribute.varray, corner_positions.as_span(), object_to_world);
}
else {
add_values_to_text_cache(state.dt, attribute.varray, positions, object_to_world);
}
}
void add_instance_attributes_to_text_cache(DRWTextStore *dt,
bke::AttributeAccessor attribute_accessor,
const float4x4 &object_to_world,
@@ -148,6 +209,9 @@ class AttributeTexts : Overlay {
const Span<StringRef> lines,
const uchar4 &color)
{
const float text_size = UI_style_get()->widget.points;
const float line_height = text_size * 1.1f * UI_SCALE_FAC;
const float center_offset = (lines.size() - 1) / 2.0f;
for (const int i : lines.index_range()) {
const StringRef line = lines[i];
DRW_text_cache_add(dt,
@@ -155,7 +219,7 @@ class AttributeTexts : Overlay {
line.data(),
line.size(),
0,
-i * 12.0f * UI_SCALE_FAC,
(center_offset - i) * line_height,
DRW_TEXT_CACHE_GLOBALSPACE,
color,
true,
@@ -194,9 +258,11 @@ class AttributeTexts : Overlay {
add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
}
else if constexpr (std::is_same_v<T, int2>) {
char numstr[64];
const size_t numstr_len = SNPRINTF_UTF8_RLEN(numstr, "(%d, %d)", value.x, value.y);
add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
char x_str[64], y_str[64];
const size_t x_str_len = SNPRINTF_UTF8_RLEN(x_str, "X: %d", value.x);
const size_t y_str_len = SNPRINTF_UTF8_RLEN(y_str, "Y: %d", value.y);
add_lines_to_cache(
dt, position, {StringRef(x_str, x_str_len), StringRef(y_str, y_str_len)}, col);
}
else if constexpr (std::is_same_v<T, float>) {
char numstr[64];
@@ -204,34 +270,66 @@ class AttributeTexts : Overlay {
add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
}
else if constexpr (std::is_same_v<T, float2>) {
char numstr[64];
const size_t numstr_len = SNPRINTF_UTF8_RLEN(numstr, "(%g, %g)", value.x, value.y);
add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
char x_str[64], y_str[64];
const size_t x_str_len = SNPRINTF_UTF8_RLEN(x_str, "X: %g", value.x);
const size_t y_str_len = SNPRINTF_UTF8_RLEN(y_str, "Y: %g", value.y);
add_lines_to_cache(
dt, position, {StringRef(x_str, x_str_len), StringRef(y_str, y_str_len)}, col);
}
else if constexpr (std::is_same_v<T, float3>) {
char numstr[64];
const size_t numstr_len = SNPRINTF_UTF8_RLEN(
numstr, "(%g, %g, %g)", value.x, value.y, value.z);
add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
char x_str[64], y_str[64], z_str[64];
const size_t x_str_len = SNPRINTF_UTF8_RLEN(x_str, "X: %g", value.x);
const size_t y_str_len = SNPRINTF_UTF8_RLEN(y_str, "Y: %g", value.y);
const size_t z_str_len = SNPRINTF_UTF8_RLEN(z_str, "Z: %g", value.z);
add_lines_to_cache(dt,
position,
{StringRef(x_str, x_str_len),
StringRef(y_str, y_str_len),
StringRef(z_str, z_str_len)},
col);
}
else if constexpr (std::is_same_v<T, ColorGeometry4b>) {
const ColorGeometry4f color = color::decode(value);
char numstr[64];
const size_t numstr_len = SNPRINTF_UTF8_RLEN(
numstr, "(%.3f, %.3f, %.3f, %.3f)", color.r, color.g, color.b, color.a);
add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
char r_str[64], g_str[64], b_str[64], a_str[64];
const size_t r_str_len = SNPRINTF_UTF8_RLEN(r_str, "R: %.3f", color.r);
const size_t g_str_len = SNPRINTF_UTF8_RLEN(g_str, "G: %.3f", color.g);
const size_t b_str_len = SNPRINTF_UTF8_RLEN(b_str, "B: %.3f", color.b);
const size_t a_str_len = SNPRINTF_UTF8_RLEN(a_str, "A: %.3f", color.a);
add_lines_to_cache(dt,
position,
{StringRef(r_str, r_str_len),
StringRef(g_str, g_str_len),
StringRef(b_str, b_str_len),
StringRef(a_str, a_str_len)},
col);
}
else if constexpr (std::is_same_v<T, ColorGeometry4f>) {
char numstr[64];
const size_t numstr_len = SNPRINTF_UTF8_RLEN(
numstr, "(%.3f, %.3f, %.3f, %.3f)", value.r, value.g, value.b, value.a);
add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
char r_str[64], g_str[64], b_str[64], a_str[64];
const size_t r_str_len = SNPRINTF_UTF8_RLEN(r_str, "R: %.3f", value.r);
const size_t g_str_len = SNPRINTF_UTF8_RLEN(g_str, "G: %.3f", value.g);
const size_t b_str_len = SNPRINTF_UTF8_RLEN(b_str, "B: %.3f", value.b);
const size_t a_str_len = SNPRINTF_UTF8_RLEN(a_str, "A: %.3f", value.a);
add_lines_to_cache(dt,
position,
{StringRef(r_str, r_str_len),
StringRef(g_str, g_str_len),
StringRef(b_str, b_str_len),
StringRef(a_str, a_str_len)},
col);
}
else if constexpr (std::is_same_v<T, math::Quaternion>) {
char numstr[64];
const size_t numstr_len = SNPRINTF_UTF8_RLEN(
numstr, "(%.3f, %.3f, %.3f, %.3f)", value.w, value.x, value.y, value.z);
add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
char w_str[64], x_str[64], y_str[64], z_str[64];
const size_t w_str_len = SNPRINTF_UTF8_RLEN(w_str, "W: %.3f", value.w);
const size_t x_str_len = SNPRINTF_UTF8_RLEN(x_str, "X: %.3f", value.x);
const size_t y_str_len = SNPRINTF_UTF8_RLEN(y_str, "Y: %.3f", value.y);
const size_t z_str_len = SNPRINTF_UTF8_RLEN(z_str, "Z: %.3f", value.z);
add_lines_to_cache(dt,
position,
{StringRef(w_str, w_str_len),
StringRef(x_str, x_str_len),
StringRef(y_str, y_str_len),
StringRef(z_str, z_str_len)},
col);
}
else if constexpr (std::is_same_v<T, float4x4>) {
float3 location;
@@ -264,6 +362,42 @@ class AttributeTexts : Overlay {
}
});
}
static float3 calc_corner_text_position(const float3 &corner_pos,
const float3 &prev_corner_pos,
const float3 &next_corner_pos,
const float3 &face_normal,
const RegionView3D *rv3d,
const float4x4 &object_to_world,
const float offset_scale = 1.0f)
{
const float3 prev_edge_vec = prev_corner_pos - corner_pos;
const float3 next_edge_vec = next_corner_pos - corner_pos;
const float3 prev_edge_dir = math::normalize(prev_edge_vec);
const float3 next_edge_dir = math::normalize(next_edge_vec);
const float pre_edge_len = math::length(prev_edge_vec);
const float next_edge_len = math::length(next_edge_vec);
const float max_offset = math::min(pre_edge_len, next_edge_len) / 2;
const float3 corner_normal = math::cross(next_edge_dir, prev_edge_dir);
const float concavity_check = math::dot(corner_normal, face_normal);
const float direction_correct = concavity_check > 0.0f ? 1.0f : -1.0f;
const float3 bisector_dir = (prev_edge_dir + next_edge_dir) / 2 * direction_correct;
const float sharp_factor = std::clamp(math::dot(prev_edge_dir, next_edge_dir), 0.0f, 1.0f);
const float sharp_multiplier = math::pow(sharp_factor, 4.0f) * 2 + 1;
const float3 pos_o_world = math::transform_point(object_to_world, corner_pos);
const float pixel_size = ED_view3d_pixel_size(rv3d, pos_o_world);
const float pixel_offset = UI_style_get()->widget.points * 7.0f * UI_SCALE_FAC;
const float screen_space_offset = pixel_size * pixel_offset;
const float offset_distance = std::clamp(
screen_space_offset * sharp_multiplier * offset_scale, 0.0f, max_offset);
return corner_pos + bisector_dir * offset_distance;
}
};
} // namespace blender::draw::overlay