GPv3: Opacity modifier #116946
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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/)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 *>(
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -50,7 +50,7 @@ template<typename T> struct AngleRadianBase {
|
|||
|
||||
static AngleRadianBase from_degree(const T °rees)
|
||||
{
|
||||
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 °rees)
|
||||
{
|
||||
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". */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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))};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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. */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "BLI_math_vector.h"
|
||||
#include "BLI_rect.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
struct ColormanageProcessor;
|
||||
struct ImBuf;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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*/
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 Unity’s 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(
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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 Unity’s 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);
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ¤t_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
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ set(INC
|
|||
../../makesrna
|
||||
../../windowmanager
|
||||
../../../../extern/curve_fit_nd
|
||||
../../geometry
|
||||
# RNA_prototypes.h
|
||||
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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]);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 ¶ms, 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 ¶ms) const;
|
||||
bool can_rotate_(const UVPackIsland_Params ¶ms) const;
|
||||
bool can_scale_(const UVPackIsland_Params ¶ms) const;
|
||||
bool can_translate_(const UVPackIsland_Params ¶ms) const;
|
||||
|
|
|
@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
|||
|
||||
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 ¶ms) const
|
||||
static bool can_rotate_with_method(const PackIsland &island,
|
||||
const UVPackIsland_Params ¶ms,
|
||||
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 ¶ms) const
|
|||
}
|
||||
}
|
||||
|
||||
bool PackIsland::can_rotate_before_pack_(const UVPackIsland_Params ¶ms) 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 ¶ms) const
|
||||
{
|
||||
return can_rotate_with_method(*this, params, params.rotate_method);
|
||||
}
|
||||
|
||||
bool PackIsland::can_scale_(const UVPackIsland_Params ¶ms) const
|
||||
{
|
||||
if (!params.scale_to_fit) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue