GPv3: Opacity modifier #116946

Merged
Lukas Tönne merged 52 commits from LukasTonne/blender:gp3-opacity-modifier into main 2024-01-16 16:56:22 +01:00
134 changed files with 2273 additions and 1260 deletions
Showing only changes of commit cd9cb1c279 - Show all commits

View File

@ -1234,10 +1234,10 @@ if(WITH_PYTHON)
# Do this before main 'platform_*' checks,
# because UNIX will search for the old Python paths which may not exist.
# giving errors about missing paths before this case is met.
if(DEFINED PYTHON_VERSION AND "${PYTHON_VERSION}" VERSION_LESS "3.10")
if(DEFINED PYTHON_VERSION AND "${PYTHON_VERSION}" VERSION_LESS "3.11")
message(
FATAL_ERROR
"At least Python 3.10 is required to build, but found Python ${PYTHON_VERSION}"
"At least Python 3.11 is required to build, but found Python ${PYTHON_VERSION}"
)
endif()

View File

@ -223,9 +223,6 @@ ifeq (, $(wildcard $(LIBDIR)/python/lib/python$(PY_LIB_VERSION)))
PY_LIB_VERSION:=3.12
ifeq (, $(wildcard $(LIBDIR)/python/lib/python$(PY_LIB_VERSION)))
PY_LIB_VERSION:=3.11
ifeq (, $(wildcard $(LIBDIR)/python/lib/python$(PY_LIB_VERSION)))
PY_LIB_VERSION:=3.10
endif
endif
endif
endif

View File

@ -8,8 +8,8 @@ if(BUILD_MODE STREQUAL Release)
-DALSOFT_NO_CONFIG_UTIL=ON
-DALSOFT_EXAMPLES=OFF
-DALSOFT_TESTS=OFF
-DALSOFT_CONFIG=OFF
-DALSOFT_HRTF_DEFS=OFF
-DALSOFT_INSTALL_CONFIG=OFF
-DALSOFT_INSTALL_HRTF_DATA=OFF
-DALSOFT_INSTALL=ON
-DALSOFT_BACKEND_SNDIO=OFF
)

View File

@ -18,9 +18,9 @@ set(ZLIB_FILE zlib-${ZLIB_VERSION}.tar.gz)
set(ZLIB_CPE "cpe:2.3:a:zlib:zlib:${ZLIB_VERSION}:*:*:*:*:*:*:*")
set(ZLIB_HOMEPAGE https://zlib.net)
set(OPENAL_VERSION 1.21.1)
set(OPENAL_URI http://openal-soft.org/openal-releases/openal-soft-${OPENAL_VERSION}.tar.bz2)
set(OPENAL_HASH a936806ebd8de417b0ffd8cf3f48f456)
set(OPENAL_VERSION 1.23.1)
set(OPENAL_URI https://github.com/kcat/openal-soft/releases/download/${OPENAL_VERSION}/openal-soft-${OPENAL_VERSION}.tar.bz2)
set(OPENAL_HASH 58a73698288d2787451b61f8f4431513)
set(OPENAL_HASH_TYPE MD5)
set(OPENAL_FILE openal-soft-${OPENAL_VERSION}.tar.bz2)
set(OPENAL_HOMEPAGE https://openal-soft.org/)

View File

@ -555,11 +555,15 @@ endif()
set(_PYTHON_VERSION "3.11")
string(REPLACE "." "" _PYTHON_VERSION_NO_DOTS ${_PYTHON_VERSION})
if(NOT EXISTS ${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS})
set(_PYTHON_VERSION "3.10")
string(REPLACE "." "" _PYTHON_VERSION_NO_DOTS ${_PYTHON_VERSION})
# Enable for a short time when bumping to the next Python version.
if(FALSE)
if(NOT EXISTS ${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS})
message(FATAL_ERROR "Missing python libraries! Neither 3.11 nor 3.10 are found in ${LIBDIR}/python")
set(_PYTHON_VERSION "3.12")
string(REPLACE "." "" _PYTHON_VERSION_NO_DOTS ${_PYTHON_VERSION})
if(NOT EXISTS ${LIBDIR}/python/${_PYTHON_VERSION_NO_DOTS})
message(FATAL_ERROR "Missing python libraries! Neither 3.12 nor 3.11 are found in ${LIBDIR}/python")
endif()
endif()
endif()

View File

@ -26,14 +26,9 @@ function(blender_test_set_envvars testname envvars_list)
if(NOT CMAKE_BUILD_TYPE MATCHES "Release")
if(WITH_COMPILER_ASAN)
# Don't fail tests on leaks since these often happen in external libraries that we can't fix.
# FIXME This is a 'nuke solution', no xSAN errors will ever fail tests. Needs more refined handling,
# see https://projects.blender.org/blender/blender/pulls/116635 .
set(_lsan_options "LSAN_OPTIONS=exitcode=0")
set(_lsan_options "LSAN_OPTIONS=print_suppressions=false:suppressions=${CMAKE_SOURCE_DIR}/tools/config/analysis/lsan.supp")
# FIXME That `allocator_may_return_null=true` ASAN option is only needed for the `guardedalloc` test,
# would be nice to allow tests definition to pass extra envvars better.
# NOTE: This is needed for Mac builds currently, on Linux the `exitcode=0` option passed above to LSAN
# also seems to silence reports from ASAN.
set(_asan_options "ASAN_OPTIONS=allocator_may_return_null=true")
if(DEFINED ENV{LSAN_OPTIONS})
set(_lsan_options "${_lsan_options}:$ENV{LSAN_OPTIONS}")

View File

@ -1,6 +1,11 @@
"""
Get the property associated with a hovered button.
Returns a tuple of the datablock, data path to the property, and array index.
Returns a tuple of the data-block, data path to the property, and array index.
.. note::
When the property doesn't have an associated :class:`bpy.types.ID` non-ID data may be returned.
This may occur when accessing windowing data, for example, operator properties.
"""
# Example inserting keyframe for the hovered property.

View File

@ -1217,7 +1217,7 @@ context_type_map = {
"particle_settings": ("ParticleSettings", False),
"particle_system": ("ParticleSystem", False),
"particle_system_editable": ("ParticleSystem", False),
"property": ("(:class:`bpy.types.ID`, :class:`string`, :class:`int`)", False),
"property": ("(:class:`bpy.types.AnyType`, :class:`string`, :class:`int`)", False),
"pointcloud": ("PointCloud", False),
"pose_bone": ("PoseBone", False),
"pose_object": ("Object", False),

View File

@ -231,7 +231,6 @@ class BlenderVolumeLoader : public VDBImageLoader {
if (b_volume_grid.name() == grid_name) {
const auto *volume_grid = static_cast<const blender::bke::VolumeGridData *>(
b_volume_grid.ptr.data);
tree_access_token = volume_grid->tree_access_token();
grid = volume_grid->grid_ptr(tree_access_token);
break;
}

View File

@ -495,12 +495,13 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = sc->N;
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
unguided_bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
}
#ifdef __LIGHT_LINKING__
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING) {
INTEGRATOR_STATE_WRITE(state, path, mis_ray_object) = sd->object;
}
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING) {
INTEGRATOR_STATE_WRITE(state, path, mis_ray_object) = sd->object;
}
#endif
}
path_state_next(kg, state, label, sd->flag);

View File

@ -6077,6 +6077,7 @@ def km_edit_curves(params):
("curves.set_selection_domain", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("domain", 'CURVE')]}),
("curves.duplicate_move", {"type": 'D', "value": 'PRESS', "shift": True}, None),
*_template_items_select_actions(params, "curves.select_all"),
("curves.extrude_move", {"type": 'E', "value": 'PRESS'}, None),
("curves.select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None),
("curves.delete", {"type": 'X', "value": 'PRESS'}, None),
("curves.delete", {"type": 'DEL', "value": 'PRESS'}, None),

View File

@ -158,11 +158,18 @@ class ARMATURE_MT_collection_tree_context_menu(Menu):
layout = self.layout
arm = context.armature
props = layout.operator(
active_bcoll_is_locked = arm.collections.active and not arm.collections.active.is_editable
# The poll function doesn't have access to the parent index property, so
# it cannot disable this operator depending on whether the parent is
# editable or not. That means this menu has to do the disabling for it.
sub = layout.column()
sub.enabled = not active_bcoll_is_locked
props = sub.operator(
"armature.collection_add", text="Add Child Collection"
)
props.parent_index = arm.collections.active_index
layout.operator("armature.collection_remove")
sub.operator("armature.collection_remove")
layout.separator()
@ -171,8 +178,13 @@ class ARMATURE_MT_collection_tree_context_menu(Menu):
layout.separator()
layout.operator("armature.collection_assign", text="Assign Selected Bones")
layout.operator("armature.collection_unassign", text="Remove Selected Bones")
# These operators can be used to assign to a named collection as well, and
# don't necessarily always use the active bone collection. That means that
# they have the same limitation as described above.
sub = layout.column()
sub.enabled = not active_bcoll_is_locked
sub.operator("armature.collection_assign", text="Assign Selected Bones")
sub.operator("armature.collection_unassign", text="Remove Selected Bones")
layout.separator()

View File

@ -1225,7 +1225,7 @@ class SEQUENCER_MT_context_menu(Menu):
layout.operator("sequencer.retiming_segment_speed_set")
layout.separator()
layout.operator("sequencer.retiming_key_remove")
layout.operator("sequencer.delete", text="Delete Retiming Keys")
def draw(self, context):
ed = context.scene.sequence_editor

View File

@ -1307,6 +1307,7 @@ class _defs_edit_curves:
props = tool.operator_properties("curves.draw")
col = layout.column(align=True)
col.prop(props, "is_curve_2d", text="Curve 2D")
col.prop(props, "bezier_as_nurbs", text="As NURBS")
return dict(
idname="builtin.draw",

View File

@ -68,7 +68,7 @@ void ANIM_armature_runtime_free(bArmature *armature);
/**
* Add a new bone collection to the given armature.
*
* \param parent_index Index into the Armature's `collections_array`. -1 adds it
* \param parent_index: Index into the Armature's `collections_array`. -1 adds it
* as a root (i.e. parentless) collection.
*
* The Armature owns the returned pointer.
@ -364,14 +364,14 @@ bool bonecoll_has_children(const BoneCollection *bcoll);
/**
* Move a bone collection from one parent to another.
*
* \param from_bcoll_index index of the bone collection to move.
* \param to_child_num gap index of where to insert the collection; 0 to make it
* \param from_bcoll_index: Index of the bone collection to move.
* \param to_child_num: Gap index of where to insert the collection; 0 to make it
* the first child, and parent->child_count to make it the last child. -1 also
* works as an indicator for the last child, as that makes it possible to call
* this function without requiring the caller to find the BoneCollection* of the
* parent.
* \param from_parent_index index of its current parent (-1 if it is a root collection).
* \param to_parent_index index of the new parent (-1 if it is to become a root collection).
* \param from_parent_index: Index of its current parent (-1 if it is a root collection).
* \param to_parent_index: Index of the new parent (-1 if it is to become a root collection).
* \return the collection's new index in the collections_array.
*/
int armature_bonecoll_move_to_parent(bArmature *armature,

View File

@ -35,7 +35,7 @@ namespace blender::animrig::internal {
* Updating those, as well as any references to the rotated element, is the
* responsibility of the caller.
*
* \param direction must be either -1 or 1.
* \param direction: Must be either -1 or 1.
*/
void bonecolls_rotate_block(bArmature *armature, int start_index, int count, int direction);

View File

@ -35,7 +35,6 @@
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_anim_types.h"
#include "ED_keyframing.hh"
#include "MEM_guardedalloc.h"
#include "RNA_access.hh"
#include "RNA_path.hh"
@ -69,21 +68,21 @@ void update_autoflags_fcurve_direct(FCurve *fcu, PropertyRNA *prop)
}
/** Used to make curves newly added to a cyclic Action cycle with the correct period. */
static void make_new_fcurve_cyclic(const bAction *act, FCurve *fcu)
static void make_new_fcurve_cyclic(FCurve *fcu, const blender::float2 &action_range)
{
/* The curve must contain one (newly-added) keyframe. */
if (fcu->totvert != 1 || !fcu->bezt) {
return;
}
const float period = act->frame_end - act->frame_start;
const float period = action_range[1] - action_range[0];
if (period < 0.1f) {
return;
}
/* Move the keyframe into the range. */
const float frame_offset = fcu->bezt[0].vec[1][0] - act->frame_start;
const float frame_offset = fcu->bezt[0].vec[1][0] - action_range[0];
const float fix = floorf(frame_offset / period) * period;
fcu->bezt[0].vec[0][0] -= fix;
@ -471,7 +470,7 @@ static bool insert_keyframe_fcurve_value(Main *bmain,
const bool is_cyclic_action = (flag & INSERTKEY_CYCLE_AWARE) && BKE_action_is_cyclic(act);
if (is_cyclic_action && fcu->totvert == 1) {
make_new_fcurve_cyclic(act, fcu);
make_new_fcurve_cyclic(fcu, {act->frame_start, act->frame_end});
}
/* Update F-Curve flags to ensure proper behavior for property type. */
@ -495,7 +494,7 @@ static bool insert_keyframe_fcurve_value(Main *bmain,
/* If the curve is new, make it cyclic if appropriate. */
if (is_cyclic_action && is_new_curve) {
make_new_fcurve_cyclic(act, fcu);
make_new_fcurve_cyclic(fcu, {act->frame_start, act->frame_end});
}
return success;

View File

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 9
#define BLENDER_FILE_SUBVERSION 10
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@ -33,7 +33,11 @@ class bNodeTreeZone {
Vector<bNodeTreeZone *> child_zones;
/** Direct children nodes excluding nodes that belong to child zones. */
Vector<const bNode *> child_nodes;
/** Links that enter the zone through the zone border. */
/**
* Links that enter the zone through the zone border and carry information. This excludes muted
* and unavailable links as well as links that are dangling because they are only connected to a
* reroute.
*/
Vector<const bNodeLink *> border_links;
bool contains_node_recursively(const bNode &node) const;

View File

@ -139,12 +139,6 @@ class VolumeGridData : public ImplicitSharingMixin {
~VolumeGridData();
/**
* Get an access token for the underlying tree. This is necessary to be able to detect whether
* the grid is currently unused so that it can be safely unloaded.
*/
VolumeTreeAccessToken tree_access_token() const;
/**
* Create a copy of the volume grid. This should generally only be done when the current grid is
* shared and one owner wants to modify it.
@ -158,22 +152,20 @@ class VolumeGridData : public ImplicitSharingMixin {
* Get the underlying OpenVDB grid for read-only access. This may load the tree lazily if it's
* not loaded already.
*/
const openvdb::GridBase &grid(const VolumeTreeAccessToken &tree_access_token) const;
const openvdb::GridBase &grid(VolumeTreeAccessToken &r_token) const;
/**
* Get the underlying OpenVDB grid for read and write access. This may load the tree lazily if
* it's not loaded already. It may also make a copy of the tree if it's currently shared.
*/
openvdb::GridBase &grid_for_write(const VolumeTreeAccessToken &tree_access_token);
openvdb::GridBase &grid_for_write(VolumeTreeAccessToken &r_token);
/**
* Same as #grid and #grid_for_write but returns the grid as a `shared_ptr` so that it can be
* used with APIs that only support grids wrapped into one. This method is not supposed to
* actually transfer ownership of the grid.
*/
std::shared_ptr<const openvdb::GridBase> grid_ptr(
const VolumeTreeAccessToken &tree_access_token) const;
std::shared_ptr<openvdb::GridBase> grid_ptr_for_write(
const VolumeTreeAccessToken &tree_access_token);
std::shared_ptr<const openvdb::GridBase> grid_ptr(VolumeTreeAccessToken &r_token) const;
std::shared_ptr<openvdb::GridBase> grid_ptr_for_write(VolumeTreeAccessToken &r_token);
/**
* Get the name of the grid that's stored in the grid meta-data.
@ -319,8 +311,8 @@ template<typename T> class VolumeGrid : public GVolumeGrid {
/**
* Wraps the same methods on #VolumeGridData but casts to the correct OpenVDB type.
*/
const OpenvdbGridType<T> &grid(const VolumeTreeAccessToken &tree_access_token) const;
OpenvdbGridType<T> &grid_for_write(const VolumeTreeAccessToken &tree_access_token);
const OpenvdbGridType<T> &grid(VolumeTreeAccessToken &r_token) const;
OpenvdbGridType<T> &grid_for_write(VolumeTreeAccessToken &r_token);
private:
void assert_correct_type() const;
@ -381,18 +373,15 @@ inline VolumeGrid<T>::VolumeGrid(std::shared_ptr<OpenvdbGridType<T>> grid)
}
template<typename T>
inline const OpenvdbGridType<T> &VolumeGrid<T>::grid(
const VolumeTreeAccessToken &tree_access_token) const
inline const OpenvdbGridType<T> &VolumeGrid<T>::grid(VolumeTreeAccessToken &r_token) const
{
return static_cast<const OpenvdbGridType<T> &>(data_->grid(tree_access_token));
return static_cast<const OpenvdbGridType<T> &>(data_->grid(r_token));
}
template<typename T>
inline OpenvdbGridType<T> &VolumeGrid<T>::grid_for_write(
const VolumeTreeAccessToken &tree_access_token)
inline OpenvdbGridType<T> &VolumeGrid<T>::grid_for_write(VolumeTreeAccessToken &r_token)
{
return static_cast<OpenvdbGridType<T> &>(
this->get_for_write().grid_for_write(tree_access_token));
return static_cast<OpenvdbGridType<T> &>(this->get_for_write().grid_for_write(r_token));
}
template<typename T> inline void VolumeGrid<T>::assert_correct_type() const

View File

@ -2116,7 +2116,7 @@ float BKE_brush_sample_tex_3d(const Scene *scene,
if (mtex->brush_map_mode == MTEX_MAP_MODE_VIEW) {
/* keep coordinates relative to mouse */
rotation += ups->brush_rotation;
rotation -= ups->brush_rotation;
x = point_2d[0] - ups->tex_mouse[0];
y = point_2d[1] - ups->tex_mouse[1];
@ -2134,7 +2134,7 @@ float BKE_brush_sample_tex_3d(const Scene *scene,
y = point_2d[1];
}
else if (mtex->brush_map_mode == MTEX_MAP_MODE_RANDOM) {
rotation += ups->brush_rotation;
rotation -= ups->brush_rotation;
/* these contain a random coordinate */
x = point_2d[0] - ups->tex_mouse[0];
y = point_2d[1] - ups->tex_mouse[1];
@ -2229,7 +2229,7 @@ float BKE_brush_sample_masktex(
if (mtex->brush_map_mode == MTEX_MAP_MODE_VIEW) {
/* keep coordinates relative to mouse */
rotation += ups->brush_rotation_sec;
rotation -= ups->brush_rotation_sec;
x = point_2d[0] - ups->mask_tex_mouse[0];
y = point_2d[1] - ups->mask_tex_mouse[1];
@ -2247,7 +2247,7 @@ float BKE_brush_sample_masktex(
y = point_2d[1];
}
else if (mtex->brush_map_mode == MTEX_MAP_MODE_RANDOM) {
rotation += ups->brush_rotation_sec;
rotation -= ups->brush_rotation_sec;
/* these contain a random coordinate */
x = point_2d[0] - ups->mask_tex_mouse[0];
y = point_2d[1] - ups->mask_tex_mouse[1];

View File

@ -89,7 +89,7 @@ static void remember_deformed_grease_pencil_if_necessary(const GreasePencil *gre
*grease_pencil, layer_index);
const greasepencil::Layer &orig_layer = *orig_layers[layer_index];
const greasepencil::Drawing *orig_drawing = orig_grease_pencil.get_drawing_at(
&orig_layer, grease_pencil->runtime->eval_frame);
orig_layer, grease_pencil->runtime->eval_frame);
GreasePencilDrawingEditHints &drawing_hints = all_hints[layer_index];
if (!drawing || !orig_drawing) {

View File

@ -1825,12 +1825,9 @@ void GreasePencil::move_duplicate_frames(
}
const blender::bke::greasepencil::Drawing *GreasePencil::get_drawing_at(
const blender::bke::greasepencil::Layer *layer, const int frame_number) const
const blender::bke::greasepencil::Layer &layer, const int frame_number) const
{
if (layer == nullptr) {
return nullptr;
}
const int drawing_index = layer->drawing_index_at(frame_number);
const int drawing_index = layer.drawing_index_at(frame_number);
if (drawing_index == -1) {
/* No drawing found. */
return nullptr;
@ -1845,13 +1842,13 @@ const blender::bke::greasepencil::Drawing *GreasePencil::get_drawing_at(
}
blender::bke::greasepencil::Drawing *GreasePencil::get_editable_drawing_at(
const blender::bke::greasepencil::Layer *layer, const int frame_number)
const blender::bke::greasepencil::Layer &layer, const int frame_number)
{
if (layer == nullptr || !layer->is_editable()) {
if (!layer.is_editable()) {
return nullptr;
}
const int drawing_index = layer->drawing_index_at(frame_number);
const int drawing_index = layer.drawing_index_at(frame_number);
if (drawing_index == -1) {
/* No drawing found. */
return nullptr;
@ -1871,8 +1868,8 @@ std::optional<blender::Bounds<blender::float3>> GreasePencil::bounds_min_max(con
std::optional<Bounds<float3>> bounds;
const Span<const bke::greasepencil::Layer *> layers = this->layers();
for (const int layer_i : layers.index_range()) {
const bke::greasepencil::Layer *layer = layers[layer_i];
if (!layer->is_visible()) {
const bke::greasepencil::Layer &layer = *layers[layer_i];
if (!layer.is_visible()) {
continue;
}
if (const bke::greasepencil::Drawing *drawing = this->get_drawing_at(layer, frame)) {

View File

@ -1305,6 +1305,29 @@ static void ntree_set_typeinfo(bNodeTree *ntree, bNodeTreeType *typeinfo)
BKE_ntree_update_tag_all(ntree);
}
/* Build a set of built-in node types to check for known types. */
static blender::Set<int> get_known_node_types_set()
{
blender::Set<int> result;
NODE_TYPES_BEGIN (ntype) {
result.add(ntype->type);
}
NODE_TYPES_END;
return result;
}
static bool can_read_node_type(const int type)
{
/* Can always read custom node types. */
if (type == NODE_CUSTOM) {
return true;
}
/* Check known built-in types. */
static blender::Set<int> known_types = get_known_node_types_set();
return known_types.contains(type);
}
static void node_set_typeinfo(const bContext *C,
bNodeTree *ntree,
bNode *node,
@ -1312,6 +1335,19 @@ static void node_set_typeinfo(const bContext *C,
{
/* for nodes saved in older versions storage can get lost, make undefined then */
if (node->flag & NODE_INIT) {
/* If the integer type is unknown then this is a node from a newer Blender version.
* These cannot be read reliably so replace the idname with an undefined type. This keeps links
* and socket names but discards storage and other type-specific data.
*/
if (!can_read_node_type(node->type)) {
node->type = NODE_CUSTOM;
/* This type name is arbitrary, it just has to be unique enough to not match a future node
* idname. Includes the old type identifier for debugging purposes. */
const std::string old_idname = node->idname;
BLI_snprintf(node->idname, sizeof(node->idname), "Undefined[%s]", old_idname.c_str());
typeinfo = nullptr;
}
if (typeinfo && typeinfo->storagename[0] && !node->storage) {
typeinfo = nullptr;
}

View File

@ -117,10 +117,13 @@ static const aal::RelationsInNode &get_relations_in_node(const bNode &node, Reso
}
}
else if (socket_is_field(*socket)) {
/* Reference relations are not added for the output node, because then nodes after the
* repeat zone would have to know about the individual field sources within the repeat
* zone. This is not necessary, because the field outputs of a repeat zone already serve as
* field sources and anonymous attributes are extracted from them. */
/* Reference relations are not added for the repeat output node here, because those need
* some special handling which is done during the actual inferencing. This is necessary,
* because nodes coming after the repeat zone don't have access to the intermediate fields
* created inside of the repeat zone. Instead, the outputs of the repeat zone are treated
* as new field sources which wrap all fields created in the zone.
*
* The repeat input node does get the expected reference relations though. */
if (node.type == GEO_NODE_REPEAT_INPUT) {
for (const bNodeSocket *input_socket : node.input_sockets()) {
if (socket_is_field(*input_socket)) {
@ -399,8 +402,8 @@ static AnonymousAttributeInferencingResult analyze_anonymous_attribute_usages(
* Input to the Repeat Output node. Therefor, all anonymous attributes may be propagated as
* well. */
const bNodeTreeZone *zone = zones->get_zone_by_node(node->identifier);
const int items_num = node->output_sockets().size() - 1;
if (const bNode *input_node = zone->input_node) {
const int items_num = node->output_sockets().size();
for (const int i : IndexRange(items_num)) {
const int src_index = input_node->input_socket(i + 1).index_in_tree();
const int dst_index = node->output_socket(i).index_in_tree();
@ -411,6 +414,25 @@ static AnonymousAttributeInferencingResult analyze_anonymous_attribute_usages(
available_fields_by_geometry_socket[src_index];
}
}
/* Propagate fields that have not been created inside of the repeat zones. Field sources
* from inside the repeat zone become new field sources on the outside. */
for (const int i : IndexRange(items_num)) {
const int src_index = node->input_socket(i).index_in_tree();
const int dst_index = node->output_socket(i).index_in_tree();
bits::foreach_1_index(
propagated_fields_by_socket[src_index], [&](const int field_source_index) {
const FieldSource &field_source = all_field_sources[field_source_index];
if (const auto *socket_field_source = std::get_if<SocketFieldSource>(
&field_source.data))
{
const bNode &field_source_node = socket_field_source->socket->owner_node();
if (zone->contains_node_recursively(field_source_node)) {
return;
}
}
propagated_fields_by_socket[dst_index][field_source_index].set();
});
}
}
}
};

View File

@ -561,9 +561,6 @@ class NodeTreeMainUpdater {
if (ntype.group_update_func) {
ntype.group_update_func(&ntree, node);
}
if (ntype.updatefunc) {
ntype.updatefunc(&ntree, node);
}
if (ntype.declare) {
/* Should have been created when the node was registered. */
BLI_assert(ntype.static_declaration != nullptr);
@ -571,6 +568,9 @@ class NodeTreeMainUpdater {
nodes::update_node_declaration_and_sockets(ntree, *node);
}
}
if (ntype.updatefunc) {
ntype.updatefunc(&ntree, node);
}
}
}
}

View File

@ -196,6 +196,9 @@ static void update_zone_border_links(const bNodeTree &tree, bNodeTreeZones &tree
if (link->is_muted()) {
continue;
}
if (bke::nodeIsDanglingReroute(&tree, link->fromnode)) {
continue;
}
bNodeTreeZone *from_zone = const_cast<bNodeTreeZone *>(
tree_zones.get_zone_by_socket(*link->fromsock));
bNodeTreeZone *to_zone = const_cast<bNodeTreeZone *>(

View File

@ -1352,11 +1352,11 @@ bool paint_calculate_rake_rotation(UnifiedPaintSettings *ups,
}
float dpos[2];
sub_v2_v2v2(dpos, ups->last_rake, mouse_pos);
sub_v2_v2v2(dpos, mouse_pos, ups->last_rake);
/* Limit how often we update the angle to prevent jitter. */
if (len_squared_v2(dpos) >= r * r) {
rotation = atan2f(dpos[0], dpos[1]);
rotation = atan2f(dpos[1], dpos[0]) + float(0.5f * M_PI);
copy_v2_v2(ups->last_rake, mouse_pos);

View File

@ -1732,7 +1732,7 @@ void BKE_pbvh_node_mark_redraw(PBVHNode *node)
void BKE_pbvh_node_mark_normals_update(PBVHNode *node)
{
node->flag |= PBVH_UpdateNormals;
node->flag |= PBVH_UpdateNormals | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw;
}
void BKE_pbvh_node_fully_hidden_set(PBVHNode *node, int fully_hidden)

View File

@ -556,11 +556,11 @@ bool BKE_volume_save(const Volume *volume,
openvdb::GridCPtrVec vdb_grids;
/* Tree users need to be kept alive for as long as the grids may be accessed. */
blender::Vector<blender::bke::VolumeTreeAccessToken> tree_users;
blender::Vector<blender::bke::VolumeTreeAccessToken> tree_tokens;
for (const GVolumeGrid &grid : grids) {
tree_users.append(grid->tree_access_token());
vdb_grids.push_back(grid->grid_ptr(tree_users.last()));
tree_tokens.append_as();
vdb_grids.push_back(grid->grid_ptr(tree_tokens.last()));
}
try {
@ -594,9 +594,9 @@ std::optional<blender::Bounds<blender::float3>> BKE_volume_min_max(const Volume
std::optional<blender::Bounds<blender::float3>> result;
for (const int i : IndexRange(BKE_volume_num_grids(volume))) {
const blender::bke::VolumeGridData *volume_grid = BKE_volume_grid_get(volume, i);
blender::bke::VolumeTreeAccessToken access_token = volume_grid->tree_access_token();
blender::bke::VolumeTreeAccessToken tree_token;
result = blender::bounds::merge(result,
BKE_volume_grid_bounds(volume_grid->grid_ptr(access_token)));
BKE_volume_grid_bounds(volume_grid->grid_ptr(tree_token)));
}
return result;
}

View File

@ -93,41 +93,32 @@ void VolumeGridData::delete_self()
MEM_delete(this);
}
VolumeTreeAccessToken VolumeGridData::tree_access_token() const
const openvdb::GridBase &VolumeGridData::grid(VolumeTreeAccessToken &r_token) const
{
VolumeTreeAccessToken user;
user.token_ = tree_access_token_;
return user;
return *this->grid_ptr(r_token);
}
const openvdb::GridBase &VolumeGridData::grid(const VolumeTreeAccessToken &access_token) const
openvdb::GridBase &VolumeGridData::grid_for_write(VolumeTreeAccessToken &r_token)
{
return *this->grid_ptr(access_token);
}
openvdb::GridBase &VolumeGridData::grid_for_write(const VolumeTreeAccessToken &access_token)
{
return *this->grid_ptr_for_write(access_token);
return *this->grid_ptr_for_write(r_token);
}
std::shared_ptr<const openvdb::GridBase> VolumeGridData::grid_ptr(
const VolumeTreeAccessToken &access_token) const
VolumeTreeAccessToken &r_token) const
{
BLI_assert(access_token.valid_for(*this));
UNUSED_VARS_NDEBUG(access_token);
std::lock_guard lock{mutex_};
this->ensure_grid_loaded();
r_token.token_ = tree_access_token_;
return grid_;
}
std::shared_ptr<openvdb::GridBase> VolumeGridData::grid_ptr_for_write(
const VolumeTreeAccessToken &access_token)
VolumeTreeAccessToken &r_token)
{
BLI_assert(access_token.valid_for(*this));
UNUSED_VARS_NDEBUG(access_token);
BLI_assert(this->is_mutable());
std::lock_guard lock{mutex_};
this->ensure_grid_loaded();
r_token.token_ = tree_access_token_;
if (tree_sharing_info_->is_mutable()) {
tree_sharing_info_->tag_ensured_mutable();
}
@ -468,8 +459,8 @@ void set_transform_matrix(VolumeGridData &grid, const float4x4 &matrix)
void clear_tree(VolumeGridData &grid)
{
#ifdef WITH_OPENVDB
VolumeTreeAccessToken access_token = grid.tree_access_token();
grid.grid_for_write(access_token).clear();
VolumeTreeAccessToken tree_token;
grid.grid_for_write(tree_token).clear();
#else
UNUSED_VARS(grid);
#endif
@ -488,9 +479,9 @@ bool is_loaded(const VolumeGridData &grid)
void load(const VolumeGridData &grid)
{
#ifdef WITH_OPENVDB
VolumeTreeAccessToken access_token = grid.tree_access_token();
VolumeTreeAccessToken tree_token;
/* Just "touch" the grid, so that it is loaded. */
grid.grid(access_token);
grid.grid(tree_token);
#else
UNUSED_VARS(grid);
#endif

View File

@ -162,9 +162,9 @@ static GVolumeGrid get_cached_grid(const StringRef file_path,
const GVolumeGrid main_grid = get_grid_from_file(file_path, grid_name, 0);
const VolumeGridType grid_type = main_grid->grid_type();
const float resolution_factor = 1.0f / (1 << simplify_level);
const VolumeTreeAccessToken access_token = main_grid->tree_access_token();
VolumeTreeAccessToken tree_token;
return BKE_volume_grid_create_with_changed_resolution(
grid_type, main_grid->grid(access_token), resolution_factor);
grid_type, main_grid->grid(tree_token), resolution_factor);
};
/* This allows the returned grid to already contain meta-data and transforms, even if the tree is
* not loaded yet. */

View File

@ -98,8 +98,8 @@ bool BKE_volume_grid_dense_floats(const Volume *volume,
{
#ifdef WITH_OPENVDB
const VolumeGridType grid_type = volume_grid->grid_type();
blender::bke::VolumeTreeAccessToken access_token = volume_grid->tree_access_token();
const openvdb::GridBase &grid = volume_grid->grid(access_token);
blender::bke::VolumeTreeAccessToken tree_token;
const openvdb::GridBase &grid = volume_grid->grid(tree_token);
const openvdb::CoordBBox bbox = grid.evalActiveVoxelBoundingBox();
if (bbox.empty()) {
@ -333,8 +333,8 @@ void BKE_volume_grid_wireframe(const Volume *volume,
}
#ifdef WITH_OPENVDB
blender::bke::VolumeTreeAccessToken access_token = volume_grid->tree_access_token();
const openvdb::GridBase &grid = volume_grid->grid(access_token);
blender::bke::VolumeTreeAccessToken tree_token;
const openvdb::GridBase &grid = volume_grid->grid(tree_token);
if (volume->display.wireframe_type == VOLUME_WIREFRAME_BOUNDS) {
/* Bounding box. */
@ -412,8 +412,8 @@ void BKE_volume_grid_selection_surface(const Volume * /*volume*/,
void *cb_userdata)
{
#ifdef WITH_OPENVDB
blender::bke::VolumeTreeAccessToken access_token = volume_grid->tree_access_token();
const openvdb::GridBase &grid = volume_grid->grid(access_token);
blender::bke::VolumeTreeAccessToken tree_token;
const openvdb::GridBase &grid = volume_grid->grid(tree_token);
blender::Vector<openvdb::CoordBBox> boxes = get_bounding_boxes(
volume_grid->grid_type(), grid, true);

View File

@ -85,24 +85,23 @@ TEST_F(VolumeTest, lazy_load_grid)
VolumeGrid<float> volume_grid{MEM_new<VolumeGridData>(__func__, load_grid)};
EXPECT_EQ(load_counter, 0);
EXPECT_FALSE(volume_grid->is_loaded());
VolumeTreeAccessToken access_token = volume_grid->tree_access_token();
EXPECT_EQ(volume_grid.grid(access_token).background(), 10.0f);
VolumeTreeAccessToken tree_token;
EXPECT_EQ(volume_grid.grid(tree_token).background(), 10.0f);
EXPECT_EQ(load_counter, 1);
EXPECT_TRUE(volume_grid->is_loaded());
EXPECT_TRUE(volume_grid->is_reloadable());
EXPECT_EQ(volume_grid.grid(access_token).background(), 10.0f);
EXPECT_EQ(volume_grid.grid(tree_token).background(), 10.0f);
EXPECT_EQ(load_counter, 1);
volume_grid->unload_tree_if_possible();
EXPECT_TRUE(volume_grid->is_loaded());
access_token.reset();
tree_token.reset();
volume_grid->unload_tree_if_possible();
EXPECT_FALSE(volume_grid->is_loaded());
access_token = volume_grid->tree_access_token();
EXPECT_EQ(volume_grid.grid(access_token).background(), 10.0f);
EXPECT_EQ(volume_grid.grid(tree_token).background(), 10.0f);
EXPECT_TRUE(volume_grid->is_loaded());
EXPECT_EQ(load_counter, 2);
volume_grid.grid_for_write(access_token).getAccessor().setValue({0, 0, 0}, 1.0f);
EXPECT_EQ(volume_grid.grid(access_token).getAccessor().getValue({0, 0, 0}), 1.0f);
volume_grid.grid_for_write(tree_token).getAccessor().setValue({0, 0, 0}, 1.0f);
EXPECT_EQ(volume_grid.grid(tree_token).getAccessor().getValue({0, 0, 0}), 1.0f);
EXPECT_FALSE(volume_grid->is_reloadable());
}
@ -121,11 +120,11 @@ TEST_F(VolumeTest, lazy_load_tree_only)
volume_grid.get_for_write().set_name("Test");
EXPECT_FALSE(load_run);
EXPECT_EQ(volume_grid->name(), "Test");
VolumeTreeAccessToken access_token = volume_grid->tree_access_token();
volume_grid.grid_for_write(access_token);
VolumeTreeAccessToken tree_token;
volume_grid.grid_for_write(tree_token);
EXPECT_TRUE(load_run);
EXPECT_EQ(volume_grid->name(), "Test");
EXPECT_EQ(volume_grid.grid(access_token).background(), 10.0f);
EXPECT_EQ(volume_grid.grid(tree_token).background(), 10.0f);
}
} // namespace blender::bke::tests

View File

@ -73,20 +73,6 @@ enum CDT_output_type {
namespace blender::meshintersect {
/** #vec2<Arith_t> is a 2d vector with #Arith_t as the type for coordinates. */
template<typename Arith_t> struct vec2_impl;
template<> struct vec2_impl<double> {
typedef double2 type;
};
#ifdef WITH_GMP
template<> struct vec2_impl<mpq_class> {
typedef mpq2 type;
};
#endif
template<typename Arith_t> using vec2 = typename vec2_impl<Arith_t>::type;
/**
* Input to Constrained Delaunay Triangulation.
* Input vertex coordinates are stored in `vert`. For the rest of the input,
@ -137,12 +123,12 @@ template<typename Arith_t> using vec2 = typename vec2_impl<Arith_t>::type;
* If this is not needed, set need_ids to false and the execution may be much
* faster in some circumstances.
*/
template<typename Arith_t> class CDT_input {
template<typename T> class CDT_input {
public:
Array<vec2<Arith_t>> vert;
Array<VecBase<T, 2>> vert;
Array<std::pair<int, int>> edge;
Array<Vector<int>> face;
Arith_t epsilon{0};
T epsilon{0};
bool need_ids{true};
};
@ -168,9 +154,9 @@ template<typename Arith_t> class CDT_input {
* edge is part of a given output edge. See the comment below for how
* to decode the entries in the edge_orig table.
*/
template<typename Arith_t> class CDT_result {
template<typename T> class CDT_result {
public:
Array<vec2<Arith_t>> vert;
Array<VecBase<T, 2>> vert;
Array<std::pair<int, int>> edge;
Array<Vector<int>> face;
/* The orig vectors are only populated if the need_ids input field is true. */

View File

@ -50,7 +50,7 @@ template<typename T> struct AngleRadianBase {
static AngleRadianBase from_degree(const T &degrees)
{
return degrees * T(M_PI / 180.0);
return degrees * T(numbers::pi / 180.0);
}
/** Conversions. */
@ -64,7 +64,7 @@ template<typename T> struct AngleRadianBase {
/* Return angle value in degree. */
T degree() const
{
return value_ * T(180.0 / M_PI);
return value_ * T(180.0 / numbers::pi);
}
/* Return angle value in radian. */
@ -80,7 +80,7 @@ template<typename T> struct AngleRadianBase {
*/
AngleRadianBase wrapped() const
{
return math::mod_periodic(value_ + T(M_PI), T(2 * M_PI)) - T(M_PI);
return math::mod_periodic(value_ + T(numbers::pi), T(2 * numbers::pi)) - T(numbers::pi);
}
/**
@ -197,7 +197,7 @@ template<typename T> struct AngleCartesianBase {
static AngleCartesianBase from_degree(const T &degrees)
{
return AngleCartesianBase(degrees * T(M_PI / 180.0));
return AngleCartesianBase(degrees * T(numbers::pi / 180.0));
}
/**
@ -224,7 +224,7 @@ template<typename T> struct AngleCartesianBase {
/* Return angle value in degree. */
T degree() const
{
return T(*this) * T(180.0 / M_PI);
return T(*this) * T(180.0 / numbers::pi);
}
/* Return angle value in radian. */
@ -420,19 +420,19 @@ template<typename T = float> struct AngleFraction {
const bool is_negative = numerator_ < 0;
/* TODO jump table. */
if (abs(numerator_) == denominator_ * 2) {
return is_negative ? T(-M_PI * 2) : T(M_PI * 2);
return is_negative ? T(-numbers::pi * 2) : T(numbers::pi * 2);
}
if (abs(numerator_) == denominator_) {
return is_negative ? T(-M_PI) : T(M_PI);
return is_negative ? T(-numbers::pi) : T(numbers::pi);
}
if (numerator_ == 0) {
return T(0);
}
if (abs(numerator_) * 2 == denominator_) {
return is_negative ? T(-M_PI_2) : T(M_PI_2);
return is_negative ? T(-numbers::pi * 0.5) : T(numbers::pi * 0.5);
}
if (abs(numerator_) * 4 == denominator_) {
return is_negative ? T(-M_PI_4) : T(M_PI_4);
return is_negative ? T(-numbers::pi * 0.25) : T(numbers::pi * 0.25);
}
/* TODO(fclem): No idea if this is precise or not. Just doing something for now. */
const int64_t number_of_pi = numerator_ / denominator_;
@ -442,14 +442,14 @@ template<typename T = float> struct AngleFraction {
/* TODO(fclem): This is conservative. Could find a better threshold. */
if (slice_numerator > 0xFFFFFFFF || denominator_ > 0xFFFFFFFF) {
/* Certainly loose precision. */
slice_of_pi = T(M_PI) * slice_numerator / T(denominator_);
slice_of_pi = T(numbers::pi) * slice_numerator / T(denominator_);
}
else {
/* Pi as a fraction can be expressed as 80143857 / 25510582 with 15th digit of precision. */
slice_of_pi = T(slice_numerator * 80143857) / T(denominator_ * 25510582);
}
/* If angle is inside [-pi..pi] range, `number_of_pi` is 0 and has no effect on precision. */
return slice_of_pi + T(M_PI) * number_of_pi;
return slice_of_pi + T(numbers::pi) * number_of_pi;
}
/** Methods. */
@ -631,7 +631,7 @@ template<typename T = float> struct AngleFraction {
break;
case 1:
case 3:
x = y = T(M_SQRT1_2);
x = y = math::rcp(T(numbers::sqrt2));
break;
default:
BLI_assert_unreachable();
@ -661,7 +661,7 @@ template<typename T = float> struct AngleFraction {
}
/* Resulting angle should be oscillating in [0..pi/4] range. */
BLI_assert(a.numerator_ >= 0 && a.numerator_ <= a.denominator_ / 4);
T angle = T(M_PI) * (T(a.numerator_) / T(a.denominator_));
T angle = T(numbers::pi) * (T(a.numerator_) / T(a.denominator_));
x = math::cos(angle);
y = math::sin(angle);
/* Diagonal symmetry "unfolding". */

View File

@ -12,7 +12,7 @@
#include <cmath>
#include <type_traits>
#include "BLI_math_base.h"
#include "BLI_math_numbers.hh"
#include "BLI_utildefines.h"
namespace blender::math {
@ -184,7 +184,7 @@ template<typename T> inline T exp(const T &x)
template<typename T> inline T safe_acos(const T &a)
{
if (UNLIKELY(a <= T(-1))) {
return T(M_PI);
return T(numbers::pi);
}
else if (UNLIKELY(a >= T(1))) {
return T(0);
@ -207,7 +207,7 @@ inline float safe_acos_approx(float x)
*/
const float a = std::sqrt(1.0f - m) *
(1.5707963267f + m * (-0.213300989f + m * (0.077980478f + m * -0.02164095f)));
return x < 0.0f ? float(M_PI) - a : a;
return x < 0.0f ? float(numbers::pi) - a : a;
}
template<typename T> inline T asin(const T &a)

View File

@ -253,7 +253,7 @@ template<typename MatT> [[nodiscard]] MatT orthogonalize(const MatT &mat, const
/**
* Construct a transformation that is pivoted around the given origin point. So for instance,
* from_origin_transform<MatT>(from_rotation(M_PI_2), float2(0.0f, 2.0f))
* from_origin_transform<MatT>(from_rotation(numbers::pi * 0.5), float2(0.0f, 2.0f))
* will construct a transformation representing a 90 degree rotation around the point (0, 2).
*/
template<typename MatT, typename VectorT>
@ -985,10 +985,10 @@ MatBase<T, NumCol, NumRow> from_rotation(const QuaternionBase<T> &rotation)
{
using MatT = MatBase<T, NumCol, NumRow>;
using DoublePrecision = typename TypeTraits<T>::DoublePrecision;
const DoublePrecision q0 = M_SQRT2 * DoublePrecision(rotation.w);
const DoublePrecision q1 = M_SQRT2 * DoublePrecision(rotation.x);
const DoublePrecision q2 = M_SQRT2 * DoublePrecision(rotation.y);
const DoublePrecision q3 = M_SQRT2 * DoublePrecision(rotation.z);
const DoublePrecision q0 = numbers::sqrt2 * DoublePrecision(rotation.w);
const DoublePrecision q1 = numbers::sqrt2 * DoublePrecision(rotation.x);
const DoublePrecision q2 = numbers::sqrt2 * DoublePrecision(rotation.y);
const DoublePrecision q3 = numbers::sqrt2 * DoublePrecision(rotation.z);
const DoublePrecision qda = q0 * q1;
const DoublePrecision qdb = q0 * q2;

View File

@ -0,0 +1,84 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* Contains the same definitions as the C++20 <numbers> header and will be replaced when we switch.
*/
#include <type_traits>
#include "BLI_utildefines.h"
namespace blender::math::numbers {
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T e_v = 2.718281828459045235360287471352662498L;
/* log_2 e */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T log2e_v = 1.442695040888963407359924681001892137L;
/* log_10 e */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T log10e_v = 0.434294481903251827651128918916605082L;
/* pi */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T pi_v = 3.141592653589793238462643383279502884L;
/* 1/pi */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T inv_pi_v = 0.318309886183790671537767526745028724L;
/* 1/sqrt(pi) */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T inv_sqrtpi_v = 0.564189583547756286948079451560772586L;
/* log_e 2 */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T ln2_v = 0.693147180559945309417232121458176568L;
/* log_e 10 */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T ln10_v = 2.302585092994045684017991454684364208L;
/* sqrt(2) */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T sqrt2_v = 1.414213562373095048801688724209698079L;
/* sqrt(3) */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T sqrt3_v = 1.732050807568877293527446341505872367L;
/* 1/sqrt(3) */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T inv_sqrt3_v = 0.577350269189625764509148780501957456L;
/* The Euler-Mascheroni constant */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T egamma_v = 0.577215664901532860606512090082402431L;
/* The golden ratio, (1+sqrt(5))/2 */
template<typename T, BLI_ENABLE_IF((std::is_floating_point_v<T>))>
inline constexpr T phi_v = 1.618033988749894848204586834365638118L;
inline constexpr double e = e_v<double>;
inline constexpr double log2e = log2e_v<double>;
inline constexpr double log10e = log10e_v<double>;
inline constexpr double pi = pi_v<double>;
inline constexpr double inv_pi = inv_pi_v<double>;
inline constexpr double inv_sqrtpi = inv_sqrtpi_v<double>;
inline constexpr double ln2 = ln2_v<double>;
inline constexpr double ln10 = ln10_v<double>;
inline constexpr double sqrt2 = sqrt2_v<double>;
inline constexpr double sqrt3 = sqrt3_v<double>;
inline constexpr double inv_sqrt3 = inv_sqrt3_v<double>;
inline constexpr double egamma = egamma_v<double>;
inline constexpr double phi = phi_v<double>;
} // namespace blender::math::numbers

View File

@ -407,49 +407,49 @@ template<typename T> QuaternionBase<T> to_quaternion(const CartesianBasis &rotat
case map(AxisSigned::Z_POS, AxisSigned::X_POS, AxisSigned::Y_POS):
return QuaternionBase<T>{T(0.5), T(-0.5), T(-0.5), T(-0.5)};
case map(AxisSigned::Y_NEG, AxisSigned::X_POS, AxisSigned::Z_POS):
return QuaternionBase<T>{T(M_SQRT1_2), T(0), T(0), T(-M_SQRT1_2)};
return QuaternionBase<T>{T(rcp(numbers::sqrt2)), T(0), T(0), T(-rcp(numbers::sqrt2))};
case map(AxisSigned::Z_NEG, AxisSigned::X_POS, AxisSigned::Y_NEG):
return QuaternionBase<T>{T(0.5), T(0.5), T(0.5), T(-0.5)};
case map(AxisSigned::Y_POS, AxisSigned::X_POS, AxisSigned::Z_NEG):
return QuaternionBase<T>{T(0), T(M_SQRT1_2), T(M_SQRT1_2), T(0)};
return QuaternionBase<T>{T(0), T(rcp(numbers::sqrt2)), T(rcp(numbers::sqrt2)), T(0)};
case map(AxisSigned::Z_NEG, AxisSigned::Y_POS, AxisSigned::X_POS):
return QuaternionBase<T>{T(M_SQRT1_2), T(0), T(M_SQRT1_2), T(0)};
return QuaternionBase<T>{T(rcp(numbers::sqrt2)), T(0), T(rcp(numbers::sqrt2)), T(0)};
case map(AxisSigned::Z_POS, AxisSigned::Y_POS, AxisSigned::X_NEG):
return QuaternionBase<T>{T(M_SQRT1_2), T(0), T(-M_SQRT1_2), T(0)};
return QuaternionBase<T>{T(rcp(numbers::sqrt2)), T(0), T(-rcp(numbers::sqrt2)), T(0)};
case map(AxisSigned::X_NEG, AxisSigned::Y_POS, AxisSigned::Z_NEG):
return QuaternionBase<T>{T(0), T(0), T(1), T(0)};
case map(AxisSigned::Y_POS, AxisSigned::Z_POS, AxisSigned::X_POS):
return QuaternionBase<T>{T(0.5), T(0.5), T(0.5), T(0.5)};
case map(AxisSigned::X_NEG, AxisSigned::Z_POS, AxisSigned::Y_POS):
return QuaternionBase<T>{T(0), T(0), T(M_SQRT1_2), T(M_SQRT1_2)};
return QuaternionBase<T>{T(0), T(0), T(rcp(numbers::sqrt2)), T(rcp(numbers::sqrt2))};
case map(AxisSigned::Y_NEG, AxisSigned::Z_POS, AxisSigned::X_NEG):
return QuaternionBase<T>{T(0.5), T(0.5), T(-0.5), T(-0.5)};
case map(AxisSigned::X_POS, AxisSigned::Z_POS, AxisSigned::Y_NEG):
return QuaternionBase<T>{T(M_SQRT1_2), T(M_SQRT1_2), T(0), T(0)};
return QuaternionBase<T>{T(rcp(numbers::sqrt2)), T(rcp(numbers::sqrt2)), T(0), T(0)};
case map(AxisSigned::Z_NEG, AxisSigned::X_NEG, AxisSigned::Y_POS):
return QuaternionBase<T>{T(0.5), T(-0.5), T(0.5), T(0.5)};
case map(AxisSigned::Y_POS, AxisSigned::X_NEG, AxisSigned::Z_POS):
return QuaternionBase<T>{T(M_SQRT1_2), T(0), T(0), T(M_SQRT1_2)};
return QuaternionBase<T>{T(rcp(numbers::sqrt2)), T(0), T(0), T(rcp(numbers::sqrt2))};
case map(AxisSigned::Z_POS, AxisSigned::X_NEG, AxisSigned::Y_NEG):
return QuaternionBase<T>{T(0.5), T(0.5), T(-0.5), T(0.5)};
case map(AxisSigned::Y_NEG, AxisSigned::X_NEG, AxisSigned::Z_NEG):
return QuaternionBase<T>{T(0), T(-M_SQRT1_2), T(M_SQRT1_2), T(0)};
return QuaternionBase<T>{T(0), T(-rcp(numbers::sqrt2)), T(rcp(numbers::sqrt2)), T(0)};
case map(AxisSigned::Z_POS, AxisSigned::Y_NEG, AxisSigned::X_POS):
return QuaternionBase<T>{T(0), T(M_SQRT1_2), T(0), T(M_SQRT1_2)};
return QuaternionBase<T>{T(0), T(rcp(numbers::sqrt2)), T(0), T(rcp(numbers::sqrt2))};
case map(AxisSigned::X_NEG, AxisSigned::Y_NEG, AxisSigned::Z_POS):
return QuaternionBase<T>{T(0), T(0), T(0), T(1)};
case map(AxisSigned::Z_NEG, AxisSigned::Y_NEG, AxisSigned::X_NEG):
return QuaternionBase<T>{T(0), T(-M_SQRT1_2), T(0), T(M_SQRT1_2)};
return QuaternionBase<T>{T(0), T(-rcp(numbers::sqrt2)), T(0), T(rcp(numbers::sqrt2))};
case map(AxisSigned::X_POS, AxisSigned::Y_NEG, AxisSigned::Z_NEG):
return QuaternionBase<T>{T(0), T(1), T(0), T(0)};
case map(AxisSigned::Y_NEG, AxisSigned::Z_NEG, AxisSigned::X_POS):
return QuaternionBase<T>{T(0.5), T(-0.5), T(0.5), T(-0.5)};
case map(AxisSigned::X_POS, AxisSigned::Z_NEG, AxisSigned::Y_POS):
return QuaternionBase<T>{T(M_SQRT1_2), T(-M_SQRT1_2), T(0), T(0)};
return QuaternionBase<T>{T(rcp(numbers::sqrt2)), T(-rcp(numbers::sqrt2)), T(0), T(0)};
case map(AxisSigned::Y_POS, AxisSigned::Z_NEG, AxisSigned::X_NEG):
return QuaternionBase<T>{T(0.5), T(-0.5), T(-0.5), T(0.5)};
case map(AxisSigned::X_NEG, AxisSigned::Z_NEG, AxisSigned::Y_NEG):
return QuaternionBase<T>{T(0), T(0), T(-M_SQRT1_2), T(M_SQRT1_2)};
return QuaternionBase<T>{T(0), T(0), T(-rcp(numbers::sqrt2)), T(rcp(numbers::sqrt2))};
}
}

View File

@ -342,42 +342,24 @@ struct BoundingBox {
void combine(const float3 &p)
{
min.x = min_ff(min.x, p.x);
min.y = min_ff(min.y, p.y);
min.z = min_ff(min.z, p.z);
max.x = max_ff(max.x, p.x);
max.y = max_ff(max.y, p.y);
max.z = max_ff(max.z, p.z);
math::min_max(p, this->min, this->max);
}
void combine(const double3 &p)
{
min.x = min_ff(min.x, float(p.x));
min.y = min_ff(min.y, float(p.y));
min.z = min_ff(min.z, float(p.z));
max.x = max_ff(max.x, float(p.x));
max.y = max_ff(max.y, float(p.y));
max.z = max_ff(max.z, float(p.z));
math::min_max(float3(p), this->min, this->max);
}
void combine(const BoundingBox &bb)
{
min.x = min_ff(min.x, bb.min.x);
min.y = min_ff(min.y, bb.min.y);
min.z = min_ff(min.z, bb.min.z);
max.x = max_ff(max.x, bb.max.x);
max.y = max_ff(max.y, bb.max.y);
max.z = max_ff(max.z, bb.max.z);
min = math::min(this->min, bb.min);
max = math::max(this->max, bb.max);
}
void expand(float pad)
{
min.x -= pad;
min.y -= pad;
min.z -= pad;
max.x += pad;
max.y += pad;
max.z += pad;
min -= pad;
max += pad;
}
};

View File

@ -292,6 +292,7 @@ set(SRC
BLI_math_matrix.hh
BLI_math_matrix_types.hh
BLI_math_mpq.hh
BLI_math_numbers.hh
BLI_math_quaternion.hh
BLI_math_quaternion_types.hh
BLI_math_rotation.h

View File

@ -76,21 +76,21 @@ template<> double math_to_double<double>(const double v)
* While this could be cleaned up some, it is usable by other routines in Blender
* that need to keep track of a 2D arrangement, with topology.
*/
template<typename Arith_t> struct CDTVert;
template<typename Arith_t> struct CDTEdge;
template<typename Arith_t> struct CDTFace;
template<typename T> struct CDTVert;
template<typename T> struct CDTEdge;
template<typename T> struct CDTFace;
template<typename Arith_t> struct SymEdge {
template<typename T> struct SymEdge {
/** Next #SymEdge in face, doing CCW traversal of face. */
SymEdge<Arith_t> *next{nullptr};
SymEdge<T> *next{nullptr};
/** Next #SymEdge CCW around vert. */
SymEdge<Arith_t> *rot{nullptr};
SymEdge<T> *rot{nullptr};
/** Vert at origin. */
CDTVert<Arith_t> *vert{nullptr};
CDTVert<T> *vert{nullptr};
/** Un-directed edge this is for. */
CDTEdge<Arith_t> *edge{nullptr};
CDTEdge<T> *edge{nullptr};
/** Face on left side. */
CDTFace<Arith_t> *face{nullptr};
CDTFace<T> *face{nullptr};
SymEdge() = default;
};
@ -112,67 +112,62 @@ template<typename T> inline SymEdge<T> *prev(const SymEdge<T> *se)
/** A coordinate class with extra information for fast filtered orient tests. */
template<typename T> struct FatCo {
vec2<T> exact;
vec2<double> approx;
vec2<double> abs_approx;
VecBase<T, 2> exact;
double2 approx;
double2 abs_approx;
FatCo();
#ifdef WITH_GMP
FatCo(const vec2<mpq_class> &v);
FatCo(const mpq2 &v);
#endif
FatCo(const vec2<double> &v);
FatCo(const double2 &v);
};
#ifdef WITH_GMP
template<> struct FatCo<mpq_class> {
vec2<mpq_class> exact;
vec2<double> approx;
vec2<double> abs_approx;
mpq2 exact;
double2 approx;
double2 abs_approx;
FatCo()
: exact(vec2<mpq_class>(0, 0)), approx(vec2<double>(0, 0)), abs_approx(vec2<double>(0, 0))
{
}
FatCo() : exact(mpq2(0, 0)), approx(double2(0, 0)), abs_approx(double2(0, 0)) {}
FatCo(const vec2<mpq_class> &v)
FatCo(const mpq2 &v)
{
exact = v;
approx = vec2<double>(v.x.get_d(), v.y.get_d());
abs_approx = vec2<double>(fabs(approx.x), fabs(approx.y));
approx = double2(v.x.get_d(), v.y.get_d());
abs_approx = double2(fabs(approx.x), fabs(approx.y));
}
FatCo(const vec2<double> &v)
FatCo(const double2 &v)
{
exact = vec2<mpq_class>(v.x, v.y);
exact = mpq2(v.x, v.y);
approx = v;
abs_approx = vec2<double>(fabs(approx.x), fabs(approx.y));
abs_approx = double2(fabs(approx.x), fabs(approx.y));
}
};
#endif
template<> struct FatCo<double> {
vec2<double> exact;
vec2<double> approx;
vec2<double> abs_approx;
double2 exact;
double2 approx;
double2 abs_approx;
FatCo() : exact(vec2<double>(0, 0)), approx(vec2<double>(0, 0)), abs_approx(vec2<double>(0, 0))
{
}
FatCo() : exact(double2(0, 0)), approx(double2(0, 0)), abs_approx(double2(0, 0)) {}
#ifdef WITH_GMP
FatCo(const vec2<mpq_class> &v)
FatCo(const mpq2 &v)
{
exact = vec2<double>(v.x.get_d(), v.y.get_d());
exact = double2(v.x.get_d(), v.y.get_d());
approx = exact;
abs_approx = vec2<double>(fabs(approx.x), fabs(approx.y));
abs_approx = double2(fabs(approx.x), fabs(approx.y));
}
#endif
FatCo(const vec2<double> &v)
FatCo(const double2 &v)
{
exact = v;
approx = v;
abs_approx = vec2<double>(fabs(approx.x), fabs(approx.y));
abs_approx = double2(fabs(approx.x), fabs(approx.y));
}
};
@ -197,23 +192,23 @@ template<typename T> struct CDTVert {
int visit_index{0};
CDTVert() = default;
explicit CDTVert(const vec2<T> &pt);
explicit CDTVert(const VecBase<T, 2> &pt);
};
template<typename Arith_t> struct CDTEdge {
template<typename T> struct CDTEdge {
/** Set of input edge ids that this is part of.
* If don't need_ids, then should contain 0 if it is a constrained edge,
* else empty. */
blender::Set<int> input_ids;
/** The directed edges for this edge. */
SymEdge<Arith_t> symedges[2]{SymEdge<Arith_t>(), SymEdge<Arith_t>()};
SymEdge<T> symedges[2]{SymEdge<T>(), SymEdge<T>()};
CDTEdge() = default;
};
template<typename Arith_t> struct CDTFace {
template<typename T> struct CDTFace {
/** A symedge in face; only used during output, so only valid then. */
SymEdge<Arith_t> *symedge{nullptr};
SymEdge<T> *symedge{nullptr};
/** Set of input face ids that this is part of.
* If don't need_ids, then should contain 0 if it is part of a constrained face,
* else empty. */
@ -228,19 +223,19 @@ template<typename Arith_t> struct CDTFace {
CDTFace() = default;
};
template<typename Arith_t> struct CDTArrangement {
template<typename T> struct CDTArrangement {
/* The arrangement owns the memory pointed to by the pointers in these vectors.
* They are pointers instead of actual structures because these vectors may be resized and
* other elements refer to the elements by pointer. */
/** The verts. Some may be merged to others (see their merge_to_index). */
Vector<CDTVert<Arith_t> *> verts;
Vector<CDTVert<T> *> verts;
/** The edges. Some may be deleted (SymEdge next and rot pointers are null). */
Vector<CDTEdge<Arith_t> *> edges;
Vector<CDTEdge<T> *> edges;
/** The faces. Some may be deleted (see their delete member). */
Vector<CDTFace<Arith_t> *> faces;
Vector<CDTFace<T> *> faces;
/** Which CDTFace is the outer face. */
CDTFace<Arith_t> *outer_face{nullptr};
CDTFace<T> *outer_face{nullptr};
CDTArrangement() = default;
~CDTArrangement();
@ -253,7 +248,7 @@ template<typename Arith_t> struct CDTArrangement {
* Add a new vertex to the arrangement, with the given 2D coordinate.
* It will not be connected to anything yet.
*/
CDTVert<Arith_t> *add_vert(const vec2<Arith_t> &pt);
CDTVert<T> *add_vert(const VecBase<T, 2> &pt);
/**
* Add an edge from v1 to v2. The edge will have a left face and a right face,
@ -261,19 +256,16 @@ template<typename Arith_t> struct CDTArrangement {
* If the vertices do not yet have a #SymEdge pointer,
* their pointer is set to the #SymEdge in this new edge.
*/
CDTEdge<Arith_t> *add_edge(CDTVert<Arith_t> *v1,
CDTVert<Arith_t> *v2,
CDTFace<Arith_t> *fleft,
CDTFace<Arith_t> *fright);
CDTEdge<T> *add_edge(CDTVert<T> *v1, CDTVert<T> *v2, CDTFace<T> *fleft, CDTFace<T> *fright);
/**
* Add a new face. It is disconnected until an add_edge makes it the
* left or right face of an edge.
*/
CDTFace<Arith_t> *add_face();
CDTFace<T> *add_face();
/** Make a new edge from v to se->vert, splicing it in. */
CDTEdge<Arith_t> *add_vert_to_symedge_edge(CDTVert<Arith_t> *v, SymEdge<Arith_t> *se);
CDTEdge<T> *add_vert_to_symedge_edge(CDTVert<T> *v, SymEdge<T> *se);
/**
* Assuming s1 and s2 are both #SymEdge's in a face with > 3 sides and one is not the next of the
@ -281,34 +273,34 @@ template<typename Arith_t> struct CDTArrangement {
* be the one that s1 has as left face, and a new face will be added and made s2 and its
* next-cycle's left face.
*/
CDTEdge<Arith_t> *add_diagonal(SymEdge<Arith_t> *s1, SymEdge<Arith_t> *s2);
CDTEdge<T> *add_diagonal(SymEdge<T> *s1, SymEdge<T> *s2);
/**
* Connect the verts of se1 and se2, assuming that currently those two #SymEdge's are on the
* outer boundary (have face == outer_face) of two components that are isolated from each other.
*/
CDTEdge<Arith_t> *connect_separate_parts(SymEdge<Arith_t> *se1, SymEdge<Arith_t> *se2);
CDTEdge<T> *connect_separate_parts(SymEdge<T> *se1, SymEdge<T> *se2);
/**
* Split se at fraction lambda, and return the new #CDTEdge that is the new second half.
* Copy the edge input_ids into the new one.
*/
CDTEdge<Arith_t> *split_edge(SymEdge<Arith_t> *se, Arith_t lambda);
CDTEdge<T> *split_edge(SymEdge<T> *se, T lambda);
/**
* Delete an edge. The new combined face on either side of the deleted edge will be the one that
* was e's face. There will now be an unused face, which will be marked deleted, and an unused
* #CDTEdge, marked by setting the next and rot pointers of its #SymEdge's to #nullptr.
*/
void delete_edge(SymEdge<Arith_t> *se);
void delete_edge(SymEdge<T> *se);
/**
* If the vertex with index i in the vert array has not been merge, return it.
* Else return the one that it has merged to.
*/
CDTVert<Arith_t> *get_vert_resolve_merge(int i)
CDTVert<T> *get_vert_resolve_merge(int i)
{
CDTVert<Arith_t> *v = this->verts[i];
CDTVert<T> *v = this->verts[i];
if (v->merge_to_index != -1) {
v = this->verts[v->merge_to_index];
}
@ -499,18 +491,10 @@ template<typename T> void cdt_draw(const std::string &label, const CDTArrangemen
if (cdt.verts.is_empty()) {
return;
}
vec2<double> vmin(DBL_MAX, DBL_MAX);
vec2<double> vmax(-DBL_MAX, -DBL_MAX);
double2 vmin(std::numeric_limits<double>::max());
double2 vmax(std::numeric_limits<double>::lowest());
for (const CDTVert<T> *v : cdt.verts) {
for (int i = 0; i < 2; ++i) {
double dvi = v->co.approx[i];
if (dvi < vmin[i]) {
vmin[i] = dvi;
}
if (dvi > vmax[i]) {
vmax[i] = dvi;
}
}
math::min_max(v->co.approx, vmin, vmax);
}
double draw_margin = ((vmax.x - vmin.x) + (vmax.y - vmin.y)) * margin_expand;
double minx = vmin.x - draw_margin;
@ -557,8 +541,8 @@ template<typename T> void cdt_draw(const std::string &label, const CDTArrangemen
}
const CDTVert<T> *u = e->symedges[0].vert;
const CDTVert<T> *v = e->symedges[1].vert;
const vec2<double> &uco = u->co.approx;
const vec2<double> &vco = v->co.approx;
const double2 &uco = u->co.approx;
const double2 &vco = v->co.approx;
int strokew = e->input_ids.size() == 0 ? thin_line : thick_line;
f << R"(<line fill="none" stroke="black" stroke-width=")" << strokew << "\" x1=\""
<< SX(uco[0]) << "\" y1=\"" << SY(uco[1]) << "\" x2=\"" << SX(vco[0]) << "\" y2=\""
@ -603,7 +587,7 @@ template<typename T> void cdt_draw(const std::string &label, const CDTArrangemen
if (se_face_start != nullptr) {
/* Find center of face. */
int face_nverts = 0;
vec2<double> cen(0.0, 0.0);
double2 cen(0.0, 0.0);
if (face == cdt.outer_face) {
cen.x = minx;
cen.y = miny;
@ -756,12 +740,12 @@ bool in_line<mpq_class>(const FatCo<mpq_class> &a,
const FatCo<mpq_class> &b,
const FatCo<mpq_class> &c)
{
vec2<double> ab = b.approx - a.approx;
vec2<double> bc = c.approx - b.approx;
vec2<double> ac = c.approx - a.approx;
vec2<double> supremum_ab = b.abs_approx + a.abs_approx;
vec2<double> supremum_bc = c.abs_approx + b.abs_approx;
vec2<double> supremum_ac = c.abs_approx + a.abs_approx;
double2 ab = b.approx - a.approx;
double2 bc = c.approx - b.approx;
double2 ac = c.approx - a.approx;
double2 supremum_ab = b.abs_approx + a.abs_approx;
double2 supremum_bc = c.abs_approx + b.abs_approx;
double2 supremum_ac = c.abs_approx + a.abs_approx;
double dot_ab_ac = ab.x * ac.x + ab.y * ac.y;
double supremum_dot_ab_ac = supremum_ab.x * supremum_ac.x + supremum_ab.y * supremum_ac.y;
constexpr double index = 6;
@ -775,12 +759,12 @@ bool in_line<mpq_class>(const FatCo<mpq_class> &a,
if (dot_bc_ac < -err_bound) {
return false;
}
vec2<mpq_class> exact_ab = b.exact - a.exact;
vec2<mpq_class> exact_ac = c.exact - a.exact;
mpq2 exact_ab = b.exact - a.exact;
mpq2 exact_ac = c.exact - a.exact;
if (dot(exact_ab, exact_ac) < 0) {
return false;
}
vec2<mpq_class> exact_bc = c.exact - b.exact;
mpq2 exact_bc = c.exact - b.exact;
return dot(exact_bc, exact_ac) >= 0;
}
#endif
@ -788,16 +772,16 @@ bool in_line<mpq_class>(const FatCo<mpq_class> &a,
template<>
bool in_line<double>(const FatCo<double> &a, const FatCo<double> &b, const FatCo<double> &c)
{
vec2<double> ab = b.approx - a.approx;
vec2<double> ac = c.approx - a.approx;
double2 ab = b.approx - a.approx;
double2 ac = c.approx - a.approx;
if (dot(ab, ac) < 0) {
return false;
}
vec2<double> bc = c.approx - b.approx;
double2 bc = c.approx - b.approx;
return dot(bc, ac) >= 0;
}
template<> CDTVert<double>::CDTVert(const vec2<double> &pt)
template<> CDTVert<double>::CDTVert(const double2 &pt)
{
this->co.exact = pt;
this->co.approx = pt;
@ -809,7 +793,7 @@ template<> CDTVert<double>::CDTVert(const vec2<double> &pt)
}
#ifdef WITH_GMP
template<> CDTVert<mpq_class>::CDTVert(const vec2<mpq_class> &pt)
template<> CDTVert<mpq_class>::CDTVert(const mpq2 &pt)
{
this->co.exact = pt;
this->co.approx = double2(pt.x.get_d(), pt.y.get_d());
@ -821,7 +805,7 @@ template<> CDTVert<mpq_class>::CDTVert(const vec2<mpq_class> &pt)
}
#endif
template<typename T> CDTVert<T> *CDTArrangement<T>::add_vert(const vec2<T> &pt)
template<typename T> CDTVert<T> *CDTArrangement<T>::add_vert(const VecBase<T, 2> &pt)
{
CDTVert<T> *v = new CDTVert<T>(pt);
int index = this->verts.append_and_get_index(v);
@ -1064,8 +1048,8 @@ CDTEdge<T> *CDTArrangement<T>::connect_separate_parts(SymEdge<T> *se1, SymEdge<T
template<typename T> CDTEdge<T> *CDTArrangement<T>::split_edge(SymEdge<T> *se, T lambda)
{
/* Split e at lambda. */
const vec2<T> *a = &se->vert->co.exact;
const vec2<T> *b = &se->next->vert->co.exact;
const VecBase<T, 2> *a = &se->vert->co.exact;
const VecBase<T, 2> *b = &se->next->vert->co.exact;
SymEdge<T> *sesym = sym(se);
SymEdge<T> *sesymprev = prev(sesym);
SymEdge<T> *sesymprevsym = sym(sesymprev);
@ -1177,8 +1161,8 @@ template<typename T> class SiteInfo {
*/
template<typename T> bool site_lexicographic_sort(const SiteInfo<T> &a, const SiteInfo<T> &b)
{
const vec2<T> &co_a = a.v->co.exact;
const vec2<T> &co_b = b.v->co.exact;
const VecBase<T, 2> &co_a = a.v->co.exact;
const VecBase<T, 2> &co_b = b.v->co.exact;
if (co_a[0] < co_b[0]) {
return true;
}
@ -1697,7 +1681,7 @@ void fill_crossdata_for_intersect(const FatCo<T> &curco,
auto isect = isect_seg_seg(va->co.exact, vb->co.exact, curco.exact, v2->co.exact);
T &lambda = isect.lambda;
switch (isect.kind) {
case isect_result<vec2<T>>::LINE_LINE_CROSS: {
case isect_result<VecBase<T, 2>>::LINE_LINE_CROSS: {
#ifdef WITH_GMP
if (!std::is_same<T, mpq_class>::value) {
#else
@ -1725,7 +1709,7 @@ void fill_crossdata_for_intersect(const FatCo<T> &curco,
}
break;
}
case isect_result<vec2<T>>::LINE_LINE_EXACT: {
case isect_result<VecBase<T, 2>>::LINE_LINE_EXACT: {
if (lambda == 0) {
fill_crossdata_for_through_vert(va, se_vcva, cd, cd_next);
}
@ -1740,7 +1724,7 @@ void fill_crossdata_for_intersect(const FatCo<T> &curco,
}
break;
}
case isect_result<vec2<T>>::LINE_LINE_NONE: {
case isect_result<VecBase<T, 2>>::LINE_LINE_NONE: {
#ifdef WITH_GMP
if (std::is_same<T, mpq_class>::value) {
BLI_assert(false);
@ -1756,7 +1740,7 @@ void fill_crossdata_for_intersect(const FatCo<T> &curco,
}
break;
}
case isect_result<vec2<T>>::LINE_LINE_COLINEAR: {
case isect_result<VecBase<T, 2>>::LINE_LINE_COLINEAR: {
if (distance_squared(va->co.approx, v2->co.approx) <=
distance_squared(vb->co.approx, v2->co.approx))
{
@ -1836,7 +1820,7 @@ void get_next_crossing_from_edge(CrossData<T> *cd,
{
CDTVert<T> *va = cd->in->vert;
CDTVert<T> *vb = cd->in->next->vert;
vec2<T> curco = interpolate(va->co.exact, vb->co.exact, cd->lambda);
VecBase<T, 2> curco = interpolate(va->co.exact, vb->co.exact, cd->lambda);
FatCo<T> fat_curco(curco);
SymEdge<T> *se_ac = sym(cd->in)->next;
CDTVert<T> *vc = se_ac->next->vert;
@ -2185,7 +2169,7 @@ static int power_of_10_greater_equal_to(int x)
return 1;
}
int ans = 1;
BLI_assert(x < INT_MAX / 10);
BLI_assert(x < std::numeric_limits<int>::max() / 10);
while (ans < x) {
ans *= 10;
}
@ -2214,16 +2198,16 @@ int add_face_constraints(CDT_state<T> *cdt_state,
int maxflen = 0;
for (const int f : input_faces.index_range()) {
maxflen = max_ii(maxflen, input_faces[f].size());
maxflen = std::max<int>(maxflen, input_faces[f].size());
}
/* For convenience in debugging, make face_edge_offset be a power of 10. */
cdt_state->face_edge_offset = power_of_10_greater_equal_to(
max_ii(maxflen, cdt_state->face_edge_offset));
std::max(maxflen, cdt_state->face_edge_offset));
/* The original_edge encoding scheme doesn't work if the following is false.
* If we really have that many faces and that large a max face length that when multiplied
* together the are >= INT_MAX, then the Delaunay calculation will take unreasonably long anyway.
*/
BLI_assert(INT_MAX / cdt_state->face_edge_offset > input_faces.size());
BLI_assert(std::numeric_limits<int>::max() / cdt_state->face_edge_offset > input_faces.size());
int faces_added = 0;
for (const int f : input_faces.index_range()) {
const Span<int> face = input_faces[f];
@ -2376,8 +2360,8 @@ template<typename T> void remove_non_constraint_edges_leave_valid_bmesh(CDT_stat
if (!is_deleted_edge(e) && !is_constrained_edge(e)) {
dissolvable_edges.append(EdgeToSort<T>());
dissolvable_edges[i].e = e;
const vec2<double> &co1 = e->symedges[0].vert->co.approx;
const vec2<double> &co2 = e->symedges[1].vert->co.approx;
const double2 &co1 = e->symedges[0].vert->co.approx;
const double2 &co2 = e->symedges[1].vert->co.approx;
dissolvable_edges[i].len_squared = distance_squared(co1, co2);
i++;
}
@ -2544,7 +2528,7 @@ template<typename T> void detect_holes(CDT_state<T> *cdt_state)
/* Pick a ray end almost certain to be outside everything and in direction
* that is unlikely to hit a vertex or overlap an edge exactly. */
FatCo<T> ray_end;
ray_end.exact = vec2<T>(123456, 654321);
ray_end.exact = VecBase<T, 2>(123456, 654321);
for (int i : region_rep_face.index_range()) {
CDTFace<T> *f = region_rep_face[i];
FatCo<T> mid;
@ -2568,13 +2552,13 @@ template<typename T> void detect_holes(CDT_state<T> *cdt_state)
e->symedges[0].vert->co.exact,
e->symedges[1].vert->co.exact);
switch (isect.kind) {
case isect_result<vec2<T>>::LINE_LINE_CROSS: {
case isect_result<VecBase<T, 2>>::LINE_LINE_CROSS: {
hits++;
break;
}
case isect_result<vec2<T>>::LINE_LINE_EXACT:
case isect_result<vec2<T>>::LINE_LINE_NONE:
case isect_result<vec2<T>>::LINE_LINE_COLINEAR:
case isect_result<VecBase<T, 2>>::LINE_LINE_EXACT:
case isect_result<VecBase<T, 2>>::LINE_LINE_NONE:
case isect_result<VecBase<T, 2>>::LINE_LINE_COLINEAR:
break;
}
}
@ -2692,7 +2676,7 @@ CDT_result<T> get_cdt_output(CDT_state<T> *cdt_state,
}
}
}
result.vert = Array<vec2<T>>(nv);
result.vert = Array<VecBase<T, 2>>(nv);
if (cdt_state->need_ids) {
result.vert_orig = Array<Vector<int>>(nv);
}

View File

@ -744,7 +744,9 @@ std::optional<RawMaskIterator> IndexMask::find(const int64_t query_index) const
if (local_segment[index_in_segment] != local_query_index) {
return std::nullopt;
}
return RawMaskIterator{segment_i, int16_t(index_in_segment)};
const int64_t actual_index_in_segment = index_in_segment +
(segment_i == 0 ? begin_index_in_segment_ : 0);
return RawMaskIterator{segment_i, int16_t(actual_index_in_segment)};
}
bool IndexMask::contains(const int64_t query_index) const

View File

@ -45,7 +45,7 @@ template<typename T> CDT_input<T> fill_input_from_string(const char *spec)
if (nverts == 0) {
return CDT_input<T>();
}
Array<vec2<T>> verts(nverts);
Array<VecBase<T, 2>> verts(nverts);
Array<std::pair<int, int>> edges(nedges);
Array<Vector<int>> faces(nfaces);
int i = 0;
@ -55,7 +55,7 @@ template<typename T> CDT_input<T> fill_input_from_string(const char *spec)
iss >> dp0 >> dp1;
T p0(dp0);
T p1(dp1);
verts[i] = vec2<T>(p0, p1);
verts[i] = VecBase<T, 2>(p0, p1);
i++;
}
i = 0;
@ -264,7 +264,7 @@ static bool draw_append = false; /* Will be set to true after first call. */
template<typename T>
void graph_draw(const std::string &label,
const Array<vec2<T>> &verts,
const Array<VecBase<T, 2>> &verts,
const Array<std::pair<int, int>> &edges,
const Array<Vector<int>> &faces)
{
@ -286,9 +286,9 @@ void graph_draw(const std::string &label,
if (verts.is_empty()) {
return;
}
vec2<double> vmin(1e10, 1e10);
vec2<double> vmax(-1e10, -1e10);
for (const vec2<T> &v : verts) {
double2 vmin(1e10, 1e10);
double2 vmax(-1e10, -1e10);
for (const VecBase<T, 2> &v : verts) {
for (int i = 0; i < 2; ++i) {
double dvi = math_to_double(v[i]);
if (dvi < vmin[i]) {
@ -341,15 +341,15 @@ void graph_draw(const std::string &label,
for (const Vector<int> &fverts : faces) {
f << "<polygon fill=\"azure\" stroke=\"none\"\n points=\"";
for (int vi : fverts) {
const vec2<T> &co = verts[vi];
const VecBase<T, 2> &co = verts[vi];
f << SX(co[0]) << "," << SY(co[1]) << " ";
}
f << "\"\n />\n";
}
for (const std::pair<int, int> &e : edges) {
const vec2<T> &uco = verts[e.first];
const vec2<T> &vco = verts[e.second];
const VecBase<T, 2> &uco = verts[e.first];
const VecBase<T, 2> &vco = verts[e.second];
int strokew = thin_line;
f << R"(<line fill="none" stroke="black" stroke-width=")" << strokew << "\" x1=\""
<< SX(uco[0]) << "\" y1=\"" << SY(uco[1]) << "\" x2=\"" << SX(vco[0]) << "\" y2=\""
@ -364,7 +364,7 @@ void graph_draw(const std::string &label,
}
int i = 0;
for (const vec2<T> &vco : verts) {
for (const VecBase<T, 2> &vco : verts) {
f << R"(<circle fill="black" cx=")" << SX(vco[0]) << "\" cy=\"" << SY(vco[1]) << "\" r=\""
<< vert_radius << "\">\n";
f << " <title>[" << i << "]" << vco << "</title>\n";
@ -384,18 +384,18 @@ void graph_draw(const std::string &label,
/* Should tests draw their output to an html file? */
constexpr bool DO_DRAW = false;
template<typename T> void expect_coord_near(const vec2<T> &testco, const vec2<T> &refco);
template<typename T>
void expect_coord_near(const VecBase<T, 2> &testco, const VecBase<T, 2> &refco);
#ifdef WITH_GMP
template<>
void expect_coord_near<mpq_class>(const vec2<mpq_class> &testco, const vec2<mpq_class> &refco)
template<> void expect_coord_near<mpq_class>(const mpq2 &testco, const mpq2 &refco)
{
EXPECT_EQ(testco[0], refco[0]);
EXPECT_EQ(testco[0], refco[0]);
}
#endif
template<> void expect_coord_near<double>(const vec2<double> &testco, const vec2<double> &refco)
template<> void expect_coord_near<double>(const double2 &testco, const double2 &refco)
{
EXPECT_NEAR(testco[0], refco[0], 1e-5);
EXPECT_NEAR(testco[1], refco[1], 1e-5);
@ -428,7 +428,7 @@ template<typename T> void onept_test()
EXPECT_EQ(out.edge.size(), 0);
EXPECT_EQ(out.face.size(), 0);
if (out.vert.size() >= 1) {
expect_coord_near<T>(out.vert[0], vec2<T>(0, 0));
expect_coord_near<T>(out.vert[0], VecBase<T, 2>(0, 0));
}
}
@ -450,8 +450,8 @@ template<typename T> void twopt_test()
EXPECT_NE(v1_out, -1);
EXPECT_NE(v0_out, v1_out);
if (out.vert.size() >= 1) {
expect_coord_near<T>(out.vert[v0_out], vec2<T>(0.0, -0.75));
expect_coord_near<T>(out.vert[v1_out], vec2<T>(0.0, 0.75));
expect_coord_near<T>(out.vert[v0_out], VecBase<T, 2>(0.0, -0.75));
expect_coord_near<T>(out.vert[v1_out], VecBase<T, 2>(0.0, 0.75));
}
int e0_out = get_output_edge_index(out, v0_out, v1_out);
EXPECT_EQ(e0_out, 0);
@ -787,7 +787,7 @@ template<typename T> void crosssegs_test()
}
EXPECT_NE(v_intersect, -1);
if (v_intersect != -1) {
expect_coord_near<T>(out.vert[v_intersect], vec2<T>(0, 0));
expect_coord_near<T>(out.vert[v_intersect], VecBase<T, 2>(0, 0));
}
}
if (DO_DRAW) {
@ -1156,8 +1156,8 @@ template<typename T> void overlapfaces_test()
v_int1 = 13;
v_int2 = 12;
}
expect_coord_near<T>(out.vert[v_int1], vec2<T>(1, 0.5));
expect_coord_near<T>(out.vert[v_int2], vec2<T>(0.5, 1));
expect_coord_near<T>(out.vert[v_int1], VecBase<T, 2>(1, 0.5));
expect_coord_near<T>(out.vert[v_int2], VecBase<T, 2>(0.5, 1));
EXPECT_EQ(out.vert_orig[v_int1].size(), 0);
EXPECT_EQ(out.vert_orig[v_int2].size(), 0);
int f0_out = get_output_tri_index(out, v_out[1], v_int1, v_out[4]);
@ -1785,7 +1785,7 @@ void text_test(
constexpr int narcs = 4;
int b_npts = b_before_arcs_in.vert.size() + narcs * arc_points_num;
constexpr int b_nfaces = 3;
Array<vec2<T>> b_vert(b_npts);
Array<VecBase<T, 2>> b_vert(b_npts);
Array<Vector<int>> b_face(b_nfaces);
std::copy(b_before_arcs_in.vert.begin(), b_before_arcs_in.vert.end(), b_vert.begin());
std::copy(b_before_arcs_in.face.begin(), b_before_arcs_in.face.end(), b_face.begin());
@ -1819,16 +1819,16 @@ void text_test(
default:
BLI_assert(false);
}
vec2<T> start_co = b_vert[arc_origin_vert];
vec2<T> end_co = b_vert[arc_terminal_vert];
vec2<T> center_co = 0.5 * (start_co + end_co);
VecBase<T, 2> start_co = b_vert[arc_origin_vert];
VecBase<T, 2> end_co = b_vert[arc_terminal_vert];
VecBase<T, 2> center_co = 0.5 * (start_co + end_co);
BLI_assert(start_co[0] == end_co[0]);
double radius = abs(math_to_double<T>(end_co[1] - center_co[1]));
double angle_delta = M_PI / (arc_points_num + 1);
int start_vert = b_before_arcs_in.vert.size() + arc * arc_points_num;
Vector<int> &face = b_face[(arc <= 1) ? 0 : arc - 1];
for (int i = 0; i < arc_points_num; ++i) {
vec2<T> delta;
VecBase<T, 2> delta;
float ang = ccw ? (-M_PI_2 + (i + 1) * angle_delta) : (M_PI_2 - (i + 1) * angle_delta);
delta[0] = T(radius * cos(ang));
delta[1] = T(radius * sin(ang));
@ -1848,7 +1848,7 @@ void text_test(
in.face = b_face;
}
else {
in.vert = Array<vec2<T>>(tot_instances * b_vert.size());
in.vert = Array<VecBase<T, 2>>(tot_instances * b_vert.size());
in.face = Array<Vector<int>>(tot_instances * b_face.size());
T cur_x = T(0);
T cur_y = T(0);
@ -1857,7 +1857,7 @@ void text_test(
int instance = 0;
for (int line = 0; line < lines_num; ++line) {
for (int let = 0; let < lets_per_line_num; ++let) {
vec2<T> co_offset(cur_x, cur_y);
VecBase<T, 2> co_offset(cur_x, cur_y);
int in_v_offset = instance * b_vert.size();
for (int v = 0; v < b_vert.size(); ++v) {
in.vert[in_v_offset + v] = b_vert[v] + co_offset;
@ -2102,7 +2102,7 @@ void rand_delaunay_test(int test_kind,
}
CDT_input<T> in;
in.vert = Array<vec2<T>>(npts);
in.vert = Array<VecBase<T, 2>>(npts);
if (nedges > 0) {
in.edge = Array<std::pair<int, int>>(nedges);
}

View File

@ -320,4 +320,13 @@ TEST(index_mask, ComplementFuzzy)
}
}
TEST(index_mask, OffsetIndexRangeFind)
{
IndexMask mask = IndexRange(1, 2);
auto result = mask.find(1);
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mask.iterator_to_index(*result), 0);
EXPECT_EQ(mask[0], 1);
}
} // namespace blender::index_mask::tests

View File

@ -2972,18 +2972,11 @@ void do_versions_after_linking_280(FileData *fd, Main *bmain)
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - #blo_do_versions_280 in this file.
* - `versioning_userdef.cc`, #blo_do_versions_userdef
* - `versioning_userdef.cc`, #do_versions_theme
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
}
}
/* NOTE: This version patch is intended for versions < 2.52.2,
@ -6394,16 +6387,9 @@ void blo_do_versions_280(FileData *fd, Library * /*lib*/, Main *bmain)
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - #do_versions_after_linking_280 in this file.
* - `versioning_userdef.cc`, #blo_do_versions_userdef
* - `versioning_userdef.cc`, #do_versions_theme
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
}
}

View File

@ -680,18 +680,11 @@ void do_versions_after_linking_290(FileData * /*fd*/, Main *bmain)
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - #blo_do_versions_290 in this file.
* - `versioning_userdef.cc`, #blo_do_versions_userdef
* - `versioning_userdef.cc`, #do_versions_theme
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
}
}
static void panels_remove_x_closed_flag_recursive(Panel *panel)
@ -1979,15 +1972,9 @@ void blo_do_versions_290(FileData *fd, Library * /*lib*/, Main *bmain)
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - `versioning_userdef.cc`, #blo_do_versions_userdef
* - `versioning_userdef.cc`, #do_versions_theme
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
}
}

View File

@ -1381,18 +1381,11 @@ void do_versions_after_linking_300(FileData * /*fd*/, Main *bmain)
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - #blo_do_versions_300 in this file.
* - `versioning_userdef.cc`, #blo_do_versions_userdef
* - `versioning_userdef.cc`, #do_versions_theme
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
}
}
static void version_switch_node_input_prefix(Main *bmain)
@ -4567,16 +4560,9 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - #do_versions_after_linking_300 in this file.
* - `versioning_userdef.cc`, #blo_do_versions_userdef
* - `versioning_userdef.cc`, #do_versions_theme
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
}
}

View File

@ -449,18 +449,11 @@ void do_versions_after_linking_400(FileData *fd, Main *bmain)
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - #blo_do_versions_400 in this file.
* - `versioning_userdef.cc`, #blo_do_versions_userdef
* - `versioning_userdef.cc`, #do_versions_theme
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
}
}
static void version_mesh_legacy_to_struct_of_array_format(Mesh &mesh)
@ -2559,19 +2552,7 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - #do_versions_after_linking_400 in this file.
* - `versioning_userdef.cc`, #blo_do_versions_userdef
* - `versioning_userdef.cc`, #do_versions_theme
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 401, 10)) {
if (!DNA_struct_member_exists(
fd->filesdna, "SceneEEVEE", "RaytraceEEVEE", "ray_tracing_options"))
{
@ -2596,6 +2577,13 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
/* Always run this versioning; meshes are written with the legacy format which always needs to
* be converted to the new format on file load. Can be moved to a subversion check in a larger
* breaking release. */

View File

@ -141,17 +141,11 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme)
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - #blo_do_versions_userdef in this file.
* - "versioning_{BLENDER_VERSION}.c"
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a USER_VERSION_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
}
#undef FROM_DEFAULT_V4_UCHAR
@ -912,17 +906,11 @@ void blo_do_versions_userdef(UserDef *userdef)
}
/**
* Versioning code until next subversion bump goes here.
*
* \note Be sure to check when bumping the version:
* - #do_versions_theme in this file.
* - "versioning_{BLENDER_VERSION}.c"
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a USER_VERSION_ATLEAST check.
*
* \note Keep this message at the bottom of the function.
*/
{
/* Keep this block, even when empty. */
}
LISTBASE_FOREACH (bTheme *, btheme, &userdef->themes) {
do_versions_theme(userdef, btheme);

View File

@ -13,6 +13,8 @@
#include "BLI_math_vector.h"
#include "BLI_rect.h"
#include <cstring>
struct ColormanageProcessor;
struct ImBuf;

View File

@ -2,35 +2,50 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
ivec2 input_size = texture_size(input_tx);
vec2 input_size = vec2(texture_size(input_tx));
vec2 coordinates = (vec2(texel) + vec2(0.5)) / vec2(input_size);
/* The number of steps is the distance in pixels from the source to the current texel. With at
* least a single step and at most the user specified maximum ray length, which is proportional
* to the diagonal pixel count. */
float unbounded_steps = max(1.0, distance(vec2(texel), source * input_size));
int max_steps = int(max_ray_length * length(input_size));
int steps = min(max_steps, int(unbounded_steps));
/* We integrate from the current pixel to the source pixel, so compute the start coordinates and
* step vector in the direction to source. Notice that the step vector is still computed from the
* unbounded steps, such that the total integration length becomes limited by the bounded steps,
* and thus by the maximum ray length. */
vec2 coordinates = (vec2(texel) + vec2(0.5)) / input_size;
vec2 vector_to_source = source - coordinates;
float distance_to_source = length(vector_to_source);
vec2 direction_to_source = vector_to_source / distance_to_source;
/* We integrate from the current pixel to the source pixel, but up until the user specified
* maximum ray length. The number of integration steps is roughly equivalent to the number of
* pixels along the integration path. Assume a minimum number of steps of 1 to avoid zero
* division handling and return source pixels as is. */
float integration_length = min(distance_to_source, max_ray_length);
float integration_length_in_pixels = length(vec2(input_size)) * integration_length;
int steps = max(1, int(integration_length_in_pixels));
vec2 step_vector = (direction_to_source * integration_length) / steps;
vec2 step_vector = vector_to_source / unbounded_steps;
float accumulated_weight = 0.0;
vec4 accumulated_color = vec4(0.0);
for (int i = 0; i < steps; i++) {
for (int i = 0; i <= steps; i++) {
vec2 position = coordinates + i * step_vector;
/* We are already past the image boundaries, and any future steps are also past the image
* boundaries, so break. */
if (any(lessThan(position, vec2(0.0))) || any(greaterThan(position, vec2(1.0)))) {
break;
}
vec4 sample_color = texture(input_tx, position);
/* Attenuate the contributions of pixels that are further away from the source using a
* quadratic falloff. */
float weight = pow(1.0f - i / integration_length_in_pixels, 2.0);
accumulated_color += texture(input_tx, coordinates + i * step_vector) * weight;
* quadratic falloff. Also weight by the alpha to give more significance to opaque pixels. */
float weight = (square(1.0 - i / float(steps))) * sample_color.a;
accumulated_weight += weight;
accumulated_color += sample_color * weight;
}
imageStore(output_img, texel, accumulated_color / steps);
accumulated_color /= accumulated_weight != 0.0 ? accumulated_weight : 1.0;
imageStore(output_img, texel, accumulated_color);
}

View File

@ -539,6 +539,7 @@ set(GLSL_SRC
engines/eevee_next/shaders/eevee_lightprobe_irradiance_offset_comp.glsl
engines/eevee_next/shaders/eevee_lightprobe_irradiance_load_comp.glsl
engines/eevee_next/shaders/eevee_lightprobe_lib.glsl
engines/eevee_next/shaders/eevee_lightprobe_volume_eval_lib.glsl
engines/eevee_next/shaders/eevee_lookdev_display_frag.glsl
engines/eevee_next/shaders/eevee_lookdev_display_vert.glsl
engines/eevee_next/shaders/eevee_ltc_lib.glsl

View File

@ -29,7 +29,7 @@ vec3 out_aov;
bool output_sss(ClosureSubsurface diffuse, ClosureOutputDiffuse diffuse_out)
{
if (diffuse.sss_radius.r == -1.0 || !do_sss || !sssToggle || outputSssId == 0) {
if (diffuse.sss_radius.b == -1.0 || !do_sss || !sssToggle || outputSssId == 0) {
return false;
}
if (renderPassSSSColor) {

View File

@ -54,6 +54,7 @@ vec3 coordinate_incoming(vec3 P);
/* Single BSDFs. */
Closure closure_eval(ClosureDiffuse diffuse);
Closure closure_eval(ClosureSubsurface diffuse);
Closure closure_eval(ClosureTranslucent translucent);
Closure closure_eval(ClosureReflection reflection);
Closure closure_eval(ClosureRefraction refraction);
@ -66,7 +67,7 @@ Closure closure_eval(ClosureHair hair);
/* Glass BSDF. */
Closure closure_eval(ClosureReflection reflection, ClosureRefraction refraction);
/* Dielectric BSDF. */
Closure closure_eval(ClosureDiffuse diffuse, ClosureReflection reflection);
Closure closure_eval(ClosureSubsurface diffuse, ClosureReflection reflection);
/* Coat BSDF. */
Closure closure_eval(ClosureReflection reflection, ClosureReflection coat);
/* Volume BSDF. */
@ -74,9 +75,11 @@ Closure closure_eval(ClosureVolumeScatter volume_scatter,
ClosureVolumeAbsorption volume_absorption,
ClosureEmission emission);
/* Specular BSDF. */
Closure closure_eval(ClosureDiffuse diffuse, ClosureReflection reflection, ClosureReflection coat);
Closure closure_eval(ClosureSubsurface diffuse,
ClosureReflection reflection,
ClosureReflection coat);
/* Principled BSDF. */
Closure closure_eval(ClosureDiffuse diffuse,
Closure closure_eval(ClosureSubsurface diffuse,
ClosureReflection reflection,
ClosureReflection coat,
ClosureRefraction refraction);

View File

@ -705,7 +705,7 @@ void DeferredLayer::render(View &main_view,
/* FIXME(fclem): Metal has bug in backend. */
GPU_backend_get_type() == GPU_BACKEND_METAL)
{
inst_.gbuffer.header_tx.clear(int4(0));
inst_.gbuffer.header_tx.clear(uint4(0));
}
if (GPU_backend_get_type() == GPU_BACKEND_METAL) {
@ -725,8 +725,9 @@ void DeferredLayer::render(View &main_view,
{GPU_LOADACTION_LOAD, GPU_STOREACTION_STORE}, /* Depth */
{GPU_LOADACTION_LOAD, GPU_STOREACTION_STORE}, /* Combined */
{GPU_LOADACTION_CLEAR, GPU_STOREACTION_STORE, {0}}, /* GBuf Header */
{GPU_LOADACTION_DONT_CARE, GPU_STOREACTION_STORE}, /* GBuf Normal*/
{GPU_LOADACTION_DONT_CARE, GPU_STOREACTION_STORE}, /* GBuf Closure */
{GPU_LOADACTION_DONT_CARE, GPU_STOREACTION_STORE}, /* GBuf Color */
{GPU_LOADACTION_DONT_CARE, GPU_STOREACTION_STORE}, /* GBuf Closure 2*/
});
}

View File

@ -110,7 +110,6 @@ void main()
bool use_lightprobe_eval = uniform_buf.pipeline.use_combined_lightprobe_eval;
#endif
if (use_lightprobe_eval) {
vec2 noise_probe = interlieved_gradient_noise(gl_FragCoord.xy, vec2(0, 1), vec2(0.0));
LightProbeSample samp = lightprobe_load(P, Ng, V);
for (int i = 0; i < LIGHT_CLOSURE_EVAL_COUNT && i < gbuf.closure_count; i++) {
@ -118,23 +117,19 @@ void main()
switch (cl.type) {
case CLOSURE_BSDF_TRANSLUCENT_ID:
/* TODO: Support in ray tracing first. Otherwise we have a discrepancy. */
stack.cl[i].light_shadowed += lightprobe_eval(
samp, to_closure_translucent(cl), P, V, noise_probe);
stack.cl[i].light_shadowed += lightprobe_eval(samp, to_closure_translucent(cl), P, V);
break;
case CLOSURE_BSSRDF_BURLEY_ID:
/* TODO: Support translucency in ray tracing first. Otherwise we have a discrepancy. */
case CLOSURE_BSDF_DIFFUSE_ID:
stack.cl[i].light_shadowed += lightprobe_eval(
samp, to_closure_diffuse(cl), P, V, noise_probe);
stack.cl[i].light_shadowed += lightprobe_eval(samp, to_closure_diffuse(cl), P, V);
break;
case CLOSURE_BSDF_MICROFACET_GGX_REFLECTION_ID:
stack.cl[i].light_shadowed += lightprobe_eval(
samp, to_closure_reflection(cl), P, V, noise_probe);
stack.cl[i].light_shadowed += lightprobe_eval(samp, to_closure_reflection(cl), P, V);
break;
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
/* TODO(fclem): Add instead of replacing when we support correct refracted light. */
stack.cl[i].light_shadowed = lightprobe_eval(
samp, to_closure_refraction(cl), P, V, noise_probe);
stack.cl[i].light_shadowed = lightprobe_eval(samp, to_closure_refraction(cl), P, V);
break;
case CLOSURE_NONE_ID:
/* TODO(fclem): Assert. */

View File

@ -80,28 +80,24 @@ void forward_lighting_eval(float thickness, out vec3 radiance, out vec3 transmit
vec3 reflection_light = vec3(0.0);
vec3 refraction_light = vec3(0.0);
vec2 noise_probe = interlieved_gradient_noise(gl_FragCoord.xy, vec2(0, 1), vec2(0.0));
LightProbeSample samp = lightprobe_load(g_data.P, g_data.Ng, V);
#ifdef MAT_DIFFUSE
diffuse_light = stack.cl[cl_diffuse_id].light_shadowed;
diffuse_light += lightprobe_eval(
samp, to_closure_diffuse(g_diffuse_data), g_data.P, V, noise_probe);
diffuse_light += lightprobe_eval(samp, to_closure_diffuse(g_diffuse_data), g_data.P, V);
#endif
#ifdef MAT_TRANSLUCENT
translucent_light = stack.cl[cl_translucent_id].light_shadowed;
translucent_light += lightprobe_eval(
samp, to_closure_translucent(g_translucent_data), g_data.P, V, noise_probe);
samp, to_closure_translucent(g_translucent_data), g_data.P, V);
#endif
#ifdef MAT_REFLECTION
reflection_light = stack.cl[cl_reflection_id].light_shadowed;
reflection_light += lightprobe_eval(
samp, to_closure_reflection(g_reflection_data), g_data.P, V, noise_probe);
reflection_light += lightprobe_eval(samp, to_closure_reflection(g_reflection_data), g_data.P, V);
#endif
#ifdef MAT_REFRACTION
/* TODO(fclem): Refraction from lightprobe. */
// refraction_light += lightprobe_eval(samp, to_closure_refraction(g_refraction_data), g_data.P,
// V, noise_probe);
/* TODO(fclem): Refraction from light. */
refraction_light += lightprobe_eval(samp, to_closure_refraction(g_refraction_data), g_data.P, V);
#endif
/* Apply weight. */

View File

@ -30,7 +30,12 @@ int culling_z_to_zbin(float scale, float bias, float z)
* or
* - Vulkan 1.1
*/
#if 1
#ifdef GPU_METAL
# define subgroupMin(a) simd_min(a)
# define subgroupMax(a) simd_max(a)
# define subgroupOr(a) simd_or(a)
# define subgroupBroadcastFirst(a) simd_broadcast_first(a)
#else
# define subgroupMin(a) a
# define subgroupMax(a) a
# define subgroupOr(a) a

View File

@ -2,212 +2,14 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* The resources expected to be defined are:
* - grids_infos_buf
* - bricks_infos_buf
* - irradiance_atlas_tx
* Needed for sampling (not for upload):
* - util_tx
* - sampling_buf
*/
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_generate_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_eval_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
/**
* Return the brick coordinate inside the grid.
*/
ivec3 lightprobe_irradiance_grid_brick_coord(vec3 lP)
{
ivec3 brick_coord = ivec3((lP - 0.5) / float(IRRADIANCE_GRID_BRICK_SIZE - 1));
/* Avoid sampling adjacent bricks. */
return max(brick_coord, ivec3(0));
}
/**
* Return the local coordinated of the shading point inside the brick in unnormalized coordinate.
*/
vec3 lightprobe_irradiance_grid_brick_local_coord(IrradianceGridData grid_data,
vec3 lP,
ivec3 brick_coord)
{
/* Avoid sampling adjacent bricks around the origin. */
lP = max(lP, vec3(0.5));
/* Local position inside the brick (still in grid sample spacing unit). */
vec3 brick_lP = lP - vec3(brick_coord) * float(IRRADIANCE_GRID_BRICK_SIZE - 1);
return brick_lP;
}
/**
* Return the biased local brick local coordinated.
*/
vec3 lightprobe_irradiance_grid_bias_sample_coord(IrradianceGridData grid_data,
uvec2 brick_atlas_coord,
vec3 brick_lP,
vec3 lNg)
{
/* A cell is the interpolation region between 8 texels. */
vec3 cell_lP = brick_lP - 0.5;
vec3 cell_start = floor(cell_lP);
vec3 cell_fract = cell_lP - cell_start;
/* NOTE(fclem): Use uint to avoid signed int modulo. */
uint vis_comp = uint(cell_start.z) % 4u;
/* Visibility is stored after the irradiance. */
ivec3 vis_coord = ivec3(ivec2(brick_atlas_coord), IRRADIANCE_GRID_BRICK_SIZE * 4) +
ivec3(cell_start);
/* Visibility is stored packed 1 cell per channel. */
vis_coord.z -= int(vis_comp);
float cell_visibility = texelFetch(irradiance_atlas_tx, vis_coord, 0)[vis_comp];
int cell_visibility_bits = int(cell_visibility);
/**
* References:
*
* "Probe-based lighting, strand-based hair system, and physical hair shading in Unitys Enemies"
* by Francesco Cifariello Ciardi, Lasse Jon Fuglsang Pedersen and John Parsaie.
*
* "Multi-Scale Global Illumination in Quantum Break"
* by Ari Silvennoinen and Ville Timonen.
*
* Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields
* by Morgan McGuire.
*/
float trilinear_weights[8];
float total_weight = 0.0;
for (int i = 0; i < 8; i++) {
ivec3 sample_position = lightprobe_irradiance_grid_cell_corner(i);
vec3 trilinear = select(1.0 - cell_fract, cell_fract, bvec3(sample_position));
float positional_weight = trilinear.x * trilinear.y * trilinear.z;
float len;
vec3 corner_vec = vec3(sample_position) - cell_fract;
vec3 corner_dir = normalize_and_get_length(corner_vec, len);
float cos_theta = (len > 1e-8) ? dot(lNg, corner_dir) : 1.0;
float geometry_weight = saturate(cos_theta * 0.5 + 0.5);
float validity_weight = float((cell_visibility_bits >> i) & 1);
/* Biases. See McGuire's presentation. */
positional_weight += 0.001;
geometry_weight = square(geometry_weight) + 0.2 + grid_data.facing_bias;
trilinear_weights[i] = saturate(positional_weight * geometry_weight * validity_weight);
total_weight += trilinear_weights[i];
}
float total_weight_inv = safe_rcp(total_weight);
vec3 trilinear_coord = vec3(0.0);
for (int i = 0; i < 8; i++) {
vec3 sample_position = vec3((ivec3(i) >> ivec3(0, 1, 2)) & 1);
trilinear_coord += sample_position * trilinear_weights[i] * total_weight_inv;
}
/* Replace sampling coordinates with manually weighted trilinear coordinates. */
return 0.5 + cell_start + trilinear_coord;
}
SphericalHarmonicL1 lightprobe_irradiance_sample_atlas(sampler3D atlas_tx, vec3 atlas_coord)
{
vec4 texture_coord = vec4(atlas_coord, float(IRRADIANCE_GRID_BRICK_SIZE)) /
vec3(textureSize(atlas_tx, 0)).xyzz;
SphericalHarmonicL1 sh;
sh.L0.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
texture_coord.z += texture_coord.w;
sh.L1.Mn1 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
texture_coord.z += texture_coord.w;
sh.L1.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
texture_coord.z += texture_coord.w;
sh.L1.Mp1 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
return sh;
}
SphericalHarmonicL1 lightprobe_irradiance_sample(
sampler3D atlas_tx, vec3 P, vec3 V, vec3 Ng, const bool do_bias)
{
vec3 lP;
int index = -1;
int i = 0;
#ifdef IRRADIANCE_GRID_UPLOAD
i = grid_start_index;
#endif
#ifdef IRRADIANCE_GRID_SAMPLING
float random = interlieved_gradient_noise(UTIL_TEXEL, 0.0, 0.0);
random = fract(random + sampling_rng_1D_get(SAMPLING_LIGHTPROBE));
#endif
for (; i < IRRADIANCE_GRID_MAX; i++) {
/* Last grid is tagged as invalid to stop the iteration. */
if (grids_infos_buf[i].grid_size.x == -1) {
/* Sample the last grid instead. */
index = i - 1;
break;
}
/* If sample fall inside the grid, step out of the loop. */
if (lightprobe_irradiance_grid_local_coord(grids_infos_buf[i], P, lP)) {
index = i;
#ifdef IRRADIANCE_GRID_SAMPLING
float distance_to_border = reduce_min(min(lP, vec3(grids_infos_buf[i].grid_size) - lP));
if (distance_to_border < random) {
/* Remap random to the remaining interval. */
random = (random - distance_to_border) / (1.0 - distance_to_border);
/* Try to sample another grid to get smooth transitions at borders. */
continue;
}
#endif
break;
}
}
IrradianceGridData grid_data = grids_infos_buf[index];
/* TODO(fclem): Make sure this is working as expected. */
mat3x3 world_to_grid_transposed = mat3x3(grid_data.world_to_grid_transposed);
vec3 lNg = safe_normalize(world_to_grid_transposed * Ng);
vec3 lV = safe_normalize(V * world_to_grid_transposed);
if (do_bias) {
/* Shading point bias. */
lP += lNg * grid_data.normal_bias;
lP += lV * grid_data.view_bias;
}
else {
lNg = vec3(0.0);
}
ivec3 brick_coord = lightprobe_irradiance_grid_brick_coord(lP);
int brick_index = lightprobe_irradiance_grid_brick_index_get(grid_data, brick_coord);
IrradianceBrick brick = irradiance_brick_unpack(bricks_infos_buf[brick_index]);
vec3 brick_lP = lightprobe_irradiance_grid_brick_local_coord(grid_data, lP, brick_coord);
/* Sampling point bias. */
brick_lP = lightprobe_irradiance_grid_bias_sample_coord(
grid_data, brick.atlas_coord, brick_lP, lNg);
vec3 atlas_coord = vec3(vec2(brick.atlas_coord), 0.0) + brick_lP;
return lightprobe_irradiance_sample_atlas(atlas_tx, atlas_coord);
}
SphericalHarmonicL1 lightprobe_irradiance_world()
{
return lightprobe_irradiance_sample_atlas(irradiance_atlas_tx, vec3(0.0));
}
SphericalHarmonicL1 lightprobe_irradiance_sample(vec3 P)
{
return lightprobe_irradiance_sample(irradiance_atlas_tx, P, vec3(0), vec3(0), false);
}
SphericalHarmonicL1 lightprobe_irradiance_sample(vec3 P, vec3 V, vec3 Ng)
{
return lightprobe_irradiance_sample(irradiance_atlas_tx, P, V, Ng, true);
}
#pragma BLENDER_REQUIRE(eevee_lightprobe_volume_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#ifdef REFLECTION_PROBE
@ -294,13 +96,13 @@ float lightprobe_roughness_to_lod(float roughness)
return sqrt(roughness) * 11.0;
}
vec3 lightprobe_eval(LightProbeSample samp, ClosureDiffuse cl, vec3 P, vec3 V, vec2 noise)
vec3 lightprobe_eval(LightProbeSample samp, ClosureDiffuse cl, vec3 P, vec3 V)
{
vec3 radiance_sh = spherical_harmonics_evaluate_lambert(cl.N, samp.volume_irradiance);
return radiance_sh;
}
vec3 lightprobe_eval(LightProbeSample samp, ClosureTranslucent cl, vec3 P, vec3 V, vec2 noise)
vec3 lightprobe_eval(LightProbeSample samp, ClosureTranslucent cl, vec3 P, vec3 V)
{
vec3 radiance_sh = spherical_harmonics_evaluate_lambert(-cl.N, samp.volume_irradiance);
return radiance_sh;
@ -314,11 +116,9 @@ vec3 lightprobe_reflection_dominant_dir(vec3 N, vec3 V, float roughness)
return normalize(mix(N, R, fac));
}
vec3 lightprobe_eval(
LightProbeSample samp, ClosureReflection reflection, vec3 P, vec3 V, vec2 noise)
vec3 lightprobe_eval(LightProbeSample samp, ClosureReflection reflection, vec3 P, vec3 V)
{
vec3 L = lightprobe_reflection_dominant_dir(reflection.N, V, reflection.roughness);
// vec3 L = ray_generate_direction(noise, reflection, V, pdf);
float lod = lightprobe_roughness_to_lod(reflection.roughness);
vec3 radiance_cube = lightprobe_spherical_sample_normalized_with_parallax(
@ -337,10 +137,9 @@ vec3 lightprobe_refraction_dominant_dir(vec3 N, vec3 V, float ior, float roughne
return normalize(mix(-N, R, fac));
}
vec3 lightprobe_eval(LightProbeSample samp, ClosureRefraction cl, vec3 P, vec3 V, vec2 noise)
vec3 lightprobe_eval(LightProbeSample samp, ClosureRefraction cl, vec3 P, vec3 V)
{
vec3 L = lightprobe_refraction_dominant_dir(cl.N, V, cl.ior, cl.roughness);
// vec3 L = ray_generate_direction(noise, cl, V, pdf);
float lod = lightprobe_roughness_to_lod(cl.roughness);
vec3 radiance_cube = lightprobe_spherical_sample_normalized_with_parallax(

View File

@ -14,7 +14,7 @@
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_volume_eval_lib.glsl)
void atlas_store(vec4 sh_coefficient, ivec2 atlas_coord, int layer)
{

View File

@ -0,0 +1,208 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* The resources expected to be defined are:
* - grids_infos_buf
* - bricks_infos_buf
* - irradiance_atlas_tx
* Needed for sampling (not for upload):
* - util_tx
* - sampling_buf
*/
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
/**
* Return the brick coordinate inside the grid.
*/
ivec3 lightprobe_irradiance_grid_brick_coord(vec3 lP)
{
ivec3 brick_coord = ivec3((lP - 0.5) / float(IRRADIANCE_GRID_BRICK_SIZE - 1));
/* Avoid sampling adjacent bricks. */
return max(brick_coord, ivec3(0));
}
/**
* Return the local coordinated of the shading point inside the brick in unnormalized coordinate.
*/
vec3 lightprobe_irradiance_grid_brick_local_coord(IrradianceGridData grid_data,
vec3 lP,
ivec3 brick_coord)
{
/* Avoid sampling adjacent bricks around the origin. */
lP = max(lP, vec3(0.5));
/* Local position inside the brick (still in grid sample spacing unit). */
vec3 brick_lP = lP - vec3(brick_coord) * float(IRRADIANCE_GRID_BRICK_SIZE - 1);
return brick_lP;
}
/**
* Return the biased local brick local coordinated.
*/
vec3 lightprobe_irradiance_grid_bias_sample_coord(IrradianceGridData grid_data,
uvec2 brick_atlas_coord,
vec3 brick_lP,
vec3 lNg)
{
/* A cell is the interpolation region between 8 texels. */
vec3 cell_lP = brick_lP - 0.5;
vec3 cell_start = floor(cell_lP);
vec3 cell_fract = cell_lP - cell_start;
/* NOTE(fclem): Use uint to avoid signed int modulo. */
uint vis_comp = uint(cell_start.z) % 4u;
/* Visibility is stored after the irradiance. */
ivec3 vis_coord = ivec3(ivec2(brick_atlas_coord), IRRADIANCE_GRID_BRICK_SIZE * 4) +
ivec3(cell_start);
/* Visibility is stored packed 1 cell per channel. */
vis_coord.z -= int(vis_comp);
float cell_visibility = texelFetch(irradiance_atlas_tx, vis_coord, 0)[vis_comp];
int cell_visibility_bits = int(cell_visibility);
/**
* References:
*
* "Probe-based lighting, strand-based hair system, and physical hair shading in Unitys Enemies"
* by Francesco Cifariello Ciardi, Lasse Jon Fuglsang Pedersen and John Parsaie.
*
* "Multi-Scale Global Illumination in Quantum Break"
* by Ari Silvennoinen and Ville Timonen.
*
* Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields
* by Morgan McGuire.
*/
float trilinear_weights[8];
float total_weight = 0.0;
for (int i = 0; i < 8; i++) {
ivec3 sample_position = lightprobe_irradiance_grid_cell_corner(i);
vec3 trilinear = select(1.0 - cell_fract, cell_fract, bvec3(sample_position));
float positional_weight = trilinear.x * trilinear.y * trilinear.z;
float len;
vec3 corner_vec = vec3(sample_position) - cell_fract;
vec3 corner_dir = normalize_and_get_length(corner_vec, len);
float cos_theta = (len > 1e-8) ? dot(lNg, corner_dir) : 1.0;
float geometry_weight = saturate(cos_theta * 0.5 + 0.5);
float validity_weight = float((cell_visibility_bits >> i) & 1);
/* Biases. See McGuire's presentation. */
positional_weight += 0.001;
geometry_weight = square(geometry_weight) + 0.2 + grid_data.facing_bias;
trilinear_weights[i] = saturate(positional_weight * geometry_weight * validity_weight);
total_weight += trilinear_weights[i];
}
float total_weight_inv = safe_rcp(total_weight);
vec3 trilinear_coord = vec3(0.0);
for (int i = 0; i < 8; i++) {
vec3 sample_position = vec3((ivec3(i) >> ivec3(0, 1, 2)) & 1);
trilinear_coord += sample_position * trilinear_weights[i] * total_weight_inv;
}
/* Replace sampling coordinates with manually weighted trilinear coordinates. */
return 0.5 + cell_start + trilinear_coord;
}
SphericalHarmonicL1 lightprobe_irradiance_sample_atlas(sampler3D atlas_tx, vec3 atlas_coord)
{
vec4 texture_coord = vec4(atlas_coord, float(IRRADIANCE_GRID_BRICK_SIZE)) /
vec3(textureSize(atlas_tx, 0)).xyzz;
SphericalHarmonicL1 sh;
sh.L0.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
texture_coord.z += texture_coord.w;
sh.L1.Mn1 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
texture_coord.z += texture_coord.w;
sh.L1.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
texture_coord.z += texture_coord.w;
sh.L1.Mp1 = textureLod(atlas_tx, texture_coord.xyz, 0.0);
return sh;
}
SphericalHarmonicL1 lightprobe_irradiance_sample(
sampler3D atlas_tx, vec3 P, vec3 V, vec3 Ng, const bool do_bias)
{
vec3 lP;
int index = -1;
int i = 0;
#ifdef IRRADIANCE_GRID_UPLOAD
i = grid_start_index;
#endif
#ifdef IRRADIANCE_GRID_SAMPLING
float random = interlieved_gradient_noise(UTIL_TEXEL, 0.0, 0.0);
random = fract(random + sampling_rng_1D_get(SAMPLING_LIGHTPROBE));
#endif
for (; i < IRRADIANCE_GRID_MAX; i++) {
/* Last grid is tagged as invalid to stop the iteration. */
if (grids_infos_buf[i].grid_size.x == -1) {
/* Sample the last grid instead. */
index = i - 1;
break;
}
/* If sample fall inside the grid, step out of the loop. */
if (lightprobe_irradiance_grid_local_coord(grids_infos_buf[i], P, lP)) {
index = i;
#ifdef IRRADIANCE_GRID_SAMPLING
float distance_to_border = reduce_min(min(lP, vec3(grids_infos_buf[i].grid_size) - lP));
if (distance_to_border < random) {
/* Remap random to the remaining interval. */
random = (random - distance_to_border) / (1.0 - distance_to_border);
/* Try to sample another grid to get smooth transitions at borders. */
continue;
}
#endif
break;
}
}
IrradianceGridData grid_data = grids_infos_buf[index];
/* TODO(fclem): Make sure this is working as expected. */
mat3x3 world_to_grid_transposed = mat3x3(grid_data.world_to_grid_transposed);
vec3 lNg = safe_normalize(world_to_grid_transposed * Ng);
vec3 lV = safe_normalize(V * world_to_grid_transposed);
if (do_bias) {
/* Shading point bias. */
lP += lNg * grid_data.normal_bias;
lP += lV * grid_data.view_bias;
}
else {
lNg = vec3(0.0);
}
ivec3 brick_coord = lightprobe_irradiance_grid_brick_coord(lP);
int brick_index = lightprobe_irradiance_grid_brick_index_get(grid_data, brick_coord);
IrradianceBrick brick = irradiance_brick_unpack(bricks_infos_buf[brick_index]);
vec3 brick_lP = lightprobe_irradiance_grid_brick_local_coord(grid_data, lP, brick_coord);
/* Sampling point bias. */
brick_lP = lightprobe_irradiance_grid_bias_sample_coord(
grid_data, brick.atlas_coord, brick_lP, lNg);
vec3 atlas_coord = vec3(vec2(brick.atlas_coord), 0.0) + brick_lP;
return lightprobe_irradiance_sample_atlas(atlas_tx, atlas_coord);
}
SphericalHarmonicL1 lightprobe_irradiance_world()
{
return lightprobe_irradiance_sample_atlas(irradiance_atlas_tx, vec3(0.0));
}
SphericalHarmonicL1 lightprobe_irradiance_sample(vec3 P)
{
return lightprobe_irradiance_sample(irradiance_atlas_tx, P, vec3(0), vec3(0), false);
}
SphericalHarmonicL1 lightprobe_irradiance_sample(vec3 P, vec3 V, vec3 Ng)
{
return lightprobe_irradiance_sample(irradiance_atlas_tx, P, V, Ng, true);
}

View File

@ -34,6 +34,7 @@ void main()
vec3 P = drw_point_screen_to_world(vec3(uv, 0.5));
vec3 V = drw_world_incident_vector(P);
vec2 noise = utility_tx_fetch(utility_tx, vec2(texel), UTIL_BLUE_NOISE_LAYER).rg;
noise = fract(noise + sampling_rng_2D_get(SAMPLING_RAYTRACE_U));
BsdfSample samp = ray_generate_direction(noise.xy, gbuffer_closure_get(gbuf, closure_index), V);

View File

@ -6,13 +6,13 @@
* Ray generation routines for each BSDF types.
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
struct BsdfSample {
vec3 direction;
@ -30,8 +30,7 @@ bool is_singular_ray(float roughness)
/* Returns view-space ray. */
BsdfSample ray_generate_direction(vec2 noise, ClosureUndetermined cl, vec3 V)
{
vec2 noise_offset = sampling_rng_2D_get(SAMPLING_RAYTRACE_U);
vec3 random_point_on_cylinder = sample_cylinder(fract(noise_offset + noise));
vec3 random_point_on_cylinder = sample_cylinder(noise);
/* Bias the rays so we never get really high energy rays almost parallel to the surface. */
random_point_on_cylinder.x = random_point_on_cylinder.x * (1.0 - RAY_BIAS) + RAY_BIAS;

View File

@ -7,7 +7,8 @@
* irradiance cache from each spherical probe location except for the world probe.
*/
#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_lightprobe_volume_eval_lib.glsl)
void main()
{

View File

@ -19,7 +19,7 @@ GPU_SHADER_CREATE_INFO(eevee_hiz_update_base)
.image(4, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_4")
.image(5, GPU_R32F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "out_mip_5")
.image(6, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_6")
.specialization_constant(Type::BOOL, "update_mip_0", false)
.specialization_constant(Type::BOOL, "update_mip_0", true)
.compute_source("eevee_hiz_update_comp.glsl");
GPU_SHADER_CREATE_INFO(eevee_hiz_update)

View File

@ -1085,8 +1085,8 @@ static void menu_add_item_for_move_assign_unassign(uiLayout *layout,
/**
* Add menu items to the layout, for a set of bone collections.
*
* \param menu_custom_data contains two values, encoded as void* to match the signature required by
* `uiItemMenuF`. It contains the parent bone collection index (either -1 to show all roots, or
* \param menu_custom_data: Contains two values, encoded as void* to match the signature required
* by `uiItemMenuF`. It contains the parent bone collection index (either -1 to show all roots, or
* another value to show the children of that collection), as well as a boolean that indicates
* whether the menu is created for the "move to collection" or "assign to collection" operator.
*

View File

@ -26,6 +26,7 @@ set(SRC
intern/curves_data.cc
intern/curves_draw.cc
intern/curves_edit.cc
intern/curves_extrude.cc
intern/curves_masks.cc
intern/curves_ops.cc
intern/curves_selection.cc

View File

@ -621,6 +621,110 @@ static bool curve_draw_init(bContext *C, wmOperator *op, bool is_invoke)
return true;
}
static void create_Bezier(bke::CurvesGeometry &curves,
bke::MutableAttributeAccessor &attributes,
const CurveDrawData *cdd,
const int curve_index,
const bool is_cyclic,
const uint cubic_spline_len,
const int dims,
const int radius_index,
const float radius_max,
const float *cubic_spline,
const uint *corners_index,
const uint corners_index_len)
{
curves.resize(curves.points_num() + cubic_spline_len, curve_index + 1);
MutableSpan<float3> positions = curves.positions_for_write();
MutableSpan<float3> handle_positions_l = curves.handle_positions_left_for_write();
MutableSpan<float3> handle_positions_r = curves.handle_positions_right_for_write();
MutableSpan<int8_t> handle_types_l = curves.handle_types_left_for_write();
MutableSpan<int8_t> handle_types_r = curves.handle_types_right_for_write();
const IndexRange new_points = curves.points_by_curve()[curve_index];
bke::SpanAttributeWriter<float> radii = attributes.lookup_or_add_for_write_only_span<float>(
"radius", bke::AttrDomain::Point);
const float *co = cubic_spline;
for (const int64_t i : new_points) {
const float *handle_l = co + (dims * 0);
const float *pt = co + (dims * 1);
const float *handle_r = co + (dims * 2);
copy_v3_v3(handle_positions_l[i], handle_l);
copy_v3_v3(positions[i], pt);
copy_v3_v3(handle_positions_r[i], handle_r);
const float radius = (radius_index != -1) ?
(pt[radius_index] * cdd->radius.range) + cdd->radius.min :
radius_max;
radii.span[i] = radius;
handle_types_l[i] = BEZIER_HANDLE_ALIGN;
handle_types_r[i] = BEZIER_HANDLE_ALIGN;
co += (dims * 3);
}
if (corners_index) {
/* ignore the first and last */
uint i_start = 0, i_end = corners_index_len;
if ((corners_index_len >= 2) && !is_cyclic) {
i_start += 1;
i_end -= 1;
}
for (const auto i : IndexRange(i_start, i_end - i_start)) {
const int64_t corner_i = new_points[corners_index[i]];
handle_types_l[corner_i] = BEZIER_HANDLE_FREE;
handle_types_r[corner_i] = BEZIER_HANDLE_FREE;
}
}
radii.finish();
}
static void create_NURBS(bke::CurvesGeometry &curves,
bke::MutableAttributeAccessor &attributes,
const CurveDrawData *cdd,
const int curve_index,
const bool is_cyclic,
const uint cubic_spline_len,
const int dims,
const int radius_index,
const float radius_max,
const float *cubic_spline)
{
const int point_num = (cubic_spline_len - 2) * 3 + 4 + (is_cyclic ? 2 : 0);
curves.resize(curves.points_num() + point_num, curve_index + 1);
MutableSpan<float3> positions = curves.positions_for_write();
MutableSpan<float> weights = curves.nurbs_weights_for_write();
const IndexRange new_points = curves.points_by_curve()[curve_index];
bke::SpanAttributeWriter<float> radii = attributes.lookup_or_add_for_write_only_span<float>(
"radius", bke::AttrDomain::Point);
/* If cyclic shows to first left handle else first control point. */
const float *pt = cubic_spline + (is_cyclic ? 0 : dims);
for (const int64_t i : new_points) {
const float radius = (radius_index != -1) ?
(pt[radius_index] * cdd->radius.range) + cdd->radius.min :
radius_max;
copy_v3_v3(positions[i], pt);
weights[i] = 1.0f;
radii.span[i] = radius;
pt += dims;
}
radii.finish();
}
static int curves_draw_exec(bContext *C, wmOperator *op)
{
if (op->customdata == nullptr) {
@ -648,6 +752,7 @@ static int curves_draw_exec(bContext *C, wmOperator *op)
const float error_threshold = RNA_float_get(op->ptr, "error_threshold");
const float corner_angle = RNA_float_get(op->ptr, "corner_angle");
const bool use_cyclic = RNA_boolean_get(op->ptr, "use_cyclic");
const bool bezier_as_nurbs = RNA_boolean_get(op->ptr, "bezier_as_nurbs");
bool is_cyclic = (stroke_len > 2) && use_cyclic;
const float radius_min = cps->radius_min;
@ -768,58 +873,45 @@ static int curves_draw_exec(bContext *C, wmOperator *op)
}
if (result == 0) {
curves.resize(curves.points_num() + cubic_spline_len, curve_index + 1);
curves.fill_curve_types(IndexRange(curve_index, 1), CURVE_TYPE_BEZIER);
MutableSpan<float3> positions = curves.positions_for_write();
MutableSpan<float3> handle_positions_l = curves.handle_positions_left_for_write();
MutableSpan<float3> handle_positions_r = curves.handle_positions_right_for_write();
MutableSpan<int8_t> handle_types_l = curves.handle_types_left_for_write();
MutableSpan<int8_t> handle_types_r = curves.handle_types_right_for_write();
const IndexRange new_points = curves.points_by_curve()[curve_index];
bke::SpanAttributeWriter<float> radii = attributes.lookup_or_add_for_write_only_span<float>(
"radius", bke::AttrDomain::Point);
float *co = cubic_spline;
for (const int64_t i : new_points) {
const float *handle_l = co + (dims * 0);
const float *pt = co + (dims * 1);
const float *handle_r = co + (dims * 2);
copy_v3_v3(handle_positions_l[i], handle_l);
copy_v3_v3(positions[i], pt);
copy_v3_v3(handle_positions_r[i], handle_r);
const float radius = (radius_index != -1) ?
(pt[radius_index] * cdd->radius.range) + cdd->radius.min :
radius_max;
radii.span[i] = radius;
handle_types_l[i] = BEZIER_HANDLE_ALIGN;
handle_types_r[i] = BEZIER_HANDLE_ALIGN;
co += (dims * 3);
int8_t knots_mode;
int8_t order;
CurveType curve_type;
if (bezier_as_nurbs) {
bool is_cyclic_curve = calc_flag & CURVE_FIT_CALC_CYCLIC;
create_NURBS(curves,
attributes,
cdd,
curve_index,
is_cyclic_curve,
cubic_spline_len,
dims,
radius_index,
radius_max,
cubic_spline);
order = 4;
knots_mode = is_cyclic_curve ? NURBS_KNOT_MODE_BEZIER : NURBS_KNOT_MODE_ENDPOINT_BEZIER;
curve_type = CURVE_TYPE_NURBS;
}
if (corners_index) {
/* ignore the first and last */
uint i_start = 0, i_end = corners_index_len;
if ((corners_index_len >= 2) && !is_cyclic) {
i_start += 1;
i_end -= 1;
}
for (const auto i : IndexRange(i_start, i_end - i_start)) {
const int64_t corner_i = new_points[corners_index[i]];
handle_types_l[corner_i] = BEZIER_HANDLE_FREE;
handle_types_r[corner_i] = BEZIER_HANDLE_FREE;
}
else {
create_Bezier(curves,
attributes,
cdd,
curve_index,
is_cyclic,
cubic_spline_len,
dims,
radius_index,
radius_max,
cubic_spline,
corners_index,
corners_index_len);
order = 0;
knots_mode = 0;
curve_type = CURVE_TYPE_BEZIER;
}
radii.finish();
curves.nurbs_knots_modes_for_write()[curve_index] = knots_mode;
curves.nurbs_orders_for_write()[curve_index] = order;
curves.fill_curve_types(IndexRange(curve_index, 1), curve_type);
bke::AttributeWriter<bool> selection = attributes.lookup_or_add_for_write<bool>(
".selection", bke::AttrDomain::Curve);
@ -837,12 +929,14 @@ static int curves_draw_exec(bContext *C, wmOperator *op)
"handle_right",
"handle_type_left",
"handle_type_right",
"nurbs_weight",
".selection"},
new_points);
bke::fill_attribute_range_default(attributes,
bke::AttrDomain::Curve,
{"curve_type", "resolution", ".selection"},
IndexRange(curve_index, 1));
curves.points_by_curve()[curve_index]);
bke::fill_attribute_range_default(
attributes,
bke::AttrDomain::Curve,
{"curve_type", "resolution", "cyclic", "nurbs_order", "knots_mode", ".selection"},
IndexRange(curve_index, 1));
}
if (corners_index) {
@ -1219,6 +1313,9 @@ void CURVES_OT_draw(wmOperatorType *ot)
prop = RNA_def_boolean(ot->srna, "is_curve_2d", false, "Curve 2D", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "bezier_as_nurbs", false, "As NURBS", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
} // namespace blender::ed::curves

View File

@ -0,0 +1,357 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute.hh"
#include "BKE_context.hh"
#include "BKE_curves_utils.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_curves.hh"
#include "DEG_depsgraph.hh"
namespace blender::ed::curves {
/**
* Merges copy intervals at curve endings to minimize number of copy operations.
* For example above intervals [0, 3, 4, 4, 4] became [0, 4, 4].
* Leading to only two copy operations.
*/
static Span<int> compress_intervals(const Span<IndexRange> curve_interval_ranges,
MutableSpan<int> intervals)
{
const int *src = intervals.data();
/* Skip the first curve, as all the data stays in the same place. */
int *dst = intervals.data() + curve_interval_ranges[0].size();
for (const int curve : IndexRange(1, curve_interval_ranges.size() - 1)) {
const IndexRange range = curve_interval_ranges[curve];
const int width = range.size() - 1;
std::copy_n(src + range.first() + 1, width, dst);
dst += width;
}
(*dst) = src[curve_interval_ranges[curve_interval_ranges.size() - 1].last() + 1];
return {intervals.data(), dst - intervals.data() + 1};
}
/**
* Creates copy intervals for selection #range in the context of #curve_index.
* If part of the #range is outside given curve, slices it and returns false indicating remaining
* still needs to be handled. If whole #range was handled returns true.
*/
static bool handle_range(const int curve_index,
const int interval_offset,
const Span<int> offsets,
int &current_interval,
IndexRange &range,
MutableSpan<int> curve_intervals,
MutableSpan<bool> is_first_selected)
{
const int first_elem = offsets[curve_index];
const int last_elem = offsets[curve_index + 1] - 1;
if (current_interval == 0) {
is_first_selected[curve_index] = range.first() == first_elem && range.size() == 1;
if (!is_first_selected[curve_index]) {
current_interval++;
}
}
curve_intervals[interval_offset + current_interval] = range.first();
current_interval++;
bool inside_curve = last_elem >= range.last();
if (inside_curve) {
curve_intervals[interval_offset + current_interval] = range.last();
}
else {
curve_intervals[interval_offset + current_interval] = last_elem;
range = IndexRange(last_elem + 1, range.last() - last_elem);
}
current_interval++;
return inside_curve;
}
/**
* Calculates number of points in resulting curve denoted by #curve_index and sets it's
* #curve_offsets value.
*/
static void calc_curve_offset(const int curve_index,
int &interval_offset,
const Span<int> offsets,
MutableSpan<int> new_offsets,
MutableSpan<IndexRange> curve_interval_ranges)
{
const int points_in_curve = (offsets[curve_index + 1] - offsets[curve_index] +
curve_interval_ranges[curve_index].size() - 1);
new_offsets[curve_index + 1] = new_offsets[curve_index] + points_in_curve;
interval_offset += curve_interval_ranges[curve_index].size() + 1;
}
static void finish_curve(int &curve_index,
int &interval_offset,
int last_interval,
int last_elem,
const Span<int> offsets,
MutableSpan<int> new_offsets,
MutableSpan<int> curve_intervals,
MutableSpan<IndexRange> curve_interval_ranges,
MutableSpan<bool> is_first_selected)
{
if (curve_intervals[interval_offset + last_interval] != last_elem ||
curve_intervals[interval_offset + last_interval - 1] !=
curve_intervals[interval_offset + last_interval])
{
/* Append last element of the current curve if it is not extruded or extruded together with
* preceding points. */
last_interval++;
curve_intervals[interval_offset + last_interval] = last_elem;
}
else if (is_first_selected[curve_index] && last_interval == 1) {
/* Extrusion from one point. */
curve_intervals[interval_offset + last_interval + 1] =
curve_intervals[interval_offset + last_interval];
is_first_selected[curve_index] = false;
last_interval++;
}
curve_interval_ranges[curve_index] = IndexRange(interval_offset, last_interval);
calc_curve_offset(curve_index, interval_offset, offsets, new_offsets, curve_interval_ranges);
curve_index++;
}
static void finish_curve_or_full_copy(int &curve_index,
int &interval_offset,
int current_interval,
const std::optional<IndexRange> prev_range,
const Span<int> offsets,
MutableSpan<int> new_offsets,
MutableSpan<int> curve_intervals,
MutableSpan<IndexRange> curve_interval_ranges,
MutableSpan<bool> is_first_selected)
{
const int last = offsets[curve_index + 1] - 1;
if (prev_range.has_value() && prev_range.value().last() >= offsets[curve_index]) {
finish_curve(curve_index,
interval_offset,
current_interval - 1,
last,
offsets,
new_offsets,
curve_intervals,
curve_interval_ranges,
is_first_selected);
}
else {
/* Copy full curve if previous selected point vas not on this curve. */
const int first = offsets[curve_index];
curve_interval_ranges[curve_index] = IndexRange(interval_offset, 1);
is_first_selected[curve_index] = false;
curve_intervals[interval_offset] = first;
curve_intervals[interval_offset + 1] = last;
calc_curve_offset(curve_index, interval_offset, offsets, new_offsets, curve_interval_ranges);
curve_index++;
}
}
static void calc_curves_extrusion(const IndexMask &selection,
const Span<int> offsets,
MutableSpan<int> new_offsets,
MutableSpan<int> curve_intervals,
MutableSpan<IndexRange> curve_interval_ranges,
MutableSpan<bool> is_first_selected)
{
std::optional<IndexRange> prev_range;
int current_interval = 0;
int curve_index = 0;
int interval_offset = 0;
curve_intervals[interval_offset] = offsets[0];
new_offsets[0] = offsets[0];
selection.foreach_range([&](const IndexRange range) {
/* Beginning of the range outside current curve. */
if (range.first() > offsets[curve_index + 1] - 1) {
do {
finish_curve_or_full_copy(curve_index,
interval_offset,
current_interval,
prev_range,
offsets,
new_offsets,
curve_intervals,
curve_interval_ranges,
is_first_selected);
} while (range.first() > offsets[curve_index + 1] - 1);
current_interval = 0;
curve_intervals[interval_offset] = offsets[curve_index];
}
IndexRange range_to_handle = range;
while (!handle_range(curve_index,
interval_offset,
offsets,
current_interval,
range_to_handle,
curve_intervals,
is_first_selected))
{
finish_curve(curve_index,
interval_offset,
current_interval - 1,
offsets[curve_index + 1] - 1,
offsets,
new_offsets,
curve_intervals,
curve_interval_ranges,
is_first_selected);
current_interval = 0;
curve_intervals[interval_offset] = offsets[curve_index];
}
prev_range = range;
});
do {
finish_curve_or_full_copy(curve_index,
interval_offset,
current_interval,
prev_range,
offsets,
new_offsets,
curve_intervals,
curve_interval_ranges,
is_first_selected);
prev_range.reset();
} while (curve_index < offsets.size() - 1);
}
static void extrude_curves(Curves &curves_id)
{
const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id.selection_domain);
if (selection_domain != bke::AttrDomain::Point) {
return;
}
IndexMaskMemory memory;
const IndexMask extruded_points = retrieve_selected_points(curves_id, memory);
if (extruded_points.is_empty()) {
return;
}
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
const Span<int> old_offsets = curves.offsets();
bke::CurvesGeometry new_curves = bke::curves::copy_only_curve_domain(curves);
const int curves_num = curves.curves_num();
const int curve_intervals_size = extruded_points.size() * 2 + curves_num * 2;
new_curves.resize(0, curves_num);
MutableSpan<int> new_offsets = new_curves.offsets_for_write();
/* Buffer for intervals of all curves. Beginning and end of a curve can be determined only by
* #curve_interval_ranges. For ex. [0, 3, 4, 4, 4] indicates one copy interval for first curve
* [0, 3] and two for second [4, 4][4, 4]. The first curve will be copied as is without changes,
* in the second one (consisting only one point - 4) first point will be duplicated (extruded).
*/
Array<int> curve_intervals(curve_intervals_size);
/* Points to intervals for each curve in the curve_intervals array.
* For example above value would be [{0, 1}, {2, 2}] */
Array<IndexRange> curve_interval_ranges(curves_num);
/* Per curve boolean indicating if first interval in a curve is selected.
* Other can be calculated as in a curve two adjacent intervals can have same selection state. */
Array<bool> is_first_selected(curves_num);
calc_curves_extrusion(extruded_points,
old_offsets,
new_offsets,
curve_intervals,
curve_interval_ranges,
is_first_selected);
new_curves.resize(new_offsets.last(), new_curves.curves_num());
const bke::AttributeAccessor src_attributes = curves.attributes();
const GVArraySpan src_selection = *src_attributes.lookup(".selection", bke::AttrDomain::Point);
const CPPType &src_selection_type = src_selection.type();
bke::GSpanAttributeWriter dst_selection = ensure_selection_attribute(
new_curves,
bke::AttrDomain::Point,
src_selection_type.is<bool>() ? CD_PROP_BOOL : CD_PROP_FLOAT);
threading::parallel_for(curves.curves_range(), 256, [&](IndexRange curves_range) {
for (const int curve : curves_range) {
const int first_index = curve_interval_ranges[curve].start();
const int first_value = curve_intervals[first_index];
bool is_selected = is_first_selected[curve];
for (const int i : curve_interval_ranges[curve]) {
const int dest_index = new_offsets[curve] + curve_intervals[i] - first_value + i -
first_index;
const int size = curve_intervals[i + 1] - curve_intervals[i] + 1;
GMutableSpan dst_span = dst_selection.span.slice(IndexRange(dest_index, size));
if (is_selected) {
src_selection_type.copy_assign_n(
src_selection.slice(IndexRange(curve_intervals[i], size)).data(),
dst_span.data(),
size);
}
else {
fill_selection(dst_span, false);
}
is_selected = !is_selected;
}
}
});
dst_selection.finish();
const Span<int> intervals = compress_intervals(curve_interval_ranges, curve_intervals);
bke::MutableAttributeAccessor dst_attributes = new_curves.attributes_for_write();
for (auto &attribute : bke::retrieve_attributes_for_transfer(
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, {}, {".selection"}))
{
const CPPType &type = attribute.src.type();
threading::parallel_for(IndexRange(intervals.size() - 1), 512, [&](IndexRange range) {
for (const int i : range) {
const int first = intervals[i];
const int size = intervals[i + 1] - first + 1;
const int dest_index = intervals[i] + i;
type.copy_assign_n(attribute.src.slice(IndexRange(first, size)).data(),
attribute.dst.span.slice(IndexRange(dest_index, size)).data(),
size);
}
});
attribute.dst.finish();
}
curves_id.geometry.wrap() = std::move(new_curves);
DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
}
static int curves_extrude_exec(bContext *C, wmOperator * /*op*/)
{
for (Curves *curves_id : get_unique_editable_curves(*C)) {
extrude_curves(*curves_id);
}
return OPERATOR_FINISHED;
}
void CURVES_OT_extrude(wmOperatorType *ot)
{
ot->name = "Extrude";
ot->description = "Extrude selected control point(s)";
ot->idname = "CURVES_OT_extrude";
ot->exec = curves_extrude_exec;
ot->poll = editable_curves_in_edit_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
} // namespace blender::ed::curves

View File

@ -1332,6 +1332,7 @@ void ED_operatortypes_curves()
WM_operatortype_append(CURVES_OT_convert_to_particle_system);
WM_operatortype_append(CURVES_OT_convert_from_particle_system);
WM_operatortype_append(CURVES_OT_draw);
WM_operatortype_append(CURVES_OT_extrude);
WM_operatortype_append(CURVES_OT_snap_curves_to_surface);
WM_operatortype_append(CURVES_OT_set_selection_domain);
WM_operatortype_append(CURVES_OT_select_all);
@ -1360,6 +1361,16 @@ void ED_operatormacros_curves()
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
/* Extrude + Move */
ot = WM_operatortype_append_macro("CURVES_OT_extrude_move",
"Extrude Curve and Move",
"Extrude curve and move result",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "CURVES_OT_extrude");
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
}
void ED_keymap_curves(wmKeyConfig *keyconf)

View File

@ -128,6 +128,16 @@ void fill_selection_true(GMutableSpan selection)
}
}
void fill_selection(GMutableSpan selection, bool value)
{
if (selection.type().is<bool>()) {
selection.typed<bool>().fill(value);
}
else if (selection.type().is<float>()) {
selection.typed<float>().fill(value ? 1.0f : 0.0f);
}
}
void fill_selection_false(GMutableSpan selection, const IndexMask &mask)
{
if (selection.type().is<bool>()) {
@ -173,7 +183,7 @@ static bool contains(const VArray<bool> &varray,
for (const int64_t segment_i : IndexRange(sliced_mask.segments_num())) {
const IndexMaskSegment segment = sliced_mask.segment(segment_i);
for (const int i : segment) {
if (span[i]) {
if (span[i] == value) {
return true;
}
}
@ -191,13 +201,15 @@ static bool contains(const VArray<bool> &varray,
return init;
}
constexpr int64_t MaxChunkSize = 512;
for (int64_t start = range.start(); start < range.last(); start += MaxChunkSize) {
const int64_t end = std::min<int64_t>(start + MaxChunkSize, range.last());
const int64_t slice_end = range.one_after_last();
for (int64_t start = range.start(); start < slice_end; start += MaxChunkSize) {
const int64_t end = std::min<int64_t>(start + MaxChunkSize, slice_end);
const int64_t size = end - start;
const IndexMask sliced_mask = indices_to_check.slice(start, size);
std::array<bool, MaxChunkSize> values;
auto values_end = values.begin() + size;
varray.materialize_compressed(sliced_mask, values);
if (std::find(values.begin(), values.end(), true) != values.end()) {
if (std::find(values.begin(), values_end, value) != values_end) {
return true;
}
}

View File

@ -12,6 +12,7 @@ set(INC
../../makesrna
../../windowmanager
../../../../extern/curve_fit_nd
../../geometry
# RNA_prototypes.h
${CMAKE_BINARY_DIR}/source/blender/makesrna
)

View File

@ -32,6 +32,8 @@
#include "ED_grease_pencil.hh"
#include "ED_screen.hh"
#include "GEO_subdivide_curves.hh"
#include "WM_api.hh"
#include "UI_resources.hh"
@ -1642,6 +1644,136 @@ static void GREASE_PENCIL_OT_clean_loose(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Stroke Subdivide Operator
* \{ */
static int gpencil_stroke_subdivide_exec(bContext *C, wmOperator *op)
{
const int cuts = RNA_int_get(op->ptr, "number_cuts");
const bool only_selected = RNA_boolean_get(op->ptr, "only_selected");
std::atomic<bool> changed = false;
const Scene *scene = CTX_data_scene(C);
Object *object = CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
const bke::AttrDomain selection_domain = ED_grease_pencil_selection_domain_get(
scene->toolsettings);
const Array<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
IndexMaskMemory memory;
const IndexMask strokes = ed::greasepencil::retrieve_editable_and_selected_strokes(
*object, info.drawing, memory);
if (strokes.is_empty()) {
return;
}
bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
VArray<int> vcuts = {};
if (selection_domain == bke::AttrDomain::Curve || !only_selected) {
/* Subdivide entire selected curve, every stroke subdivides to the same cut. */
vcuts = VArray<int>::ForSingle(cuts, curves.points_num());
}
else if (selection_domain == bke::AttrDomain::Point) {
/* Subdivide between selected points. Only cut between selected points.
* Make the cut array the same length as point count for specifying
* cut/uncut for each segment. */
const VArray<bool> selection = *curves.attributes().lookup_or_default<bool>(
".selection", bke::AttrDomain::Point, true);
const OffsetIndices points_by_curve = curves.points_by_curve();
const VArray<bool> cyclic = curves.cyclic();
Array<int> use_cuts(curves.points_num(), 0);
/* The cut is after each point, so the last point selected wouldn't need to be registered. */
for (const int curve : curves.curves_range()) {
/* No need to loop to the last point since the cut is registered on the point before the
* segment. */
for (const int point : points_by_curve[curve].drop_back(1)) {
/* The point itself should be selected. */
if (!selection[point]) {
continue;
}
/* If the next point in the curve is selected, then cut this segment. */
if (selection[point + 1]) {
use_cuts[point] = cuts;
}
}
/* Check for cyclic and selection. */
if (cyclic[curve]) {
const int first_point = points_by_curve[curve].first();
const int last_point = points_by_curve[curve].last();
if (selection[first_point] && selection[last_point]) {
use_cuts[last_point] = cuts;
}
}
}
vcuts = VArray<int>::ForContainer(std::move(use_cuts));
}
curves = geometry::subdivide_curves(curves, strokes, vcuts, {});
info.drawing.tag_topology_changed();
changed.store(true, std::memory_order_relaxed);
});
if (changed) {
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, nullptr);
}
return OPERATOR_FINISHED;
}
static void GREASE_PENCIL_OT_stroke_subdivide(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Subdivide Stroke";
ot->idname = "GREASE_PENCIL_OT_stroke_subdivide";
ot->description =
"Subdivide between continuous selected points of the stroke adding a point half way "
"between "
"them";
/* API callbacks. */
ot->exec = gpencil_stroke_subdivide_exec;
ot->poll = ed::greasepencil::editable_grease_pencil_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Properties. */
prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 32, "Number of Cuts", "", 1, 5);
/* Avoid re-using last var because it can cause _very_ high value and annoy users. */
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
RNA_def_boolean(ot->srna,
"only_selected",
true,
"Selected Points",
"Smooth only selected points in the stroke");
}
/** \} */
static void grease_pencil_operatormarcos_define()
{
wmOperatorType *ot;
ot = WM_operatortype_append_macro("GREASE_PENCIL_OT_stroke_subdivide_smooth",
"Subdivide and Smooth",
"Subdivide strokes and smooth them",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_stroke_subdivide");
WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_stroke_smooth");
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil_edit()
@ -1662,6 +1794,9 @@ void ED_operatortypes_grease_pencil_edit()
WM_operatortype_append(GREASE_PENCIL_OT_duplicate);
WM_operatortype_append(GREASE_PENCIL_OT_set_material);
WM_operatortype_append(GREASE_PENCIL_OT_clean_loose);
WM_operatortype_append(GREASE_PENCIL_OT_stroke_subdivide);
grease_pencil_operatormarcos_define();
}
void ED_keymap_grease_pencil(wmKeyConfig *keyconf)

View File

@ -152,7 +152,7 @@ bool duplicate_selected_frames(GreasePencil &grease_pencil, bke::greasepencil::L
}
/* Create the duplicate drawing. */
const Drawing *drawing = grease_pencil.get_editable_drawing_at(&layer, frame_number);
const Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, frame_number);
if (drawing == nullptr) {
continue;
}

View File

@ -263,12 +263,12 @@ Array<MutableDrawingInfo> retrieve_editable_drawings(const Scene &scene,
Vector<MutableDrawingInfo> editable_drawings;
Span<const Layer *> layers = grease_pencil.layers();
for (const int layer_i : layers.index_range()) {
const Layer *layer = layers[layer_i];
if (!layer->is_editable()) {
const Layer &layer = *layers[layer_i];
if (!layer.is_editable()) {
continue;
}
const Array<int> frame_numbers = get_frame_numbers_for_layer(
*layer, current_frame, use_multi_frame_editing);
layer, current_frame, use_multi_frame_editing);
for (const int frame_number : frame_numbers) {
if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, frame_number)) {
editable_drawings.append({*drawing, layer_i, frame_number});
@ -290,12 +290,12 @@ Array<DrawingInfo> retrieve_visible_drawings(const Scene &scene, const GreasePen
Vector<DrawingInfo> visible_drawings;
Span<const Layer *> layers = grease_pencil.layers();
for (const int layer_i : layers.index_range()) {
const Layer *layer = layers[layer_i];
if (!layer->is_visible()) {
const Layer &layer = *layers[layer_i];
if (!layer.is_visible()) {
continue;
}
const Array<int> frame_numbers = get_frame_numbers_for_layer(
*layer, current_frame, use_multi_frame_editing);
layer, current_frame, use_multi_frame_editing);
for (const int frame_number : frame_numbers) {
if (const Drawing *drawing = grease_pencil.get_drawing_at(layer, frame_number)) {
visible_drawings.append({*drawing, layer_i, frame_number});

View File

@ -80,6 +80,7 @@ bool curves_poll(bContext *C);
void CURVES_OT_attribute_set(wmOperatorType *ot);
void CURVES_OT_draw(wmOperatorType *ot);
void CURVES_OT_extrude(wmOperatorType *ot);
/** \} */
@ -143,6 +144,7 @@ IndexMask random_mask(const bke::CurvesGeometry &curves,
void fill_selection_false(GMutableSpan span);
void fill_selection_true(GMutableSpan span);
void fill_selection(GMutableSpan selection, bool value);
void fill_selection_false(GMutableSpan selection, const IndexMask &mask);
void fill_selection_true(GMutableSpan selection, const IndexMask &mask);

View File

@ -5426,30 +5426,31 @@ static bool button_matches_search_filter(uiBut *but, const char *search_filter)
if (but->type == UI_BTYPE_MENU) {
PointerRNA *ptr = &but->rnapoin;
PropertyRNA *enum_prop = but->rnaprop;
int items_len;
const EnumPropertyItem *items_array = nullptr;
bool free;
RNA_property_enum_items_gettexted(nullptr, ptr, enum_prop, &items_array, &items_len, &free);
BLI_SCOPED_DEFER([&] {
if (free) {
MEM_freeN((EnumPropertyItem *)items_array);
}
});
if (items_array == nullptr) {
return false;
}
bool found = false;
for (int i = 0; i < items_len; i++) {
/* Check for nullptr name field which enums use for separators. */
if (items_array[i].name == nullptr) {
continue;
}
if (BLI_strcasestr(items_array[i].name, search_filter)) {
return true;
found = true;
break;
}
}
if (free) {
MEM_freeN((EnumPropertyItem *)items_array);
}
if (found) {
return true;
}
}
}

View File

@ -239,11 +239,6 @@ class BoneCollectionItem : public AbstractTreeViewItem {
UI_menutype_draw(&C, mt, &column);
}
bool supports_collapsing() const override
{
return true;
}
std::optional<bool> should_be_active() const override
{
return armature_.runtime.active_collection_index == bcoll_index_;

View File

@ -7,6 +7,7 @@
*/
#include <cfloat>
#include <cmath>
#include <limits>
#include <stdexcept>
@ -236,7 +237,7 @@ IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range() const
{
int first_idx_in_view = 0;
const float scroll_ofs_y = abs(v2d_.cur.ymax - v2d_.tot.ymax);
const float scroll_ofs_y = std::abs(v2d_.cur.ymax - v2d_.tot.ymax);
if (!IS_EQF(scroll_ofs_y, 0)) {
const int scrolled_away_rows = int(scroll_ofs_y) / style_.tile_height;

View File

@ -508,7 +508,7 @@ void AbstractTreeViewItem::toggle_collapsed()
is_open_ = !is_open_;
}
void AbstractTreeViewItem::set_collapsed(bool collapsed)
void AbstractTreeViewItem::set_collapsed(const bool collapsed)
{
is_open_ = !collapsed;
}

View File

@ -549,9 +549,16 @@ void ED_object_data_xform_by_mat4(XFormObjectData *xod_base, const float mat[4][
}
else {
MutableSpan<float3> positions = mesh->vert_positions_for_write();
#ifdef __GNUC__ /* Invalid `xod->elem_array` warning with GCC 13.2.1. */
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Warray-bounds"
#endif
for (const int i : positions.index_range()) {
mul_v3_m4v3(positions[i], mat, xod->elem_array[i]);
}
#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif
mesh->tag_positions_changed();
}

View File

@ -806,7 +806,7 @@ struct EraseOperationExecutor {
if (self.active_layer_only) {
/* Erase only on the drawing at the current frame of the active layer. */
const Layer *active_layer = grease_pencil.get_active_layer();
const Layer &active_layer = *grease_pencil.get_active_layer();
Drawing *drawing = grease_pencil.get_editable_drawing_at(active_layer, scene->r.cfra);
if (drawing == nullptr) {
@ -814,7 +814,7 @@ struct EraseOperationExecutor {
}
execute_eraser_on_drawing(
active_layer->drawing_index_at(scene->r.cfra), scene->r.cfra, *drawing);
active_layer.drawing_index_at(scene->r.cfra), scene->r.cfra, *drawing);
}
else {
/* Erase on all editable drawings. */

View File

@ -173,7 +173,7 @@ struct PaintOperationExecutor {
// brush->gpencil_settings->vertex_mode, GPPAINT_MODE_STROKE, GPPAINT_MODE_BOTH);
BLI_assert(grease_pencil->has_active_layer());
drawing_ = grease_pencil->get_editable_drawing_at(grease_pencil->get_active_layer(),
drawing_ = grease_pencil->get_editable_drawing_at(*grease_pencil->get_active_layer(),
scene_->r.cfra);
BLI_assert(drawing_ != nullptr);
}

View File

@ -584,7 +584,7 @@ static bool paint_draw_tex_overlay(UnifiedPaintSettings *ups,
/* Brush rotation. */
GPU_matrix_translate_2fv(center);
GPU_matrix_rotate_2d(-RAD2DEGF(primary ? ups->brush_rotation : ups->brush_rotation_sec));
GPU_matrix_rotate_2d(RAD2DEGF(primary ? ups->brush_rotation : ups->brush_rotation_sec));
GPU_matrix_translate_2f(-center[0], -center[1]);
/* Scale based on tablet pressure. */

View File

@ -400,7 +400,7 @@ static bool paint_brush_update(bContext *C,
ups->anchored_size = ups->pixel_radius = sqrtf(dx * dx + dy * dy);
ups->brush_rotation = ups->brush_rotation_sec = atan2f(dx, dy) + float(M_PI);
ups->brush_rotation = ups->brush_rotation_sec = atan2f(dy, dx) + float(0.5f * M_PI);
if (brush->flag & BRUSH_EDGE_TO_EDGE) {
halfway[0] = dx * 0.5f + stroke->initial_mouse[0];
@ -1373,7 +1373,7 @@ static bool paint_stroke_curve_end(bContext *C, wmOperator *op, PaintStroke *str
for (j = 0; j < PAINT_CURVE_NUM_SEGMENTS; j++) {
if (do_rake) {
float rotation = atan2f(tangents[2 * j], tangents[2 * j + 1]);
float rotation = atan2f(tangents[2 * j + 1], tangents[2 * j]) + float(0.5f * M_PI);
paint_update_brush_rake_rotation(ups, br, rotation);
}

View File

@ -2712,20 +2712,22 @@ static void update_sculpt_normal(Sculpt *sd, Object *ob, Span<PBVHNode *> nodes)
}
}
static void calc_local_y(ViewContext *vc, const float center[3], float y[3])
static void calc_local_from_screen(ViewContext *vc,
const float center[3],
const float screen_dir[2],
float r_local_dir[3])
{
Object *ob = vc->obact;
float loc[3];
const float xy_delta[2] = {0.0f, 1.0f};
mul_v3_m4v3(loc, ob->world_to_object, center);
mul_v3_m4v3(loc, ob->object_to_world, center);
const float zfac = ED_view3d_calc_zfac(vc->rv3d, loc);
ED_view3d_win_to_delta(vc->region, xy_delta, zfac, y);
normalize_v3(y);
ED_view3d_win_to_delta(vc->region, screen_dir, zfac, r_local_dir);
normalize_v3(r_local_dir);
add_v3_v3(y, ob->loc);
mul_m4_v3(ob->world_to_object, y);
add_v3_v3(r_local_dir, ob->loc);
mul_m4_v3(ob->world_to_object, r_local_dir);
}
static void calc_brush_local_mat(const float rotation,
@ -2738,7 +2740,6 @@ static void calc_brush_local_mat(const float rotation,
float mat[4][4];
float scale[4][4];
float angle, v[3];
float up[3];
/* Ensure `ob->world_to_object` is up to date. */
invert_m4_m4(ob->world_to_object, ob->object_to_world);
@ -2749,17 +2750,33 @@ static void calc_brush_local_mat(const float rotation,
mat[2][3] = 0.0f;
mat[3][3] = 1.0f;
/* Get view's up vector in object-space. */
calc_local_y(cache->vc, cache->location, up);
/* Read rotation (user angle, rake, etc.) to find the view's movement direction (negative X of
* the brush). */
angle = rotation + cache->special_rotation;
/* By convention, motion direction points down the brush's Y axis, the angle represents the X
* axis, normal is a 90 deg ccw rotation of the motion direction. */
float motion_normal_screen[2];
motion_normal_screen[0] = cosf(angle);
motion_normal_screen[1] = sinf(angle);
/* Convert view's brush transverse direction to object-space,
* i.e. the normal of the plane described by the motion */
float motion_normal_local[3];
calc_local_from_screen(cache->vc, cache->location, motion_normal_screen, motion_normal_local);
/* Calculate the X axis of the local matrix. */
cross_v3_v3v3(v, up, cache->sculpt_normal);
/* Apply rotation (user angle, rake, etc.) to X axis. */
angle = rotation - cache->special_rotation;
rotate_v3_v3v3fl(mat[0], v, cache->sculpt_normal, angle);
/* Calculate the movement direction for the local matrix.
* Note that there is a deliberate prioritization here: Our calculations are
* designed such that the _motion vector_ gets projected into the tangent space;
* in most cases this will be more intuitive than projecting the transverse
* direction (which is orthogonal to the motion direction and therefore less
* apparent to the user).
* The Y-axis of the brush-local frame has to lie in the intersection of the tangent plane
* and the motion plane. */
cross_v3_v3v3(v, cache->sculpt_normal, motion_normal_local);
normalize_v3_v3(mat[1], v);
/* Get other axes. */
cross_v3_v3v3(mat[1], cache->sculpt_normal, mat[0]);
cross_v3_v3v3(mat[0], mat[1], cache->sculpt_normal);
copy_v3_v3(mat[2], cache->sculpt_normal);
/* Set location. */

View File

@ -953,6 +953,7 @@ static void restore_list(bContext *C, Depsgraph *depsgraph, UndoSculpt &usculpt)
switch (unode->type) {
case Type::Position:
modified_verts_position.resize(ss->totvert, false);
if (restore_coords(C, ob, depsgraph, *unode, modified_verts_position)) {
changed_position = true;
}
@ -1032,7 +1033,7 @@ static void restore_list(bContext *C, Depsgraph *depsgraph, UndoSculpt &usculpt)
data.changed_mask = changed_mask;
data.pbvh = ss->pbvh;
data.modified_grids = modified_grids;
data.modified_hidden_verts = modified_verts_position;
data.modified_position_verts = modified_verts_position;
data.modified_hidden_verts = modified_verts_hide;
data.modified_hidden_faces = modified_faces_hide;
data.modified_mask_verts = modified_verts_mask;

View File

@ -87,6 +87,7 @@ static Set<StringRef> get_builtin_menus(const int tree_type)
"Curve/Operations",
"Curve/Primitives",
"Curve/Topology",
"Instances",
"Mesh",
"Mesh/Read",
"Mesh/Sample",

View File

@ -1225,6 +1225,12 @@ bNodeSocket *node_find_indicated_socket(SpaceNode &snode,
};
for (bNode *node : sorted_nodes) {
const bool node_hidden = node->flag & NODE_HIDDEN;
if (!node_hidden && node->runtime->totr.ymax - cursor.y < NODE_DY) {
/* Don't pick socket when cursor is over node header. This allows the user to always resize
* by dragging on the left and right side of the header. */
continue;
}
if (in_out & SOCK_IN) {
for (bNodeSocket *sock : node->input_sockets()) {
if (!node->is_socket_icon_drawn(*sock)) {
@ -1232,7 +1238,7 @@ bNodeSocket *node_find_indicated_socket(SpaceNode &snode,
}
const float2 location = sock->runtime->location;
const float distance = math::distance(location, cursor);
if (sock->flag & SOCK_MULTI_INPUT && !(node->flag & NODE_HIDDEN)) {
if (sock->flag & SOCK_MULTI_INPUT && !node_hidden) {
if (cursor_isect_multi_input_socket(cursor, *sock)) {
update_best_socket(sock, distance);
continue;
@ -1251,7 +1257,7 @@ bNodeSocket *node_find_indicated_socket(SpaceNode &snode,
const float2 location = sock->runtime->location;
const float distance = math::distance(location, cursor);
if (distance < max_distance) {
if (node->flag & NODE_HIDDEN) {
if (node_hidden) {
if (location.x - cursor.x > padded_socket_size) {
/* Needed to be able to resize collapsed nodes. */
continue;

View File

@ -1675,7 +1675,7 @@ static int sequencer_delete_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (sequencer_retiming_mode_is_active(C)) {
if (RNA_boolean_get(op->ptr, "use_retiming_mode")) {
sequencer_retiming_key_remove_exec(C, op);
}
@ -1711,9 +1711,26 @@ static int sequencer_delete_invoke(bContext *C, wmOperator *op, const wmEvent *e
}
}
if (sequencer_retiming_mode_is_active(C)) {
RNA_boolean_set(op->ptr, "use_retiming_mode", true);
}
return sequencer_delete_exec(C, op);
}
static bool sequencer_delete_poll_property(const bContext * /* C */,
wmOperator *op,
const PropertyRNA *prop)
{
const char *prop_id = RNA_property_identifier(prop);
if (STREQ(prop_id, "delete_data") && RNA_boolean_get(op->ptr, "use_retiming_mode")) {
return false;
}
return true;
}
void SEQUENCER_OT_delete(wmOperatorType *ot)
{
@ -1726,6 +1743,7 @@ void SEQUENCER_OT_delete(wmOperatorType *ot)
ot->invoke = sequencer_delete_invoke;
ot->exec = sequencer_delete_exec;
ot->poll = sequencer_edit_poll;
ot->poll_property = sequencer_delete_poll_property;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -1737,6 +1755,13 @@ void SEQUENCER_OT_delete(wmOperatorType *ot)
"Delete Data",
"After removing the Strip, delete the associated data also");
RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
ot->prop = RNA_def_boolean(ot->srna,
"use_retiming_mode",
false,
"Use Retiming Data",
"Operate on retiming data instead of strips");
RNA_def_property_flag(ot->prop, PROP_HIDDEN);
}
/** \} */

View File

@ -266,7 +266,7 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values(
if (STREQ(column_id.name, "Scale")) {
return std::make_unique<ColumnValues>(
column_id.name, VArray<float3>::ForFunc(domain_num, [transforms](int64_t index) {
return math::to_scale(transforms[index]);
return math::to_scale<true>(transforms[index]);
}));
}
}

View File

@ -1570,18 +1570,30 @@ static const EnumPropertyItem pack_margin_method_items[] = {
};
static const EnumPropertyItem pack_rotate_method_items[] = {
RNA_ENUM_ITEM_SEPR,
{ED_UVPACK_ROTATION_AXIS_ALIGNED,
"AXIS_ALIGNED",
0,
"Axis-aligned",
"Rotated to a minimal rectangle, either vertical or horizontal"},
{ED_UVPACK_ROTATION_ANY, "ANY", 0, "Any", "Any angle is allowed for rotation"},
{ED_UVPACK_ROTATION_CARDINAL,
"CARDINAL",
0,
"Cardinal",
"Only 90 degree rotations are allowed"},
{ED_UVPACK_ROTATION_ANY, "ANY", 0, "Any", "Any angle is allowed for rotation"},
RNA_ENUM_ITEM_SEPR,
#define PACK_ROTATE_METHOD_AXIS_ALIGNED_OFFSET 3
{ED_UVPACK_ROTATION_AXIS_ALIGNED,
"AXIS_ALIGNED",
0,
"Axis-aligned",
"Rotated to a minimal rectangle, either vertical or horizontal"},
{ED_UVPACK_ROTATION_AXIS_ALIGNED_X,
"AXIS_ALIGNED_X",
0,
"Axis-aligned (Horizontal)",
"Rotate islands to be aligned horizontally"},
{ED_UVPACK_ROTATION_AXIS_ALIGNED_Y,
"AXIS_ALIGNED_Y",
0,
"Axis-aligned (Vertical)",
"Rotate islands to be aligned vertically"},
{0, nullptr, 0, nullptr, nullptr},
};
@ -2954,7 +2966,7 @@ static int smart_project_exec(bContext *C, wmOperator *op)
const bool correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect");
blender::geometry::UVPackIsland_Params params;
params.rotate_method = ED_UVPACK_ROTATION_ANY;
params.rotate_method = eUVPackIsland_RotationMethod(RNA_enum_get(op->ptr, "rotate_method"));
params.only_selected_uvs = only_selected_uvs;
params.only_selected_faces = true;
params.correct_aspect = correct_aspect;
@ -3011,6 +3023,14 @@ void UV_OT_smart_project(wmOperatorType *ot)
ED_UVPACK_MARGIN_SCALED,
"Margin Method",
"");
RNA_def_enum(ot->srna,
"rotate_method",
/* Only show aligned options as the rotation from a projection
* generated from a direction vector isn't meaningful. */
pack_rotate_method_items + PACK_ROTATE_METHOD_AXIS_ALIGNED_OFFSET,
ED_UVPACK_ROTATION_AXIS_ALIGNED_Y,
"Rotation Method",
"");
RNA_def_float(ot->srna,
"island_margin",
0.0f,

View File

@ -33,6 +33,10 @@ enum eUVPackIsland_RotationMethod {
ED_UVPACK_ROTATION_NONE = 0,
/** Rotated to a minimal rectangle, either vertical or horizontal. */
ED_UVPACK_ROTATION_AXIS_ALIGNED,
/** Align along X axis (wide islands). */
ED_UVPACK_ROTATION_AXIS_ALIGNED_X,
/** Align along Y axis (tall islands). */
ED_UVPACK_ROTATION_AXIS_ALIGNED_Y,
/** Only 90 degree rotations are allowed. */
ED_UVPACK_ROTATION_CARDINAL,
/** Any angle. */
@ -146,7 +150,12 @@ class PackIsland {
void place_(float scale, UVPhi phi);
void finalize_geometry_(const UVPackIsland_Params &params, MemArena *arena, Heap *heap);
/**
* Check that rotation is allowed before packing,
* where the island may rotated to a desired orientation but not as part of packing.
* Needed for X/Y axis alignment to be supported.
*/
bool can_rotate_before_pack_(const UVPackIsland_Params &params) const;
bool can_rotate_(const UVPackIsland_Params &params) const;
bool can_scale_(const UVPackIsland_Params &params) const;
bool can_translate_(const UVPackIsland_Params &params) const;

View File

@ -216,11 +216,18 @@ static float angle_match(float angle_radians, float target_radians)
return angle_radians;
}
static float angle_wrap(float angle_radians)
{
angle_radians = angle_radians - floorf((angle_radians + M_PI_2) / M_PI) * M_PI;
BLI_assert(DEG2RADF(-90.0f) <= angle_radians);
BLI_assert(angle_radians <= DEG2RADF(90.0f));
return angle_radians;
}
/** Angle rounding helper for "D4" transforms. */
static float plusminus_90_angle(float angle_radians)
{
angle_radians = angle_radians - floorf((angle_radians + M_PI_2) / M_PI) * M_PI;
angle_radians = angle_wrap(angle_radians);
angle_radians = angle_match(angle_radians, DEG2RADF(-90.0f));
angle_radians = angle_match(angle_radians, DEG2RADF(0.0f));
angle_radians = angle_match(angle_radians, DEG2RADF(90.0f));
@ -232,15 +239,19 @@ static float plusminus_90_angle(float angle_radians)
void PackIsland::calculate_pre_rotation_(const UVPackIsland_Params &params)
{
pre_rotate_ = 0.0f;
if (!can_rotate_(params)) {
return; /* Nothing to do. */
}
if (params.rotate_method == ED_UVPACK_ROTATION_CARDINAL) {
/* Arbitrary rotations are not allowed. */
return;
}
BLI_assert(params.rotate_method == ED_UVPACK_ROTATION_ANY ||
params.rotate_method == ED_UVPACK_ROTATION_AXIS_ALIGNED);
if (!can_rotate_before_pack_(params)) {
return; /* Nothing to do. */
}
BLI_assert(ELEM(params.rotate_method,
ED_UVPACK_ROTATION_ANY,
ED_UVPACK_ROTATION_AXIS_ALIGNED,
ED_UVPACK_ROTATION_AXIS_ALIGNED_X,
ED_UVPACK_ROTATION_AXIS_ALIGNED_Y));
/* As a heuristic to improve layout efficiency, #PackIsland's are first rotated by an
* angle which minimizes the area of the enclosing AABB. This angle is stored in the
@ -273,11 +284,30 @@ void PackIsland::calculate_pre_rotation_(const UVPackIsland_Params &params)
Bounds<float2> island_bounds = *bounds::min_max(coords.as_span());
float2 diagonal = island_bounds.max - island_bounds.min;
if (diagonal.y < diagonal.x) {
angle += DEG2RADF(90.0f);
switch (params.rotate_method) {
case ED_UVPACK_ROTATION_AXIS_ALIGNED_X: {
if (diagonal.x < diagonal.y) {
angle += DEG2RADF(90.0f);
}
pre_rotate_ = angle_wrap(angle);
break;
}
case ED_UVPACK_ROTATION_AXIS_ALIGNED_Y: {
if (diagonal.x > diagonal.y) {
angle += DEG2RADF(90.0f);
}
pre_rotate_ = angle_wrap(angle);
break;
}
default: {
if (diagonal.y < diagonal.x) {
angle += DEG2RADF(90.0f);
}
pre_rotate_ = plusminus_90_angle(angle);
break;
}
}
}
pre_rotate_ = plusminus_90_angle(angle);
}
if (!pre_rotate_) {
return;
@ -2353,12 +2383,20 @@ void PackIsland::build_inverse_transformation(const float scale,
#endif
}
bool PackIsland::can_rotate_(const UVPackIsland_Params &params) const
static bool can_rotate_with_method(const PackIsland &island,
const UVPackIsland_Params &params,
const eUVPackIsland_RotationMethod rotate_method)
{
if (params.rotate_method == ED_UVPACK_ROTATION_NONE) {
/* When axis aligned along X/Y coordinates, rotation is performed once early on,
* but no rotation is allowed when packing. */
if (ELEM(rotate_method,
ED_UVPACK_ROTATION_NONE,
ED_UVPACK_ROTATION_AXIS_ALIGNED_X,
ED_UVPACK_ROTATION_AXIS_ALIGNED_Y))
{
return false;
}
if (!pinned) {
if (!island.pinned) {
return true;
}
switch (params.pin_method) {
@ -2371,6 +2409,20 @@ bool PackIsland::can_rotate_(const UVPackIsland_Params &params) const
}
}
bool PackIsland::can_rotate_before_pack_(const UVPackIsland_Params &params) const
{
eUVPackIsland_RotationMethod rotate_method = params.rotate_method;
if (ELEM(rotate_method, ED_UVPACK_ROTATION_AXIS_ALIGNED_X, ED_UVPACK_ROTATION_AXIS_ALIGNED_Y)) {
rotate_method = ED_UVPACK_ROTATION_AXIS_ALIGNED;
}
return can_rotate_with_method(*this, params, rotate_method);
}
bool PackIsland::can_rotate_(const UVPackIsland_Params &params) const
{
return can_rotate_with_method(*this, params, params.rotate_method);
}
bool PackIsland::can_scale_(const UVPackIsland_Params &params) const
{
if (!params.scale_to_fit) {

View File

@ -220,6 +220,8 @@ void node_bsdf_principled(vec4 base_color,
* So this has no performance penalty. However, using a separate closure for subsurface
* (just like for EEVEE-Next) would induce a huge performance hit. */
ClosureSubsurface diffuse_data;
/* Flag subsurface as disabled by default. */
diffuse_data.sss_radius.b = -1.0;
#else
ClosureDiffuse diffuse_data;
#endif
@ -234,11 +236,7 @@ void node_bsdf_principled(vec4 base_color,
/* Subsurface Scattering materials behave unpredictably with values greater than 1.0 in
* Cycles. So it's clamped there and we clamp here for consistency with Cycles. */
base_color = mix(base_color, clamped_base_color, subsurface_weight);
if (do_sss == 0.0) {
diffuse_data.sss_radius = vec3(-1);
}
else {
if (do_sss != 0.0) {
diffuse_data.sss_radius = subsurface_weight * subsurface_radius;
}
#else

View File

@ -26,7 +26,8 @@ void node_subsurface_scattering(vec4 color,
#ifdef GPU_SHADER_EEVEE_LEGACY_DEFINES
if (do_sss == 0.0) {
sss_data.sss_radius = vec3(-1);
/* Flag as disabled. */
sss_data.sss_radius.b = -1.0;
}
#endif
result = closure_eval(sss_data);

View File

@ -1015,9 +1015,10 @@ std::string VKShader::resources_declare(const shader::ShaderCreateInfo &info) co
ss << "const bool " << sc.name << "=" << (sc.default_value.u ? "true" : "false") << ";\n";
break;
case Type::FLOAT:
/* Use uint representation to allow exact same bit pattern even if NaN. */
ss << "const float " << sc.name << "= uintBitsToFloat("
<< std::to_string(sc.default_value.u) << "u);\n";
/* Use uint representation to allow exact same bit pattern even if NaN. uintBitsToFloat
* isn't supported during global const initialization. */
ss << "#define " << sc.name << " uintBitsToFloat(" << std::to_string(sc.default_value.u)
<< "u)\n";
break;
default:
BLI_assert_unreachable();

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2006 Blender Authors
# SPDX-FileCopyrightText: 2024 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
@ -197,3 +197,10 @@ set_source_files_properties(
)
blender_add_lib(bf_imbuf "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
set(TEST_SRC
intern/transform_test.cc
)
blender_add_test_suite_lib(imbuf "${TEST_SRC}" "${INC}" "${INC_SYS}" "${LIB}")
endif()

Some files were not shown because too many files have changed in this diff Show More