Blender V5.0
overlay_attribute_text.hh
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#pragma once
10
11#include "BLI_color.hh"
13#include "BLI_string_utf8.h"
14
15#include "DNA_curve_types.h"
17
18#include "BKE_attribute.hh"
19#include "BKE_curves.hh"
20#include "BKE_geometry_set.hh"
21#include "BKE_instances.hh"
22#include "BKE_mesh.hh"
23
24#include "DRW_render.hh"
25
26#include "ED_view3d.hh"
27
28#include "UI_interface_c.hh"
29#include "UI_resources.hh"
30
31#include "draw_manager_text.hh"
32
33#include "overlay_base.hh"
34
35namespace blender::draw::overlay {
36
42 public:
43 void begin_sync(Resources &res, const State &state) final
44 {
45 enabled_ = !res.is_selection() && state.show_attribute_viewer_text();
46 }
47
48 void object_sync(Manager & /*manager*/,
49 const ObjectRef &ob_ref,
50 Resources & /*res*/,
51 const State &state) final
52 {
53 if (!enabled_) {
54 return;
55 }
56
57 const Object &object = *ob_ref.object;
58 const bool is_preview = ob_ref.preview_base_geometry() != nullptr;
59 if (!is_preview) {
60 return;
61 }
62
63 DRWTextStore *dt = state.dt;
64 const float4x4 &object_to_world = object.object_to_world();
65
66 if (ob_ref.preview_instance_index() >= 0) {
67 const bke::Instances *instances = ob_ref.preview_base_geometry()->get_instances();
68 if (instances->attributes().contains(".viewer")) {
69 add_instance_attributes_to_text_cache(
70 dt, instances->attributes(), object_to_world, ob_ref.preview_instance_index());
71
72 return;
73 }
74 }
75
76 switch (object.type) {
77 case OB_MESH: {
78 const Mesh &mesh = DRW_object_get_data_for_drawing<Mesh>(object);
79 add_mesh_attributes_to_text_cache(state, mesh, object_to_world);
80 break;
81 }
82 case OB_POINTCLOUD: {
84 add_attributes_to_text_cache(dt, pointcloud.attributes(), object_to_world);
85 break;
86 }
87 case OB_CURVES_LEGACY: {
88 const Curve &curve = DRW_object_get_data_for_drawing<Curve>(object);
89 if (curve.curve_eval) {
90 const bke::CurvesGeometry &curves = curve.curve_eval->geometry.wrap();
91 add_attributes_to_text_cache(dt, curves.attributes(), object_to_world);
92 }
93 break;
94 }
95 case OB_CURVES: {
96 const Curves &curves_id = DRW_object_get_data_for_drawing<Curves>(object);
97 const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
98 add_attributes_to_text_cache(dt, curves.attributes(), object_to_world);
99 break;
100 }
101 }
102 }
103
104 private:
105 void add_attributes_to_text_cache(DRWTextStore *dt,
106 bke::AttributeAccessor attribute_accessor,
107 const float4x4 &object_to_world)
108 {
109 if (!attribute_accessor.contains(".viewer")) {
110 return;
111 }
112
113 const bke::GAttributeReader attribute = attribute_accessor.lookup(".viewer");
114 const VArraySpan<float3> positions = *attribute_accessor.lookup<float3>("position",
115 attribute.domain);
116
117 add_values_to_text_cache(dt, attribute.varray, positions, object_to_world);
118 }
119
120 void add_mesh_attributes_to_text_cache(const State &state,
121 const Mesh &mesh,
122 const float4x4 &object_to_world)
123 {
124 const bke::AttributeAccessor attributes = mesh.attributes();
125 if (!attributes.contains(".viewer")) {
126 return;
127 }
128
129 const bke::GAttributeReader attribute = attributes.lookup(".viewer");
130 const bke::AttrDomain domain = attribute.domain;
131 const VArraySpan<float3> positions = *attributes.lookup<float3>("position", domain);
132
133 if (domain == bke::AttrDomain::Corner) {
134 const CPPType &type = attribute.varray.type();
135 float offset_by_type = 1.0f;
136 if (type.is<int2>() || type.is<float2>() || type.is<float3>() ||
137 type.is<ColorGeometry4b>() || type.is<ColorGeometry4f>() || type.is<math::Quaternion>())
138 {
139 offset_by_type = 1.5f;
140 }
141 else if (type.is<float4x4>()) {
142 offset_by_type = 3.0f;
143 }
144
145 Array<float3> corner_positions(positions.size());
146 const Span<float3> positions = mesh.vert_positions();
147 const OffsetIndices<int> faces = mesh.faces();
148 const Span<int> corner_verts = mesh.corner_verts();
149 const Span<float3> face_normals = mesh.face_normals();
150
151 threading::parallel_for(faces.index_range(), 512, [&](const IndexRange range) {
152 for (const int face_index : range) {
153 const float3 &face_normal = face_normals[face_index];
154 const IndexRange face = faces[face_index];
155 for (const int corner : face) {
156 const int corner_prev = bke::mesh::face_corner_prev(face, corner);
157 const int corner_next = bke::mesh::face_corner_next(face, corner);
158 corner_positions[corner] = calc_corner_text_position(
159 positions[corner_verts[corner]],
160 positions[corner_verts[corner_prev]],
161 positions[corner_verts[corner_next]],
162 face_normal,
163 state.rv3d,
164 object_to_world,
165 offset_by_type);
166 }
167 }
168 });
169 add_values_to_text_cache(
170 state.dt, attribute.varray, corner_positions.as_span(), object_to_world);
171 }
172 else {
173 add_values_to_text_cache(state.dt, attribute.varray, positions, object_to_world);
174 }
175 }
176
177 void add_instance_attributes_to_text_cache(DRWTextStore *dt,
178 bke::AttributeAccessor attribute_accessor,
179 const float4x4 &object_to_world,
180 int instance_index)
181 {
182 /* Data from instances are read as a single value from a given index. The data is converted
183 * back to an array so one function can handle both instance and object data. */
184 const GVArray attribute = attribute_accessor.lookup(".viewer").varray.slice(
185 IndexRange(instance_index, 1));
186
187 add_values_to_text_cache(dt, attribute, {float3(0, 0, 0)}, object_to_world);
188 }
189
190 static void add_text_to_cache(DRWTextStore *dt,
191 const float3 &position,
192 const StringRef text,
193 const uchar4 &color)
194 {
196 position,
197 text.data(),
198 text.size(),
199 0,
200 0,
202 color,
203 true,
204 true);
205 }
206
207 static void add_lines_to_cache(DRWTextStore *dt,
208 const float3 &position,
209 const Span<StringRef> lines,
210 const uchar4 &color)
211 {
212 const float text_size = UI_style_get()->widget.points;
213 const float line_height = text_size * 1.1f * UI_SCALE_FAC;
214 const float center_offset = (lines.size() - 1) / 2.0f;
215 for (const int i : lines.index_range()) {
216 const StringRef line = lines[i];
218 position,
219 line.data(),
220 line.size(),
221 0,
222 (center_offset - i) * line_height,
224 color,
225 true,
226 true);
227 }
228 }
229
230 void add_values_to_text_cache(DRWTextStore *dt,
231 const GVArray &values,
232 const Span<float3> positions,
233 const float4x4 &object_to_world)
234 {
235 uchar col[4];
237
238 bke::attribute_math::convert_to_static_type(values.type(), [&](auto dummy) {
239 using T = decltype(dummy);
240 const VArray<T> &values_typed = values.typed<T>();
241 for (const int i : values.index_range()) {
242 const float3 position = math::transform_point(object_to_world, positions[i]);
243 const T &value = values_typed[i];
244
245 if constexpr (std::is_same_v<T, bool>) {
246 char numstr[64];
247 const size_t numstr_len = STRNCPY_UTF8_RLEN(numstr, value ? "True" : "False");
248 add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
249 }
250 else if constexpr (std::is_same_v<T, int8_t>) {
251 char numstr[64];
252 const size_t numstr_len = SNPRINTF_UTF8_RLEN(numstr, "%d", int(value));
253 add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
254 }
255 else if constexpr (std::is_same_v<T, int>) {
256 char numstr[64];
257 const size_t numstr_len = SNPRINTF_UTF8_RLEN(numstr, "%d", value);
258 add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
259 }
260 else if constexpr (std::is_same_v<T, int2>) {
261 char x_str[64], y_str[64];
262 const size_t x_str_len = SNPRINTF_UTF8_RLEN(x_str, "X: %d", value.x);
263 const size_t y_str_len = SNPRINTF_UTF8_RLEN(y_str, "Y: %d", value.y);
264 add_lines_to_cache(
265 dt, position, {StringRef(x_str, x_str_len), StringRef(y_str, y_str_len)}, col);
266 }
267 else if constexpr (std::is_same_v<T, float>) {
268 char numstr[64];
269 const size_t numstr_len = SNPRINTF_UTF8_RLEN(numstr, "%g", value);
270 add_text_to_cache(dt, position, StringRef(numstr, numstr_len), col);
271 }
272 else if constexpr (std::is_same_v<T, float2>) {
273 char x_str[64], y_str[64];
274 const size_t x_str_len = SNPRINTF_UTF8_RLEN(x_str, "X: %g", value.x);
275 const size_t y_str_len = SNPRINTF_UTF8_RLEN(y_str, "Y: %g", value.y);
276 add_lines_to_cache(
277 dt, position, {StringRef(x_str, x_str_len), StringRef(y_str, y_str_len)}, col);
278 }
279 else if constexpr (std::is_same_v<T, float3>) {
280 char x_str[64], y_str[64], z_str[64];
281 const size_t x_str_len = SNPRINTF_UTF8_RLEN(x_str, "X: %g", value.x);
282 const size_t y_str_len = SNPRINTF_UTF8_RLEN(y_str, "Y: %g", value.y);
283 const size_t z_str_len = SNPRINTF_UTF8_RLEN(z_str, "Z: %g", value.z);
284 add_lines_to_cache(dt,
285 position,
286 {StringRef(x_str, x_str_len),
287 StringRef(y_str, y_str_len),
288 StringRef(z_str, z_str_len)},
289 col);
290 }
291 else if constexpr (std::is_same_v<T, ColorGeometry4b>) {
292 const ColorGeometry4f color = color::decode(value);
293 char r_str[64], g_str[64], b_str[64], a_str[64];
294 const size_t r_str_len = SNPRINTF_UTF8_RLEN(r_str, "R: %.3f", color.r);
295 const size_t g_str_len = SNPRINTF_UTF8_RLEN(g_str, "G: %.3f", color.g);
296 const size_t b_str_len = SNPRINTF_UTF8_RLEN(b_str, "B: %.3f", color.b);
297 const size_t a_str_len = SNPRINTF_UTF8_RLEN(a_str, "A: %.3f", color.a);
298 add_lines_to_cache(dt,
299 position,
300 {StringRef(r_str, r_str_len),
301 StringRef(g_str, g_str_len),
302 StringRef(b_str, b_str_len),
303 StringRef(a_str, a_str_len)},
304 col);
305 }
306 else if constexpr (std::is_same_v<T, ColorGeometry4f>) {
307 char r_str[64], g_str[64], b_str[64], a_str[64];
308 const size_t r_str_len = SNPRINTF_UTF8_RLEN(r_str, "R: %.3f", value.r);
309 const size_t g_str_len = SNPRINTF_UTF8_RLEN(g_str, "G: %.3f", value.g);
310 const size_t b_str_len = SNPRINTF_UTF8_RLEN(b_str, "B: %.3f", value.b);
311 const size_t a_str_len = SNPRINTF_UTF8_RLEN(a_str, "A: %.3f", value.a);
312 add_lines_to_cache(dt,
313 position,
314 {StringRef(r_str, r_str_len),
315 StringRef(g_str, g_str_len),
316 StringRef(b_str, b_str_len),
317 StringRef(a_str, a_str_len)},
318 col);
319 }
320 else if constexpr (std::is_same_v<T, math::Quaternion>) {
321 char w_str[64], x_str[64], y_str[64], z_str[64];
322 const size_t w_str_len = SNPRINTF_UTF8_RLEN(w_str, "W: %.3f", value.w);
323 const size_t x_str_len = SNPRINTF_UTF8_RLEN(x_str, "X: %.3f", value.x);
324 const size_t y_str_len = SNPRINTF_UTF8_RLEN(y_str, "Y: %.3f", value.y);
325 const size_t z_str_len = SNPRINTF_UTF8_RLEN(z_str, "Z: %.3f", value.z);
326 add_lines_to_cache(dt,
327 position,
328 {StringRef(w_str, w_str_len),
329 StringRef(x_str, x_str_len),
330 StringRef(y_str, y_str_len),
331 StringRef(z_str, z_str_len)},
332 col);
333 }
334 else if constexpr (std::is_same_v<T, float4x4>) {
335 float3 location;
336 math::EulerXYZ rotation;
337 float3 scale;
338 math::to_loc_rot_scale_safe<true>(value, location, rotation, scale);
339
340 char location_str[64];
341 const size_t location_str_len = SNPRINTF_UTF8_RLEN(
342 location_str, "Location: %.3f, %.3f, %.3f", location.x, location.y, location.z);
343 char rotation_str[64];
344 const size_t rotation_str_len = SNPRINTF_UTF8_RLEN(rotation_str,
345 "Rotation: %.3f°, %.3f°, %.3f°",
346 rotation.x().degree(),
347 rotation.y().degree(),
348 rotation.z().degree());
349 char scale_str[64];
350 const size_t scale_str_len = SNPRINTF_UTF8_RLEN(
351 scale_str, "Scale: %.3f, %.3f, %.3f", scale.x, scale.y, scale.z);
352 add_lines_to_cache(dt,
353 position,
354 {StringRef(location_str, location_str_len),
355 StringRef(rotation_str, rotation_str_len),
356 StringRef(scale_str, scale_str_len)},
357 col);
358 }
359 else {
360 BLI_assert_unreachable();
361 }
362 }
363 });
364 }
365
366 static float3 calc_corner_text_position(const float3 &corner_pos,
367 const float3 &prev_corner_pos,
368 const float3 &next_corner_pos,
369 const float3 &face_normal,
370 const RegionView3D *rv3d,
371 const float4x4 &object_to_world,
372 const float offset_scale = 1.0f)
373 {
374 const float3 prev_edge_vec = prev_corner_pos - corner_pos;
375 const float3 next_edge_vec = next_corner_pos - corner_pos;
376 const float3 prev_edge_dir = math::normalize(prev_edge_vec);
377 const float3 next_edge_dir = math::normalize(next_edge_vec);
378
379 const float pre_edge_len = math::length(prev_edge_vec);
380 const float next_edge_len = math::length(next_edge_vec);
381 const float max_offset = math::min(pre_edge_len, next_edge_len) / 2;
382
383 const float3 corner_normal = math::cross(next_edge_dir, prev_edge_dir);
384 const float concavity_check = math::dot(corner_normal, face_normal);
385 const float direction_correct = concavity_check > 0.0f ? 1.0f : -1.0f;
386 const float3 bisector_dir = (prev_edge_dir + next_edge_dir) / 2 * direction_correct;
387
388 const float sharp_factor = std::clamp(math::dot(prev_edge_dir, next_edge_dir), 0.0f, 1.0f);
389 const float sharp_multiplier = math::pow(sharp_factor, 4.0f) * 2 + 1;
390
391 const float3 pos_o_world = math::transform_point(object_to_world, corner_pos);
392 const float pixel_size = ED_view3d_pixel_size(rv3d, pos_o_world);
393 const float pixel_offset = UI_style_get()->widget.points * 7.0f * UI_SCALE_FAC;
394 const float screen_space_offset = pixel_size * pixel_offset;
395
396 const float offset_distance = std::clamp(
397 screen_space_offset * sharp_multiplier * offset_scale, 0.0f, max_offset);
398
399 return corner_pos + bisector_dir * offset_distance;
400 }
401};
402
403} // namespace blender::draw::overlay
Low-level operations for curves.
unsigned char uchar
@ OB_MESH
@ OB_POINTCLOUD
@ OB_CURVES_LEGACY
@ OB_CURVES
#define UI_SCALE_FAC
T & DRW_object_get_data_for_drawing(const Object &object)
float ED_view3d_pixel_size(const RegionView3D *rv3d, const float co[3])
const uiStyle * UI_style_get()
@ TH_TEXT_HI
void UI_GetThemeColor4ubv(int colorid, unsigned char col[4])
bool is() const
AttributeSet attributes
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
GVArray slice(IndexRange slice) const
constexpr int64_t size() const
constexpr const char * data() const
bool contains(StringRef attribute_id) const
GAttributeReader lookup(const StringRef attribute_id) const
AttributeAccessor attributes() const
bke::AttributeAccessor attributes() const
Definition instances.cc:64
void object_sync(Manager &, const ObjectRef &ob_ref, Resources &, const State &state) final
void begin_sync(Resources &res, const State &state) final
void DRW_text_cache_add(DRWTextStore *dt, const float co[3], const char *str, const int str_len, short xoffs, short yoffs, short flag, const uchar col[4], const bool shadow, const bool align_center)
@ DRW_TEXT_CACHE_GLOBALSPACE
uint col
static ulong state[N]
static char faces[256]
void convert_to_static_type(const CPPType &cpp_type, const Func &func)
QuaternionBase< float > Quaternion
T pow(const T &x, const T &power)
T length(const VecBase< T, Size > &a)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
T min(const T &a, const T &b)
AxisSigned cross(const AxisSigned a, const AxisSigned b)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
MatBase< float, 4, 4 > float4x4
blender::VecBase< uint8_t, 4 > uchar4
VecBase< float, 3 > float3
ColorSceneLinearByteEncoded4b< eAlpha::Premultiplied > ColorGeometry4b
ColorSceneLinear4f< eAlpha::Premultiplied > ColorGeometry4f
const struct Curves * curve_eval
CurvesGeometry geometry
uiFontStyle widget
i
Definition text_draw.cc:230