1
1

Compare commits

...

7 Commits

4 changed files with 201 additions and 28 deletions

View File

@@ -2404,6 +2404,12 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
* \note Keep this message at the bottom of the function.
*/
{
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
if (ntree->type == NTREE_GEOMETRY) {
version_node_output_socket_name(
ntree, GEO_NODE_STRING_TO_CURVES, "Curves", "Curve Instances");
}
}
/* Keep this block, even when empty. */
}
}

View File

@@ -1581,7 +1581,8 @@ typedef struct NodeGeometryStringToCurves {
uint8_t align_x;
/* GeometryNodeStringToCurvesAlignYMode */
uint8_t align_y;
char _pad[1];
/* GeometryNodeStringToCurvesPivotMode */
uint8_t pivot_mode;
} NodeGeometryStringToCurves;
typedef struct NodeGeometryDeleteGeometry {
@@ -2266,6 +2267,16 @@ typedef enum GeometryNodeStringToCurvesAlignYMode {
GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM = 4,
} GeometryNodeStringToCurvesAlignYMode;
typedef enum GeometryNodeStringToCurvesPivotMode {
GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_MIDPOINT = 0,
GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_LEFT = 1,
GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_CENTER = 2,
GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_RIGHT = 3,
GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_LEFT = 4,
GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_CENTER = 5,
GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_RIGHT = 6,
} GeometryNodeStringToCurvesPivotMode;
typedef enum GeometryNodeDeleteGeometryMode {
GEO_NODE_DELETE_GEOMETRY_MODE_ALL = 0,
GEO_NODE_DELETE_GEOMETRY_MODE_EDGE_FACE = 1,

View File

@@ -11031,6 +11031,33 @@ static void def_geo_string_to_curves(StructRNA *srna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem rna_node_geometry_string_to_curves_pivot_mode[] = {
{GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_MIDPOINT, "MIDPOINT", 0, "Midpoint", "Midpoint"},
{GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_LEFT, "TOP_LEFT", 0, "Top Left", "Top Left"},
{GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_CENTER,
"TOP_CENTER",
0,
"Top Center",
"Top Center"},
{GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_RIGHT, "TOP_RIGHT", 0, "Top Right", "Top Right"},
{GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_LEFT,
"BOTTOM_LEFT",
0,
"Bottom Left",
"Bottom Left"},
{GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_CENTER,
"BOTTOM_CENTER",
0,
"Bottom Center",
"Bottom Center"},
{GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_RIGHT,
"BOTTOM_RIGHT",
0,
"Bottom Right",
"Bottom Right"},
{0, NULL, 0, NULL, NULL},
};
PropertyRNA *prop;
prop = RNA_def_property(srna, "font", PROP_POINTER, PROP_NONE);
@@ -11063,6 +11090,13 @@ static void def_geo_string_to_curves(StructRNA *srna)
RNA_def_property_enum_default(prop, GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE);
RNA_def_property_ui_text(prop, "Align Y", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "pivot_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "pivot_mode");
RNA_def_property_enum_items(prop, rna_node_geometry_string_to_curves_pivot_mode);
RNA_def_property_enum_default(prop, GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_LEFT);
RNA_def_property_ui_text(prop, "Pivot Point", "The pivot point used when rotating characters");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
static void def_geo_separate_geometry(StructRNA *srna)

View File

@@ -34,7 +34,7 @@ namespace blender::nodes::node_geo_string_to_curves_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::String>(N_("String"));
b.add_input<decl::String>(N_("String")).default_value("Text");
b.add_input<decl::Float>(N_("Size")).default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE);
b.add_input<decl::Float>(N_("Character Spacing"))
.default_value(1.0f)
@@ -56,8 +56,10 @@ static void node_declare(NodeDeclarationBuilder &b)
.default_value(0.0f)
.min(0.0f)
.subtype(PROP_DISTANCE);
b.add_output<decl::Geometry>(N_("Curves"));
b.add_output<decl::Geometry>(N_("Curve Instances"));
b.add_output<decl::String>(N_("Remainder"));
b.add_output<decl::Int>(N_("Line")).field_source();
b.add_output<decl::Vector>(N_("Pivot Point")).field_source();
}
static void node_layout(uiLayout *layout, struct bContext *C, PointerRNA *ptr)
@@ -77,6 +79,7 @@ static void node_layout(uiLayout *layout, struct bContext *C, PointerRNA *ptr)
uiItemR(layout, ptr, "overflow", 0, "", ICON_NONE);
uiItemR(layout, ptr, "align_x", 0, "", ICON_NONE);
uiItemR(layout, ptr, "align_y", 0, "", ICON_NONE);
uiItemR(layout, ptr, "pivot_mode", 0, IFACE_("Pivot Point"), ICON_NONE);
}
static void node_init(bNodeTree *UNUSED(ntree), bNode *node)
@@ -87,6 +90,7 @@ static void node_init(bNodeTree *UNUSED(ntree), bNode *node)
data->overflow = GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW;
data->align_x = GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT;
data->align_y = GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE;
data->pivot_mode = GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_LEFT;
node->storage = data;
node->id = (ID *)BKE_vfont_builtin_get();
}
@@ -109,10 +113,61 @@ static void node_update(bNodeTree *ntree, bNode *node)
N_("Text Box Width"));
}
static float3 get_pivot_point(GeoNodeExecParams &params, CurveEval &curve)
{
const NodeGeometryStringToCurves &storage =
*(const NodeGeometryStringToCurves *)params.node().storage;
const GeometryNodeStringToCurvesPivotMode pivot_mode = (GeometryNodeStringToCurvesPivotMode)
storage.pivot_mode;
float3 min(FLT_MAX), max(FLT_MIN), pivot;
curve.bounds_min_max(min, max, false);
/* Check if curve is empty. */
if (min.x == FLT_MAX) {
return {0.0f, 0.0f, 0.0f};
}
switch (pivot_mode) {
case GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_MIDPOINT:
pivot = (min + max) / 2;
break;
case GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_LEFT:
pivot = float3(min.x, min.y, 0.0f);
break;
case GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_CENTER:
pivot = float3((min.x + max.x) / 2, min.y, 0.0f);
break;
case GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_BOTTOM_RIGHT:
pivot = float3(max.x, min.y, 0.0f);
break;
case GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_LEFT:
pivot = float3(min.x, max.y, 0.0f);
break;
case GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_CENTER:
pivot = float3((min.x + max.x) / 2, max.y, 0.0f);
break;
case GEO_NODE_STRING_TO_CURVES_PIVOT_MODE_TOP_RIGHT:
pivot = float3(max.x, max.y, 0.0f);
break;
}
return pivot;
}
struct TextLayout {
/* Position of each character. */
Vector<float2> positions;
/* Line number of each character. */
Vector<int> line_numbers;
/* Map of Pivot point for each character code. */
Map<int, float3> pivot_points;
/* UTF-32 Character codes. */
Array<char32_t> char_codes;
/* The text that fit into the text box, with newline character sequences replaced. */
std::string text;
@@ -194,10 +249,12 @@ static TextLayout get_text_layout(GeoNodeExecParams &params)
Span<CharInfo> info{cu.strinfo, text_len};
layout.final_font_size = cu.fsize_realtime;
layout.positions.reserve(text_len);
layout.line_numbers.reserve(text_len);
for (const int i : IndexRange(text_len)) {
CharTrans &ct = chartransdata[i];
layout.positions.append(float2(ct.xof, ct.yof) * layout.final_font_size);
layout.line_numbers.append(ct.linenr);
if ((info[i].flag & CU_CHINFO_OVERFLOW) && (cu.overflow == CU_OVERFLOW_TRUNCATE)) {
const int offset = BLI_str_utf8_offset_from_index(layout.text.c_str(), i + 1);
@@ -207,6 +264,12 @@ static TextLayout get_text_layout(GeoNodeExecParams &params)
}
}
/* Convert UTF-8 encoded string to UTF-32. */
len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes);
Array<char32_t> char_codes_with_null(len_chars + 1);
BLI_str_utf8_as_utf32(char_codes_with_null.data(), layout.text.c_str(), len_chars + 1);
layout.char_codes = char_codes_with_null.as_span().drop_back(1);
MEM_SAFE_FREE(chartransdata);
MEM_SAFE_FREE(cu.str);
MEM_SAFE_FREE(cu.strinfo);
@@ -217,15 +280,14 @@ static TextLayout get_text_layout(GeoNodeExecParams &params)
/* Returns a mapping of UTF-32 character code to instance handle. */
static Map<int, int> create_curve_instances(GeoNodeExecParams &params,
const float fontsize,
const Span<char32_t> charcodes,
TextLayout &layout,
InstancesComponent &instance_component)
{
VFont *vfont = (VFont *)params.node().id;
Map<int, int> handles;
for (int i : charcodes.index_range()) {
if (handles.contains(charcodes[i])) {
for (int i : layout.char_codes.index_range()) {
if (handles.contains(layout.char_codes[i])) {
continue;
}
Curve cu = {{nullptr}};
@@ -235,36 +297,103 @@ static Map<int, int> create_curve_instances(GeoNodeExecParams &params,
CharInfo charinfo = {0};
charinfo.mat_nr = 1;
BKE_vfont_build_char(&cu, &cu.nurb, charcodes[i], &charinfo, 0, 0, 0, i, 1);
BKE_vfont_build_char(&cu, &cu.nurb, layout.char_codes[i], &charinfo, 0, 0, 0, i, 1);
std::unique_ptr<CurveEval> curve_eval = curve_eval_from_dna_curve(cu);
BKE_nurbList_free(&cu.nurb);
float3 pivot_point = get_pivot_point(params, *curve_eval);
layout.pivot_points.add_new(layout.char_codes[i], pivot_point);
float4x4 size_matrix = float4x4::identity();
size_matrix.apply_scale(fontsize);
size_matrix.apply_scale(layout.final_font_size);
curve_eval->transform(size_matrix);
GeometrySet geometry_set_curve = GeometrySet::create_with_curve(curve_eval.release());
handles.add_new(charcodes[i], instance_component.add_reference(std::move(geometry_set_curve)));
handles.add_new(layout.char_codes[i],
instance_component.add_reference(std::move(geometry_set_curve)));
}
return handles;
}
static void add_instances_from_handles(InstancesComponent &instances,
const Map<int, int> &char_handles,
const Span<char32_t> charcodes,
const Span<float2> positions)
const TextLayout &layout)
{
instances.resize(positions.size());
instances.resize(layout.positions.size());
MutableSpan<int> handles = instances.instance_reference_handles();
MutableSpan<float4x4> transforms = instances.instance_transforms();
threading::parallel_for(IndexRange(positions.size()), 256, [&](IndexRange range) {
threading::parallel_for(IndexRange(layout.positions.size()), 256, [&](IndexRange range) {
for (const int i : range) {
handles[i] = char_handles.lookup(charcodes[i]);
transforms[i] = float4x4::from_location({positions[i].x, positions[i].y, 0});
handles[i] = char_handles.lookup(layout.char_codes[i]);
transforms[i] = float4x4::from_location({layout.positions[i].x, layout.positions[i].y, 0});
}
});
}
static void create_attributes(GeoNodeExecParams &params,
TextLayout &layout,
InstancesComponent &instances)
{
StrongAnonymousAttributeID line_id;
StrongAnonymousAttributeID pivot_id;
if (params.output_is_required("Line")) {
line_id = StrongAnonymousAttributeID("Line");
}
if (params.output_is_required("Pivot Point")) {
pivot_id = StrongAnonymousAttributeID("Pivot");
}
MutableSpan<int> lines;
MutableSpan<float3> pivots;
OutputAttribute_Typed<int> line_attribute;
OutputAttribute_Typed<float3> pivot_attribute;
if (line_id) {
line_attribute = instances.attribute_try_get_for_output_only<int>(line_id.get(),
ATTR_DOMAIN_INSTANCE);
lines = line_attribute.as_span();
}
if (!lines.is_empty()) {
for (const int i : layout.line_numbers.index_range()) {
lines[i] = layout.line_numbers[i];
}
}
if (pivot_id) {
pivot_attribute = instances.attribute_try_get_for_output_only<float3>(pivot_id.get(),
ATTR_DOMAIN_INSTANCE);
pivots = pivot_attribute.as_span();
}
if (!pivots.is_empty()) {
for (const int i : layout.char_codes.index_range()) {
pivots[i] = layout.pivot_points.lookup(layout.char_codes[i]);
}
}
if (line_attribute) {
line_attribute.save();
}
if (pivot_attribute) {
pivot_attribute.save();
}
if (line_id) {
params.set_output("Line",
AnonymousAttributeFieldInput::Create<int>(std::move(line_id),
params.attribute_producer_name()));
}
if (pivot_id) {
params.set_output("Pivot Point",
AnonymousAttributeFieldInput::Create<float3>(
std::move(pivot_id), params.attribute_producer_name()));
}
}
static void node_geo_exec(GeoNodeExecParams params)
{
TextLayout layout = get_text_layout(params);
@@ -276,25 +405,18 @@ static void node_geo_exec(GeoNodeExecParams params)
}
if (layout.positions.size() == 0) {
params.set_output("Curves", GeometrySet());
params.set_output("Curve Instances", GeometrySet());
return;
}
/* Convert UTF-8 encoded string to UTF-32. */
size_t len_bytes;
size_t len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes);
Array<char32_t> char_codes_with_null(len_chars + 1);
BLI_str_utf8_as_utf32(char_codes_with_null.data(), layout.text.c_str(), len_chars + 1);
const Span<char32_t> char_codes = char_codes_with_null.as_span().drop_back(1);
/* Create and add instances. */
GeometrySet geometry_set_out;
InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>();
Map<int, int> char_handles = create_curve_instances(
params, layout.final_font_size, char_codes, instances);
add_instances_from_handles(instances, char_handles, char_codes, layout.positions);
Map<int, int> char_handles = create_curve_instances(params, layout, instances);
add_instances_from_handles(instances, char_handles, layout);
create_attributes(params, layout, instances);
params.set_output("Curves", std::move(geometry_set_out));
params.set_output("Curve Instances", std::move(geometry_set_out));
}
} // namespace blender::nodes::node_geo_string_to_curves_cc