diff options
Diffstat (limited to 'source/blender')
171 files changed, 6447 insertions, 2280 deletions
diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 63d6b9121d2..1f25106404a 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -39,7 +39,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 23 +#define BLENDER_FILE_SUBVERSION 25 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/render/intern/initrender.h b/source/blender/blenkernel/BKE_curve_to_mesh.hh index f5ac352752f..cc1ef08908d 100644 --- a/source/blender/render/intern/initrender.h +++ b/source/blender/blenkernel/BKE_curve_to_mesh.hh @@ -12,27 +12,20 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. - * All rights reserved. - */ - -/** \file - * \ingroup render */ #pragma once -#ifdef __cplusplus -extern "C" { -#endif +struct Mesh; +struct CurveEval; + +/** \file + * \ingroup geo + */ -/* Functions */ +namespace blender::bke { -void RE_parts_init(Render *re); -void RE_parts_free(Render *re); -void RE_parts_clamp(Render *re); +Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile); +Mesh *curve_to_wire_mesh(const CurveEval &curve); -#ifdef __cplusplus -} -#endif +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 98f5de43f84..317d513dae6 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -580,6 +580,9 @@ class InstancesComponent : public GeometryComponent { blender::Span<InstanceReference> references() const; + void ensure_geometry_instances(); + GeometrySet &geometry_set_from_reference(const int reference_index); + blender::Span<int> instance_reference_handles() const; blender::MutableSpan<int> instance_reference_handles(); blender::MutableSpan<blender::float4x4> instance_transforms(); @@ -588,6 +591,7 @@ class InstancesComponent : public GeometryComponent { blender::Span<int> instance_ids() const; int instances_amount() const; + int references_amount() const; blender::Span<int> almost_unique_ids() const; diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h index d472fd6f02b..a9cd553a8fe 100644 --- a/source/blender/blenkernel/BKE_gpencil_geom.h +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -114,7 +114,12 @@ void BKE_gpencil_dissolve_points(struct bGPdata *gpd, bool BKE_gpencil_stroke_stretch(struct bGPDstroke *gps, const float dist, const float overshoot_fac, - const short mode); + const short mode, + const bool follow_curvature, + const int extra_point_count, + const float segment_influence, + const float max_angle, + const bool invert_curvature); bool BKE_gpencil_stroke_trim_points(struct bGPDstroke *gps, const int index_from, const int index_to); diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 8e82ab6d6be..42e2cda8de3 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1494,6 +1494,11 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_MATERIAL_SELECTION 1081 #define GEO_NODE_MATERIAL_ASSIGN 1082 #define GEO_NODE_REALIZE_INSTANCES 1083 +#define GEO_NODE_ATTRIBUTE_STATISTIC 1084 +#define GEO_NODE_CURVE_SAMPLE 1085 +#define GEO_NODE_INPUT_TANGENT 1086 +#define GEO_NODE_STRING_JOIN 1087 +#define GEO_NODE_CURVE_PARAMETER 1088 /** \} */ @@ -1507,6 +1512,9 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define FN_NODE_INPUT_VECTOR 1207 #define FN_NODE_INPUT_STRING 1208 #define FN_NODE_FLOAT_TO_INT 1209 +#define FN_NODE_VALUE_TO_STRING 1210 +#define FN_NODE_STRING_LENGTH 1211 +#define FN_NODE_STRING_SUBSTRING 1212 /** \} */ diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh index 0fbf39a52fa..541ff19c1cd 100644 --- a/source/blender/blenkernel/BKE_spline.hh +++ b/source/blender/blenkernel/BKE_spline.hh @@ -565,6 +565,7 @@ struct CurveEval { blender::Array<int> control_point_offsets() const; blender::Array<int> evaluated_point_offsets() const; + blender::Array<float> accumulated_spline_lengths() const; void assert_valid_point_attributes() const; }; diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 0b082bf1c5a..de7864ef36a 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -116,6 +116,7 @@ set(SRC intern/curve_decimate.c intern/curve_deform.c intern/curve_eval.cc + intern/curve_to_mesh_convert.cc intern/curveprofile.c intern/customdata.c intern/customdata_file.c @@ -332,6 +333,7 @@ set(SRC BKE_cryptomatte.h BKE_cryptomatte.hh BKE_curve.h + BKE_curve_to_mesh.hh BKE_curveprofile.h BKE_customdata.h BKE_customdata_file.h diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc index ea84766943d..8eec7f5dfab 100644 --- a/source/blender/blenkernel/intern/curve_eval.cc +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -143,6 +143,23 @@ blender::Array<int> CurveEval::evaluated_point_offsets() const return offsets; } +/** + * Return the accumulated length at the start of every spline in the curve. + * + * \note The result is one longer than the spline count; the last element is the total length. + */ +blender::Array<float> CurveEval::accumulated_spline_lengths() const +{ + Array<float> spline_lengths(splines_.size() + 1); + float spline_length = 0.0f; + for (const int i : splines_.index_range()) { + spline_lengths[i] = spline_length; + spline_length += splines_[i]->length(); + } + spline_lengths.last() = spline_length; + return spline_lengths; +} + static BezierSpline::HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type) { switch (dna_handle_type) { diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc new file mode 100644 index 00000000000..5f2f945192c --- /dev/null +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -0,0 +1,739 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_array.hh" +#include "BLI_set.hh" +#include "BLI_task.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_attribute_access.hh" +#include "BKE_attribute_math.hh" +#include "BKE_geometry_set.hh" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_spline.hh" + +#include "BKE_curve_to_mesh.hh" + +using blender::fn::GMutableSpan; +using blender::fn::GSpan; +using blender::fn::GVArray_Typed; +using blender::fn::GVArrayPtr; + +namespace blender::bke { + +/** Information about the creation of one curve spline and profile spline combination. */ +struct ResultInfo { + const Spline &spline; + const Spline &profile; + int vert_offset; + int edge_offset; + int loop_offset; + int poly_offset; + int spline_vert_len; + int spline_edge_len; + int profile_vert_len; + int profile_edge_len; +}; + +static void vert_extrude_to_mesh_data(const Spline &spline, + const float3 profile_vert, + MutableSpan<MVert> r_verts, + MutableSpan<MEdge> r_edges, + const int vert_offset, + const int edge_offset) +{ + Span<float3> positions = spline.evaluated_positions(); + + for (const int i : IndexRange(positions.size() - 1)) { + MEdge &edge = r_edges[edge_offset + i]; + edge.v1 = vert_offset + i; + edge.v2 = vert_offset + i + 1; + edge.flag = ME_LOOSEEDGE; + } + + if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) { + MEdge &edge = r_edges[edge_offset + spline.evaluated_edges_size() - 1]; + edge.v1 = vert_offset; + edge.v2 = vert_offset + positions.size() - 1; + edge.flag = ME_LOOSEEDGE; + } + + for (const int i : positions.index_range()) { + MVert &vert = r_verts[vert_offset + i]; + copy_v3_v3(vert.co, positions[i] + profile_vert); + } +} + +static void mark_edges_sharp(MutableSpan<MEdge> edges) +{ + for (MEdge &edge : edges) { + edge.flag |= ME_SHARP; + } +} + +static void spline_extrude_to_mesh_data(const ResultInfo &info, + MutableSpan<MVert> r_verts, + MutableSpan<MEdge> r_edges, + MutableSpan<MLoop> r_loops, + MutableSpan<MPoly> r_polys) +{ + const Spline &spline = info.spline; + const Spline &profile = info.profile; + if (info.profile_vert_len == 1) { + vert_extrude_to_mesh_data(spline, + profile.evaluated_positions()[0], + r_verts, + r_edges, + info.vert_offset, + info.edge_offset); + return; + } + + /* Add the edges running along the length of the curve, starting at each profile vertex. */ + const int spline_edges_start = info.edge_offset; + for (const int i_profile : IndexRange(info.profile_vert_len)) { + const int profile_edge_offset = spline_edges_start + i_profile * info.spline_edge_len; + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; + + const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; + const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; + + MEdge &edge = r_edges[profile_edge_offset + i_ring]; + edge.v1 = ring_vert_offset + i_profile; + edge.v2 = next_ring_vert_offset + i_profile; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + } + + /* Add the edges running along each profile ring. */ + const int profile_edges_start = spline_edges_start + + info.profile_vert_len * info.spline_edge_len; + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; + + const int ring_edge_offset = profile_edges_start + i_ring * info.profile_edge_len; + for (const int i_profile : IndexRange(info.profile_edge_len)) { + const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; + + MEdge &edge = r_edges[ring_edge_offset + i_profile]; + edge.v1 = ring_vert_offset + i_profile; + edge.v2 = ring_vert_offset + i_next_profile; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + } + + /* Calculate poly and corner indices. */ + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; + + const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; + const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; + + const int ring_edge_start = profile_edges_start + info.profile_edge_len * i_ring; + const int next_ring_edge_offset = profile_edges_start + info.profile_edge_len * i_next_ring; + + const int ring_poly_offset = info.poly_offset + i_ring * info.profile_edge_len; + const int ring_loop_offset = info.loop_offset + i_ring * info.profile_edge_len * 4; + + for (const int i_profile : IndexRange(info.profile_edge_len)) { + const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4; + const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; + + const int spline_edge_start = spline_edges_start + info.spline_edge_len * i_profile; + const int next_spline_edge_start = spline_edges_start + + info.spline_edge_len * i_next_profile; + + MPoly &poly = r_polys[ring_poly_offset + i_profile]; + poly.loopstart = ring_segment_loop_offset; + poly.totloop = 4; + poly.flag = ME_SMOOTH; + + MLoop &loop_a = r_loops[ring_segment_loop_offset]; + loop_a.v = ring_vert_offset + i_profile; + loop_a.e = ring_edge_start + i_profile; + MLoop &loop_b = r_loops[ring_segment_loop_offset + 1]; + loop_b.v = ring_vert_offset + i_next_profile; + loop_b.e = next_spline_edge_start + i_ring; + MLoop &loop_c = r_loops[ring_segment_loop_offset + 2]; + loop_c.v = next_ring_vert_offset + i_next_profile; + loop_c.e = next_ring_edge_offset + i_profile; + MLoop &loop_d = r_loops[ring_segment_loop_offset + 3]; + loop_d.v = next_ring_vert_offset + i_profile; + loop_d.e = spline_edge_start + i_ring; + } + } + + /* Calculate the positions of each profile ring profile along the spline. */ + Span<float3> positions = spline.evaluated_positions(); + Span<float3> tangents = spline.evaluated_tangents(); + Span<float3> normals = spline.evaluated_normals(); + Span<float3> profile_positions = profile.evaluated_positions(); + + GVArray_Typed<float> radii = spline.interpolate_to_evaluated(spline.radii()); + for (const int i_ring : IndexRange(info.spline_vert_len)) { + float4x4 point_matrix = float4x4::from_normalized_axis_data( + positions[i_ring], normals[i_ring], tangents[i_ring]); + point_matrix.apply_scale(radii[i_ring]); + + const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; + for (const int i_profile : IndexRange(info.profile_vert_len)) { + MVert &vert = r_verts[ring_vert_start + i_profile]; + copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]); + } + } + + /* Mark edge loops from sharp vector control points sharp. */ + if (profile.type() == Spline::Type::Bezier) { + const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile); + Span<int> control_point_offsets = bezier_spline.control_point_offsets(); + for (const int i : IndexRange(bezier_spline.size())) { + if (bezier_spline.point_is_sharp(i)) { + mark_edges_sharp( + r_edges.slice(spline_edges_start + info.spline_edge_len * control_point_offsets[i], + info.spline_edge_len)); + } + } + } +} + +static inline int spline_extrude_vert_size(const Spline &curve, const Spline &profile) +{ + return curve.evaluated_points_size() * profile.evaluated_points_size(); +} + +static inline int spline_extrude_edge_size(const Spline &curve, const Spline &profile) +{ + /* Add the ring edges, with one ring for every curve vertex, and the edge loops + * that run along the length of the curve, starting on the first profile. */ + return curve.evaluated_points_size() * profile.evaluated_edges_size() + + curve.evaluated_edges_size() * profile.evaluated_points_size(); +} + +static inline int spline_extrude_loop_size(const Spline &curve, const Spline &profile) +{ + return curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4; +} + +static inline int spline_extrude_poly_size(const Spline &curve, const Spline &profile) +{ + return curve.evaluated_edges_size() * profile.evaluated_edges_size(); +} + +struct ResultOffsets { + Array<int> vert; + Array<int> edge; + Array<int> loop; + Array<int> poly; +}; +static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<SplinePtr> curves) +{ + const int total = profiles.size() * curves.size(); + Array<int> vert(total + 1); + Array<int> edge(total + 1); + Array<int> loop(total + 1); + Array<int> poly(total + 1); + + int mesh_index = 0; + int vert_offset = 0; + int edge_offset = 0; + int loop_offset = 0; + int poly_offset = 0; + for (const int i_spline : curves.index_range()) { + for (const int i_profile : profiles.index_range()) { + vert[mesh_index] = vert_offset; + edge[mesh_index] = edge_offset; + loop[mesh_index] = loop_offset; + poly[mesh_index] = poly_offset; + vert_offset += spline_extrude_vert_size(*curves[i_spline], *profiles[i_profile]); + edge_offset += spline_extrude_edge_size(*curves[i_spline], *profiles[i_profile]); + loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile]); + poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile]); + mesh_index++; + } + } + vert.last() = vert_offset; + edge.last() = edge_offset; + loop.last() = loop_offset; + poly.last() = poly_offset; + + return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)}; +} + +static AttributeDomain get_result_attribute_domain(const MeshComponent &component, + const AttributeIDRef &attribute_id) +{ + /* Only use a different domain if it is builtin and must only exist on one domain. */ + if (!component.attribute_is_builtin(attribute_id)) { + return ATTR_DOMAIN_POINT; + } + + std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_id); + if (!meta_data) { + /* This function has to return something in this case, but it shouldn't be used, + * so return an output that will assert later if the code attempts to handle it. */ + return ATTR_DOMAIN_AUTO; + } + + return meta_data->domain; +} + +/** + * The data stored in the attribute and its domain from #OutputAttribute, to avoid calling + * `as_span()` for every single profile and curve spline combination, and for readability. + */ +struct ResultAttributeData { + GMutableSpan data; + AttributeDomain domain; +}; + +static std::optional<ResultAttributeData> create_attribute_and_get_span( + MeshComponent &component, + const AttributeIDRef &attribute_id, + AttributeMetaData meta_data, + Vector<OutputAttribute> &r_attributes) +{ + const AttributeDomain domain = get_result_attribute_domain(component, attribute_id); + OutputAttribute attribute = component.attribute_try_get_for_output_only( + attribute_id, domain, meta_data.data_type); + if (!attribute) { + return std::nullopt; + } + + GMutableSpan span = attribute.as_span(); + r_attributes.append(std::move(attribute)); + return std::make_optional<ResultAttributeData>({span, domain}); +} + +/** + * Store the references to the attribute data from the curve and profile inputs. Here we rely on + * the invariants of the storage of curve attributes, that the order will be consistent between + * splines, and all splines will have the same attributes. + */ +struct ResultAttributes { + /** + * Result attributes on the mesh corresponding to each attribute on the curve input, in the same + * order. The data is optional only in case the attribute does not exist on the mesh for some + * reason, like "shade_smooth" when the result has no faces. + */ + Vector<std::optional<ResultAttributeData>> curve_point_attributes; + Vector<std::optional<ResultAttributeData>> curve_spline_attributes; + + /** + * Result attributes corresponding the attributes on the profile input, in the same order. The + * attributes are optional in case the attribute names correspond to a names used by the curve + * input, in which case the curve input attributes take precedence. + */ + Vector<std::optional<ResultAttributeData>> profile_point_attributes; + Vector<std::optional<ResultAttributeData>> profile_spline_attributes; + + /** + * Because some builtin attributes are not stored contiguously, and the curve inputs might have + * attributes with those names, it's necessary to keep OutputAttributes around to give access to + * the result data in a contiguous array. + */ + Vector<OutputAttribute> attributes; +}; +static ResultAttributes create_result_attributes(const CurveEval &curve, + const CurveEval &profile, + Mesh &mesh) +{ + MeshComponent mesh_component; + mesh_component.replace(&mesh, GeometryOwnershipType::Editable); + Set<AttributeIDRef> curve_attributes; + + /* In order to prefer attributes on the main curve input when there are name collisions, first + * check the attributes on the curve, then add attributes on the profile that are not also on the + * main curve input. */ + ResultAttributes result; + curve.splines().first()->attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + curve_attributes.add_new(id); + result.curve_point_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + return true; + }, + ATTR_DOMAIN_POINT); + curve.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + curve_attributes.add_new(id); + result.curve_spline_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + return true; + }, + ATTR_DOMAIN_CURVE); + profile.splines().first()->attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + if (curve_attributes.contains(id)) { + result.profile_point_attributes.append({}); + } + else { + result.profile_point_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + } + return true; + }, + ATTR_DOMAIN_POINT); + profile.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + if (curve_attributes.contains(id)) { + result.profile_spline_attributes.append({}); + } + else { + result.profile_spline_attributes.append( + create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); + } + return true; + }, + ATTR_DOMAIN_CURVE); + + return result; +} + +template<typename T> +static void copy_curve_point_data_to_mesh_verts(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; + dst.slice(ring_vert_start, info.profile_vert_len).fill(src[i_ring]); + } +} + +template<typename T> +static void copy_curve_point_data_to_mesh_edges(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + const int edges_start = info.edge_offset + info.profile_vert_len * info.spline_edge_len; + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int ring_edge_start = edges_start + info.profile_edge_len * i_ring; + dst.slice(ring_edge_start, info.profile_edge_len).fill(src[i_ring]); + } +} + +template<typename T> +static void copy_curve_point_data_to_mesh_faces(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int ring_face_start = info.poly_offset + info.profile_edge_len * i_ring; + dst.slice(ring_face_start, info.profile_edge_len).fill(src[i_ring]); + } +} + +static void copy_curve_point_attribute_to_mesh(const GSpan src, + const ResultInfo &info, + ResultAttributeData &dst) +{ + GVArrayPtr interpolated_gvarray = info.spline.interpolate_to_evaluated(src); + GSpan interpolated = interpolated_gvarray->get_internal_span(); + + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + switch (dst.domain) { + case ATTR_DOMAIN_POINT: + copy_curve_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_EDGE: + copy_curve_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_FACE: + copy_curve_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_CORNER: + /* Unsupported for now, since there are no builtin attributes to convert into. */ + break; + default: + BLI_assert_unreachable(); + break; + } + }); +} + +template<typename T> +static void copy_profile_point_data_to_mesh_verts(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_vert_len)) { + const int profile_vert_start = info.vert_offset + i_ring * info.profile_vert_len; + for (const int i_profile : IndexRange(info.profile_vert_len)) { + dst[profile_vert_start + i_profile] = src[i_profile]; + } + } +} + +template<typename T> +static void copy_profile_point_data_to_mesh_edges(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_profile : IndexRange(info.profile_vert_len)) { + const int profile_edge_offset = info.edge_offset + i_profile * info.spline_edge_len; + dst.slice(profile_edge_offset, info.spline_edge_len).fill(src[i_profile]); + } +} + +template<typename T> +static void copy_profile_point_data_to_mesh_faces(const Span<T> src, + const ResultInfo &info, + MutableSpan<T> dst) +{ + for (const int i_ring : IndexRange(info.spline_edge_len)) { + const int profile_face_start = info.poly_offset + i_ring * info.profile_edge_len; + for (const int i_profile : IndexRange(info.profile_edge_len)) { + dst[profile_face_start + i_profile] = src[i_profile]; + } + } +} + +static void copy_profile_point_attribute_to_mesh(const GSpan src, + const ResultInfo &info, + ResultAttributeData &dst) +{ + GVArrayPtr interpolated_gvarray = info.profile.interpolate_to_evaluated(src); + GSpan interpolated = interpolated_gvarray->get_internal_span(); + + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + switch (dst.domain) { + case ATTR_DOMAIN_POINT: + copy_profile_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_EDGE: + copy_profile_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_FACE: + copy_profile_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); + break; + case ATTR_DOMAIN_CORNER: + /* Unsupported for now, since there are no builtin attributes to convert into. */ + break; + default: + BLI_assert_unreachable(); + break; + } + }); +} + +static void copy_point_domain_attributes_to_mesh(const ResultInfo &info, + ResultAttributes &attributes) +{ + if (!attributes.curve_point_attributes.is_empty()) { + int i = 0; + info.spline.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.curve_point_attributes[i]) { + copy_curve_point_attribute_to_mesh(*info.spline.attributes.get_for_read(id), + info, + *attributes.curve_point_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_POINT); + } + if (!attributes.profile_point_attributes.is_empty()) { + int i = 0; + info.profile.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.profile_point_attributes[i]) { + copy_profile_point_attribute_to_mesh(*info.profile.attributes.get_for_read(id), + info, + *attributes.profile_point_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_POINT); + } +} + +template<typename T> +static void copy_spline_data_to_mesh(Span<T> src, Span<int> offsets, MutableSpan<T> dst) +{ + for (const int i : IndexRange(src.size())) { + dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]); + } +} + +/** + * Since the offsets for each combination of curve and profile spline are stored for every mesh + * domain, and this just needs to fill the chunks corresponding to each combination, we can use + * the same function for all mesh domains. + */ +static void copy_spline_attribute_to_mesh(const GSpan src, + const ResultOffsets &offsets, + ResultAttributeData &dst_attribute) +{ + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + switch (dst_attribute.domain) { + case ATTR_DOMAIN_POINT: + copy_spline_data_to_mesh(src.typed<T>(), offsets.vert, dst_attribute.data.typed<T>()); + break; + case ATTR_DOMAIN_EDGE: + copy_spline_data_to_mesh(src.typed<T>(), offsets.edge, dst_attribute.data.typed<T>()); + break; + case ATTR_DOMAIN_FACE: + copy_spline_data_to_mesh(src.typed<T>(), offsets.poly, dst_attribute.data.typed<T>()); + break; + case ATTR_DOMAIN_CORNER: + copy_spline_data_to_mesh(src.typed<T>(), offsets.loop, dst_attribute.data.typed<T>()); + break; + default: + BLI_assert_unreachable(); + break; + } + }); +} + +static void copy_spline_domain_attributes_to_mesh(const CurveEval &curve, + const CurveEval &profile, + const ResultOffsets &offsets, + ResultAttributes &attributes) +{ + if (!attributes.curve_spline_attributes.is_empty()) { + int i = 0; + curve.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.curve_spline_attributes[i]) { + copy_spline_attribute_to_mesh(*curve.attributes.get_for_read(id), + offsets, + *attributes.curve_spline_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_CURVE); + } + if (!attributes.profile_spline_attributes.is_empty()) { + int i = 0; + profile.attributes.foreach_attribute( + [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { + if (attributes.profile_spline_attributes[i]) { + copy_spline_attribute_to_mesh(*profile.attributes.get_for_read(id), + offsets, + *attributes.profile_spline_attributes[i]); + } + i++; + return true; + }, + ATTR_DOMAIN_CURVE); + } +} + +/** + * Extrude all splines in the profile curve along the path of every spline in the curve input. + * Transfer curve attributes to the mesh. + * + * \note Normal calculation is by far the slowest part of calculations relating to the result mesh. + * Although it would be a sensible decision to use the better topology information available while + * generating the mesh to also generate the normals, that work may wasted if the output mesh is + * changed anyway in a way that affects the normals. So currently this code uses the safer / + * simpler solution of deferring normal calculation to the rest of Blender. + */ +Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile) +{ + Span<SplinePtr> profiles = profile.splines(); + Span<SplinePtr> curves = curve.splines(); + + const ResultOffsets offsets = calculate_result_offsets(profiles, curves); + if (offsets.vert.last() == 0) { + return nullptr; + } + + Mesh *mesh = BKE_mesh_new_nomain( + offsets.vert.last(), offsets.edge.last(), 0, offsets.loop.last(), offsets.poly.last()); + BKE_id_material_eval_ensure_default_slot(&mesh->id); + mesh->flag |= ME_AUTOSMOOTH; + mesh->smoothresh = DEG2RADF(180.0f); + BKE_mesh_normals_tag_dirty(mesh); + + ResultAttributes attributes = create_result_attributes(curve, profile, *mesh); + + threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) { + for (const int i_spline : curves_range) { + const Spline &spline = *curves[i_spline]; + if (spline.evaluated_points_size() == 0) { + continue; + } + const int spline_start_index = i_spline * profiles.size(); + threading::parallel_for(profiles.index_range(), 128, [&](IndexRange profiles_range) { + for (const int i_profile : profiles_range) { + const Spline &profile = *profiles[i_profile]; + const int i_mesh = spline_start_index + i_profile; + ResultInfo info{ + spline, + profile, + offsets.vert[i_mesh], + offsets.edge[i_mesh], + offsets.loop[i_mesh], + offsets.poly[i_mesh], + spline.evaluated_points_size(), + spline.evaluated_edges_size(), + profile.evaluated_points_size(), + profile.evaluated_edges_size(), + }; + + spline_extrude_to_mesh_data(info, + {mesh->mvert, mesh->totvert}, + {mesh->medge, mesh->totedge}, + {mesh->mloop, mesh->totloop}, + {mesh->mpoly, mesh->totpoly}); + + copy_point_domain_attributes_to_mesh(info, attributes); + } + }); + } + }); + + copy_spline_domain_attributes_to_mesh(curve, profile, offsets, attributes); + + for (OutputAttribute &output_attribute : attributes.attributes) { + output_attribute.save(); + } + + return mesh; +} + +static CurveEval get_curve_single_vert() +{ + CurveEval curve; + std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + spline->add_point(float3(0), 0, 0.0f); + curve.add_spline(std::move(spline)); + + return curve; +} + +/** + * Create a loose-edge mesh based on the evaluated path of the curve's splines. + * Transfer curve attributes to the mesh. + */ +Mesh *curve_to_wire_mesh(const CurveEval &curve) +{ + static const CurveEval vert_curve = get_curve_single_vert(); + return curve_to_mesh_sweep(curve, vert_curve); +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index c4e1fe2f8e9..9479d012cb8 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -24,6 +24,7 @@ #include "DNA_collection_types.h" #include "BKE_geometry_set.hh" +#include "BKE_geometry_set_instances.hh" #include "attribute_access_intern.hh" @@ -32,6 +33,7 @@ using blender::Map; using blender::MutableSpan; using blender::Set; using blender::Span; +using blender::VectorSet; /* -------------------------------------------------------------------- */ /** \name Geometry Component Implementation @@ -120,6 +122,52 @@ blender::Span<int> InstancesComponent::instance_ids() const } /** + * If references have a collection or object type, convert them into geometry instances. This + * will join geometry components from nested instances if necessary. After that, the geometry + * sets can be edited. + */ +void InstancesComponent::ensure_geometry_instances() +{ + VectorSet<InstanceReference> new_references; + new_references.reserve(references_.size()); + for (const InstanceReference &reference : references_) { + if (reference.type() == InstanceReference::Type::Object) { + GeometrySet geometry_set; + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + const int handle = instances.add_reference(reference.object()); + instances.add_instance(handle, float4x4::identity()); + new_references.add_new(geometry_set); + } + else if (reference.type() == InstanceReference::Type::Collection) { + GeometrySet geometry_set; + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + const int handle = instances.add_reference(reference.collection()); + instances.add_instance(handle, float4x4::identity()); + new_references.add_new(geometry_set); + } + else { + new_references.add_new(reference); + } + } + references_ = std::move(new_references); +} + +/** + * With write access to the instances component, the data in the instanced geometry sets can be + * changed. This is a function on the component rather than each reference to ensure `const` + * correctness for that reason. + */ +GeometrySet &InstancesComponent::geometry_set_from_reference(const int reference_index) +{ + /* If this assert fails, it means #ensure_geometry_instances must be called first. */ + BLI_assert(references_[reference_index].type() == InstanceReference::Type::GeometrySet); + + /* The const cast is okay because the instance's hash in the set + * is not changed by adjusting the data inside the geometry set. */ + return const_cast<GeometrySet &>(references_[reference_index].geometry_set()); +} + +/** * Returns a handle for the given reference. * If the reference exists already, the handle of the existing reference is returned. * Otherwise a new handle is added. @@ -139,6 +187,11 @@ int InstancesComponent::instances_amount() const return instance_transforms_.size(); } +int InstancesComponent::references_amount() const +{ + return references_.size(); +} + bool InstancesComponent::is_empty() const { return this->instance_reference_handles_.size() == 0; diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc index 8ff026231f5..976b26a1f3a 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.cc +++ b/source/blender/blenkernel/intern/gpencil_geom.cc @@ -541,64 +541,241 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, } /** + * Give extra stroke points before and after the original tip points. + * \param gps: Target stroke + * \param count_before: how many extra points to be added before a stroke + * \param count_after: how many extra points to be added after a stroke + */ +static bool BKE_gpencil_stroke_extra_points(bGPDstroke *gps, + const int count_before, + const int count_after) +{ + bGPDspoint *pts = gps->points; + + BLI_assert(count_before >= 0); + BLI_assert(count_after >= 0); + if (!count_before && !count_after) { + return false; + } + + const int new_count = count_before + count_after + gps->totpoints; + + bGPDspoint *new_pts = (bGPDspoint *)MEM_mallocN(sizeof(bGPDspoint) * new_count, __func__); + + for (int i = 0; i < count_before; i++) { + memcpy(&new_pts[i], &pts[0], sizeof(bGPDspoint)); + } + memcpy(&new_pts[count_before], pts, sizeof(bGPDspoint) * gps->totpoints); + for (int i = new_count - count_after; i < new_count; i++) { + memcpy(&new_pts[i], &pts[gps->totpoints - 1], sizeof(bGPDspoint)); + } + + if (gps->dvert) { + MDeformVert *new_dv = (MDeformVert *)MEM_mallocN(sizeof(MDeformVert) * new_count, __func__); + + for (int i = 0; i < new_count; i++) { + MDeformVert *dv = &gps->dvert[CLAMPIS(i - count_before, 0, gps->totpoints - 1)]; + int inew = i; + new_dv[inew].flag = dv->flag; + new_dv[inew].totweight = dv->totweight; + new_dv[inew].dw = (MDeformWeight *)MEM_mallocN(sizeof(MDeformWeight) * dv->totweight, + __func__); + memcpy(new_dv[inew].dw, dv->dw, sizeof(MDeformWeight) * dv->totweight); + } + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->dvert); + gps->dvert = new_dv; + } + + MEM_freeN(gps->points); + gps->points = new_pts; + gps->totpoints = new_count; + + return true; +} + +/** * Backbone stretch similar to Freestyle. * \param gps: Stroke to sample. - * \param dist: Distance of one segment. - * \param overshoot_fac: How exact is the follow curve algorithm. + * \param dist: Length of the added section. + * \param overshoot_fac: Relative length of the curve which is used to determine the extension. * \param mode: Affect to Start, End or Both extremes (0->Both, 1->Start, 2->End) + * \param follow_curvature: True for approximating curvature of given overshoot. + * \param extra_point_count: When follow_curvature is true, use this amount of extra points */ bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, const float dist, const float overshoot_fac, - const short mode) + const short mode, + const bool follow_curvature, + const int extra_point_count, + const float segment_influence, + const float max_angle, + const bool invert_curvature) { #define BOTH 0 #define START 1 #define END 2 - bGPDspoint *pt = gps->points, *last_pt, *second_last, *next_pt; - int i; - float threshold = (overshoot_fac == 0 ? 0.001f : overshoot_fac); + const bool do_start = ELEM(mode, BOTH, START); + const bool do_end = ELEM(mode, BOTH, END); + float used_percent_length = overshoot_fac; + CLAMP(used_percent_length, 1e-4f, 1.0f); + if (!isfinite(used_percent_length)) { + /* #used_percent_length must always be finite, otherwise a segfault occurs. + * Since this function should never segfault, set #used_percent_length to a safe fallback. */ + /* NOTE: This fallback is used if gps->totpoints == 2, see MOD_gpencillength.c */ + used_percent_length = 0.1f; + } - if (gps->totpoints < 2 || dist < FLT_EPSILON) { + if (gps->totpoints <= 1 || dist < FLT_EPSILON || extra_point_count <= 0) { return false; } - last_pt = &pt[gps->totpoints - 1]; - second_last = &pt[gps->totpoints - 2]; - next_pt = &pt[1]; - - if (mode == BOTH || mode == START) { - float len1 = 0.0f; - i = 1; - while (len1 < threshold && gps->totpoints > i) { - next_pt = &pt[i]; - len1 = len_v3v3(&next_pt->x, &pt->x); - i++; + /* NOTE: When it's just a straight line, we don't need to do the curvature stuff. */ + if (!follow_curvature || gps->totpoints <= 2) { + /* Not following curvature, just straight line. */ + /* NOTE: #overshoot_point_param can not be zero. */ + float overshoot_point_param = used_percent_length * (gps->totpoints - 1); + float result[3]; + + if (do_start) { + int index1 = floor(overshoot_point_param); + int index2 = ceil(overshoot_point_param); + interp_v3_v3v3(result, + &gps->points[index1].x, + &gps->points[index2].x, + fmodf(overshoot_point_param, 1.0f)); + sub_v3_v3(result, &gps->points[0].x); + if (UNLIKELY(is_zero_v3(result))) { + sub_v3_v3v3(result, &gps->points[1].x, &gps->points[0].x); + } + madd_v3_v3fl(&gps->points[0].x, result, -dist / len_v3(result)); + } + + if (do_end) { + int index1 = gps->totpoints - 1 - floor(overshoot_point_param); + int index2 = gps->totpoints - 1 - ceil(overshoot_point_param); + interp_v3_v3v3(result, + &gps->points[index1].x, + &gps->points[index2].x, + fmodf(overshoot_point_param, 1.0f)); + sub_v3_v3(result, &gps->points[gps->totpoints - 1].x); + if (UNLIKELY(is_zero_v3(result))) { + sub_v3_v3v3( + result, &gps->points[gps->totpoints - 2].x, &gps->points[gps->totpoints - 1].x); + } + madd_v3_v3fl(&gps->points[gps->totpoints - 1].x, result, -dist / len_v3(result)); } - float extend1 = (len1 + dist) / len1; - float result1[3]; - - interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1); - copy_v3_v3(&pt->x, result1); + return true; } - if (mode == BOTH || mode == END) { - float len2 = 0.0f; - i = 2; - while (len2 < threshold && gps->totpoints >= i) { - second_last = &pt[gps->totpoints - i]; - len2 = len_v3v3(&last_pt->x, &second_last->x); - i++; + /* Curvature calculation. */ + + /* First allocate the new stroke size. */ + const int first_old_index = do_start ? extra_point_count : 0; + const int last_old_index = gps->totpoints - 1 + first_old_index; + const int orig_totpoints = gps->totpoints; + BKE_gpencil_stroke_extra_points(gps, first_old_index, do_end ? extra_point_count : 0); + + /* The fractional amount of points to query when calculating the average curvature of the + * strokes. */ + const float overshoot_parameter = used_percent_length * (orig_totpoints - 2); + int overshoot_pointcount = ceil(overshoot_parameter); + CLAMP(overshoot_pointcount, 1, orig_totpoints - 2); + + /* Do for both sides without code duplication. */ + float no[3], vec1[3], vec2[3], total_angle[3]; + for (int k = 0; k < 2; k++) { + if ((k == 0 && !do_start) || (k == 1 && !do_end)) { + continue; } - float extend2 = (len2 + dist) / len2; - float result2[3]; - interp_v3_v3v3(result2, &second_last->x, &last_pt->x, extend2); + const int start_i = k == 0 ? first_old_index : + last_old_index; // first_old_index, last_old_index + const int dir_i = 1 - k * 2; // 1, -1 - copy_v3_v3(&last_pt->x, result2); - } + sub_v3_v3v3(vec1, &gps->points[start_i + dir_i].x, &gps->points[start_i].x); + zero_v3(total_angle); + float segment_length = normalize_v3(vec1); + float overshoot_length = 0.0f; + + /* Accumulate rotation angle and length. */ + int j = 0; + for (int i = start_i; j < overshoot_pointcount; i += dir_i, j++) { + /* Don't fully add last segment to get continuity in overshoot_fac. */ + float fac = fmin(overshoot_parameter - j, 1.0f); + + /* Read segments. */ + copy_v3_v3(vec2, vec1); + sub_v3_v3v3(vec1, &gps->points[i + dir_i * 2].x, &gps->points[i + dir_i].x); + const float len = normalize_v3(vec1); + float angle = angle_normalized_v3v3(vec1, vec2) * fac; + + /* Add half of both adjacent legs of the current angle. */ + const float added_len = (segment_length + len) * 0.5f * fac; + overshoot_length += added_len; + segment_length = len; + + if (angle > max_angle) { + continue; + } + if (angle > M_PI * 0.995f) { + continue; + } + + angle *= powf(added_len, segment_influence); + + cross_v3_v3v3(no, vec1, vec2); + normalize_v3_length(no, angle); + add_v3_v3(total_angle, no); + } + if (UNLIKELY(overshoot_length == 0.0f)) { + /* Don't do a proper extension if the used points are all in the same position. */ + continue; + } + + sub_v3_v3v3(vec1, &gps->points[start_i].x, &gps->points[start_i + dir_i].x); + /* In general curvature = 1/radius. For the case without the + * weights introduced by #segment_influence, the calculation is + * curvature = delta angle/delta arclength = len_v3(total_angle) / overshoot_length */ + float curvature = normalize_v3(total_angle) / overshoot_length; + /* Compensate for the weights powf(added_len, segment_influence). */ + curvature /= powf(overshoot_length / fminf(overshoot_parameter, (float)j), segment_influence); + if (invert_curvature) { + curvature = -curvature; + } + const float angle_step = curvature * dist / extra_point_count; + float step_length = dist / extra_point_count; + if (fabsf(angle_step) > FLT_EPSILON) { + /* Make a direct step length from the assigned arc step length. */ + step_length *= sin(angle_step * 0.5f) / (angle_step * 0.5f); + } + else { + zero_v3(total_angle); + } + const float prev_length = normalize_v3_length(vec1, step_length); + + /* Build rotation matrix here to get best performance. */ + float rot[3][3]; + float q[4]; + axis_angle_to_quat(q, total_angle, angle_step); + quat_to_mat3(rot, q); + + /* Rotate the starting direction to account for change in edge lengths. */ + axis_angle_to_quat(q, + total_angle, + fmaxf(0.0f, 1.0f - fabs(segment_influence)) * + (curvature * prev_length - angle_step) / 2.0f); + mul_qt_v3(q, vec1); + + /* Now iteratively accumulate the segments with a rotating added direction. */ + for (int i = start_i - dir_i, j = 0; j < extra_point_count; i -= dir_i, j++) { + mul_v3_m3v3(vec1, rot, vec1); + add_v3_v3v3(&gps->points[i].x, vec1, &gps->points[i + dir_i].x); + } + } return true; } @@ -749,6 +926,7 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo second_last = &pt[gps->totpoints - 2]; + float len; float len1, cut_len1; float len2, cut_len2; len1 = len2 = cut_len1 = cut_len2 = 0.0f; @@ -759,11 +937,13 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo i = 0; index_end = gps->totpoints - 1; while (len1 < dist && gps->totpoints > i + 1) { - len1 += len_v3v3(&pt[i].x, &pt[i + 1].x); + len = len_v3v3(&pt[i].x, &pt[i + 1].x); + len1 += len; cut_len1 = len1 - dist; i++; } index_start = i - 1; + interp_v3_v3v3(&pt[index_start].x, &pt[index_start + 1].x, &pt[index_start].x, cut_len1 / len); } if (mode == END) { @@ -771,18 +951,20 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo i = 2; while (len2 < dist && gps->totpoints >= i) { second_last = &pt[gps->totpoints - i]; - len2 += len_v3v3(&second_last[1].x, &second_last->x); + len = len_v3v3(&second_last[1].x, &second_last->x); + len2 += len; cut_len2 = len2 - dist; i++; } index_end = gps->totpoints - i + 2; + interp_v3_v3v3(&pt[index_end].x, &pt[index_end - 1].x, &pt[index_end].x, cut_len2 / len); } if (index_end <= index_start) { index_start = index_end = 0; /* empty stroke */ } - if ((index_end == index_start + 1) && (cut_len1 + cut_len2 < dist)) { + if ((index_end == index_start + 1) && (cut_len1 + cut_len2 < 0)) { index_start = index_end = 0; /* no length left to cut */ } diff --git a/source/blender/blenkernel/intern/layer.c b/source/blender/blenkernel/intern/layer.c index b489675cd74..434a2296d95 100644 --- a/source/blender/blenkernel/intern/layer.c +++ b/source/blender/blenkernel/intern/layer.c @@ -183,7 +183,6 @@ static ViewLayer *view_layer_add(const char *name) view_layer->passflag = SCE_PASS_COMBINED; view_layer->pass_alpha_threshold = 0.5f; view_layer->cryptomatte_levels = 6; - view_layer->cryptomatte_flag = VIEW_LAYER_CRYPTOMATTE_ACCURATE; BKE_freestyle_config_init(&view_layer->freestyle_config); return view_layer; diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 3fead8b0f39..c60a9104144 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -865,7 +865,9 @@ static void lib_override_library_create_post_process(Main *bmain, Object *ob_ref = (Object *)id_ref; LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { if (BKE_collection_has_object(collection, ob_ref) && - BKE_view_layer_has_collection(view_layer, collection) && + (view_layer != NULL ? + BKE_view_layer_has_collection(view_layer, collection) : + BKE_collection_has_collection(scene->master_collection, collection)) && !ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY(collection)) { default_instantiating_collection = collection; } @@ -897,6 +899,8 @@ static void lib_override_library_create_post_process(Main *bmain, * \note It will override all IDs tagged with \a LIB_TAG_DOIT, and it does not clear that tag at * its beginning, so caller code can add extra data-blocks to be overridden as well. * + * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in + * which case \a scene's master collection children hierarchy is used instead). * \param id_root: The root ID to create an override from. * \param id_reference: Some reference ID used to do some post-processing after overrides have been * created, may be NULL. Typically, the Empty object instantiating the linked collection we @@ -960,6 +964,8 @@ bool BKE_lib_override_library_template_create(struct ID *id) * \note This is a thin wrapper around \a BKE_lib_override_library_create, only extra work is to * actually convert the proxy itself into an override first. * + * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in + * which case \a scene's master collection children hierarchy is used instead). * \return true if override was successfully created. */ bool BKE_lib_override_library_proxy_convert(Main *bmain, @@ -1002,6 +1008,8 @@ bool BKE_lib_override_library_proxy_convert(Main *bmain, * data, from an existing override hierarchy. * * \param id_root: The root liboverride ID to resync from. + * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in + * which case \a scene's master collection children hierarchy is used instead). * \return true if override was successfully resynced. */ bool BKE_lib_override_library_resync(Main *bmain, @@ -1723,6 +1731,9 @@ static int lib_override_libraries_index_define(Main *bmain) * * Then it will handle the resync of necessary IDs (through calls to * #BKE_lib_override_library_resync). + * + * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in + * which case \a scene's master collection children hierarchy is used instead). */ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index 07dc6db05aa..467f7d4543e 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -1118,7 +1118,7 @@ static Mesh *mesh_new_from_mball_object(Object *object) * balls and all evaluated child meta balls (since polygonization is only stored in the mother * ball). * - * We create empty mesh so scripters don't run into None objects. */ + * Create empty mesh so script-authors don't run into None objects. */ if (!DEG_is_evaluated_object(object) || object->runtime.curve_cache == nullptr || BLI_listbase_is_empty(&object->runtime.curve_cache->disp)) { return (Mesh *)BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2); diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 3a76cbf6f84..2d0239740f8 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -5175,6 +5175,7 @@ static void registerGeometryNodes() register_node_type_geo_attribute_randomize(); register_node_type_geo_attribute_remove(); register_node_type_geo_attribute_separate_xyz(); + register_node_type_geo_attribute_statistic(); register_node_type_geo_attribute_transfer(); register_node_type_geo_attribute_vector_math(); register_node_type_geo_attribute_vector_rotate(); @@ -5182,9 +5183,11 @@ static void registerGeometryNodes() register_node_type_geo_bounding_box(); register_node_type_geo_collection_info(); register_node_type_geo_convex_hull(); + register_node_type_geo_curve_sample(); register_node_type_geo_curve_endpoints(); register_node_type_geo_curve_fill(); register_node_type_geo_curve_length(); + register_node_type_geo_curve_parameter(); register_node_type_geo_curve_primitive_bezier_segment(); register_node_type_geo_curve_primitive_circle(); register_node_type_geo_curve_primitive_line(); @@ -5206,6 +5209,7 @@ static void registerGeometryNodes() register_node_type_geo_input_material(); register_node_type_geo_input_normal(); register_node_type_geo_input_position(); + register_node_type_geo_input_tangent(); register_node_type_geo_is_viewport(); register_node_type_geo_join_geometry(); register_node_type_geo_material_assign(); @@ -5232,6 +5236,7 @@ static void registerGeometryNodes() register_node_type_geo_realize_instances(); register_node_type_geo_sample_texture(); register_node_type_geo_select_by_handle_type(); + register_node_type_geo_string_join(); register_node_type_geo_material_selection(); register_node_type_geo_separate_components(); register_node_type_geo_set_position(); @@ -5251,6 +5256,9 @@ static void registerFunctionNodes() register_node_type_fn_input_string(); register_node_type_fn_input_vector(); register_node_type_fn_random_float(); + register_node_type_fn_string_length(); + register_node_type_fn_string_substring(); + register_node_type_fn_value_to_string(); } void BKE_node_system_init(void) diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c index 73e25a22225..4c38536b662 100644 --- a/source/blender/blenkernel/intern/screen.c +++ b/source/blender/blenkernel/intern/screen.c @@ -1679,6 +1679,8 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area) sseq->scopes.sep_waveform_ibuf = NULL; sseq->scopes.vector_ibuf = NULL; sseq->scopes.histogram_ibuf = NULL; + memset(&sseq->runtime, 0x0, sizeof(sseq->runtime)); + } else if (sl->spacetype == SPACE_PROPERTIES) { SpaceProperties *sbuts = (SpaceProperties *)sl; diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc index 79d2137ee84..b36d7a21669 100644 --- a/source/blender/blenkernel/intern/spline_bezier.cc +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -214,6 +214,11 @@ void BezierSpline::ensure_auto_handles() const return; } + if (this->size() == 1) { + auto_handles_dirty_ = false; + return; + } + for (const int i : IndexRange(this->size())) { if (ELEM(HandleType::Auto, handle_types_left_[i], handle_types_right_[i])) { const float3 prev_diff = positions_[i] - previous_position(positions_, is_cyclic_, i); diff --git a/source/blender/blenlib/BLI_array.hh b/source/blender/blenlib/BLI_array.hh index fc8fc615feb..352bf379d4d 100644 --- a/source/blender/blenlib/BLI_array.hh +++ b/source/blender/blenlib/BLI_array.hh @@ -277,6 +277,21 @@ class Array { } /** + * Return a reference to the first element in the array. + * This invokes undefined behavior when the array is empty. + */ + const T &first() const + { + BLI_assert(size_ > 0); + return *data_; + } + T &first() + { + BLI_assert(size_ > 0); + return *data_; + } + + /** * Return a reference to the last element in the array. * This invokes undefined behavior when the array is empty. */ diff --git a/source/blender/blenlib/BLI_uuid.h b/source/blender/blenlib/BLI_uuid.h index 1ce294ed723..9b85f8e65bc 100644 --- a/source/blender/blenlib/BLI_uuid.h +++ b/source/blender/blenlib/BLI_uuid.h @@ -35,24 +35,25 @@ extern "C" { /** * UUID generator for random (version 4) UUIDs. See RFC4122 section 4.4. * This function is not thread-safe. */ -UUID BLI_uuid_generate_random(void); +bUUID BLI_uuid_generate_random(void); /** * Return the UUID nil value, consisting of all-zero fields. */ -UUID BLI_uuid_nil(void); +bUUID BLI_uuid_nil(void); /** Return true only if this is the nil UUID. */ -bool BLI_uuid_is_nil(UUID uuid); +bool BLI_uuid_is_nil(bUUID uuid); /** Compare two UUIDs, return true only if they are equal. */ -bool BLI_uuid_equal(UUID uuid1, UUID uuid2); +bool BLI_uuid_equal(bUUID uuid1, bUUID uuid2); /** * Format UUID as string. * The buffer must be at least 37 bytes (36 bytes for the UUID + terminating 0). + * Use `UUID_STRING_LEN` from DNA_uuid_types.h if you want to use a constant for this. */ -void BLI_uuid_format(char *buffer, UUID uuid) ATTR_NONNULL(); +void BLI_uuid_format(char *buffer, bUUID uuid) ATTR_NONNULL(); /** * Parse a string as UUID. @@ -62,7 +63,7 @@ void BLI_uuid_format(char *buffer, UUID uuid) ATTR_NONNULL(); * Return true if the string could be parsed, and false otherwise. In the latter case, the UUID may * have been partially updated. */ -bool BLI_uuid_parse_string(UUID *uuid, const char *buffer) ATTR_NONNULL(); +bool BLI_uuid_parse_string(bUUID *uuid, const char *buffer) ATTR_NONNULL(); #ifdef __cplusplus } @@ -70,6 +71,6 @@ bool BLI_uuid_parse_string(UUID *uuid, const char *buffer) ATTR_NONNULL(); # include <ostream> /** Output the UUID as formatted ASCII string, see #BLI_uuid_format(). */ -std::ostream &operator<<(std::ostream &stream, UUID uuid); +std::ostream &operator<<(std::ostream &stream, bUUID uuid); #endif diff --git a/source/blender/blenlib/intern/uuid.cc b/source/blender/blenlib/intern/uuid.cc index f5edb356acc..ae34bcb3d32 100644 --- a/source/blender/blenlib/intern/uuid.cc +++ b/source/blender/blenlib/intern/uuid.cc @@ -27,9 +27,9 @@ #include <string> /* Ensure the UUID struct doesn't have any padding, to be compatible with memcmp(). */ -static_assert(sizeof(UUID) == 16, "expect UUIDs to be 128 bit exactly"); +static_assert(sizeof(bUUID) == 16, "expect UUIDs to be 128 bit exactly"); -UUID BLI_uuid_generate_random() +bUUID BLI_uuid_generate_random() { static std::mt19937_64 rng = []() { std::mt19937_64 rng; @@ -57,7 +57,7 @@ UUID BLI_uuid_generate_random() return rng; }(); - UUID uuid; + bUUID uuid; /* RFC4122 suggests setting certain bits to a fixed value, and then randomizing the remaining * bits. The opposite is easier to implement, though, so that's what's done here. */ @@ -78,23 +78,23 @@ UUID BLI_uuid_generate_random() return uuid; } -UUID BLI_uuid_nil(void) +bUUID BLI_uuid_nil(void) { - const UUID nil = {0, 0, 0, 0, 0, 0}; + const bUUID nil = {0, 0, 0, 0, 0, 0}; return nil; } -bool BLI_uuid_is_nil(UUID uuid) +bool BLI_uuid_is_nil(bUUID uuid) { return BLI_uuid_equal(BLI_uuid_nil(), uuid); } -bool BLI_uuid_equal(const UUID uuid1, const UUID uuid2) +bool BLI_uuid_equal(const bUUID uuid1, const bUUID uuid2) { return std::memcmp(&uuid1, &uuid2, sizeof(uuid1)) == 0; } -void BLI_uuid_format(char *buffer, const UUID uuid) +void BLI_uuid_format(char *buffer, const bUUID uuid) { std::sprintf(buffer, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", @@ -111,7 +111,7 @@ void BLI_uuid_format(char *buffer, const UUID uuid) uuid.node[5]); } -bool BLI_uuid_parse_string(UUID *uuid, const char *buffer) +bool BLI_uuid_parse_string(bUUID *uuid, const char *buffer) { const int num_fields_parsed = std::sscanf( buffer, @@ -130,7 +130,7 @@ bool BLI_uuid_parse_string(UUID *uuid, const char *buffer) return num_fields_parsed == 11; } -std::ostream &operator<<(std::ostream &stream, UUID uuid) +std::ostream &operator<<(std::ostream &stream, bUUID uuid) { std::string buffer(36, '\0'); BLI_uuid_format(buffer.data(), uuid); diff --git a/source/blender/blenlib/tests/BLI_uuid_test.cc b/source/blender/blenlib/tests/BLI_uuid_test.cc index 31c69002c1c..731489c6c9e 100644 --- a/source/blender/blenlib/tests/BLI_uuid_test.cc +++ b/source/blender/blenlib/tests/BLI_uuid_test.cc @@ -20,7 +20,7 @@ TEST(BLI_uuid, generate_random) { - const UUID uuid = BLI_uuid_generate_random(); + const bUUID uuid = BLI_uuid_generate_random(); // The 4 MSbits represent the "version" of the UUID. const uint16_t version = uuid.time_hi_and_version >> 12; @@ -33,11 +33,11 @@ TEST(BLI_uuid, generate_random) TEST(BLI_uuid, generate_many_random) { - const UUID first_uuid = BLI_uuid_generate_random(); + const bUUID first_uuid = BLI_uuid_generate_random(); /* Generate lots of UUIDs to get some indication that the randomness is okay. */ for (int i = 0; i < 1000000; ++i) { - const UUID uuid = BLI_uuid_generate_random(); + const bUUID uuid = BLI_uuid_generate_random(); EXPECT_FALSE(BLI_uuid_equal(first_uuid, uuid)); // Check that the non-random bits are set according to RFC4122. @@ -50,8 +50,8 @@ TEST(BLI_uuid, generate_many_random) TEST(BLI_uuid, nil_value) { - const UUID nil_uuid = BLI_uuid_nil(); - const UUID zeroes_uuid = {0, 0, 0, 0, 0, 0}; + const bUUID nil_uuid = BLI_uuid_nil(); + const bUUID zeroes_uuid = {0, 0, 0, 0, 0, 0}; EXPECT_TRUE(BLI_uuid_equal(nil_uuid, zeroes_uuid)); EXPECT_TRUE(BLI_uuid_is_nil(nil_uuid)); @@ -63,8 +63,8 @@ TEST(BLI_uuid, nil_value) TEST(BLI_uuid, equality) { - const UUID uuid1 = BLI_uuid_generate_random(); - const UUID uuid2 = BLI_uuid_generate_random(); + const bUUID uuid1 = BLI_uuid_generate_random(); + const bUUID uuid2 = BLI_uuid_generate_random(); EXPECT_TRUE(BLI_uuid_equal(uuid1, uuid1)); EXPECT_FALSE(BLI_uuid_equal(uuid1, uuid2)); @@ -72,7 +72,7 @@ TEST(BLI_uuid, equality) TEST(BLI_uuid, string_formatting) { - UUID uuid; + bUUID uuid; std::string buffer(36, '\0'); memset(&uuid, 0, sizeof(uuid)); @@ -91,12 +91,12 @@ TEST(BLI_uuid, string_formatting) EXPECT_EQ("00000001-0002-0003-0405-060000000007", buffer); /* Somewhat more complex bit patterns. This is a version 1 UUID generated from Python. */ - const UUID uuid1 = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}}; + const bUUID uuid1 = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}}; BLI_uuid_format(buffer.data(), uuid1); EXPECT_EQ("d30a0e60-14a2-11ec-8b99-f7736944db8b", buffer); /* Namespace UUID, example listed in RFC4211. */ - const UUID namespace_dns = { + const bUUID namespace_dns = { 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, {0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}}; BLI_uuid_format(buffer.data(), namespace_dns); EXPECT_EQ("6ba7b810-9dad-11d1-80b4-00c04fd430c8", buffer); @@ -104,7 +104,7 @@ TEST(BLI_uuid, string_formatting) TEST(BLI_uuid, string_parsing_ok) { - UUID uuid; + bUUID uuid; std::string buffer(36, '\0'); const bool parsed_ok = BLI_uuid_parse_string(&uuid, "d30a0e60-14a2-11ec-8b99-f7736944db8b"); @@ -115,7 +115,7 @@ TEST(BLI_uuid, string_parsing_ok) TEST(BLI_uuid, string_parsing_capitalisation) { - UUID uuid; + bUUID uuid; std::string buffer(36, '\0'); /* RFC4122 demands acceptance of upper-case hex digits. */ @@ -129,7 +129,7 @@ TEST(BLI_uuid, string_parsing_capitalisation) TEST(BLI_uuid, string_parsing_fail) { - UUID uuid; + bUUID uuid; std::string buffer(36, '\0'); const bool parsed_ok = BLI_uuid_parse_string(&uuid, "d30a0e60!14a2-11ec-8b99-f7736944db8b"); @@ -139,7 +139,7 @@ TEST(BLI_uuid, string_parsing_fail) TEST(BLI_uuid, stream_operator) { std::stringstream ss; - const UUID uuid = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}}; + const bUUID uuid = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}}; ss << uuid; EXPECT_EQ(ss.str(), "d30a0e60-14a2-11ec-8b99-f7736944db8b"); } diff --git a/source/blender/blenloader/intern/versioning_270.c b/source/blender/blenloader/intern/versioning_270.c index fa15e541e43..54d1efab7dd 100644 --- a/source/blender/blenloader/intern/versioning_270.c +++ b/source/blender/blenloader/intern/versioning_270.c @@ -651,13 +651,6 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *bmain) mat->line_col[3] = mat->alpha; } } - - if (!DNA_struct_elem_find(fd->filesdna, "RenderData", "int", "preview_start_resolution")) { - Scene *scene; - for (scene = bmain->scenes.first; scene; scene = scene->id.next) { - scene->r.preview_start_resolution = 64; - } - } } if (!MAIN_VERSION_ATLEAST(bmain, 271, 3)) { @@ -698,15 +691,6 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *bmain) } } - if (!MAIN_VERSION_ATLEAST(bmain, 272, 0)) { - if (!DNA_struct_elem_find(fd->filesdna, "RenderData", "int", "preview_start_resolution")) { - Scene *scene; - for (scene = bmain->scenes.first; scene; scene = scene->id.next) { - scene->r.preview_start_resolution = 64; - } - } - } - if (!MAIN_VERSION_ATLEAST(bmain, 272, 1)) { Brush *br; for (br = bmain->brushes.first; br; br = br->id.next) { diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index f667361d166..69b67460a5d 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -1774,7 +1774,7 @@ static void do_versions_seq_set_cache_defaults(Editing *ed) static bool seq_update_flags_cb(Sequence *seq, void *UNUSED(user_data)) { - seq->flag &= ~(SEQ_FLAG_UNUSED_6 | SEQ_FLAG_UNUSED_18 | SEQ_FLAG_UNUSED_19 | SEQ_FLAG_UNUSED_21); + seq->flag &= ~((1 << 6) | (1 << 18) | (1 << 19) | (1 << 21)); if (seq->type == SEQ_TYPE_SPEED) { SpeedControlVars *s = (SpeedControlVars *)seq->effectdata; s->flags &= ~(SEQ_SPEED_UNUSED_1); @@ -3718,7 +3718,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) STRNCPY(node->idname, "ShaderNodeOutputLight"); } if (node->type == SH_NODE_BSDF_PRINCIPLED && node->custom2 == 0) { - node->custom2 = SHD_SUBSURFACE_BURLEY; + node->custom2 = SHD_SUBSURFACE_DIFFUSION; } } } diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index bafba486c88..be8c4b735be 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -1461,7 +1461,6 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { view_layer->cryptomatte_levels = 6; - view_layer->cryptomatte_flag = VIEW_LAYER_CRYPTOMATTE_ACCURATE; } } } diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 30e7c9bde4c..58265bca238 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -64,6 +64,7 @@ #include "MEM_guardedalloc.h" #include "readfile.h" +#include "SEQ_iterator.h" #include "SEQ_sequencer.h" #include "RNA_access.h" @@ -109,7 +110,8 @@ static void version_idproperty_move_data_int(IDPropertyUIDataInt *ui_data, if (default_value != NULL) { if (default_value->type == IDP_ARRAY) { if (default_value->subtype == IDP_INT) { - ui_data->default_array = MEM_dupallocN(IDP_Array(default_value)); + ui_data->default_array = MEM_malloc_arrayN(default_value->len, sizeof(int), __func__); + memcpy(ui_data->default_array, IDP_Array(default_value), sizeof(int) * default_value->len); ui_data->default_array_len = default_value->len; } } @@ -151,9 +153,18 @@ static void version_idproperty_move_data_float(IDPropertyUIDataFloat *ui_data, IDProperty *default_value = IDP_GetPropertyFromGroup(prop_ui_data, "default"); if (default_value != NULL) { if (default_value->type == IDP_ARRAY) { - if (ELEM(default_value->subtype, IDP_FLOAT, IDP_DOUBLE)) { - ui_data->default_array = MEM_dupallocN(IDP_Array(default_value)); - ui_data->default_array_len = default_value->len; + const int size = default_value->len; + ui_data->default_array_len = size; + if (default_value->subtype == IDP_FLOAT) { + ui_data->default_array = MEM_malloc_arrayN(size, sizeof(double), __func__); + const float *old_default_array = IDP_Array(default_value); + for (int i = 0; i < ui_data->default_array_len; i++) { + ui_data->default_array[i] = (double)old_default_array[i]; + } + } + else if (default_value->subtype == IDP_DOUBLE) { + ui_data->default_array = MEM_malloc_arrayN(size, sizeof(double), __func__); + memcpy(ui_data->default_array, IDP_Array(default_value), sizeof(double) * size); } } else if (ELEM(default_value->type, IDP_DOUBLE, IDP_FLOAT)) { @@ -774,6 +785,69 @@ static void version_geometry_nodes_change_legacy_names(bNodeTree *ntree) } } } +static bool seq_transform_origin_set(Sequence *seq, void *UNUSED(user_data)) +{ + StripTransform *transform = seq->strip->transform; + if (seq->strip->transform != NULL) { + transform->origin[0] = transform->origin[1] = 0.5f; + } + return true; +} + +static void do_version_subsurface_methods(bNode *node) +{ + if (node->type == SH_NODE_SUBSURFACE_SCATTERING) { + if (node->custom1 != SHD_SUBSURFACE_RANDOM_WALK) { + node->custom1 = SHD_SUBSURFACE_RANDOM_WALK_FIXED_RADIUS; + } + } + else if (node->type == SH_NODE_BSDF_PRINCIPLED) { + if (node->custom2 != SHD_SUBSURFACE_RANDOM_WALK) { + node->custom2 = SHD_SUBSURFACE_RANDOM_WALK_FIXED_RADIUS; + } + } +} + +static void version_geometry_nodes_add_attribute_input_settings(NodesModifierData *nmd) +{ + /* Before versioning the properties, make sure it hasn't been done already. */ + LISTBASE_FOREACH (const IDProperty *, property, &nmd->settings.properties->data.group) { + if (strstr(property->name, "_use_attribute") || strstr(property->name, "_attribute_name")) { + return; + } + } + + LISTBASE_FOREACH_MUTABLE (IDProperty *, property, &nmd->settings.properties->data.group) { + if (!ELEM(property->type, IDP_FLOAT, IDP_INT, IDP_ARRAY)) { + continue; + } + + if (strstr(property->name, "_use_attribute") || strstr(property->name, "_attribute_name")) { + continue; + } + + char use_attribute_prop_name[MAX_IDPROP_NAME]; + BLI_snprintf(use_attribute_prop_name, + sizeof(use_attribute_prop_name), + "%s%s", + property->name, + "_use_attribute"); + + IDPropertyTemplate idprop = {0}; + IDProperty *use_attribute_prop = IDP_New(IDP_INT, &idprop, use_attribute_prop_name); + IDP_AddToGroup(nmd->settings.properties, use_attribute_prop); + + char attribute_name_prop_name[MAX_IDPROP_NAME]; + BLI_snprintf(attribute_name_prop_name, + sizeof(attribute_name_prop_name), + "%s%s", + property->name, + "_attribute_name"); + + IDProperty *attribute_prop = IDP_New(IDP_STRING, &idprop, attribute_name_prop_name); + IDP_AddToGroup(nmd->settings.properties, attribute_prop); + } +} /* NOLINTNEXTLINE: readability-function-size */ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) @@ -1290,6 +1364,62 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } + if (!MAIN_VERSION_ATLEAST(bmain, 300, 24)) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + SequencerToolSettings *sequencer_tool_settings = SEQ_tool_settings_ensure(scene); + sequencer_tool_settings->pivot_point = V3D_AROUND_CENTER_MEDIAN; + + if (scene->ed != NULL) { + SEQ_for_each_callback(&scene->ed->seqbase, seq_transform_origin_set, NULL); + } + } + LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype == SPACE_SEQ) { + SpaceSeq *sseq = (SpaceSeq *)sl; + sseq->preview_overlay.flag |= SEQ_PREVIEW_SHOW_OUTLINE_SELECTED; + } + } + } + } + + LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype == SPACE_SEQ) { + ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : + &sl->regionbase; + LISTBASE_FOREACH (ARegion *, region, regionbase) { + if (region->regiontype == RGN_TYPE_WINDOW) { + region->v2d.min[1] = 4.0f; + } + } + } + } + } + } + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 25)) { + FOREACH_NODETREE_BEGIN (bmain, ntree, id) { + if (ntree->type == NTREE_SHADER) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + do_version_subsurface_methods(node); + } + } + } + FOREACH_NODETREE_END; + + enum { + R_EXR_TILE_FILE = (1 << 10), + R_FULL_SAMPLE = (1 << 15), + }; + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + scene->r.scemode &= ~(R_EXR_TILE_FILE | R_FULL_SAMPLE); + } + } + /** * Versioning code until next subversion bump goes here. * @@ -1301,5 +1431,12 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) */ { /* Keep this block, even when empty. */ + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { + if (md->type == eModifierType_Nodes) { + version_geometry_nodes_add_attribute_input_settings((NodesModifierData *)md); + } + } + } } } diff --git a/source/blender/blenloader/intern/versioning_cycles.c b/source/blender/blenloader/intern/versioning_cycles.c index 90e6b43f02e..da57f27af4e 100644 --- a/source/blender/blenloader/intern/versioning_cycles.c +++ b/source/blender/blenloader/intern/versioning_cycles.c @@ -182,8 +182,8 @@ static void displacement_principled_nodes(bNode *node) } } else if (node->type == SH_NODE_BSDF_PRINCIPLED) { - if (node->custom2 != SHD_SUBSURFACE_RANDOM_WALK) { - node->custom2 = SHD_SUBSURFACE_BURLEY; + if (node->custom2 != SHD_SUBSURFACE_RANDOM_WALK_FIXED_RADIUS) { + node->custom2 = SHD_SUBSURFACE_DIFFUSION; } } } @@ -1373,6 +1373,11 @@ void blo_do_versions_cycles(FileData *UNUSED(fd), Library *UNUSED(lib), Main *bm void do_versions_after_linking_cycles(Main *bmain) { + const int DENOISER_AUTO = 0; + const int DENOISER_NLM = 1; + const int DENOISER_OPTIX = 2; + const int DENOISER_OPENIMAGEDENOISE = 4; + if (!MAIN_VERSION_ATLEAST(bmain, 280, 66)) { /* Shader node tree changes. After lib linking so we have all the typeinfo * pointers and updated sockets and we can use the high level node API to @@ -1578,10 +1583,6 @@ void do_versions_after_linking_cycles(Main *bmain) } if (cscene) { - const int DENOISER_AUTO = 0; - const int DENOISER_NLM = 1; - const int DENOISER_OPTIX = 2; - /* Enable denoiser if it was enabled for one view layer before. */ cycles_property_int_set(cscene, "denoiser", (use_optix) ? DENOISER_OPTIX : DENOISER_NLM); cycles_property_boolean_set(cscene, "use_denoising", use_denoising); @@ -1637,4 +1638,17 @@ void do_versions_after_linking_cycles(Main *bmain) object->visibility_flag |= flag; } } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 25)) { + /* Removal of NLM denoiser. */ + for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) { + IDProperty *cscene = cycles_properties_from_ID(&scene->id); + + if (cscene) { + if (cycles_property_int(cscene, "denoiser", DENOISER_NLM) == DENOISER_NLM) { + cycles_property_int_set(cscene, "denoiser", DENOISER_OPENIMAGEDENOISE); + } + } + } + } } diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c index 074cae669af..152ef79a38f 100644 --- a/source/blender/blenloader/intern/versioning_defaults.c +++ b/source/blender/blenloader/intern/versioning_defaults.c @@ -54,6 +54,7 @@ #include "BKE_curveprofile.h" #include "BKE_customdata.h" #include "BKE_gpencil.h" +#include "BKE_idprop.h" #include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" @@ -160,6 +161,7 @@ static void blo_update_defaults_screen(bScreen *screen, seq->render_size = SEQ_RENDER_SIZE_PROXY_100; seq->timeline_overlay.flag |= SEQ_TIMELINE_SHOW_STRIP_SOURCE | SEQ_TIMELINE_SHOW_STRIP_NAME | SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID; + seq->preview_overlay.flag |= SEQ_PREVIEW_SHOW_OUTLINE_SELECTED; } else if (area->spacetype == SPACE_TEXT) { /* Show syntax and line numbers in Script workspace text editor. */ @@ -355,6 +357,12 @@ static void blo_update_defaults_scene(Main *bmain, Scene *scene) if (ts->custom_bevel_profile_preset == NULL) { ts->custom_bevel_profile_preset = BKE_curveprofile_add(PROF_PRESET_LINE); } + + /* Clear ID properties so Cycles gets defaults. */ + IDProperty *idprop = IDP_GetProperties(&scene->id, false); + if (idprop) { + IDP_ClearProperty(idprop); + } } /** @@ -581,6 +589,10 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template) bNodeSocket *roughness_socket = nodeFindSocket(node, SOCK_IN, "Roughness"); bNodeSocketValueFloat *roughness_data = roughness_socket->default_value; roughness_data->value = 0.4f; + node->custom2 = SHD_SUBSURFACE_RANDOM_WALK; + } + else if (node->type == SH_NODE_SUBSURFACE_SCATTERING) { + node->custom1 = SHD_SUBSURFACE_RANDOM_WALK; } } } diff --git a/source/blender/compositor/nodes/COM_IDMaskNode.cc b/source/blender/compositor/nodes/COM_IDMaskNode.cc index b51e79f2dea..761cb8b98cf 100644 --- a/source/blender/compositor/nodes/COM_IDMaskNode.cc +++ b/source/blender/compositor/nodes/COM_IDMaskNode.cc @@ -28,7 +28,7 @@ IDMaskNode::IDMaskNode(bNode *editorNode) : Node(editorNode) /* pass */ } void IDMaskNode::convertToOperations(NodeConverter &converter, - const CompositorContext &context) const + const CompositorContext & /*context*/) const { bNode *bnode = this->getbNode(); @@ -38,7 +38,7 @@ void IDMaskNode::convertToOperations(NodeConverter &converter, converter.addOperation(operation); converter.mapInputSocket(getInputSocket(0), operation->getInputSocket(0)); - if (bnode->custom2 == 0 || context.getRenderData()->scemode & R_FULL_SAMPLE) { + if (bnode->custom2 == 0) { converter.mapOutputSocket(getOutputSocket(0), operation->getOutputSocket(0)); } else { diff --git a/source/blender/compositor/nodes/COM_ZCombineNode.cc b/source/blender/compositor/nodes/COM_ZCombineNode.cc index ddf66740578..e29748dc317 100644 --- a/source/blender/compositor/nodes/COM_ZCombineNode.cc +++ b/source/blender/compositor/nodes/COM_ZCombineNode.cc @@ -31,9 +31,9 @@ namespace blender::compositor { void ZCombineNode::convertToOperations(NodeConverter &converter, - const CompositorContext &context) const + const CompositorContext & /*context*/) const { - if ((context.getRenderData()->scemode & R_FULL_SAMPLE) || this->getbNode()->custom2) { + if (this->getbNode()->custom2) { ZCombineOperation *operation = nullptr; if (this->getbNode()->custom1) { operation = new ZCombineAlphaOperation(); diff --git a/source/blender/draw/DRW_engine.h b/source/blender/draw/DRW_engine.h index a125a13eaf9..5e7b812c37b 100644 --- a/source/blender/draw/DRW_engine.h +++ b/source/blender/draw/DRW_engine.h @@ -176,6 +176,9 @@ void DRW_deferred_shader_remove(struct GPUMaterial *mat); struct DrawDataList *DRW_drawdatalist_from_id(struct ID *id); void DRW_drawdata_free(struct ID *id); +bool DRW_opengl_context_release(void); +void DRW_opengl_context_activate(bool drw_state); + #ifdef __cplusplus } #endif diff --git a/source/blender/draw/engines/eevee/eevee_cryptomatte.c b/source/blender/draw/engines/eevee/eevee_cryptomatte.c index 76a1b561972..49780abc6f4 100644 --- a/source/blender/draw/engines/eevee/eevee_cryptomatte.c +++ b/source/blender/draw/engines/eevee/eevee_cryptomatte.c @@ -139,8 +139,6 @@ void EEVEE_cryptomatte_renderpasses_init(EEVEE_Data *vedata) g_data->cryptomatte_session = session; g_data->render_passes |= EEVEE_RENDER_PASS_CRYPTOMATTE | EEVEE_RENDER_PASS_VOLUME_LIGHT; - g_data->cryptomatte_accurate_mode = (view_layer->cryptomatte_flag & - VIEW_LAYER_CRYPTOMATTE_ACCURATE) != 0; } } @@ -405,7 +403,6 @@ void EEVEE_cryptomatte_output_accumulate(EEVEE_ViewLayerData *UNUSED(sldata), EE { EEVEE_FramebufferList *fbl = vedata->fbl; EEVEE_StorageList *stl = vedata->stl; - EEVEE_PrivateData *g_data = stl->g_data; EEVEE_EffectsInfo *effects = stl->effects; EEVEE_PassList *psl = vedata->psl; const DRWContextState *draw_ctx = DRW_context_state_get(); @@ -413,10 +410,9 @@ void EEVEE_cryptomatte_output_accumulate(EEVEE_ViewLayerData *UNUSED(sldata), EE const int cryptomatte_levels = view_layer->cryptomatte_levels; const int current_sample = effects->taa_current_sample; - /* In accurate mode all render samples are evaluated. In inaccurate mode this is limited to the - * number of cryptomatte levels. This will reduce the overhead of downloading the GPU buffer and - * integrating it into the accum buffer. */ - if (g_data->cryptomatte_accurate_mode || current_sample < cryptomatte_levels) { + /* Render samples used by cryptomatte are limited to the number of cryptomatte levels. This will + * reduce the overhead of downloading the GPU buffer and integrating it into the accum buffer. */ + if (current_sample < cryptomatte_levels) { static float clear_color[4] = {0.0}; GPU_framebuffer_bind(fbl->cryptomatte_fb); GPU_framebuffer_clear_color(fbl->cryptomatte_fb, clear_color); diff --git a/source/blender/draw/engines/eevee/eevee_effects.c b/source/blender/draw/engines/eevee/eevee_effects.c index 3a38edecec6..d5960ea57d5 100644 --- a/source/blender/draw/engines/eevee/eevee_effects.c +++ b/source/blender/draw/engines/eevee/eevee_effects.c @@ -38,7 +38,8 @@ static struct { struct GPUTexture *color_src; int depth_src_layer; - float cube_texel_size; + /* Size can be vec3. But we only use 2 components in the shader. */ + float texel_size[2]; } e_data = {NULL}; /* Engine data */ #define SETUP_BUFFER(tex, fb, fb_color) \ @@ -259,6 +260,7 @@ void EEVEE_effects_cache_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata) DRW_PASS_CREATE(psl->color_downsample_ps, DRW_STATE_WRITE_COLOR); grp = DRW_shgroup_create(EEVEE_shaders_effect_downsample_sh_get(), psl->color_downsample_ps); DRW_shgroup_uniform_texture_ex(grp, "source", txl->filtered_radiance, GPU_SAMPLER_FILTER); + DRW_shgroup_uniform_vec2(grp, "texelSize", e_data.texel_size, 1); DRW_shgroup_call_procedural_triangles(grp, NULL, 1); } @@ -267,7 +269,7 @@ void EEVEE_effects_cache_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata) grp = DRW_shgroup_create(EEVEE_shaders_effect_downsample_cube_sh_get(), psl->color_downsample_cube_ps); DRW_shgroup_uniform_texture_ref(grp, "source", &e_data.color_src); - DRW_shgroup_uniform_float(grp, "texelSize", &e_data.cube_texel_size, 1); + DRW_shgroup_uniform_float(grp, "texelSize", e_data.texel_size, 1); DRW_shgroup_uniform_int_copy(grp, "Layer", 0); DRW_shgroup_call_instances(grp, NULL, quad, 6); } @@ -277,6 +279,7 @@ void EEVEE_effects_cache_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata) DRW_PASS_CREATE(psl->maxz_downlevel_ps, downsample_write); grp = DRW_shgroup_create(EEVEE_shaders_effect_maxz_downlevel_sh_get(), psl->maxz_downlevel_ps); DRW_shgroup_uniform_texture_ref_ex(grp, "depthBuffer", &txl->maxzbuffer, GPU_SAMPLER_DEFAULT); + DRW_shgroup_uniform_vec2(grp, "texelSize", e_data.texel_size, 1); DRW_shgroup_call(grp, quad, NULL); /* Copy depth buffer to top level of HiZ */ @@ -345,16 +348,22 @@ static void min_downsample_cb(void *vedata, int UNUSED(level)) } #endif -static void max_downsample_cb(void *vedata, int UNUSED(level)) +static void max_downsample_cb(void *vedata, int level) { EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl; + EEVEE_TextureList *txl = ((EEVEE_Data *)vedata)->txl; + int texture_size[3]; + GPU_texture_get_mipmap_size(txl->maxzbuffer, level - 1, texture_size); + e_data.texel_size[0] = 1.0f / texture_size[0]; + e_data.texel_size[1] = 1.0f / texture_size[1]; DRW_draw_pass(psl->maxz_downlevel_ps); } static void simple_downsample_cube_cb(void *vedata, int level) { EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl; - e_data.cube_texel_size = (float)(1 << level) / (float)GPU_texture_width(e_data.color_src); + e_data.texel_size[0] = (float)(1 << level) / (float)GPU_texture_width(e_data.color_src); + e_data.texel_size[1] = e_data.texel_size[0]; DRW_draw_pass(psl->color_downsample_cube_ps); } @@ -390,9 +399,14 @@ void EEVEE_create_minmax_buffer(EEVEE_Data *vedata, GPUTexture *depth_src, int l } } -static void downsample_radiance_cb(void *vedata, int UNUSED(level)) +static void downsample_radiance_cb(void *vedata, int level) { EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl; + EEVEE_TextureList *txl = ((EEVEE_Data *)vedata)->txl; + int texture_size[3]; + GPU_texture_get_mipmap_size(txl->filtered_radiance, level - 1, texture_size); + e_data.texel_size[0] = 1.0f / texture_size[0]; + e_data.texel_size[1] = 1.0f / texture_size[1]; DRW_draw_pass(psl->color_downsample_ps); } diff --git a/source/blender/draw/engines/eevee/eevee_engine.c b/source/blender/draw/engines/eevee/eevee_engine.c index 6a66e8b1a58..f8e1cc9c923 100644 --- a/source/blender/draw/engines/eevee/eevee_engine.c +++ b/source/blender/draw/engines/eevee/eevee_engine.c @@ -648,6 +648,8 @@ RenderEngineType DRW_engine_viewport_eevee_type = { NULL, NULL, NULL, + NULL, + NULL, &EEVEE_render_update_passes, &draw_engine_eevee_type, {NULL, NULL, NULL}, diff --git a/source/blender/draw/engines/eevee/eevee_private.h b/source/blender/draw/engines/eevee/eevee_private.h index f51b4fa0127..eae5d161cc3 100644 --- a/source/blender/draw/engines/eevee/eevee_private.h +++ b/source/blender/draw/engines/eevee/eevee_private.h @@ -1042,7 +1042,6 @@ typedef struct EEVEE_PrivateData { int aov_hash; int num_aovs_used; struct CryptomatteSession *cryptomatte_session; - bool cryptomatte_accurate_mode; EEVEE_CryptomatteSample *cryptomatte_accum_buffer; float *cryptomatte_download_buffer; diff --git a/source/blender/draw/engines/eevee/shaders/ambient_occlusion_lib.glsl b/source/blender/draw/engines/eevee/shaders/ambient_occlusion_lib.glsl index d4e3b879426..93641443cac 100644 --- a/source/blender/draw/engines/eevee/shaders/ambient_occlusion_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/ambient_occlusion_lib.glsl @@ -379,7 +379,7 @@ float specular_occlusion( /* Visibility to cone angle (eq. 18). */ float vis_angle = fast_acos(sqrt(1 - visibility)); /* Roughness to cone angle (eq. 26). */ - float spec_angle = max(0.001, fast_acos(cone_cosine(roughness))); + float spec_angle = max(0.00990998744964599609375, fast_acos(cone_cosine(roughness))); /* Angle between cone axes. */ float cone_cone_dist = fast_acos(saturate(dot(visibility_dir, specular_dir))); float cone_nor_dist = fast_acos(saturate(dot(N, specular_dir))); diff --git a/source/blender/draw/engines/eevee/shaders/effect_downsample_frag.glsl b/source/blender/draw/engines/eevee/shaders/effect_downsample_frag.glsl index d1cb25af82f..9fc258da185 100644 --- a/source/blender/draw/engines/eevee/shaders/effect_downsample_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/effect_downsample_frag.glsl @@ -9,14 +9,16 @@ uniform sampler2D source; uniform float fireflyFactor; +#ifndef COPY_SRC +uniform vec2 texelSize; +#endif + out vec4 FragColor; void main() { - vec2 texel_size = 1.0 / vec2(textureSize(source, 0)); - vec2 uvs = gl_FragCoord.xy * texel_size; - #ifdef COPY_SRC + vec2 uvs = gl_FragCoord.xy / vec2(textureSize(source, 0)); FragColor = textureLod(source, uvs, 0.0); FragColor = safe_color(FragColor); @@ -25,7 +27,10 @@ void main() FragColor *= 1.0 - max(0.0, luma - fireflyFactor) / luma; #else - vec4 ofs = texel_size.xyxy * vec4(0.75, 0.75, -0.75, -0.75); + /* NOTE(@fclem): textureSize() does not work the same on all implementations + * when changing the min and max texture levels. Use uniform instead (see T87801). */ + vec2 uvs = gl_FragCoord.xy * texelSize; + vec4 ofs = texelSize.xyxy * vec4(0.75, 0.75, -0.75, -0.75); uvs *= 2.0; FragColor = textureLod(source, uvs + ofs.xy, 0.0); diff --git a/source/blender/draw/engines/eevee/shaders/effect_minmaxz_frag.glsl b/source/blender/draw/engines/eevee/shaders/effect_minmaxz_frag.glsl index ccb65d2e5a6..8ef39a55921 100644 --- a/source/blender/draw/engines/eevee/shaders/effect_minmaxz_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/effect_minmaxz_frag.glsl @@ -14,6 +14,10 @@ uniform int depthLayer; uniform sampler2D depthBuffer; #endif +#ifndef COPY_DEPTH +uniform vec2 texelSize; +#endif + #ifdef LAYERED # define sampleLowerMip(t) texture(depthBuffer, vec3(t, depthLayer)).r # define gatherLowerMip(t) textureGather(depthBuffer, vec3(t, depthLayer)) @@ -41,23 +45,24 @@ out vec4 fragColor; void main() { vec2 texel = gl_FragCoord.xy; - vec2 texel_size = 1.0 / vec2(textureSize(depthBuffer, 0).xy); #ifdef COPY_DEPTH - vec2 uv = texel * texel_size; + vec2 uv = texel / vec2(textureSize(depthBuffer, 0).xy); float val = sampleLowerMip(uv); #else - vec2 uv = texel * 2.0 * texel_size; + /* NOTE(@fclem): textureSize() does not work the same on all implementations + * when changing the min and max texture levels. Use uniform instead (see T87801). */ + vec2 uv = texel * 2.0 * texelSize; vec4 samp; # ifdef GPU_ARB_texture_gather samp = gatherLowerMip(uv); # else - samp.x = sampleLowerMip(uv + vec2(-0.5, -0.5) * texel_size); - samp.y = sampleLowerMip(uv + vec2(-0.5, 0.5) * texel_size); - samp.z = sampleLowerMip(uv + vec2(0.5, -0.5) * texel_size); - samp.w = sampleLowerMip(uv + vec2(0.5, 0.5) * texel_size); + samp.x = sampleLowerMip(uv + vec2(-0.5, -0.5) * texelSize); + samp.y = sampleLowerMip(uv + vec2(-0.5, 0.5) * texelSize); + samp.z = sampleLowerMip(uv + vec2(0.5, -0.5) * texelSize); + samp.w = sampleLowerMip(uv + vec2(0.5, 0.5) * texelSize); # endif float val = minmax4(samp.x, samp.y, samp.z, samp.w); diff --git a/source/blender/draw/engines/external/external_engine.c b/source/blender/draw/engines/external/external_engine.c index 89ee3f1b293..cc548a53a8e 100644 --- a/source/blender/draw/engines/external/external_engine.c +++ b/source/blender/draw/engines/external/external_engine.c @@ -32,13 +32,19 @@ #include "BKE_object.h" #include "BKE_particle.h" +#include "ED_image.h" #include "ED_screen.h" +#include "GPU_batch.h" +#include "GPU_debug.h" #include "GPU_matrix.h" #include "GPU_shader.h" #include "GPU_state.h" #include "GPU_viewport.h" +#include "RE_engine.h" +#include "RE_pipeline.h" + #include "external_engine.h" /* own include */ /* Shaders */ @@ -137,6 +143,22 @@ static void external_engine_init(void *vedata) } } +/* Add shading group call which will take care of writing to the depth buffer, so that the + * alpha-under overlay will happen for the render buffer. */ +static void external_cache_image_add(DRWShadingGroup *grp) +{ + float obmat[4][4]; + unit_m4(obmat); + scale_m4_fl(obmat, 0.5f); + + /* NOTE: Use the same Z-depth value as in the regular image drawing engine. */ + translate_m4(obmat, 1.0f, 1.0f, 0.75f); + + GPUBatch *geom = DRW_cache_quad_get(); + + DRW_shgroup_call_obmat(grp, geom, obmat); +} + static void external_cache_init(void *vedata) { EXTERNAL_PassList *psl = ((EXTERNAL_Data *)vedata)->psl; @@ -162,14 +184,33 @@ static void external_cache_init(void *vedata) stl->g_data->depth_shgrp = DRW_shgroup_create(e_data.depth_sh, psl->depth_pass); } - /* Do not draw depth pass when overlays are turned off. */ - stl->g_data->need_depth = (v3d->flag2 & V3D_HIDE_OVERLAYS) == 0; + if (v3d != NULL) { + /* Do not draw depth pass when overlays are turned off. */ + stl->g_data->need_depth = (v3d->flag2 & V3D_HIDE_OVERLAYS) == 0; + } + else if (draw_ctx->space_data != NULL) { + const eSpace_Type space_type = draw_ctx->space_data->spacetype; + if (space_type == SPACE_IMAGE) { + external_cache_image_add(stl->g_data->depth_shgrp); + + stl->g_data->need_depth = true; + stl->g_data->update_depth = true; + } + } } static void external_cache_populate(void *vedata, Object *ob) { + const DRWContextState *draw_ctx = DRW_context_state_get(); EXTERNAL_StorageList *stl = ((EXTERNAL_Data *)vedata)->stl; + if (draw_ctx->space_data != NULL) { + const eSpace_Type space_type = draw_ctx->space_data->spacetype; + if (space_type == SPACE_IMAGE) { + return; + } + } + if (!(DRW_object_is_renderable(ob) && DRW_object_visibility_in_active_context(ob) & OB_VISIBLE_SELF)) { return; @@ -210,13 +251,11 @@ static void external_cache_finish(void *UNUSED(vedata)) { } -static void external_draw_scene_do(void *vedata) +static void external_draw_scene_do_v3d(void *vedata) { const DRWContextState *draw_ctx = DRW_context_state_get(); - Scene *scene = draw_ctx->scene; RegionView3D *rv3d = draw_ctx->rv3d; ARegion *region = draw_ctx->region; - const RenderEngineType *type; DRW_state_reset_ex(DRW_STATE_DEFAULT & ~DRW_STATE_DEPTH_LESS_EQUAL); @@ -229,8 +268,6 @@ static void external_draw_scene_do(void *vedata) } RenderEngine *engine = RE_engine_create(engine_type); - engine->tile_x = scene->r.tilex; - engine->tile_y = scene->r.tiley; engine_type->view_update(engine, draw_ctx->evil_C, draw_ctx->depsgraph); rv3d->render_engine = engine; } @@ -241,7 +278,7 @@ static void external_draw_scene_do(void *vedata) ED_region_pixelspace(region); /* Render result draw. */ - type = rv3d->render_engine->type; + const RenderEngineType *type = rv3d->render_engine->type; type->view_draw(rv3d->render_engine, draw_ctx->evil_C, draw_ctx->depsgraph); GPU_bgl_end(); @@ -259,6 +296,116 @@ static void external_draw_scene_do(void *vedata) } } +/* Configure current matrix stack so that the external engine can use the same drawing code for + * both viewport and image editor drawing. + * + * The engine draws result in the pixel space, and is applying render offset. For image editor we + * need to switch from normalized space to pixel space, and "un-apply" offset. */ +static void external_image_space_matrix_set(const RenderEngine *engine) +{ + BLI_assert(engine != NULL); + + const DRWContextState *draw_ctx = DRW_context_state_get(); + const DRWView *view = DRW_view_get_active(); + struct SpaceImage *space_image = (struct SpaceImage *)draw_ctx->space_data; + + /* Apply current view as transformation matrix. + * This will configure drawing for normalized space with current zoom and pan applied. */ + + float view_matrix[4][4]; + DRW_view_viewmat_get(view, view_matrix, false); + + float projection_matrix[4][4]; + DRW_view_winmat_get(view, projection_matrix, false); + + GPU_matrix_projection_set(projection_matrix); + GPU_matrix_set(view_matrix); + + /* Switch from normalized space to pixel space. */ + { + int width, height; + ED_space_image_get_size(space_image, &width, &height); + + const float width_inv = width ? 1.0f / width : 0.0f; + const float height_inv = height ? 1.0f / height : 0.0f; + GPU_matrix_scale_2f(width_inv, height_inv); + } + + /* Un-apply render offset. */ + { + Render *render = engine->re; + rctf view_rect; + rcti render_rect; + RE_GetViewPlane(render, &view_rect, &render_rect); + + GPU_matrix_translate_2f(-render_rect.xmin, -render_rect.ymin); + } +} + +static void external_draw_scene_do_image(void *UNUSED(vedata)) +{ + const DRWContextState *draw_ctx = DRW_context_state_get(); + Scene *scene = draw_ctx->scene; + Render *re = RE_GetSceneRender(scene); + RenderEngine *engine = RE_engine_get(re); + + /* Is tested before enabling the drawing engine. */ + BLI_assert(re != NULL); + BLI_assert(engine != NULL); + + const DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get(); + + /* Clear the depth buffer to the value used by the background overlay so that the overlay is not + * happening outside of the drawn image. + * + * NOTE: The external engine only draws color. The depth is taken care of using the depth pass + * which initialized the depth to the values expected by the background overlay. */ + GPU_framebuffer_clear_depth(dfbl->default_fb, 1.0f); + + GPU_matrix_push_projection(); + GPU_matrix_push(); + + external_image_space_matrix_set(engine); + + GPU_debug_group_begin("External Engine"); + + const RenderEngineType *engine_type = engine->type; + BLI_assert(engine_type != NULL); + BLI_assert(engine_type->draw != NULL); + + engine_type->draw(engine, draw_ctx->evil_C, draw_ctx->depsgraph); + + GPU_debug_group_end(); + + GPU_matrix_pop(); + GPU_matrix_pop_projection(); + + DRW_state_reset(); + GPU_bgl_end(); + + RE_engine_draw_release(re); +} + +static void external_draw_scene_do(void *vedata) +{ + const DRWContextState *draw_ctx = DRW_context_state_get(); + + if (draw_ctx->v3d != NULL) { + external_draw_scene_do_v3d(vedata); + return; + } + + if (draw_ctx->space_data == NULL) { + return; + } + + const eSpace_Type space_type = draw_ctx->space_data->spacetype; + if (space_type == SPACE_IMAGE) { + external_draw_scene_do_image(vedata); + return; + } +} + static void external_draw_scene(void *vedata) { const DRWContextState *draw_ctx = DRW_context_state_get(); @@ -297,7 +444,7 @@ static void external_engine_free(void) static const DrawEngineDataSize external_data_size = DRW_VIEWPORT_DATA_SIZE(EXTERNAL_Data); -static DrawEngineType draw_engine_external_type = { +DrawEngineType draw_engine_external_type = { NULL, NULL, N_("External"), @@ -330,8 +477,45 @@ RenderEngineType DRW_engine_viewport_external_type = { NULL, NULL, NULL, + NULL, + NULL, &draw_engine_external_type, {NULL, NULL, NULL}, }; +bool DRW_engine_external_acquire_for_image_editor(void) +{ + const DRWContextState *draw_ctx = DRW_context_state_get(); + const SpaceLink *space_data = draw_ctx->space_data; + Scene *scene = draw_ctx->scene; + + if (space_data == NULL) { + return false; + } + + const eSpace_Type space_type = draw_ctx->space_data->spacetype; + if (space_type != SPACE_IMAGE) { + return false; + } + + struct SpaceImage *space_image = (struct SpaceImage *)space_data; + const Image *image = ED_space_image(space_image); + if (image == NULL || image->type != IMA_TYPE_R_RESULT) { + return false; + } + + if (image->render_slot != image->last_render_slot) { + return false; + } + + /* Render is allocated on main thread, so it is safe to access it from here. */ + Render *re = RE_GetSceneRender(scene); + + if (re == NULL) { + return false; + } + + return RE_engine_draw_acquire(re); +} + #undef EXTERNAL_ENGINE diff --git a/source/blender/draw/engines/external/external_engine.h b/source/blender/draw/engines/external/external_engine.h index c645fb99e0e..14ec4e2d3c5 100644 --- a/source/blender/draw/engines/external/external_engine.h +++ b/source/blender/draw/engines/external/external_engine.h @@ -22,4 +22,12 @@ #pragma once +extern DrawEngineType draw_engine_external_type; extern RenderEngineType DRW_engine_viewport_external_type; + +/* Check whether an external engine is to be used to draw content of an image editor. + * If the drawing is possible, the render engine is "acquired" so that it is not freed by the + * render engine for until drawing is finished. + * + * NOTE: Released by the draw engine when it is done drawing. */ +bool DRW_engine_external_acquire_for_image_editor(void); diff --git a/source/blender/draw/engines/select/select_engine.c b/source/blender/draw/engines/select/select_engine.c index 96ab8a28e09..20edd78597b 100644 --- a/source/blender/draw/engines/select/select_engine.c +++ b/source/blender/draw/engines/select/select_engine.c @@ -388,6 +388,8 @@ RenderEngineType DRW_engine_viewport_select_type = { NULL, NULL, NULL, + NULL, + NULL, &draw_engine_select_type, {NULL, NULL, NULL}, }; diff --git a/source/blender/draw/engines/workbench/workbench_engine.c b/source/blender/draw/engines/workbench/workbench_engine.c index f09c019ef8d..635aa7cef25 100644 --- a/source/blender/draw/engines/workbench/workbench_engine.c +++ b/source/blender/draw/engines/workbench/workbench_engine.c @@ -651,6 +651,8 @@ RenderEngineType DRW_engine_viewport_workbench_type = { NULL, NULL, NULL, + NULL, + NULL, &workbench_render_update_passes, &draw_engine_workbench, {NULL, NULL, NULL}, diff --git a/source/blender/draw/intern/DRW_render.h b/source/blender/draw/intern/DRW_render.h index 660a4adaf51..fb8b8536897 100644 --- a/source/blender/draw/intern/DRW_render.h +++ b/source/blender/draw/intern/DRW_render.h @@ -623,6 +623,7 @@ const DRWView *DRW_view_default_get(void); void DRW_view_default_set(DRWView *view); void DRW_view_reset(void); void DRW_view_set_active(DRWView *view); +const DRWView *DRW_view_get_active(void); void DRW_view_clip_planes_set(DRWView *view, float (*planes)[4], int plane_len); void DRW_view_camtexco_set(DRWView *view, float texco[4]); diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index 47adc0acc60..e65fdce5f2e 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -1197,6 +1197,18 @@ static void drw_engines_enable_basic(void) use_drw_engine(&draw_engine_basic_type); } +static void drw_engine_enable_image_editor(void) +{ + if (DRW_engine_external_acquire_for_image_editor()) { + use_drw_engine(&draw_engine_external_type); + } + else { + use_drw_engine(&draw_engine_image_type); + } + + use_drw_engine(&draw_engine_overlay_type); +} + static void drw_engines_enable_editors(void) { SpaceLink *space_data = DST.draw_ctx.space_data; @@ -1205,8 +1217,7 @@ static void drw_engines_enable_editors(void) } if (space_data->spacetype == SPACE_IMAGE) { - use_drw_engine(&draw_engine_image_type); - use_drw_engine(&draw_engine_overlay_type); + drw_engine_enable_image_editor(); } else if (space_data->spacetype == SPACE_NODE) { /* Only enable when drawing the space image backdrop. */ @@ -3188,3 +3199,66 @@ void DRW_draw_state_init_gtests(eGPUShaderConfig sh_cfg) #endif /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Draw manager context release/activation + * + * These functions are used in cases when an OpenGL context creation is needed during the draw. + * This happens, for example, when an external engine needs to create its own OpenGL context from + * the engine initialization. + * + * Example of context creation: + * + * const bool drw_state = DRW_opengl_context_release(); + * gl_context = WM_opengl_context_create(); + * DRW_opengl_context_activate(drw_state); + * + * Example of context destruction: + * + * const bool drw_state = DRW_opengl_context_release(); + * WM_opengl_context_activate(gl_context); + * WM_opengl_context_dispose(gl_context); + * DRW_opengl_context_activate(drw_state); + * + * + * NOTE: Will only perform context modification when on main thread. This way these functions can + * be used in an engine without check on whether it is a draw manager which manages OpenGL context + * on the current thread. The downside of this is that if the engine performs OpenGL creation from + * a non-main thread, that thread is supposed to not have OpenGL context ever bound by Blender. + * + * \{ */ + +bool DRW_opengl_context_release(void) +{ + if (!BLI_thread_is_main()) { + return false; + } + + if (GPU_context_active_get() != DST.gpu_context) { + /* Context release is requested from the outside of the draw manager main draw loop, indicate + * this to the `DRW_opengl_context_activate()` so that it restores drawable of the window. */ + return false; + } + + GPU_context_active_set(NULL); + WM_opengl_context_release(DST.gl_context); + + return true; +} + +void DRW_opengl_context_activate(bool drw_state) +{ + if (!BLI_thread_is_main()) { + return; + } + + if (drw_state) { + WM_opengl_context_activate(DST.gl_context); + GPU_context_active_set(DST.gpu_context); + } + else { + wm_window_reset_drawable(); + } +} + +/** \} */ diff --git a/source/blender/draw/intern/draw_manager_exec.c b/source/blender/draw/intern/draw_manager_exec.c index 22356a3c57b..aa01ca7a262 100644 --- a/source/blender/draw/intern/draw_manager_exec.c +++ b/source/blender/draw/intern/draw_manager_exec.c @@ -367,6 +367,11 @@ void DRW_view_set_active(DRWView *view) DST.view_active = (view) ? view : DST.view_default; } +const DRWView *DRW_view_get_active(void) +{ + return DST.view_active; +} + /* Return True if the given BoundSphere intersect the current view frustum */ static bool draw_culling_sphere_test(const BoundSphere *frustum_bsphere, const float (*frustum_planes)[4], diff --git a/source/blender/editors/animation/anim_deps.c b/source/blender/editors/animation/anim_deps.c index 97679723d84..088de80bb65 100644 --- a/source/blender/editors/animation/anim_deps.c +++ b/source/blender/editors/animation/anim_deps.c @@ -217,8 +217,6 @@ static void animchan_sync_fcurve_scene(bAnimListElem *ale) /* Check if this strip is selected. */ Editing *ed = SEQ_editing_get(scene); seq = SEQ_get_sequence_by_name(ed->seqbasep, seq_name, false); - MEM_freeN(seq_name); - if (seq == NULL) { return; } diff --git a/source/blender/editors/animation/anim_ops.c b/source/blender/editors/animation/anim_ops.c index 450d7cd100e..b4ea33920b2 100644 --- a/source/blender/editors/animation/anim_ops.c +++ b/source/blender/editors/animation/anim_ops.c @@ -241,6 +241,11 @@ static bool use_sequencer_snapping(bContext *C) /* Modal Operator init */ static int change_frame_invoke(bContext *C, wmOperator *op, const wmEvent *event) { + ARegion *region = CTX_wm_region(C); + if (CTX_wm_space_seq(C) != NULL && region->regiontype == RGN_TYPE_PREVIEW) { + return OPERATOR_CANCELLED; + } + /* Change to frame that mouse is over before adding modal handler, * as user could click on a single frame (jump to frame) as well as * click-dragging over a range (modal scrubbing). diff --git a/source/blender/editors/armature/armature_intern.h b/source/blender/editors/armature/armature_intern.h index f9950d27e97..696355324e6 100644 --- a/source/blender/editors/armature/armature_intern.h +++ b/source/blender/editors/armature/armature_intern.h @@ -216,6 +216,7 @@ void POSE_OT_relax(struct wmOperatorType *ot); void POSE_OT_push_rest(struct wmOperatorType *ot); void POSE_OT_relax_rest(struct wmOperatorType *ot); void POSE_OT_breakdown(struct wmOperatorType *ot); +void POSE_OT_blend_to_neighbours(struct wmOperatorType *ot); void POSE_OT_propagate(struct wmOperatorType *ot); diff --git a/source/blender/editors/armature/armature_ops.c b/source/blender/editors/armature/armature_ops.c index fbd89106de5..a1070a8823a 100644 --- a/source/blender/editors/armature/armature_ops.c +++ b/source/blender/editors/armature/armature_ops.c @@ -150,6 +150,7 @@ void ED_operatortypes_armature(void) WM_operatortype_append(POSE_OT_push_rest); WM_operatortype_append(POSE_OT_relax_rest); WM_operatortype_append(POSE_OT_breakdown); + WM_operatortype_append(POSE_OT_blend_to_neighbours); } void ED_operatormacros_armature(void) diff --git a/source/blender/editors/armature/pose_slide.c b/source/blender/editors/armature/pose_slide.c index f23376867af..b273d3aac76 100644 --- a/source/blender/editors/armature/pose_slide.c +++ b/source/blender/editors/armature/pose_slide.c @@ -117,6 +117,7 @@ typedef enum ePoseSlide_Modes { POSESLIDE_BREAKDOWN, POSESLIDE_PUSH_REST, POSESLIDE_RELAX_REST, + POSESLIDE_BLEND, } ePoseSlide_Modes; /** Transforms/Channels to Affect. */ @@ -423,6 +424,25 @@ static void pose_slide_apply_val(tPoseSlideOp *pso, FCurve *fcu, Object *ob, flo (*val) = ((sVal * w2) + (eVal * w1)); break; } + case POSESLIDE_BLEND: /* Blend the current pose with the previous (<50%) or next key (>50%). */ + { + /* FCurve value on current frame. */ + const float cVal = evaluate_fcurve(fcu, cframe); + const float factor = ED_slider_factor_get(pso->slider); + /* Convert factor to absolute 0-1 range. */ + const float blend_factor = fabs((factor - 0.5f) * 2); + + if (factor < 0.5) { + /* Blend to previous key. */ + (*val) = (cVal * (1 - blend_factor)) + (sVal * blend_factor); + } + else { + /* Blend to next key. */ + (*val) = (cVal * (1 - blend_factor)) + (eVal * blend_factor); + } + + break; + } /* Those are handled in pose_slide_rest_pose_apply. */ case POSESLIDE_PUSH_REST: case POSESLIDE_RELAX_REST: { @@ -614,8 +634,7 @@ static void pose_slide_apply_quat(tPoseSlideOp *pso, tPChanFCurveLink *pfl) interp_qt_qtqt(quat_final, quat_prev, quat_next, ED_slider_factor_get(pso->slider)); } - else { - /* POSESLIDE_PUSH and POSESLIDE_RELAX. */ + else if (pso->mode == POSESLIDE_PUSH || pso->mode == POSESLIDE_RELAX) { float quat_breakdown[4]; float quat_curr[4]; @@ -638,6 +657,32 @@ static void pose_slide_apply_quat(tPoseSlideOp *pso, tPChanFCurveLink *pfl) interp_qt_qtqt(quat_final, quat_curr, quat_breakdown, ED_slider_factor_get(pso->slider)); } } + else if (pso->mode == POSESLIDE_BLEND) { + float quat_blend[4]; + float quat_curr[4]; + + copy_qt_qt(quat_curr, pchan->quat); + + if (ED_slider_factor_get(pso->slider) < 0.5) { + quat_blend[0] = evaluate_fcurve(fcu_w, prevFrameF); + quat_blend[1] = evaluate_fcurve(fcu_x, prevFrameF); + quat_blend[2] = evaluate_fcurve(fcu_y, prevFrameF); + quat_blend[3] = evaluate_fcurve(fcu_z, prevFrameF); + } + else { + quat_blend[0] = evaluate_fcurve(fcu_w, nextFrameF); + quat_blend[1] = evaluate_fcurve(fcu_x, nextFrameF); + quat_blend[2] = evaluate_fcurve(fcu_y, nextFrameF); + quat_blend[3] = evaluate_fcurve(fcu_z, nextFrameF); + } + + normalize_qt(quat_blend); + normalize_qt(quat_curr); + + const float blend_factor = fabs((ED_slider_factor_get(pso->slider) - 0.5f) * 2); + + interp_qt_qtqt(quat_final, quat_curr, quat_blend, blend_factor); + } /* Apply final to the pose bone, keeping compatible for similar keyframe positions. */ quat_to_compatible_quat(pchan->quat, quat_final, pchan->quat); @@ -868,6 +913,9 @@ static void pose_slide_draw_status(bContext *C, tPoseSlideOp *pso) case POSESLIDE_BREAKDOWN: strcpy(mode_str, TIP_("Breakdown")); break; + case POSESLIDE_BLEND: + strcpy(mode_str, TIP_("Blend To Neighbour")); + break; default: /* Unknown. */ @@ -1660,6 +1708,56 @@ void POSE_OT_breakdown(wmOperatorType *ot) pose_slide_opdef_properties(ot); } +/* ........................ */ +static int pose_slide_blend_to_neighbours_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + /* Initialize data. */ + if (pose_slide_init(C, op, POSESLIDE_BLEND) == 0) { + pose_slide_exit(C, op); + return OPERATOR_CANCELLED; + } + + /* Do common setup work. */ + return pose_slide_invoke_common(C, op, event); +} + +static int pose_slide_blend_to_neighbours_exec(bContext *C, wmOperator *op) +{ + tPoseSlideOp *pso; + + /* Initialize data (from RNA-props). */ + if (pose_slide_init(C, op, POSESLIDE_BLEND) == 0) { + pose_slide_exit(C, op); + return OPERATOR_CANCELLED; + } + + pso = op->customdata; + + /* Do common exec work. */ + return pose_slide_exec_common(C, op, pso); +} + +void POSE_OT_blend_to_neighbours(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Blend To Neighbour"; + ot->idname = "POSE_OT_blend_to_neighbour"; + ot->description = "Blend from current position to previous or next keyframe"; + + /* Callbacks. */ + ot->exec = pose_slide_blend_to_neighbours_exec; + ot->invoke = pose_slide_blend_to_neighbours_invoke; + ot->modal = pose_slide_modal; + ot->cancel = pose_slide_cancel; + ot->poll = ED_operator_posemode; + + /* Flags. */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X; + + /* Properties. */ + pose_slide_opdef_properties(ot); +} + /* **************************************************** */ /* B) Pose Propagate */ diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index fdd9f44605e..d7cab85abad 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -316,6 +316,9 @@ static void gpencil_stroke_pair_table(bContext *C, if (ELEM(NULL, gps_from, gps_to)) { continue; } + if ((gps_from->totpoints == 0) || (gps_to->totpoints == 0)) { + continue; + } /* Insert the pair entry in the hash table and the list of strokes to keep order. */ BLI_addtail(&tgpil->selected_strokes, BLI_genericNodeN(gps_from)); BLI_ghash_insert(tgpil->pair_strokes, gps_from, gps_to); @@ -1333,6 +1336,9 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) if (ELEM(NULL, gps_from, gps_to)) { continue; } + if ((gps_from->totpoints == 0) || (gps_to->totpoints == 0)) { + continue; + } /* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */ if (gps_from->totpoints > gps_to->totpoints) { diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c index 2e7b0ce532c..58a9f362488 100644 --- a/source/blender/editors/interface/interface_eyedropper.c +++ b/source/blender/editors/interface/interface_eyedropper.c @@ -24,6 +24,8 @@ #include "DNA_screen_types.h" #include "DNA_space_types.h" +#include "BLI_math_color.h" + #include "BKE_context.h" #include "BKE_screen.h" @@ -107,8 +109,13 @@ static void eyedropper_draw_cursor_text_ex(const int x, const int y, const char { const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - const float col_fg[4] = {1.0f, 1.0f, 1.0f, 1.0f}; - const float col_bg[4] = {0.0f, 0.0f, 0.0f, 0.2f}; + /* Use the theme settings from tooltips. */ + const bTheme *btheme = UI_GetTheme(); + const uiWidgetColors *wcol = &btheme->tui.wcol_tooltip; + + float col_fg[4], col_bg[4]; + rgba_uchar_to_float(col_fg, wcol->text); + rgba_uchar_to_float(col_bg, wcol->inner); UI_fontstyle_draw_simple_backdrop(fstyle, x, y + U.widget_unit, name, col_fg, col_bg); } diff --git a/source/blender/editors/interface/interface_style.c b/source/blender/editors/interface/interface_style.c index 804156ba48c..6b1ff92a855 100644 --- a/source/blender/editors/interface/interface_style.c +++ b/source/blender/editors/interface/interface_style.c @@ -312,11 +312,8 @@ void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs, const float decent = BLF_descender(fs->uifont_id); const float margin = height / 4.0f; - /* backdrop */ - const float color[4] = {col_bg[0], col_bg[1], col_bg[2], 0.5f}; - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_aa( + UI_draw_roundbox_4fv( &(const rctf){ .xmin = x - margin, .xmax = x + width + margin, @@ -325,7 +322,7 @@ void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs, }, true, margin, - color); + col_bg); } BLF_position(fs->uifont_id, x, y, 0.0f); diff --git a/source/blender/editors/interface/interface_template_search_menu.c b/source/blender/editors/interface/interface_template_search_menu.c index 672f1b64943..3a5d65475f7 100644 --- a/source/blender/editors/interface/interface_template_search_menu.c +++ b/source/blender/editors/interface/interface_template_search_menu.c @@ -350,24 +350,28 @@ static void menu_types_add_from_keymap_items(bContext *C, if (handler_base->poll == NULL || handler_base->poll(region, win->eventstate)) { wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base; - wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler); - if (keymap && WM_keymap_poll(C, keymap)) { - LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) { - if (kmi->flag & KMI_INACTIVE) { - continue; - } - if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) { - char menu_idname[MAX_NAME]; - RNA_string_get(kmi->ptr, "name", menu_idname); - MenuType *mt = WM_menutype_find(menu_idname, false); - - if (mt && BLI_gset_add(menu_tagged, mt)) { - /* Unlikely, but possible this will be included twice. */ - BLI_linklist_prepend(menuid_stack_p, mt); - - void **kmi_p; - if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) { - *kmi_p = kmi; + wmEventHandler_KeymapResult km_result; + WM_event_get_keymaps_from_handler(wm, handler, &km_result); + for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) { + wmKeyMap *keymap = km_result.keymaps[km_index]; + if (keymap && WM_keymap_poll(C, keymap)) { + LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) { + if (kmi->flag & KMI_INACTIVE) { + continue; + } + if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) { + char menu_idname[MAX_NAME]; + RNA_string_get(kmi->ptr, "name", menu_idname); + MenuType *mt = WM_menutype_find(menu_idname, false); + + if (mt && BLI_gset_add(menu_tagged, mt)) { + /* Unlikely, but possible this will be included twice. */ + BLI_linklist_prepend(menuid_stack_p, mt); + + void **kmi_p; + if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) { + *kmi_p = kmi; + } } } } diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 0c9eb20af19..320371ad9ea 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -5823,6 +5823,11 @@ void uiTemplateRunningJobs(uiLayout *layout, bContext *C) icon = ICON_SEQUENCE; break; } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL)) { + handle_event = B_STOPSEQ; + icon = ICON_SEQUENCE; + break; + } if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_BUILD_PROXY)) { handle_event = B_STOPCLIP; icon = ICON_TRACKER; diff --git a/source/blender/editors/interface/view2d_ops.c b/source/blender/editors/interface/view2d_ops.c index 1fd1b6c984d..4ef4c3dbc6d 100644 --- a/source/blender/editors/interface/view2d_ops.c +++ b/source/blender/editors/interface/view2d_ops.c @@ -147,6 +147,8 @@ static void view_pan_init(bContext *C, wmOperator *op) const float winy = (float)(BLI_rcti_size_y(&vpd->region->winrct) + 1); vpd->facx = (BLI_rctf_size_x(&vpd->v2d->cur)) / winx; vpd->facy = (BLI_rctf_size_y(&vpd->v2d->cur)) / winy; + + vpd->v2d->flag |= V2D_IS_NAVIGATING; } /* apply transform to view (i.e. adjust 'cur' rect) */ @@ -190,6 +192,8 @@ static void view_pan_apply(bContext *C, wmOperator *op) /* Cleanup temp custom-data. */ static void view_pan_exit(wmOperator *op) { + v2dViewPanData *vpd = op->customdata; + vpd->v2d->flag &= ~V2D_IS_NAVIGATING; MEM_SAFE_FREE(op->customdata); } @@ -358,6 +362,7 @@ static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event View2DEdgePanData *vpd = op->customdata; if (event->val == KM_RELEASE || event->type == EVT_ESCKEY) { + vpd->v2d->flag &= ~V2D_IS_NAVIGATING; MEM_SAFE_FREE(op->customdata); return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); } @@ -371,6 +376,8 @@ static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event static void view_edge_pan_cancel(bContext *UNUSED(C), wmOperator *op) { + v2dViewPanData *vpd = op->customdata; + vpd->v2d->flag &= ~V2D_IS_NAVIGATING; MEM_SAFE_FREE(op->customdata); } @@ -680,6 +687,8 @@ static void view_zoomdrag_init(bContext *C, wmOperator *op) vzd->v2d = &vzd->region->v2d; /* False by default. Interactive callbacks (ie invoke()) can set it to true. */ vzd->zoom_to_mouse_pos = false; + + vzd->v2d->flag |= V2D_IS_NAVIGATING; } /* apply transform to view (i.e. adjust 'cur' rect) */ @@ -809,7 +818,8 @@ static void view_zoomstep_apply(bContext *C, wmOperator *op) static void view_zoomstep_exit(wmOperator *op) { UI_view2d_zoom_cache_reset(); - + v2dViewZoomData *vzd = op->customdata; + vzd->v2d->flag &= ~V2D_IS_NAVIGATING; MEM_SAFE_FREE(op->customdata); } @@ -1041,6 +1051,7 @@ static void view_zoomdrag_exit(bContext *C, wmOperator *op) if (op->customdata) { v2dViewZoomData *vzd = op->customdata; + vzd->v2d->flag &= ~V2D_IS_NAVIGATING; if (vzd->timer) { WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer); @@ -1911,6 +1922,8 @@ static void scroller_activate_init(bContext *C, vsm->scrollbar_orig = ((scrollers.vert_max + scrollers.vert_min) / 2) + region->winrct.ymin; } + vsm->v2d->flag |= V2D_IS_NAVIGATING; + ED_region_tag_redraw_no_rebuild(region); } @@ -1921,6 +1934,7 @@ static void scroller_activate_exit(bContext *C, wmOperator *op) v2dScrollerMove *vsm = op->customdata; vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE | V2D_SCROLL_V_ACTIVE); + vsm->v2d->flag &= ~V2D_IS_NAVIGATING; MEM_freeN(op->customdata); op->customdata = NULL; diff --git a/source/blender/editors/mesh/editmesh_extrude_spin_gizmo.c b/source/blender/editors/mesh/editmesh_extrude_spin_gizmo.c index ae37d6c8deb..5faafa77bba 100644 --- a/source/blender/editors/mesh/editmesh_extrude_spin_gizmo.c +++ b/source/blender/editors/mesh/editmesh_extrude_spin_gizmo.c @@ -461,7 +461,7 @@ void MESH_GGT_spin(struct wmGizmoGroupType *gzgt) gzgt->name = "Mesh Spin Init"; gzgt->idname = "MESH_GGT_spin"; - gzgt->flag = WM_GIZMOGROUPTYPE_3D; + gzgt->flag = WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_3D; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; @@ -1063,7 +1063,7 @@ void MESH_GGT_spin_redo(struct wmGizmoGroupType *gzgt) gzgt->name = "Mesh Spin Redo"; gzgt->idname = "MESH_GGT_spin_redo"; - gzgt->flag = WM_GIZMOGROUPTYPE_3D; + gzgt->flag = WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_3D; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; diff --git a/source/blender/editors/object/object_bake_api.c b/source/blender/editors/object/object_bake_api.c index 0a2df655395..26f5b21a311 100644 --- a/source/blender/editors/object/object_bake_api.c +++ b/source/blender/editors/object/object_bake_api.c @@ -412,6 +412,7 @@ static bool is_noncolor_pass(eScenePassType pass_type) { return ELEM(pass_type, SCE_PASS_Z, + SCE_PASS_POSITION, SCE_PASS_NORMAL, SCE_PASS_VECTOR, SCE_PASS_INDEXOB, @@ -554,19 +555,10 @@ static bool bake_pass_filter_check(eScenePassType pass_type, return true; } - if ((pass_filter & R_BAKE_PASS_FILTER_AO) != 0) { - BKE_report( - reports, - RPT_ERROR, - "Combined bake pass Ambient Occlusion contribution requires an enabled light pass " - "(bake the Ambient Occlusion pass type instead)"); - } - else { - BKE_report(reports, - RPT_ERROR, - "Combined bake pass requires Emit, or a light pass with " - "Direct or Indirect contributions enabled"); - } + BKE_report(reports, + RPT_ERROR, + "Combined bake pass requires Emit, or a light pass with " + "Direct or Indirect contributions enabled"); return false; } diff --git a/source/blender/editors/render/render_preview.c b/source/blender/editors/render/render_preview.c index 95351de45f0..81aecfdf788 100644 --- a/source/blender/editors/render/render_preview.c +++ b/source/blender/editors/render/render_preview.c @@ -479,15 +479,6 @@ static Scene *preview_prepare_scene( BKE_color_managed_view_settings_free(&sce->view_settings); BKE_color_managed_view_settings_copy(&sce->view_settings, &scene->view_settings); - /* prevent overhead for small renders and icons (32) */ - if (id && sp->sizex < 40) { - sce->r.tilex = sce->r.tiley = 64; - } - else { - sce->r.tilex = sce->r.xsch / 4; - sce->r.tiley = sce->r.ysch / 4; - } - if ((id && sp->pr_method == PR_ICON_RENDER) && id_type != ID_WO) { sce->r.alphamode = R_ALPHAPREMUL; } diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index 9546035375c..c71e68df2fd 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -1735,10 +1735,14 @@ static void ed_default_handlers( WM_event_add_keymap_handler(handlers, keymap); } if (flag & ED_KEYMAP_TOOL) { - WM_event_add_keymap_handler_dynamic( - ®ion->handlers, WM_event_get_keymap_from_toolsystem_fallback, area); - WM_event_add_keymap_handler_dynamic( - ®ion->handlers, WM_event_get_keymap_from_toolsystem, area); + if (flag & ED_KEYMAP_GIZMO) { + WM_event_add_keymap_handler_dynamic( + ®ion->handlers, WM_event_get_keymap_from_toolsystem_fallback, area); + } + else { + WM_event_add_keymap_handler_dynamic( + ®ion->handlers, WM_event_get_keymap_from_toolsystem, area); + } } if (flag & ED_KEYMAP_FRAMES) { /* frame changing/jumping (for all spaces) */ diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c index 11b06d2b414..f7bdb4326a5 100644 --- a/source/blender/editors/space_file/filesel.c +++ b/source/blender/editors/space_file/filesel.c @@ -1271,7 +1271,7 @@ void file_params_rename_end(wmWindowManager *wm, /* Ensure smooth-scroll timer is active, even if not needed, because that way rename state is * handled properly. */ file_params_invoke_rename_postscroll(wm, win, sfile); - /* Also always activate the rename file, even if renaming was cancelled. */ + /* Also always activate the rename file, even if renaming was canceled. */ file_params_renamefile_activate(sfile, params); } diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index aa241071425..10a3285be8b 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -1446,6 +1446,8 @@ static int node_error_type_to_icon(const geo_log::NodeWarningType type) return ICON_ERROR; case geo_log::NodeWarningType::Info: return ICON_INFO; + case geo_log::NodeWarningType::Legacy: + return ICON_ERROR; } BLI_assert(false); @@ -1456,6 +1458,8 @@ static uint8_t node_error_type_priority(const geo_log::NodeWarningType type) { switch (type) { case geo_log::NodeWarningType::Error: + return 4; + case geo_log::NodeWarningType::Legacy: return 3; case geo_log::NodeWarningType::Warning: return 2; diff --git a/source/blender/editors/space_node/node_intern.h b/source/blender/editors/space_node/node_intern.h index d35fd729131..f069038cc09 100644 --- a/source/blender/editors/space_node/node_intern.h +++ b/source/blender/editors/space_node/node_intern.h @@ -175,6 +175,7 @@ int space_node_view_flag(struct bContext *C, void NODE_OT_view_all(struct wmOperatorType *ot); void NODE_OT_view_selected(struct wmOperatorType *ot); +void NODE_OT_geometry_node_view_legacy(struct wmOperatorType *ot); void NODE_OT_backimage_move(struct wmOperatorType *ot); void NODE_OT_backimage_zoom(struct wmOperatorType *ot); diff --git a/source/blender/editors/space_node/node_ops.c b/source/blender/editors/space_node/node_ops.c index 610c2889e7a..df4f63af20b 100644 --- a/source/blender/editors/space_node/node_ops.c +++ b/source/blender/editors/space_node/node_ops.c @@ -51,6 +51,7 @@ void node_operatortypes(void) WM_operatortype_append(NODE_OT_view_all); WM_operatortype_append(NODE_OT_view_selected); + WM_operatortype_append(NODE_OT_geometry_node_view_legacy); WM_operatortype_append(NODE_OT_mute_toggle); WM_operatortype_append(NODE_OT_hide_toggle); diff --git a/source/blender/editors/space_node/node_view.cc b/source/blender/editors/space_node/node_view.cc index f0db0539c4f..762b4b36a39 100644 --- a/source/blender/editors/space_node/node_view.cc +++ b/source/blender/editors/space_node/node_view.cc @@ -23,8 +23,10 @@ #include "DNA_node_types.h" +#include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_rect.h" +#include "BLI_string_ref.hh" #include "BLI_utildefines.h" #include "BKE_context.h" @@ -54,6 +56,8 @@ #include "node_intern.h" /* own include */ +using blender::StringRef; + /* -------------------------------------------------------------------- */ /** \name View All Operator * \{ */ @@ -700,3 +704,89 @@ void NODE_OT_backimage_sample(wmOperatorType *ot) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View Geometry Nodes Legacy Operator + * + * This operator should be removed when the 2.93 legacy nodes are removed. + * \{ */ + +static int space_node_view_geometry_nodes_legacy(bContext *C, SpaceNode *snode, wmOperator *op) +{ + ARegion *region = CTX_wm_region(C); + + /* Only use the node editor's active node tree. Otherwise this will be too complicated. */ + bNodeTree *node_tree = snode->nodetree; + if (node_tree == nullptr || node_tree->type != NTREE_GEOMETRY) { + return OPERATOR_CANCELLED; + } + + bool found_legacy_node = false; + LISTBASE_FOREACH_BACKWARD (bNode *, node, &node_tree->nodes) { + StringRef idname{node->idname}; + if (idname.find("Legacy") == StringRef::not_found) { + node->flag &= ~NODE_SELECT; + } + else { + found_legacy_node = true; + node->flag |= NODE_SELECT; + } + } + + if (!found_legacy_node) { + WM_report(RPT_INFO, "Legacy node not found, may be in nested node group"); + } + + const int smooth_viewtx = WM_operator_smooth_viewtx_get(op); + if (space_node_view_flag(C, snode, region, NODE_SELECT, smooth_viewtx)) { + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static int geometry_node_view_legacy_exec(bContext *C, wmOperator *op) +{ + /* Allow running this operator directly in a specific node editor. */ + if (SpaceNode *snode = CTX_wm_space_node(C)) { + return space_node_view_geometry_nodes_legacy(C, snode, op); + } + + /* Since the operator is meant to be called from a button in the modifier panel, the node tree + * must be found from the screen, using the largest node editor if there is more than one. */ + if (ScrArea *area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_NODE, 0)) { + if (SpaceNode *snode = static_cast<SpaceNode *>(area->spacedata.first)) { + ScrArea *old_area = CTX_wm_area(C); + ARegion *old_region = CTX_wm_region(C); + + /* Override the context since it is used by the View2D panning code. */ + CTX_wm_area_set(C, area); + CTX_wm_region_set(C, static_cast<ARegion *>(area->regionbase.last)); + const int result = space_node_view_geometry_nodes_legacy(C, snode, op); + CTX_wm_area_set(C, old_area); + CTX_wm_region_set(C, old_region); + return result; + } + } + + return OPERATOR_CANCELLED; +} + +static bool geometry_node_view_legacy_poll(bContext *C) +{ + /* Allow direct execution in a node editor, but also affecting any visible node editor. */ + return ED_operator_node_active(C) || BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_NODE, 0); +} + +void NODE_OT_geometry_node_view_legacy(wmOperatorType *ot) +{ + ot->name = "View Deprecated Geometry Nodes"; + ot->idname = "NODE_OT_geometry_node_view_legacy"; + ot->description = "Select and view legacy geometry nodes in the node editor"; + + ot->exec = geometry_node_view_legacy_exec; + ot->poll = geometry_node_view_legacy_poll; + + ot->flag = OPTYPE_INTERNAL; +} + +/** \} */ diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c index c06a1010168..7cdfb553da5 100644 --- a/source/blender/editors/space_outliner/outliner_draw.c +++ b/source/blender/editors/space_outliner/outliner_draw.c @@ -2358,7 +2358,10 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te) case eGpencilModifierType_Texture: data.icon = ICON_TEXTURE; break; - case eGpencilModifierType_Weight: + case eGpencilModifierType_WeightProximity: + data.icon = ICON_MOD_VERTEX_WEIGHT; + break; + case eGpencilModifierType_WeightAngle: data.icon = ICON_MOD_VERTEX_WEIGHT; break; diff --git a/source/blender/editors/space_outliner/outliner_tree.c b/source/blender/editors/space_outliner/outliner_tree.c index c5ec656080a..5427ae31ac3 100644 --- a/source/blender/editors/space_outliner/outliner_tree.c +++ b/source/blender/editors/space_outliner/outliner_tree.c @@ -1864,6 +1864,15 @@ static void outliner_filter_tree(SpaceOutliner *space_outliner, ViewLayer *view_ space_outliner, view_layer, &space_outliner->tree, search_string, exclude_filter); } +static void outliner_clear_newid_from_main(Main *bmain) +{ + ID *id_iter; + FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { + id_iter->newid = NULL; + } + FOREACH_MAIN_ID_END; +} + /* ======================================================= */ /* Main Tree Building API */ @@ -1926,5 +1935,7 @@ void outliner_build_tree(Main *mainvar, outliner_filter_tree(space_outliner, view_layer); outliner_restore_scrolling_position(space_outliner, region, &focus); - BKE_main_id_newptr_and_tag_clear(mainvar); + /* `ID.newid` pointer is abused when building tree, DO NOT call #BKE_main_id_newptr_and_tag_clear + * as this expects valid IDs in this pointer, not random unknown data. */ + outliner_clear_newid_from_main(mainvar); } diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index 5b39feacfe3..53f1c35776c 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -25,6 +25,7 @@ #include <string.h> #include "BLI_blenlib.h" +#include "BLI_ghash.h" #include "BLI_math.h" #include "BLI_string_utils.h" #include "BLI_threads.h" @@ -44,6 +45,7 @@ #include "BKE_context.h" #include "BKE_fcurve.h" #include "BKE_global.h" +#include "BKE_main.h" #include "BKE_scene.h" #include "BKE_sound.h" @@ -71,6 +73,7 @@ #include "BIF_glutil.h" #include "SEQ_effects.h" +#include "SEQ_iterator.h" #include "SEQ_prefetch.h" #include "SEQ_proxy.h" #include "SEQ_relations.h" @@ -1282,6 +1285,526 @@ static void draw_seq_fcurve_overlay( } } +typedef struct ThumbnailDrawJob { + SeqRenderData context; + GHash *sequences_ghash; + Scene *scene; + rctf *view_area; + float pixelx; + float pixely; +} ThumbnailDrawJob; + +typedef struct ThumbDataItem { + Sequence *seq_dupli; + Scene *scene; +} ThumbDataItem; + +static void thumbnail_hash_data_free(void *val) +{ + ThumbDataItem *item = val; + SEQ_sequence_free(item->scene, item->seq_dupli, 0); + MEM_freeN(val); +} + +static void thumbnail_freejob(void *data) +{ + ThumbnailDrawJob *tj = data; + BLI_ghash_free(tj->sequences_ghash, NULL, thumbnail_hash_data_free); + MEM_freeN(tj->view_area); + MEM_freeN(tj); +} + +static void thumbnail_endjob(void *data) +{ + ThumbnailDrawJob *tj = data; + WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, tj->scene); +} + +static bool check_seq_need_thumbnails(Sequence *seq, rctf *view_area) +{ + if (seq->type != SEQ_TYPE_MOVIE && seq->type != SEQ_TYPE_IMAGE) { + return false; + } + if (min_ii(seq->startdisp, seq->start) > view_area->xmax) { + return false; + } + if (max_ii(seq->enddisp, seq->start + seq->len) < view_area->xmin) { + return false; + } + if (seq->machine + 1.0f < view_area->ymin) { + return false; + } + if (seq->machine > view_area->ymax) { + return false; + } + + return true; +} + +static void seq_get_thumb_image_dimensions(Sequence *seq, + float pixelx, + float pixely, + float *r_thumb_width, + float *r_thumb_height, + float *r_image_width, + float *r_image_height) +{ + float image_width = seq->strip->stripdata->orig_width; + float image_height = seq->strip->stripdata->orig_height; + + /* Fix the dimensions to be max SEQ_RENDER_THUMB_SIZE (256) for x or y. */ + float aspect_ratio = (float)image_width / image_height; + if (image_width > image_height) { + image_width = SEQ_RENDER_THUMB_SIZE; + image_height = round_fl_to_int(image_width / aspect_ratio); + } + else { + image_height = SEQ_RENDER_THUMB_SIZE; + image_width = round_fl_to_int(image_height * aspect_ratio); + } + + /* Calculate thumb dimensions. */ + float thumb_height = (SEQ_STRIP_OFSTOP - SEQ_STRIP_OFSBOTTOM) - (20 * U.dpi_fac * pixely); + aspect_ratio = ((float)image_width) / image_height; + float thumb_h_px = thumb_height / pixely; + float thumb_width = aspect_ratio * thumb_h_px * pixelx; + + if (r_thumb_height == NULL) { + *r_thumb_width = thumb_width; + return; + } + + *r_thumb_height = thumb_height; + *r_image_width = image_width; + *r_image_height = image_height; + *r_thumb_width = thumb_width; +} + +static float seq_thumbnail_get_start_frame(Sequence *seq, float frame_step, rctf *view_area) +{ + if (seq->start > view_area->xmin && seq->start < view_area->xmax) { + return seq->start; + } + + /* Drawing and caching both check to see if strip is in view area or not before calling this + * function so assuming strip/part of strip in view. */ + + int no_invisible_thumbs = (view_area->xmin - seq->start) / frame_step; + return ((no_invisible_thumbs - 1) * frame_step) + seq->start; +} + +static void thumbnail_start_job(void *data, + short *stop, + short *UNUSED(do_update), + float *UNUSED(progress)) +{ + ThumbnailDrawJob *tj = data; + float start_frame, frame_step; + + GHashIterator gh_iter; + BLI_ghashIterator_init(&gh_iter, tj->sequences_ghash); + while (!BLI_ghashIterator_done(&gh_iter) & !*stop) { + Sequence *seq_orig = BLI_ghashIterator_getKey(&gh_iter); + ThumbDataItem *val = BLI_ghash_lookup(tj->sequences_ghash, seq_orig); + + if (check_seq_need_thumbnails(seq_orig, tj->view_area)) { + seq_get_thumb_image_dimensions( + val->seq_dupli, tj->pixelx, tj->pixely, &frame_step, NULL, NULL, NULL); + start_frame = seq_thumbnail_get_start_frame(seq_orig, frame_step, tj->view_area); + SEQ_render_thumbnails( + &tj->context, val->seq_dupli, seq_orig, start_frame, frame_step, tj->view_area, stop); + SEQ_render_thumbnails_base_set(&tj->context, val->seq_dupli, seq_orig, tj->view_area, stop); + } + BLI_ghashIterator_step(&gh_iter); + } +} + +static SeqRenderData sequencer_thumbnail_context_init(const bContext *C) +{ + struct Main *bmain = CTX_data_main(C); + struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Scene *scene = CTX_data_scene(C); + SpaceSeq *sseq = CTX_wm_space_seq(C); + SeqRenderData context = {0}; + + /* Taking rectx and recty as 0 as dimensions not known here, and context is used to calculate + * hash key but not necessary as other variables of SeqRenderData are unique enough. */ + SEQ_render_new_render_data(bmain, depsgraph, scene, 0, 0, sseq->render_size, false, &context); + context.view_id = BKE_scene_multiview_view_id_get(&scene->r, STEREO_LEFT_NAME); + context.use_proxies = false; + + return context; +} + +static GHash *sequencer_thumbnail_ghash_init(const bContext *C, View2D *v2d, Editing *ed) +{ + Scene *scene = CTX_data_scene(C); + + /* Set the data for thumbnail caching job. */ + GHash *thumb_data_hash = BLI_ghash_ptr_new("seq_duplicates_and_origs"); + + LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) { + ThumbDataItem *val_need_update = BLI_ghash_lookup(thumb_data_hash, seq); + if (val_need_update == NULL && check_seq_need_thumbnails(seq, &v2d->cur)) { + ThumbDataItem *val = MEM_callocN(sizeof(ThumbDataItem), "Thumbnail Hash Values"); + val->seq_dupli = SEQ_sequence_dupli_recursive(scene, scene, NULL, seq, 0); + val->scene = scene; + BLI_ghash_insert(thumb_data_hash, seq, val); + } + else { + if (val_need_update != NULL) { + val_need_update->seq_dupli->start = seq->start; + val_need_update->seq_dupli->startdisp = seq->startdisp; + } + } + } + + return thumb_data_hash; +} + +static void sequencer_thumbnail_init_job(const bContext *C, View2D *v2d, Editing *ed) +{ + wmJob *wm_job; + ThumbnailDrawJob *tj = NULL; + ScrArea *area = CTX_wm_area(C); + wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + CTX_data_scene(C), + "Draw Thumbnails", + 0, + WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL); + + /* Get the thumbnail job if it exists. */ + tj = WM_jobs_customdata_get(wm_job); + if (!tj) { + tj = MEM_callocN(sizeof(ThumbnailDrawJob), "Thumbnail cache job"); + + /* Duplicate value of v2d->cur and v2d->tot to have module separation. */ + rctf *view_area = MEM_callocN(sizeof(struct rctf), "viewport area"); + view_area->xmax = v2d->cur.xmax; + view_area->xmin = v2d->cur.xmin; + view_area->ymax = v2d->cur.ymax; + view_area->ymin = v2d->cur.ymin; + + tj->scene = CTX_data_scene(C); + tj->view_area = view_area; + tj->context = sequencer_thumbnail_context_init(C); + tj->sequences_ghash = sequencer_thumbnail_ghash_init(C, v2d, ed); + tj->pixelx = BLI_rctf_size_x(&v2d->cur) / BLI_rcti_size_x(&v2d->mask); + tj->pixely = BLI_rctf_size_y(&v2d->cur) / BLI_rcti_size_y(&v2d->mask); + WM_jobs_customdata_set(wm_job, tj, thumbnail_freejob); + WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_SEQUENCER, NC_SCENE | ND_SEQUENCER); + WM_jobs_callbacks(wm_job, thumbnail_start_job, NULL, NULL, thumbnail_endjob); + } + + if (!WM_jobs_is_running(wm_job)) { + G.is_break = false; + WM_jobs_start(CTX_wm_manager(C), wm_job); + } + else { + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, NULL); + } + + ED_area_tag_redraw(area); +} + +static bool sequencer_thumbnail_v2d_is_navigating(const bContext *C) +{ + ARegion *region = CTX_wm_region(C); + View2D *v2d = ®ion->v2d; + return (v2d->flag & V2D_IS_NAVIGATING) != 0; +} + +static void sequencer_thumbnail_start_job_if_necessary(const bContext *C, + Editing *ed, + View2D *v2d, + bool thumbnail_is_missing) +{ + SpaceSeq *sseq = CTX_wm_space_seq(C); + + if (sequencer_thumbnail_v2d_is_navigating(C)) { + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, NULL); + return; + } + + /* `thumbnail_is_missing` should be set to true if missing image in strip. False when normal call + * to all strips done. */ + if (v2d->cur.xmax != sseq->runtime.last_thumbnail_area.xmax || + v2d->cur.ymax != sseq->runtime.last_thumbnail_area.ymax || thumbnail_is_missing) { + + /* Stop the job first as view has changed. Pointless to continue old job. */ + if (v2d->cur.xmax != sseq->runtime.last_thumbnail_area.xmax || + v2d->cur.ymax != sseq->runtime.last_thumbnail_area.ymax) { + WM_jobs_stop(CTX_wm_manager(C), NULL, thumbnail_start_job); + } + + sequencer_thumbnail_init_job(C, v2d, ed); + sseq->runtime.last_thumbnail_area = v2d->cur; + } +} + +void last_displayed_thumbnails_list_free(void *val) +{ + BLI_gset_free(val, NULL); +} + +static GSet *last_displayed_thumbnails_list_ensure(const bContext *C, Sequence *seq) +{ + SpaceSeq *sseq = CTX_wm_space_seq(C); + if (sseq->runtime.last_displayed_thumbnails == NULL) { + sseq->runtime.last_displayed_thumbnails = BLI_ghash_ptr_new(__func__); + } + + GSet *displayed_thumbnails = BLI_ghash_lookup(sseq->runtime.last_displayed_thumbnails, seq); + if (displayed_thumbnails == NULL) { + displayed_thumbnails = BLI_gset_int_new(__func__); + BLI_ghash_insert(sseq->runtime.last_displayed_thumbnails, seq, displayed_thumbnails); + } + + return displayed_thumbnails; +} + +static void last_displayed_thumbnails_list_cleanup(GSet *previously_displayed, + float range_start, + float range_end) +{ + GSetIterator gset_iter; + BLI_gsetIterator_init(&gset_iter, previously_displayed); + while (!BLI_gsetIterator_done(&gset_iter)) { + int frame = (float)POINTER_AS_INT(BLI_gsetIterator_getKey(&gset_iter)); + BLI_gsetIterator_step(&gset_iter); + + if (frame > range_start && frame < range_end) { + BLI_gset_remove(previously_displayed, POINTER_FROM_INT(frame), NULL); + } + } +} + +static int sequencer_thumbnail_closest_previous_frame_get(int timeline_frame, + GSet *previously_displayed) +{ + int best_diff = INT_MAX; + int best_frame = timeline_frame; + + /* Previously displayed thumbnails. */ + GSetIterator gset_iter; + BLI_gsetIterator_init(&gset_iter, previously_displayed); + while (!BLI_gsetIterator_done(&gset_iter)) { + int frame = POINTER_AS_INT(BLI_gsetIterator_getKey(&gset_iter)); + int diff = abs(frame - timeline_frame); + if (diff < best_diff) { + best_diff = diff; + best_frame = frame; + } + BLI_gsetIterator_step(&gset_iter); + } + return best_frame; +} + +static int sequencer_thumbnail_closest_guaranteed_frame_get(Sequence *seq, int timeline_frame) +{ + if (timeline_frame <= seq->startdisp) { + return seq->startdisp; + } + + /* Set of "guaranteed" thumbnails. */ + const int frame_index = timeline_frame - seq->startdisp; + const int frame_step = SEQ_render_thumbnails_guaranteed_set_frame_step_get(seq); + const int relative_base_frame = round_fl_to_int((frame_index / (float)frame_step)) * frame_step; + const int nearest_guaranted_absolute_frame = relative_base_frame + seq->startdisp; + return nearest_guaranted_absolute_frame; +} + +static ImBuf *sequencer_thumbnail_closest_from_memory(const SeqRenderData *context, + Sequence *seq, + int timeline_frame, + GSet *previously_displayed, + rcti *crop, + bool clipped) +{ + int frame_previous = sequencer_thumbnail_closest_previous_frame_get(timeline_frame, + previously_displayed); + ImBuf *ibuf_previous = SEQ_get_thumbnail(context, seq, frame_previous, crop, clipped); + + int frame_guaranteed = sequencer_thumbnail_closest_guaranteed_frame_get(seq, timeline_frame); + ImBuf *ibuf_guaranteed = SEQ_get_thumbnail(context, seq, frame_guaranteed, crop, clipped); + + ImBuf *closest_in_memory = NULL; + + if (ibuf_previous && ibuf_guaranteed) { + if (abs(frame_previous - timeline_frame) < abs(frame_guaranteed - timeline_frame)) { + IMB_freeImBuf(ibuf_guaranteed); + closest_in_memory = ibuf_previous; + } + else { + IMB_freeImBuf(ibuf_previous); + closest_in_memory = ibuf_guaranteed; + } + } + + if (ibuf_previous == NULL) { + closest_in_memory = ibuf_guaranteed; + } + + if (ibuf_guaranteed == NULL) { + closest_in_memory = ibuf_previous; + } + + return closest_in_memory; +} + +static void draw_seq_strip_thumbnail(View2D *v2d, + const bContext *C, + Scene *scene, + Sequence *seq, + float y1, + float y2, + float pixelx, + float pixely) +{ + bool clipped = false; + float image_height, image_width, thumb_width, thumb_height; + rcti crop; + + /* If width of the strip too small ignore drawing thumbnails. */ + if ((y2 - y1) / pixely <= 40 * U.dpi_fac) { + return; + } + + SeqRenderData context = sequencer_thumbnail_context_init(C); + + if ((seq->flag & SEQ_FLAG_SKIP_THUMBNAILS) != 0) { + return; + } + + seq_get_thumb_image_dimensions( + seq, pixelx, pixely, &thumb_width, &thumb_height, &image_width, &image_height); + + float thumb_y_end = y1 + thumb_height - pixely; + + float cut_off = 0; + float upper_thumb_bound = (seq->endstill) ? (seq->start + seq->len) : seq->enddisp; + if (seq->type == SEQ_TYPE_IMAGE) { + upper_thumb_bound = seq->enddisp; + } + + float thumb_x_start = seq_thumbnail_get_start_frame(seq, thumb_width, &v2d->cur); + float thumb_x_end; + + while (thumb_x_start + thumb_width < v2d->cur.xmin) { + thumb_x_start += thumb_width; + } + + /* Ignore thumbs to the left of strip. */ + while (thumb_x_start + thumb_width < seq->startdisp) { + thumb_x_start += thumb_width; + } + + GSet *last_displayed_thumbnails = last_displayed_thumbnails_list_ensure(C, seq); + /* Cleanup thumbnail list outside of rendered range, which is cleaned up one by one to prevent + * flickering after zooming. */ + if (!sequencer_thumbnail_v2d_is_navigating(C)) { + last_displayed_thumbnails_list_cleanup(last_displayed_thumbnails, -FLT_MAX, thumb_x_start); + } + + /* Start drawing. */ + while (thumb_x_start < upper_thumb_bound) { + thumb_x_end = thumb_x_start + thumb_width; + clipped = false; + + /* Checks to make sure that thumbs are loaded only when in view and within the confines of the + * strip. Some may not be required but better to have conditions for safety as x1 here is + * point to start caching from and not drawing. */ + if (thumb_x_start > v2d->cur.xmax) { + break; + } + + /* Set the clipping bound to show the left handle moving over thumbs and not shift thumbs. */ + if (IN_RANGE_INCL(seq->startdisp, thumb_x_start, thumb_x_end)) { + cut_off = seq->startdisp - thumb_x_start; + clipped = true; + } + + /* Clip if full thumbnail cannot be displayed. */ + if (thumb_x_end > (upper_thumb_bound)) { + thumb_x_end = upper_thumb_bound; + clipped = true; + if (thumb_x_end - thumb_x_start < 1) { + break; + } + } + + float zoom_x = thumb_width / image_width; + float zoom_y = thumb_height / image_height; + + float cropx_min = (cut_off / pixelx) / (zoom_y / pixely); + float cropx_max = ((thumb_x_end - thumb_x_start) / pixelx) / (zoom_y / pixely); + if (cropx_max == (thumb_x_end - thumb_x_start)) { + cropx_max = cropx_max + 1; + } + BLI_rcti_init(&crop, (int)(cropx_min), (int)cropx_max, 0, (int)(image_height)-1); + + int timeline_frame = round_fl_to_int(thumb_x_start); + + /* Get the image. */ + ImBuf *ibuf = SEQ_get_thumbnail(&context, seq, timeline_frame, &crop, clipped); + + if (!ibuf) { + sequencer_thumbnail_start_job_if_necessary(C, scene->ed, v2d, true); + + ibuf = sequencer_thumbnail_closest_from_memory( + &context, seq, timeline_frame, last_displayed_thumbnails, &crop, clipped); + } + /* Store recently rendered frames, so they can be reused when zooming. */ + else if (!sequencer_thumbnail_v2d_is_navigating(C)) { + /* Clear images in frame range occupied by new thumbnail. */ + last_displayed_thumbnails_list_cleanup( + last_displayed_thumbnails, thumb_x_start, thumb_x_end); + /* Insert new thumbnail frame to list. */ + BLI_gset_add(last_displayed_thumbnails, POINTER_FROM_INT(timeline_frame)); + } + + /* If there is no image still, abort. */ + if (!ibuf) { + break; + } + + /* Transparency on overlap. */ + if (seq->flag & SEQ_OVERLAP) { + GPU_blend(GPU_BLEND_ALPHA); + if (ibuf->rect) { + unsigned char *buf = (unsigned char *)ibuf->rect; + for (int pixel = ibuf->x * ibuf->y; pixel--; buf += 4) { + buf[3] = OVERLAP_ALPHA; + } + } + else if (ibuf->rect_float) { + float *buf = (float *)ibuf->rect_float; + for (int pixel = ibuf->x * ibuf->y; pixel--; buf += ibuf->channels) { + buf[3] = (OVERLAP_ALPHA / 255.0f); + } + } + } + + ED_draw_imbuf_ctx_clipping(C, + ibuf, + thumb_x_start + cut_off, + y1, + true, + thumb_x_start + cut_off, + y1, + thumb_x_end, + thumb_y_end, + zoom_x, + zoom_y); + IMB_freeImBuf(ibuf); + GPU_blend(GPU_BLEND_NONE); + cut_off = 0; + thumb_x_start += thumb_width; + } + last_displayed_thumbnails_list_cleanup(last_displayed_thumbnails, thumb_x_start, FLT_MAX); +} + /* Draw visible strips. Bounds check are already made. */ static void draw_seq_strip(const bContext *C, SpaceSeq *sseq, @@ -1356,6 +1879,12 @@ static void draw_seq_strip(const bContext *C, } if ((sseq->flag & SEQ_SHOW_OVERLAY) && + (sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_THUMBNAILS) && + (seq->type == SEQ_TYPE_MOVIE || seq->type == SEQ_TYPE_IMAGE)) { + draw_seq_strip_thumbnail(v2d, C, scene, seq, y1, y2, pixelx, pixely); + } + + if ((sseq->flag & SEQ_SHOW_OVERLAY) && (sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_FCURVES)) { draw_seq_fcurve_overlay(scene, v2d, seq, x1, y1, x2, y2, pixelx); } @@ -2056,6 +2585,64 @@ static int sequencer_draw_get_transform_preview_frame(Scene *scene) return preview_frame; } +static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq) +{ + SpaceSeq *sseq = CTX_wm_space_seq(C); + if ((seq->flag & SELECT) == 0) { + return; + } + if (ED_screen_animation_no_scrub(CTX_wm_manager(C))) { + return; + } + if ((sseq->flag & SEQ_SHOW_OVERLAY) == 0 || + (sseq->preview_overlay.flag & SEQ_PREVIEW_SHOW_OUTLINE_SELECTED) == 0) { + return; + } + if (ELEM(sseq->mainb, SEQ_DRAW_IMG_WAVEFORM, SEQ_DRAW_IMG_VECTORSCOPE, SEQ_DRAW_IMG_HISTOGRAM)) { + return; + } + + float origin[2]; + SEQ_image_transform_origin_offset_pixelspace_get(CTX_data_scene(C), seq, origin); + + /* Origin. */ + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_OUTLINE_AA); + immUniform1f("outlineWidth", 1.5f); + immUniformColor3f(1.0f, 1.0f, 1.0f); + immUniform4f("outlineColor", 0.0f, 0.0f, 0.0f, 1.0f); + immUniform1f("size", 15.0f * U.pixelsize); + immBegin(GPU_PRIM_POINTS, 1); + immVertex2f(pos, origin[0], origin[1]); + immEnd(); + immUnbindProgram(); + + /* Outline. */ + float seq_image_quad[4][2]; + SEQ_image_transform_final_quad_get(CTX_data_scene(C), seq, seq_image_quad); + + GPU_line_smooth(true); + GPU_blend(GPU_BLEND_ALPHA); + GPU_line_width(2); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + float col[3]; + UI_GetThemeColor3fv(TH_SEQ_SELECTED, col); + immUniformColor3fv(col); + immUniform1f("lineWidth", U.pixelsize); + immBegin(GPU_PRIM_LINE_LOOP, 4); + immVertex2f(pos, seq_image_quad[0][0], seq_image_quad[0][1]); + immVertex2f(pos, seq_image_quad[1][0], seq_image_quad[1][1]); + immVertex2f(pos, seq_image_quad[2][0], seq_image_quad[2][1]); + immVertex2f(pos, seq_image_quad[3][0], seq_image_quad[3][1]); + immEnd(); + immUnbindProgram(); + GPU_line_width(1); + GPU_blend(GPU_BLEND_NONE); + GPU_line_smooth(false); +} + void sequencer_draw_preview(const bContext *C, Scene *scene, ARegion *region, @@ -2132,9 +2719,17 @@ void sequencer_draw_preview(const bContext *C, sequencer_draw_borders_overlay(sseq, v2d, scene); } + SeqCollection *collection = SEQ_query_rendered_strips(&scene->ed->seqbase, timeline_frame, 0); + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + seq_draw_image_origin_and_outline(C, seq); + } + SEQ_collection_free(collection); + if (draw_gpencil && show_imbuf && (sseq->flag & SEQ_SHOW_OVERLAY)) { sequencer_draw_gpencil_overlay(C); } + #if 0 sequencer_draw_maskedit(C, scene, region, sseq); #endif diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c index b95b7fa0620..9f21fc0676c 100644 --- a/source/blender/editors/space_sequencer/sequencer_edit.c +++ b/source/blender/editors/space_sequencer/sequencer_edit.c @@ -579,7 +579,6 @@ static int sequencer_slip_invoke(bContext *C, wmOperator *op, const wmEvent *eve static bool sequencer_slip_recursively(Scene *scene, SlipData *data, int offset) { /* Only data types supported for now. */ - Editing *ed = SEQ_editing_get(scene); bool changed = false; /* Iterate in reverse so meta-strips are iterated after their children. */ @@ -633,7 +632,10 @@ static bool sequencer_slip_recursively(Scene *scene, SlipData *data, int offset) } } if (changed) { - SEQ_relations_free_imbuf(scene, &ed->seqbase, false); + for (int i = data->num_seq - 1; i >= 0; i--) { + Sequence *seq = data->seq_array[i]; + SEQ_relations_invalidate_cache_preprocessed(scene, seq); + } } return changed; } diff --git a/source/blender/editors/space_sequencer/sequencer_intern.h b/source/blender/editors/space_sequencer/sequencer_intern.h index 767ac76efe6..5b5c381509f 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.h +++ b/source/blender/editors/space_sequencer/sequencer_intern.h @@ -67,6 +67,7 @@ struct ImBuf *sequencer_ibuf_get(struct Main *bmain, int timeline_frame, int frame_ofs, const char *viewname); +void last_displayed_thumbnails_list_free(void *val); /* sequencer_edit.c */ struct View2D; diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c index 80d3e2cbdaa..aa6599a7c53 100644 --- a/source/blender/editors/space_sequencer/sequencer_select.c +++ b/source/blender/editors/space_sequencer/sequencer_select.c @@ -44,6 +44,7 @@ #include "SEQ_sequencer.h" #include "SEQ_time.h" #include "SEQ_transform.h" +#include "SEQ_utils.h" /* For menu, popup, icons, etc. */ @@ -385,6 +386,20 @@ void recurs_sel_seq(Sequence *seq_meta) } } +static bool seq_point_image_isect(const Scene *scene, const Sequence *seq, float point[2]) +{ + float seq_image_quad[4][2]; + SEQ_image_transform_final_quad_get(scene, seq, seq_image_quad); + return isect_point_quad_v2( + point, seq_image_quad[0], seq_image_quad[1], seq_image_quad[2], seq_image_quad[3]); +} + +static void sequencer_select_do_updates(bContext *C, Scene *scene) +{ + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -523,12 +538,6 @@ static void sequencer_select_set_active(Scene *scene, Sequence *seq) recurs_sel_seq(seq); } -static void sequencer_select_do_updates(bContext *C, Scene *scene) -{ - ED_outliner_select_sync_from_sequence_tag(C); - WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); -} - static void sequencer_select_side_of_frame(const bContext *C, const View2D *v2d, const int mval[2], @@ -626,6 +635,45 @@ static void sequencer_select_linked_handle(const bContext *C, } } +/* Check if click happened on image which belongs to strip. If multiple strips are found, loop + * through them in order. */ +static Sequence *seq_select_seq_from_preview(const bContext *C, const int mval[2]) +{ + Scene *scene = CTX_data_scene(C); + Editing *ed = SEQ_editing_get(scene); + ListBase *seqbase = SEQ_active_seqbase_get(ed); + SpaceSeq *sseq = CTX_wm_space_seq(C); + View2D *v2d = UI_view2d_fromcontext(C); + + float mouseco_view[2]; + UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouseco_view[0], &mouseco_view[1]); + + SeqCollection *strips = SEQ_query_rendered_strips(seqbase, scene->r.cfra, sseq->chanshown); + ListBase strips_ordered = {NULL}; + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, strips) { + if (seq_point_image_isect(scene, seq, mouseco_view)) { + BLI_remlink(seqbase, seq); + BLI_addtail(&strips_ordered, seq); + } + } + SEQ_collection_free(strips); + SEQ_sort(&strips_ordered); + + Sequence *seq_active = SEQ_select_active_get(scene); + Sequence *seq_select = strips_ordered.first; + LISTBASE_FOREACH (Sequence *, seq_iter, &strips_ordered) { + if (seq_iter == seq_active && seq_iter->next != NULL) { + seq_select = seq_iter->next; + break; + } + } + + BLI_movelisttolist(seqbase, &strips_ordered); + + return seq_select; +} + static bool element_already_selected(const Sequence *seq, const int handle_clicked) { const bool handle_already_selected = ((handle_clicked == SEQ_SIDE_LEFT) && @@ -680,8 +728,15 @@ static int sequencer_select_exec(bContext *C, wmOperator *op) mval[0] = RNA_int_get(op->ptr, "mouse_x"); mval[1] = RNA_int_get(op->ptr, "mouse_y"); - int handle_clicked; - Sequence *seq = find_nearest_seq(scene, v2d, &handle_clicked, mval); + ARegion *region = CTX_wm_region(C); + int handle_clicked = SEQ_SIDE_NONE; + Sequence *seq = NULL; + if (region->regiontype == RGN_TYPE_PREVIEW) { + seq = seq_select_seq_from_preview(C, mval); + } + else { + seq = find_nearest_seq(scene, v2d, &handle_clicked, mval); + } /* NOTE: `side_of_frame` and `linked_time` functionality is designed to be shared on one keymap, * therefore both properties can be true at the same time. */ @@ -1311,6 +1366,47 @@ void SEQUENCER_OT_select_side(wmOperatorType *ot) /** \name Box Select Operator * \{ */ +static bool seq_box_select_rect_image_isect(const Scene *scene, const Sequence *seq, rctf *rect) +{ + float seq_image_quad[4][2]; + SEQ_image_transform_final_quad_get(scene, seq, seq_image_quad); + float rect_quad[4][2] = {{rect->xmax, rect->ymax}, + {rect->xmax, rect->ymin}, + {rect->xmin, rect->ymin}, + {rect->xmin, rect->ymax}}; + + return seq_point_image_isect(scene, seq, rect_quad[0]) || + seq_point_image_isect(scene, seq, rect_quad[1]) || + seq_point_image_isect(scene, seq, rect_quad[2]) || + seq_point_image_isect(scene, seq, rect_quad[3]) || + isect_point_quad_v2( + seq_image_quad[0], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) || + isect_point_quad_v2( + seq_image_quad[1], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) || + isect_point_quad_v2( + seq_image_quad[2], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) || + isect_point_quad_v2( + seq_image_quad[3], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]); +} + +static void seq_box_select_seq_from_preview(const bContext *C, rctf *rect) +{ + Scene *scene = CTX_data_scene(C); + Editing *ed = SEQ_editing_get(scene); + ListBase *seqbase = SEQ_active_seqbase_get(ed); + SpaceSeq *sseq = CTX_wm_space_seq(C); + + SeqCollection *strips = SEQ_query_rendered_strips(seqbase, scene->r.cfra, sseq->chanshown); + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, strips) { + if (seq_box_select_rect_image_isect(scene, seq, rect)) { + seq->flag |= SELECT; + } + } + + SEQ_collection_free(strips); +} + static int sequencer_box_select_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); @@ -1333,6 +1429,13 @@ static int sequencer_box_select_exec(bContext *C, wmOperator *op) WM_operator_properties_border_to_rctf(op, &rectf); UI_view2d_region_to_view_rctf(v2d, &rectf, &rectf); + ARegion *region = CTX_wm_region(C); + if (region->regiontype == RGN_TYPE_PREVIEW) { + seq_box_select_seq_from_preview(C, &rectf); + sequencer_select_do_updates(C, scene); + return OPERATOR_FINISHED; + } + LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) { rctf rq; seq_rectf(seq, &rq); @@ -1378,9 +1481,7 @@ static int sequencer_box_select_exec(bContext *C, wmOperator *op) } } - ED_outliner_select_sync_from_sequence_tag(C); - - WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); + sequencer_select_do_updates(C, scene); return OPERATOR_FINISHED; } diff --git a/source/blender/editors/space_sequencer/space_sequencer.c b/source/blender/editors/space_sequencer/space_sequencer.c index 0d09f2564e8..99b75f82922 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.c +++ b/source/blender/editors/space_sequencer/space_sequencer.c @@ -32,6 +32,7 @@ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_ghash.h" #include "BLI_utildefines.h" #include "BKE_context.h" @@ -42,6 +43,7 @@ #include "ED_screen.h" #include "ED_space_api.h" +#include "ED_transform.h" #include "ED_view3d.h" #include "ED_view3d_offscreen.h" /* Only for sequencer view3d drawing callback. */ @@ -98,10 +100,14 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce sseq->chanshown = 0; sseq->view = SEQ_VIEW_SEQUENCE; sseq->mainb = SEQ_DRAW_IMG_IMBUF; - sseq->flag = SEQ_PREVIEW_SHOW_GPENCIL | SEQ_USE_ALPHA | SEQ_SHOW_MARKERS | - SEQ_TIMELINE_SHOW_FCURVES | SEQ_ZOOM_TO_FIT | SEQ_SHOW_OVERLAY | - SEQ_TIMELINE_SHOW_STRIP_NAME | SEQ_TIMELINE_SHOW_STRIP_SOURCE | - SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID; + sseq->flag = SEQ_USE_ALPHA | SEQ_SHOW_MARKERS | SEQ_ZOOM_TO_FIT | SEQ_SHOW_OVERLAY; + sseq->preview_overlay.flag = SEQ_PREVIEW_SHOW_GPENCIL | SEQ_PREVIEW_SHOW_OUTLINE_SELECTED; + sseq->timeline_overlay.flag = SEQ_TIMELINE_SHOW_STRIP_NAME | SEQ_TIMELINE_SHOW_STRIP_SOURCE | + SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID | + SEQ_TIMELINE_SHOW_FCURVES; + + BLI_rctf_init(&sseq->runtime.last_thumbnail_area, 0.0f, 0.0f, 0.0f, 0.0f); + sseq->runtime.last_displayed_thumbnails = NULL; /* Tool header. */ region = MEM_callocN(sizeof(ARegion), "tool header for sequencer"); @@ -172,7 +178,7 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce region->v2d.cur = region->v2d.tot; region->v2d.min[0] = 10.0f; - region->v2d.min[1] = 0.5f; + region->v2d.min[1] = 4.0f; region->v2d.max[0] = MAXFRAMEF; region->v2d.max[1] = MAXSEQ; @@ -186,6 +192,8 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce region->v2d.keeptot = 0; region->v2d.align = V2D_ALIGN_NO_NEG_Y; + sseq->runtime.last_displayed_thumbnails = NULL; + return (SpaceLink *)sseq; } @@ -216,6 +224,12 @@ static void sequencer_free(SpaceLink *sl) if (scopes->histogram_ibuf) { IMB_freeImBuf(scopes->histogram_ibuf); } + + if (sseq->runtime.last_displayed_thumbnails) { + BLI_ghash_free( + sseq->runtime.last_displayed_thumbnails, NULL, last_displayed_thumbnails_list_free); + sseq->runtime.last_displayed_thumbnails = NULL; + } } /* Spacetype init callback. */ @@ -330,6 +344,7 @@ static SpaceLink *sequencer_duplicate(SpaceLink *sl) /* XXX sseq->gpd = gpencil_data_duplicate(sseq->gpd, false); */ memset(&sseqn->scopes, 0, sizeof(sseqn->scopes)); + memset(&sseqn->runtime, 0, sizeof(sseqn->runtime)); return (SpaceLink *)sseqn; } @@ -481,11 +496,72 @@ static void SEQUENCER_GGT_navigate(wmGizmoGroupType *gzgt) VIEW2D_GGT_navigate_impl(gzgt, "SEQUENCER_GGT_navigate"); } +static void SEQUENCER_GGT_gizmo2d(wmGizmoGroupType *gzgt) +{ + gzgt->name = "Sequencer Transform Gizmo"; + gzgt->idname = "SEQUENCER_GGT_gizmo2d"; + + gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); + + gzgt->gzmap_params.spaceid = SPACE_SEQ; + gzgt->gzmap_params.regionid = RGN_TYPE_PREVIEW; + + ED_widgetgroup_gizmo2d_xform_callbacks_set(gzgt); +} + +static void SEQUENCER_GGT_gizmo2d_translate(wmGizmoGroupType *gzgt) +{ + gzgt->name = "Sequencer Translate Gizmo"; + gzgt->idname = "SEQUENCER_GGT_gizmo2d_translate"; + + gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); + + gzgt->gzmap_params.spaceid = SPACE_SEQ; + gzgt->gzmap_params.regionid = RGN_TYPE_PREVIEW; + + ED_widgetgroup_gizmo2d_xform_no_cage_callbacks_set(gzgt); +} + +static void SEQUENCER_GGT_gizmo2d_resize(wmGizmoGroupType *gzgt) +{ + gzgt->name = "Sequencer Transform Gizmo Resize"; + gzgt->idname = "SEQUENCER_GGT_gizmo2d_resize"; + + gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); + + gzgt->gzmap_params.spaceid = SPACE_SEQ; + gzgt->gzmap_params.regionid = RGN_TYPE_PREVIEW; + + ED_widgetgroup_gizmo2d_resize_callbacks_set(gzgt); +} + +static void SEQUENCER_GGT_gizmo2d_rotate(wmGizmoGroupType *gzgt) +{ + gzgt->name = "Sequencer Transform Gizmo Resize"; + gzgt->idname = "SEQUENCER_GGT_gizmo2d_rotate"; + + gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK); + + gzgt->gzmap_params.spaceid = SPACE_SEQ; + gzgt->gzmap_params.regionid = RGN_TYPE_PREVIEW; + + ED_widgetgroup_gizmo2d_rotate_callbacks_set(gzgt); +} + static void sequencer_gizmos(void) { wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure( &(const struct wmGizmoMapType_Params){SPACE_SEQ, RGN_TYPE_PREVIEW}); + WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d); + WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_translate); + WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_resize); + WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_rotate); + WM_gizmogrouptype_append_and_link(gzmap_type, SEQUENCER_GGT_navigate); } @@ -742,6 +818,8 @@ static void sequencer_preview_region_listener(const wmRegionListenerParams *para ARegion *region = params->region; wmNotifier *wmn = params->notifier; + WM_gizmomap_tag_refresh(region->gizmo_map); + /* Context changes. */ switch (wmn->category) { case NC_GPENCIL: diff --git a/source/blender/editors/space_view3d/view3d_gizmo_preselect.c b/source/blender/editors/space_view3d/view3d_gizmo_preselect.c index 441182d7a5f..918ecb14752 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_preselect.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_preselect.c @@ -58,7 +58,7 @@ void VIEW3D_GGT_mesh_preselect_elem(wmGizmoGroupType *gzgt) gzgt->name = "Mesh Preselect Element"; gzgt->idname = "VIEW3D_GGT_mesh_preselect_elem"; - gzgt->flag = WM_GIZMOGROUPTYPE_3D; + gzgt->flag = WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_3D; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; @@ -95,7 +95,7 @@ void VIEW3D_GGT_mesh_preselect_edgering(wmGizmoGroupType *gzgt) gzgt->name = "Mesh Preselect Edge Ring"; gzgt->idname = "VIEW3D_GGT_mesh_preselect_edgering"; - gzgt->flag = WM_GIZMOGROUPTYPE_3D; + gzgt->flag = WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_3D; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c index 3f572bf9d5a..39aed131ea1 100644 --- a/source/blender/editors/space_view3d/view3d_select.c +++ b/source/blender/editors/space_view3d/view3d_select.c @@ -2813,7 +2813,9 @@ static int view3d_select_invoke(bContext *C, wmOperator *op, const wmEvent *even { RNA_int_set_array(op->ptr, "location", event->mval); - return view3d_select_exec(C, op); + const int retval = view3d_select_exec(C, op); + + return WM_operator_flag_only_pass_through_on_press(retval, event); } void VIEW3D_OT_select(wmOperatorType *ot) diff --git a/source/blender/editors/transform/CMakeLists.txt b/source/blender/editors/transform/CMakeLists.txt index e9efed3cd61..64a720322c1 100644 --- a/source/blender/editors/transform/CMakeLists.txt +++ b/source/blender/editors/transform/CMakeLists.txt @@ -60,6 +60,7 @@ set(SRC transform_convert_particle.c transform_convert_sculpt.c transform_convert_sequencer.c + transform_convert_sequencer_image.c transform_convert_tracking.c transform_draw_cursors.c transform_generics.c diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index 58491f8c2d3..e58e524e341 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -1703,11 +1703,13 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve t->draw_handle_cursor = WM_paint_cursor_activate( SPACE_TYPE_ANY, RGN_TYPE_ANY, transform_draw_cursor_poll, transform_draw_cursor_draw, t); } - else if (t->spacetype == SPACE_SEQ) { - t->draw_handle_view = ED_region_draw_cb_activate( - t->region->type, drawTransformView, t, REGION_DRAW_POST_VIEW); - } - else if (ELEM(t->spacetype, SPACE_IMAGE, SPACE_CLIP, SPACE_NODE, SPACE_GRAPH, SPACE_ACTION)) { + else if (ELEM(t->spacetype, + SPACE_IMAGE, + SPACE_CLIP, + SPACE_NODE, + SPACE_GRAPH, + SPACE_ACTION, + SPACE_SEQ)) { t->draw_handle_view = ED_region_draw_cb_activate( t->region->type, drawTransformView, t, REGION_DRAW_POST_VIEW); t->draw_handle_cursor = WM_paint_cursor_activate( diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index d1a1937cef1..7f4e533ccd7 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -87,15 +87,16 @@ typedef enum { CTX_PAINT_CURVE = (1 << 7), CTX_POSE_BONE = (1 << 8), CTX_TEXTURE_SPACE = (1 << 9), + CTX_SEQUENCER_IMAGE = (1 << 10), - CTX_NO_PET = (1 << 10), - CTX_AUTOCONFIRM = (1 << 11), + CTX_NO_PET = (1 << 11), + CTX_AUTOCONFIRM = (1 << 12), /** When transforming object's, adjust the object data so it stays in the same place. */ - CTX_OBMODE_XFORM_OBDATA = (1 << 12), + CTX_OBMODE_XFORM_OBDATA = (1 << 13), /** Transform object parents without moving their children. */ - CTX_OBMODE_XFORM_SKIP_CHILDREN = (1 << 13), + CTX_OBMODE_XFORM_SKIP_CHILDREN = (1 << 14), /** Enable edge scrolling in 2D views */ - CTX_VIEW2D_EDGE_PAN = (1 << 14), + CTX_VIEW2D_EDGE_PAN = (1 << 15), } eTContext; /** #TransInfo.flag */ @@ -240,6 +241,7 @@ typedef enum { TC_PARTICLE_VERTS, TC_SCULPT, TC_SEQ_DATA, + TC_SEQ_IMAGE_DATA, TC_TRACKING_DATA, } eTConvertType; diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index d756e2c90a6..557fa79e7ac 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -955,6 +955,7 @@ void special_aftertrans_update(bContext *C, TransInfo *t) case TC_OBJECT_TEXSPACE: case TC_PAINT_CURVE_VERTS: case TC_PARTICLE_VERTS: + case TC_SEQ_IMAGE_DATA: case TC_NONE: default: break; @@ -1042,6 +1043,7 @@ static void init_proportional_edit(TransInfo *t) case TC_PAINT_CURVE_VERTS: case TC_SCULPT: case TC_SEQ_DATA: + case TC_SEQ_IMAGE_DATA: case TC_TRACKING_DATA: case TC_NONE: default: @@ -1120,6 +1122,7 @@ static void init_TransDataContainers(TransInfo *t, case TC_PARTICLE_VERTS: case TC_SCULPT: case TC_SEQ_DATA: + case TC_SEQ_IMAGE_DATA: case TC_TRACKING_DATA: case TC_NONE: default: @@ -1204,6 +1207,7 @@ static eTFlag flags_from_data_type(eTConvertType data_type) case TC_NODE_DATA: case TC_PAINT_CURVE_VERTS: case TC_SEQ_DATA: + case TC_SEQ_IMAGE_DATA: case TC_TRACKING_DATA: return T_POINTS | T_2D_EDIT; case TC_ARMATURE_VERTS: @@ -1282,7 +1286,12 @@ static eTConvertType convert_type_get(const TransInfo *t, Object **r_obj_armatur convert_type = TC_NLA_DATA; } else if (t->spacetype == SPACE_SEQ) { - convert_type = TC_SEQ_DATA; + if (t->options & CTX_SEQUENCER_IMAGE) { + convert_type = TC_SEQ_IMAGE_DATA; + } + else { + convert_type = TC_SEQ_DATA; + } } else if (t->spacetype == SPACE_GRAPH) { convert_type = TC_GRAPH_EDIT_DATA; @@ -1470,6 +1479,10 @@ void createTransData(bContext *C, TransInfo *t) t->num.flag |= NUM_NO_FRACTION; /* sequencer has no use for floating point transform. */ createTransSeqData(t); break; + case TC_SEQ_IMAGE_DATA: + t->obedit_type = -1; + createTransSeqImageData(t); + break; case TC_TRACKING_DATA: createTransTrackingData(C, t); break; @@ -1746,6 +1759,9 @@ void recalcData(TransInfo *t) case TC_SEQ_DATA: recalcData_sequencer(t); break; + case TC_SEQ_IMAGE_DATA: + recalcData_sequencer_image(t); + break; case TC_TRACKING_DATA: recalcData_tracking(t); break; diff --git a/source/blender/editors/transform/transform_convert.h b/source/blender/editors/transform/transform_convert.h index 9cb0400cad9..66d84bca2d2 100644 --- a/source/blender/editors/transform/transform_convert.h +++ b/source/blender/editors/transform/transform_convert.h @@ -218,6 +218,10 @@ void createTransSeqData(TransInfo *t); void recalcData_sequencer(TransInfo *t); void special_aftertrans_update__sequencer(bContext *C, TransInfo *t); +/* transform_convert_sequencer_image.c */ +void createTransSeqImageData(TransInfo *t); +void recalcData_sequencer_image(TransInfo *t); + /* transform_convert_tracking.c */ void createTransTrackingData(bContext *C, TransInfo *t); void recalcData_tracking(TransInfo *t); diff --git a/source/blender/editors/transform/transform_convert_sequencer_image.c b/source/blender/editors/transform/transform_convert_sequencer_image.c new file mode 100644 index 00000000000..465f8b9a694 --- /dev/null +++ b/source/blender/editors/transform/transform_convert_sequencer_image.c @@ -0,0 +1,195 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edtransform + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_space_types.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" + +#include "BKE_context.h" +#include "BKE_report.h" + +#include "SEQ_iterator.h" +#include "SEQ_relations.h" +#include "SEQ_sequencer.h" +#include "SEQ_time.h" +#include "SEQ_transform.h" +#include "SEQ_utils.h" + +#include "UI_view2d.h" + +#include "transform.h" +#include "transform_convert.h" + +/** Used for sequencer transform. */ +typedef struct TransDataSeq { + struct Sequence *seq; + float orig_origin_position[2]; + float orig_translation[2]; + float orig_scale[2]; + float orig_rotation; +} TransDataSeq; + +static TransData *SeqToTransData(const Scene *scene, + Sequence *seq, + TransData *td, + TransData2D *td2d, + TransDataSeq *tdseq, + int vert_index) +{ + const StripTransform *transform = seq->strip->transform; + float origin[2]; + SEQ_image_transform_origin_offset_pixelspace_get(scene, seq, origin); + float vertex[2] = {origin[0], origin[1]}; + + /* Add control vertex, so rotation and scale can be calculated. */ + if (vert_index == 1) { + vertex[0] += 1.0f; + } + else if (vert_index == 2) { + vertex[1] += 1.0f; + } + + td2d->loc[0] = vertex[0]; + td2d->loc[1] = vertex[1]; + td2d->loc2d = NULL; + td->loc = td2d->loc; + copy_v3_v3(td->iloc, td->loc); + + td->center[0] = origin[0]; + td->center[1] = origin[1]; + + memset(td->axismtx, 0, sizeof(td->axismtx)); + td->axismtx[2][2] = 1.0f; + unit_m3(td->mtx); + unit_m3(td->smtx); + + tdseq->seq = seq; + copy_v2_v2(tdseq->orig_origin_position, origin); + tdseq->orig_translation[0] = transform->xofs; + tdseq->orig_translation[1] = transform->yofs; + tdseq->orig_scale[0] = transform->scale_x; + tdseq->orig_scale[1] = transform->scale_y; + tdseq->orig_rotation = transform->rotation; + + td->extra = (void *)tdseq; + td->ext = NULL; + td->flag |= TD_SELECTED; + td->dist = 0.0; + + return td; +} + +static void freeSeqData(TransInfo *UNUSED(t), TransDataContainer *tc, TransCustomData *UNUSED(custom_data)) +{ + TransData *td = (TransData *)tc->data; + MEM_freeN(td->extra); +} + +void createTransSeqImageData(TransInfo *t) +{ + Editing *ed = SEQ_editing_get(t->scene); + ListBase *seqbase = SEQ_active_seqbase_get(ed); + SeqCollection *strips = SEQ_query_rendered_strips(seqbase, t->scene->r.cfra, 0); + SEQ_filter_selected_strips(strips); + + const int count = SEQ_collection_len(strips); + if (ed == NULL || count == 0) { + SEQ_collection_free(strips); + return; + } + + TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); + tc->custom.type.free_cb = freeSeqData; + + tc->data_len = count * 3; /* 3 vertices per sequence are needed. */ + TransData *td = tc->data = MEM_callocN(tc->data_len * sizeof(TransData), "TransSeq TransData"); + TransData2D *td2d = tc->data_2d = MEM_callocN(tc->data_len * sizeof(TransData2D), + "TransSeq TransData2D"); + TransDataSeq *tdseq = MEM_callocN(tc->data_len * sizeof(TransDataSeq), "TransSeq TransDataSeq"); + + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, strips) { + /* One `Sequence` needs 3 `TransData` entries - center point placed in image origin, then 2 + * points offset by 1 in X and Y direction respectively, so rotation and scale can be + * calculated from these points. */ + SeqToTransData(t->scene, seq, td++, td2d++, tdseq++, 0); + SeqToTransData(t->scene, seq, td++, td2d++, tdseq++, 1); + SeqToTransData(t->scene, seq, td++, td2d++, tdseq++, 2); + } + + SEQ_collection_free(strips); +} + +void recalcData_sequencer_image(TransInfo *t) +{ + TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); + TransData *td = NULL; + TransData2D *td2d = NULL; + int i; + + for (i = 0, td = tc->data, td2d = tc->data_2d; i < tc->data_len; i++, td++, td2d++) { + /* Origin. */ + float loc[2]; + copy_v2_v2(loc, td2d->loc); + i++, td++, td2d++; + + /* X and Y control points used to read scale and rotation. */ + float handle_x[2]; + copy_v2_v2(handle_x, td2d->loc); + sub_v2_v2(handle_x, loc); + i++, td++, td2d++; + float handle_y[2]; + copy_v2_v2(handle_y, td2d->loc); + sub_v2_v2(handle_y, loc); + + TransDataSeq *tdseq = td->extra; + Sequence *seq = tdseq->seq; + StripTransform *transform = seq->strip->transform; + float mirror[2]; + SEQ_image_transform_mirror_factor_get(seq, mirror); + + /* Calculate translation. */ + float translation[2]; + copy_v2_v2(translation, tdseq->orig_origin_position); + sub_v2_v2(translation, loc); + mul_v2_v2(translation, mirror); + transform->xofs = tdseq->orig_translation[0] - translation[0]; + transform->yofs = tdseq->orig_translation[1] - translation[1]; + + /* Scale. */ + transform->scale_x = tdseq->orig_scale[0] * fabs(len_v2(handle_x)); + transform->scale_y = tdseq->orig_scale[1] * fabs(len_v2(handle_y)); + + /* Rotation. Scaling can cause negative rotation. */ + if (t->mode == TFM_ROTATION) { + float rotation = angle_signed_v2v2(handle_x, (float[]){1, 0}) * mirror[0] * mirror[1]; + transform->rotation = tdseq->orig_rotation + rotation; + transform->rotation += DEG2RAD(360.0); + transform->rotation = fmod(transform->rotation, DEG2RAD(360.0)); + } + SEQ_relations_invalidate_cache_preprocessed(t->scene, seq); + } +} diff --git a/source/blender/editors/transform/transform_draw_cursors.c b/source/blender/editors/transform/transform_draw_cursors.c index ead8eae0997..af1f3cb72a4 100644 --- a/source/blender/editors/transform/transform_draw_cursors.c +++ b/source/blender/editors/transform/transform_draw_cursors.c @@ -95,7 +95,7 @@ static void drawArrow(const uint pos_id, const enum eArrowDirection dir) bool transform_draw_cursor_poll(bContext *C) { ARegion *region = CTX_wm_region(C); - return (region && region->regiontype == RGN_TYPE_WINDOW) ? 1 : 0; + return (region && ELEM(region->regiontype, RGN_TYPE_WINDOW, RGN_TYPE_PREVIEW)) ? 1 : 0; } /** diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index c493b9bd102..fa323f0c1f7 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -59,6 +59,8 @@ #include "UI_resources.h" #include "UI_view2d.h" +#include "SEQ_sequencer.h" + #include "transform.h" #include "transform_convert.h" #include "transform_mode.h" @@ -335,6 +337,11 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve t->options |= CTX_MASK; } } + else if (t->spacetype == SPACE_SEQ && region->regiontype == RGN_TYPE_PREVIEW) { + t->view = ®ion->v2d; + t->around = SEQ_tool_settings_pivot_point_get(t->scene); + t->options |= CTX_SEQUENCER_IMAGE; + } else { if (region) { /* XXX: For now, get View2D from the active region. */ diff --git a/source/blender/editors/transform/transform_gizmo_2d.c b/source/blender/editors/transform/transform_gizmo_2d.c index 0b677e2560b..0d66db0d7e1 100644 --- a/source/blender/editors/transform/transform_gizmo_2d.c +++ b/source/blender/editors/transform/transform_gizmo_2d.c @@ -49,6 +49,11 @@ #include "ED_screen.h" #include "ED_uvedit.h" +#include "SEQ_iterator.h" +#include "SEQ_sequencer.h" +#include "SEQ_time.h" +#include "SEQ_transform.h" + #include "transform.h" /* own include */ /* -------------------------------------------------------------------- */ @@ -234,17 +239,66 @@ static bool gizmo2d_calc_bounds(const bContext *C, float *r_center, float *r_min return changed; } +static float gizmo2d_calc_rotation(const bContext *C) +{ + ScrArea *area = CTX_wm_area(C); + if (area->spacetype != SPACE_SEQ) { + return 0.0f; + } + + Scene *scene = CTX_data_scene(C); + Editing *ed = SEQ_editing_get(scene); + ListBase *seqbase = SEQ_active_seqbase_get(ed); + SeqCollection *strips = SEQ_query_rendered_strips(seqbase, scene->r.cfra, 0); + SEQ_filter_selected_strips(strips); + + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, strips) { + if (seq == ed->act_seq) { + StripTransform *transform = seq->strip->transform; + float mirror[2]; + SEQ_image_transform_mirror_factor_get(seq, mirror); + SEQ_collection_free(strips); + return transform->rotation * mirror[0] * mirror[1]; + } + } + + SEQ_collection_free(strips); + return 0.0f; +} + static bool gizmo2d_calc_center(const bContext *C, float r_center[2]) { ScrArea *area = CTX_wm_area(C); + Scene *scene = CTX_data_scene(C); bool has_select = false; zero_v2(r_center); if (area->spacetype == SPACE_IMAGE) { SpaceImage *sima = area->spacedata.first; - Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); ED_uvedit_center_from_pivot_ex(sima, scene, view_layer, r_center, sima->around, &has_select); } + else if (area->spacetype == SPACE_SEQ) { + ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene)); + SeqCollection *strips = SEQ_query_rendered_strips(seqbase, scene->r.cfra, 0); + SEQ_filter_selected_strips(strips); + + if (SEQ_collection_len(strips) <= 0) { + SEQ_collection_free(strips); + return false; + } + + has_select = true; + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, strips) { + float origin[2]; + SEQ_image_transform_origin_offset_pixelspace_get(scene, seq, origin); + add_v2_v2(r_center, origin); + } + mul_v2_fl(r_center, 1.0f / SEQ_collection_len(strips)); + + SEQ_collection_free(strips); + } return has_select; } @@ -338,7 +392,7 @@ static void gizmo2d_xform_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup } } - RNA_boolean_set(ptr, "release_confirm", 1); + RNA_boolean_set(ptr, "release_confirm", true); } { @@ -539,6 +593,7 @@ void ED_widgetgroup_gizmo2d_xform_no_cage_callbacks_set(wmGizmoGroupType *gzgt) typedef struct GizmoGroup_Resize2D { wmGizmo *gizmo_xy[3]; float origin[2]; + float rotation; } GizmoGroup_Resize2D; static GizmoGroup_Resize2D *gizmogroup2d_resize_init(wmGizmoGroup *gzgroup) @@ -571,6 +626,7 @@ static void gizmo2d_resize_refresh(const bContext *C, wmGizmoGroup *gzgroup) ggd->gizmo_xy[i]->flag &= ~WM_GIZMO_HIDDEN; } copy_v2_v2(ggd->origin, origin); + ggd->rotation = gizmo2d_calc_rotation(C); } } @@ -595,6 +651,13 @@ static void gizmo2d_resize_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup for (int i = 0; i < ARRAY_SIZE(ggd->gizmo_xy); i++) { wmGizmo *gz = ggd->gizmo_xy[i]; WM_gizmo_set_matrix_location(gz, origin); + + if (i < 2) { + float axis[3] = {0.0f}, rotated_axis[3]; + axis[i] = 1.0f; + rotate_v3_v3v3fl(rotated_axis, axis, (float[3]){0, 0, 1}, ggd->rotation); + WM_gizmo_set_matrix_rotation_from_z_axis(gz, rotated_axis); + } } } @@ -617,10 +680,6 @@ static void gizmo2d_resize_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgrou /* set up widget data */ RNA_float_set(gz->ptr, "length", 1.0f); - float axis[3] = {0.0f}; - axis[i] = 1.0f; - WM_gizmo_set_matrix_rotation_from_z_axis(gz, axis); - RNA_enum_set(gz->ptr, "draw_style", ED_GIZMO_ARROW_STYLE_BOX); WM_gizmo_set_line_width(gz, GIZMO_AXIS_LINE_WIDTH); diff --git a/source/blender/editors/transform/transform_mode.c b/source/blender/editors/transform/transform_mode.c index b9fb8a86752..b14d499cb66 100644 --- a/source/blender/editors/transform/transform_mode.c +++ b/source/blender/editors/transform/transform_mode.c @@ -75,7 +75,7 @@ bool transdata_check_local_center(const TransInfo *t, short around) /* implicit: (t->flag & T_EDIT) */ (ELEM(t->obedit_type, OB_MESH, OB_CURVE, OB_MBALL, OB_ARMATURE, OB_GPENCIL)) || (t->spacetype == SPACE_GRAPH) || - (t->options & (CTX_MOVIECLIP | CTX_MASK | CTX_PAINT_CURVE)))); + (t->options & (CTX_MOVIECLIP | CTX_MASK | CTX_PAINT_CURVE | CTX_SEQUENCER_IMAGE)))); } /* Informs if the mode can be switched during modal. */ diff --git a/source/blender/editors/transform/transform_snap_sequencer.c b/source/blender/editors/transform/transform_snap_sequencer.c index e82a00bcc77..2acdf5cfd9c 100644 --- a/source/blender/editors/transform/transform_snap_sequencer.c +++ b/source/blender/editors/transform/transform_snap_sequencer.c @@ -254,6 +254,10 @@ static int seq_snap_threshold_get_frame_distance(const TransInfo *t) TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t) { + if (t->data_type == TC_SEQ_IMAGE_DATA) { + return NULL; + } + TransSeqSnapData *snap_data = MEM_callocN(sizeof(TransSeqSnapData), __func__); ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(t->scene)); diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c index c0ccf1b7095..86390882bed 100644 --- a/source/blender/editors/uvedit/uvedit_select.c +++ b/source/blender/editors/uvedit/uvedit_select.c @@ -2122,7 +2122,9 @@ static int uv_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &co[0], &co[1]); RNA_float_set_array(op->ptr, "location", co); - return uv_select_exec(C, op); + const int retval = uv_select_exec(C, op); + + return WM_operator_flag_only_pass_through_on_press(retval, event); } void UV_OT_select(wmOperatorType *ot) @@ -2281,7 +2283,9 @@ static int uv_select_loop_invoke(bContext *C, wmOperator *op, const wmEvent *eve UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &co[0], &co[1]); RNA_float_set_array(op->ptr, "location", co); - return uv_select_loop_exec(C, op); + const int retval = uv_select_loop_exec(C, op); + + return WM_operator_flag_only_pass_through_on_press(retval, event); } void UV_OT_select_loop(wmOperatorType *ot) @@ -2341,7 +2345,9 @@ static int uv_select_edge_ring_invoke(bContext *C, wmOperator *op, const wmEvent UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &co[0], &co[1]); RNA_float_set_array(op->ptr, "location", co); - return uv_select_edge_ring_exec(C, op); + const int retval = uv_select_edge_ring_exec(C, op); + + return WM_operator_flag_only_pass_through_on_press(retval, event); } void UV_OT_select_edge_ring(wmOperatorType *ot) diff --git a/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp b/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp index 937a10f26b1..0a82c237256 100644 --- a/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp +++ b/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp @@ -94,17 +94,15 @@ BlenderStrokeRenderer::BlenderStrokeRenderer(Render *re, int render_count) freestyle_scene = BKE_scene_add(freestyle_bmain, name); freestyle_scene->r.cfra = old_scene->r.cfra; freestyle_scene->r.mode = old_scene->r.mode & ~(R_EDGE_FRS | R_BORDER); - freestyle_scene->r.xsch = re->rectx; // old_scene->r.xsch - freestyle_scene->r.ysch = re->recty; // old_scene->r.ysch - freestyle_scene->r.xasp = 1.0f; // old_scene->r.xasp; - freestyle_scene->r.yasp = 1.0f; // old_scene->r.yasp; - freestyle_scene->r.tilex = old_scene->r.tilex; - freestyle_scene->r.tiley = old_scene->r.tiley; + freestyle_scene->r.xsch = re->rectx; // old_scene->r.xsch + freestyle_scene->r.ysch = re->recty; // old_scene->r.ysch + freestyle_scene->r.xasp = 1.0f; // old_scene->r.xasp; + freestyle_scene->r.yasp = 1.0f; // old_scene->r.yasp; freestyle_scene->r.size = 100; // old_scene->r.size freestyle_scene->r.color_mgt_flag = 0; // old_scene->r.color_mgt_flag; freestyle_scene->r.scemode = (old_scene->r.scemode & ~(R_SINGLE_LAYER | R_NO_FRAME_UPDATE | R_MULTIVIEW)) & - (re->r.scemode | ~R_FULL_SAMPLE); + (re->r.scemode); freestyle_scene->r.flag = old_scene->r.flag; freestyle_scene->r.threads = old_scene->r.threads; freestyle_scene->r.border.xmin = old_scene->r.border.xmin; diff --git a/source/blender/functions/FN_cpp_type.hh b/source/blender/functions/FN_cpp_type.hh index 7277bf99c12..643b2fc1f28 100644 --- a/source/blender/functions/FN_cpp_type.hh +++ b/source/blender/functions/FN_cpp_type.hh @@ -96,6 +96,7 @@ class CPPType : NonCopyable, NonMovable { int64_t size_ = 0; int64_t alignment_ = 0; uintptr_t alignment_mask_ = 0; + bool is_trivial_ = false; bool is_trivially_destructible_ = false; bool has_special_member_functions_ = false; @@ -340,7 +341,6 @@ class CPPType : NonCopyable, NonMovable { */ void copy_assign(const void *src, void *dst) const { - BLI_assert(src != dst); BLI_assert(this->pointer_can_point_to_instance(src)); BLI_assert(this->pointer_can_point_to_instance(dst)); @@ -371,7 +371,7 @@ class CPPType : NonCopyable, NonMovable { */ void copy_construct(const void *src, void *dst) const { - BLI_assert(src != dst); + BLI_assert(src != dst || is_trivial_); BLI_assert(this->pointer_can_point_to_instance(src)); BLI_assert(this->pointer_can_point_to_instance(dst)); @@ -402,7 +402,6 @@ class CPPType : NonCopyable, NonMovable { */ void move_assign(void *src, void *dst) const { - BLI_assert(src != dst); BLI_assert(this->pointer_can_point_to_instance(src)); BLI_assert(this->pointer_can_point_to_instance(dst)); @@ -433,7 +432,7 @@ class CPPType : NonCopyable, NonMovable { */ void move_construct(void *src, void *dst) const { - BLI_assert(src != dst); + BLI_assert(src != dst || is_trivial_); BLI_assert(this->pointer_can_point_to_instance(src)); BLI_assert(this->pointer_can_point_to_instance(dst)); @@ -464,7 +463,7 @@ class CPPType : NonCopyable, NonMovable { */ void relocate_assign(void *src, void *dst) const { - BLI_assert(src != dst); + BLI_assert(src != dst || is_trivial_); BLI_assert(this->pointer_can_point_to_instance(src)); BLI_assert(this->pointer_can_point_to_instance(dst)); @@ -495,7 +494,7 @@ class CPPType : NonCopyable, NonMovable { */ void relocate_construct(void *src, void *dst) const { - BLI_assert(src != dst); + BLI_assert(src != dst || is_trivial_); BLI_assert(this->pointer_can_point_to_instance(src)); BLI_assert(this->pointer_can_point_to_instance(dst)); diff --git a/source/blender/functions/FN_cpp_type_make.hh b/source/blender/functions/FN_cpp_type_make.hh index 088f6b469f4..74dbcabf81a 100644 --- a/source/blender/functions/FN_cpp_type_make.hh +++ b/source/blender/functions/FN_cpp_type_make.hh @@ -195,6 +195,7 @@ CPPType::CPPType(CPPTypeParam<T, Flags> /* unused */, StringRef debug_name) debug_name_ = debug_name; size_ = (int64_t)sizeof(T); alignment_ = (int64_t)alignof(T); + is_trivial_ = std::is_trivial_v<T>; is_trivially_destructible_ = std::is_trivially_destructible_v<T>; if constexpr (std::is_default_constructible_v<T>) { default_construct_ = default_construct_cb<T>; diff --git a/source/blender/functions/intern/multi_function_procedure.cc b/source/blender/functions/intern/multi_function_procedure.cc index fa95e8de71e..986c5dff0c4 100644 --- a/source/blender/functions/intern/multi_function_procedure.cc +++ b/source/blender/functions/intern/multi_function_procedure.cc @@ -419,6 +419,10 @@ bool MFProcedure::validate_initialization() const const MultiFunction &fn = *instruction->fn_; for (const int param_index : fn.param_indices()) { const MFParamType param_type = fn.param_type(param_index); + /* If the parameter was an unneeded output, it could be null. */ + if (!instruction->params_[param_index]) { + continue; + } const MFVariable &variable = *instruction->params_[param_index]; const InitState state = this->find_initialization_state_before_instruction(*instruction, variable); diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt index adf68e534bb..eb1f61b1862 100644 --- a/source/blender/gpencil_modifiers/CMakeLists.txt +++ b/source/blender/gpencil_modifiers/CMakeLists.txt @@ -69,7 +69,8 @@ set(SRC intern/MOD_gpencilthick.c intern/MOD_gpenciltime.c intern/MOD_gpenciltint.c - intern/MOD_gpencilweight.c + intern/MOD_gpencilweight_proximity.c + intern/MOD_gpencilweight_angle.c MOD_gpencil_lineart.h MOD_gpencil_modifiertypes.h diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h index 043186155b7..d9285f44a37 100644 --- a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h +++ b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h @@ -44,7 +44,8 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_Armature; extern GpencilModifierTypeInfo modifierType_Gpencil_Time; extern GpencilModifierTypeInfo modifierType_Gpencil_Multiply; extern GpencilModifierTypeInfo modifierType_Gpencil_Texture; -extern GpencilModifierTypeInfo modifierType_Gpencil_Weight; +extern GpencilModifierTypeInfo modifierType_Gpencil_WeightProximity; +extern GpencilModifierTypeInfo modifierType_Gpencil_WeightAngle; extern GpencilModifierTypeInfo modifierType_Gpencil_Lineart; extern GpencilModifierTypeInfo modifierType_Gpencil_Dash; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c index 5eb1eeab780..df78ac8110e 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c @@ -63,7 +63,8 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]) INIT_GP_TYPE(Time); INIT_GP_TYPE(Multiply); INIT_GP_TYPE(Texture); - INIT_GP_TYPE(Weight); + INIT_GP_TYPE(WeightAngle); + INIT_GP_TYPE(WeightProximity); INIT_GP_TYPE(Lineart); INIT_GP_TYPE(Dash); #undef INIT_GP_TYPE diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c index 6aa0e6c152e..80b60547e92 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c @@ -72,9 +72,14 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) } static bool gpencil_modify_stroke(bGPDstroke *gps, - float length, + const float length, const float overshoot_fac, - const short len_mode) + const short len_mode, + const bool use_curvature, + const int extra_point_count, + const float segment_influence, + const float max_angle, + const bool invert_curvature) { bool changed = false; if (length == 0.0f) { @@ -82,10 +87,18 @@ static bool gpencil_modify_stroke(bGPDstroke *gps, } if (length > 0.0f) { - BKE_gpencil_stroke_stretch(gps, length, overshoot_fac, len_mode); + changed = BKE_gpencil_stroke_stretch(gps, + length, + overshoot_fac, + len_mode, + use_curvature, + extra_point_count, + segment_influence, + max_angle, + invert_curvature); } else { - changed |= BKE_gpencil_stroke_shrink(gps, fabs(length), len_mode); + changed = BKE_gpencil_stroke_shrink(gps, fabs(length), len_mode); } return changed; @@ -96,12 +109,51 @@ static void applyLength(LengthGpencilModifierData *lmd, bGPdata *gpd, bGPDstroke bool changed = false; const float len = (lmd->mode == GP_LENGTH_ABSOLUTE) ? 1.0f : BKE_gpencil_stroke_length(gps, true); + const int totpoints = gps->totpoints; if (len < FLT_EPSILON) { return; } - changed |= gpencil_modify_stroke(gps, len * lmd->start_fac, lmd->overshoot_fac, 1); - changed |= gpencil_modify_stroke(gps, len * lmd->end_fac, lmd->overshoot_fac, 2); + /* Always do the stretching first since it might depend on points which could be deleted by the + * shrink. */ + float first_fac = lmd->start_fac; + int first_mode = 1; + float second_fac = lmd->end_fac; + int second_mode = 2; + if (first_fac < 0) { + SWAP(float, first_fac, second_fac); + SWAP(int, first_mode, second_mode); + } + + const int first_extra_point_count = ceil(first_fac * lmd->point_density); + const int second_extra_point_count = ceil(second_fac * lmd->point_density); + + changed |= gpencil_modify_stroke(gps, + len * first_fac, + lmd->overshoot_fac, + first_mode, + lmd->flag & GP_LENGTH_USE_CURVATURE, + first_extra_point_count, + lmd->segment_influence, + lmd->max_angle, + lmd->flag & GP_LENGTH_INVERT_CURVATURE); + /* HACK: The second #overshoot_fac needs to be adjusted because it is not + * done in the same stretch call, because it can have a different length. + * The adjustment needs to be stable when + * `ceil(overshoot_fac*(gps->totpoints - 2))` is used in stretch and never + * produce a result higher than `totpoints - 2`. */ + const float second_overshoot_fac = lmd->overshoot_fac * (totpoints - 2) / + ((float)gps->totpoints - 2) * + (1.0f - 0.1f / (totpoints - 1.0f)); + changed |= gpencil_modify_stroke(gps, + len * second_fac, + second_overshoot_fac, + second_mode, + lmd->flag & GP_LENGTH_USE_CURVATURE, + second_extra_point_count, + lmd->segment_influence, + lmd->max_angle, + lmd->flag & GP_LENGTH_INVERT_CURVATURE); if (changed) { BKE_gpencil_stroke_geometry_update(gpd, gps); @@ -117,20 +169,25 @@ static void deformStroke(GpencilModifierData *md, { bGPdata *gpd = ob->data; LengthGpencilModifierData *lmd = (LengthGpencilModifierData *)md; - if (is_stroke_affected_by_modifier(ob, - lmd->layername, - lmd->material, - lmd->pass_index, - lmd->layer_pass, - 1, - gpl, - gps, - lmd->flag & GP_LENGTH_INVERT_LAYER, - lmd->flag & GP_LENGTH_INVERT_PASS, - lmd->flag & GP_LENGTH_INVERT_LAYERPASS, - lmd->flag & GP_LENGTH_INVERT_MATERIAL)) { - applyLength(lmd, gpd, gps); + if (!is_stroke_affected_by_modifier(ob, + lmd->layername, + lmd->material, + lmd->pass_index, + lmd->layer_pass, + 1, + gpl, + gps, + lmd->flag & GP_LENGTH_INVERT_LAYER, + lmd->flag & GP_LENGTH_INVERT_PASS, + lmd->flag & GP_LENGTH_INVERT_LAYERPASS, + lmd->flag & GP_LENGTH_INVERT_MATERIAL)) { + return; } + if ((gps->flag & GP_STROKE_CYCLIC) != 0) { + /* Don't affect cyclic strokes as they have no start/end. */ + return; + } + applyLength(lmd, gpd, gps); } static void bakeModifier(Main *UNUSED(bmain), @@ -168,10 +225,16 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) uiLayout *col = uiLayoutColumn(layout, true); - uiItemR(col, ptr, "start_factor", 0, IFACE_("Start"), ICON_NONE); - uiItemR(col, ptr, "end_factor", 0, IFACE_("End"), ICON_NONE); + if (RNA_enum_get(ptr, "mode") == GP_LENGTH_RELATIVE) { + uiItemR(col, ptr, "start_factor", 0, IFACE_("Start"), ICON_NONE); + uiItemR(col, ptr, "end_factor", 0, IFACE_("End"), ICON_NONE); + } + else { + uiItemR(col, ptr, "start_length", 0, IFACE_("Start"), ICON_NONE); + uiItemR(col, ptr, "end_length", 0, IFACE_("End"), ICON_NONE); + } - uiItemR(layout, ptr, "overshoot_factor", UI_ITEM_R_SLIDER, IFACE_("Overshoot"), ICON_NONE); + uiItemR(layout, ptr, "overshoot_factor", UI_ITEM_R_SLIDER, IFACE_("Used Length"), ICON_NONE); gpencil_modifier_panel_end(layout, ptr); } @@ -181,11 +244,40 @@ static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel) gpencil_modifier_masking_panel_draw(panel, true, false); } +static void curvature_header_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiItemR(layout, ptr, "use_curvature", 0, IFACE_("Curvature"), ICON_NONE); +} + +static void curvature_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiLayoutSetPropSep(layout, true); + + uiLayout *col = uiLayoutColumn(layout, false); + + uiLayoutSetActive(col, RNA_boolean_get(ptr, "use_curvature")); + + uiItemR(col, ptr, "point_density", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "segment_influence", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "max_angle", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "invert_curvature", 0, IFACE_("Invert"), ICON_NONE); +} + static void panelRegister(ARegionType *region_type) { PanelType *panel_type = gpencil_modifier_panel_register( region_type, eGpencilModifierType_Length, panel_draw); gpencil_modifier_subpanel_register( + region_type, "curvature", "", curvature_header_draw, curvature_panel_draw, panel_type); + gpencil_modifier_subpanel_register( region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type); } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_angle.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_angle.c new file mode 100644 index 00000000000..2c0f3d2d8ad --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_angle.c @@ -0,0 +1,260 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021, Blender Foundation + * This is a new part of Blender + */ + +/** \file + * \ingroup modifiers + */ + +#include <stdio.h> + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "DNA_defaults.h" +#include "DNA_gpencil_modifier_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" + +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_deform.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_lib_query.h" +#include "BKE_modifier.h" +#include "BKE_screen.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "RNA_access.h" + +#include "MOD_gpencil_modifiertypes.h" +#include "MOD_gpencil_ui_common.h" +#include "MOD_gpencil_util.h" + +static void initData(GpencilModifierData *md) +{ + WeightAngleGpencilModifierData *gpmd = (WeightAngleGpencilModifierData *)md; + + BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier)); + + MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(WeightAngleGpencilModifierData), modifier); +} + +static void copyData(const GpencilModifierData *md, GpencilModifierData *target) +{ + BKE_gpencil_modifier_copydata_generic(md, target); +} + +/* change stroke thickness */ +static void deformStroke(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) +{ + WeightAngleGpencilModifierData *mmd = (WeightAngleGpencilModifierData *)md; + const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); + + if (!is_stroke_affected_by_modifier(ob, + mmd->layername, + mmd->material, + mmd->pass_index, + mmd->layer_pass, + 1, + gpl, + gps, + mmd->flag & GP_WEIGHT_INVERT_LAYER, + mmd->flag & GP_WEIGHT_INVERT_PASS, + mmd->flag & GP_WEIGHT_INVERT_LAYERPASS, + mmd->flag & GP_WEIGHT_INVERT_MATERIAL)) { + return; + } + + const int target_def_nr = BKE_object_defgroup_name_index(ob, mmd->target_vgname); + + if (target_def_nr == -1) { + return; + } + + /* Use default Z up. */ + float vec_axis[3] = {0.0f, 0.0f, 1.0f}; + float axis[3] = {0.0f, 0.0f, 0.0f}; + axis[mmd->axis] = 1.0f; + float vec_ref[3]; + /* Apply modifier rotation (sub 90 degrees for Y axis due Z-Up vector). */ + float rot_angle = mmd->angle - ((mmd->axis == 1) ? M_PI_2 : 0.0f); + rotate_normalized_v3_v3v3fl(vec_ref, vec_axis, axis, rot_angle); + + /* Apply the rotation of the object. */ + if (mmd->space == GP_SPACE_LOCAL) { + mul_mat3_m4_v3(ob->obmat, vec_ref); + } + + /* Ensure there is a vertex group. */ + BKE_gpencil_dvert_ensure(gps); + + float weight_pt = 1.0f; + for (int i = 0; i < gps->totpoints; i++) { + MDeformVert *dvert = gps->dvert != NULL ? &gps->dvert[i] : NULL; + /* Verify point is part of vertex group. */ + float weight = get_modifier_point_weight( + dvert, (mmd->flag & GP_WEIGHT_INVERT_VGROUP) != 0, def_nr); + if (weight < 0.0f) { + continue; + } + + /* Special case for single points. */ + if (gps->totpoints == 1) { + weight_pt = 1.0f; + break; + } + + bGPDspoint *pt1 = (i > 0) ? &gps->points[i] : &gps->points[i + 1]; + bGPDspoint *pt2 = (i > 0) ? &gps->points[i - 1] : &gps->points[i]; + float fpt1[3], fpt2[3]; + mul_v3_m4v3(fpt1, ob->obmat, &pt1->x); + mul_v3_m4v3(fpt2, ob->obmat, &pt2->x); + + float vec[3]; + sub_v3_v3v3(vec, fpt1, fpt2); + float angle = angle_on_axis_v3v3_v3(vec_ref, vec, axis); + /* Use sin to get a value between 0 and 1. */ + weight_pt = 1.0f - sin(angle); + + /* Invert weight if required. */ + if (mmd->flag & GP_WEIGHT_INVERT_OUTPUT) { + weight_pt = 1.0f - weight_pt; + } + /* Assign weight. */ + dvert = gps->dvert != NULL ? &gps->dvert[i] : NULL; + if (dvert != NULL) { + MDeformWeight *dw = BKE_defvert_ensure_index(dvert, target_def_nr); + if (dw) { + dw->weight = (mmd->flag & GP_WEIGHT_MULTIPLY_DATA) ? dw->weight * weight_pt : weight_pt; + CLAMP(dw->weight, mmd->min_weight, 1.0f); + } + } + } +} + +static void bakeModifier(struct Main *UNUSED(bmain), + Depsgraph *depsgraph, + GpencilModifierData *md, + Object *ob) +{ + bGPdata *gpd = ob->data; + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + deformStroke(md, depsgraph, ob, gpl, gpf, gps); + } + } + } +} + +static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + WeightAngleGpencilModifierData *mmd = (WeightAngleGpencilModifierData *)md; + + walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER); +} + +static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams)) +{ + WeightAngleGpencilModifierData *mmd = (WeightAngleGpencilModifierData *)md; + + return (mmd->target_vgname[0] == '\0'); +} + +static void panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *row, *sub; + uiLayout *layout = panel->layout; + + PointerRNA ob_ptr; + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr); + + uiLayoutSetPropSep(layout, true); + row = uiLayoutRow(layout, true); + uiItemPointerR(row, ptr, "target_vertex_group", &ob_ptr, "vertex_groups", NULL, ICON_NONE); + sub = uiLayoutRow(row, true); + bool has_output = RNA_string_length(ptr, "target_vertex_group") != 0; + uiLayoutSetPropDecorate(sub, false); + uiLayoutSetActive(sub, has_output); + uiItemR(sub, ptr, "use_invert_output", 0, "", ICON_ARROW_LEFTRIGHT); + + uiItemR(layout, ptr, "angle", 0, NULL, ICON_NONE); + uiItemR(layout, ptr, "axis", 0, NULL, ICON_NONE); + uiItemR(layout, ptr, "space", 0, NULL, ICON_NONE); + + uiItemR(layout, ptr, "minimum_weight", 0, NULL, ICON_NONE); + uiItemR(layout, ptr, "use_multiply", 0, NULL, ICON_NONE); + + + gpencil_modifier_panel_end(layout, ptr); +} + +static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + gpencil_modifier_masking_panel_draw(panel, true, true); +} + +static void panelRegister(ARegionType *region_type) +{ + PanelType *panel_type = gpencil_modifier_panel_register( + region_type, eGpencilModifierType_WeightAngle, panel_draw); + + gpencil_modifier_subpanel_register( + region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type); +} + +GpencilModifierTypeInfo modifierType_Gpencil_WeightAngle = { + /* name */ "Vertex Weight Angle", + /* structName */ "WeightAngleGpencilModifierData", + /* structSize */ sizeof(WeightAngleGpencilModifierData), + /* type */ eGpencilModifierTypeType_Gpencil, + /* flags */ 0, + + /* copyData */ copyData, + + /* deformStroke */ deformStroke, + /* generateStrokes */ NULL, + /* bakeModifier */ bakeModifier, + /* remapTime */ NULL, + + /* initData */ initData, + /* freeData */ NULL, + /* isDisabled */ isDisabled, + /* updateDepsgraph */ NULL, + /* dependsOnTime */ NULL, + /* foreachIDLink */ foreachIDLink, + /* foreachTexLink */ NULL, + /* panelRegister */ panelRegister, +}; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c index 686023a36d4..0885828a3a0 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c @@ -58,11 +58,11 @@ static void initData(GpencilModifierData *md) { - WeightGpencilModifierData *gpmd = (WeightGpencilModifierData *)md; + WeightProxGpencilModifierData *gpmd = (WeightProxGpencilModifierData *)md; BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier)); - MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(WeightGpencilModifierData), modifier); + MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(WeightProxGpencilModifierData), modifier); } static void copyData(const GpencilModifierData *md, GpencilModifierData *target) @@ -72,7 +72,7 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) /* Calc distance between point and target object. */ static float calc_point_weight_by_distance(Object *ob, - WeightGpencilModifierData *mmd, + WeightProxGpencilModifierData *mmd, const float dist_max, const float dist_min, bGPDspoint *pt) @@ -103,9 +103,8 @@ static void deformStroke(GpencilModifierData *md, bGPDframe *UNUSED(gpf), bGPDstroke *gps) { - WeightGpencilModifierData *mmd = (WeightGpencilModifierData *)md; + WeightProxGpencilModifierData *mmd = (WeightProxGpencilModifierData *)md; const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname); - const eWeightGpencilModifierMode mode = mmd->mode; if (!is_stroke_affected_by_modifier(ob, mmd->layername, @@ -130,20 +129,6 @@ static void deformStroke(GpencilModifierData *md, return; } - /* Use default Z up. */ - float vec_axis[3] = {0.0f, 0.0f, 1.0f}; - float axis[3] = {0.0f, 0.0f, 0.0f}; - axis[mmd->axis] = 1.0f; - float vec_ref[3]; - /* Apply modifier rotation (sub 90 degrees for Y axis due Z-Up vector). */ - float rot_angle = mmd->angle - ((mmd->axis == 1) ? M_PI_2 : 0.0f); - rotate_normalized_v3_v3v3fl(vec_ref, vec_axis, axis, rot_angle); - - /* Apply the rotation of the object. */ - if (mmd->space == GP_SPACE_LOCAL) { - mul_mat3_m4_v3(ob->obmat, vec_ref); - } - /* Ensure there is a vertex group. */ BKE_gpencil_dvert_ensure(gps); @@ -157,36 +142,9 @@ static void deformStroke(GpencilModifierData *md, continue; } - switch (mode) { - case GP_WEIGHT_MODE_DISTANCE: { - if (mmd->object) { - bGPDspoint *pt = &gps->points[i]; - weight_pt = calc_point_weight_by_distance(ob, mmd, dist_max, dist_min, pt); - } - break; - } - case GP_WEIGHT_MODE_ANGLE: { - /* Special case for single points. */ - if (gps->totpoints == 1) { - weight_pt = 1.0f; - break; - } - - bGPDspoint *pt1 = (i > 0) ? &gps->points[i] : &gps->points[i + 1]; - bGPDspoint *pt2 = (i > 0) ? &gps->points[i - 1] : &gps->points[i]; - float fpt1[3], fpt2[3]; - mul_v3_m4v3(fpt1, ob->obmat, &pt1->x); - mul_v3_m4v3(fpt2, ob->obmat, &pt2->x); - - float vec[3]; - sub_v3_v3v3(vec, fpt1, fpt2); - float angle = angle_on_axis_v3v3_v3(vec_ref, vec, axis); - /* Use sin to get a value between 0 and 1. */ - weight_pt = 1.0f - sin(angle); - break; - } - default: - break; + if (mmd->object) { + bGPDspoint *pt = &gps->points[i]; + weight_pt = calc_point_weight_by_distance(ob, mmd, dist_max, dist_min, pt); } /* Invert weight if required. */ @@ -198,7 +156,7 @@ static void deformStroke(GpencilModifierData *md, if (dvert != NULL) { MDeformWeight *dw = BKE_defvert_ensure_index(dvert, target_def_nr); if (dw) { - dw->weight = (mmd->flag & GP_WEIGHT_BLEND_DATA) ? dw->weight * weight_pt : weight_pt; + dw->weight = (mmd->flag & GP_WEIGHT_MULTIPLY_DATA) ? dw->weight * weight_pt : weight_pt; CLAMP(dw->weight, mmd->min_weight, 1.0f); } } @@ -223,7 +181,7 @@ static void bakeModifier(struct Main *UNUSED(bmain), static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData) { - WeightGpencilModifierData *mmd = (WeightGpencilModifierData *)md; + WeightProxGpencilModifierData *mmd = (WeightProxGpencilModifierData *)md; walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER); walk(userData, ob, (ID **)&mmd->object, IDWALK_CB_NOP); @@ -233,7 +191,7 @@ static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx, const int UNUSED(mode)) { - WeightGpencilModifierData *mmd = (WeightGpencilModifierData *)md; + WeightProxGpencilModifierData *mmd = (WeightProxGpencilModifierData *)md; if (mmd->object != NULL) { DEG_add_object_relation( ctx->node, mmd->object, DEG_OB_COMP_TRANSFORM, "GPencil Weight Modifier"); @@ -244,54 +202,36 @@ static void updateDepsgraph(GpencilModifierData *md, static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams)) { - WeightGpencilModifierData *mmd = (WeightGpencilModifierData *)md; - - return (mmd->target_vgname[0] == '\0'); -} - -static void distance_panel_draw(const bContext *UNUSED(C), Panel *panel) -{ - PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); - - uiLayout *layout = panel->layout; - uiLayoutSetPropSep(layout, true); + WeightProxGpencilModifierData *mmd = (WeightProxGpencilModifierData *)md; - uiItemR(layout, ptr, "object", 0, NULL, ICON_CUBE); - uiLayout *sub = uiLayoutColumn(layout, true); - uiItemR(sub, ptr, "distance_start", 0, NULL, ICON_NONE); - uiItemR(sub, ptr, "distance_end", 0, "End", ICON_NONE); + return ((mmd->target_vgname[0] == '\0') || (mmd->object == NULL)); } -static void panel_draw(const bContext *C, Panel *panel) +static void panel_draw(const bContext *UNUSED(C), Panel *panel) { + uiLayout *row, *sub; uiLayout *layout = panel->layout; PointerRNA ob_ptr; PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr); uiLayoutSetPropSep(layout, true); - uiItemR(layout, ptr, "mode", 0, NULL, ICON_NONE); + row = uiLayoutRow(layout, true); + uiItemPointerR(row, ptr, "target_vertex_group", &ob_ptr, "vertex_groups", NULL, ICON_NONE); + sub = uiLayoutRow(row, true); + bool has_output = RNA_string_length(ptr, "target_vertex_group") != 0; + uiLayoutSetPropDecorate(sub, false); + uiLayoutSetActive(sub, has_output); + uiItemR(sub, ptr, "use_invert_output", 0, "", ICON_ARROW_LEFTRIGHT); - const eWeightGpencilModifierMode mode = RNA_enum_get(ptr, "mode"); + uiItemR(layout, ptr, "object", 0, NULL, ICON_NONE); - uiItemPointerR(layout, ptr, "target_vertex_group", &ob_ptr, "vertex_groups", NULL, ICON_NONE); + sub = uiLayoutColumn(layout, true); + uiItemR(sub, ptr, "distance_start", 0, NULL, ICON_NONE); + uiItemR(sub, ptr, "distance_end", 0, NULL, ICON_NONE); uiItemR(layout, ptr, "minimum_weight", 0, NULL, ICON_NONE); - uiItemR(layout, ptr, "use_invert_output", 0, NULL, ICON_NONE); - uiItemR(layout, ptr, "use_blend", 0, NULL, ICON_NONE); - - switch (mode) { - case GP_WEIGHT_MODE_DISTANCE: - distance_panel_draw(C, panel); - break; - case GP_WEIGHT_MODE_ANGLE: - uiItemR(layout, ptr, "angle", 0, NULL, ICON_NONE); - uiItemR(layout, ptr, "axis", 0, NULL, ICON_NONE); - uiItemR(layout, ptr, "space", 0, NULL, ICON_NONE); - break; - default: - break; - } + uiItemR(layout, ptr, "use_multiply", 0, NULL, ICON_NONE); gpencil_modifier_panel_end(layout, ptr); } @@ -304,16 +244,16 @@ static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel) static void panelRegister(ARegionType *region_type) { PanelType *panel_type = gpencil_modifier_panel_register( - region_type, eGpencilModifierType_Weight, panel_draw); + region_type, eGpencilModifierType_WeightProximity, panel_draw); gpencil_modifier_subpanel_register( region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type); } -GpencilModifierTypeInfo modifierType_Gpencil_Weight = { - /* name */ "Vertex Weight", - /* structName */ "WeightGpencilModifierData", - /* structSize */ sizeof(WeightGpencilModifierData), +GpencilModifierTypeInfo modifierType_Gpencil_WeightProximity = { + /* name */ "Vertex Weight Proximity", + /* structName */ "WeightProxGpencilModifierData", + /* structSize */ sizeof(WeightProxGpencilModifierData), /* type */ eGpencilModifierTypeType_Gpencil, /* flags */ 0, diff --git a/source/blender/gpu/GPU_material.h b/source/blender/gpu/GPU_material.h index 312da491a36..e64521768f9 100644 --- a/source/blender/gpu/GPU_material.h +++ b/source/blender/gpu/GPU_material.h @@ -175,10 +175,7 @@ GPUNodeLink *GPU_uniformbuf_link_out(struct GPUMaterial *mat, void GPU_material_output_link(GPUMaterial *material, GPUNodeLink *link); void GPU_material_add_output_link_aov(GPUMaterial *material, GPUNodeLink *link, int hash); -void GPU_material_sss_profile_create(GPUMaterial *material, - float radii[3], - const short *falloff_type, - const float *sharpness); +void GPU_material_sss_profile_create(GPUMaterial *material, float radii[3]); struct GPUUniformBuf *GPU_material_sss_profile_get(GPUMaterial *material, int sample_len, struct GPUTexture **tex_profile); diff --git a/source/blender/gpu/intern/gpu_material.c b/source/blender/gpu/intern/gpu_material.c index 56e72fbeca9..6872a08e854 100644 --- a/source/blender/gpu/intern/gpu_material.c +++ b/source/blender/gpu/intern/gpu_material.c @@ -96,8 +96,6 @@ struct GPUMaterial { float sss_enabled; float sss_radii[3]; int sss_samples; - short int sss_falloff; - float sss_sharpness; bool sss_dirty; GPUTexture *coba_tex; /* 1D Texture array containing all color bands. */ @@ -266,18 +264,6 @@ static void sss_calculate_offsets(GPUSssKernelData *kd, int count, float exponen } } -#define GAUSS_TRUNCATE 12.46f -static float gaussian_profile(float r, float radius) -{ - const float v = radius * radius * (0.25f * 0.25f); - const float Rm = sqrtf(v * GAUSS_TRUNCATE); - - if (r >= Rm) { - return 0.0f; - } - return expf(-r * r / (2.0f * v)) / (2.0f * M_PI * v); -} - #define BURLEY_TRUNCATE 16.0f #define BURLEY_TRUNCATE_CDF 0.9963790093708328f // cdf(BURLEY_TRUNCATE) static float burley_profile(float r, float d) @@ -287,45 +273,15 @@ static float burley_profile(float r, float d) return (exp_r_d + exp_r_3_d) / (4.0f * d); } -static float cubic_profile(float r, float radius, float sharpness) -{ - float Rm = radius * (1.0f + sharpness); - - if (r >= Rm) { - return 0.0f; - } - /* custom variation with extra sharpness, to match the previous code */ - const float y = 1.0f / (1.0f + sharpness); - float Rmy, ry, ryinv; - - Rmy = powf(Rm, y); - ry = powf(r, y); - ryinv = (r > 0.0f) ? powf(r, y - 1.0f) : 0.0f; - - const float Rmy5 = (Rmy * Rmy) * (Rmy * Rmy) * Rmy; - const float f = Rmy - ry; - const float num = f * (f * f) * (y * ryinv); - - return (10.0f * num) / (Rmy5 * M_PI); -} - -static float eval_profile(float r, short falloff_type, float sharpness, float param) +static float eval_profile(float r, float param) { r = fabsf(r); - - if (ELEM(falloff_type, SHD_SUBSURFACE_BURLEY, SHD_SUBSURFACE_RANDOM_WALK)) { - return burley_profile(r, param) / BURLEY_TRUNCATE_CDF; - } - if (falloff_type == SHD_SUBSURFACE_CUBIC) { - return cubic_profile(r, param, sharpness); - } - - return gaussian_profile(r, param); + return burley_profile(r, param) / BURLEY_TRUNCATE_CDF; } /* Resolution for each sample of the precomputed kernel profile */ #define INTEGRAL_RESOLUTION 32 -static float eval_integral(float x0, float x1, short falloff_type, float sharpness, float param) +static float eval_integral(float x0, float x1, float param) { const float range = x1 - x0; const float step = range / INTEGRAL_RESOLUTION; @@ -333,7 +289,7 @@ static float eval_integral(float x0, float x1, short falloff_type, float sharpne for (int i = 0; i < INTEGRAL_RESOLUTION; i++) { float x = x0 + range * ((float)i + 0.5f) / (float)INTEGRAL_RESOLUTION; - float y = eval_profile(x, falloff_type, sharpness, param); + float y = eval_profile(x, param); integral += y * step; } @@ -341,8 +297,7 @@ static float eval_integral(float x0, float x1, short falloff_type, float sharpne } #undef INTEGRAL_RESOLUTION -static void compute_sss_kernel( - GPUSssKernelData *kd, const float radii[3], int sample_len, int falloff_type, float sharpness) +static void compute_sss_kernel(GPUSssKernelData *kd, const float radii[3], int sample_len) { float rad[3]; /* Minimum radius */ @@ -353,27 +308,15 @@ static void compute_sss_kernel( /* Christensen-Burley fitting */ float l[3], d[3]; - if (ELEM(falloff_type, SHD_SUBSURFACE_BURLEY, SHD_SUBSURFACE_RANDOM_WALK)) { - mul_v3_v3fl(l, rad, 0.25f * M_1_PI); - const float A = 1.0f; - const float s = 1.9f - A + 3.5f * (A - 0.8f) * (A - 0.8f); - /* XXX 0.6f Out of nowhere to match cycles! Empirical! Can be tweak better. */ - mul_v3_v3fl(d, l, 0.6f / s); - mul_v3_v3fl(rad, d, BURLEY_TRUNCATE); - kd->max_radius = MAX3(rad[0], rad[1], rad[2]); - - copy_v3_v3(kd->param, d); - } - else if (falloff_type == SHD_SUBSURFACE_CUBIC) { - copy_v3_v3(kd->param, rad); - mul_v3_fl(rad, 1.0f + sharpness); - kd->max_radius = MAX3(rad[0], rad[1], rad[2]); - } - else { - kd->max_radius = MAX3(rad[0], rad[1], rad[2]); + mul_v3_v3fl(l, rad, 0.25f * M_1_PI); + const float A = 1.0f; + const float s = 1.9f - A + 3.5f * (A - 0.8f) * (A - 0.8f); + /* XXX 0.6f Out of nowhere to match cycles! Empirical! Can be tweak better. */ + mul_v3_v3fl(d, l, 0.6f / s); + mul_v3_v3fl(rad, d, BURLEY_TRUNCATE); + kd->max_radius = MAX3(rad[0], rad[1], rad[2]); - copy_v3_v3(kd->param, rad); - } + copy_v3_v3(kd->param, d); /* Compute samples locations on the 1d kernel [-1..1] */ sss_calculate_offsets(kd, sample_len, SSS_EXPONENT); @@ -403,9 +346,9 @@ static void compute_sss_kernel( x0 *= kd->max_radius; x1 *= kd->max_radius; - kd->kernel[i][0] = eval_integral(x0, x1, falloff_type, sharpness, kd->param[0]); - kd->kernel[i][1] = eval_integral(x0, x1, falloff_type, sharpness, kd->param[1]); - kd->kernel[i][2] = eval_integral(x0, x1, falloff_type, sharpness, kd->param[2]); + kd->kernel[i][0] = eval_integral(x0, x1, kd->param[0]); + kd->kernel[i][1] = eval_integral(x0, x1, kd->param[1]); + kd->kernel[i][2] = eval_integral(x0, x1, kd->param[2]); sum[0] += kd->kernel[i][0]; sum[1] += kd->kernel[i][1]; @@ -439,8 +382,6 @@ static void compute_sss_kernel( #define INTEGRAL_RESOLUTION 512 static void compute_sss_translucence_kernel(const GPUSssKernelData *kd, int resolution, - short falloff_type, - float sharpness, float **output) { float(*texels)[4]; @@ -463,9 +404,9 @@ static void compute_sss_translucence_kernel(const GPUSssKernelData *kd, float dist = hypotf(r + r_step * 0.5f, d); float profile[3]; - profile[0] = eval_profile(dist, falloff_type, sharpness, kd->param[0]); - profile[1] = eval_profile(dist, falloff_type, sharpness, kd->param[1]); - profile[2] = eval_profile(dist, falloff_type, sharpness, kd->param[2]); + profile[0] = eval_profile(dist, kd->param[0]); + profile[1] = eval_profile(dist, kd->param[1]); + profile[2] = eval_profile(dist, kd->param[2]); /* Since the profile and configuration are radially symmetrical we * can just evaluate it once and weight it accordingly */ @@ -499,14 +440,9 @@ static void compute_sss_translucence_kernel(const GPUSssKernelData *kd, } #undef INTEGRAL_RESOLUTION -void GPU_material_sss_profile_create(GPUMaterial *material, - float radii[3], - const short *falloff_type, - const float *sharpness) +void GPU_material_sss_profile_create(GPUMaterial *material, float radii[3]) { copy_v3_v3(material->sss_radii, radii); - material->sss_falloff = (falloff_type) ? *falloff_type : 0.0; - material->sss_sharpness = (sharpness) ? *sharpness : 0.0; material->sss_dirty = true; material->sss_enabled = true; @@ -527,20 +463,14 @@ struct GPUUniformBuf *GPU_material_sss_profile_get(GPUMaterial *material, if (material->sss_dirty || (material->sss_samples != sample_len)) { GPUSssKernelData kd; - float sharpness = material->sss_sharpness; - - /* XXX Black magic but it seems to fit. Maybe because we integrate -1..1 */ - sharpness *= 0.5f; - - compute_sss_kernel(&kd, material->sss_radii, sample_len, material->sss_falloff, sharpness); + compute_sss_kernel(&kd, material->sss_radii, sample_len); /* Update / Create UBO */ GPU_uniformbuf_update(material->sss_profile, &kd); /* Update / Create Tex */ float *translucence_profile; - compute_sss_translucence_kernel( - &kd, 64, material->sss_falloff, sharpness, &translucence_profile); + compute_sss_translucence_kernel(&kd, 64, &translucence_profile); if (material->sss_tex_profile != NULL) { GPU_texture_free(material->sss_tex_profile); diff --git a/source/blender/gpu/intern/gpu_material_library.h b/source/blender/gpu/intern/gpu_material_library.h index 782d89d6f2a..d3b12d3a2b7 100644 --- a/source/blender/gpu/intern/gpu_material_library.h +++ b/source/blender/gpu/intern/gpu_material_library.h @@ -27,7 +27,7 @@ #include "GPU_material.h" #define MAX_FUNCTION_NAME 64 -#define MAX_PARAMETER 32 +#define MAX_PARAMETER 36 struct GSet; diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl index d77259638fd..bba84c2be52 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl @@ -19,6 +19,8 @@ void node_bsdf_principled(vec4 base_color, float subsurface, vec3 subsurface_radius, vec4 subsurface_color, + float subsurface_ior, + float subsurface_anisotropy, float metallic, float specular, float specular_tint, @@ -201,6 +203,6 @@ void node_bsdf_principled(vec4 base_color, #else /* clang-format off */ /* Stub principled because it is not compatible with volumetrics. */ -# define node_bsdf_principled(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb, cc, dd, result) (result = CLOSURE_DEFAULT) +# define node_bsdf_principled(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb, cc, dd, ee, ff, result) (result = CLOSURE_DEFAULT) /* clang-format on */ #endif diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl index 5129bf71903..d0c159cdf37 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl @@ -5,8 +5,8 @@ CLOSURE_EVAL_FUNCTION_DECLARE_1(node_subsurface_scattering, Diffuse) void node_subsurface_scattering(vec4 color, float scale, vec3 radius, - float sharpen, - float texture_blur, + float ior, + float anisotropy, vec3 N, float sss_id, out Closure result) @@ -20,15 +20,7 @@ void node_subsurface_scattering(vec4 color, result = CLOSURE_DEFAULT; - /* Not perfect for texture_blur values between 0.0 and 1.0. - * Interpolate between separated color and color applied on irradiance. */ - float one_minus_texture_blur = 1.0 - texture_blur; - vec3 sss_albedo = color.rgb * one_minus_texture_blur + texture_blur; - vec3 radiance_tint = color.rgb * texture_blur + one_minus_texture_blur; - /* Consider output radiance as irradiance. */ - out_Diffuse_0.radiance *= radiance_tint; - - closure_load_sss_data(scale, out_Diffuse_0.radiance, sss_albedo, int(sss_id), result); + closure_load_sss_data(scale, out_Diffuse_0.radiance, color.rgb, int(sss_id), result); /* TODO(fclem) Try to not use this. */ closure_load_ssr_data(vec3(0.0), 0.0, in_Diffuse_0.N, -1.0, result); diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h index 450527c7443..2a3c6f4e3db 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h @@ -283,7 +283,7 @@ .colorband = NULL, \ } -#define _DNA_DEFAULT_WeightGpencilModifierData \ +#define _DNA_DEFAULT_WeightProxGpencilModifierData \ { \ .target_vgname = "", \ .material = NULL, \ @@ -291,12 +291,23 @@ .vgname = "", \ .pass_index = 0, \ .flag = 0, \ - .axis = 1, \ .layer_pass = 0, \ .dist_start = 0.0f, \ .dist_end = 20.0f, \ } +#define _DNA_DEFAULT_WeightAngleGpencilModifierData \ + { \ + .target_vgname = "", \ + .material = NULL, \ + .layername = "", \ + .vgname = "", \ + .pass_index = 0, \ + .flag = 0, \ + .axis = 1, \ + .layer_pass = 0, \ + } + #define _DNA_DEFAULT_LineartGpencilModifierData \ { \ .edge_types = LRT_EDGE_FLAG_ALL_TYPE, \ @@ -314,9 +325,13 @@ { \ .start_fac = 0.1f,\ .end_fac = 0.1f,\ - .overshoot_fac = 0.01f,\ + .overshoot_fac = 0.1f,\ .pass_index = 0,\ .material = NULL,\ + .flag = GP_LENGTH_USE_CURVATURE,\ + .point_density = 30.0f,\ + .segment_influence = 0.0f,\ + .max_angle = DEG2RAD(170.0f),\ } #define _DNA_DEFAULT_DashGpencilModifierData \ diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h index d3429329ef6..8d967a38808 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_types.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h @@ -55,8 +55,9 @@ typedef enum GpencilModifierType { eGpencilModifierType_Texture = 18, eGpencilModifierType_Lineart = 19, eGpencilModifierType_Length = 20, - eGpencilModifierType_Weight = 21, + eGpencilModifierType_WeightProximity = 21, eGpencilModifierType_Dash = 22, + eGpencilModifierType_WeightAngle = 23, /* Keep last. */ NUM_GREASEPENCIL_MODIFIER_TYPES, } GpencilModifierType; @@ -493,7 +494,10 @@ typedef struct LengthGpencilModifierData { float overshoot_fac; /** Modifier mode. */ int mode; - char _pad[4]; + /* Curvature parameters. */ + float point_density; + float segment_influence; + float max_angle; } LengthGpencilModifierData; typedef enum eLengthGpencil_Flag { @@ -501,6 +505,8 @@ typedef enum eLengthGpencil_Flag { GP_LENGTH_INVERT_PASS = (1 << 1), GP_LENGTH_INVERT_LAYERPASS = (1 << 2), GP_LENGTH_INVERT_MATERIAL = (1 << 3), + GP_LENGTH_USE_CURVATURE = (1 << 4), + GP_LENGTH_INVERT_CURVATURE = (1 << 5), } eLengthGpencil_Flag; typedef enum eLengthGpencil_Type { @@ -891,7 +897,7 @@ typedef enum eTextureGpencil_Mode { STROKE_AND_FILL = 2, } eTextureGpencil_Mode; -typedef struct WeightGpencilModifierData { +typedef struct WeightProxGpencilModifierData { GpencilModifierData modifier; /** Target vertexgroup name, MAX_VGROUP_NAME. */ char target_vgname[64]; @@ -909,22 +915,39 @@ typedef struct WeightGpencilModifierData { float min_weight; /** Custom index for passes. */ int layer_pass; - /** Calculation Mode. */ - short mode; - /** Axis. */ - short axis; - /** Angle */ - float angle; /** Start/end distances. */ float dist_start; float dist_end; - /** Space (Local/World). */ - short space; - char _pad[6]; /** Reference object */ struct Object *object; -} WeightGpencilModifierData; +} WeightProxGpencilModifierData; + +typedef struct WeightAngleGpencilModifierData { + GpencilModifierData modifier; + /** Target vertexgroup name, MAX_VGROUP_NAME. */ + char target_vgname[64]; + /** Material for filtering. */ + struct Material *material; + /** Layer name. */ + char layername[64]; + /** Optional vertexgroup filter name, MAX_VGROUP_NAME. */ + char vgname[64]; + /** Custom index for passes. */ + int pass_index; + /** Flags. */ + int flag; + /** Minimum valid weight (clamp value). */ + float min_weight; + /** Custom index for passes. */ + int layer_pass; + /** Axis. */ + short axis; + /** Space (Local/World). */ + short space; + /** Angle */ + float angle; +} WeightAngleGpencilModifierData; typedef enum eWeightGpencil_Flag { GP_WEIGHT_INVERT_LAYER = (1 << 0), @@ -932,15 +955,10 @@ typedef enum eWeightGpencil_Flag { GP_WEIGHT_INVERT_VGROUP = (1 << 2), GP_WEIGHT_INVERT_LAYERPASS = (1 << 3), GP_WEIGHT_INVERT_MATERIAL = (1 << 4), - GP_WEIGHT_BLEND_DATA = (1 << 5), + GP_WEIGHT_MULTIPLY_DATA = (1 << 5), GP_WEIGHT_INVERT_OUTPUT = (1 << 6), } eWeightGpencil_Flag; -typedef enum eWeightGpencilModifierMode { - GP_WEIGHT_MODE_DISTANCE = 0, - GP_WEIGHT_MODE_ANGLE = 1, -} eWeightGpencilModifierMode; - typedef enum eGpencilModifierSpace { GP_SPACE_LOCAL = 0, GP_SPACE_WORLD = 1, diff --git a/source/blender/makesdna/DNA_layer_types.h b/source/blender/makesdna/DNA_layer_types.h index 63e4597150c..520f989452c 100644 --- a/source/blender/makesdna/DNA_layer_types.h +++ b/source/blender/makesdna/DNA_layer_types.h @@ -68,7 +68,7 @@ typedef enum eViewLayerCryptomatteFlags { VIEW_LAYER_CRYPTOMATTE_OBJECT = (1 << 0), VIEW_LAYER_CRYPTOMATTE_MATERIAL = (1 << 1), VIEW_LAYER_CRYPTOMATTE_ASSET = (1 << 2), - VIEW_LAYER_CRYPTOMATTE_ACCURATE = (1 << 3), + /* VIEW_LAYER_CRYPTOMATTE_ACCURATE = (1 << 3), */ /* DEPRECATED */ } eViewLayerCryptomatteFlags; #define VIEW_LAYER_CRYPTOMATTE_ALL \ (VIEW_LAYER_CRYPTOMATTE_OBJECT | VIEW_LAYER_CRYPTOMATTE_MATERIAL | VIEW_LAYER_CRYPTOMATTE_ASSET) diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index f4c88333528..cf159a1e28d 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1032,6 +1032,11 @@ typedef struct NodeShaderTexPointDensity { char _pad2[4]; } NodeShaderTexPointDensity; +typedef struct NodeShaderPrincipled { + char use_subsurface_auto_radius; + char _pad[3]; +} NodeShaderPrincipled; + /* TEX_output */ typedef struct TexNodeOutput { char name[64]; @@ -1452,6 +1457,11 @@ typedef struct NodeGeometryCurveToPoints { uint8_t mode; } NodeGeometryCurveToPoints; +typedef struct NodeGeometryCurveSample { + /* GeometryNodeCurveSampleMode. */ + uint8_t mode; +} NodeGeometryCurveSample; + typedef struct NodeGeometryAttributeTransfer { /* AttributeDomain. */ int8_t domain; @@ -1798,11 +1808,12 @@ enum { enum { #ifdef DNA_DEPRECATED_ALLOW SHD_SUBSURFACE_COMPATIBLE = 0, /* Deprecated */ -#endif SHD_SUBSURFACE_CUBIC = 1, SHD_SUBSURFACE_GAUSSIAN = 2, - SHD_SUBSURFACE_BURLEY = 3, - SHD_SUBSURFACE_RANDOM_WALK = 4, +#endif + SHD_SUBSURFACE_DIFFUSION = 3, + SHD_SUBSURFACE_RANDOM_WALK_FIXED_RADIUS = 4, + SHD_SUBSURFACE_RANDOM_WALK = 5, }; /* blur node */ diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h index 61707964191..9ecf94ebd6e 100644 --- a/source/blender/makesdna/DNA_scene_defaults.h +++ b/source/blender/makesdna/DNA_scene_defaults.h @@ -135,8 +135,6 @@ .border.xmax = 1.0f, \ .border.ymax = 1.0f, \ \ - .preview_start_resolution = 64, \ - \ .line_thickness_mode = R_LINE_THICKNESS_ABSOLUTE, \ .unit_line_thickness = 1.0f, \ \ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 7800e7f9efe..b28c3ac2b85 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -261,7 +261,7 @@ typedef enum eScenePassType { SCE_PASS_UNUSED_3 = (1 << 4), /* SPEC */ SCE_PASS_SHADOW = (1 << 5), SCE_PASS_AO = (1 << 6), - SCE_PASS_UNUSED_4 = (1 << 7), /* REFLECT */ + SCE_PASS_POSITION = (1 << 7), SCE_PASS_NORMAL = (1 << 8), SCE_PASS_VECTOR = (1 << 9), SCE_PASS_UNUSED_5 = (1 << 10), /* REFRACT */ @@ -293,6 +293,7 @@ typedef enum eScenePassType { #define RE_PASSNAME_COMBINED "Combined" #define RE_PASSNAME_Z "Depth" #define RE_PASSNAME_VECTOR "Vector" +#define RE_PASSNAME_POSITION "Position" #define RE_PASSNAME_NORMAL "Normal" #define RE_PASSNAME_UV "UV" #define RE_PASSNAME_EMIT "Emit" @@ -592,7 +593,7 @@ typedef enum eBakeSaveMode { /** #BakeData.pass_filter */ typedef enum eBakePassFilter { R_BAKE_PASS_FILTER_NONE = 0, - R_BAKE_PASS_FILTER_AO = (1 << 0), + R_BAKE_PASS_FILTER_UNUSED = (1 << 0), R_BAKE_PASS_FILTER_EMIT = (1 << 1), R_BAKE_PASS_FILTER_DIFFUSE = (1 << 2), R_BAKE_PASS_FILTER_GLOSSY = (1 << 3), @@ -653,7 +654,8 @@ typedef struct RenderData { /** * render tile dimensions */ - int tilex, tiley; + int tilex DNA_DEPRECATED; + int tiley DNA_DEPRECATED; short planes DNA_DEPRECATED; short imtype DNA_DEPRECATED; @@ -764,13 +766,10 @@ typedef struct RenderData { /* Cycles baking */ struct BakeData bake; - int preview_start_resolution; + int _pad8; short preview_pixel_size; - /* Type of the debug pass to use. - * Only used when built with debug passes support. - */ - short debug_pass_type; + short _pad4; /* MultiView */ /** SceneRenderView. */ @@ -1344,6 +1343,7 @@ typedef struct SequencerToolSettings { /** When there are many snap points, 0-1 range corresponds to resolution from boundbox to all * possible snap points. */ int snap_distance; + int pivot_point; } SequencerToolSettings; typedef enum eSeqOverlapMode { @@ -1886,12 +1886,12 @@ enum { #define R_COMP_CROP (1 << 7) #define R_SCEMODE_UNUSED_8 (1 << 8) /* cleared */ #define R_SINGLE_LAYER (1 << 9) -#define R_EXR_TILE_FILE (1 << 10) +#define R_SCEMODE_UNUSED_10 (1 << 10) /* cleared */ #define R_SCEMODE_UNUSED_11 (1 << 11) /* cleared */ #define R_NO_IMAGE_LOAD (1 << 12) #define R_SCEMODE_UNUSED_13 (1 << 13) /* cleared */ #define R_NO_FRAME_UPDATE (1 << 14) -#define R_FULL_SAMPLE (1 << 15) +#define R_SCEMODE_UNUSED_15 (1 << 15) /* cleared */ #define R_SCEMODE_UNUSED_16 (1 << 16) /* cleared */ #define R_SCEMODE_UNUSED_17 (1 << 17) /* cleared */ #define R_TEXNODE_PREVIEW (1 << 18) diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index 03c38eb71a0..25330acd486 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -74,6 +74,8 @@ typedef struct StripTransform { float scale_x; float scale_y; float rotation; + /** 0-1 range, use SEQ_image_transform_origin_offset_pixelspace_get to convert to pixel space. */ + float origin[2]; } StripTransform; typedef struct StripColorBalance { @@ -516,7 +518,7 @@ enum { SEQ_OVERLAP = (1 << 3), SEQ_FILTERY = (1 << 4), SEQ_MUTE = (1 << 5), - SEQ_FLAG_UNUSED_6 = (1 << 6), /* cleared */ + SEQ_FLAG_SKIP_THUMBNAILS = (1 << 6), SEQ_REVERSE_FRAMES = (1 << 7), SEQ_IPO_FRAME_LOCKED = (1 << 8), SEQ_EFFECT_NOT_LOADED = (1 << 9), @@ -722,6 +724,7 @@ enum { SEQ_CACHE_PREFETCH_ENABLE = (1 << 10), SEQ_CACHE_DISK_CACHE_ENABLE = (1 << 11), + SEQ_CACHE_STORE_THUMBNAIL = (1 << 12), }; #ifdef __cplusplus diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 6505816256c..e849039fa93 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -583,6 +583,7 @@ typedef struct SequencerPreviewOverlay { /* SequencerPreviewOverlay.flag */ typedef enum eSpaceSeq_SequencerPreviewOverlay_Flag { + SEQ_PREVIEW_SHOW_OUTLINE_SELECTED = (1 << 2), SEQ_PREVIEW_SHOW_SAFE_MARGINS = (1 << 3), SEQ_PREVIEW_SHOW_GPENCIL = (1 << 4), SEQ_PREVIEW_SHOW_SAFE_CENTER = (1 << 9), @@ -597,6 +598,7 @@ typedef struct SequencerTimelineOverlay { /* SequencerTimelineOverlay.flag */ typedef enum eSpaceSeq_SequencerTimelineOverlay_Flag { SEQ_TIMELINE_SHOW_STRIP_OFFSETS = (1 << 1), + SEQ_TIMELINE_SHOW_THUMBNAILS = (1 << 2), SEQ_TIMELINE_SHOW_FCURVES = (1 << 5), SEQ_TIMELINE_ALL_WAVEFORMS = (1 << 7), /* draw all waveforms */ SEQ_TIMELINE_NO_WAVEFORMS = (1 << 8), /* draw no waveforms */ @@ -606,6 +608,13 @@ typedef enum eSpaceSeq_SequencerTimelineOverlay_Flag { SEQ_TIMELINE_SHOW_GRID = (1 << 18), } eSpaceSeq_SequencerTimelineOverlay_Flag; +typedef struct SpaceSeqRuntime { + /** Required for Thumbnail job start condition. */ + struct rctf last_thumbnail_area; + /** Stores lists of most recently displayed thumbnails. */ + struct GHash *last_displayed_thumbnails; +} SpaceSeqRuntime; + /* Sequencer */ typedef struct SpaceSeq { SpaceLink *next, *prev; @@ -649,6 +658,7 @@ typedef struct SpaceSeq { char multiview_eye; char _pad2[7]; + SpaceSeqRuntime runtime; } SpaceSeq; /* SpaceSeq.mainb */ @@ -685,6 +695,7 @@ typedef enum eSpaceSeq_Flag { SPACE_SEQ_FLAG_UNUSED_15 = (1 << 15), SPACE_SEQ_FLAG_UNUSED_16 = (1 << 16), SEQ_USE_PROXIES = (1 << 17), + SEQ_SHOW_GRID = (1 << 18), } eSpaceSeq_Flag; /* SpaceSeq.view */ diff --git a/source/blender/makesdna/DNA_uuid_types.h b/source/blender/makesdna/DNA_uuid_types.h index 30c8beaa628..fa0a78f074b 100644 --- a/source/blender/makesdna/DNA_uuid_types.h +++ b/source/blender/makesdna/DNA_uuid_types.h @@ -28,15 +28,17 @@ extern "C" { /** * \brief Universally Unique Identifier according to RFC4122. + * + * Cannot be named simply `UUID`, because Windows already defines that type. */ -typedef struct UUID { +typedef struct bUUID { uint32_t time_low; uint16_t time_mid; uint16_t time_hi_and_version; uint8_t clock_seq_hi_and_reserved; uint8_t clock_seq_low; uint8_t node[6]; -} UUID; +} bUUID; #ifdef __cplusplus } diff --git a/source/blender/makesdna/DNA_view2d_types.h b/source/blender/makesdna/DNA_view2d_types.h index c385ac04bd3..f8166305fd9 100644 --- a/source/blender/makesdna/DNA_view2d_types.h +++ b/source/blender/makesdna/DNA_view2d_types.h @@ -132,6 +132,8 @@ enum { V2D_PIXELOFS_X = (1 << 2), /* apply pixel offsets on y-axis when setting view matrices */ V2D_PIXELOFS_Y = (1 << 3), + /* zoom, pan or similar action is in progress */ + V2D_IS_NAVIGATING = (1 << 9), /* view settings need to be set still... */ V2D_IS_INIT = (1 << 10), }; diff --git a/source/blender/makesdna/DNA_workspace_types.h b/source/blender/makesdna/DNA_workspace_types.h index e0294d3534c..a0856588a58 100644 --- a/source/blender/makesdna/DNA_workspace_types.h +++ b/source/blender/makesdna/DNA_workspace_types.h @@ -29,6 +29,15 @@ extern "C" { #endif +/** #bToolRef_Runtime.flag */ +enum { + /** + * This tool should use the fallback key-map. + * Typically gizmos handle this but some tools (such as the knife tool) don't use a gizmo. + */ + TOOLREF_FLAG_FALLBACK_KEYMAP = (1 << 0), +}; + # # typedef struct bToolRef_Runtime { @@ -47,6 +56,8 @@ typedef struct bToolRef_Runtime { /** Index when a tool is a member of a group. */ int index; + /** Options: `TOOLREF_FLAG_*`. */ + int flag; } bToolRef_Runtime; /* Stored per mode. */ diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index 4cb8610f6ac..2dbbb35c3ca 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -318,7 +318,8 @@ SDNA_DEFAULT_DECL_STRUCT(TextureGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(ThickGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(TimeGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(TintGpencilModifierData); -SDNA_DEFAULT_DECL_STRUCT(WeightGpencilModifierData); +SDNA_DEFAULT_DECL_STRUCT(WeightProxGpencilModifierData); +SDNA_DEFAULT_DECL_STRUCT(WeightAngleGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(LineartGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(LengthGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierData); @@ -548,7 +549,8 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = { SDNA_DEFAULT_DECL(ThickGpencilModifierData), SDNA_DEFAULT_DECL(TimeGpencilModifierData), SDNA_DEFAULT_DECL(TintGpencilModifierData), - SDNA_DEFAULT_DECL(WeightGpencilModifierData), + SDNA_DEFAULT_DECL(WeightAngleGpencilModifierData), + SDNA_DEFAULT_DECL(WeightProxGpencilModifierData), SDNA_DEFAULT_DECL(LineartGpencilModifierData), SDNA_DEFAULT_DECL(LengthGpencilModifierData), SDNA_DEFAULT_DECL(DashGpencilModifierData), diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 4fa33424994..a2d5b134056 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -58,6 +58,17 @@ #include "WM_types.h" const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { + {0, "", 0, N_("Modify"), ""}, + {eGpencilModifierType_WeightAngle, + "GP_WEIGHT_ANGLE", + ICON_MOD_VERTEX_WEIGHT, + "Vertex Weight Angle", + "Generate Vertex Weights base on stroke angle"}, + {eGpencilModifierType_WeightProximity, + "GP_WEIGHT_PROXIMITY", + ICON_MOD_VERTEX_WEIGHT, + "Vertex Weight Proximity", + "Generate Vertex Weights base on distance to object"}, {0, "", 0, N_("Generate"), ""}, {eGpencilModifierType_Array, "GP_ARRAY", @@ -74,6 +85,11 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_DASH, "Dot Dash", "Generate dot-dash styled strokes"}, + {eGpencilModifierType_Length, + "GP_LENGTH", + ICON_MOD_LENGTH, + "Length", + "Extend or shrink strokes"}, {eGpencilModifierType_Lineart, "GP_LINEART", ICON_MOD_LINEART, @@ -99,11 +115,6 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_SUBSURF, "Subdivide", "Subdivide stroke adding more control points"}, - {eGpencilModifierType_Weight, - "GP_WEIGHT", - ICON_MOD_VERTEX_WEIGHT, - "Vertex Weight", - "Generate Vertex Weights"}, {0, "", 0, N_("Deform"), ""}, {eGpencilModifierType_Armature, "GP_ARMATURE", @@ -120,11 +131,6 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_LATTICE, "Lattice", "Deform strokes using lattice"}, - {eGpencilModifierType_Length, - "GP_LENGTH", - ICON_MOD_LENGTH, - "Length", - "Extend or shrink strokes"}, {eGpencilModifierType_Noise, "GP_NOISE", ICON_MOD_NOISE, "Noise", "Add noise to strokes"}, {eGpencilModifierType_Offset, "GP_OFFSET", @@ -244,8 +250,10 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr) return &RNA_TintGpencilModifier; case eGpencilModifierType_Time: return &RNA_TimeGpencilModifier; - case eGpencilModifierType_Weight: - return &RNA_WeightGpencilModifier; + case eGpencilModifierType_WeightProximity: + return &RNA_WeightProxGpencilModifier; + case eGpencilModifierType_WeightAngle: + return &RNA_WeightAngleGpencilModifier; case eGpencilModifierType_Color: return &RNA_ColorGpencilModifier; case eGpencilModifierType_Array: @@ -346,8 +354,10 @@ RNA_GP_MOD_VGROUP_NAME_SET(Offset, vgname); RNA_GP_MOD_VGROUP_NAME_SET(Armature, vgname); RNA_GP_MOD_VGROUP_NAME_SET(Texture, vgname); RNA_GP_MOD_VGROUP_NAME_SET(Tint, vgname); -RNA_GP_MOD_VGROUP_NAME_SET(Weight, target_vgname); -RNA_GP_MOD_VGROUP_NAME_SET(Weight, vgname); +RNA_GP_MOD_VGROUP_NAME_SET(WeightProx, target_vgname); +RNA_GP_MOD_VGROUP_NAME_SET(WeightProx, vgname); +RNA_GP_MOD_VGROUP_NAME_SET(WeightAngle, target_vgname); +RNA_GP_MOD_VGROUP_NAME_SET(WeightAngle, vgname); RNA_GP_MOD_VGROUP_NAME_SET(Lineart, vgname); # undef RNA_GP_MOD_VGROUP_NAME_SET @@ -380,7 +390,7 @@ static void greasepencil_modifier_object_set(Object *self, RNA_GP_MOD_OBJECT_SET(Armature, object, OB_ARMATURE); RNA_GP_MOD_OBJECT_SET(Lattice, object, OB_LATTICE); RNA_GP_MOD_OBJECT_SET(Mirror, object, OB_EMPTY); -RNA_GP_MOD_OBJECT_SET(Weight, object, OB_EMPTY); +RNA_GP_MOD_OBJECT_SET(WeightProx, object, OB_EMPTY); # undef RNA_GP_MOD_OBJECT_SET @@ -554,11 +564,21 @@ static void rna_ThickGpencilModifier_material_set(PointerRNA *ptr, rna_GpencilModifier_material_set(ptr, value, ma_target, reports); } -static void rna_WeightGpencilModifier_material_set(PointerRNA *ptr, - PointerRNA value, - struct ReportList *reports) +static void rna_WeightProxGpencilModifier_material_set(PointerRNA *ptr, + PointerRNA value, + struct ReportList *reports) { - WeightGpencilModifierData *tmd = (WeightGpencilModifierData *)ptr->data; + WeightProxGpencilModifierData *tmd = (WeightProxGpencilModifierData *)ptr->data; + Material **ma_target = &tmd->material; + + rna_GpencilModifier_material_set(ptr, value, ma_target, reports); +} + +static void rna_WeightAngleGpencilModifier_material_set(PointerRNA *ptr, + PointerRNA value, + struct ReportList *reports) +{ + WeightAngleGpencilModifierData *tmd = (WeightAngleGpencilModifierData *)ptr->data; Material **ma_target = &tmd->material; rna_GpencilModifier_material_set(ptr, value, ma_target, reports); @@ -2783,24 +2803,129 @@ static void rna_def_modifier_gpenciltexture(BlenderRNA *brna) RNA_define_lib_overridable(false); } -static void rna_def_modifier_gpencilweight(BlenderRNA *brna) +static void rna_def_modifier_gpencilweight_proximity(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "WeightProxGpencilModifier", "GpencilModifier"); + RNA_def_struct_ui_text(srna, "Weight Modifier Proximity", "Calculate Vertex Weight dynamically"); + RNA_def_struct_sdna(srna, "WeightProxGpencilModifierData"); + RNA_def_struct_ui_icon(srna, ICON_MOD_VERTEX_WEIGHT); + + RNA_define_lib_overridable(true); + + prop = RNA_def_property(srna, "target_vertex_group", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "target_vgname"); + RNA_def_property_ui_text(prop, "Vertex Group", "Output Vertex group"); + RNA_def_property_string_funcs( + prop, NULL, NULL, "rna_WeightProxGpencilModifier_target_vgname_set"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "use_multiply", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_MULTIPLY_DATA); + RNA_def_property_ui_text( + prop, + "Multiply Weights", + "Multiply the calculated weights with the existing values in the vertex group"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "use_invert_output", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_OUTPUT); + RNA_def_property_ui_text(prop, "Invert", "Invert output weight values"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "layer", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "layername"); + RNA_def_property_ui_text(prop, "Layer", "Layer name"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "material", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_pointer_funcs(prop, + NULL, + "rna_WeightProxGpencilModifier_material_set", + NULL, + "rna_GpencilModifier_material_poll"); + RNA_def_property_ui_text(prop, "Material", "Material used for filtering effect"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "vgname"); + RNA_def_property_ui_text(prop, "Vertex Group", "Vertex group name for modulating the deform"); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_WeightProxGpencilModifier_vgname_set"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + /* Distance reference object */ + prop = RNA_def_property(srna, "object", PROP_POINTER, PROP_NONE); + RNA_def_property_ui_text(prop, "Target Object", "Object used as distance reference"); + RNA_def_property_pointer_funcs( + prop, NULL, "rna_WeightProxGpencilModifier_object_set", NULL, NULL); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update"); + + prop = RNA_def_property(srna, "distance_start", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "dist_start"); + RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); + RNA_def_property_ui_text(prop, "Lowest", "Start value for distance calculation"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "minimum_weight", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "min_weight"); + RNA_def_property_ui_text(prop, "Minimum", "Minimum value for vertex weight"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "distance_end", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "dist_end"); + RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); + RNA_def_property_ui_text(prop, "Highest", "Max value for distance calculation"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "pass_index"); + RNA_def_property_range(prop, 0, 100); + RNA_def_property_ui_text(prop, "Pass", "Pass index"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_layers", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_LAYER); + RNA_def_property_ui_text(prop, "Inverse Layers", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_materials", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_MATERIAL); + RNA_def_property_ui_text(prop, "Inverse Materials", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_material_pass", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_PASS); + RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_vertex", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_VGROUP); + RNA_def_property_ui_text(prop, "Inverse VertexGroup", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "layer_pass", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "layer_pass"); + RNA_def_property_range(prop, 0, 100); + RNA_def_property_ui_text(prop, "Pass", "Layer pass index"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_layer_pass", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_LAYERPASS); + RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + RNA_define_lib_overridable(false); +} + +static void rna_def_modifier_gpencilweight_angle(BlenderRNA *brna) { StructRNA *srna; PropertyRNA *prop; - static const EnumPropertyItem mode_items[] = { - {GP_WEIGHT_MODE_DISTANCE, - "DISTANCE", - 0, - "Distance", - "Calculate weights depending on the distance to the target object"}, - {GP_WEIGHT_MODE_ANGLE, - "ANGLE", - 0, - "Angle", - "Calculate weights depending on the stroke orientation"}, - {0, NULL, 0, NULL, NULL}, - }; static const EnumPropertyItem axis_items[] = { {0, "X", 0, "X", ""}, {1, "Y", 0, "Y", ""}, @@ -2814,34 +2939,31 @@ static void rna_def_modifier_gpencilweight(BlenderRNA *brna) {0, NULL, 0, NULL, NULL}, }; - srna = RNA_def_struct(brna, "WeightGpencilModifier", "GpencilModifier"); - RNA_def_struct_ui_text(srna, "Weight Modifier", "Calculate Vertex Weight dynamically"); - RNA_def_struct_sdna(srna, "WeightGpencilModifierData"); + srna = RNA_def_struct(brna, "WeightAngleGpencilModifier", "GpencilModifier"); + RNA_def_struct_ui_text(srna, "Weight Modifier Amgle", "Calculate Vertex Weight dynamically"); + RNA_def_struct_sdna(srna, "WeightAngleGpencilModifierData"); RNA_def_struct_ui_icon(srna, ICON_MOD_VERTEX_WEIGHT); RNA_define_lib_overridable(true); - prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_sdna(prop, NULL, "mode"); - RNA_def_property_enum_items(prop, mode_items); - RNA_def_property_ui_text(prop, "Mode", ""); - RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); - prop = RNA_def_property(srna, "target_vertex_group", PROP_STRING, PROP_NONE); RNA_def_property_string_sdna(prop, NULL, "target_vgname"); - RNA_def_property_ui_text(prop, "Output", "Output Vertex group"); - RNA_def_property_string_funcs(prop, NULL, NULL, "rna_WeightGpencilModifier_target_vgname_set"); + RNA_def_property_ui_text(prop, "Vertex Group", "Output Vertex group"); + RNA_def_property_string_funcs( + prop, NULL, NULL, "rna_WeightProxGpencilModifier_target_vgname_set"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); - prop = RNA_def_property(srna, "use_blend", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_BLEND_DATA); + prop = RNA_def_property(srna, "use_multiply", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_MULTIPLY_DATA); RNA_def_property_ui_text( - prop, "Blend", "Blend results with existing weights in output weight group"); + prop, + "Multiply Weights", + "Multiply the calculated weights with the existing values in the vertex group"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "use_invert_output", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_OUTPUT); - RNA_def_property_ui_text(prop, "Invert", "Invert weight values"); + RNA_def_property_ui_text(prop, "Invert", "Invert output weight values"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "angle", PROP_FLOAT, PROP_ANGLE); @@ -2871,7 +2993,7 @@ static void rna_def_modifier_gpencilweight(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_EDITABLE); RNA_def_property_pointer_funcs(prop, NULL, - "rna_WeightGpencilModifier_material_set", + "rna_WeightAngleGpencilModifier_material_set", NULL, "rna_GpencilModifier_material_poll"); RNA_def_property_ui_text(prop, "Material", "Material used for filtering effect"); @@ -2880,20 +3002,7 @@ static void rna_def_modifier_gpencilweight(BlenderRNA *brna) prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE); RNA_def_property_string_sdna(prop, NULL, "vgname"); RNA_def_property_ui_text(prop, "Vertex Group", "Vertex group name for modulating the deform"); - RNA_def_property_string_funcs(prop, NULL, NULL, "rna_WeightGpencilModifier_vgname_set"); - RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); - - /* Distance reference object */ - prop = RNA_def_property(srna, "object", PROP_POINTER, PROP_NONE); - RNA_def_property_ui_text(prop, "Object", "Object used as distance reference"); - RNA_def_property_pointer_funcs(prop, NULL, "rna_WeightGpencilModifier_object_set", NULL, NULL); - RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); - RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update"); - - prop = RNA_def_property(srna, "distance_start", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, NULL, "dist_start"); - RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); - RNA_def_property_ui_text(prop, "Distance Start", "Start value for distance calculation"); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_WeightAngleGpencilModifier_vgname_set"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "minimum_weight", PROP_FLOAT, PROP_FACTOR); @@ -2901,12 +3010,6 @@ static void rna_def_modifier_gpencilweight(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Minimum", "Minimum value for vertex weight"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); - prop = RNA_def_property(srna, "distance_end", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, NULL, "dist_end"); - RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); - RNA_def_property_ui_text(prop, "Distance End", "End value for distance calculation"); - RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); - prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "pass_index"); RNA_def_property_range(prop, 0, 100); @@ -3278,14 +3381,29 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna) prop = RNA_def_property(srna, "start_factor", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "start_fac"); - RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.1, 1); - RNA_def_property_ui_text(prop, "Start Factor", "Length difference for each segment"); + RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.1, 2); + RNA_def_property_ui_text( + prop, "Start Factor", "Added length to the start of each stroke relative to its length"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "end_factor", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "end_fac"); - RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.1, 1); - RNA_def_property_ui_text(prop, "End Factor", "Length difference for each segment"); + RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.1, 2); + RNA_def_property_ui_text( + prop, "End Factor", "Added length to the end of each stroke relative to its length"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "start_length", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_float_sdna(prop, NULL, "start_fac"); + RNA_def_property_ui_range(prop, -100.0f, 100.0f, 0.1f, 3); + RNA_def_property_ui_text( + prop, "Start Factor", "Absolute added length to the start of each stroke"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "end_length", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_float_sdna(prop, NULL, "end_fac"); + RNA_def_property_ui_range(prop, -100.0f, 100.0f, 0.1f, 3); + RNA_def_property_ui_text(prop, "End Factor", "Absolute added length to the end of each stroke"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "overshoot_factor", PROP_FLOAT, PROP_FACTOR); @@ -3293,8 +3411,8 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna) RNA_def_property_range(prop, 0.0f, 1.0f); RNA_def_property_ui_text( prop, - "Overshoot Factor", - "Defines how precise must follow the stroke trajectory for the overshoot extremes"); + "Used Length", + "Defines what portion of the stroke is used for the calculation of the extension"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); @@ -3303,6 +3421,44 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Mode", "Mode to define length"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "use_curvature", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_USE_CURVATURE); + RNA_def_property_ui_text(prop, "Use Curvature", "Follow the curvature of the stroke"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_curvature", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_CURVATURE); + RNA_def_property_ui_text( + prop, "Invert Curvature", "Invert the curvature of the stroke's extension"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "point_density", PROP_FLOAT, PROP_NONE); + RNA_def_property_range(prop, 0.1f, 1000.0f); + RNA_def_property_ui_range(prop, 0.1f, 1000.0f, 1.0f, 1); + RNA_def_property_ui_scale_type(prop, PROP_SCALE_CUBIC); + RNA_def_property_ui_text( + prop, "Point Density", "Multiplied by Start/End for the total added point count"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "segment_influence", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_range(prop, -2.0f, 3.0f); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1f, 2); + RNA_def_property_ui_text(prop, + "Segment Influence", + "Factor to determine how much the length of the individual segments " + "should influence the final computed curvature. Higher factors makes " + "small segments influence the overall curvature less"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "max_angle", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_ui_text(prop, + "Filter Angle", + "Ignore points on the stroke that deviate from their neighbors by more " + "than this angle when determining the extrapolation shape"); + RNA_def_property_range(prop, 0.0f, DEG2RAD(180.0f)); + RNA_def_property_ui_range(prop, 0.0f, DEG2RAD(179.5f), 10.0f, 1); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "layer", PROP_STRING, PROP_NONE); RNA_def_property_string_sdna(prop, NULL, "layername"); RNA_def_property_ui_text(prop, "Layer", "Layer name"); @@ -3552,7 +3708,8 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna) rna_def_modifier_gpencilarmature(brna); rna_def_modifier_gpencilmultiply(brna); rna_def_modifier_gpenciltexture(brna); - rna_def_modifier_gpencilweight(brna); + rna_def_modifier_gpencilweight_angle(brna); + rna_def_modifier_gpencilweight_proximity(brna); rna_def_modifier_gpencillineart(brna); rna_def_modifier_gpencillength(brna); rna_def_modifier_gpencildash(brna); diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 76e37dbcdbc..ec53f35df4c 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -2168,6 +2168,17 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeFill_type_itemf(bContext return itemf_function_check(rna_enum_attribute_type_items, attribute_fill_type_supported); } +static bool attribute_statistic_type_supported(const EnumPropertyItem *item) +{ + return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3); +} +static const EnumPropertyItem *rna_GeometryNodeAttributeStatistic_type_itemf( + bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + *r_free = true; + return itemf_function_check(rna_enum_attribute_type_items, attribute_statistic_type_supported); +} + /** * This bit of ugly code makes sure the float / attribute option shows up instead of * vector / attribute if the node uses an operation that uses a float for input B or C. @@ -4654,16 +4665,18 @@ static const EnumPropertyItem node_principled_distribution_items[] = { }; static const EnumPropertyItem node_subsurface_method_items[] = { - {SHD_SUBSURFACE_BURLEY, - "BURLEY", + {SHD_SUBSURFACE_RANDOM_WALK_FIXED_RADIUS, + "RANDOM_WALK_FIXED_RADIUS", 0, - "Christensen-Burley", - "Approximation to physically based volume scattering"}, + "Random Walk (Fixed Radius)", + "Volumetric approximation to physically based volume scattering, using the scattering radius " + "as specified"}, {SHD_SUBSURFACE_RANDOM_WALK, "RANDOM_WALK", 0, "Random Walk", - "Volumetric approximation to physically based volume scattering"}, + "Volumetric approximation to physically based volume scattering, with scattering radius " + "automatically adjusted to match color textures"}, {0, NULL, 0, NULL, NULL}}; /* -- Common nodes ---------------------------------------------------------- */ @@ -6133,35 +6146,12 @@ static void def_sh_ambient_occlusion(StructRNA *srna) static void def_sh_subsurface(StructRNA *srna) { - static const EnumPropertyItem prop_subsurface_falloff_items[] = { - {SHD_SUBSURFACE_CUBIC, "CUBIC", 0, "Cubic", "Simple cubic falloff function"}, - {SHD_SUBSURFACE_GAUSSIAN, - "GAUSSIAN", - 0, - "Gaussian", - "Normal distribution, multiple can be combined to fit more complex profiles"}, - {SHD_SUBSURFACE_BURLEY, - "BURLEY", - 0, - "Christensen-Burley", - "Approximation to physically based volume scattering"}, - {SHD_SUBSURFACE_RANDOM_WALK, - "RANDOM_WALK", - 0, - "Random Walk", - "Volumetric approximation to physically based volume scattering"}, - {0, NULL, 0, NULL, NULL}, - }; - PropertyRNA *prop; prop = RNA_def_property(srna, "falloff", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "custom1"); - RNA_def_property_enum_items(prop, prop_subsurface_falloff_items); - RNA_def_property_ui_text(prop, - "Falloff", - "Function to determine how much light nearby points contribute based " - "on their distance to the shading point"); + RNA_def_property_enum_items(prop, node_subsurface_method_items); + RNA_def_property_ui_text(prop, "Method", "Method for rendering subsurface scattering"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_ShaderNode_socket_update"); } @@ -9077,6 +9067,30 @@ static void def_geo_curve_primitive_bezier_segment(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_curve_sample(StructRNA *srna) +{ + static EnumPropertyItem mode_items[] = { + {GEO_NODE_CURVE_SAMPLE_FACTOR, + "FACTOR", + 0, + "Factor", + "Find sample positions on the curve using a factor of its total length"}, + {GEO_NODE_CURVE_SAMPLE_LENGTH, + "LENGTH", + 0, + "Length", + "Find sample positions on the curve using a distance from its beginning"}, + {0, NULL, 0, NULL, NULL}, + }; + + RNA_def_struct_sdna_from(srna, "NodeGeometryCurveSample", "storage"); + + PropertyRNA *prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, mode_items); + RNA_def_property_ui_text(prop, "Mode", "Method for sampling input"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_triangulate(StructRNA *srna) { PropertyRNA *prop; @@ -9219,6 +9233,29 @@ static void def_geo_attribute_convert(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_attribute_statistic(StructRNA *srna) +{ + PropertyRNA *prop; + + prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeStatistic_type_itemf"); + RNA_def_property_enum_default(prop, CD_PROP_FLOAT); + RNA_def_property_ui_text( + prop, + "Data Type", + "The data type the attribute is converted to before calculating the results"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update"); + + prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom2"); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); + RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT); + RNA_def_property_ui_text(prop, "Domain", "Which domain to read the data from"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + static void def_geo_attribute_math(StructRNA *srna) { PropertyRNA *prop; diff --git a/source/blender/makesrna/intern/rna_render.c b/source/blender/makesrna/intern/rna_render.c index 4400d198b4a..fcb46904e8d 100644 --- a/source/blender/makesrna/intern/rna_render.c +++ b/source/blender/makesrna/intern/rna_render.c @@ -52,6 +52,7 @@ const EnumPropertyItem rna_enum_render_pass_type_items[] = { {SCE_PASS_Z, "Z", 0, "Z", ""}, {SCE_PASS_SHADOW, "SHADOW", 0, "Shadow", ""}, {SCE_PASS_AO, "AO", 0, "Ambient Occlusion", ""}, + {SCE_PASS_POSITION, "POSITION", 0, "Position", ""}, {SCE_PASS_NORMAL, "NORMAL", 0, "Normal", ""}, {SCE_PASS_VECTOR, "VECTOR", 0, "Vector", ""}, {SCE_PASS_INDEXOB, "OBJECT_INDEX", 0, "Object Index", ""}, @@ -79,6 +80,7 @@ const EnumPropertyItem rna_enum_bake_pass_type_items[] = { {SCE_PASS_COMBINED, "COMBINED", 0, "Combined", ""}, {SCE_PASS_AO, "AO", 0, "Ambient Occlusion", ""}, {SCE_PASS_SHADOW, "SHADOW", 0, "Shadow", ""}, + {SCE_PASS_POSITION, "POSITION", 0, "Position", ""}, {SCE_PASS_NORMAL, "NORMAL", 0, "Normal", ""}, {SCE_PASS_UV, "UV", 0, "UV", ""}, {SCE_PASS_ROUGHNESS, "ROUGHNESS", 0, "ROUGHNESS", ""}, @@ -177,6 +179,40 @@ static void engine_render(RenderEngine *engine, Depsgraph *depsgraph) RNA_parameter_list_free(&list); } +static void engine_render_frame_finish(RenderEngine *engine) +{ + extern FunctionRNA rna_RenderEngine_render_frame_finish_func; + PointerRNA ptr; + ParameterList list; + FunctionRNA *func; + + RNA_pointer_create(NULL, engine->type->rna_ext.srna, engine, &ptr); + func = &rna_RenderEngine_render_frame_finish_func; + + RNA_parameter_list_create(&list, &ptr, func); + engine->type->rna_ext.call(NULL, &ptr, func, &list); + + RNA_parameter_list_free(&list); +} + +static void engine_draw(RenderEngine *engine, const struct bContext *context, Depsgraph *depsgraph) +{ + extern FunctionRNA rna_RenderEngine_draw_func; + PointerRNA ptr; + ParameterList list; + FunctionRNA *func; + + RNA_pointer_create(NULL, engine->type->rna_ext.srna, engine, &ptr); + func = &rna_RenderEngine_draw_func; + + RNA_parameter_list_create(&list, &ptr, func); + RNA_parameter_set_lookup(&list, "context", &context); + RNA_parameter_set_lookup(&list, "depsgraph", &depsgraph); + engine->type->rna_ext.call(NULL, &ptr, func, &list); + + RNA_parameter_list_free(&list); +} + static void engine_bake(RenderEngine *engine, struct Depsgraph *depsgraph, struct Object *object, @@ -315,7 +351,7 @@ static StructRNA *rna_RenderEngine_register(Main *bmain, RenderEngineType *et, dummyet = {NULL}; RenderEngine dummyengine = {NULL}; PointerRNA dummyptr; - int have_function[8]; + int have_function[9]; /* setup dummy engine & engine type to store static properties in */ dummyengine.type = &dummyet; @@ -358,11 +394,13 @@ static StructRNA *rna_RenderEngine_register(Main *bmain, et->update = (have_function[0]) ? engine_update : NULL; et->render = (have_function[1]) ? engine_render : NULL; - et->bake = (have_function[2]) ? engine_bake : NULL; - et->view_update = (have_function[3]) ? engine_view_update : NULL; - et->view_draw = (have_function[4]) ? engine_view_draw : NULL; - et->update_script_node = (have_function[5]) ? engine_update_script_node : NULL; - et->update_render_passes = (have_function[6]) ? engine_update_render_passes : NULL; + et->render_frame_finish = (have_function[2]) ? engine_render_frame_finish : NULL; + et->draw = (have_function[3]) ? engine_draw : NULL; + et->bake = (have_function[4]) ? engine_bake : NULL; + et->view_update = (have_function[5]) ? engine_view_update : NULL; + et->view_draw = (have_function[6]) ? engine_view_draw : NULL; + et->update_script_node = (have_function[7]) ? engine_update_script_node : NULL; + et->update_render_passes = (have_function[8]) ? engine_update_render_passes : NULL; RE_engines_register(et); @@ -519,6 +557,19 @@ static void rna_def_render_engine(BlenderRNA *brna) parm = RNA_def_pointer(func, "depsgraph", "Depsgraph", "", ""); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + func = RNA_def_function(srna, "render_frame_finish", NULL); + RNA_def_function_ui_description( + func, "Perform finishing operations after all view layers in a frame were rendered"); + RNA_def_function_flag(func, FUNC_REGISTER_OPTIONAL | FUNC_ALLOW_WRITE); + + func = RNA_def_function(srna, "draw", NULL); + RNA_def_function_ui_description(func, "Draw render image"); + RNA_def_function_flag(func, FUNC_REGISTER_OPTIONAL); + parm = RNA_def_pointer(func, "context", "Context", "", ""); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer(func, "depsgraph", "Depsgraph", "", ""); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + func = RNA_def_function(srna, "bake", NULL); RNA_def_function_ui_description(func, "Bake passes"); RNA_def_function_flag(func, FUNC_REGISTER_OPTIONAL | FUNC_ALLOW_WRITE); @@ -641,6 +692,14 @@ static void rna_def_render_engine(BlenderRNA *brna) parm = RNA_def_boolean(func, "do_break", 0, "Break", ""); RNA_def_function_return(func, parm); + func = RNA_def_function(srna, "pass_by_index_get", "RE_engine_pass_by_index_get"); + parm = RNA_def_string(func, "layer", NULL, 0, "Layer", "Name of render layer to get pass for"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_int(func, "index", 0, 0, INT_MAX, "Index", "Index of pass to get", 0, INT_MAX); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer(func, "render_pass", "RenderPass", "Index", "Index of pass to get"); + RNA_def_function_return(func, parm); + func = RNA_def_function(srna, "active_view_get", "RE_engine_active_view_get"); parm = RNA_def_string(func, "view", NULL, 0, "View", "Single view active"); RNA_def_function_return(func, parm); @@ -761,6 +820,22 @@ static void rna_def_render_engine(BlenderRNA *brna) func = RNA_def_function(srna, "free_blender_memory", "RE_engine_free_blender_memory"); RNA_def_function_ui_description(func, "Free Blender side memory of render engine"); + func = RNA_def_function(srna, "tile_highlight_set", "RE_engine_tile_highlight_set"); + RNA_def_function_ui_description(func, "Set highlighted state of the given tile"); + parm = RNA_def_int(func, "x", 0, 0, INT_MAX, "X", "", 0, INT_MAX); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_int(func, "y", 0, 0, INT_MAX, "Y", "", 0, INT_MAX); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_int(func, "width", 0, 0, INT_MAX, "Width", "", 0, INT_MAX); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_int(func, "height", 0, 0, INT_MAX, "Height", "", 0, INT_MAX); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_boolean(func, "highlight", 0, "Highlight", ""); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + + func = RNA_def_function(srna, "tile_highlight_clear_all", "RE_engine_tile_highlight_clear_all"); + RNA_def_function_ui_description(func, "Clear highlight from all tiles"); + RNA_define_verify_sdna(0); prop = RNA_def_property(srna, "is_animation", PROP_BOOLEAN, PROP_NONE); @@ -777,11 +852,6 @@ static void rna_def_render_engine(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "layer_override", 1); RNA_def_property_array(prop, 20); - prop = RNA_def_property(srna, "tile_x", PROP_INT, PROP_UNSIGNED); - RNA_def_property_int_sdna(prop, NULL, "tile_x"); - prop = RNA_def_property(srna, "tile_y", PROP_INT, PROP_UNSIGNED); - RNA_def_property_int_sdna(prop, NULL, "tile_y"); - prop = RNA_def_property(srna, "resolution_x", PROP_INT, PROP_PIXEL); RNA_def_property_int_sdna(prop, NULL, "resolution_x"); RNA_def_property_clear_flag(prop, PROP_EDITABLE); @@ -880,12 +950,6 @@ static void rna_def_render_engine(BlenderRNA *brna) "Don't expose Cycles and Eevee shading nodes in the node editor user " "interface, so own nodes can be used instead"); - prop = RNA_def_property(srna, "bl_use_save_buffers", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "type->flag", RE_USE_SAVE_BUFFERS); - RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); - RNA_def_property_ui_text( - prop, "Use Save Buffers", "Support render to an on disk buffer during rendering"); - prop = RNA_def_property(srna, "bl_use_spherical_stereo", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "type->flag", RE_USE_SPHERICAL_STEREO); RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL); diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index badaaa14aa4..e45d39a1ddc 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -532,7 +532,6 @@ const EnumPropertyItem rna_enum_stereo3d_interlace_type_items[] = { const EnumPropertyItem rna_enum_bake_pass_filter_type_items[] = { {R_BAKE_PASS_FILTER_NONE, "NONE", 0, "None", ""}, - {R_BAKE_PASS_FILTER_AO, "AO", 0, "Ambient Occlusion", ""}, {R_BAKE_PASS_FILTER_EMIT, "EMIT", 0, "Emit", ""}, {R_BAKE_PASS_FILTER_DIRECT, "DIRECT", 0, "Direct", ""}, {R_BAKE_PASS_FILTER_INDIRECT, "INDIRECT", 0, "Indirect", ""}, @@ -3525,6 +3524,16 @@ static void rna_def_sequencer_tool_settings(BlenderRNA *brna) {0, NULL, 0, NULL, NULL}, }; + static const EnumPropertyItem pivot_points[] = { + {V3D_AROUND_CENTER_MEDIAN, "MEDIAN", ICON_PIVOT_MEDIAN, "Median Point", ""}, + {V3D_AROUND_LOCAL_ORIGINS, + "INDIVIDUAL_ORIGINS", + ICON_PIVOT_INDIVIDUAL, + "Individual Origins", + "Pivot around each selected island's own median point"}, + {0, NULL, 0, NULL, NULL}, + + }; srna = RNA_def_struct(brna, "SequencerToolSettings", NULL); RNA_def_struct_path_func(srna, "rna_SequencerToolSettings_path"); RNA_def_struct_ui_text(srna, "Sequencer Tool Settings", ""); @@ -3568,6 +3577,10 @@ static void rna_def_sequencer_tool_settings(BlenderRNA *brna) prop = RNA_def_property(srna, "overlap_mode", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, scale_overlap_modes); RNA_def_property_ui_text(prop, "Overlap Mode", "How to resolve overlap after transformation"); + + prop = RNA_def_property(srna, "pivot_point", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, pivot_points); + RNA_def_property_ui_text(prop, "Pivot Point", "Rotation or scaling pivot point"); } static void rna_def_unified_paint_settings(BlenderRNA *brna) @@ -4137,13 +4150,6 @@ void rna_def_view_layer_common(BlenderRNA *brna, StructRNA *srna, const bool sce prop, "Cryptomatte Levels", "Sets how many unique objects can be distinguished per pixel"); RNA_def_property_ui_range(prop, 2.0, 16.0, 2.0, 0.0); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_ViewLayer_pass_update"); - - prop = RNA_def_property(srna, "use_pass_cryptomatte_accurate", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "cryptomatte_flag", VIEW_LAYER_CRYPTOMATTE_ACCURATE); - RNA_def_property_boolean_default(prop, true); - RNA_def_property_ui_text( - prop, "Cryptomatte Accurate", "Generate a more accurate cryptomatte pass"); - RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_ViewLayer_pass_update"); } prop = RNA_def_property(srna, "use_solid", PROP_BOOLEAN, PROP_NONE); @@ -4237,6 +4243,16 @@ void rna_def_view_layer_common(BlenderRNA *brna, StructRNA *srna, const bool sce RNA_def_property_clear_flag(prop, PROP_EDITABLE); } + prop = RNA_def_property(srna, "use_pass_position", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "passflag", SCE_PASS_POSITION); + RNA_def_property_ui_text(prop, "Position", "Deliver position pass"); + if (scene) { + RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_ViewLayer_pass_update"); + } + else { + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + } + prop = RNA_def_property(srna, "use_pass_normal", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "passflag", SCE_PASS_NORMAL); RNA_def_property_ui_text(prop, "Normal", "Deliver normal pass"); @@ -5108,10 +5124,6 @@ static void rna_def_bake_data(BlenderRNA *brna) RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); /* custom passes flags */ - prop = RNA_def_property(srna, "use_pass_ambient_occlusion", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "pass_filter", R_BAKE_PASS_FILTER_AO); - RNA_def_property_ui_text(prop, "Ambient Occlusion", "Add ambient occlusion contribution"); - prop = RNA_def_property(srna, "use_pass_emit", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "pass_filter", R_BAKE_PASS_FILTER_EMIT); RNA_def_property_ui_text(prop, "Emit", "Add emission contribution"); @@ -5920,29 +5932,6 @@ static void rna_def_scene_render_data(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Resolution %", "Percentage scale for render resolution"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_SceneSequencer_update"); - prop = RNA_def_property(srna, "tile_x", PROP_INT, PROP_PIXEL); - RNA_def_property_int_sdna(prop, NULL, "tilex"); - RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_range(prop, 8, 65536); - RNA_def_property_ui_text(prop, "Tile X", "Horizontal tile size to use while rendering"); - RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); - - prop = RNA_def_property(srna, "tile_y", PROP_INT, PROP_PIXEL); - RNA_def_property_int_sdna(prop, NULL, "tiley"); - RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_range(prop, 8, 65536); - RNA_def_property_ui_text(prop, "Tile Y", "Vertical tile size to use while rendering"); - RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); - - prop = RNA_def_property(srna, "preview_start_resolution", PROP_INT, PROP_NONE); - RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_range(prop, 8, 16384); - RNA_def_property_ui_text(prop, - "Start Resolution", - "Resolution to start rendering preview at, " - "progressively increasing it to the full viewport size"); - RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); - prop = RNA_def_property(srna, "preview_pixel_size", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "preview_pixel_size"); RNA_def_property_enum_items(prop, pixel_size_items); @@ -6199,24 +6188,6 @@ static void rna_def_scene_render_data(BlenderRNA *brna) RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "Movie Format", "When true the format is a movie"); - prop = RNA_def_property(srna, "use_save_buffers", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "scemode", R_EXR_TILE_FILE); - RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_ui_text( - prop, - "Save Buffers", - "Save tiles for all RenderLayers and SceneNodes to files in the temp directory " - "(saves memory, required for Full Sample)"); - RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); - - prop = RNA_def_property(srna, "use_full_sample", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "scemode", R_FULL_SAMPLE); - RNA_def_property_ui_text(prop, - "Full Sample", - "Save for every anti-aliasing sample the entire RenderLayer results " - "(this solves anti-aliasing issues with compositing)"); - RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); - prop = RNA_def_property(srna, "use_lock_interface", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "use_lock_interface", 1); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index cd87e4d10c1..b713ffb68b4 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -1442,6 +1442,12 @@ static void rna_def_strip_transform(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Rotation", "Rotate around image center"); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceTransform_update"); + prop = RNA_def_property(srna, "origin", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "origin"); + RNA_def_property_ui_text(prop, "Origin", "Origin of image for transformation"); + RNA_def_property_ui_range(prop, 0, 1, 1, 3); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceTransform_update"); + RNA_def_struct_path_func(srna, "rna_SequenceTransform_path"); } diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 8c331bd1911..a05cef7a1cd 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -5371,6 +5371,11 @@ static void rna_def_space_sequencer_preview_overlay(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_PREVIEW_SHOW_GPENCIL); RNA_def_property_ui_text(prop, "Show Annotation", "Show annotations for this view"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_image_outline", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_PREVIEW_SHOW_OUTLINE_SELECTED); + RNA_def_property_ui_text(prop, "Image Outline", ""); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); } static void rna_def_space_sequencer_timeline_overlay(BlenderRNA *brna) @@ -5439,6 +5444,11 @@ static void rna_def_space_sequencer_timeline_overlay(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_STRIP_OFFSETS); RNA_def_property_ui_text(prop, "Show Offsets", "Display strip in/out offsets"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_thumbnails", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_THUMBNAILS); + RNA_def_property_ui_text(prop, "Show Thumbnails", "Show strip thumbnails"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); } static void rna_def_space_sequencer(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_workspace_api.c b/source/blender/makesrna/intern/rna_workspace_api.c index a2bb89dd5ee..15230f1198b 100644 --- a/source/blender/makesrna/intern/rna_workspace_api.c +++ b/source/blender/makesrna/intern/rna_workspace_api.c @@ -29,6 +29,7 @@ #include "DNA_object_types.h" #include "DNA_windowmanager_types.h" +#include "DNA_workspace_types.h" #include "RNA_enum_types.h" /* own include */ @@ -51,6 +52,7 @@ static void rna_WorkSpaceTool_setup(ID *id, const char *data_block, const char *op_idname, int index, + int options, const char *idname_fallback, const char *keymap_fallback) { @@ -62,6 +64,7 @@ static void rna_WorkSpaceTool_setup(ID *id, STRNCPY(tref_rt.data_block, data_block); STRNCPY(tref_rt.op, op_idname); tref_rt.index = index; + tref_rt.flag = options; /* While it's logical to assign both these values from setup, * it's useful to stored this in DNA for re-use, exceptional case: write to the 'tref'. */ @@ -131,6 +134,11 @@ void RNA_api_workspace_tool(StructRNA *srna) PropertyRNA *parm; FunctionRNA *func; + static EnumPropertyItem options_items[] = { + {TOOLREF_FLAG_FALLBACK_KEYMAP, "KEYMAP_FALLBACK", 0, "Fallback", ""}, + {0, NULL, 0, NULL, NULL}, + }; + func = RNA_def_function(srna, "setup", "rna_WorkSpaceTool_setup"); RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_CONTEXT); RNA_def_function_ui_description(func, "Set the tool settings"); @@ -146,6 +154,7 @@ void RNA_api_workspace_tool(StructRNA *srna) RNA_def_string(func, "data_block", NULL, MAX_NAME, "Data Block", ""); RNA_def_string(func, "operator", NULL, MAX_NAME, "Operator", ""); RNA_def_int(func, "index", 0, INT_MIN, INT_MAX, "Index", "", INT_MIN, INT_MAX); + RNA_def_enum_flag(func, "options", options_items, 0, "Tool Options", ""); RNA_def_string(func, "idname_fallback", NULL, MAX_NAME, "Fallback Identifier", ""); RNA_def_string(func, "keymap_fallback", NULL, KMAP_MAX_NAME, "Fallback Key Map", ""); diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 6b976b016e1..8c02c83d479 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -68,6 +68,8 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "BLT_translation.h" + #include "WM_types.h" #include "RNA_access.h" @@ -1090,17 +1092,29 @@ static void panel_draw(const bContext *C, Panel *panel) } /* Draw node warnings. */ + bool has_legacy_node = false; if (nmd->runtime_eval_log != nullptr) { const geo_log::ModifierLog &log = *static_cast<geo_log::ModifierLog *>(nmd->runtime_eval_log); - log.foreach_node_log([layout](const geo_log::NodeLog &node_log) { + log.foreach_node_log([&](const geo_log::NodeLog &node_log) { for (const geo_log::NodeWarning &warning : node_log.warnings()) { - if (warning.type != geo_log::NodeWarningType::Info) { + if (warning.type == geo_log::NodeWarningType::Legacy) { + has_legacy_node = true; + } + else if (warning.type != geo_log::NodeWarningType::Info) { uiItemL(layout, warning.message.c_str(), ICON_ERROR); } } }); } + if (USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields) && has_legacy_node) { + uiLayout *row = uiLayoutRow(layout, false); + uiItemL(row, IFACE_("Node tree has legacy node"), ICON_ERROR); + uiLayout *sub = uiLayoutRow(row, false); + uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_RIGHT); + uiItemO(sub, "", ICON_VIEWZOOM, "NODE_OT_geometry_node_view_legacy"); + } + modifier_panel_end(layout, ptr); } diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc index 56de0f87ed8..e50c07ce6f2 100644 --- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -26,6 +26,8 @@ #include "FN_generic_value_map.hh" #include "FN_multi_function.hh" +#include "BLT_translation.h" + #include "BLI_enumerable_thread_specific.hh" #include "BLI_stack.hh" #include "BLI_task.h" @@ -868,6 +870,12 @@ class GeometryNodesEvaluator { NodeParamsProvider params_provider{*this, node, node_state}; GeoNodeExecParams params{params_provider}; + if (USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields)) { + if (node->idname().find("Legacy") != StringRef::not_found) { + params.error_message_add(geo_log::NodeWarningType::Legacy, + TIP_("Legacy node will be removed before Blender 4.0")); + } + } bnode.typeinfo->geometry_node_execute(params); } diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index b0fc55fab0c..a8795649ede 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -139,6 +139,9 @@ set(SRC function/nodes/node_fn_input_string.cc function/nodes/node_fn_input_vector.cc function/nodes/node_fn_random_float.cc + function/nodes/node_fn_string_length.cc + function/nodes/node_fn_string_substring.cc + function/nodes/node_fn_value_to_string.cc function/node_function_util.cc geometry/nodes/legacy/node_geo_material_assign.cc @@ -161,6 +164,7 @@ set(SRC geometry/nodes/node_geo_attribute_remove.cc geometry/nodes/node_geo_attribute_sample_texture.cc geometry/nodes/node_geo_attribute_separate_xyz.cc + geometry/nodes/node_geo_attribute_statistic.cc geometry/nodes/node_geo_attribute_transfer.cc geometry/nodes/node_geo_attribute_vector_math.cc geometry/nodes/node_geo_attribute_vector_rotate.cc @@ -169,9 +173,11 @@ set(SRC geometry/nodes/node_geo_collection_info.cc geometry/nodes/node_geo_common.cc geometry/nodes/node_geo_convex_hull.cc + geometry/nodes/node_geo_curve_sample.cc geometry/nodes/node_geo_curve_endpoints.cc geometry/nodes/node_geo_curve_fill.cc geometry/nodes/node_geo_curve_length.cc + geometry/nodes/node_geo_curve_parameter.cc geometry/nodes/node_geo_curve_primitive_bezier_segment.cc geometry/nodes/node_geo_curve_primitive_circle.cc geometry/nodes/node_geo_curve_primitive_line.cc @@ -193,6 +199,7 @@ set(SRC geometry/nodes/node_geo_input_material.cc geometry/nodes/node_geo_input_normal.cc geometry/nodes/node_geo_input_position.cc + geometry/nodes/node_geo_input_tangent.cc geometry/nodes/node_geo_input_index.cc geometry/nodes/node_geo_is_viewport.cc geometry/nodes/node_geo_join_geometry.cc @@ -221,6 +228,7 @@ set(SRC geometry/nodes/node_geo_realize_instances.cc geometry/nodes/node_geo_separate_components.cc geometry/nodes/node_geo_set_position.cc + geometry/nodes/node_geo_string_join.cc geometry/nodes/node_geo_subdivision_surface.cc geometry/nodes/node_geo_switch.cc geometry/nodes/node_geo_transform.cc diff --git a/source/blender/nodes/NOD_function.h b/source/blender/nodes/NOD_function.h index 29f1a465491..a67458418f2 100644 --- a/source/blender/nodes/NOD_function.h +++ b/source/blender/nodes/NOD_function.h @@ -26,6 +26,9 @@ void register_node_type_fn_float_to_int(void); void register_node_type_fn_input_string(void); void register_node_type_fn_input_vector(void); void register_node_type_fn_random_float(void); +void register_node_type_fn_string_length(void); +void register_node_type_fn_string_substring(void); +void register_node_type_fn_value_to_string(void); #ifdef __cplusplus } diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 0d31ae2143a..24f60263d8a 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -48,6 +48,7 @@ void register_node_type_geo_attribute_proximity(void); void register_node_type_geo_attribute_randomize(void); void register_node_type_geo_attribute_remove(void); void register_node_type_geo_attribute_separate_xyz(void); +void register_node_type_geo_attribute_statistic(void); void register_node_type_geo_attribute_transfer(void); void register_node_type_geo_attribute_vector_math(void); void register_node_type_geo_attribute_vector_rotate(void); @@ -58,6 +59,8 @@ void register_node_type_geo_convex_hull(void); void register_node_type_geo_curve_endpoints(void); void register_node_type_geo_curve_fill(void); void register_node_type_geo_curve_length(void); +void register_node_type_geo_curve_parameter(void); +void register_node_type_geo_curve_sample(void); void register_node_type_geo_curve_primitive_bezier_segment(void); void register_node_type_geo_curve_primitive_circle(void); void register_node_type_geo_curve_primitive_line(void); @@ -79,6 +82,7 @@ void register_node_type_geo_input_index(void); void register_node_type_geo_input_material(void); void register_node_type_geo_input_normal(void); void register_node_type_geo_input_position(void); +void register_node_type_geo_input_tangent(void); void register_node_type_geo_is_viewport(void); void register_node_type_geo_join_geometry(void); void register_node_type_geo_material_assign(void); @@ -108,6 +112,7 @@ void register_node_type_geo_sample_texture(void); void register_node_type_geo_select_by_handle_type(void); void register_node_type_geo_separate_components(void); void register_node_type_geo_set_position(void); +void register_node_type_geo_string_join(void); void register_node_type_geo_subdivision_surface(void); void register_node_type_geo_switch(void); void register_node_type_geo_transform(void); diff --git a/source/blender/nodes/NOD_geometry_nodes_eval_log.hh b/source/blender/nodes/NOD_geometry_nodes_eval_log.hh index 00d97b24646..ff8e137e341 100644 --- a/source/blender/nodes/NOD_geometry_nodes_eval_log.hh +++ b/source/blender/nodes/NOD_geometry_nodes_eval_log.hh @@ -131,6 +131,7 @@ enum class NodeWarningType { Error, Warning, Info, + Legacy, }; struct NodeWarning { diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index b2f1fa5e83a..8fb18e839a7 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -267,7 +267,10 @@ DefNode(FunctionNode, FN_NODE_FLOAT_COMPARE, def_float_compare, "FLOAT_COMPARE", DefNode(FunctionNode, FN_NODE_FLOAT_TO_INT, def_float_to_int, "FLOAT_TO_INT", FloatToInt, "Float to Integer", "") DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_STRING", InputString, "String", "") DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "") -DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") +DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") +DefNode(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToString, "Value to String", "") +DefNode(FunctionNode, FN_NODE_STRING_LENGTH, 0, "STRING_LENGTH", StringLength, "String Length", "") +DefNode(FunctionNode, FN_NODE_STRING_SUBSTRING, 0, "STRING_SUBSTRING", StringSubstring, "String Substring", "") DefNode(GeometryNode, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "LEGACY_ATTRIBUTE_CLAMP", LegacyAttributeClamp, "Attribute Clamp", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "LEGACY_ALIGN_ROTATION_TO_VECTOR", LegacyAlignRotationToVector, "Align Rotation to Vector", "") @@ -307,13 +310,16 @@ DefNode(GeometryNode, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, 0, "LEGACY_SELECT_BY_M DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CAPTURE, def_geo_attribute_capture, "ATTRIBUTE_CAPTURE", AttributeCapture, "Attribute Capture", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "LEGACY_ATTRIBUTE_VECTOR_ROTATE", LegacyAttributeVectorRotate, "Attribute Vector Rotate", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_STATISTIC, def_geo_attribute_statistic, "ATTRIBUTE_STATISTIC", AttributeStatistic, "Attribute Statistic", "") DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "") DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "") DefNode(GeometryNode, GEO_NODE_CONVEX_HULL, 0, "CONVEX_HULL", ConvexHull, "Convex Hull", "") +DefNode(GeometryNode, GEO_NODE_CURVE_SAMPLE, def_geo_curve_sample, "CURVE_SAMPLE", CurveSample, "Curve Sample", "") DefNode(GeometryNode, GEO_NODE_CURVE_ENDPOINTS, 0, "CURVE_ENDPOINTS", CurveEndpoints, "Curve Endpoints", "") DefNode(GeometryNode, GEO_NODE_CURVE_FILL, def_geo_curve_fill, "CURVE_FILL", CurveFill, "Curve Fill", "") DefNode(GeometryNode, GEO_NODE_CURVE_LENGTH, 0, "CURVE_LENGTH", CurveLength, "Curve Length", "") +DefNode(GeometryNode, GEO_NODE_CURVE_PARAMETER, 0, "CURVE_PARAMETER", CurveParameter, "Curve Parameter", "") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT, def_geo_curve_primitive_bezier_segment, "CURVE_PRIMITIVE_BEZIER_SEGMENT", CurvePrimitiveBezierSegment, "Bezier Segment", "") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_CIRCLE, def_geo_curve_primitive_circle, "CURVE_PRIMITIVE_CIRCLE", CurvePrimitiveCircle, "Curve Circle", "") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_LINE, def_geo_curve_primitive_line, "CURVE_PRIMITIVE_LINE", CurvePrimitiveLine, "Curve Line", "") @@ -330,6 +336,7 @@ DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "") DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "") DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "") DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "") +DefNode(GeometryNode, GEO_NODE_INPUT_TANGENT, 0, "INPUT_TANGENT", InputTangent, "Curve Tangent", "") DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "") DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "") @@ -348,6 +355,7 @@ DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "") DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "") DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "") +DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "String Join", "") DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") diff --git a/source/blender/nodes/composite/nodes/node_composite_image.c b/source/blender/nodes/composite/nodes/node_composite_image.c index 243300b0a44..a56dfea9dbf 100644 --- a/source/blender/nodes/composite/nodes/node_composite_image.c +++ b/source/blender/nodes/composite/nodes/node_composite_image.c @@ -45,7 +45,7 @@ static bNodeSocketTemplate cmp_node_rlayers_out[] = { {SOCK_VECTOR, N_(RE_PASSNAME_NORMAL), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_VECTOR, N_(RE_PASSNAME_UV), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_VECTOR, N_(RE_PASSNAME_VECTOR), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_RGBA, N_(RE_PASSNAME_DEPRECATED), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, + {SOCK_VECTOR, N_(RE_PASSNAME_POSITION), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_RGBA, N_(RE_PASSNAME_DEPRECATED), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_RGBA, N_(RE_PASSNAME_DEPRECATED), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_RGBA, N_(RE_PASSNAME_SHADOW), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, @@ -72,7 +72,7 @@ static bNodeSocketTemplate cmp_node_rlayers_out[] = { {SOCK_RGBA, N_(RE_PASSNAME_SUBSURFACE_COLOR), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {-1, ""}, }; -#define MAX_LEGACY_SOCKET_INDEX 30 +#define NUM_LEGACY_SOCKETS (ARRAY_SIZE(cmp_node_rlayers_out) - 1) static void cmp_node_image_add_pass_output(bNodeTree *ntree, bNode *node, @@ -382,7 +382,7 @@ static void cmp_node_image_verify_outputs(bNodeTree *ntree, bNode *node, bool rl break; } } - if (!link && (!rlayer || sock_index > MAX_LEGACY_SOCKET_INDEX)) { + if (!link && (!rlayer || sock_index >= NUM_LEGACY_SOCKETS)) { MEM_freeN(sock->storage); nodeRemoveSocket(ntree, node, sock); } @@ -468,43 +468,12 @@ void node_cmp_rlayers_outputs(bNodeTree *ntree, bNode *node) const char *node_cmp_rlayers_sock_to_pass(int sock_index) { - const char *sock_to_passname[] = { - RE_PASSNAME_COMBINED, - RE_PASSNAME_COMBINED, - RE_PASSNAME_Z, - RE_PASSNAME_NORMAL, - RE_PASSNAME_UV, - RE_PASSNAME_VECTOR, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_SHADOW, - RE_PASSNAME_AO, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_DEPRECATED, - RE_PASSNAME_INDEXOB, - RE_PASSNAME_INDEXMA, - RE_PASSNAME_MIST, - RE_PASSNAME_EMIT, - RE_PASSNAME_ENVIRONMENT, - RE_PASSNAME_DIFFUSE_DIRECT, - RE_PASSNAME_DIFFUSE_INDIRECT, - RE_PASSNAME_DIFFUSE_COLOR, - RE_PASSNAME_GLOSSY_DIRECT, - RE_PASSNAME_GLOSSY_INDIRECT, - RE_PASSNAME_GLOSSY_COLOR, - RE_PASSNAME_TRANSM_DIRECT, - RE_PASSNAME_TRANSM_INDIRECT, - RE_PASSNAME_TRANSM_COLOR, - RE_PASSNAME_SUBSURFACE_DIRECT, - RE_PASSNAME_SUBSURFACE_INDIRECT, - RE_PASSNAME_SUBSURFACE_COLOR, - }; - if (sock_index > MAX_LEGACY_SOCKET_INDEX) { + if (sock_index >= NUM_LEGACY_SOCKETS) { return NULL; } - return sock_to_passname[sock_index]; + const char *name = cmp_node_rlayers_out[sock_index].name; + /* Exception for alpha, which is derived from Combined. */ + return (STREQ(name, "Alpha")) ? RE_PASSNAME_COMBINED : name; } static void node_composit_init_rlayers(const bContext *C, PointerRNA *ptr) diff --git a/source/blender/nodes/function/nodes/node_fn_string_length.cc b/source/blender/nodes/function/nodes/node_fn_string_length.cc new file mode 100644 index 00000000000..a0f85dfd2bf --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_string_length.cc @@ -0,0 +1,49 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_string_utf8.h" + +#include <iomanip> + +#include "node_function_util.hh" + +namespace blender::nodes { + +static void fn_node_string_length_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::String>("String"); + b.add_output<decl::Int>("Length"); +}; + +} // namespace blender::nodes + +static void fn_node_string_length_build_multi_function( + blender::nodes::NodeMultiFunctionBuilder &builder) +{ + static blender::fn::CustomMF_SI_SO<std::string, int> str_len_fn{ + "String Length", [](const std::string &a) { return BLI_strlen_utf8(a.c_str()); }}; + builder.set_matching_fn(&str_len_fn); +} + +void register_node_type_fn_string_length() +{ + static bNodeType ntype; + + fn_node_type_base(&ntype, FN_NODE_STRING_LENGTH, "String Length", NODE_CLASS_CONVERTER, 0); + ntype.declare = blender::nodes::fn_node_string_length_declare; + ntype.build_multi_function = fn_node_string_length_build_multi_function; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/function/nodes/node_fn_string_substring.cc b/source/blender/nodes/function/nodes/node_fn_string_substring.cc new file mode 100644 index 00000000000..55a01093ae9 --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_string_substring.cc @@ -0,0 +1,54 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_string_utf8.h" + +#include "node_function_util.hh" + +namespace blender::nodes { + +static void fn_node_string_substring_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::String>("String"); + b.add_input<decl::Int>("Position"); + b.add_input<decl::Int>("Length").min(0); + b.add_output<decl::String>("String"); +}; + +} // namespace blender::nodes + +static void fn_node_string_substring_build_multi_function( + blender::nodes::NodeMultiFunctionBuilder &builder) +{ + static blender::fn::CustomMF_SI_SI_SI_SO<std::string, int, int, std::string> substring_fn{ + "Substring", [](const std::string &str, int a, int b) { + const int len = BLI_strlen_utf8(str.c_str()); + const int start = BLI_str_utf8_offset_from_index(str.c_str(), std::clamp(a, 0, len)); + const int end = BLI_str_utf8_offset_from_index(str.c_str(), std::clamp(a + b, 0, len)); + return str.substr(start, std::max<int>(end - start, 0)); + }}; + builder.set_matching_fn(&substring_fn); +} + +void register_node_type_fn_string_substring() +{ + static bNodeType ntype; + + fn_node_type_base(&ntype, FN_NODE_STRING_SUBSTRING, "String Substring", NODE_CLASS_CONVERTER, 0); + ntype.declare = blender::nodes::fn_node_string_substring_declare; + ntype.build_multi_function = fn_node_string_substring_build_multi_function; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/function/nodes/node_fn_value_to_string.cc b/source/blender/nodes/function/nodes/node_fn_value_to_string.cc new file mode 100644 index 00000000000..c1e6373cb6d --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_value_to_string.cc @@ -0,0 +1,51 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_function_util.hh" +#include <iomanip> + +namespace blender::nodes { + +static void fn_node_value_to_string_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Value"); + b.add_input<decl::Int>("Decimals").min(0); + b.add_output<decl::String>("String"); +}; + +} // namespace blender::nodes + +static void fn_node_value_to_string_build_multi_function( + blender::nodes::NodeMultiFunctionBuilder &builder) +{ + static blender::fn::CustomMF_SI_SI_SO<float, int, std::string> to_str_fn{ + "Value To String", [](float a, int b) { + std::stringstream stream; + stream << std::fixed << std::setprecision(std::max(0, b)) << a; + return stream.str(); + }}; + builder.set_matching_fn(&to_str_fn); +} + +void register_node_type_fn_value_to_string() +{ + static bNodeType ntype; + + fn_node_type_base(&ntype, FN_NODE_VALUE_TO_STRING, "Value to String", NODE_CLASS_CONVERTER, 0); + ntype.declare = blender::nodes::fn_node_value_to_string_declare; + ntype.build_multi_function = fn_node_value_to_string_build_multi_function; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc new file mode 100644 index 00000000000..5001034518c --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc @@ -0,0 +1,378 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <algorithm> +#include <numeric> + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "BLI_math_base_safe.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_attribute_statistic_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::Float>("Attribute").hide_value(); + b.add_input<decl::Vector>("Attribute", "Attribute_001").hide_value(); + + b.add_output<decl::Float>("Mean"); + b.add_output<decl::Float>("Median"); + b.add_output<decl::Float>("Sum"); + b.add_output<decl::Float>("Min"); + b.add_output<decl::Float>("Max"); + b.add_output<decl::Float>("Range"); + b.add_output<decl::Float>("Standard Deviation"); + b.add_output<decl::Float>("Variance"); + + b.add_output<decl::Vector>("Mean", "Mean_001"); + b.add_output<decl::Vector>("Median", "Median_001"); + b.add_output<decl::Vector>("Sum", "Sum_001"); + b.add_output<decl::Vector>("Min", "Min_001"); + b.add_output<decl::Vector>("Max", "Max_001"); + b.add_output<decl::Vector>("Range", "Range_001"); + b.add_output<decl::Vector>("Standard Deviation", "Standard Deviation_001"); + b.add_output<decl::Vector>("Variance", "Variance_001"); +} + +static void geo_node_attribute_statistic_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); +} + +static void geo_node_attribute_statistic_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = CD_PROP_FLOAT; + node->custom2 = ATTR_DOMAIN_POINT; +} + +static void geo_node_attribute_statistic_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *socket_geo = (bNodeSocket *)node->inputs.first; + bNodeSocket *socket_float_attr = socket_geo->next; + bNodeSocket *socket_float3_attr = socket_float_attr->next; + + bNodeSocket *socket_float_mean = (bNodeSocket *)node->outputs.first; + bNodeSocket *socket_float_median = socket_float_mean->next; + bNodeSocket *socket_float_sum = socket_float_median->next; + bNodeSocket *socket_float_min = socket_float_sum->next; + bNodeSocket *socket_float_max = socket_float_min->next; + bNodeSocket *socket_float_range = socket_float_max->next; + bNodeSocket *socket_float_std = socket_float_range->next; + bNodeSocket *socket_float_variance = socket_float_std->next; + + bNodeSocket *socket_vector_mean = socket_float_variance->next; + bNodeSocket *socket_vector_median = socket_vector_mean->next; + bNodeSocket *socket_vector_sum = socket_vector_median->next; + bNodeSocket *socket_vector_min = socket_vector_sum->next; + bNodeSocket *socket_vector_max = socket_vector_min->next; + bNodeSocket *socket_vector_range = socket_vector_max->next; + bNodeSocket *socket_vector_std = socket_vector_range->next; + bNodeSocket *socket_vector_variance = socket_vector_std->next; + + const CustomDataType data_type = static_cast<CustomDataType>(node->custom1); + + nodeSetSocketAvailability(socket_float_attr, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_mean, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_median, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_sum, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_min, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_max, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_range, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_std, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(socket_float_variance, data_type == CD_PROP_FLOAT); + + nodeSetSocketAvailability(socket_float3_attr, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_mean, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_median, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_sum, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_min, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_max, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_range, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_std, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(socket_vector_variance, data_type == CD_PROP_FLOAT3); +} + +template<typename T> static T compute_sum(const Span<T> data) +{ + return std::accumulate(data.begin(), data.end(), T()); +} + +static float compute_variance(const Span<float> data, const float mean) +{ + if (data.size() <= 1) { + return 0.0f; + } + + float sum_of_squared_differences = std::accumulate( + data.begin(), data.end(), 0.0f, [mean](float accumulator, float value) { + float difference = mean - value; + return accumulator + difference * difference; + }); + + return sum_of_squared_differences / (data.size() - 1); +} + +static float median_of_sorted_span(const Span<float> data) +{ + if (data.is_empty()) { + return 0.0f; + } + + const float median = data[data.size() / 2]; + + /* For spans of even length, the median is the average of the middle two elements. */ + if (data.size() % 2 == 0) { + return (median + data[data.size() / 2 - 1]) * 0.5f; + } + return median; +} +static void set_empty(CustomDataType data_type, GeoNodeExecParams ¶ms) +{ + if (data_type == CD_PROP_FLOAT) { + params.set_output("Mean", 0.0f); + params.set_output("Median", 0.0f); + params.set_output("Sum", 0.0f); + params.set_output("Min", 0.0f); + params.set_output("Max", 0.0f); + params.set_output("Range", 0.0f); + params.set_output("Standard Deviation", 0.0f); + params.set_output("Variance", 0.0f); + } + else if (data_type == CD_PROP_FLOAT3) { + params.set_output("Mean_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Median_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Sum_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Min_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Max_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Range_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Standard Deviation_001", float3{0.0f, 0.0f, 0.0f}); + params.set_output("Variance_001", float3{0.0f, 0.0f, 0.0f}); + } +} + +static void geo_node_attribute_statistic_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.get_input<GeometrySet>("Geometry"); + + const bNode &node = params.node(); + const CustomDataType data_type = static_cast<CustomDataType>(node.custom1); + const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2); + + int64_t total_size = 0; + Vector<const GeometryComponent *> components = geometry_set.get_components_for_read(); + + for (const GeometryComponent *component : components) { + if (component->attribute_domain_supported(domain)) { + total_size += component->attribute_domain_size(domain); + } + } + if (total_size == 0) { + set_empty(data_type, params); + return; + } + + switch (data_type) { + case CD_PROP_FLOAT: { + const Field<float> input_field = params.get_input<Field<float>>("Attribute"); + Array<float> data = Array<float>(total_size); + int offset = 0; + for (const GeometryComponent *component : components) { + if (component->attribute_domain_supported(domain)) { + GeometryComponentFieldContext field_context{*component, domain}; + const int domain_size = component->attribute_domain_size(domain); + fn::FieldEvaluator data_evaluator{field_context, domain_size}; + MutableSpan<float> component_result = data.as_mutable_span().slice(offset, domain_size); + data_evaluator.add_with_destination(input_field, component_result); + data_evaluator.evaluate(); + offset += domain_size; + } + } + + float mean = 0.0f; + float median = 0.0f; + float sum = 0.0f; + float min = 0.0f; + float max = 0.0f; + float range = 0.0f; + float standard_deviation = 0.0f; + float variance = 0.0f; + const bool sort_required = params.output_is_required("Min") || + params.output_is_required("Max") || + params.output_is_required("Range") || + params.output_is_required("Median"); + const bool sum_required = params.output_is_required("Sum") || + params.output_is_required("Mean"); + const bool variance_required = params.output_is_required("Standard Deviation") || + params.output_is_required("Variance"); + + if (total_size != 0) { + if (sort_required) { + std::sort(data.begin(), data.end()); + median = median_of_sorted_span(data); + + min = data.first(); + max = data.last(); + range = max - min; + } + if (sum_required || variance_required) { + sum = compute_sum<float>(data); + mean = sum / total_size; + + if (variance_required) { + variance = compute_variance(data, mean); + standard_deviation = std::sqrt(variance); + } + } + } + + if (sum_required) { + params.set_output("Sum", sum); + params.set_output("Mean", mean); + } + if (sort_required) { + params.set_output("Min", min); + params.set_output("Max", max); + params.set_output("Range", range); + params.set_output("Median", median); + } + if (variance_required) { + params.set_output("Standard Deviation", standard_deviation); + params.set_output("Variance", variance); + } + break; + } + case CD_PROP_FLOAT3: { + const Field<float3> input_field = params.get_input<Field<float3>>("Attribute_001"); + + Array<float3> data = Array<float3>(total_size); + int offset = 0; + for (const GeometryComponent *component : components) { + if (component->attribute_domain_supported(domain)) { + GeometryComponentFieldContext field_context{*component, domain}; + const int domain_size = component->attribute_domain_size(domain); + fn::FieldEvaluator data_evaluator{field_context, domain_size}; + MutableSpan<float3> component_result = data.as_mutable_span().slice(offset, domain_size); + data_evaluator.add_with_destination(input_field, component_result); + data_evaluator.evaluate(); + offset += domain_size; + } + } + + float3 median{0}; + float3 min{0}; + float3 max{0}; + float3 range{0}; + float3 sum{0}; + float3 mean{0}; + float3 variance{0}; + float3 standard_deviation{0}; + const bool sort_required = params.output_is_required("Min_001") || + params.output_is_required("Max_001") || + params.output_is_required("Range_001") || + params.output_is_required("Median_001"); + const bool sum_required = params.output_is_required("Sum_001") || + params.output_is_required("Mean_001"); + const bool variance_required = params.output_is_required("Standard Deviation_001") || + params.output_is_required("Variance_001"); + + Array<float> data_x; + Array<float> data_y; + Array<float> data_z; + if (sort_required || variance_required) { + data_x.reinitialize(total_size); + data_y.reinitialize(total_size); + data_z.reinitialize(total_size); + for (const int i : data.index_range()) { + data_x[i] = data[i].x; + data_y[i] = data[i].y; + data_z[i] = data[i].z; + } + } + + if (total_size != 0) { + if (sort_required) { + std::sort(data_x.begin(), data_x.end()); + std::sort(data_y.begin(), data_y.end()); + std::sort(data_z.begin(), data_z.end()); + + const float x_median = median_of_sorted_span(data_x); + const float y_median = median_of_sorted_span(data_y); + const float z_median = median_of_sorted_span(data_z); + median = float3(x_median, y_median, z_median); + + min = float3(data_x.first(), data_y.first(), data_z.first()); + max = float3(data_x.last(), data_y.last(), data_z.last()); + range = max - min; + } + if (sum_required || variance_required) { + sum = compute_sum(data.as_span()); + mean = sum / total_size; + + if (variance_required) { + const float x_variance = compute_variance(data_x, mean.x); + const float y_variance = compute_variance(data_y, mean.y); + const float z_variance = compute_variance(data_z, mean.z); + variance = float3(x_variance, y_variance, z_variance); + standard_deviation = float3( + std::sqrt(variance.x), std::sqrt(variance.y), std::sqrt(variance.z)); + } + } + } + + if (sum_required) { + params.set_output("Sum_001", sum); + params.set_output("Mean_001", mean); + } + if (sort_required) { + params.set_output("Min_001", min); + params.set_output("Max_001", max); + params.set_output("Range_001", range); + params.set_output("Median_001", median); + } + if (variance_required) { + params.set_output("Standard Deviation_001", standard_deviation); + params.set_output("Variance_001", variance); + } + break; + } + default: + break; + } +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_statistic() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_STATISTIC, "Attribute Statistic", NODE_CLASS_ATTRIBUTE, 0); + + ntype.declare = blender::nodes::geo_node_attribute_statistic_declare; + node_type_init(&ntype, blender::nodes::geo_node_attribute_statistic_init); + node_type_update(&ntype, blender::nodes::geo_node_attribute_statistic_update); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_statistic_exec; + ntype.draw_buttons = blender::nodes::geo_node_attribute_statistic_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc index d8f40b0a0df..8de2975f9b0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc @@ -124,37 +124,55 @@ static Mesh *cdt_to_mesh(const blender::meshintersect::CDT_result<double> &resul return mesh; } -static Mesh *curve_fill_calculate(GeoNodeExecParams ¶ms, const CurveComponent &component) +static void curve_fill_calculate(GeometrySet &geometry_set, const GeometryNodeCurveFillMode mode) { - const CurveEval &curve = *component.get_for_read(); - if (curve.splines().size() == 0) { - return nullptr; + if (!geometry_set.has_curve()) { + return; } - const NodeGeometryCurveFill &storage = *(const NodeGeometryCurveFill *)params.node().storage; - const GeometryNodeCurveFillMode mode = (GeometryNodeCurveFillMode)storage.mode; + const CurveEval &curve = *geometry_set.get_curve_for_read(); + if (curve.splines().is_empty()) { + geometry_set.replace_curve(nullptr); + return; + } const CDT_output_type output_type = (mode == GEO_NODE_CURVE_FILL_MODE_NGONS) ? CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES : CDT_INSIDE_WITH_HOLES; const blender::meshintersect::CDT_result<double> results = do_cdt(curve, output_type); - return cdt_to_mesh(results); + Mesh *mesh = cdt_to_mesh(results); + + geometry_set.replace_mesh(mesh); + geometry_set.replace_curve(nullptr); } static void geo_node_curve_fill_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_curve()) { - params.set_output("Mesh", GeometrySet()); + const NodeGeometryCurveFill &storage = *(const NodeGeometryCurveFill *)params.node().storage; + const GeometryNodeCurveFillMode mode = (GeometryNodeCurveFillMode)storage.mode; + + if (geometry_set.has_instances()) { + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + instances.ensure_geometry_instances(); + + threading::parallel_for(IndexRange(instances.references_amount()), 16, [&](IndexRange range) { + for (int i : range) { + GeometrySet &geometry_set = instances.geometry_set_from_reference(i); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + curve_fill_calculate(geometry_set, mode); + } + }); + + params.set_output("Mesh", std::move(geometry_set)); return; } - Mesh *mesh = curve_fill_calculate(params, - *geometry_set.get_component_for_read<CurveComponent>()); - params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); + curve_fill_calculate(geometry_set, mode); + + params.set_output("Mesh", std::move(geometry_set)); } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc new file mode 100644 index 00000000000..2cde198e679 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc @@ -0,0 +1,206 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_task.hh" + +#include "BKE_spline.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_curve_parameter_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("Factor"); +} + +/** + * A basic interpolation from the point domain to the spline domain would be useless, since the + * average parameter for each spline would just be 0.5, or close to it. Instead, the parameter for + * each spline is the portion of the total length at the start of the spline. + */ +static Array<float> curve_parameter_spline_domain(const CurveEval &curve, const IndexMask mask) +{ + Span<SplinePtr> splines = curve.splines(); + float length = 0.0f; + Array<float> parameters(splines.size()); + for (const int i : splines.index_range()) { + parameters[i] = length; + length += splines[i]->length(); + } + const float total_length_inverse = length == 0.0f ? 0.0f : 1.0f / length; + mask.foreach_index([&](const int64_t i) { parameters[i] *= total_length_inverse; }); + + return parameters; +} + +/** + * The parameter at each control point is the factor at the corresponding evaluated point. + */ +static void calculate_bezier_parameters(const BezierSpline &spline, MutableSpan<float> parameters) +{ + Span<int> offsets = spline.control_point_offsets(); + Span<float> lengths = spline.evaluated_lengths(); + const float total_length = spline.length(); + const float total_length_inverse = total_length == 0.0f ? 0.0f : 1.0f / total_length; + + for (const int i : IndexRange(1, spline.size() - 1)) { + parameters[i] = lengths[offsets[i] - 1] * total_length_inverse; + } +} + +/** + * The parameter for poly splines is simply the evaluated lengths divided by the total length. + */ +static void calculate_poly_parameters(const PolySpline &spline, MutableSpan<float> parameters) +{ + Span<float> lengths = spline.evaluated_lengths(); + const float total_length = spline.length(); + const float total_length_inverse = total_length == 0.0f ? 0.0f : 1.0f / total_length; + + for (const int i : IndexRange(1, spline.size() - 1)) { + parameters[i] = lengths[i - 1] * total_length_inverse; + } +} + +/** + * Since NURBS control points do not necessarily coincide with the evaluated curve's path, and + * each control point doesn't correspond well to a specific evaluated point, the parameter at + * each point is not well defined. So instead, treat the control points as if they were a poly + * spline. + */ +static void calculate_nurbs_parameters(const NURBSpline &spline, MutableSpan<float> parameters) +{ + Span<float3> positions = spline.positions(); + Array<float> control_point_lengths(spline.size()); + + float length = 0.0f; + for (const int i : IndexRange(positions.size() - 1)) { + parameters[i] = length; + length += float3::distance(positions[i], positions[i + 1]); + } + + const float total_length_inverse = length == 0.0f ? 0.0f : 1.0f / length; + for (float ¶meter : parameters) { + parameter *= total_length_inverse; + } +} + +static Array<float> curve_parameter_point_domain(const CurveEval &curve) +{ + Span<SplinePtr> splines = curve.splines(); + Array<int> offsets = curve.control_point_offsets(); + const int total_size = offsets.last(); + Array<float> parameters(total_size); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + MutableSpan spline_factors{parameters.as_mutable_span().slice(offsets[i], spline.size())}; + spline_factors.first() = 0.0f; + switch (splines[i]->type()) { + case Spline::Type::Bezier: { + calculate_bezier_parameters(static_cast<const BezierSpline &>(spline), spline_factors); + break; + } + case Spline::Type::Poly: { + calculate_poly_parameters(static_cast<const PolySpline &>(spline), spline_factors); + break; + } + case Spline::Type::NURBS: { + calculate_nurbs_parameters(static_cast<const NURBSpline &>(spline), spline_factors); + break; + } + } + } + }); + return parameters; +} + +static const GVArray *construct_curve_parameter_gvarray(const CurveEval &curve, + const IndexMask mask, + const AttributeDomain domain, + ResourceScope &scope) +{ + if (domain == ATTR_DOMAIN_POINT) { + Array<float> parameters = curve_parameter_point_domain(curve); + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float>>>(std::move(parameters)); + } + + if (domain == ATTR_DOMAIN_CURVE) { + Array<float> parameters = curve_parameter_spline_domain(curve, mask); + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float>>>(std::move(parameters)); + } + + return nullptr; +} + +class CurveParameterFieldInput final : public fn::FieldInput { + public: + CurveParameterFieldInput() : fn::FieldInput(CPPType::get<float>(), "Curve Parameter") + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask mask, + ResourceScope &scope) const final + { + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + const CurveEval *curve = curve_component.get_for_read(); + if (curve) { + return construct_curve_parameter_gvarray(*curve, mask, domain, scope); + } + } + } + return nullptr; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 29837456298; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const CurveParameterFieldInput *>(&other) != nullptr; + } +}; + +static void geo_node_curve_parameter_exec(GeoNodeExecParams params) +{ + Field<float> parameter_field{std::make_shared<CurveParameterFieldInput>()}; + params.set_output("Factor", std::move(parameter_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_parameter() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_PARAMETER, "Curve Parameter", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_curve_parameter_exec; + ntype.declare = blender::nodes::geo_node_curve_parameter_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc new file mode 100644 index 00000000000..ac0cd510ffa --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc @@ -0,0 +1,288 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_task.hh" + +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_curve_sample_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Curve"); + b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Float>("Length").min(0.0f).subtype(PROP_DISTANCE); + + b.add_output<decl::Vector>("Position"); + b.add_output<decl::Vector>("Tangent"); + b.add_output<decl::Vector>("Normal"); +} + +static void geo_node_curve_sample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); +} + +static void geo_node_curve_sample_type_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSample *data = (NodeGeometryCurveSample *)MEM_callocN( + sizeof(NodeGeometryCurveSample), __func__); + data->mode = GEO_NODE_CURVE_SAMPLE_LENGTH; + node->storage = data; +} + +static void geo_node_curve_sample_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeGeometryCurveSample &node_storage = *(NodeGeometryCurveSample *)node->storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + + bNodeSocket *factor = ((bNodeSocket *)node->inputs.first)->next; + bNodeSocket *length = factor->next; + + nodeSetSocketAvailability(factor, mode == GEO_NODE_CURVE_SAMPLE_FACTOR); + nodeSetSocketAvailability(length, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); +} + +template<typename T> static T sample_with_lookup(const Spline::LookupResult lookup, Span<T> data) +{ + return attribute_math::mix2( + lookup.factor, data[lookup.evaluated_index], data[lookup.next_evaluated_index]); +} + +class SampleCurveFunction : public fn::MultiFunction { + private: + /** + * The function holds a geometry set instead of a curve or a curve component in order to + * maintain a reference to the geometry while the field tree is being built, so that the + * curve is not freed before the function can execute. + */ + GeometrySet geometry_set_; + /** + * To support factor inputs, the node adds another field operation before this one to multiply by + * the curve's total length. Since that must calculate the spline lengths anyway, store them to + * reuse the calculation. + */ + Array<float> spline_lengths_; + /** The last member of #spline_lengths_, extracted for convenience. */ + const float total_length_; + + public: + SampleCurveFunction(GeometrySet geometry_set, Array<float> spline_lengths) + : geometry_set_(std::move(geometry_set)), + spline_lengths_(std::move(spline_lengths)), + total_length_(spline_lengths_.last()) + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Curve Sample"}; + signature.single_input<float>("Length"); + signature.single_output<float3>("Position"); + signature.single_output<float3>("Tangent"); + signature.single_output<float3>("Normal"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + MutableSpan<float3> sampled_positions = params.uninitialized_single_output_if_required<float3>( + 1, "Position"); + MutableSpan<float3> sampled_tangents = params.uninitialized_single_output_if_required<float3>( + 2, "Tangent"); + MutableSpan<float3> sampled_normals = params.uninitialized_single_output_if_required<float3>( + 3, "Normal"); + + auto return_default = [&]() { + if (!sampled_positions.is_empty()) { + sampled_positions.fill_indices(mask, {0, 0, 0}); + } + if (!sampled_tangents.is_empty()) { + sampled_tangents.fill_indices(mask, {0, 0, 0}); + } + if (!sampled_normals.is_empty()) { + sampled_normals.fill_indices(mask, {0, 0, 0}); + } + }; + + if (!geometry_set_.has_curve()) { + return return_default(); + } + + const CurveComponent *curve_component = geometry_set_.get_component_for_read<CurveComponent>(); + const CurveEval *curve = curve_component->get_for_read(); + Span<SplinePtr> splines = curve->splines(); + if (splines.is_empty()) { + return return_default(); + } + + const VArray<float> &lengths_varray = params.readonly_single_input<float>(0, "Length"); + const VArray_Span lengths{lengths_varray}; +#ifdef DEBUG + for (const float length : lengths) { + /* Lengths must be in range of the curve's total length. This is ensured in + * #get_length_input_field by adding another multi-function before this one + * to clamp the lengths. */ + BLI_assert(length >= 0.0f && length <= total_length_); + } +#endif + + Array<int> spline_indices(mask.min_array_size()); + for (const int i : mask) { + const float *offset = std::lower_bound( + spline_lengths_.begin(), spline_lengths_.end(), lengths[i]); + const int index = offset - spline_lengths_.data() - 1; + spline_indices[i] = std::max(index, 0); + } + + /* Storing lookups in an array is unnecessary but will simplify custom attribute transfer. */ + Array<Spline::LookupResult> lookups(mask.min_array_size()); + for (const int i : mask) { + const float length_in_spline = lengths[i] - spline_lengths_[spline_indices[i]]; + lookups[i] = splines[spline_indices[i]]->lookup_evaluated_length(length_in_spline); + } + + if (!sampled_positions.is_empty()) { + for (const int i : mask) { + const Spline::LookupResult &lookup = lookups[i]; + const Span<float3> evaluated_positions = splines[spline_indices[i]]->evaluated_positions(); + sampled_positions[i] = sample_with_lookup(lookup, evaluated_positions); + } + } + + if (!sampled_tangents.is_empty()) { + for (const int i : mask) { + const Spline::LookupResult &lookup = lookups[i]; + const Span<float3> evaluated_tangents = splines[spline_indices[i]]->evaluated_tangents(); + sampled_tangents[i] = sample_with_lookup(lookup, evaluated_tangents).normalized(); + } + } + + if (!sampled_normals.is_empty()) { + for (const int i : mask) { + const Spline::LookupResult &lookup = lookups[i]; + const Span<float3> evaluated_normals = splines[spline_indices[i]]->evaluated_normals(); + sampled_normals[i] = sample_with_lookup(lookup, evaluated_normals).normalized(); + } + } + } +}; + +/** + * Pre-process the lengths or factors used for the sampling, turning factors into lengths, and + * clamping between zero and the total length of the curve. Do this as a separate operation in the + * field tree to make the sampling simpler, and to let the evaluator optimize better. + * + * \todo Use a mutable single input instead when they are supported. + */ +static Field<float> get_length_input_field(const GeoNodeExecParams ¶ms, + const float curve_total_length) +{ + const NodeGeometryCurveSample &node_storage = *(NodeGeometryCurveSample *)params.node().storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + + if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + /* Just make sure the length is in bounds of the curve. */ + Field<float> length_field = params.get_input<Field<float>>("Length"); + auto clamp_fn = std::make_unique<fn::CustomMF_SI_SO<float, float>>( + __func__, [curve_total_length](float length) { + return std::clamp(length, 0.0f, curve_total_length); + }); + auto clamp_op = std::make_shared<FieldOperation>( + FieldOperation(std::move(clamp_fn), {std::move(length_field)})); + + return Field<float>(std::move(clamp_op), 0); + } + + /* Convert the factor to a length and clamp it to the bounds of the curve. */ + Field<float> factor_field = params.get_input<Field<float>>("Factor"); + auto clamp_fn = std::make_unique<fn::CustomMF_SI_SO<float, float>>( + __func__, [curve_total_length](float factor) { + const float length = factor * curve_total_length; + return std::clamp(length, 0.0f, curve_total_length); + }); + auto process_op = std::make_shared<FieldOperation>( + FieldOperation(std::move(clamp_fn), {std::move(factor_field)})); + + return Field<float>(std::move(process_op), 0); +} + +static void geo_node_curve_sample_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + + auto return_default = [&]() { + params.set_output("Position", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); + params.set_output("Tangent", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); + params.set_output("Normal", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); + }; + + const CurveComponent *component = geometry_set.get_component_for_read<CurveComponent>(); + if (component == nullptr) { + return return_default(); + } + + const CurveEval *curve = component->get_for_read(); + if (curve == nullptr) { + return return_default(); + } + + if (curve->splines().is_empty()) { + return return_default(); + } + + Array<float> spline_lengths = curve->accumulated_spline_lengths(); + const float total_length = spline_lengths.last(); + if (total_length == 0.0f) { + return return_default(); + } + + Field<float> length_field = get_length_input_field(params, total_length); + + auto sample_fn = std::make_unique<SampleCurveFunction>(std::move(geometry_set), + std::move(spline_lengths)); + auto sample_op = std::make_shared<FieldOperation>( + FieldOperation(std::move(sample_fn), {length_field})); + + params.set_output("Position", Field<float3>(sample_op, 0)); + params.set_output("Tangent", Field<float3>(sample_op, 1)); + params.set_output("Normal", Field<float3>(sample_op, 2)); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_sample() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_SAMPLE, "Curve Sample", NODE_CLASS_GEOMETRY, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_curve_sample_exec; + ntype.declare = blender::nodes::geo_node_curve_sample_declare; + node_type_init(&ntype, blender::nodes::geo_node_curve_sample_type_init); + node_type_update(&ntype, blender::nodes::geo_node_curve_sample_update); + node_type_storage( + &ntype, "NodeGeometryCurveSample", node_free_standard_storage, node_copy_standard_storage); + ntype.draw_buttons = blender::nodes::geo_node_curve_sample_layout; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc index b8bdb3d71d6..89ba635ff4b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -14,17 +14,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "BLI_array.hh" -#include "BLI_float4x4.hh" -#include "BLI_task.hh" - -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" - -#include "BKE_material.h" -#include "BKE_mesh.h" #include "BKE_spline.hh" +#include "BKE_curve_to_mesh.hh" + #include "UI_interface.h" #include "UI_resources.h" @@ -39,692 +32,6 @@ static void geo_node_curve_to_mesh_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Mesh"); } -/** Information about the creation of one curve spline and profile spline combination. */ -struct ResultInfo { - const Spline &spline; - const Spline &profile; - int vert_offset; - int edge_offset; - int loop_offset; - int poly_offset; - int spline_vert_len; - int spline_edge_len; - int profile_vert_len; - int profile_edge_len; -}; - -static void vert_extrude_to_mesh_data(const Spline &spline, - const float3 profile_vert, - MutableSpan<MVert> r_verts, - MutableSpan<MEdge> r_edges, - const int vert_offset, - const int edge_offset) -{ - Span<float3> positions = spline.evaluated_positions(); - - for (const int i : IndexRange(positions.size() - 1)) { - MEdge &edge = r_edges[edge_offset + i]; - edge.v1 = vert_offset + i; - edge.v2 = vert_offset + i + 1; - edge.flag = ME_LOOSEEDGE; - } - - if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) { - MEdge &edge = r_edges[edge_offset + spline.evaluated_edges_size() - 1]; - edge.v1 = vert_offset; - edge.v2 = vert_offset + positions.size() - 1; - edge.flag = ME_LOOSEEDGE; - } - - for (const int i : positions.index_range()) { - MVert &vert = r_verts[vert_offset + i]; - copy_v3_v3(vert.co, positions[i] + profile_vert); - } -} - -static void mark_edges_sharp(MutableSpan<MEdge> edges) -{ - for (MEdge &edge : edges) { - edge.flag |= ME_SHARP; - } -} - -static void spline_extrude_to_mesh_data(const ResultInfo &info, - MutableSpan<MVert> r_verts, - MutableSpan<MEdge> r_edges, - MutableSpan<MLoop> r_loops, - MutableSpan<MPoly> r_polys) -{ - const Spline &spline = info.spline; - const Spline &profile = info.profile; - if (info.profile_vert_len == 1) { - vert_extrude_to_mesh_data(spline, - profile.evaluated_positions()[0], - r_verts, - r_edges, - info.vert_offset, - info.edge_offset); - return; - } - - /* Add the edges running along the length of the curve, starting at each profile vertex. */ - const int spline_edges_start = info.edge_offset; - for (const int i_profile : IndexRange(info.profile_vert_len)) { - const int profile_edge_offset = spline_edges_start + i_profile * info.spline_edge_len; - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; - - const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; - const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; - - MEdge &edge = r_edges[profile_edge_offset + i_ring]; - edge.v1 = ring_vert_offset + i_profile; - edge.v2 = next_ring_vert_offset + i_profile; - edge.flag = ME_EDGEDRAW | ME_EDGERENDER; - } - } - - /* Add the edges running along each profile ring. */ - const int profile_edges_start = spline_edges_start + - info.profile_vert_len * info.spline_edge_len; - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; - - const int ring_edge_offset = profile_edges_start + i_ring * info.profile_edge_len; - for (const int i_profile : IndexRange(info.profile_edge_len)) { - const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; - - MEdge &edge = r_edges[ring_edge_offset + i_profile]; - edge.v1 = ring_vert_offset + i_profile; - edge.v2 = ring_vert_offset + i_next_profile; - edge.flag = ME_EDGEDRAW | ME_EDGERENDER; - } - } - - /* Calculate poly and corner indices. */ - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1; - - const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring; - const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring; - - const int ring_edge_start = profile_edges_start + info.profile_edge_len * i_ring; - const int next_ring_edge_offset = profile_edges_start + info.profile_edge_len * i_next_ring; - - const int ring_poly_offset = info.poly_offset + i_ring * info.profile_edge_len; - const int ring_loop_offset = info.loop_offset + i_ring * info.profile_edge_len * 4; - - for (const int i_profile : IndexRange(info.profile_edge_len)) { - const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4; - const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1; - - const int spline_edge_start = spline_edges_start + info.spline_edge_len * i_profile; - const int next_spline_edge_start = spline_edges_start + - info.spline_edge_len * i_next_profile; - - MPoly &poly = r_polys[ring_poly_offset + i_profile]; - poly.loopstart = ring_segment_loop_offset; - poly.totloop = 4; - poly.flag = ME_SMOOTH; - - MLoop &loop_a = r_loops[ring_segment_loop_offset]; - loop_a.v = ring_vert_offset + i_profile; - loop_a.e = ring_edge_start + i_profile; - MLoop &loop_b = r_loops[ring_segment_loop_offset + 1]; - loop_b.v = ring_vert_offset + i_next_profile; - loop_b.e = next_spline_edge_start + i_ring; - MLoop &loop_c = r_loops[ring_segment_loop_offset + 2]; - loop_c.v = next_ring_vert_offset + i_next_profile; - loop_c.e = next_ring_edge_offset + i_profile; - MLoop &loop_d = r_loops[ring_segment_loop_offset + 3]; - loop_d.v = next_ring_vert_offset + i_profile; - loop_d.e = spline_edge_start + i_ring; - } - } - - /* Calculate the positions of each profile ring profile along the spline. */ - Span<float3> positions = spline.evaluated_positions(); - Span<float3> tangents = spline.evaluated_tangents(); - Span<float3> normals = spline.evaluated_normals(); - Span<float3> profile_positions = profile.evaluated_positions(); - - GVArray_Typed<float> radii = spline.interpolate_to_evaluated(spline.radii()); - for (const int i_ring : IndexRange(info.spline_vert_len)) { - float4x4 point_matrix = float4x4::from_normalized_axis_data( - positions[i_ring], normals[i_ring], tangents[i_ring]); - point_matrix.apply_scale(radii[i_ring]); - - const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; - for (const int i_profile : IndexRange(info.profile_vert_len)) { - MVert &vert = r_verts[ring_vert_start + i_profile]; - copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]); - } - } - - /* Mark edge loops from sharp vector control points sharp. */ - if (profile.type() == Spline::Type::Bezier) { - const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile); - Span<int> control_point_offsets = bezier_spline.control_point_offsets(); - for (const int i : IndexRange(bezier_spline.size())) { - if (bezier_spline.point_is_sharp(i)) { - mark_edges_sharp( - r_edges.slice(spline_edges_start + info.spline_edge_len * control_point_offsets[i], - info.spline_edge_len)); - } - } - } -} - -static inline int spline_extrude_vert_size(const Spline &curve, const Spline &profile) -{ - return curve.evaluated_points_size() * profile.evaluated_points_size(); -} - -static inline int spline_extrude_edge_size(const Spline &curve, const Spline &profile) -{ - /* Add the ring edges, with one ring for every curve vertex, and the edge loops - * that run along the length of the curve, starting on the first profile. */ - return curve.evaluated_points_size() * profile.evaluated_edges_size() + - curve.evaluated_edges_size() * profile.evaluated_points_size(); -} - -static inline int spline_extrude_loop_size(const Spline &curve, const Spline &profile) -{ - return curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4; -} - -static inline int spline_extrude_poly_size(const Spline &curve, const Spline &profile) -{ - return curve.evaluated_edges_size() * profile.evaluated_edges_size(); -} - -struct ResultOffsets { - Array<int> vert; - Array<int> edge; - Array<int> loop; - Array<int> poly; -}; -static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<SplinePtr> curves) -{ - const int total = profiles.size() * curves.size(); - Array<int> vert(total + 1); - Array<int> edge(total + 1); - Array<int> loop(total + 1); - Array<int> poly(total + 1); - - int mesh_index = 0; - int vert_offset = 0; - int edge_offset = 0; - int loop_offset = 0; - int poly_offset = 0; - for (const int i_spline : curves.index_range()) { - for (const int i_profile : profiles.index_range()) { - vert[mesh_index] = vert_offset; - edge[mesh_index] = edge_offset; - loop[mesh_index] = loop_offset; - poly[mesh_index] = poly_offset; - vert_offset += spline_extrude_vert_size(*curves[i_spline], *profiles[i_profile]); - edge_offset += spline_extrude_edge_size(*curves[i_spline], *profiles[i_profile]); - loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile]); - poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile]); - mesh_index++; - } - } - vert.last() = vert_offset; - edge.last() = edge_offset; - loop.last() = loop_offset; - poly.last() = poly_offset; - - return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)}; -} - -static AttributeDomain get_result_attribute_domain(const MeshComponent &component, - const AttributeIDRef &attribute_id) -{ - /* Only use a different domain if it is builtin and must only exist on one domain. */ - if (!component.attribute_is_builtin(attribute_id)) { - return ATTR_DOMAIN_POINT; - } - - std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_id); - if (!meta_data) { - /* This function has to return something in this case, but it shouldn't be used, - * so return an output that will assert later if the code attempts to handle it. */ - return ATTR_DOMAIN_AUTO; - } - - return meta_data->domain; -} - -/** - * The data stored in the attribute and its domain from #OutputAttribute, to avoid calling - * `as_span()` for every single profile and curve spline combination, and for readability. - */ -struct ResultAttributeData { - GMutableSpan data; - AttributeDomain domain; -}; - -static std::optional<ResultAttributeData> create_attribute_and_get_span( - MeshComponent &component, - const AttributeIDRef &attribute_id, - AttributeMetaData meta_data, - Vector<OutputAttribute> &r_attributes) -{ - const AttributeDomain domain = get_result_attribute_domain(component, attribute_id); - OutputAttribute attribute = component.attribute_try_get_for_output_only( - attribute_id, domain, meta_data.data_type); - if (!attribute) { - return std::nullopt; - } - - GMutableSpan span = attribute.as_span(); - r_attributes.append(std::move(attribute)); - return std::make_optional<ResultAttributeData>({span, domain}); -} - -/** - * Store the references to the attribute data from the curve and profile inputs. Here we rely on - * the invariants of the storage of curve attributes, that the order will be consistent between - * splines, and all splines will have the same attributes. - */ -struct ResultAttributes { - /** - * Result attributes on the mesh corresponding to each attribute on the curve input, in the same - * order. The data is optional only in case the attribute does not exist on the mesh for some - * reason, like "shade_smooth" when the result has no faces. - */ - Vector<std::optional<ResultAttributeData>> curve_point_attributes; - Vector<std::optional<ResultAttributeData>> curve_spline_attributes; - - /** - * Result attributes corresponding the attributes on the profile input, in the same order. The - * attributes are optional in case the attribute names correspond to a names used by the curve - * input, in which case the curve input attributes take precedence. - */ - Vector<std::optional<ResultAttributeData>> profile_point_attributes; - Vector<std::optional<ResultAttributeData>> profile_spline_attributes; - - /** - * Because some builtin attributes are not stored contiguously, and the curve inputs might have - * attributes with those names, it's necessary to keep OutputAttributes around to give access to - * the result data in a contiguous array. - */ - Vector<OutputAttribute> attributes; -}; -static ResultAttributes create_result_attributes(const CurveEval &curve, - const CurveEval &profile, - Mesh &mesh) -{ - MeshComponent mesh_component; - mesh_component.replace(&mesh, GeometryOwnershipType::Editable); - Set<AttributeIDRef> curve_attributes; - - /* In order to prefer attributes on the main curve input when there are name collisions, first - * check the attributes on the curve, then add attributes on the profile that are not also on the - * main curve input. */ - ResultAttributes result; - curve.splines().first()->attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - curve_attributes.add_new(id); - result.curve_point_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - return true; - }, - ATTR_DOMAIN_POINT); - curve.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - curve_attributes.add_new(id); - result.curve_spline_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - return true; - }, - ATTR_DOMAIN_CURVE); - profile.splines().first()->attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - if (curve_attributes.contains(id)) { - result.profile_point_attributes.append({}); - } - else { - result.profile_point_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - } - return true; - }, - ATTR_DOMAIN_POINT); - profile.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - if (curve_attributes.contains(id)) { - result.profile_spline_attributes.append({}); - } - else { - result.profile_spline_attributes.append( - create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes)); - } - return true; - }, - ATTR_DOMAIN_CURVE); - - return result; -} - -template<typename T> -static void copy_curve_point_data_to_mesh_verts(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len; - dst.slice(ring_vert_start, info.profile_vert_len).fill(src[i_ring]); - } -} - -template<typename T> -static void copy_curve_point_data_to_mesh_edges(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - const int edges_start = info.edge_offset + info.profile_vert_len * info.spline_edge_len; - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int ring_edge_start = edges_start + info.profile_edge_len * i_ring; - dst.slice(ring_edge_start, info.profile_edge_len).fill(src[i_ring]); - } -} - -template<typename T> -static void copy_curve_point_data_to_mesh_faces(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int ring_face_start = info.poly_offset + info.profile_edge_len * i_ring; - dst.slice(ring_face_start, info.profile_edge_len).fill(src[i_ring]); - } -} - -static void copy_curve_point_attribute_to_mesh(const GSpan src, - const ResultInfo &info, - ResultAttributeData &dst) -{ - GVArrayPtr interpolated_gvarray = info.spline.interpolate_to_evaluated(src); - GSpan interpolated = interpolated_gvarray->get_internal_span(); - - attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { - using T = decltype(dummy); - switch (dst.domain) { - case ATTR_DOMAIN_POINT: - copy_curve_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_EDGE: - copy_curve_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_FACE: - copy_curve_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_CORNER: - /* Unsupported for now, since there are no builtin attributes to convert into. */ - break; - default: - BLI_assert_unreachable(); - break; - } - }); -} - -template<typename T> -static void copy_profile_point_data_to_mesh_verts(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - for (const int i_ring : IndexRange(info.spline_vert_len)) { - const int profile_vert_start = info.vert_offset + i_ring * info.profile_vert_len; - for (const int i_profile : IndexRange(info.profile_vert_len)) { - dst[profile_vert_start + i_profile] = src[i_profile]; - } - } -} - -template<typename T> -static void copy_profile_point_data_to_mesh_edges(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - for (const int i_profile : IndexRange(info.profile_vert_len)) { - const int profile_edge_offset = info.edge_offset + i_profile * info.spline_edge_len; - dst.slice(profile_edge_offset, info.spline_edge_len).fill(src[i_profile]); - } -} - -template<typename T> -static void copy_profile_point_data_to_mesh_faces(const Span<T> src, - const ResultInfo &info, - MutableSpan<T> dst) -{ - for (const int i_ring : IndexRange(info.spline_edge_len)) { - const int profile_face_start = info.poly_offset + i_ring * info.profile_edge_len; - for (const int i_profile : IndexRange(info.profile_edge_len)) { - dst[profile_face_start + i_profile] = src[i_profile]; - } - } -} - -static void copy_profile_point_attribute_to_mesh(const GSpan src, - const ResultInfo &info, - ResultAttributeData &dst) -{ - GVArrayPtr interpolated_gvarray = info.profile.interpolate_to_evaluated(src); - GSpan interpolated = interpolated_gvarray->get_internal_span(); - - attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { - using T = decltype(dummy); - switch (dst.domain) { - case ATTR_DOMAIN_POINT: - copy_profile_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_EDGE: - copy_profile_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_FACE: - copy_profile_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>()); - break; - case ATTR_DOMAIN_CORNER: - /* Unsupported for now, since there are no builtin attributes to convert into. */ - break; - default: - BLI_assert_unreachable(); - break; - } - }); -} - -static void copy_point_domain_attributes_to_mesh(const ResultInfo &info, - ResultAttributes &attributes) -{ - if (!attributes.curve_point_attributes.is_empty()) { - int i = 0; - info.spline.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.curve_point_attributes[i]) { - copy_curve_point_attribute_to_mesh(*info.spline.attributes.get_for_read(id), - info, - *attributes.curve_point_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_POINT); - } - if (!attributes.profile_point_attributes.is_empty()) { - int i = 0; - info.profile.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.profile_point_attributes[i]) { - copy_profile_point_attribute_to_mesh(*info.profile.attributes.get_for_read(id), - info, - *attributes.profile_point_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_POINT); - } -} - -template<typename T> -static void copy_spline_data_to_mesh(Span<T> src, Span<int> offsets, MutableSpan<T> dst) -{ - for (const int i : IndexRange(src.size())) { - dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]); - } -} - -/** - * Since the offsets for each combination of curve and profile spline are stored for every mesh - * domain, and this just needs to fill the chunks corresponding to each combination, we can use - * the same function for all mesh domains. - */ -static void copy_spline_attribute_to_mesh(const GSpan src, - const ResultOffsets &offsets, - ResultAttributeData &dst_attribute) -{ - attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { - using T = decltype(dummy); - switch (dst_attribute.domain) { - case ATTR_DOMAIN_POINT: - copy_spline_data_to_mesh(src.typed<T>(), offsets.vert, dst_attribute.data.typed<T>()); - break; - case ATTR_DOMAIN_EDGE: - copy_spline_data_to_mesh(src.typed<T>(), offsets.edge, dst_attribute.data.typed<T>()); - break; - case ATTR_DOMAIN_FACE: - copy_spline_data_to_mesh(src.typed<T>(), offsets.poly, dst_attribute.data.typed<T>()); - break; - case ATTR_DOMAIN_CORNER: - copy_spline_data_to_mesh(src.typed<T>(), offsets.loop, dst_attribute.data.typed<T>()); - break; - default: - BLI_assert_unreachable(); - break; - } - }); -} - -static void copy_spline_domain_attributes_to_mesh(const CurveEval &curve, - const CurveEval &profile, - const ResultOffsets &offsets, - ResultAttributes &attributes) -{ - if (!attributes.curve_spline_attributes.is_empty()) { - int i = 0; - curve.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.curve_spline_attributes[i]) { - copy_spline_attribute_to_mesh(*curve.attributes.get_for_read(id), - offsets, - *attributes.curve_spline_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_CURVE); - } - if (!attributes.profile_spline_attributes.is_empty()) { - int i = 0; - profile.attributes.foreach_attribute( - [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) { - if (attributes.profile_spline_attributes[i]) { - copy_spline_attribute_to_mesh(*profile.attributes.get_for_read(id), - offsets, - *attributes.profile_spline_attributes[i]); - } - i++; - return true; - }, - ATTR_DOMAIN_CURVE); - } -} - -/** - * \note Normal calculation is by far the slowest part of calculations relating to the result mesh. - * Although it would be a sensible decision to use the better topology information available while - * generating the mesh to also generate the normals, that work may wasted if the output mesh is - * changed anyway in a way that affects the normals. So currently this code uses the safer / - * simpler solution of deferring normal calculation to the rest of Blender. - */ -static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &profile) -{ - Span<SplinePtr> profiles = profile.splines(); - Span<SplinePtr> curves = curve.splines(); - - const ResultOffsets offsets = calculate_result_offsets(profiles, curves); - if (offsets.vert.last() == 0) { - return nullptr; - } - - Mesh *mesh = BKE_mesh_new_nomain( - offsets.vert.last(), offsets.edge.last(), 0, offsets.loop.last(), offsets.poly.last()); - BKE_id_material_eval_ensure_default_slot(&mesh->id); - mesh->flag |= ME_AUTOSMOOTH; - mesh->smoothresh = DEG2RADF(180.0f); - BKE_mesh_normals_tag_dirty(mesh); - - ResultAttributes attributes = create_result_attributes(curve, profile, *mesh); - - threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) { - for (const int i_spline : curves_range) { - const Spline &spline = *curves[i_spline]; - if (spline.evaluated_points_size() == 0) { - continue; - } - const int spline_start_index = i_spline * profiles.size(); - threading::parallel_for(profiles.index_range(), 128, [&](IndexRange profiles_range) { - for (const int i_profile : profiles_range) { - const Spline &profile = *profiles[i_profile]; - const int i_mesh = spline_start_index + i_profile; - ResultInfo info{ - spline, - profile, - offsets.vert[i_mesh], - offsets.edge[i_mesh], - offsets.loop[i_mesh], - offsets.poly[i_mesh], - spline.evaluated_points_size(), - spline.evaluated_edges_size(), - profile.evaluated_points_size(), - profile.evaluated_edges_size(), - }; - - spline_extrude_to_mesh_data(info, - {mesh->mvert, mesh->totvert}, - {mesh->medge, mesh->totedge}, - {mesh->mloop, mesh->totloop}, - {mesh->mpoly, mesh->totpoly}); - - copy_point_domain_attributes_to_mesh(info, attributes); - } - }); - } - }); - - copy_spline_domain_attributes_to_mesh(curve, profile, offsets, attributes); - - for (OutputAttribute &output_attribute : attributes.attributes) { - output_attribute.save(); - } - - return mesh; -} - -static CurveEval get_curve_single_vert() -{ - CurveEval curve; - std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); - spline->add_point(float3(0), 0, 0.0f); - curve.add_spline(std::move(spline)); - - return curve; -} - static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params) { GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); @@ -750,11 +57,14 @@ static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params) const CurveEval *profile_curve = profile_set.get_curve_for_read(); - static const CurveEval vert_curve = get_curve_single_vert(); - - Mesh *mesh = curve_to_mesh_calculate(*curve_set.get_curve_for_read(), - (profile_curve == nullptr) ? vert_curve : *profile_curve); - params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); + if (profile_curve == nullptr) { + Mesh *mesh = bke::curve_to_wire_mesh(*curve_set.get_curve_for_read()); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); + } + else { + Mesh *mesh = bke::curve_to_mesh_sweep(*curve_set.get_curve_for_read(), *profile_curve); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); + } } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc index 07818f2a3ad..f92086acdf0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc @@ -14,10 +14,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "BKE_mesh.h" +#include "BKE_spline.hh" #include "node_geometry_util.hh" @@ -147,6 +150,95 @@ static const GVArray *construct_mesh_normals_gvarray(const MeshComponent &mesh_c } } +static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan<float3> normals) +{ + Span<int> offsets = spline.control_point_offsets(); + Span<float3> evaluated_normals = spline.evaluated_normals(); + for (const int i : IndexRange(spline.size())) { + normals[i] = evaluated_normals[offsets[i]]; + } +} + +static void calculate_poly_normals(const PolySpline &spline, MutableSpan<float3> normals) +{ + normals.copy_from(spline.evaluated_normals()); +} + +/** + * Because NURBS control points are not necessarily on the path, the normal at the control points + * is not well defined, so create a temporary poly spline to find the normals. This requires extra + * copying currently, but may be more efficient in the future if attributes have some form of CoW. + */ +static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan<float3> normals) +{ + PolySpline poly_spline; + poly_spline.resize(spline.size()); + poly_spline.positions().copy_from(spline.positions()); + normals.copy_from(poly_spline.evaluated_normals()); +} + +static Array<float3> curve_normal_point_domain(const CurveEval &curve) +{ + Span<SplinePtr> splines = curve.splines(); + Array<int> offsets = curve.control_point_offsets(); + const int total_size = offsets.last(); + Array<float3> normals(total_size); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())}; + switch (splines[i]->type()) { + case Spline::Type::Bezier: + calculate_bezier_normals(static_cast<const BezierSpline &>(spline), spline_normals); + break; + case Spline::Type::Poly: + calculate_poly_normals(static_cast<const PolySpline &>(spline), spline_normals); + break; + case Spline::Type::NURBS: + calculate_nurbs_normals(static_cast<const NURBSpline &>(spline), spline_normals); + break; + } + } + }); + return normals; +} + +static const GVArray *construct_curve_normal_gvarray(const CurveComponent &component, + const AttributeDomain domain, + ResourceScope &scope) +{ + const CurveEval *curve = component.get_for_read(); + if (curve == nullptr) { + return nullptr; + } + + if (domain == ATTR_DOMAIN_POINT) { + const Span<SplinePtr> splines = curve->splines(); + + /* Use a reference to evaluated normals if possible to avoid an allocation and a copy. + * This is only possible when there is only one poly spline. */ + if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) { + const PolySpline &spline = static_cast<PolySpline &>(*splines.first()); + return &scope.construct<fn::GVArray_For_Span<float3>>(spline.evaluated_normals()); + } + + Array<float3> normals = curve_normal_point_domain(*curve); + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals)); + } + + if (domain == ATTR_DOMAIN_CURVE) { + Array<float3> point_normals = curve_normal_point_domain(*curve); + GVArrayPtr gvarray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>( + std::move(point_normals)); + GVArrayPtr spline_normals = component.attribute_try_adapt_domain( + std::move(gvarray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); + return scope.add_value(std::move(spline_normals)).get(); + } + + return nullptr; +} + class NormalFieldInput final : public fn::FieldInput { public: NormalFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Normal") @@ -173,8 +265,8 @@ class NormalFieldInput final : public fn::FieldInput { return construct_mesh_normals_gvarray(mesh_component, *mesh, mask, domain, scope); } if (component.type() == GEO_COMPONENT_TYPE_CURVE) { - /* TODO: Add curve normals support. */ - return nullptr; + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return construct_curve_normal_gvarray(curve_component, domain, scope); } } return nullptr; diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc new file mode 100644 index 00000000000..68788709f1e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc @@ -0,0 +1,174 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_task.hh" + +#include "BKE_spline.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_input_tangent_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Vector>("Tangent"); +} + +static void calculate_bezier_tangents(const BezierSpline &spline, MutableSpan<float3> tangents) +{ + Span<int> offsets = spline.control_point_offsets(); + Span<float3> evaluated_tangents = spline.evaluated_tangents(); + for (const int i : IndexRange(spline.size())) { + tangents[i] = evaluated_tangents[offsets[i]]; + } +} + +static void calculate_poly_tangents(const PolySpline &spline, MutableSpan<float3> tangents) +{ + tangents.copy_from(spline.evaluated_tangents()); +} + +/** + * Because NURBS control points are not necessarily on the path, the tangent at the control points + * is not well defined, so create a temporary poly spline to find the tangents. This requires extra + * copying currently, but may be more efficient in the future if attributes have some form of CoW. + */ +static void calculate_nurbs_tangents(const NURBSpline &spline, MutableSpan<float3> tangents) +{ + PolySpline poly_spline; + poly_spline.resize(spline.size()); + poly_spline.positions().copy_from(spline.positions()); + tangents.copy_from(poly_spline.evaluated_tangents()); +} + +static Array<float3> curve_tangent_point_domain(const CurveEval &curve) +{ + Span<SplinePtr> splines = curve.splines(); + Array<int> offsets = curve.control_point_offsets(); + const int total_size = offsets.last(); + Array<float3> tangents(total_size); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + MutableSpan spline_tangents{tangents.as_mutable_span().slice(offsets[i], spline.size())}; + switch (splines[i]->type()) { + case Spline::Type::Bezier: { + calculate_bezier_tangents(static_cast<const BezierSpline &>(spline), spline_tangents); + break; + } + case Spline::Type::Poly: { + calculate_poly_tangents(static_cast<const PolySpline &>(spline), spline_tangents); + break; + } + case Spline::Type::NURBS: { + calculate_nurbs_tangents(static_cast<const NURBSpline &>(spline), spline_tangents); + break; + } + } + } + }); + return tangents; +} + +static const GVArray *construct_curve_tangent_gvarray(const CurveComponent &component, + const AttributeDomain domain, + ResourceScope &scope) +{ + const CurveEval *curve = component.get_for_read(); + if (curve == nullptr) { + return nullptr; + } + + if (domain == ATTR_DOMAIN_POINT) { + const Span<SplinePtr> splines = curve->splines(); + + /* Use a reference to evaluated tangents if possible to avoid an allocation and a copy. + * This is only possible when there is only one poly spline. */ + if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) { + const PolySpline &spline = static_cast<PolySpline &>(*splines.first()); + return &scope.construct<fn::GVArray_For_Span<float3>>(spline.evaluated_tangents()); + } + + Array<float3> tangents = curve_tangent_point_domain(*curve); + return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(tangents)); + } + + if (domain == ATTR_DOMAIN_CURVE) { + Array<float3> point_tangents = curve_tangent_point_domain(*curve); + GVArrayPtr gvarray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>( + std::move(point_tangents)); + GVArrayPtr spline_tangents = component.attribute_try_adapt_domain( + std::move(gvarray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); + return scope.add_value(std::move(spline_tangents)).get(); + } + + return nullptr; +} + +class TangentFieldInput final : public fn::FieldInput { + public: + TangentFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Tangent") + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask UNUSED(mask), + ResourceScope &scope) const final + { + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return construct_curve_tangent_gvarray(curve_component, domain, scope); + } + } + return nullptr; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 91827364589; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const TangentFieldInput *>(&other) != nullptr; + } +}; + +static void geo_node_input_tangent_exec(GeoNodeExecParams params) +{ + Field<float3> tangent_field{std::make_shared<TangentFieldInput>()}; + params.set_output("Tangent", std::move(tangent_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_tangent() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_TANGENT, "Curve Tangent", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_input_tangent_exec; + ntype.declare = blender::nodes::geo_node_input_tangent_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_join.cc b/source/blender/nodes/geometry/nodes/node_geo_string_join.cc new file mode 100644 index 00000000000..1e4a4d1f68b --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_string_join.cc @@ -0,0 +1,53 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_string_join_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::String>("Delimiter"); + b.add_input<decl::String>("Strings").multi_input().hide_value(); + b.add_output<decl::String>("String"); +}; + +static void geo_node_string_join_exec(GeoNodeExecParams params) +{ + Vector<std::string> strings = params.extract_multi_input<std::string>("Strings"); + const std::string delim = params.extract_input<std::string>("Delimiter"); + + std::string output; + for (const int i : strings.index_range()) { + output += strings[i]; + if (i < (strings.size() - 1)) { + output += delim; + } + } + params.set_output("String", std::move(output)); +} + +} // namespace blender::nodes + +void register_node_type_geo_string_join() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_STRING_JOIN, "String Join", NODE_CLASS_CONVERTER, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_string_join_exec; + ntype.declare = blender::nodes::geo_node_string_join_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c b/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c index f601f3e9fd0..06f4d1f1b79 100644 --- a/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c +++ b/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c @@ -35,6 +35,8 @@ static bNodeSocketTemplate sh_node_bsdf_principled_in[] = { PROP_NONE, SOCK_COMPACT}, {SOCK_RGBA, N_("Subsurface Color"), 0.8f, 0.8f, 0.8f, 1.0f, 0.0f, 1.0f}, + {SOCK_FLOAT, N_("Subsurface IOR"), 1.4f, 0.0f, 0.0f, 0.0f, 1.01f, 3.8f, PROP_FACTOR}, + {SOCK_FLOAT, N_("Subsurface Anisotropy"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, {SOCK_FLOAT, N_("Metallic"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, {SOCK_FLOAT, N_("Specular"), 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, {SOCK_FLOAT, N_("Specular Tint"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, @@ -74,7 +76,7 @@ static bNodeSocketTemplate sh_node_bsdf_principled_out[] = { static void node_shader_init_principled(bNodeTree *UNUSED(ntree), bNode *node) { node->custom1 = SHD_GLOSSY_GGX; - node->custom2 = SHD_SUBSURFACE_BURLEY; + node->custom2 = SHD_SUBSURFACE_RANDOM_WALK; } #define socket_not_zero(sock) (in[sock].link || (clamp_f(in[sock].vec[0], 0.0f, 1.0f) > 1e-5f)) @@ -90,41 +92,40 @@ static int node_shader_gpu_bsdf_principled(GPUMaterial *mat, GPUNodeLink *sss_scale; /* Normals */ - if (!in[20].link) { - GPU_link(mat, "world_normals_get", &in[20].link); + if (!in[22].link) { + GPU_link(mat, "world_normals_get", &in[22].link); } /* Clearcoat Normals */ - if (!in[21].link) { - GPU_link(mat, "world_normals_get", &in[21].link); + if (!in[23].link) { + GPU_link(mat, "world_normals_get", &in[23].link); } #if 0 /* Not used at the moment. */ /* Tangents */ - if (!in[22].link) { + if (!in[24].link) { GPUNodeLink *orco = GPU_attribute(CD_ORCO, ""); - GPU_link(mat, "tangent_orco_z", orco, &in[22].link); + GPU_link(mat, "tangent_orco_z", orco, &in[24].link); GPU_link(mat, "node_tangent", GPU_builtin(GPU_WORLD_NORMAL), - in[22].link, + in[24].link, GPU_builtin(GPU_OBJECT_MATRIX), - &in[22].link); + &in[24].link); } #endif - bool use_diffuse = socket_not_one(4) && socket_not_one(15); + bool use_diffuse = socket_not_one(6) && socket_not_one(17); bool use_subsurf = socket_not_zero(1) && use_diffuse && node->sss_id > 0; - bool use_refract = socket_not_one(4) && socket_not_zero(15); - bool use_clear = socket_not_zero(12); + bool use_refract = socket_not_one(6) && socket_not_zero(17); + bool use_clear = socket_not_zero(14); /* SSS Profile */ if (use_subsurf) { - static short profile = SHD_SUBSURFACE_BURLEY; bNodeSocket *socket = BLI_findlink(&node->original->inputs, 2); bNodeSocketValueRGBA *socket_data = socket->default_value; /* For some reason it seems that the socket value is in ARGB format. */ - GPU_material_sss_profile_create(mat, &socket_data->value[1], &profile, NULL); + GPU_material_sss_profile_create(mat, &socket_data->value[1]); } if (in[2].link) { diff --git a/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c b/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c index 4b91bcbd11c..e917858e0f2 100644 --- a/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c +++ b/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c @@ -25,8 +25,8 @@ static bNodeSocketTemplate sh_node_subsurface_scattering_in[] = { {SOCK_RGBA, N_("Color"), 0.8f, 0.8f, 0.8f, 1.0f, 0.0f, 1.0f}, {SOCK_FLOAT, N_("Scale"), 1.0, 0.0f, 0.0f, 0.0f, 0.0f, 1000.0f}, {SOCK_VECTOR, N_("Radius"), 1.0f, 0.2f, 0.1f, 0.0f, 0.0f, 100.0f, PROP_NONE, SOCK_COMPACT}, - {SOCK_FLOAT, N_("Sharpness"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_FLOAT, N_("Texture Blur"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, + {SOCK_FLOAT, N_("IOR"), 1.4f, 0.0f, 0.0f, 0.0f, 1.01f, 3.8f, PROP_FACTOR}, + {SOCK_FLOAT, N_("Anisotropy"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, {SOCK_VECTOR, N_("Normal"), 0.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE}, {-1, ""}, }; @@ -38,7 +38,8 @@ static bNodeSocketTemplate sh_node_subsurface_scattering_out[] = { static void node_shader_init_subsurface_scattering(bNodeTree *UNUSED(ntree), bNode *node) { - node->custom1 = SHD_SUBSURFACE_BURLEY; + node->custom1 = SHD_SUBSURFACE_RANDOM_WALK; + node->custom2 = true; } static int node_shader_gpu_subsurface_scattering(GPUMaterial *mat, @@ -54,11 +55,8 @@ static int node_shader_gpu_subsurface_scattering(GPUMaterial *mat, if (node->sss_id > 0) { bNodeSocket *socket = BLI_findlink(&node->original->inputs, 2); bNodeSocketValueRGBA *socket_data = socket->default_value; - bNodeSocket *socket_sharp = BLI_findlink(&node->original->inputs, 3); - bNodeSocketValueFloat *socket_data_sharp = socket_sharp->default_value; /* For some reason it seems that the socket value is in ARGB format. */ - GPU_material_sss_profile_create( - mat, &socket_data->value[1], &node->original->custom1, &socket_data_sharp->value); + GPU_material_sss_profile_create(mat, &socket_data->value[1]); /* sss_id is 0 only the node is not connected to any output. * In this case flagging the material would trigger a bug (see T68736). */ @@ -69,23 +67,6 @@ static int node_shader_gpu_subsurface_scattering(GPUMaterial *mat, mat, node, "node_subsurface_scattering", in, out, GPU_constant(&node->sss_id)); } -static void node_shader_update_subsurface_scattering(bNodeTree *UNUSED(ntree), bNode *node) -{ - bNodeSocket *sock; - int falloff = node->custom1; - - for (sock = node->inputs.first; sock; sock = sock->next) { - if (STREQ(sock->name, "Sharpness")) { - if (falloff == SHD_SUBSURFACE_CUBIC) { - sock->flag &= ~SOCK_UNAVAIL; - } - else { - sock->flag |= SOCK_UNAVAIL; - } - } - } -} - /* node type definition */ void register_node_type_sh_subsurface_scattering(void) { @@ -99,7 +80,6 @@ void register_node_type_sh_subsurface_scattering(void) node_type_init(&ntype, node_shader_init_subsurface_scattering); node_type_storage(&ntype, "", NULL, NULL); node_type_gpu(&ntype, node_shader_gpu_subsurface_scattering); - node_type_update(&ntype, node_shader_update_subsurface_scattering); nodeRegisterType(&ntype); } diff --git a/source/blender/render/CMakeLists.txt b/source/blender/render/CMakeLists.txt index 0046474d064..494415a4077 100644 --- a/source/blender/render/CMakeLists.txt +++ b/source/blender/render/CMakeLists.txt @@ -59,7 +59,6 @@ set(SRC RE_pipeline.h RE_texture.h - intern/initrender.h intern/pipeline.h intern/render_result.h intern/render_types.h diff --git a/source/blender/render/RE_engine.h b/source/blender/render/RE_engine.h index dfc0d5d0e9f..2a3a5964262 100644 --- a/source/blender/render/RE_engine.h +++ b/source/blender/render/RE_engine.h @@ -40,6 +40,7 @@ struct RenderData; struct RenderEngine; struct RenderEngineType; struct RenderLayer; +struct RenderPass; struct RenderResult; struct ReportList; struct Scene; @@ -59,7 +60,7 @@ extern "C" { #define RE_USE_PREVIEW 4 #define RE_USE_POSTPROCESS 8 #define RE_USE_EEVEE_VIEWPORT 16 -#define RE_USE_SAVE_BUFFERS 32 +/* #define RE_USE_SAVE_BUFFERS_DEPRECATED 32 */ #define RE_USE_SHADING_NODES_CUSTOM 64 #define RE_USE_SPHERICAL_STEREO 128 #define RE_USE_STEREO_VIEWPORT 256 @@ -75,6 +76,7 @@ extern "C" { #define RE_ENGINE_DO_UPDATE 8 #define RE_ENGINE_RENDERING 16 #define RE_ENGINE_HIGHLIGHT_TILES 32 +#define RE_ENGINE_CAN_DRAW 64 extern ListBase R_engines; @@ -87,7 +89,20 @@ typedef struct RenderEngineType { int flag; void (*update)(struct RenderEngine *engine, struct Main *bmain, struct Depsgraph *depsgraph); + void (*render)(struct RenderEngine *engine, struct Depsgraph *depsgraph); + + /* Offline rendering is finished - no more view layers will be rendered. + * + * All the pending data is to be communicated from the engine back to Blender. In a possibly + * most memory-efficient manner (engine might free its database before making Blender to allocate + * full-frame render result). */ + void (*render_frame_finish)(struct RenderEngine *engine); + + void (*draw)(struct RenderEngine *engine, + const struct bContext *context, + struct Depsgraph *depsgraph); + void (*bake)(struct RenderEngine *engine, struct Depsgraph *depsgraph, struct Object *object, @@ -132,9 +147,6 @@ typedef struct RenderEngine { struct Object *camera_override; unsigned int layer_override; - int tile_x; - int tile_y; - struct Render *re; ListBase fullresult; char text[512]; /* IMA_MAX_RENDER_TEXT */ @@ -189,6 +201,10 @@ void RE_engine_end_result(RenderEngine *engine, bool merge_results); struct RenderResult *RE_engine_get_result(struct RenderEngine *engine); +struct RenderPass *RE_engine_pass_by_index_get(struct RenderEngine *engine, + const char *layer_name, + int index); + const char *RE_engine_active_view_get(RenderEngine *engine); void RE_engine_active_view_set(RenderEngine *engine, const char *viewname); float RE_engine_get_camera_shift_x(RenderEngine *engine, @@ -228,6 +244,24 @@ void RE_engine_register_pass(struct RenderEngine *engine, bool RE_engine_use_persistent_data(struct RenderEngine *engine); +struct RenderEngine *RE_engine_get(const struct Render *re); + +/* Acquire render engine for drawing via its `draw()` callback. + * + * If drawing is not possible false is returned. If drawing is possible then the engine is + * "acquired" so that it can not be freed by the render pipeline. + * + * Drawing is possible if the engine has the `draw()` callback and it is in its `render()` + * callback. */ +bool RE_engine_draw_acquire(struct Render *re); +void RE_engine_draw_release(struct Render *re); + +/* NOTE: Only used for Cycles's BLenderGPUDisplay integration with the draw manager. A subject + * for re-consideration. Do not use this functionality. */ +bool RE_engine_has_render_context(struct RenderEngine *engine); +void RE_engine_render_context_enable(struct RenderEngine *engine); +void RE_engine_render_context_disable(struct RenderEngine *engine); + /* Engine Types */ void RE_engines_init(void); @@ -252,6 +286,10 @@ void RE_bake_engine_set_engine_parameters(struct Render *re, void RE_engine_free_blender_memory(struct RenderEngine *engine); +void RE_engine_tile_highlight_set( + struct RenderEngine *engine, int x, int y, int width, int height, bool highlight); +void RE_engine_tile_highlight_clear_all(struct RenderEngine *engine); + #ifdef __cplusplus } #endif diff --git a/source/blender/render/RE_pipeline.h b/source/blender/render/RE_pipeline.h index cd839385bfb..3237772dd80 100644 --- a/source/blender/render/RE_pipeline.h +++ b/source/blender/render/RE_pipeline.h @@ -141,9 +141,6 @@ typedef struct RenderResult { volatile rcti renrect; volatile RenderLayer *renlay; - /* optional saved endresult on disk */ - int do_exr_tile; - /* for render results in Image, verify validity for sequences */ int framenr; diff --git a/source/blender/render/intern/bake.c b/source/blender/render/intern/bake.c index 76839651b5d..0f893ce8cd5 100644 --- a/source/blender/render/intern/bake.c +++ b/source/blender/render/intern/bake.c @@ -774,18 +774,6 @@ void RE_bake_pixels_populate(Mesh *me, /* ******************** NORMALS ************************ */ -/** - * convert a normalized normal to the -1.0 1.0 range - * the input is expected to be POS_X, POS_Y, POS_Z - */ -static void normal_uncompress(float out[3], const float in[3]) -{ - int i; - for (i = 0; i < 3; i++) { - out[i] = 2.0f * in[i] - 1.0f; - } -} - static void normal_compress(float out[3], const float in[3], const eBakeNormalSwizzle normal_swizzle[3]) @@ -934,7 +922,7 @@ void RE_bake_normal_world_to_tangent(const BakePixel pixel_array[], copy_v3_v3(tsm[2], normal); /* texture values */ - normal_uncompress(nor, &result[offset]); + copy_v3_v3(nor, &result[offset]); /* converts from world space to local space */ mul_transposed_mat3_m4_v3(mat, nor); @@ -976,7 +964,7 @@ void RE_bake_normal_world_to_object(const BakePixel pixel_array[], } offset = i * depth; - normal_uncompress(nor, &result[offset]); + copy_v3_v3(nor, &result[offset]); /* rotates only without translation */ mul_mat3_m4_v3(iobmat, nor); @@ -1004,7 +992,7 @@ void RE_bake_normal_world_to_world(const BakePixel pixel_array[], } offset = i * depth; - normal_uncompress(nor, &result[offset]); + copy_v3_v3(nor, &result[offset]); /* save back the values */ normal_compress(&result[offset], nor, normal_swizzle); @@ -1053,6 +1041,7 @@ int RE_pass_depth(const eScenePassType pass_type) } case SCE_PASS_COMBINED: case SCE_PASS_SHADOW: + case SCE_PASS_POSITION: case SCE_PASS_NORMAL: case SCE_PASS_VECTOR: case SCE_PASS_INDEXOB: /* XXX double check */ diff --git a/source/blender/render/intern/engine.c b/source/blender/render/intern/engine.c index 5728b784714..389b821ca35 100644 --- a/source/blender/render/intern/engine.c +++ b/source/blender/render/intern/engine.c @@ -62,7 +62,6 @@ #include "DRW_engine.h" -#include "initrender.h" #include "pipeline.h" #include "render_result.h" #include "render_types.h" @@ -283,14 +282,6 @@ static void render_result_to_bake(RenderEngine *engine, RenderResult *rr) /* Render Results */ -static RenderPart *get_part_from_result(Render *re, RenderResult *result) -{ - rcti key = result->tilerect; - BLI_rcti_translate(&key, re->disprect.xmin, re->disprect.ymin); - - return BLI_ghash_lookup(re->parts, &key); -} - static HighlightedTile highlighted_tile_from_result_get(Render *re, RenderResult *result) { HighlightedTile tile; @@ -300,6 +291,37 @@ static HighlightedTile highlighted_tile_from_result_get(Render *re, RenderResult return tile; } +static void engine_tile_highlight_set(RenderEngine *engine, + const HighlightedTile *tile, + bool highlight) +{ + if ((engine->flag & RE_ENGINE_HIGHLIGHT_TILES) == 0) { + return; + } + + Render *re = engine->re; + + BLI_mutex_lock(&re->highlighted_tiles_mutex); + + if (re->highlighted_tiles == NULL) { + re->highlighted_tiles = BLI_gset_new( + BLI_ghashutil_inthash_v4_p, BLI_ghashutil_inthash_v4_cmp, "highlighted tiles"); + } + + if (highlight) { + HighlightedTile **tile_in_set; + if (!BLI_gset_ensure_p_ex(re->highlighted_tiles, tile, (void ***)&tile_in_set)) { + *tile_in_set = MEM_mallocN(sizeof(HighlightedTile), __func__); + **tile_in_set = *tile; + } + } + else { + BLI_gset_remove(re->highlighted_tiles, tile, MEM_freeN); + } + + BLI_mutex_unlock(&re->highlighted_tiles_mutex); +} + RenderResult *RE_engine_begin_result( RenderEngine *engine, int x, int y, int w, int h, const char *layername, const char *viewname) { @@ -332,7 +354,7 @@ RenderResult *RE_engine_begin_result( disprect.ymin = y; disprect.ymax = y + h; - result = render_result_new(re, &disprect, RR_USE_MEM, layername, viewname); + result = render_result_new(re, &disprect, layername, viewname); /* TODO: make this thread safe. */ @@ -341,25 +363,12 @@ RenderResult *RE_engine_begin_result( render_result_clone_passes(re, result, viewname); render_result_passes_allocated_ensure(result); - RenderPart *pa; - - /* Copy EXR tile settings, so pipeline knows whether this is a result - * for Save Buffers enabled rendering. - */ - result->do_exr_tile = re->result->do_exr_tile; - BLI_addtail(&engine->fullresult, result); result->tilerect.xmin += re->disprect.xmin; result->tilerect.xmax += re->disprect.xmin; result->tilerect.ymin += re->disprect.ymin; result->tilerect.ymax += re->disprect.ymin; - - pa = get_part_from_result(re, result); - - if (pa) { - pa->status = PART_STATUS_IN_PROGRESS; - } } return result; @@ -426,53 +435,14 @@ void RE_engine_end_result( re_ensure_passes_allocated_thread_safe(re); - /* merge. on break, don't merge in result for preview renders, looks nicer */ - if (!highlight) { - /* for exr tile render, detect tiles that are done */ - RenderPart *pa = get_part_from_result(re, result); - - if (pa) { - pa->status = (!cancel && merge_results) ? PART_STATUS_MERGED : PART_STATUS_RENDERED; - } - else if (re->result->do_exr_tile) { - /* If written result does not match any tile and we are using save - * buffers, we are going to get OpenEXR save errors. */ - fprintf(stderr, "RenderEngine.end_result: dimensions do not match any OpenEXR tile.\n"); - } - } - if (re->engine && (re->engine->flag & RE_ENGINE_HIGHLIGHT_TILES)) { - BLI_mutex_lock(&re->highlighted_tiles_mutex); - - if (re->highlighted_tiles == NULL) { - re->highlighted_tiles = BLI_gset_new( - BLI_ghashutil_inthash_v4_p, BLI_ghashutil_inthash_v4_cmp, "highlighted tiles"); - } + const HighlightedTile tile = highlighted_tile_from_result_get(re, result); - HighlightedTile tile = highlighted_tile_from_result_get(re, result); - if (highlight) { - void **tile_in_set; - if (!BLI_gset_ensure_p_ex(re->highlighted_tiles, &tile, &tile_in_set)) { - *tile_in_set = MEM_mallocN(sizeof(HighlightedTile), __func__); - memcpy(*tile_in_set, &tile, sizeof(tile)); - } - BLI_gset_add(re->highlighted_tiles, &tile); - } - else { - BLI_gset_remove(re->highlighted_tiles, &tile, MEM_freeN); - } - - BLI_mutex_unlock(&re->highlighted_tiles_mutex); + engine_tile_highlight_set(engine, &tile, highlight); } if (!cancel || merge_results) { - if (re->result->do_exr_tile) { - if (!cancel && merge_results) { - render_result_exr_file_merge(re->result, result, re->viewname); - render_result_merge(re->result, result); - } - } - else if (!(re->test_break(re->tbh) && (re->r.scemode & R_BUTS_PREVIEW))) { + if (!(re->test_break(re->tbh) && (re->r.scemode & R_BUTS_PREVIEW))) { render_result_merge(re->result, result); } @@ -582,6 +552,27 @@ void RE_engine_set_error_message(RenderEngine *engine, const char *msg) } } +RenderPass *RE_engine_pass_by_index_get(RenderEngine *engine, const char *layer_name, int index) +{ + Render *re = engine->re; + if (re == NULL) { + return NULL; + } + + RenderPass *pass = NULL; + + RenderResult *rr = RE_AcquireResultRead(re); + if (rr != NULL) { + const RenderLayer *layer = RE_GetRenderLayer(rr, layer_name); + if (layer != NULL) { + pass = BLI_findlink(&layer->passes, index); + } + } + RE_ReleaseResult(re); + + return pass; +} + const char *RE_engine_active_view_get(RenderEngine *engine) { Render *re = engine->re; @@ -837,12 +828,6 @@ bool RE_bake_engine(Render *re, engine->resolution_x = re->winx; engine->resolution_y = re->winy; - BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE); - RE_parts_init(re); - engine->tile_x = re->r.tilex; - engine->tile_y = re->r.tiley; - BLI_rw_mutex_unlock(&re->partsmutex); - if (type->bake) { engine->depsgraph = depsgraph; @@ -870,21 +855,13 @@ bool RE_bake_engine(Render *re, engine->depsgraph = NULL; } - engine->tile_x = 0; - engine->tile_y = 0; engine->flag &= ~RE_ENGINE_RENDERING; - /* Free depsgraph outside of parts mutex lock, since this locks OpenGL context - * while the UI drawing might also lock the OpenGL context and parts mutex. */ engine_depsgraph_free(engine); - BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE); RE_engine_free(engine); re->engine = NULL; - RE_parts_free(re); - BLI_rw_mutex_unlock(&re->partsmutex); - if (BKE_reports_contain(re->reports, RPT_ERROR)) { G.is_break = true; } @@ -928,15 +905,23 @@ static void engine_render_view_layer(Render *re, DRW_render_context_enable(engine->re); } + BLI_mutex_lock(&engine->re->engine_draw_mutex); + re->engine->flag |= RE_ENGINE_CAN_DRAW; + BLI_mutex_unlock(&engine->re->engine_draw_mutex); + engine->type->render(engine, engine->depsgraph); + BLI_mutex_lock(&engine->re->engine_draw_mutex); + re->engine->flag &= ~RE_ENGINE_CAN_DRAW; + BLI_mutex_unlock(&engine->re->engine_draw_mutex); + if (use_gpu_context) { DRW_render_context_disable(engine->re); } } /* Optionally composite grease pencil over render result. */ - if (engine->has_grease_pencil && use_grease_pencil && !re->result->do_exr_tile) { + if (engine->has_grease_pencil && use_grease_pencil) { /* NOTE: External engine might have been requested to free its * dependency graph, which is only allowed if there is no grease * pencil (pipeline is taking care of that). */ @@ -981,16 +966,11 @@ bool RE_engine_render(Render *re, bool do_all) /* create render result */ BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); if (re->result == NULL || !(re->r.scemode & R_BUTS_PREVIEW)) { - int savebuffers = RR_USE_MEM; - if (re->result) { render_result_free(re->result); } - if ((type->flag & RE_USE_SAVE_BUFFERS) && (re->r.scemode & R_EXR_TILE_FILE)) { - savebuffers = RR_USE_EXR; - } - re->result = render_result_new(re, &re->disprect, savebuffers, RR_ALL_LAYERS, RR_ALL_VIEWS); + re->result = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS); } BLI_rw_mutex_unlock(&re->resultmutex); @@ -1035,32 +1015,15 @@ bool RE_engine_render(Render *re, bool do_all) engine->resolution_x = re->winx; engine->resolution_y = re->winy; - BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE); - RE_parts_init(re); - engine->tile_x = re->partx; - engine->tile_y = re->party; - BLI_rw_mutex_unlock(&re->partsmutex); - - if (re->result->do_exr_tile) { - render_result_exr_file_begin(re, engine); - } - /* Clear UI drawing locks. */ if (re->draw_lock) { re->draw_lock(re->dlh, false); } - /* Render view layers. */ - bool delay_grease_pencil = false; - if (type->render) { FOREACH_VIEW_LAYER_TO_RENDER_BEGIN (re, view_layer_iter) { engine_render_view_layer(re, engine, view_layer_iter, true, true); - /* With save buffers there is no render buffer in memory for compositing, delay - * grease pencil in that case. */ - delay_grease_pencil = engine->has_grease_pencil && re->result->do_exr_tile; - if (RE_engine_test_break(engine)) { break; } @@ -1068,42 +1031,18 @@ bool RE_engine_render(Render *re, bool do_all) FOREACH_VIEW_LAYER_TO_RENDER_END; } + if (type->render_frame_finish) { + type->render_frame_finish(engine); + } + /* Clear tile data */ - engine->tile_x = 0; - engine->tile_y = 0; engine->flag &= ~RE_ENGINE_RENDERING; render_result_free_list(&engine->fullresult, engine->fullresult.first); - BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE); - - /* For save buffers, read back from disk. */ - if (re->result->do_exr_tile) { - render_result_exr_file_end(re, engine); - } - - /* Perform delayed grease pencil rendering. */ - if (delay_grease_pencil) { - BLI_rw_mutex_unlock(&re->partsmutex); - - FOREACH_VIEW_LAYER_TO_RENDER_BEGIN (re, view_layer_iter) { - engine_render_view_layer(re, engine, view_layer_iter, false, true); - if (RE_engine_test_break(engine)) { - break; - } - } - FOREACH_VIEW_LAYER_TO_RENDER_END; - - BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE); - } - /* re->engine becomes zero if user changed active render engine during render */ if (!engine_keep_depsgraph(engine) || !re->engine) { - /* Free depsgraph outside of parts mutex lock, since this locks OpenGL context - * while the UI drawing might also lock the OpenGL context and parts mutex. */ - BLI_rw_mutex_unlock(&re->partsmutex); engine_depsgraph_free(engine); - BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE); RE_engine_free(engine); re->engine = NULL; @@ -1115,9 +1054,6 @@ bool RE_engine_render(Render *re, bool do_all) BLI_rw_mutex_unlock(&re->resultmutex); } - RE_parts_free(re); - BLI_rw_mutex_unlock(&re->partsmutex); - if (BKE_reports_contain(re->reports, RPT_ERROR)) { G.is_break = true; } @@ -1179,3 +1115,81 @@ void RE_engine_free_blender_memory(RenderEngine *engine) } engine_depsgraph_free(engine); } + +struct RenderEngine *RE_engine_get(const Render *re) +{ + return re->engine; +} + +bool RE_engine_draw_acquire(Render *re) +{ + BLI_mutex_lock(&re->engine_draw_mutex); + + RenderEngine *engine = re->engine; + + if (engine == NULL || engine->type->draw == NULL || (engine->flag & RE_ENGINE_CAN_DRAW) == 0) { + BLI_mutex_unlock(&re->engine_draw_mutex); + return false; + } + + return true; +} + +void RE_engine_draw_release(Render *re) +{ + BLI_mutex_unlock(&re->engine_draw_mutex); +} + +void RE_engine_tile_highlight_set( + RenderEngine *engine, int x, int y, int width, int height, bool highlight) +{ + HighlightedTile tile; + BLI_rcti_init(&tile.rect, x, x + width, y, y + height); + + engine_tile_highlight_set(engine, &tile, highlight); +} + +void RE_engine_tile_highlight_clear_all(RenderEngine *engine) +{ + if ((engine->flag & RE_ENGINE_HIGHLIGHT_TILES) == 0) { + return; + } + + Render *re = engine->re; + + BLI_mutex_lock(&re->highlighted_tiles_mutex); + + if (re->highlighted_tiles != NULL) { + BLI_gset_clear(re->highlighted_tiles, MEM_freeN); + } + + BLI_mutex_unlock(&re->highlighted_tiles_mutex); +} + +/* -------------------------------------------------------------------- */ +/** \name OpenGL context manipulation. + * + * NOTE: Only used for Cycles's BLenderGPUDisplay integration with the draw manager. A subject + * for re-consideration. Do not use this functionality. + * \{ */ + +bool RE_engine_has_render_context(RenderEngine *engine) +{ + if (engine->re == NULL) { + return false; + } + + return RE_gl_context_get(engine->re) != NULL; +} + +void RE_engine_render_context_enable(RenderEngine *engine) +{ + DRW_render_context_enable(engine->re); +} + +void RE_engine_render_context_disable(RenderEngine *engine) +{ + DRW_render_context_disable(engine->re); +} + +/** \} */ diff --git a/source/blender/render/intern/initrender.c b/source/blender/render/intern/initrender.c index 3148625c866..2370d8e893b 100644 --- a/source/blender/render/intern/initrender.c +++ b/source/blender/render/intern/initrender.c @@ -43,9 +43,6 @@ #include "pipeline.h" #include "render_types.h" -/* Own includes */ -#include "initrender.h" - /* ****************** MASKS and LUTS **************** */ static float filt_quadratic(float x) @@ -244,91 +241,3 @@ void RE_GetViewPlane(Render *re, rctf *r_viewplane, rcti *r_disprect) BLI_rcti_init(r_disprect, 0, 0, 0, 0); } } - -/* ~~~~~~~~~~~~~~~~ part (tile) calculus ~~~~~~~~~~~~~~~~~~~~~~ */ - -void RE_parts_free(Render *re) -{ - if (re->parts) { - BLI_ghash_free(re->parts, NULL, MEM_freeN); - re->parts = NULL; - } -} - -void RE_parts_clamp(Render *re) -{ - /* part size */ - re->partx = max_ii(1, min_ii(re->r.tilex, re->rectx)); - re->party = max_ii(1, min_ii(re->r.tiley, re->recty)); -} - -void RE_parts_init(Render *re) -{ - int nr, xd, yd, partx, party, xparts, yparts; - int xminb, xmaxb, yminb, ymaxb; - - RE_parts_free(re); - - re->parts = BLI_ghash_new( - BLI_ghashutil_inthash_v4_p, BLI_ghashutil_inthash_v4_cmp, "render parts"); - - /* Just for readable code. */ - xminb = re->disprect.xmin; - yminb = re->disprect.ymin; - xmaxb = re->disprect.xmax; - ymaxb = re->disprect.ymax; - - RE_parts_clamp(re); - - partx = re->partx; - party = re->party; - /* part count */ - xparts = (re->rectx + partx - 1) / partx; - yparts = (re->recty + party - 1) / party; - - for (nr = 0; nr < xparts * yparts; nr++) { - rcti disprect; - int rectx, recty; - - xd = (nr % xparts); - yd = (nr - xd) / xparts; - - disprect.xmin = xminb + xd * partx; - disprect.ymin = yminb + yd * party; - - /* ensure we cover the entire picture, so last parts go to end */ - if (xd < xparts - 1) { - disprect.xmax = disprect.xmin + partx; - if (disprect.xmax > xmaxb) { - disprect.xmax = xmaxb; - } - } - else { - disprect.xmax = xmaxb; - } - - if (yd < yparts - 1) { - disprect.ymax = disprect.ymin + party; - if (disprect.ymax > ymaxb) { - disprect.ymax = ymaxb; - } - } - else { - disprect.ymax = ymaxb; - } - - rectx = BLI_rcti_size_x(&disprect); - recty = BLI_rcti_size_y(&disprect); - - /* so, now can we add this part? */ - if (rectx > 0 && recty > 0) { - RenderPart *pa = MEM_callocN(sizeof(RenderPart), "new part"); - - pa->disprect = disprect; - pa->rectx = rectx; - pa->recty = recty; - - BLI_ghash_insert(re->parts, &pa->disprect, pa); - } - } -} diff --git a/source/blender/render/intern/pipeline.c b/source/blender/render/intern/pipeline.c index 5418f4035b1..72ff920561d 100644 --- a/source/blender/render/intern/pipeline.c +++ b/source/blender/render/intern/pipeline.c @@ -102,7 +102,6 @@ #include "DEG_depsgraph.h" /* internal */ -#include "initrender.h" #include "pipeline.h" #include "render_result.h" #include "render_types.h" @@ -568,7 +567,7 @@ Render *RE_NewRender(const char *name) BLI_addtail(&RenderGlobal.renderlist, re); BLI_strncpy(re->name, name, RE_MAXNAME); BLI_rw_mutex_init(&re->resultmutex); - BLI_rw_mutex_init(&re->partsmutex); + BLI_mutex_init(&re->engine_draw_mutex); BLI_mutex_init(&re->highlighted_tiles_mutex); } @@ -633,7 +632,7 @@ void RE_FreeRender(Render *re) } BLI_rw_mutex_end(&re->resultmutex); - BLI_rw_mutex_end(&re->partsmutex); + BLI_mutex_end(&re->engine_draw_mutex); BLI_mutex_end(&re->highlighted_tiles_mutex); BLI_freelistN(&re->view_layers); @@ -722,26 +721,6 @@ void RE_FreePersistentData(const Scene *scene) /* ********* initialize state ******** */ -/* clear full sample and tile flags if needed */ -static int check_mode_full_sample(RenderData *rd) -{ - int scemode = rd->scemode; - - /* not supported by any current renderer */ - scemode &= ~R_FULL_SAMPLE; - -#ifdef WITH_OPENEXR - if (scemode & R_FULL_SAMPLE) { - scemode |= R_EXR_TILE_FILE; /* enable automatic */ - } -#else - /* can't do this without openexr support */ - scemode &= ~(R_EXR_TILE_FILE | R_FULL_SAMPLE); -#endif - - return scemode; -} - static void re_init_resolution(Render *re, Render *source, int winx, int winy, rcti *disprect) { re->winx = winx; @@ -839,8 +818,6 @@ void RE_InitState(Render *re, return; } - re->r.scemode = check_mode_full_sample(&re->r); - if (single_layer) { int index = BLI_findindex(render_layers, single_layer); if (index != -1) { @@ -890,9 +867,6 @@ void RE_InitState(Render *re, render_result_view_new(re->result, ""); } - /* ensure renderdatabase can use part settings correct */ - RE_parts_clamp(re); - BLI_rw_mutex_unlock(&re->resultmutex); RE_init_threadcount(re); @@ -1040,7 +1014,7 @@ static void render_result_uncrop(Render *re) /* weak is: it chances disprect from border */ render_result_disprect_to_full_resolution(re); - rres = render_result_new(re, &re->disprect, RR_USE_MEM, RR_ALL_LAYERS, RR_ALL_VIEWS); + rres = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS); render_result_passes_allocated_ensure(rres); rres->stamp_data = BKE_stamp_data_copy(re->result->stamp_data); @@ -1227,7 +1201,7 @@ static void do_render_compositor(Render *re) if ((re->r.mode & R_CROP) == 0) { render_result_disprect_to_full_resolution(re); } - re->result = render_result_new(re, &re->disprect, RR_USE_MEM, RR_ALL_LAYERS, RR_ALL_VIEWS); + re->result = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS); BLI_rw_mutex_unlock(&re->resultmutex); @@ -1647,7 +1621,7 @@ bool RE_is_rendering_allowed(Scene *scene, Object *camera_override, ReportList *reports) { - int scemode = check_mode_full_sample(&scene->r); + const int scemode = scene->r.scemode; if (scene->r.mode & R_BORDER) { if (scene->r.border.xmax <= scene->r.border.xmin || @@ -1657,17 +1631,6 @@ bool RE_is_rendering_allowed(Scene *scene, } } - if (scemode & (R_EXR_TILE_FILE | R_FULL_SAMPLE)) { - char str[FILE_MAX]; - - render_result_exr_file_path(scene, "", 0, str); - - if (!BLI_file_is_writable(str)) { - BKE_report(reports, RPT_ERROR, "Cannot save render buffers, check the temp default path"); - return 0; - } - } - if (RE_seq_render_active(scene, &scene->r)) { /* Sequencer */ if (scene->r.mode & R_BORDER) { @@ -1686,13 +1649,6 @@ bool RE_is_rendering_allowed(Scene *scene, BKE_report(reports, RPT_ERROR, "No render output node in scene"); return 0; } - - if (scemode & R_FULL_SAMPLE) { - if (compositor_needs_render(scene, 0) == 0) { - BKE_report(reports, RPT_ERROR, "Full sample AA not supported without 3D rendering"); - return 0; - } - } } else { /* Regular Render */ @@ -1710,14 +1666,6 @@ bool RE_is_rendering_allowed(Scene *scene, return 1; } -static void validate_render_settings(Render *re) -{ - if (RE_engine_is_external(re)) { - /* not supported yet */ - re->r.scemode &= ~R_FULL_SAMPLE; - } -} - static void update_physics_cache(Render *re, Scene *scene, ViewLayer *view_layer, @@ -1820,8 +1768,6 @@ static int render_init_from_main(Render *re, /* initstate makes new result, have to send changed tags around */ ntreeCompositTagRender(re->scene); - validate_render_settings(re); - re->display_init(re->dih, re->result); re->display_clear(re->dch, re->result); diff --git a/source/blender/render/intern/render_result.c b/source/blender/render/intern/render_result.c index 6cb6aabe885..c308147fc5b 100644 --- a/source/blender/render/intern/render_result.c +++ b/source/blender/render/intern/render_result.c @@ -260,8 +260,10 @@ RenderPass *render_layer_add_pass(RenderResult *rr, /* will read info from Render *re to define layers */ /* called in threads */ /* re->winx,winy is coordinate space of entire image, partrct the part within */ -RenderResult *render_result_new( - Render *re, rcti *partrct, int savebuffers, const char *layername, const char *viewname) +RenderResult *render_result_new(Render *re, + rcti *partrct, + const char *layername, + const char *viewname) { RenderResult *rr; RenderLayer *rl; @@ -287,10 +289,6 @@ RenderResult *render_result_new( rr->tilerect.ymin = partrct->ymin - re->disprect.ymin; rr->tilerect.ymax = partrct->ymax - re->disprect.ymin; - if (savebuffers) { - rr->do_exr_tile = true; - } - rr->passes_allocated = false; render_result_views_new(rr, &re->r); @@ -314,10 +312,6 @@ RenderResult *render_result_new( rl->rectx = rectx; rl->recty = recty; - if (rr->do_exr_tile) { - rl->exrhandle = IMB_exr_get_handle(); - } - for (rv = rr->views.first; rv; rv = rv->next) { const char *view = rv->name; @@ -327,10 +321,6 @@ RenderResult *render_result_new( } } - if (rr->do_exr_tile) { - IMB_exr_add_view(rl->exrhandle, view); - } - #define RENDER_LAYER_ADD_PASS_SAFE(rr, rl, channels, name, viewname, chan_id) \ do { \ if (render_layer_add_pass(rr, rl, channels, name, viewname, chan_id) == NULL) { \ @@ -351,6 +341,9 @@ RenderResult *render_result_new( if (view_layer->passflag & SCE_PASS_NORMAL) { RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_NORMAL, view, "XYZ"); } + if (view_layer->passflag & SCE_PASS_POSITION) { + RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_POSITION, view, "XYZ"); + } if (view_layer->passflag & SCE_PASS_UV) { RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_UV, view, "UVA"); } @@ -424,11 +417,6 @@ RenderResult *render_result_new( rl->rectx = rectx; rl->recty = recty; - /* duplicate code... */ - if (rr->do_exr_tile) { - rl->exrhandle = IMB_exr_get_handle(); - } - for (rv = rr->views.first; rv; rv = rv->next) { const char *view = rv->name; @@ -438,10 +426,6 @@ RenderResult *render_result_new( } } - if (rr->do_exr_tile) { - IMB_exr_add_view(rl->exrhandle, view); - } - /* a renderlayer should always have a Combined pass */ render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, view, "RGBA"); } @@ -1089,227 +1073,6 @@ void render_result_single_layer_end(Render *re) re->pushedresult = NULL; } -/************************* EXR Tile File Rendering ***************************/ - -static void save_render_result_tile(RenderResult *rr, RenderResult *rrpart, const char *viewname) -{ - RenderLayer *rlp, *rl; - RenderPass *rpassp; - int partx, party; - - BLI_thread_lock(LOCK_IMAGE); - - for (rlp = rrpart->layers.first; rlp; rlp = rlp->next) { - rl = RE_GetRenderLayer(rr, rlp->name); - - /* should never happen but prevents crash if it does */ - BLI_assert(rl); - if (UNLIKELY(rl == NULL)) { - continue; - } - - /* passes are allocated in sync */ - for (rpassp = rlp->passes.first; rpassp; rpassp = rpassp->next) { - const int xstride = rpassp->channels; - int a; - char fullname[EXR_PASS_MAXNAME]; - - for (a = 0; a < xstride; a++) { - set_pass_full_name(fullname, rpassp->name, a, viewname, rpassp->chan_id); - - IMB_exr_set_channel(rl->exrhandle, - rlp->name, - fullname, - xstride, - xstride * rrpart->rectx, - rpassp->rect + a); - } - } - } - - party = rrpart->tilerect.ymin; - partx = rrpart->tilerect.xmin; - - for (rlp = rrpart->layers.first; rlp; rlp = rlp->next) { - rl = RE_GetRenderLayer(rr, rlp->name); - - /* should never happen but prevents crash if it does */ - BLI_assert(rl); - if (UNLIKELY(rl == NULL)) { - continue; - } - - IMB_exrtile_write_channels(rl->exrhandle, partx, party, 0, viewname, false); - } - - BLI_thread_unlock(LOCK_IMAGE); -} - -void render_result_save_empty_result_tiles(Render *re) -{ - RenderResult *rr; - RenderLayer *rl; - - for (rr = re->result; rr; rr = rr->next) { - for (rl = rr->layers.first; rl; rl = rl->next) { - GHashIterator pa_iter; - GHASH_ITER (pa_iter, re->parts) { - RenderPart *pa = BLI_ghashIterator_getValue(&pa_iter); - if (pa->status != PART_STATUS_MERGED) { - int party = pa->disprect.ymin - re->disprect.ymin; - int partx = pa->disprect.xmin - re->disprect.xmin; - IMB_exrtile_write_channels(rl->exrhandle, partx, party, 0, re->viewname, true); - } - } - } - } -} - -/* Compute list of passes needed by render engine. */ -static void templates_register_pass_cb(void *userdata, - Scene *UNUSED(scene), - ViewLayer *UNUSED(view_layer), - const char *name, - int channels, - const char *chan_id, - eNodeSocketDatatype UNUSED(type)) -{ - ListBase *templates = userdata; - RenderPass *pass = MEM_callocN(sizeof(RenderPass), "RenderPassTemplate"); - - pass->channels = channels; - BLI_strncpy(pass->name, name, sizeof(pass->name)); - BLI_strncpy(pass->chan_id, chan_id, sizeof(pass->chan_id)); - - BLI_addtail(templates, pass); -} - -static void render_result_get_pass_templates(RenderEngine *engine, - Render *re, - RenderLayer *rl, - ListBase *templates) -{ - BLI_listbase_clear(templates); - - if (engine && engine->type->update_render_passes) { - ViewLayer *view_layer = BLI_findstring(&re->view_layers, rl->name, offsetof(ViewLayer, name)); - if (view_layer) { - RE_engine_update_render_passes( - engine, re->scene, view_layer, templates_register_pass_cb, templates); - } - } -} - -/* begin write of exr tile file */ -void render_result_exr_file_begin(Render *re, RenderEngine *engine) -{ - char str[FILE_MAX]; - - for (RenderResult *rr = re->result; rr; rr = rr->next) { - LISTBASE_FOREACH (RenderLayer *, rl, &rr->layers) { - /* Get passes needed by engine. Normally we would wait for the - * engine to create them, but for EXR file we need to know in - * advance. */ - ListBase templates; - render_result_get_pass_templates(engine, re, rl, &templates); - - /* Create render passes requested by engine. Only this part is - * mutex locked to avoid deadlock with Python GIL. */ - BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); - LISTBASE_FOREACH (RenderPass *, pass, &templates) { - RE_create_render_pass( - re->result, pass->name, pass->channels, pass->chan_id, rl->name, NULL); - } - BLI_rw_mutex_unlock(&re->resultmutex); - - BLI_freelistN(&templates); - - /* Open EXR file for writing. */ - render_result_exr_file_path(re->scene, rl->name, rr->sample_nr, str); - printf("write exr tmp file, %dx%d, %s\n", rr->rectx, rr->recty, str); - IMB_exrtile_begin_write(rl->exrhandle, str, 0, rr->rectx, rr->recty, re->partx, re->party); - } - } -} - -/* end write of exr tile file, read back first sample */ -void render_result_exr_file_end(Render *re, RenderEngine *engine) -{ - /* Preserve stamp data. */ - struct StampData *stamp_data = re->result->stamp_data; - re->result->stamp_data = NULL; - - /* Close EXR files. */ - for (RenderResult *rr = re->result; rr; rr = rr->next) { - LISTBASE_FOREACH (RenderLayer *, rl, &rr->layers) { - IMB_exr_close(rl->exrhandle); - rl->exrhandle = NULL; - } - - rr->do_exr_tile = false; - } - - /* Create new render result in memory instead of on disk. */ - BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); - render_result_free_list(&re->fullresult, re->result); - re->result = render_result_new(re, &re->disprect, RR_USE_MEM, RR_ALL_LAYERS, RR_ALL_VIEWS); - re->result->stamp_data = stamp_data; - render_result_passes_allocated_ensure(re->result); - BLI_rw_mutex_unlock(&re->resultmutex); - - LISTBASE_FOREACH (RenderLayer *, rl, &re->result->layers) { - /* Get passes needed by engine. */ - ListBase templates; - render_result_get_pass_templates(engine, re, rl, &templates); - - /* Create render passes requested by engine. Only this part is - * mutex locked to avoid deadlock with Python GIL. */ - BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); - LISTBASE_FOREACH (RenderPass *, pass, &templates) { - RE_create_render_pass(re->result, pass->name, pass->channels, pass->chan_id, rl->name, NULL); - } - - BLI_freelistN(&templates); - - /* Render passes contents from file. */ - char str[FILE_MAXFILE + MAX_ID_NAME + MAX_ID_NAME + 100] = ""; - render_result_exr_file_path(re->scene, rl->name, 0, str); - printf("read exr tmp file: %s\n", str); - - if (!render_result_exr_file_read_path(re->result, rl, str)) { - printf("cannot read: %s\n", str); - } - BLI_rw_mutex_unlock(&re->resultmutex); - } -} - -/* save part into exr file */ -void render_result_exr_file_merge(RenderResult *rr, RenderResult *rrpart, const char *viewname) -{ - for (; rr && rrpart; rr = rr->next, rrpart = rrpart->next) { - save_render_result_tile(rr, rrpart, viewname); - } -} - -/* path to temporary exr file */ -void render_result_exr_file_path(Scene *scene, const char *layname, int sample, char *filepath) -{ - char name[FILE_MAXFILE + MAX_ID_NAME + MAX_ID_NAME + 100]; - const char *fi = BLI_path_basename(BKE_main_blendfile_path_from_global()); - - if (sample == 0) { - BLI_snprintf(name, sizeof(name), "%s_%s_%s.exr", fi, scene->id.name + 2, layname); - } - else { - BLI_snprintf(name, sizeof(name), "%s_%s_%s%d.exr", fi, scene->id.name + 2, layname, sample); - } - - /* Make name safe for paths, see T43275. */ - BLI_filename_make_safe(name); - - BLI_join_dirfile(filepath, FILE_MAX, BKE_tempdir_session(), name); -} - /* called for reading temp files, and for external engines */ int render_result_exr_file_read_path(RenderResult *rr, RenderLayer *rl_single, @@ -1416,7 +1179,7 @@ bool render_result_exr_file_cache_read(Render *re) char *root = U.render_cachedir; RE_FreeRenderResult(re->result); - re->result = render_result_new(re, &re->disprect, RR_USE_MEM, RR_ALL_LAYERS, RR_ALL_VIEWS); + re->result = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS); /* First try cache. */ render_result_exr_file_cache_path(re->scene, root, str); diff --git a/source/blender/render/intern/render_result.h b/source/blender/render/intern/render_result.h index 1fc64a4ea97..4145bb3b8ab 100644 --- a/source/blender/render/intern/render_result.h +++ b/source/blender/render/intern/render_result.h @@ -25,9 +25,6 @@ #define PASS_VECTOR_MAX 10000.0f -#define RR_USE_MEM 0 -#define RR_USE_EXR 1 - #define RR_ALL_LAYERS NULL #define RR_ALL_VIEWS NULL @@ -51,7 +48,6 @@ extern "C" { struct RenderResult *render_result_new(struct Render *re, struct rcti *partrct, - int savebuffers, const char *layername, const char *viewname); @@ -81,12 +77,6 @@ void render_result_free_list(struct ListBase *lb, struct RenderResult *rr); void render_result_single_layer_begin(struct Render *re); void render_result_single_layer_end(struct Render *re); -/* EXR Tile File Render */ - -void render_result_save_empty_result_tiles(struct Render *re); -void render_result_exr_file_begin(struct Render *re, struct RenderEngine *engine); -void render_result_exr_file_end(struct Render *re, struct RenderEngine *engine); - /* render pass wrapper for gpencil */ struct RenderPass *render_layer_add_pass(struct RenderResult *rr, struct RenderLayer *rl, @@ -95,14 +85,6 @@ struct RenderPass *render_layer_add_pass(struct RenderResult *rr, const char *viewname, const char *chan_id); -void render_result_exr_file_merge(struct RenderResult *rr, - struct RenderResult *rrpart, - const char *viewname); - -void render_result_exr_file_path(struct Scene *scene, - const char *layname, - int sample, - char *filepath); int render_result_exr_file_read_path(struct RenderResult *rr, struct RenderLayer *rl_single, const char *filepath); diff --git a/source/blender/render/intern/render_types.h b/source/blender/render/intern/render_types.h index d2d2b499495..ca4f72350e1 100644 --- a/source/blender/render/intern/render_types.h +++ b/source/blender/render/intern/render_types.h @@ -47,30 +47,10 @@ struct ReportList; extern "C" { #endif -/* this is handed over to threaded hiding/passes/shading engine */ -typedef struct RenderPart { - struct RenderPart *next, *prev; - - RenderResult *result; /* result of part rendering */ - ListBase fullresult; /* optional full sample buffers */ - - rcti disprect; /* part coordinates within total picture */ - int rectx, recty; /* the size */ - int nr; /* nr is partnr */ - short status; -} RenderPart; - typedef struct HighlightedTile { rcti rect; } HighlightedTile; -enum { - /* PART_STATUS_NONE = 0, */ /* UNUSED */ - PART_STATUS_IN_PROGRESS = 1, - PART_STATUS_RENDERED = 2, - PART_STATUS_MERGED = 3, -}; - /* controls state of render, everything that's read-only during render stage */ struct Render { struct Render *next, *prev; @@ -91,6 +71,9 @@ struct Render { * to not conflict with writes, so no lock used for that */ ThreadRWMutex resultmutex; + /* Guard for drawing render result using engine's `draw()` callback. */ + ThreadMutex engine_draw_mutex; + /** Window size, display rect, viewplane. * \note Buffer width and height with percentage applied * without border & crop. convert to long before multiplying together to avoid overflow. */ @@ -101,10 +84,6 @@ struct Render { /* final picture width and height (within disprect) */ int rectx, recty; - /* real maximum size of parts after correction for minimum - * partx*xparts can be larger than rectx, in that case last part is smaller */ - int partx, party; - /* Camera transform, only used by Freestyle. */ float winmat[4][4]; @@ -120,9 +99,6 @@ struct Render { int active_view_layer; struct Object *camera_override; - ThreadRWMutex partsmutex; - struct GHash *parts; - ThreadMutex highlighted_tiles_mutex; struct GSet *highlighted_tiles; diff --git a/source/blender/sequencer/SEQ_iterator.h b/source/blender/sequencer/SEQ_iterator.h index 4f7d603fd6a..d2a47a13db3 100644 --- a/source/blender/sequencer/SEQ_iterator.h +++ b/source/blender/sequencer/SEQ_iterator.h @@ -94,11 +94,15 @@ SeqCollection *SEQ_query_by_reference(struct Sequence *seq_reference, SeqCollection *collection)); SeqCollection *SEQ_query_selected_strips(struct ListBase *seqbase); SeqCollection *SEQ_query_unselected_strips(struct ListBase *seqbase); -SeqCollection *SEQ_query_all_strips(struct ListBase *seqbase); -SeqCollection *SEQ_query_all_strips_recursive(struct ListBase *seqbase); +SeqCollection *SEQ_query_all_strips(ListBase *seqbase); +SeqCollection *SEQ_query_all_strips_recursive(ListBase *seqbase); +SeqCollection *SEQ_query_rendered_strips(ListBase *seqbase, + const int timeline_frame, + const int displayed_channel); void SEQ_query_strip_effect_chain(struct Sequence *seq_reference, struct ListBase *seqbase, SeqCollection *collection); +void SEQ_filter_selected_strips(SeqCollection *collection); #ifdef __cplusplus } diff --git a/source/blender/sequencer/SEQ_render.h b/source/blender/sequencer/SEQ_render.h index c138daf1318..e99dc6d344f 100644 --- a/source/blender/sequencer/SEQ_render.h +++ b/source/blender/sequencer/SEQ_render.h @@ -27,6 +27,8 @@ extern "C" { #endif +#define SEQ_RENDER_THUMB_SIZE 256 + struct ListBase; struct Main; struct Scene; @@ -67,6 +69,25 @@ struct ImBuf *SEQ_render_give_ibuf(const SeqRenderData *context, struct ImBuf *SEQ_render_give_ibuf_direct(const SeqRenderData *context, float timeline_frame, struct Sequence *seq); +void SEQ_render_thumbnails(const struct SeqRenderData *context, + struct Sequence *seq, + struct Sequence *seq_orig, + float start_frame, + float frame_step, + rctf *view_area, + const short *stop); +struct ImBuf *SEQ_get_thumbnail(const struct SeqRenderData *context, + struct Sequence *seq, + float timeline_frame, + rcti *crop, + bool clipped); +int SEQ_render_thumbnails_guaranteed_set_frame_step_get(const struct Sequence *seq); +void SEQ_render_thumbnails_base_set(const struct SeqRenderData *context, + struct Sequence *seq, + struct Sequence *seq_orig, + rctf *view_area, + const short *stop); + void SEQ_render_init_colorspace(struct Sequence *seq); void SEQ_render_new_render_data(struct Main *bmain, struct Depsgraph *depsgraph, diff --git a/source/blender/sequencer/SEQ_sequencer.h b/source/blender/sequencer/SEQ_sequencer.h index d7800d208a4..7e733817630 100644 --- a/source/blender/sequencer/SEQ_sequencer.h +++ b/source/blender/sequencer/SEQ_sequencer.h @@ -64,6 +64,7 @@ short SEQ_tool_settings_snap_flag_get(struct Scene *scene); short SEQ_tool_settings_snap_mode_get(struct Scene *scene); int SEQ_tool_settings_snap_distance_get(struct Scene *scene); eSeqOverlapMode SEQ_tool_settings_overlap_mode_get(struct Scene *scene); +int SEQ_tool_settings_pivot_point_get(struct Scene *scene); struct SequencerToolSettings *SEQ_tool_settings_copy(struct SequencerToolSettings *tool_settings); struct Editing *SEQ_editing_get(const struct Scene *scene); struct Editing *SEQ_editing_ensure(struct Scene *scene); diff --git a/source/blender/sequencer/SEQ_transform.h b/source/blender/sequencer/SEQ_transform.h index 1977835f627..328efb9424a 100644 --- a/source/blender/sequencer/SEQ_transform.h +++ b/source/blender/sequencer/SEQ_transform.h @@ -61,6 +61,15 @@ void SEQ_transform_offset_after_frame(struct Scene *scene, const int delta, const int timeline_frame); +/* Image transformation. */ +void SEQ_image_transform_mirror_factor_get(const struct Sequence *seq, float r_mirror[2]); +void SEQ_image_transform_origin_offset_pixelspace_get(const struct Scene *scene, + const struct Sequence *seq, + float r_origin[2]); +void SEQ_image_transform_final_quad_get(const struct Scene *scene, + const struct Sequence *seq, + float r_quad[4][2]); + #ifdef __cplusplus } #endif diff --git a/source/blender/sequencer/SEQ_utils.h b/source/blender/sequencer/SEQ_utils.h index 09de7bc67e9..d30a1b2d7ae 100644 --- a/source/blender/sequencer/SEQ_utils.h +++ b/source/blender/sequencer/SEQ_utils.h @@ -34,6 +34,7 @@ struct Mask; struct Scene; struct Sequence; struct StripElem; +struct SeqRenderData; void SEQ_sort(struct ListBase *seqbase); void SEQ_sequence_base_unique_name_recursive(struct Scene *scene, @@ -57,7 +58,6 @@ void SEQ_set_scale_to_fit(const struct Sequence *seq, const int preview_height, const eSeqImageFitMethod fit_method); void SEQ_ensure_unique_name(struct Sequence *seq, struct Scene *scene); - #ifdef __cplusplus } #endif diff --git a/source/blender/sequencer/intern/image_cache.c b/source/blender/sequencer/intern/image_cache.c index 86bd840ce31..86c198075e9 100644 --- a/source/blender/sequencer/intern/image_cache.c +++ b/source/blender/sequencer/intern/image_cache.c @@ -104,6 +104,7 @@ #define DCACHE_IMAGES_PER_FILE 100 #define DCACHE_CURRENT_VERSION 2 #define COLORSPACE_NAME_MAX 64 /* XXX: defined in imb intern */ +#define THUMB_CACHE_LIMIT 5000 typedef struct DiskCacheHeaderEntry { unsigned char encoding; @@ -148,6 +149,7 @@ typedef struct SeqCache { struct BLI_mempool *items_pool; struct SeqCacheKey *last_key; SeqDiskCache *disk_cache; + int thumbnail_count; } SeqCache; typedef struct SeqCacheItem { @@ -776,7 +778,7 @@ static float seq_cache_timeline_frame_to_frame_index(Sequence *seq, float timeli /* With raw images, map timeline_frame to strip input media frame range. This means that static * images or extended frame range of movies will only generate one cache entry. No special * treatment in converting frame index to timeline_frame is needed. */ - if (type == SEQ_CACHE_STORE_RAW) { + if (type == SEQ_CACHE_STORE_RAW || type == SEQ_CACHE_STORE_THUMBNAIL) { return seq_give_frame_index(seq, timeline_frame); } @@ -875,7 +877,7 @@ static void seq_cache_put_ex(Scene *scene, SeqCacheKey *key, ImBuf *ibuf) if (BLI_ghash_reinsert(cache->hash, key, item, seq_cache_keyfree, seq_cache_valfree)) { IMB_refImBuf(ibuf); - if (!key->is_temp_cache) { + if (!key->is_temp_cache || key->type != SEQ_CACHE_STORE_THUMBNAIL) { cache->last_key = key; } } @@ -1161,6 +1163,7 @@ static void seq_cache_create(Main *bmain, Scene *scene) cache->hash = BLI_ghash_new(seq_cache_hashhash, seq_cache_hashcmp, "SeqCache hash"); cache->last_key = NULL; cache->bmain = bmain; + cache->thumbnail_count = 0; BLI_mutex_init(&cache->iterator_mutex); scene->ed->cache = cache; @@ -1217,7 +1220,7 @@ void seq_cache_free_temp_cache(Scene *scene, short id, int timeline_frame) SeqCacheKey *key = BLI_ghashIterator_getKey(&gh_iter); BLI_ghashIterator_step(&gh_iter); - if (key->is_temp_cache && key->task_id == id) { + if (key->is_temp_cache && key->task_id == id && key->type != SEQ_CACHE_STORE_THUMBNAIL) { /* Use frame_index here to avoid freeing raw images if they are used for multiple frames. */ float frame_index = seq_cache_timeline_frame_to_frame_index( key->seq, timeline_frame, key->type); @@ -1278,6 +1281,7 @@ void SEQ_cache_cleanup(Scene *scene) BLI_ghash_remove(cache->hash, key, seq_cache_keyfree, seq_cache_valfree); } cache->last_key = NULL; + cache->thumbnail_count = 0; seq_cache_unlock(scene); } @@ -1345,6 +1349,46 @@ void seq_cache_cleanup_sequence(Scene *scene, seq_cache_unlock(scene); } +void seq_cache_thumbnail_cleanup(Scene *scene, rctf *view_area_safe) +{ + /* Add offsets to the left and right end to keep some frames in cache. */ + view_area_safe->xmax += 200; + view_area_safe->xmin -= 200; + view_area_safe->ymin -= 1; + view_area_safe->ymax += 1; + + SeqCache *cache = seq_cache_get_from_scene(scene); + if (!cache) { + return; + } + + GHashIterator gh_iter; + BLI_ghashIterator_init(&gh_iter, cache->hash); + while (!BLI_ghashIterator_done(&gh_iter)) { + SeqCacheKey *key = BLI_ghashIterator_getKey(&gh_iter); + BLI_ghashIterator_step(&gh_iter); + + const int frame_index = key->timeline_frame - key->seq->startdisp; + const int frame_step = SEQ_render_thumbnails_guaranteed_set_frame_step_get(key->seq); + const int relative_base_frame = round_fl_to_int((frame_index / (float)frame_step)) * + frame_step; + const int nearest_guaranted_absolute_frame = relative_base_frame + key->seq->startdisp; + + if (nearest_guaranted_absolute_frame == key->timeline_frame) { + continue; + } + + if ((key->type & SEQ_CACHE_STORE_THUMBNAIL) && + (key->timeline_frame > view_area_safe->xmax || + key->timeline_frame < view_area_safe->xmin || key->seq->machine > view_area_safe->ymax || + key->seq->machine < view_area_safe->ymin)) { + BLI_ghash_remove(cache->hash, key, seq_cache_keyfree, seq_cache_valfree); + cache->thumbnail_count--; + } + } + cache->last_key = NULL; +} + struct ImBuf *seq_cache_get(const SeqRenderData *context, Sequence *seq, float timeline_frame, @@ -1436,6 +1480,37 @@ bool seq_cache_put_if_possible( return false; } +void seq_cache_thumbnail_put( + const SeqRenderData *context, Sequence *seq, float timeline_frame, ImBuf *i, rctf *view_area) +{ + Scene *scene = context->scene; + + if (!scene->ed->cache) { + seq_cache_create(context->bmain, scene); + } + + seq_cache_lock(scene); + SeqCache *cache = seq_cache_get_from_scene(scene); + SeqCacheKey *key = seq_cache_allocate_key( + cache, context, seq, timeline_frame, SEQ_CACHE_STORE_THUMBNAIL); + + /* Prevent reinserting, it breaks cache key linking. */ + if (BLI_ghash_haskey(cache->hash, key)) { + seq_cache_unlock(scene); + return; + } + + /* Limit cache to THUMB_CACHE_LIMIT (5000) images stored. */ + if (cache->thumbnail_count >= THUMB_CACHE_LIMIT) { + rctf view_area_safe = *view_area; + seq_cache_thumbnail_cleanup(scene, &view_area_safe); + } + + seq_cache_put_ex(scene, key, i); + cache->thumbnail_count++; + seq_cache_unlock(scene); +} + void seq_cache_put( const SeqRenderData *context, Sequence *seq, float timeline_frame, int type, ImBuf *i) { diff --git a/source/blender/sequencer/intern/image_cache.h b/source/blender/sequencer/intern/image_cache.h index 63c559caee9..60031311985 100644 --- a/source/blender/sequencer/intern/image_cache.h +++ b/source/blender/sequencer/intern/image_cache.h @@ -46,6 +46,11 @@ void seq_cache_put(const struct SeqRenderData *context, float timeline_frame, int type, struct ImBuf *i); +void seq_cache_thumbnail_put(const struct SeqRenderData *context, + struct Sequence *seq, + float timeline_frame, + struct ImBuf *i, + rctf *view_area); bool seq_cache_put_if_possible(const struct SeqRenderData *context, struct Sequence *seq, float timeline_frame, @@ -60,6 +65,7 @@ void seq_cache_cleanup_sequence(struct Scene *scene, struct Sequence *seq_changed, int invalidate_types, bool force_seq_changed_range); +void seq_cache_thumbnail_cleanup(Scene *scene, rctf *view_area); bool seq_cache_is_full(void); #ifdef __cplusplus diff --git a/source/blender/sequencer/intern/iterator.c b/source/blender/sequencer/intern/iterator.c index 58f68205f51..2429405350b 100644 --- a/source/blender/sequencer/intern/iterator.c +++ b/source/blender/sequencer/intern/iterator.c @@ -37,6 +37,8 @@ #include "BKE_scene.h" #include "SEQ_iterator.h" +#include "SEQ_time.h" +#include "render.h" /* -------------------------------------------------------------------- */ /** \Iterator API @@ -340,6 +342,114 @@ SeqCollection *SEQ_query_selected_strips(ListBase *seqbase) return collection; } +static SeqCollection *query_strips_at_frame(ListBase *seqbase, const int timeline_frame) +{ + SeqCollection *collection = SEQ_collection_create(__func__); + + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if (SEQ_time_strip_intersects_frame(seq, timeline_frame)) { + SEQ_collection_append_strip(seq, collection); + } + } + return collection; +} + +static void collection_filter_channel_up_to_incl(SeqCollection *collection, const int channel) +{ + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + if (seq->machine <= channel) { + continue; + } + SEQ_collection_remove_strip(seq, collection); + } +} + +static bool seq_is_effect_of(const Sequence *seq_effect, const Sequence *possibly_input) +{ + if (seq_effect->seq1 == possibly_input || seq_effect->seq2 == possibly_input || + seq_effect->seq3 == possibly_input) { + return true; + } + return false; +} + +/* Check if seq must be rendered. This depends on whole stack in some cases, not only seq itself. + * Order of applying these conditions is important. */ +static bool must_render_strip(const Sequence *seq, SeqCollection *strips_at_timeline_frame) +{ + bool seq_have_effect_in_stack = false; + Sequence *seq_iter; + SEQ_ITERATOR_FOREACH (seq_iter, strips_at_timeline_frame) { + /* Strips is below another strip with replace blending are not rendered. */ + if (seq_iter->blend_mode == SEQ_BLEND_REPLACE && seq->machine < seq_iter->machine) { + return false; + } + + if ((seq_iter->type & SEQ_TYPE_EFFECT) != 0 && seq_is_effect_of(seq_iter, seq)) { + /* Strips in same channel or higher than its effect are rendered. */ + if (seq->machine >= seq_iter->machine) { + return true; + } + /* Mark that this strip has effect in stack, that is above the strip. */ + seq_have_effect_in_stack = true; + } + } + + /* All effects are rendered (with respect to conditions above). */ + if ((seq->type & SEQ_TYPE_EFFECT) != 0) { + return true; + } + + /* If strip has effects in stack, and all effects are above this strip, it is not rendered. */ + if (seq_have_effect_in_stack) { + return false; + } + + return true; +} + +/* Remove strips we don't want to render from collection. */ +static void collection_filter_rendered_strips(SeqCollection *collection) +{ + Sequence *seq; + + /* Remove sound strips and muted strips from collection, because these are not rendered. + * Function #must_render_strip() don't have to check for these strips anymore. */ + SEQ_ITERATOR_FOREACH (seq, collection) { + if (seq->type == SEQ_TYPE_SOUND_RAM || (seq->flag & SEQ_MUTE) != 0) { + SEQ_collection_remove_strip(seq, collection); + } + } + + SEQ_ITERATOR_FOREACH (seq, collection) { + if (must_render_strip(seq, collection)) { + continue; + } + SEQ_collection_remove_strip(seq, collection); + } +} + +/** + * Query strips that are rendered at \a timeline_frame when \a displayed channel is viewed + * + * \param seqbase: ListBase in which strips are queried + * \param timeline_frame: viewed frame + * \param displayed_channel: viewed channel. when set to 0, no channel filter is applied + * \return strip collection + */ +SeqCollection *SEQ_query_rendered_strips(ListBase *seqbase, + const int timeline_frame, + const int displayed_channel) +{ + SeqCollection *collection = query_strips_at_frame(seqbase, timeline_frame); + if (displayed_channel != 0) { + collection_filter_channel_up_to_incl(collection, displayed_channel); + } + collection_filter_rendered_strips(collection); + return collection; +} + /** * Query all unselected strips in seqbase. * @@ -396,3 +506,13 @@ void SEQ_query_strip_effect_chain(Sequence *seq_reference, } } } + +void SEQ_filter_selected_strips(SeqCollection *collection) +{ + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + if ((seq->flag & SELECT) == 0) { + SEQ_collection_remove_strip(seq, collection); + } + } +} diff --git a/source/blender/sequencer/intern/render.c b/source/blender/sequencer/intern/render.c index 6c4502a3608..2578a6d4223 100644 --- a/source/blender/sequencer/intern/render.c +++ b/source/blender/sequencer/intern/render.c @@ -72,6 +72,7 @@ #include "SEQ_render.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" +#include "SEQ_transform.h" #include "SEQ_utils.h" #include "effects.h" @@ -262,94 +263,6 @@ StripElem *SEQ_render_give_stripelem(Sequence *seq, int timeline_frame) return se; } -static bool seq_is_effect_of(const Sequence *seq_effect, const Sequence *possibly_input) -{ - if (seq_effect->seq1 == possibly_input || seq_effect->seq2 == possibly_input || - seq_effect->seq3 == possibly_input) { - return true; - } - return false; -} - -/* Check if seq must be rendered. This depends on whole stack in some cases, not only seq itself. - * Order of applying these conditions is important. */ -static bool must_render_strip(const Sequence *seq, SeqCollection *strips_at_timeline_frame) -{ - bool seq_have_effect_in_stack = false; - Sequence *seq_iter; - SEQ_ITERATOR_FOREACH (seq_iter, strips_at_timeline_frame) { - /* Strips is below another strip with replace blending are not rendered. */ - if (seq_iter->blend_mode == SEQ_BLEND_REPLACE && seq->machine < seq_iter->machine) { - return false; - } - - if ((seq_iter->type & SEQ_TYPE_EFFECT) != 0 && seq_is_effect_of(seq_iter, seq)) { - /* Strips in same channel or higher than its effect are rendered. */ - if (seq->machine >= seq_iter->machine) { - return true; - } - /* Mark that this strip has effect in stack, that is above the strip. */ - seq_have_effect_in_stack = true; - } - } - - /* All effects are rendered (with respect to conditions above). */ - if ((seq->type & SEQ_TYPE_EFFECT) != 0) { - return true; - } - - /* If strip has effects in stack, and all effects are above this strip, it is not rendered. */ - if (seq_have_effect_in_stack) { - return false; - } - - return true; -} - -static SeqCollection *query_strips_at_frame(ListBase *seqbase, const int timeline_frame) -{ - SeqCollection *collection = SEQ_collection_create(__func__); - - LISTBASE_FOREACH (Sequence *, seq, seqbase) { - if (SEQ_time_strip_intersects_frame(seq, timeline_frame)) { - SEQ_collection_append_strip(seq, collection); - } - } - return collection; -} - -static void collection_filter_channel_up_to_incl(SeqCollection *collection, const int channel) -{ - Sequence *seq; - SEQ_ITERATOR_FOREACH (seq, collection) { - if (seq->machine <= channel) { - continue; - } - SEQ_collection_remove_strip(seq, collection); - } -} - -/* Remove strips we don't want to render from collection. */ -static void collection_filter_rendered_strips(SeqCollection *collection) -{ - Sequence *seq; - - /* Remove sound strips and muted strips from collection, because these are not rendered. - * Function #must_render_strip() don't have to check for these strips anymore. */ - SEQ_ITERATOR_FOREACH (seq, collection) { - if (seq->type == SEQ_TYPE_SOUND_RAM || (seq->flag & SEQ_MUTE) != 0) { - SEQ_collection_remove_strip(seq, collection); - } - } - - SEQ_ITERATOR_FOREACH (seq, collection) { - if (must_render_strip(seq, collection)) { - continue; - } - SEQ_collection_remove_strip(seq, collection); - } -} - static int seq_channel_cmp_fn(const void *a, const void *b) { return (*(Sequence **)a)->machine - (*(Sequence **)b)->machine; @@ -360,13 +273,7 @@ int seq_get_shown_sequences(ListBase *seqbase, const int chanshown, Sequence **r_seq_arr) { - SeqCollection *collection = query_strips_at_frame(seqbase, timeline_frame); - - if (chanshown != 0) { - collection_filter_channel_up_to_incl(collection, chanshown); - } - collection_filter_rendered_strips(collection); - + SeqCollection *collection = SEQ_query_rendered_strips(seqbase, timeline_frame, chanshown); const int strip_count = BLI_gset_len(collection->set); if (strip_count > MAXSEQ) { @@ -504,7 +411,7 @@ static void sequencer_image_crop_transform_matrix(const Sequence *seq, const float image_center_offs_y = (out->y - in->y) / 2; const float translate_x = transform->xofs * preview_scale_factor + image_center_offs_x; const float translate_y = transform->yofs * preview_scale_factor + image_center_offs_y; - const float pivot[2] = {in->x / 2, in->y / 2}; + const float pivot[2] = {in->x * transform->origin[0], in->y * transform->origin[1]}; loc_rot_size_to_mat3(r_transform_matrix, (const float[]){translate_x, translate_y}, transform->rotation, @@ -527,6 +434,31 @@ static void sequencer_image_crop_init(const Sequence *seq, BLI_rctf_init(r_crop, left, in->x - right, bottom, in->y - top); } +static void sequencer_thumbnail_transform(ImBuf *in, ImBuf *out) +{ + float image_scale_factor = (float)out->x / in->x; + float transform_matrix[3][3]; + + /* Set to keep same loc,scale,rot but change scale to thumb size limit. */ + const float scale_x = 1 * image_scale_factor; + const float scale_y = 1 * image_scale_factor; + const float image_center_offs_x = (out->x - in->x) / 2; + const float image_center_offs_y = (out->y - in->y) / 2; + const float pivot[2] = {in->x / 2, in->y / 2}; + loc_rot_size_to_mat3(transform_matrix, + (const float[]){image_center_offs_x, image_center_offs_y}, + 0, + (const float[]){scale_x, scale_y}); + transform_pivot_set_m3(transform_matrix, pivot); + invert_m3(transform_matrix); + + /* No crop. */ + rctf source_crop; + BLI_rctf_init(&source_crop, 0, in->x, 0, in->y); + + IMB_transform(in, out, transform_matrix, &source_crop, IMB_FILTER_NEAREST); +} + static void sequencer_preprocess_transform_crop( ImBuf *in, ImBuf *out, const SeqRenderData *context, Sequence *seq, const bool is_proxy_image) { @@ -1989,7 +1921,164 @@ ImBuf *SEQ_render_give_ibuf_direct(const SeqRenderData *context, seq_render_state_init(&state); ImBuf *ibuf = seq_render_strip(context, &state, seq, timeline_frame); - return ibuf; } + +/* Gets the direct image from source and scales to thumbnail size. */ +static ImBuf *seq_get_uncached_thumbnail(const SeqRenderData *context, + SeqRenderState *state, + Sequence *seq, + float timeline_frame) +{ + bool is_proxy_image = false; + ImBuf *ibuf = do_render_strip_uncached(context, state, seq, timeline_frame, &is_proxy_image); + + if (ibuf == NULL) { + return NULL; + } + + float aspect_ratio = (float)ibuf->x / ibuf->y; + int rectx, recty; + /* Calculate new dimensions - THUMB_SIZE (256) for x or y. */ + if (ibuf->x > ibuf->y) { + rectx = SEQ_RENDER_THUMB_SIZE; + recty = round_fl_to_int(rectx / aspect_ratio); + } + else { + recty = SEQ_RENDER_THUMB_SIZE; + rectx = round_fl_to_int(recty * aspect_ratio); + } + + /* Scale ibuf to thumbnail size. */ + ImBuf *scaled_ibuf = IMB_allocImBuf(rectx, recty, 32, ibuf->rect_float ? IB_rectfloat : IB_rect); + sequencer_thumbnail_transform(ibuf, scaled_ibuf); + seq_imbuf_assign_spaces(context->scene, scaled_ibuf); + IMB_freeImBuf(ibuf); + + return scaled_ibuf; +} + +/* Get cached thumbnails. */ +ImBuf *SEQ_get_thumbnail( + const SeqRenderData *context, Sequence *seq, float timeline_frame, rcti *crop, bool clipped) +{ + ImBuf *ibuf = seq_cache_get(context, seq, roundf(timeline_frame), SEQ_CACHE_STORE_THUMBNAIL); + + if (!clipped || ibuf == NULL) { + return ibuf; + } + + /* Do clipping. */ + ImBuf *ibuf_cropped = IMB_dupImBuf(ibuf); + if (crop->xmin < 0 || crop->ymin < 0) { + crop->xmin = 0; + crop->ymin = 0; + } + if (crop->xmax >= ibuf->x || crop->ymax >= ibuf->y) { + crop->xmax = ibuf->x - 1; + crop->ymax = ibuf->y - 1; + } + IMB_rect_crop(ibuf_cropped, crop); + IMB_freeImBuf(ibuf); + return ibuf_cropped; +} + +/* Render the series of thumbnails and store in cache. */ +void SEQ_render_thumbnails(const SeqRenderData *context, + Sequence *seq, + Sequence *seq_orig, + float start_frame, + float frame_step, + rctf *view_area, + const short *stop) +{ + SeqRenderState state; + seq_render_state_init(&state); + + /* Adding the hold offset value (seq->anim_startofs) to the start frame. Position of image not + * affected, but frame loaded affected. */ + start_frame = start_frame - frame_step; + float upper_thumb_bound = (seq->endstill) ? (seq->start + seq->len) : seq->enddisp; + upper_thumb_bound = (upper_thumb_bound > view_area->xmax) ? view_area->xmax + frame_step : + upper_thumb_bound; + + while ((start_frame < upper_thumb_bound) & !*stop) { + ImBuf *ibuf = seq_cache_get( + context, seq_orig, round_fl_to_int(start_frame), SEQ_CACHE_STORE_THUMBNAIL); + if (ibuf) { + IMB_freeImBuf(ibuf); + start_frame += frame_step; + continue; + } + + ibuf = seq_get_uncached_thumbnail(context, &state, seq, round_fl_to_int(start_frame)); + + if (ibuf) { + seq_cache_thumbnail_put(context, seq_orig, round_fl_to_int(start_frame), ibuf, view_area); + IMB_freeImBuf(ibuf); + seq_orig->flag &= ~SEQ_FLAG_SKIP_THUMBNAILS; + } + else { + /* Can not open source file. */ + seq_orig->flag |= SEQ_FLAG_SKIP_THUMBNAILS; + return; + } + + start_frame += frame_step; + } +} + +/* Get frame step for equally spaced thumbnails. These thumbnails should always be present in + * memory, so they can be used when zooming.*/ +int SEQ_render_thumbnails_guaranteed_set_frame_step_get(const Sequence *seq) +{ + const int content_len = (seq->enddisp - seq->startdisp - seq->startstill - seq->endstill); + + /* Arbitrary, but due to performance reasons should be as low as possible. */ + const int thumbnails_base_set_count = min_ii(content_len / 100, 30); + if (thumbnails_base_set_count <= 0) { + return 0; + } + return content_len / thumbnails_base_set_count; +} + +/* Render set of evenly spaced thumbnails that are drawn when zooming. */ +void SEQ_render_thumbnails_base_set( + const SeqRenderData *context, Sequence *seq, Sequence *seq_orig, rctf *view_area, const short *stop) +{ + SeqRenderState state; + seq_render_state_init(&state); + + int timeline_frame = seq->startdisp; + const int frame_step = SEQ_render_thumbnails_guaranteed_set_frame_step_get(seq); + + while (timeline_frame < seq->enddisp && !*stop) { + ImBuf *ibuf = seq_cache_get( + context, seq_orig, roundf(timeline_frame), SEQ_CACHE_STORE_THUMBNAIL); + if (ibuf) { + IMB_freeImBuf(ibuf); + + if (frame_step == 0) { + return; + } + + timeline_frame += frame_step; + continue; + } + + ibuf = seq_get_uncached_thumbnail(context, &state, seq, timeline_frame); + + if (ibuf) { + seq_cache_thumbnail_put(context, seq_orig, timeline_frame, ibuf, view_area); + IMB_freeImBuf(ibuf); + } + + if (frame_step == 0) { + return; + } + + timeline_frame += frame_step; + } +} + /** \} */ diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c index bf5942090c9..382bd51aae1 100644 --- a/source/blender/sequencer/intern/sequencer.c +++ b/source/blender/sequencer/intern/sequencer.c @@ -79,6 +79,8 @@ static Strip *seq_strip_alloc(int type) strip->transform = MEM_callocN(sizeof(struct StripTransform), "StripTransform"); strip->transform->scale_x = 1; strip->transform->scale_y = 1; + strip->transform->origin[0] = 0.5f; + strip->transform->origin[1] = 0.5f; strip->crop = MEM_callocN(sizeof(struct StripCrop), "StripCrop"); } @@ -321,6 +323,7 @@ SequencerToolSettings *SEQ_tool_settings_init(void) SEQ_SNAP_TO_STRIP_HOLD; tool_settings->snap_distance = 15; tool_settings->overlap_mode = SEQ_OVERLAP_SHUFFLE; + tool_settings->pivot_point = V3D_AROUND_LOCAL_ORIGINS; return tool_settings; } @@ -377,6 +380,12 @@ eSeqOverlapMode SEQ_tool_settings_overlap_mode_get(Scene *scene) return tool_settings->overlap_mode; } +int SEQ_tool_settings_pivot_point_get(Scene *scene) +{ + const SequencerToolSettings *tool_settings = SEQ_tool_settings_ensure(scene); + return tool_settings->pivot_point; +} + /** * Get seqbase that is being viewed currently. This can be main seqbase or meta strip seqbase * @@ -948,6 +957,8 @@ static bool seq_read_lib_cb(Sequence *seq, void *user_data) BLI_listbase_clear(&seq->anims); SEQ_modifier_blend_read_lib(reader, sce, &seq->modifiers); + + seq->flag &= ~SEQ_FLAG_SKIP_THUMBNAILS; return true; } diff --git a/source/blender/sequencer/intern/strip_transform.c b/source/blender/sequencer/intern/strip_transform.c index 3a5f93a72b0..d5ff455c694 100644 --- a/source/blender/sequencer/intern/strip_transform.c +++ b/source/blender/sequencer/intern/strip_transform.c @@ -421,3 +421,101 @@ void SEQ_transform_offset_after_frame(Scene *scene, } } } + +void SEQ_image_transform_mirror_factor_get(const Sequence *seq, float r_mirror[2]) +{ + r_mirror[0] = 1.0f; + r_mirror[1] = 1.0f; + + if ((seq->flag & SEQ_FLIPX) != 0) { + r_mirror[0] = -1.0f; + } + if ((seq->flag & SEQ_FLIPY) != 0) { + r_mirror[1] = -1.0f; + } +} + +/** + * Get strip transform origin offset from image center + * Note: This function does not apply axis mirror. + * + * \param scene: Scene in which strips are located + * \param seq: Sequence to calculate image transform origin + * \param r_origin: return value + */ +void SEQ_image_transform_origin_offset_pixelspace_get(const Scene *scene, + const Sequence *seq, + float r_origin[2]) +{ + float image_size[2]; + StripElem *strip_elem = seq->strip->stripdata; + if (strip_elem == NULL) { + image_size[0] = scene->r.xsch; + image_size[1] = scene->r.ysch; + } + else { + image_size[0] = strip_elem->orig_width; + image_size[1] = strip_elem->orig_height; + } + + const StripTransform *transform = seq->strip->transform; + r_origin[0] = (image_size[0] * transform->origin[0]) - (image_size[0] * 0.5f) + transform->xofs; + r_origin[1] = (image_size[1] * transform->origin[1]) - (image_size[1] * 0.5f) + transform->yofs; + + float mirror[2]; + SEQ_image_transform_mirror_factor_get(seq, mirror); + mul_v2_v2(r_origin, mirror); +} + +/** + * Get strip transform origin offset from image center + * + * \param scene: Scene in which strips are located + * \param seq: Sequence to calculate image transform origin + * \param r_origin: return value + */ + +void SEQ_image_transform_final_quad_get(const Scene *scene, + const Sequence *seq, + float r_quad[4][2]) +{ + StripTransform *transform = seq->strip->transform; + StripCrop *crop = seq->strip->crop; + + int imgage_size[2] = {scene->r.xsch, scene->r.ysch}; + if (ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_IMAGE)) { + imgage_size[0] = seq->strip->stripdata->orig_width; + imgage_size[1] = seq->strip->stripdata->orig_height; + } + + float transform_matrix[3][3]; + loc_rot_size_to_mat3(transform_matrix, + (const float[]){transform->xofs, transform->yofs}, + transform->rotation, + (const float[]){transform->scale_x, transform->scale_y}); + const float origin[2] = {imgage_size[0] * transform->origin[0], + imgage_size[1] * transform->origin[1]}; + const float pivot[2] = {origin[0] - (imgage_size[0] / 2), origin[1] - (imgage_size[1] / 2)}; + transform_pivot_set_m3(transform_matrix, pivot); + + r_quad[0][0] = (imgage_size[0] / 2) - crop->right; + r_quad[0][1] = (imgage_size[1] / 2) - crop->top; + r_quad[1][0] = (imgage_size[0] / 2) - crop->right; + r_quad[1][1] = (-imgage_size[1] / 2) + crop->bottom; + r_quad[2][0] = (-imgage_size[0] / 2) + crop->left; + r_quad[2][1] = (-imgage_size[1] / 2) + crop->bottom; + r_quad[3][0] = (-imgage_size[0] / 2) + crop->left; + r_quad[3][1] = (imgage_size[1] / 2) - crop->top; + + mul_m3_v2(transform_matrix, r_quad[0]); + mul_m3_v2(transform_matrix, r_quad[1]); + mul_m3_v2(transform_matrix, r_quad[2]); + mul_m3_v2(transform_matrix, r_quad[3]); + + float mirror[2]; + SEQ_image_transform_mirror_factor_get(seq, mirror); + mul_v2_v2(r_quad[0], mirror); + mul_v2_v2(r_quad[1], mirror); + mul_v2_v2(r_quad[2], mirror); + mul_v2_v2(r_quad[3], mirror); +} diff --git a/source/blender/sequencer/intern/utils.c b/source/blender/sequencer/intern/utils.c index 1d3e7e4a223..8421aab5217 100644 --- a/source/blender/sequencer/intern/utils.c +++ b/source/blender/sequencer/intern/utils.c @@ -42,6 +42,7 @@ #include "SEQ_edit.h" #include "SEQ_iterator.h" #include "SEQ_relations.h" +#include "SEQ_render.h" #include "SEQ_select.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 189a231616e..6794b1f4091 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -262,14 +262,21 @@ struct wmEventHandler_Keymap *WM_event_add_keymap_handler_priority(ListBase *han wmKeyMap *keymap, int priority); -typedef struct wmKeyMap *(wmEventHandler_KeymapDynamicFn)(wmWindowManager *wm, - struct wmEventHandler_Keymap *handler) - ATTR_WARN_UNUSED_RESULT; - -struct wmKeyMap *WM_event_get_keymap_from_toolsystem_fallback( - struct wmWindowManager *wm, struct wmEventHandler_Keymap *handler); -struct wmKeyMap *WM_event_get_keymap_from_toolsystem(struct wmWindowManager *wm, - struct wmEventHandler_Keymap *handler); +typedef struct wmEventHandler_KeymapResult { + wmKeyMap *keymaps[3]; + int keymaps_len; +} wmEventHandler_KeymapResult; + +typedef void(wmEventHandler_KeymapDynamicFn)(wmWindowManager *wm, + struct wmEventHandler_Keymap *handler, + struct wmEventHandler_KeymapResult *km_result); + +void WM_event_get_keymap_from_toolsystem_fallback(struct wmWindowManager *wm, + struct wmEventHandler_Keymap *handler, + wmEventHandler_KeymapResult *km_result); +void WM_event_get_keymap_from_toolsystem(struct wmWindowManager *wm, + struct wmEventHandler_Keymap *handler, + wmEventHandler_KeymapResult *km_result); struct wmEventHandler_Keymap *WM_event_add_keymap_handler_dynamic( ListBase *handlers, wmEventHandler_KeymapDynamicFn *keymap_fn, void *user_data); @@ -281,8 +288,9 @@ void WM_event_set_keymap_handler_post_callback(struct wmEventHandler_Keymap *han wmKeyMapItem *kmi, void *user_data), void *user_data); -wmKeyMap *WM_event_get_keymap_from_handler(wmWindowManager *wm, - struct wmEventHandler_Keymap *handler); +void WM_event_get_keymaps_from_handler(wmWindowManager *wm, + struct wmEventHandler_Keymap *handler, + struct wmEventHandler_KeymapResult *km_result); wmKeyMapItem *WM_event_match_keymap_item(struct bContext *C, wmKeyMap *keymap, @@ -707,6 +715,8 @@ void WM_event_fileselect_event(struct wmWindowManager *wm, void *ophandle, int e void WM_operator_region_active_win_set(struct bContext *C); +int WM_operator_flag_only_pass_through_on_press(int retval, const struct wmEvent *event); + /* drag and drop */ struct wmDrag *WM_event_start_drag( struct bContext *C, int icon, int type, void *poin, double value, unsigned int flags); @@ -787,6 +797,7 @@ enum { WM_JOB_TYPE_QUADRIFLOW_REMESH, WM_JOB_TYPE_TRACE_IMAGE, WM_JOB_TYPE_LINEART, + WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL, /* add as needed, bake, seq proxy build * if having hard coded values is a problem */ }; diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c index 213a3c2e342..22bdf65a169 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c @@ -265,6 +265,8 @@ void WM_gizmogroup_ensure_init(const bContext *C, wmGizmoGroup *gzgroup) { /* prepare for first draw */ if (UNLIKELY((gzgroup->init_flag & WM_GIZMOGROUP_INIT_SETUP) == 0)) { + + gzgroup->use_fallback_keymap = true; gzgroup->type->setup(C, gzgroup); /* Not ideal, initialize keymap here, needed for RNA runtime generated gizmos. */ diff --git a/source/blender/windowmanager/intern/wm_dragdrop.c b/source/blender/windowmanager/intern/wm_dragdrop.c index 76bb93b681c..6585349c83c 100644 --- a/source/blender/windowmanager/intern/wm_dragdrop.c +++ b/source/blender/windowmanager/intern/wm_dragdrop.c @@ -34,6 +34,7 @@ #include "BLT_translation.h" #include "BLI_blenlib.h" +#include "BLI_math_color.h" #include "BIF_glutil.h" @@ -50,6 +51,7 @@ #include "UI_interface.h" #include "UI_interface_icons.h" +#include "UI_resources.h" #include "RNA_access.h" @@ -426,7 +428,7 @@ ID *WM_drag_get_local_ID_or_import_from_asset(const wmDrag *drag, int idcode) } /** - * \brief Free asset ID imported for cancelled drop. + * \brief Free asset ID imported for canceled drop. * * If the asset was imported (linked/appended) using #WM_drag_get_local_ID_or_import_from_asset()` * (typically via a #wmDropBox.copy() callback), we want the ID to be removed again if the drop @@ -463,8 +465,14 @@ void WM_drag_free_imported_drag_ID(struct Main *bmain, wmDrag *drag, wmDropBox * static void wm_drop_operator_draw(const char *name, int x, int y) { const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - const float col_fg[4] = {1.0f, 1.0f, 1.0f, 1.0f}; - const float col_bg[4] = {0.0f, 0.0f, 0.0f, 0.2f}; + + /* Use the theme settings from tooltips. */ + const bTheme *btheme = UI_GetTheme(); + const uiWidgetColors *wcol = &btheme->tui.wcol_tooltip; + + float col_fg[4], col_bg[4]; + rgba_uchar_to_float(col_fg, wcol->text); + rgba_uchar_to_float(col_bg, wcol->inner); UI_fontstyle_draw_simple_backdrop(fstyle, x, y, name, col_fg, col_bg); } diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index ae09786356a..14fcc1d69cc 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -2989,9 +2989,18 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers /* Handle all types here. */ if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) { wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base; - wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler); - action |= wm_handlers_do_keymap_with_keymap_handler( - C, event, handlers, handler, keymap, do_debug_handler); + wmEventHandler_KeymapResult km_result; + WM_event_get_keymaps_from_handler(wm, handler, &km_result); + int action_iter = WM_HANDLER_CONTINUE; + for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) { + wmKeyMap *keymap = km_result.keymaps[km_index]; + action_iter |= wm_handlers_do_keymap_with_keymap_handler( + C, event, handlers, handler, keymap, do_debug_handler); + if (action_iter & WM_HANDLER_BREAK) { + break; + } + } + action |= action_iter; /* Clear the tool-tip whenever a key binding is handled, without this tool-tips * are kept when a modal operators starts (annoying but otherwise harmless). */ @@ -3905,17 +3914,34 @@ wmEventHandler_Keymap *WM_event_add_keymap_handler(ListBase *handlers, wmKeyMap * * Follow #wmEventHandler_KeymapDynamicFn signature. */ -wmKeyMap *WM_event_get_keymap_from_toolsystem_fallback(wmWindowManager *wm, - wmEventHandler_Keymap *handler) +void WM_event_get_keymap_from_toolsystem_fallback(wmWindowManager *wm, + wmEventHandler_Keymap *handler, + wmEventHandler_KeymapResult *km_result) { + memset(km_result, 0x0, sizeof(*km_result)); + + const char *keymap_id_list[ARRAY_SIZE(km_result->keymaps)]; + int keymap_id_list_len = 0; + ScrArea *area = handler->dynamic.user_data; handler->keymap_tool = NULL; bToolRef_Runtime *tref_rt = area->runtime.tool ? area->runtime.tool->runtime : NULL; - if (tref_rt && tref_rt->keymap_fallback[0]) { - const char *keymap_id = NULL; + if (tref_rt && tref_rt->keymap[0]) { + keymap_id_list[keymap_id_list_len++] = tref_rt->keymap; + } + + bool is_gizmo_visible = false; + bool is_gizmo_highlight = false; + + if (tref_rt && tref_rt->keymap_fallback[0]) { + bool add_keymap = false; /* Support for the gizmo owning the tool keymap. */ - if (tref_rt->gizmo_group[0] != '\0' && tref_rt->keymap_fallback[0] != '\0') { + + if (tref_rt->flag & TOOLREF_FLAG_FALLBACK_KEYMAP) { + add_keymap = true; + } + if (tref_rt->gizmo_group[0] != '\0') { wmGizmoMap *gzmap = NULL; wmGizmoGroup *gzgroup = NULL; LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { @@ -3931,32 +3957,49 @@ wmKeyMap *WM_event_get_keymap_from_toolsystem_fallback(wmWindowManager *wm, if (gzgroup->type->flag & WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP) { /* If all are hidden, don't override. */ if (gzgroup->use_fallback_keymap) { + is_gizmo_visible = true; wmGizmo *highlight = wm_gizmomap_highlight_get(gzmap); - if (highlight == NULL) { - keymap_id = tref_rt->keymap_fallback; + if (highlight) { + is_gizmo_highlight = true; } + add_keymap = true; } } } } + if (add_keymap) { + keymap_id_list[keymap_id_list_len++] = tref_rt->keymap_fallback; + } + } - if (keymap_id && keymap_id[0]) { - wmKeyMap *km = WM_keymap_list_find_spaceid_or_empty( - &wm->userconf->keymaps, keymap_id, area->spacetype, RGN_TYPE_WINDOW); - /* We shouldn't use keymaps from unrelated spaces. */ - if (km != NULL) { - handler->keymap_tool = area->runtime.tool; - return km; - } - printf( - "Keymap: '%s' not found for tool '%s'\n", tref_rt->keymap, area->runtime.tool->idname); + if (is_gizmo_visible && !is_gizmo_highlight) { + if (keymap_id_list_len == 2) { + SWAP(const char *, keymap_id_list[0], keymap_id_list[1]); } } - return NULL; + + for (int i = 0; i < keymap_id_list_len; i++) { + const char *keymap_id = keymap_id_list[i]; + BLI_assert(keymap_id && keymap_id[0]); + + wmKeyMap *km = WM_keymap_list_find_spaceid_or_empty( + &wm->userconf->keymaps, keymap_id, area->spacetype, RGN_TYPE_WINDOW); + /* We shouldn't use keymaps from unrelated spaces. */ + if (km == NULL) { + printf("Keymap: '%s' not found for tool '%s'\n", keymap_id, area->runtime.tool->idname); + continue; + } + handler->keymap_tool = area->runtime.tool; + km_result->keymaps[km_result->keymaps_len++] = km; + } } -wmKeyMap *WM_event_get_keymap_from_toolsystem(wmWindowManager *wm, wmEventHandler_Keymap *handler) +void WM_event_get_keymap_from_toolsystem(wmWindowManager *wm, + wmEventHandler_Keymap *handler, + wmEventHandler_KeymapResult *km_result) { + memset(km_result, 0x0, sizeof(*km_result)); + ScrArea *area = handler->dynamic.user_data; handler->keymap_tool = NULL; bToolRef_Runtime *tref_rt = area->runtime.tool ? area->runtime.tool->runtime : NULL; @@ -3968,13 +4011,14 @@ wmKeyMap *WM_event_get_keymap_from_toolsystem(wmWindowManager *wm, wmEventHandle /* We shouldn't use keymaps from unrelated spaces. */ if (km != NULL) { handler->keymap_tool = area->runtime.tool; - return km; + km_result->keymaps[km_result->keymaps_len++] = km; + } + else { + printf( + "Keymap: '%s' not found for tool '%s'\n", tref_rt->keymap, area->runtime.tool->idname); } - printf( - "Keymap: '%s' not found for tool '%s'\n", tref_rt->keymap, area->runtime.tool->idname); } } - return NULL; } struct wmEventHandler_Keymap *WM_event_add_keymap_handler_dynamic( @@ -5088,18 +5132,22 @@ void WM_set_locked_interface(wmWindowManager *wm, bool lock) /** \name Event / Keymap Matching API * \{ */ -wmKeyMap *WM_event_get_keymap_from_handler(wmWindowManager *wm, wmEventHandler_Keymap *handler) +void WM_event_get_keymaps_from_handler(wmWindowManager *wm, + wmEventHandler_Keymap *handler, + wmEventHandler_KeymapResult *km_result) { - wmKeyMap *keymap; if (handler->dynamic.keymap_fn != NULL) { - keymap = handler->dynamic.keymap_fn(wm, handler); + handler->dynamic.keymap_fn(wm, handler, km_result); BLI_assert(handler->keymap == NULL); } else { - keymap = WM_keymap_active(wm, handler->keymap); + memset(km_result, 0x0, sizeof(*km_result)); + wmKeyMap *keymap = WM_keymap_active(wm, handler->keymap); BLI_assert(keymap != NULL); + if (keymap != NULL) { + km_result->keymaps[km_result->keymaps_len++] = keymap; + } } - return keymap; } wmKeyMapItem *WM_event_match_keymap_item(bContext *C, wmKeyMap *keymap, const wmEvent *event) @@ -5128,11 +5176,15 @@ wmKeyMapItem *WM_event_match_keymap_item_from_handlers(bContext *C, else if (handler_base->poll == NULL || handler_base->poll(CTX_wm_region(C), event)) { if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) { wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base; - wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler); - if (keymap && WM_keymap_poll(C, keymap)) { - wmKeyMapItem *kmi = WM_event_match_keymap_item(C, keymap, event); - if (kmi != NULL) { - return kmi; + wmEventHandler_KeymapResult km_result; + WM_event_get_keymaps_from_handler(wm, handler, &km_result); + for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) { + wmKeyMap *keymap = km_result.keymaps[km_index]; + if (WM_keymap_poll(C, keymap)) { + wmKeyMapItem *kmi = WM_event_match_keymap_item(C, keymap, event); + if (kmi != NULL) { + return kmi; + } } } } diff --git a/source/blender/windowmanager/intern/wm_jobs.c b/source/blender/windowmanager/intern/wm_jobs.c index 6494c337c10..2604105896d 100644 --- a/source/blender/windowmanager/intern/wm_jobs.c +++ b/source/blender/windowmanager/intern/wm_jobs.c @@ -230,7 +230,7 @@ bool WM_jobs_test(const wmWindowManager *wm, const void *owner, int job_type) LISTBASE_FOREACH (wmJob *, wm_job, &wm->jobs) { if (wm_job->owner == owner) { if (ELEM(job_type, WM_JOB_TYPE_ANY, wm_job->job_type)) { - if (wm_job->running || wm_job->suspended) { + if ((wm_job->flag & WM_JOB_PROGRESS) && (wm_job->running || wm_job->suspended)) { return true; } } diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c index f955abaed53..e5aedfc7f47 100644 --- a/source/blender/windowmanager/intern/wm_keymap.c +++ b/source/blender/windowmanager/intern/wm_keymap.c @@ -462,7 +462,9 @@ bool WM_keymap_poll(bContext *C, wmKeyMap *keymap) /* Empty key-maps may be missing more there may be a typo in the name. * Warn early to avoid losing time investigating each case. * When developing a customized Blender though you may want empty keymaps. */ - if (!U.app_template[0]) { + if (!U.app_template[0] && + /* Fallback key-maps may be intentionally empty, don't flood the output. */ + !BLI_str_endswith(keymap->idname, " (fallback)")) { CLOG_WARN(WM_LOG_KEYMAPS, "empty keymap '%s'", keymap->idname); } } @@ -1402,15 +1404,19 @@ static wmKeyMapItem *wm_keymap_item_find_handlers(const bContext *C, LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) { if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) { wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base; - wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler); - if (keymap && WM_keymap_poll((bContext *)C, keymap)) { - wmKeyMapItem *kmi = wm_keymap_item_find_in_keymap( - keymap, opname, properties, is_strict, params); - if (kmi != NULL) { - if (r_keymap) { - *r_keymap = keymap; + wmEventHandler_KeymapResult km_result; + WM_event_get_keymaps_from_handler(wm, handler, &km_result); + for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) { + wmKeyMap *keymap = km_result.keymaps[km_index]; + if (WM_keymap_poll((bContext *)C, keymap)) { + wmKeyMapItem *kmi = wm_keymap_item_find_in_keymap( + keymap, opname, properties, is_strict, params); + if (kmi != NULL) { + if (r_keymap) { + *r_keymap = keymap; + } + return kmi; } - return kmi; } } } diff --git a/source/blender/windowmanager/intern/wm_operator_utils.c b/source/blender/windowmanager/intern/wm_operator_utils.c index 81b597f7484..85a0a28de79 100644 --- a/source/blender/windowmanager/intern/wm_operator_utils.c +++ b/source/blender/windowmanager/intern/wm_operator_utils.c @@ -41,6 +41,24 @@ #include "ED_screen.h" /* -------------------------------------------------------------------- */ +/** \name Generic Utilities + * \{ */ + +/** + * Only finish + pass through for press events (allowing press-tweak). + */ +int WM_operator_flag_only_pass_through_on_press(int retval, const struct wmEvent *event) +{ + if ((event->val != KM_PRESS) && + ((retval & OPERATOR_PASS_THROUGH) && (retval & OPERATOR_FINISHED))) { + retval &= ~OPERATOR_PASS_THROUGH; + } + return retval; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Value Interaction Helper * * Possible additions (add as needed). diff --git a/source/blender/windowmanager/intern/wm_toolsystem.c b/source/blender/windowmanager/intern/wm_toolsystem.c index 5eaf026191f..0c24520d565 100644 --- a/source/blender/windowmanager/intern/wm_toolsystem.c +++ b/source/blender/windowmanager/intern/wm_toolsystem.c @@ -326,7 +326,10 @@ void WM_toolsystem_ref_set_from_runtime(struct bContext *C, bool use_fallback_keymap = false; if (tref->idname_fallback[0] || tref->runtime->keymap_fallback[0]) { - if (tref_rt->gizmo_group[0]) { + if (tref_rt->flag & TOOLREF_FLAG_FALLBACK_KEYMAP) { + use_fallback_keymap = true; + } + else if (tref_rt->gizmo_group[0]) { wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(tref_rt->gizmo_group, false); if (gzgt) { if (gzgt->flag & WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP) { diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 887aed7ffc7..8baf4a0e013 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -2426,10 +2426,15 @@ void wm_window_IME_end(wmWindow *win) void *WM_opengl_context_create(void) { - /* On Windows there is a problem creating contexts that share lists - * from one context that is current in another thread. - * So we should call this function only on the main thread. - */ + /* On Windows there is a problem creating contexts that share resources (almost any object, + * including legacy display lists, but also textures) with a context which is current in another + * thread. This is a documented and behavior of both `::wglCreateContextAttribsARB()` and + * `::wglShareLists()`. + * + * Other platforms might successfully share resources from context which is active somewhere + * else, but to keep our code behave the same on all platform we expect contexts to only be + * created from the main thread. */ + BLI_assert(BLI_thread_is_main()); BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get()); |