WIP: Brush assets project #106303

Draft
Julian Eisel wants to merge 358 commits from brush-assets-project into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
44 changed files with 813 additions and 508 deletions
Showing only changes of commit 1657c3643d - Show all commits

View File

@ -1,4 +1,4 @@
set BUILD_VS_LIBDIR="lib\windows_x64"
set BUILD_VS_LIBDIR=lib/windows_x64
if NOT "%verbose%" == "" (
echo Library Directory = "%BUILD_VS_LIBDIR%"
@ -15,8 +15,8 @@ if NOT EXIST "%BUILD_VS_LIBDIR%\.git" (
echo Downloading %BUILD_VS_LIBDIR% libraries, please wait.
echo.
:RETRY
"%GIT%" -C "%BLENDER_DIR%" config --local "submodule.%BUILD_VS_LIBDIR%.update" "checkout"
"%GIT%" -C "%BLENDER_DIR%" submodule update --init "%BUILD_VS_LIBDIR%"
"%GIT%" -C "%BLENDER_DIR%\" config --local "submodule.%BUILD_VS_LIBDIR%.update" "checkout"
"%GIT%" -C "%BLENDER_DIR%\" submodule update --progress --init "%BUILD_VS_LIBDIR%"
if errorlevel 1 (
set /p LibRetry= "Error during download, retry? y/n"
if /I "!LibRetry!"=="Y" (

View File

@ -24,10 +24,7 @@ if EXIST %PYTHON% (
)
if NOT EXIST %PYTHON% (
REM Only emit this warning when the library folder exists but the
REM python folder does not. As we don't want to concern people that
REM run make update for the first time.
if EXIST %BLENDER_DIR%\..\lib\win64_vc15 (
if EXIST %BLENDER_DIR%\lib\windows_x64\ (
echo Warning: Python not found, there is likely an issue with the library folder
)
set PYTHON=""

View File

@ -1,8 +1,8 @@
set BUILD_VS_LIBDIR="lib\windows_x64"
set BUILD_VS_LIBDIR=lib/windows_x64
:RETRY
"%GIT%" -C "%BLENDER_DIR%" config --local "submodule.%BUILD_VS_LIBDIR%.update" "checkout"
"%GIT%" -C "%BLENDER_DIR%" submodule update --init "%BUILD_VS_LIBDIR%"
"%GIT%" -C "%BLENDER_DIR%\" config --local "submodule.%BUILD_VS_LIBDIR%.update" "checkout"
"%GIT%" -C "%BLENDER_DIR%\" submodule update --progress --init "%BUILD_VS_LIBDIR%"
if errorlevel 1 (
set /p LibRetry= "Error during update, retry? y/n"
if /I "!LibRetry!"=="Y" (
@ -14,3 +14,6 @@ if errorlevel 1 (
echo.
exit /b 1
)
REM re-detect the dependencies after updating the libraries so any python version
REM changes are accounted for.
call "%~dp0\find_dependencies.cmd"

View File

@ -29,6 +29,12 @@ typedef struct HuangHairExtra {
/* Squared Eccentricity. */
float e2;
/* The projected width of half a pixel at `sd->P` in `h` space. */
float pixel_coverage;
/* Valid integration interval. */
float gamma_m_min, gamma_m_max;
} HuangHairExtra;
typedef struct HuangHairBSDF {
@ -135,6 +141,14 @@ ccl_device_inline float to_gamma(float phi, float b)
return atan2f(sin_phi, b * cos_phi);
}
/* Intersect `wi` with the ellipse defined by `x = sin_gamma, y = b * cos_gamma` results in solving
* for `gamma` in equation `-cos_phi_i * sin_gamma + b * sin_phi_i * cos_gamma = h`.
* Also, make use of `r = sqrt(sqr(cos_phi_i) + sqr(b * sin_phi_i))` to pre-map `h` to [-1, 1]. */
ccl_device_inline float h_to_gamma(const float h_div_r, const float b, const float3 wi)
{
return (b == 1.0f) ? -asinf(h_div_r) : atan2f(wi.z, -b * wi.x) - acosf(-h_div_r);
}
/* Compute the coordinate on the ellipse, given `gamma` and the aspect ratio between the minor axis
* and the major axis. */
ccl_device_inline float2 to_point(float gamma, float b)
@ -170,6 +184,11 @@ ccl_device_inline float arc_length(float e2, float gamma)
return e2 == 0 ? 1.0f : sqrtf(1.0f - e2 * sqr(sinf(gamma)));
}
ccl_device_inline bool is_nearfield(ccl_private const HuangHairBSDF *bsdf)
{
return bsdf->extra->radius > bsdf->extra->pixel_coverage;
}
/** \} */
#ifdef __HAIR__
@ -188,8 +207,8 @@ ccl_device int bsdf_hair_huang_setup(ccl_private ShaderData *sd,
/* Compute local frame. The Y axis is aligned with the curve tangent; the X axis is perpendicular
to the ray direction for circular cross-sections, or aligned with the major axis for elliptical
cross-sections. */
const float3 Y = safe_normalize(sd->dPdu);
const float3 X = safe_normalize(cross(Y, sd->wi));
bsdf->extra->Y = safe_normalize(sd->dPdu);
const float3 X = safe_normalize(cross(sd->dPdu, sd->wi));
/* h from -1..0..1 means the rays goes from grazing the hair, to hitting it at the center, to
* grazing the other edge. This is the cosine of the angle between `sd->N` and `X`. */
@ -199,6 +218,8 @@ ccl_device int bsdf_hair_huang_setup(ccl_private ShaderData *sd,
kernel_assert(isfinite_safe(bsdf->h));
if (bsdf->aspect_ratio != 1.0f && (sd->type & PRIMITIVE_CURVE)) {
/* Adjust `bsdf->N` to be orthogonal to `sd->dPdu`. */
bsdf->N = safe_normalize(cross(sd->dPdu, safe_normalize(cross(bsdf->N, sd->dPdu))));
/* Align local frame with the curve normal. */
if (bsdf->aspect_ratio > 1.0f) {
/* Switch major and minor axis. */
@ -214,14 +235,12 @@ ccl_device int bsdf_hair_huang_setup(ccl_private ShaderData *sd,
/* Fill extra closure. */
if (is_zero(bsdf->N) || !isfinite_safe(bsdf->N)) {
bsdf->extra->Y = Y;
/* Construct arbitrary local coordinate system. The implementation should ensure smooth
* transition along the hair shaft. */
make_orthonormals(Y, &bsdf->extra->Z, &bsdf->N);
make_orthonormals(bsdf->extra->Y, &bsdf->extra->Z, &bsdf->N);
}
else {
bsdf->extra->Z = safe_normalize(cross(bsdf->N, sd->dPdu));
bsdf->extra->Y = safe_normalize(cross(bsdf->extra->Z, bsdf->N));
}
const float3 I = make_float3(
@ -311,50 +330,6 @@ ccl_device Spectrum bsdf_hair_huang_eval_r(KernelGlobals kg,
/* Get minor axis, assuming major axis is 1. */
const float b = bsdf->aspect_ratio;
const bool is_circular = (b == 1.0f);
const float phi_i = is_circular ? 0.0f : dir_phi(wi);
const float phi_o = dir_phi(wo);
/* Compute visible azimuthal range from incoming and outgoing directions. */
/* `dot(wi, wmi) > 0` */
const float tan_tilt = tanf(bsdf->tilt);
float phi_m_max1 = acosf(fmaxf(-tan_tilt * tan_theta(wi), 0.0f)) + phi_i;
if (isnan_safe(phi_m_max1)) {
return zero_spectrum();
}
float phi_m_min1 = -phi_m_max1 + 2.0f * phi_i;
/* `dot(wo, wmi) > 0` */
float phi_m_max2 = acosf(fmaxf(-tan_tilt * tan_theta(wo), 0.0f)) + phi_o;
if (isnan_safe(phi_m_max2)) {
return zero_spectrum();
}
float phi_m_min2 = -phi_m_max2 + 2.0f * phi_o;
if (!is_circular) {
/* Try to wrap range. */
if ((phi_m_max2 - phi_m_min1) > M_2PI_F) {
phi_m_min2 -= M_2PI_F;
phi_m_max2 -= M_2PI_F;
}
if ((phi_m_max1 - phi_m_min2) > M_2PI_F) {
phi_m_min1 -= M_2PI_F;
phi_m_max1 -= M_2PI_F;
}
}
const float phi_m_min = fmaxf(phi_m_min1, phi_m_min2) + 1e-3f;
const float phi_m_max = fminf(phi_m_max1, phi_m_max2) - 1e-3f;
if (phi_m_min > phi_m_max) {
return zero_spectrum();
}
const float gamma_m_min = to_gamma(phi_m_min, b);
float gamma_m_max = to_gamma(phi_m_max, b);
if (gamma_m_max < gamma_m_min) {
gamma_m_max += M_2PI_F;
}
const float3 wh = normalize(wi + wo);
@ -363,16 +338,19 @@ ccl_device Spectrum bsdf_hair_huang_eval_r(KernelGlobals kg,
/* Maximal sample resolution. */
float res = roughness * 0.7f;
const float gamma_m_range = bsdf->extra->gamma_m_max - bsdf->extra->gamma_m_min;
/* Number of intervals should be even. */
const size_t intervals = 2 * (size_t)ceilf((gamma_m_max - gamma_m_min) / res * 0.5f);
const size_t intervals = 2 * (size_t)ceilf(gamma_m_range / res * 0.5f);
/* Modified resolution based on numbers of intervals. */
res = (gamma_m_max - gamma_m_min) / float(intervals);
res = gamma_m_range / float(intervals);
/* Integrate using Composite Simpson's 1/3 rule. */
float integral = 0.0f;
for (size_t i = 0; i <= intervals; i++) {
const float gamma_m = gamma_m_min + i * res;
const float gamma_m = bsdf->extra->gamma_m_min + i * res;
const float3 wm = sphg_dir(bsdf->tilt, gamma_m, b);
if (microfacet_visible(wi, wo, make_float3(wm.x, 0.0f, wm.z), wh)) {
@ -385,11 +363,12 @@ ccl_device Spectrum bsdf_hair_huang_eval_r(KernelGlobals kg,
}
}
/* Simpson coefficient */
integral *= (2.0f / 3.0f * res);
const float F = fresnel_dielectric_cos(dot(wi, wh), bsdf->eta);
return make_spectrum(bsdf->extra->R * 0.125f * F * integral / bsdf->extra->radius);
return make_spectrum(bsdf->extra->R * 0.25f * F * integral);
}
/* Approximate components beyond TRT (starting TRRT) by summing up a geometric series. Attenuations
@ -420,46 +399,25 @@ ccl_device Spectrum bsdf_hair_huang_eval_residual(KernelGlobals kg,
const float b = bsdf->aspect_ratio;
const bool is_circular = (b == 1.0f);
const float phi_i = is_circular ? 0.0f : dir_phi(wi);
/* Compute visible azimuthal range from the incoming direction. */
const float tan_tilt = tanf(bsdf->tilt);
const float phi_m_max = acosf(fmaxf(-tan_tilt * tan_theta(wi), 0.0f)) + phi_i;
if (isnan_safe(phi_m_max)) {
/* Early detection of `dot(wi, wmi) < 0`. */
return zero_spectrum();
}
const float phi_m_min = -phi_m_max + 2.0f * phi_i;
if (tan_tilt * tan_theta(wo) < -1.0f) {
/* Early detection of `dot(wo, wmo) < 0`. */
return zero_spectrum();
}
const Spectrum mu_a = bsdf->sigma;
const float eta = bsdf->eta;
const float inv_eta = 1.0f / eta;
const float gamma_m_min = to_gamma(phi_m_min, b) + 1e-3f;
float gamma_m_max = to_gamma(phi_m_max, b) - 1e-3f;
if (gamma_m_max < gamma_m_min) {
gamma_m_max += M_2PI_F;
}
const float roughness = bsdf->roughness;
const float roughness2 = sqr(roughness);
const float sqrt_roughness = sqrtf(roughness);
float res = roughness * 0.8f;
const size_t intervals = 2 * (size_t)ceilf((gamma_m_max - gamma_m_min) / res * 0.5f);
res = (gamma_m_max - gamma_m_min) / intervals;
const float gamma_m_range = bsdf->extra->gamma_m_max - bsdf->extra->gamma_m_min;
const size_t intervals = 2 * (size_t)ceilf(gamma_m_range / res * 0.5f);
res = gamma_m_range / intervals;
Spectrum S_tt = zero_spectrum();
Spectrum S_trt = zero_spectrum();
Spectrum S_trrt = zero_spectrum();
for (size_t i = 0; i <= intervals; i++) {
const float gamma_mi = gamma_m_min + i * res;
const float gamma_mi = bsdf->extra->gamma_m_min + i * res;
const float3 wmi = sphg_dir(bsdf->tilt, gamma_mi, b);
const float3 wmi_ = sphg_dir(0.0f, gamma_mi, b);
@ -603,8 +561,9 @@ ccl_device Spectrum bsdf_hair_huang_eval_residual(KernelGlobals kg,
sin_theta(wi), cos_theta(wi), sin_theta(wo), cos_theta(wo), 4.0f * bsdf->roughness);
const float N = M_1_2PI_F;
return ((S_tt + S_trt) * sqr(inv_eta) / bsdf->extra->radius + S_trrt * M * N * M_2_PI_F) * res /
3.0f;
const float simpson_coeff = 2.0f / 3.0f * res;
return ((S_tt + S_trt) * sqr(inv_eta) + S_trrt * M * N * M_2_PI_F) * simpson_coeff;
}
ccl_device int bsdf_hair_huang_sample(const KernelGlobals kg,
@ -635,20 +594,14 @@ ccl_device int bsdf_hair_huang_sample(const KernelGlobals kg,
/* Get `wi` in local coordinate. */
const float3 wi = bsdf->extra->wi;
const float2 sincos_phi_i = sincos_phi(wi);
const float sin_phi_i = sincos_phi_i.x;
const float cos_phi_i = sincos_phi_i.y;
/* Get minor axis, assuming major axis is 1. */
const float b = bsdf->aspect_ratio;
const bool is_circular = (b == 1.0f);
const float h = sample_h * 2.0f - 1.0f;
const float gamma_mi = is_circular ?
asinf(h) :
atan2f(cos_phi_i, -b * sin_phi_i) -
acosf(h * bsdf->extra->radius *
inversesqrtf(sqr(cos_phi_i) + sqr(b * sin_phi_i)));
/* Sample `h` for farfield model, as the computed intersection might have numerical issues. */
const float h_div_r = is_nearfield(bsdf) ? bsdf->h / bsdf->extra->radius :
(sample_h * 2.0f - 1.0f);
const float gamma_mi = h_to_gamma(h_div_r, b, wi);
/* Macronormal. */
const float3 wmi_ = sphg_dir(0, gamma_mi, b);
@ -853,9 +806,74 @@ ccl_device Spectrum bsdf_hair_huang_eval(KernelGlobals kg,
/* TODO: better estimation of the pdf */
*pdf = 1.0f;
/* Early detection of `dot(wo, wmo) < 0`. */
const float tan_tilt = tanf(bsdf->tilt);
if (tan_tilt * tan_theta(local_O) < -1.0f) {
return zero_spectrum();
}
/* Compute visible azimuthal range from the incoming direction. */
const float half_span = acosf(fmaxf(-tan_tilt * tan_theta(local_I), 0.0f));
if (isnan_safe(half_span)) {
/* Early detection of `dot(wi, wmi) < 0`. */
return zero_spectrum();
}
const float r = bsdf->extra->radius;
const float b = bsdf->aspect_ratio;
const float phi_i = (b == 1.0f) ? 0.0f : dir_phi(local_I);
float gamma_m_min = to_gamma(phi_i - half_span, b);
float gamma_m_max = to_gamma(phi_i + half_span, b);
if (gamma_m_max < gamma_m_min) {
gamma_m_max += M_2PI_F;
}
/* Prevent numerical issues at the boundary. */
gamma_m_min += 1e-3f;
gamma_m_max -= 1e-3f;
/* Length of the integral interval. */
float dh = 2.0f * r;
if (is_nearfield(bsdf)) {
/* Reduce the integration interval to the subset that's visible to the current pixel.
* Inspired by [An Efficient and Practical Near and Far Field Fur Reflectance Model]
* (https://sites.cs.ucsb.edu/~lingqi/publications/paper_fur2.pdf) by Ling-Qi Yan, Henrik Wann
* Jensen and Ravi Ramamoorthi. */
const float h_max = min(bsdf->h + bsdf->extra->pixel_coverage, r);
const float h_min = max(bsdf->h - bsdf->extra->pixel_coverage, -r);
/* At the boundaries the hair might not cover the whole pixel. */
dh = h_max - h_min;
float nearfield_gamma_min = h_to_gamma(h_max / r, bsdf->aspect_ratio, local_I);
float nearfield_gamma_max = h_to_gamma(h_min / r, bsdf->aspect_ratio, local_I);
if (nearfield_gamma_max < nearfield_gamma_min) {
nearfield_gamma_max += M_2PI_F;
}
/* Wrap range to compute the intersection. */
if ((gamma_m_max - nearfield_gamma_min) > M_2PI_F) {
gamma_m_min -= M_2PI_F;
gamma_m_max -= M_2PI_F;
}
else if ((nearfield_gamma_max - gamma_m_min) > M_2PI_F) {
nearfield_gamma_min -= M_2PI_F;
nearfield_gamma_max -= M_2PI_F;
}
gamma_m_min = fmaxf(gamma_m_min, nearfield_gamma_min);
gamma_m_max = fminf(gamma_m_max, nearfield_gamma_max);
}
bsdf->extra->gamma_m_min = gamma_m_min;
bsdf->extra->gamma_m_max = gamma_m_max;
const float projected_area = cos_theta(local_I) * dh;
return (bsdf_hair_huang_eval_r(kg, sc, local_I, local_O) +
bsdf_hair_huang_eval_residual(kg, sc, local_I, local_O, sd->lcg_state)) /
cos_theta(local_I);
projected_area;
}
/* Implements Filter Glossy by capping the effective roughness. */

View File

@ -988,6 +988,21 @@ ccl_device void osl_closure_hair_huang_setup(KernelGlobals kg,
bsdf->extra->TT = closure->tt_lobe;
bsdf->extra->TRT = closure->trt_lobe;
bsdf->extra->pixel_coverage = 1.0f;
/* For camera ray, check if the hair covers more than one pixel, in which case a nearfield model
* is needed to prevent ribbon-like appearance. */
if ((path_flag & PATH_RAY_CAMERA) && (sd->type & PRIMITIVE_CURVE)) {
/* Interpolate radius between curve keys. */
const KernelCurve kcurve = kernel_data_fetch(curves, sd->prim);
const int k0 = kcurve.first_key + PRIMITIVE_UNPACK_SEGMENT(sd->type);
const int k1 = k0 + 1;
const float radius = mix(
kernel_data_fetch(curve_keys, k0).w, kernel_data_fetch(curve_keys, k1).w, sd->u);
bsdf->extra->pixel_coverage = 0.5f * sd->dP / radius;
}
sd->flag |= bsdf_hair_huang_setup(sd, bsdf, path_flag);
#endif
}

View File

@ -790,6 +790,21 @@ ccl_device
bsdf->extra->TT = fmaxf(0.0f, TT);
bsdf->extra->TRT = fmaxf(0.0f, TRT);
bsdf->extra->pixel_coverage = 1.0f;
/* For camera ray, check if the hair covers more than one pixel, in which case a
* nearfield model is needed to prevent ribbon-like appearance. */
if ((path_flag & PATH_RAY_CAMERA) && (sd->type & PRIMITIVE_CURVE)) {
/* Interpolate radius between curve keys. */
const KernelCurve kcurve = kernel_data_fetch(curves, sd->prim);
const int k0 = kcurve.first_key + PRIMITIVE_UNPACK_SEGMENT(sd->type);
const int k1 = k0 + 1;
const float radius = mix(
kernel_data_fetch(curve_keys, k0).w, kernel_data_fetch(curve_keys, k1).w, sd->u);
bsdf->extra->pixel_coverage = 0.5f * sd->dP / radius;
}
bsdf->aspect_ratio = stack_load_float_default(stack, shared_ofs1, data_node3.w);
if (bsdf->aspect_ratio != 1.0f) {
/* Align ellipse major axis with the curve normal direction. */

View File

@ -844,7 +844,7 @@ class PrincipledHairBsdfNode : public BsdfBaseNode {
NODE_SOCKET_API(float, random)
/* Selected coloring parametrization. */
NODE_SOCKET_API(NodePrincipledHairParametrization, parametrization)
/* Selected scattering model (near-/far-field). */
/* Selected scattering model (chiang/huang). */
NODE_SOCKET_API(NodePrincipledHairModel, model)
virtual int get_feature()

View File

@ -131,11 +131,9 @@ static bool egl_chk(bool result,
{
if (!result) {
const EGLint error = eglGetError();
#ifndef NDEBUG
const char *code = get_egl_error_enum_string(error);
const char *msg = get_egl_error_message_string(error);
#ifndef NDEBUG
fprintf(stderr,
"%s:%d: [%s] -> EGL Error (0x%04X): %s: %s\n",
file,

View File

@ -57,7 +57,7 @@ if not os.path.exists(blender_bin):
blender_bin = blender_app_path
icons_blend = (
os.path.join(ROOTDIR, "..", "lib", "resources", "icon_geom.blend"),
os.path.join(ROOTDIR, "release", "datafiles", "assets", "icons", "toolbar.blend"),
)

View File

@ -666,6 +666,7 @@ class NODE_MT_category_GEO_VOLUME(Menu):
layout = self.layout
if context.preferences.experimental.use_new_volume_nodes:
layout.menu("NODE_MT_geometry_node_GEO_VOLUME_READ")
layout.menu("NODE_MT_geometry_node_volume_sample")
layout.menu("NODE_MT_geometry_node_GEO_VOLUME_WRITE")
layout.separator()
layout.menu("NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS")
@ -693,6 +694,16 @@ class NODE_MT_geometry_node_GEO_VOLUME_WRITE(Menu):
node_add_menu.draw_assets_for_catalog(layout, "Volume/Write")
class NODE_MT_geometry_node_volume_sample(Menu):
bl_idname = "NODE_MT_geometry_node_volume_sample"
bl_label = "Sample"
def draw(self, context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeSampleGrid")
node_add_menu.draw_assets_for_catalog(layout, "Volume/Sample")
class NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS(Menu):
bl_idname = "NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS"
bl_label = "Operations"
@ -785,6 +796,7 @@ classes = (
NODE_MT_category_simulation,
NODE_MT_category_GEO_VOLUME,
NODE_MT_geometry_node_GEO_VOLUME_READ,
NODE_MT_geometry_node_volume_sample,
NODE_MT_geometry_node_GEO_VOLUME_WRITE,
NODE_MT_geometry_node_GEO_VOLUME_OPERATIONS,
NODE_MT_geometry_node_GEO_VOLUME_PRIMITIVES,

View File

@ -812,4 +812,5 @@ void CustomData_debug_info_from_layers(const CustomData *data, const char *inden
namespace blender::bke {
std::optional<VolumeGridType> custom_data_type_to_volume_grid_type(eCustomDataType type);
std::optional<eCustomDataType> volume_grid_type_to_custom_data_type(VolumeGridType type);
} // namespace blender::bke

View File

@ -27,6 +27,7 @@
#include "BLI_math_matrix_types.hh"
#include "BLI_shared_cache.hh"
#include "BLI_vector.hh"
#include "BLI_virtual_array_fwd.hh"
#include "DNA_customdata_types.h"
@ -99,8 +100,7 @@ class Instances {
*/
Vector<InstanceReference> references_;
/** Transformation of the instances. */
Vector<float4x4> transforms_;
int instances_num_ = 0;
CustomData attributes_;
@ -159,8 +159,8 @@ class Instances {
Span<int> reference_handles() const;
MutableSpan<int> reference_handles_for_write();
MutableSpan<float4x4> transforms();
Span<float4x4> transforms() const;
MutableSpan<float4x4> transforms_for_write();
int instances_num() const;
int references_num() const;
@ -193,6 +193,9 @@ class Instances {
}
};
VArray<float3> instance_position_varray(const Instances &instances);
VMutableArray<float3> instance_position_varray_for_write(Instances &instances);
/* -------------------------------------------------------------------- */
/** \name #InstanceReference Inline Methods
* \{ */

View File

@ -1329,6 +1329,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_STORE_NAMED_GRID 2122
#define GEO_NODE_SORT_ELEMENTS 2123
#define GEO_NODE_MENU_SWITCH 2124
#define GEO_NODE_SAMPLE_GRID 2125
/** \} */

View File

@ -716,14 +716,6 @@ static std::unique_ptr<Instances> try_load_instances(const DictionaryValue &io_g
instances->add_reference(std::move(reference_geometry));
}
const auto *io_transforms = io_instances->lookup_dict("transforms");
if (!io_transforms) {
return {};
}
if (!read_blob_simple_gspan(blob_reader, *io_transforms, instances->transforms())) {
return {};
}
MutableAttributeAccessor attributes = instances->attributes_for_write();
if (!load_attributes(*io_attributes, attributes, blob_reader, blob_sharing)) {
return {};
@ -743,6 +735,18 @@ static std::unique_ptr<Instances> try_load_instances(const DictionaryValue &io_g
}
}
if (!attributes.contains("instance_transform")) {
/* Try reading the transform attribute from the old bake format from before it was an
* attribute. */
const auto *io_handles = io_instances->lookup_dict("transforms");
if (!io_handles) {
return {};
}
if (!read_blob_simple_gspan(blob_reader, *io_handles, instances->transforms_for_write())) {
return {};
}
}
return instances;
}
@ -973,11 +977,8 @@ static std::shared_ptr<DictionaryValue> serialize_geometry_set(const GeometrySet
serialize_geometry_set(reference.geometry_set(), blob_writer, blob_sharing));
}
io_instances->append(
"transforms", write_blob_simple_gspan(blob_writer, blob_sharing, instances.transforms()));
auto io_attributes = serialize_attributes(
instances.attributes(), blob_writer, blob_sharing, {"position"});
instances.attributes(), blob_writer, blob_sharing, {});
io_instances->append("attributes", io_attributes);
}
return io_geometry;

View File

@ -4593,7 +4593,7 @@ void CustomData_validate_layer_name(const CustomData *data,
int index = -1;
/* if a layer name was given, try to find that layer */
if (name[0]) {
if (!name.is_empty()) {
index = CustomData_get_named_layer_index(data, type, name);
}
@ -5542,6 +5542,22 @@ std::optional<VolumeGridType> custom_data_type_to_volume_grid_type(const eCustom
}
}
std::optional<eCustomDataType> volume_grid_type_to_custom_data_type(const VolumeGridType type)
{
switch (type) {
case VOLUME_GRID_FLOAT:
return CD_PROP_FLOAT;
case VOLUME_GRID_VECTOR_FLOAT:
return CD_PROP_FLOAT3;
case VOLUME_GRID_INT:
return CD_PROP_INT32;
case VOLUME_GRID_BOOLEAN:
return CD_PROP_BOOL;
default:
return std::nullopt;
}
}
/** \} */
} // namespace blender::bke

View File

@ -102,65 +102,6 @@ void InstancesComponent::replace(Instances *instances, GeometryOwnershipType own
ownership_ = ownership;
}
static float3 get_transform_position(const float4x4 &transform)
{
return transform.location();
}
static void set_transform_position(float4x4 &transform, const float3 position)
{
transform.location() = position;
}
class InstancePositionAttributeProvider final : public BuiltinAttributeProvider {
public:
InstancePositionAttributeProvider()
: BuiltinAttributeProvider(
"position", AttrDomain::Instance, CD_PROP_FLOAT3, NonCreatable, NonDeletable)
{
}
GAttributeReader try_get_for_read(const void *owner) const final
{
const Instances *instances = static_cast<const Instances *>(owner);
if (instances == nullptr) {
return {};
}
Span<float4x4> transforms = instances->transforms();
return {VArray<float3>::ForDerivedSpan<float4x4, get_transform_position>(transforms),
domain_,
nullptr};
}
GAttributeWriter try_get_for_write(void *owner) const final
{
Instances *instances = static_cast<Instances *>(owner);
if (instances == nullptr) {
return {};
}
MutableSpan<float4x4> transforms = instances->transforms();
return {VMutableArray<float3>::ForDerivedSpan<float4x4,
get_transform_position,
set_transform_position>(transforms),
domain_};
}
bool try_delete(void * /*owner*/) const final
{
return false;
}
bool try_create(void * /*owner*/, const AttributeInit & /*initializer*/) const final
{
return false;
}
bool exists(const void * /*owner*/) const final
{
return true;
}
};
static void tag_component_reference_index_changed(void *owner)
{
Instances &instances = *static_cast<Instances *>(owner);
@ -169,7 +110,6 @@ static void tag_component_reference_index_changed(void *owner)
static ComponentAttributeProviders create_attribute_providers_for_instances()
{
static InstancePositionAttributeProvider position;
static CustomDataAccessInfo instance_custom_data_access = {
[](void *owner) -> CustomData * {
Instances *instances = static_cast<Instances *>(owner);
@ -199,6 +139,15 @@ static ComponentAttributeProviders create_attribute_providers_for_instances()
instance_custom_data_access,
nullptr);
static BuiltinCustomDataLayerProvider instance_transform("instance_transform",
AttrDomain::Instance,
CD_PROP_FLOAT4X4,
CD_PROP_FLOAT4X4,
BuiltinAttributeProvider::Creatable,
BuiltinAttributeProvider::NonDeletable,
instance_custom_data_access,
nullptr);
/** Indices into `Instances::references_`. Determines what data is instanced. */
static BuiltinCustomDataLayerProvider reference_index(".reference_index",
AttrDomain::Instance,
@ -212,7 +161,8 @@ static ComponentAttributeProviders create_attribute_providers_for_instances()
static CustomDataAttributeProvider instance_custom_data(AttrDomain::Instance,
instance_custom_data_access);
return ComponentAttributeProviders({&position, &id, &reference_index}, {&instance_custom_data});
return ComponentAttributeProviders({&instance_transform, &id, &reference_index},
{&instance_custom_data});
}
static AttributeAccessorFunctions get_instances_accessor_functions()

View File

@ -396,9 +396,14 @@ GVArray AttributeFieldInput::get_varray_for_context(const GeometryFieldContext &
}
}
}
else if (context.domain() == bke::AttrDomain::Instance && name_ == "position") {
/* Special case for "position" which is no longer an attribute on instances. */
return bke::instance_position_varray(*context.instances());
}
else if (auto attributes = context.attributes()) {
return *attributes->lookup(name_, domain, data_type);
}
return {};
}

View File

@ -127,13 +127,21 @@ void Instances::ensure_geometry_instances()
* collection as instances. */
std::unique_ptr<Instances> instances = std::make_unique<Instances>();
Collection &collection = reference.collection();
Vector<Object *, 8> objects;
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (&collection, object) {
const int handle = instances->add_reference(*object);
instances->add_instance(handle, object->object_to_world());
float4x4 &transform = instances->transforms().last();
transform.location() -= collection.instance_offset;
objects.append(object);
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
instances->resize(objects.size());
MutableSpan<int> handles = instances->reference_handles_for_write();
MutableSpan<float4x4> transforms = instances->transforms_for_write();
for (const int i : objects.index_range()) {
handles[i] = instances->add_reference(*objects[i]);
transforms[i] = objects[i]->object_to_world();
transforms[i].location() -= collection.instance_offset;
}
instances->ensure_geometry_instances();
new_references.append(GeometrySet::from_instances(instances.release()));
break;

View File

@ -50,7 +50,7 @@ Instances::Instances()
Instances::Instances(Instances &&other)
: references_(std::move(other.references_)),
transforms_(std::move(other.transforms_)),
instances_num_(other.instances_num_),
attributes_(other.attributes_),
almost_unique_ids_cache_(std::move(other.almost_unique_ids_cache_))
{
@ -59,15 +59,15 @@ Instances::Instances(Instances &&other)
Instances::Instances(const Instances &other)
: references_(other.references_),
transforms_(other.transforms_),
instances_num_(other.instances_num_),
almost_unique_ids_cache_(other.almost_unique_ids_cache_)
{
CustomData_copy(&other.attributes_, &attributes_, CD_MASK_ALL, other.instances_num());
CustomData_copy(&other.attributes_, &attributes_, CD_MASK_ALL, other.instances_num_);
}
Instances::~Instances()
{
CustomData_free(&attributes_, this->instances_num());
CustomData_free(&attributes_, instances_num_);
}
Instances &Instances::operator=(const Instances &other)
@ -92,46 +92,55 @@ Instances &Instances::operator=(Instances &&other)
void Instances::resize(int capacity)
{
const int old_size = this->instances_num();
transforms_.resize(capacity);
CustomData_realloc(&attributes_, old_size, capacity, CD_SET_DEFAULT);
CustomData_realloc(&attributes_, instances_num_, capacity, CD_SET_DEFAULT);
instances_num_ = capacity;
}
void Instances::add_instance(const int instance_handle, const float4x4 &transform)
{
BLI_assert(instance_handle >= 0);
BLI_assert(instance_handle < references_.size());
const int old_size = this->instances_num();
transforms_.append(transform);
CustomData_realloc(&attributes_, old_size, transforms_.size());
const int old_size = instances_num_;
instances_num_++;
CustomData_realloc(&attributes_, old_size, instances_num_);
this->reference_handles_for_write().last() = instance_handle;
this->transforms_for_write().last() = transform;
}
Span<int> Instances::reference_handles() const
{
return {static_cast<const int *>(
CustomData_get_layer_named(&attributes_, CD_PROP_INT32, ".reference_index")),
this->instances_num()};
instances_num_};
}
MutableSpan<int> Instances::reference_handles_for_write()
{
int *data = static_cast<int *>(CustomData_get_layer_named_for_write(
&attributes_, CD_PROP_INT32, ".reference_index", this->instances_num()));
&attributes_, CD_PROP_INT32, ".reference_index", instances_num_));
if (!data) {
data = static_cast<int *>(CustomData_add_layer_named(
&attributes_, CD_PROP_INT32, CD_SET_DEFAULT, this->instances_num(), ".reference_index"));
&attributes_, CD_PROP_INT32, CD_SET_DEFAULT, instances_num_, ".reference_index"));
}
return {data, this->instances_num()};
return {data, instances_num_};
}
MutableSpan<float4x4> Instances::transforms()
{
return transforms_;
}
Span<float4x4> Instances::transforms() const
{
return transforms_;
return {static_cast<const float4x4 *>(
CustomData_get_layer_named(&attributes_, CD_PROP_FLOAT4X4, "instance_transform")),
instances_num_};
}
MutableSpan<float4x4> Instances::transforms_for_write()
{
float4x4 *data = static_cast<float4x4 *>(CustomData_get_layer_named_for_write(
&attributes_, CD_PROP_FLOAT4X4, "instance_transform", instances_num_));
if (!data) {
data = static_cast<float4x4 *>(CustomData_add_layer_named(
&attributes_, CD_PROP_FLOAT4X4, CD_SET_DEFAULT, instances_num_, "instance_transform"));
}
return {data, instances_num_};
}
GeometrySet &Instances::geometry_set_from_reference(const int reference_index)
@ -178,17 +187,14 @@ void Instances::remove(const IndexMask &mask,
return;
}
const int new_size = mask.size();
Instances new_instances;
new_instances.references_ = std::move(references_);
new_instances.transforms_.resize(new_size);
array_utils::gather(transforms_.as_span(), mask, new_instances.transforms_.as_mutable_span());
new_instances.instances_num_ = mask.size();
gather_attributes(this->attributes(),
AttrDomain::Instance,
propagation_info,
{"position"},
{},
mask,
new_instances.attributes_for_write());
@ -199,7 +205,7 @@ void Instances::remove(const IndexMask &mask,
void Instances::remove_unused_references()
{
const int tot_instances = this->instances_num();
const int tot_instances = instances_num_;
const int tot_references_before = references_.size();
if (tot_instances == 0) {
@ -281,7 +287,7 @@ void Instances::remove_unused_references()
int Instances::instances_num() const
{
return transforms_.size();
return this->instances_num_;
}
int Instances::references_num() const
@ -372,11 +378,33 @@ Span<int> Instances::almost_unique_ids() const
}
}
else {
r_data.reinitialize(this->instances_num());
r_data.reinitialize(instances_num_);
array_utils::fill_index_range(r_data.as_mutable_span());
}
});
return almost_unique_ids_cache_.data();
}
static float3 get_transform_position(const float4x4 &transform)
{
return transform.location();
}
static void set_transform_position(float4x4 &transform, const float3 position)
{
transform.location() = position;
}
VArray<float3> instance_position_varray(const Instances &instances)
{
return VArray<float3>::ForDerivedSpan<float4x4, get_transform_position>(instances.transforms());
}
VMutableArray<float3> instance_position_varray_for_write(Instances &instances)
{
MutableSpan<float4x4> transforms = instances.transforms_for_write();
return VMutableArray<float3>::
ForDerivedSpan<float4x4, get_transform_position, set_transform_position>(transforms);
}
} // namespace blender::bke

View File

@ -563,11 +563,11 @@ CCGKey BKE_subdiv_ccg_key_top_level(const SubdivCCG &subdiv_ccg)
static void subdiv_ccg_recalc_inner_face_normals(SubdivCCG &subdiv_ccg,
const CCGKey &key,
MutableSpan<float3> face_normals,
const int grid_index)
const int corner)
{
const int grid_size = subdiv_ccg.grid_size;
const int grid_size_1 = grid_size - 1;
CCGElem *grid = subdiv_ccg.grids[grid_index];
CCGElem *grid = subdiv_ccg.grids[corner];
for (int y = 0; y < grid_size - 1; y++) {
for (int x = 0; x < grid_size - 1; x++) {
CCGElem *grid_elements[4] = {
@ -593,11 +593,11 @@ static void subdiv_ccg_recalc_inner_face_normals(SubdivCCG &subdiv_ccg,
static void subdiv_ccg_average_inner_face_normals(SubdivCCG &subdiv_ccg,
const CCGKey &key,
const Span<float3> face_normals,
const int grid_index)
const int corner)
{
const int grid_size = subdiv_ccg.grid_size;
const int grid_size_1 = grid_size - 1;
CCGElem *grid = subdiv_ccg.grids[grid_index];
CCGElem *grid = subdiv_ccg.grids[corner];
for (int y = 0; y < grid_size; y++) {
for (int x = 0; x < grid_size; x++) {
float normal_acc[3] = {0.0f, 0.0f, 0.0f};
@ -642,7 +642,7 @@ static void subdiv_ccg_recalc_inner_grid_normals(SubdivCCG &subdiv_ccg, const In
MutableSpan<float3> face_normals = face_normals_tls.local();
for (const int face_index : segment) {
const IndexRange face = faces[face_index];
for (const int grid_index : IndexRange(face.start(), face.size())) {
for (const int grid_index : face) {
subdiv_ccg_recalc_inner_face_normals(subdiv_ccg, key, face_normals, grid_index);
subdiv_ccg_average_inner_face_normals(subdiv_ccg, key, face_normals, grid_index);
}

View File

@ -394,75 +394,6 @@ void SCULPT_OT_sample_detail_size(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Dynamic-topology detail size
*
* Currently, there are two operators editing the detail size:
* - #SCULPT_OT_set_detail_size uses radial control for all methods
* - #SCULPT_OT_dyntopo_detail_size_edit shows a triangle grid representation of the detail
* resolution (for constant detail method,
* falls back to radial control for the remaining methods).
* \{ */
static void set_brush_rc_props(PointerRNA *ptr, const char *prop)
{
char *path = BLI_sprintfN("tool_settings.sculpt.brush.%s", prop);
RNA_string_set(ptr, "data_path_primary", path);
MEM_freeN(path);
}
static void sculpt_detail_size_set_radial_control(bContext *C)
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
PointerRNA props_ptr;
wmOperatorType *ot = WM_operatortype_find("WM_OT_radial_control", true);
WM_operator_properties_create_ptr(&props_ptr, ot);
if (sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL)) {
set_brush_rc_props(&props_ptr, "constant_detail_resolution");
RNA_string_set(
&props_ptr, "data_path_primary", "tool_settings.sculpt.constant_detail_resolution");
}
else if (sd->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) {
set_brush_rc_props(&props_ptr, "constant_detail_resolution");
RNA_string_set(&props_ptr, "data_path_primary", "tool_settings.sculpt.detail_percent");
}
else {
set_brush_rc_props(&props_ptr, "detail_size");
RNA_string_set(&props_ptr, "data_path_primary", "tool_settings.sculpt.detail_size");
}
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, nullptr);
WM_operator_properties_free(&props_ptr);
}
static int sculpt_set_detail_size_exec(bContext *C, wmOperator * /*op*/)
{
sculpt_detail_size_set_radial_control(C);
return OPERATOR_FINISHED;
}
void SCULPT_OT_set_detail_size(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Set Detail Size";
ot->idname = "SCULPT_OT_set_detail_size";
ot->description =
"Set the mesh detail (either relative or constant one, depending on current dyntopo mode)";
/* API callbacks. */
ot->exec = sculpt_set_detail_size_exec;
ot->poll = sculpt_and_dynamic_topology_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Dyntopo Detail Size Edit Operator
* \{ */

View File

@ -1725,7 +1725,6 @@ namespace blender::ed::sculpt_paint::dyntopo {
void SCULPT_OT_detail_flood_fill(wmOperatorType *ot);
void SCULPT_OT_sample_detail_size(wmOperatorType *ot);
void SCULPT_OT_set_detail_size(wmOperatorType *ot);
void SCULPT_OT_dyntopo_detail_size_edit(wmOperatorType *ot);
void SCULPT_OT_dynamic_topology_toggle(wmOperatorType *ot);

View File

@ -1294,7 +1294,6 @@ void ED_operatortypes_sculpt()
WM_operatortype_append(SCULPT_OT_symmetrize);
WM_operatortype_append(dyntopo::SCULPT_OT_detail_flood_fill);
WM_operatortype_append(dyntopo::SCULPT_OT_sample_detail_size);
WM_operatortype_append(dyntopo::SCULPT_OT_set_detail_size);
WM_operatortype_append(filter::SCULPT_OT_mesh_filter);
WM_operatortype_append(mask::SCULPT_OT_mask_filter);
WM_operatortype_append(SCULPT_OT_set_pivot_position);

View File

@ -204,6 +204,13 @@ void GeometryDataSource::foreach_default_column_ids(
if (!bke::allow_procedural_attribute_access(attribute_id.name())) {
return true;
}
if (meta_data.domain == bke::AttrDomain::Instance &&
attribute_id.name() == "instance_transform")
{
/* Don't display the instance transform attribute, since matrix visualization in the
* spreadsheet isn't helpful. */
return true;
}
SpreadsheetColumnID column_id;
column_id.name = (char *)attribute_id.name().data();
const bool is_front = attribute_id.name() == ".viewer";
@ -212,6 +219,7 @@ void GeometryDataSource::foreach_default_column_ids(
});
if (component_->type() == bke::GeometryComponent::Type::Instance) {
fn({(char *)"Position"}, false);
fn({(char *)"Rotation"}, false);
fn({(char *)"Scale"}, false);
}
@ -257,6 +265,12 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values(
}));
}
Span<float4x4> transforms = instances->transforms();
if (STREQ(column_id.name, "Position")) {
return std::make_unique<ColumnValues>(
column_id.name, VArray<float3>::ForFunc(domain_num, [transforms](int64_t index) {
return transforms[index].location();
}));
}
if (STREQ(column_id.name, "Rotation")) {
return std::make_unique<ColumnValues>(
column_id.name, VArray<float3>::ForFunc(domain_num, [transforms](int64_t index) {

View File

@ -109,7 +109,6 @@ static void join_instances(const Span<const GeometryComponent *> src_components,
std::unique_ptr<bke::Instances> dst_instances = std::make_unique<bke::Instances>();
dst_instances->resize(offsets.total_size());
MutableSpan<float4x4> all_transforms = dst_instances->transforms();
MutableSpan<int> all_handles = dst_instances->reference_handles_for_write();
for (const int i : src_components.index_range()) {
@ -126,12 +125,11 @@ static void join_instances(const Span<const GeometryComponent *> src_components,
const Span<int> src_handles = src_instances.reference_handles();
array_utils::gather(handle_map.as_span(), src_handles, all_handles.slice(dst_range));
array_utils::copy(src_instances.transforms(), all_transforms.slice(dst_range));
}
result.replace_instances(dst_instances.release());
auto &dst_component = result.get_component_for_write<bke::InstancesComponent>();
join_attributes(src_components, dst_component, {"position", ".reference_index"});
join_attributes(src_components, dst_component, {".reference_index"});
}
static void join_volumes(const Span<const GeometryComponent *> /*src_components*/,
@ -174,11 +172,13 @@ static void join_component_type(const bke::GeometryComponent::Type component_typ
}
std::unique_ptr<bke::Instances> instances = std::make_unique<bke::Instances>();
for (const GeometryComponent *component : components) {
instances->resize(components.size());
instances->transforms_for_write().fill(float4x4::identity());
MutableSpan<int> handles = instances->reference_handles_for_write();
for (const int i : components.index_range()) {
GeometrySet tmp_geo;
tmp_geo.add(*component);
const int handle = instances->add_reference(bke::InstanceReference{tmp_geo});
instances->add_instance(handle, float4x4::identity());
tmp_geo.add(*components[i]);
handles[i] = instances->add_reference(bke::InstanceReference{tmp_geo});
}
RealizeInstancesOptions options;

View File

@ -69,29 +69,6 @@ static void mix(GMutableSpan a, const GVArray &b, const float factor)
});
}
static void mix(MutableSpan<float4x4> a, const Span<float4x4> b, const float factor)
{
threading::parallel_for(a.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
a[i] = math::interpolate(a[i], b[i], factor);
}
});
}
static void mix_with_indices(MutableSpan<float4x4> a,
const Span<float4x4> b,
const Span<int> index_map,
const float factor)
{
threading::parallel_for(a.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
if (index_map[i] != -1) {
a[i] = math::interpolate(a[i], b[index_map[i]], factor);
}
}
});
}
static void mix_attributes(bke::MutableAttributeAccessor attributes_a,
const bke::AttributeAccessor b_attributes,
const Span<int> index_map,
@ -216,13 +193,7 @@ bke::GeometrySet mix_geometries(bke::GeometrySet a, const bke::GeometrySet &b, c
index_map,
bke::AttrDomain::Instance,
factor,
{"position"});
if (index_map.is_empty()) {
mix(instances_a->transforms(), instances_b->transforms(), factor);
}
else {
mix_with_indices(instances_a->transforms(), instances_b->transforms(), index_map, factor);
}
{});
}
}
return a;

View File

@ -233,27 +233,10 @@ void debug_randomize_instance_order(bke::Instances *instances)
if (instances == nullptr || !use_debug_randomization()) {
return;
}
const int instances_num = instances->instances_num();
const int seed = seed_from_instances(*instances);
const Array<int> new_by_old_map = get_permutation(instances_num, seed);
reorder_customdata(instances->custom_data_attributes(), new_by_old_map);
const Span<int> old_reference_handles = instances->reference_handles();
const Span<float4x4> old_transforms = instances->transforms();
Vector<int> new_reference_handles(instances_num);
Vector<float4x4> new_transforms(instances_num);
for (const int old_i : new_by_old_map.index_range()) {
const int new_i = new_by_old_map[old_i];
new_reference_handles[new_i] = old_reference_handles[old_i];
new_transforms[new_i] = old_transforms[old_i];
}
instances->reference_handles_for_write().copy_from(new_reference_handles);
instances->transforms().copy_from(new_transforms);
}
bool use_debug_randomization()

View File

@ -209,10 +209,6 @@ static void reorder_instaces_exec(const bke::Instances &src_instances,
{},
old_by_new_map,
dst_instances.attributes_for_write());
const Span<float4x4> old_transforms = src_instances.transforms();
MutableSpan<float4x4> new_transforms = dst_instances.transforms();
array_utils::gather(old_transforms, old_by_new_map, new_transforms);
}
static void clean_unused_attributes(const bke::AnonymousAttributePropagationInfo &propagation_info,

View File

@ -111,7 +111,7 @@ static void transform_greasepencil(GreasePencil &grease_pencil, const float4x4 &
static void translate_instances(bke::Instances &instances, const float3 translation)
{
MutableSpan<float4x4> transforms = instances.transforms();
MutableSpan<float4x4> transforms = instances.transforms_for_write();
threading::parallel_for(transforms.index_range(), 1024, [&](const IndexRange range) {
for (float4x4 &instance_transform : transforms.slice(range)) {
add_v3_v3(instance_transform.ptr()[3], translation);
@ -121,7 +121,7 @@ static void translate_instances(bke::Instances &instances, const float3 translat
static void transform_instances(bke::Instances &instances, const float4x4 &transform)
{
MutableSpan<float4x4> transforms = instances.transforms();
MutableSpan<float4x4> transforms = instances.transforms_for_write();
threading::parallel_for(transforms.index_range(), 1024, [&](const IndexRange range) {
for (float4x4 &instance_transform : transforms.slice(range)) {
instance_transform = transform * instance_transform;

View File

@ -4120,9 +4120,9 @@ static const EnumPropertyItem node_principled_hair_model_items[] = {
"HUANG",
0,
"Huang",
"Far-field hair scattering model by Huang et al. 2022, suitable for viewing from a distance, "
"supports elliptical cross-sections and has more precise highlight in forward scattering "
"directions"},
"Multi-scale hair scattering model by Huang et al. 2022, suitable for viewing both up close "
"and from a distance, supports elliptical cross-sections and has more precise highlight in "
"forward scattering directions"},
{0, nullptr, 0, nullptr, nullptr},
};

View File

@ -421,6 +421,7 @@ DefNode(GeometryNode, GEO_NODE_REPLACE_MATERIAL, 0, "REPLACE_MATERIAL", ReplaceM
DefNode(GeometryNode, GEO_NODE_RESAMPLE_CURVE, 0, "RESAMPLE_CURVE", ResampleCurve, "Resample Curve", "Generate a poly spline for each input spline")
DefNode(GeometryNode, GEO_NODE_REVERSE_CURVE, 0, "REVERSE_CURVE", ReverseCurve, "Reverse Curve", "Change the direction of curves by swapping their start and end data")
DefNode(GeometryNode, GEO_NODE_ROTATE_INSTANCES, 0, "ROTATE_INSTANCES", RotateInstances, "Rotate Instances", "Rotate geometry instances in local or global space")
DefNode(GeometryNode, GEO_NODE_SAMPLE_GRID, 0, "SAMPLE_GRID", SampleGrid, "Sample Grid", "")
DefNode(GeometryNode, GEO_NODE_SAMPLE_CURVE, def_geo_curve_sample, "SAMPLE_CURVE", SampleCurve, "Sample Curve", "Retrieve data from a point on a curve at a certain distance from its start")
DefNode(GeometryNode, GEO_NODE_SAMPLE_INDEX, def_geo_sample_index, "SAMPLE_INDEX", SampleIndex, "Sample Index", "Retrieve values from specific geometry elements")
DefNode(GeometryNode, GEO_NODE_SAMPLE_NEAREST_SURFACE, 0, "SAMPLE_NEAREST_SURFACE", SampleNearestSurface, "Sample Nearest Surface", "Calculate the interpolated value of a mesh attribute on the closest point of its surface")

View File

@ -155,6 +155,7 @@ set(SRC
nodes/node_geo_remove_attribute.cc
nodes/node_geo_repeat.cc
nodes/node_geo_rotate_instances.cc
nodes/node_geo_sample_grid.cc
nodes/node_geo_sample_index.cc
nodes/node_geo_sample_nearest.cc
nodes/node_geo_sample_nearest_surface.cc

View File

@ -954,8 +954,6 @@ static void duplicate_instances(GeometrySet &geometry_set,
const int old_handle = src_instances.reference_handles()[i_selection];
const bke::InstanceReference reference = src_instances.references()[old_handle];
const int new_handle = dst_instances->add_reference(reference);
const float4x4 transform = src_instances.transforms()[i_selection];
dst_instances->transforms().slice(range).fill(transform);
dst_instances->reference_handles_for_write().slice(range).fill(new_handle);
}

View File

@ -81,7 +81,8 @@ static void add_instances_from_component(
MutableSpan<int> dst_handles = dst_component.reference_handles_for_write().slice(start_len,
select_len);
MutableSpan<float4x4> dst_transforms = dst_component.transforms().slice(start_len, select_len);
MutableSpan<float4x4> dst_transforms = dst_component.transforms_for_write().slice(start_len,
select_len);
const VArraySpan positions = *src_attributes.lookup<float3>("position");

View File

@ -39,7 +39,7 @@ static void rotate_instances(GeoNodeExecParams &params, bke::Instances &instance
const VArray<float3> pivots = evaluator.get_evaluated<float3>(1);
const VArray<bool> local_spaces = evaluator.get_evaluated<bool>(2);
MutableSpan<float4x4> transforms = instances.transforms();
MutableSpan<float4x4> transforms = instances.transforms_for_write();
selection.foreach_index(GrainSize(512), [&](const int64_t i) {
const float3 pivot = pivots[i];

View File

@ -0,0 +1,303 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_customdata.hh"
#include "BKE_type_conversions.hh"
#include "BKE_volume_grid.hh"
#include "BKE_volume_openvdb.hh"
#include "BLI_index_mask.hh"
#include "BLI_virtual_array.hh"
#include "NOD_rna_define.hh"
#include "NOD_socket_search_link.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "RNA_enum_types.hh"
#ifdef WITH_OPENVDB
# include <openvdb/tools/Interpolation.h>
#endif
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_sample_grid_cc {
enum class InterpolationMode {
Nearest = 0,
TriLinear = 1,
TriQuadratic = 2,
};
static void node_declare(NodeDeclarationBuilder &b)
{
const bNode *node = b.node_or_null();
if (!node) {
return;
}
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node->custom1);
b.add_input(data_type, "Grid").hide_value();
b.add_input<decl::Vector>("Position").implicit_field(implicit_field_inputs::position);
b.add_output(data_type, "Value").dependent_field({1});
}
static std::optional<eNodeSocketDatatype> node_type_for_socket_type(const bNodeSocket &socket)
{
switch (socket.type) {
case SOCK_FLOAT:
return SOCK_FLOAT;
case SOCK_BOOLEAN:
return SOCK_BOOLEAN;
case SOCK_INT:
return SOCK_INT;
case SOCK_VECTOR:
case SOCK_RGBA:
return SOCK_VECTOR;
default:
return std::nullopt;
}
}
static void node_gather_link_search_ops(GatherLinkSearchOpParams &params)
{
if (!U.experimental.use_new_volume_nodes) {
return;
}
const std::optional<eNodeSocketDatatype> node_type = node_type_for_socket_type(
params.other_socket());
if (!node_type) {
return;
}
if (params.in_out() == SOCK_IN) {
params.add_item(IFACE_("Grid"), [node_type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeSampleGrid");
node.custom1 = *node_type;
params.update_and_connect_available_socket(node, "Grid");
});
const eNodeSocketDatatype other_type = eNodeSocketDatatype(params.other_socket().type);
if (params.node_tree().typeinfo->validate_link(other_type, SOCK_VECTOR)) {
params.add_item(IFACE_("Position"), [node_type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeSampleGrid");
params.update_and_connect_available_socket(node, "Position");
});
}
}
else {
params.add_item(IFACE_("Value"), [node_type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeSampleGrid");
node.custom1 = *node_type;
params.update_and_connect_available_socket(node, "Value");
});
}
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
uiItemR(layout, ptr, "interpolation_mode", UI_ITEM_NONE, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = SOCK_FLOAT;
node->custom2 = int16_t(InterpolationMode::TriLinear);
}
#ifdef WITH_OPENVDB
template<typename T>
void sample_grid(const bke::OpenvdbGridType<T> &grid,
const InterpolationMode interpolation,
const Span<float3> positions,
const IndexMask &mask,
MutableSpan<T> dst)
{
using GridType = bke::OpenvdbGridType<T>;
using GridValueT = typename GridType::ValueType;
using AccessorT = typename GridType::ConstAccessor;
using TraitsT = typename bke::VolumeGridTraits<T>;
AccessorT accessor = grid.getConstAccessor();
auto sample_data = [&](auto sampler) {
mask.foreach_index([&](const int64_t i) {
const float3 &pos = positions[i];
GridValueT value = sampler.wsSample(openvdb::Vec3R(pos.x, pos.y, pos.z));
dst[i] = TraitsT::to_blender(value);
});
};
/* Use to the Nearest Neighbor sampler for Bool grids (no interpolation). */
InterpolationMode real_interpolation = interpolation;
if constexpr (std::is_same_v<T, bool>) {
real_interpolation = InterpolationMode::Nearest;
}
switch (real_interpolation) {
case InterpolationMode::TriLinear: {
openvdb::tools::GridSampler<AccessorT, openvdb::tools::BoxSampler> sampler(accessor,
grid.transform());
sample_data(sampler);
break;
}
case InterpolationMode::TriQuadratic: {
openvdb::tools::GridSampler<AccessorT, openvdb::tools::QuadraticSampler> sampler(
accessor, grid.transform());
sample_data(sampler);
break;
}
case InterpolationMode::Nearest: {
openvdb::tools::GridSampler<AccessorT, openvdb::tools::PointSampler> sampler(
accessor, grid.transform());
sample_data(sampler);
break;
}
}
}
template<typename Fn> void convert_to_static_type(const VolumeGridType type, const Fn &fn)
{
switch (type) {
case VOLUME_GRID_BOOLEAN:
fn(bool());
break;
case VOLUME_GRID_FLOAT:
fn(float());
break;
case VOLUME_GRID_INT:
fn(int());
break;
case VOLUME_GRID_MASK:
fn(bool());
break;
case VOLUME_GRID_VECTOR_FLOAT:
fn(float3());
break;
default:
break;
}
}
class SampleGridFunction : public mf::MultiFunction {
bke::GVolumeGrid grid_;
InterpolationMode interpolation_;
mf::Signature signature_;
public:
SampleGridFunction(bke::GVolumeGrid grid, InterpolationMode interpolation)
: grid_(std::move(grid)), interpolation_(interpolation)
{
BLI_assert(grid_);
const std::optional<eNodeSocketDatatype> data_type = bke::grid_type_to_socket_type(
grid_->grid_type());
const CPPType *cpp_type = bke::socket_type_to_geo_nodes_base_cpp_type(*data_type);
mf::SignatureBuilder builder{"Sample Volume", signature_};
builder.single_input<float3>("Position");
builder.single_output("Value", *cpp_type);
this->set_signature(&signature_);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArraySpan<float3> positions = params.readonly_single_input<float3>(0, "Position");
GMutableSpan dst = params.uninitialized_single_output(1, "Value");
bke::VolumeTreeAccessToken tree_token;
convert_to_static_type(grid_->grid_type(), [&](auto dummy) {
using T = decltype(dummy);
sample_grid<T>(
grid_.typed<T>().grid(tree_token), interpolation_, positions, mask, dst.typed<T>());
});
}
};
#endif /* WITH_OPENVDB */
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
const bNode &node = params.node();
const eNodeSocketDatatype data_type = eNodeSocketDatatype(node.custom1);
const InterpolationMode interpolation = InterpolationMode(node.custom2);
Field<float3> position = params.extract_input<Field<float3>>("Position");
bke::GVolumeGrid grid = params.extract_input<bke::GVolumeGrid>("Grid");
if (!grid) {
params.set_default_remaining_outputs();
return;
}
auto fn = std::make_shared<SampleGridFunction>(std::move(grid), interpolation);
auto op = FieldOperation::Create(std::move(fn), {std::move(position)});
const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions();
const CPPType &output_type = *bke::socket_type_to_geo_nodes_base_cpp_type(data_type);
const GField output_field = conversions.try_convert(fn::GField(std::move(op)), output_type);
params.set_output("Value", std::move(output_field));
#else
node_geo_exec_with_missing_openvdb(params);
#endif
}
static const EnumPropertyItem *data_type_filter_fn(bContext * /*C*/,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free)
{
*r_free = true;
return enum_items_filter(
rna_enum_node_socket_type_items, [](const EnumPropertyItem &item) -> bool {
return ELEM(item.value, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR);
});
}
static void node_rna(StructRNA *srna)
{
RNA_def_node_enum(srna,
"data_type",
"Data Type",
"Node socket data type",
rna_enum_node_socket_type_items,
NOD_inline_enum_accessors(custom1),
CD_PROP_FLOAT,
data_type_filter_fn);
static const EnumPropertyItem interpolation_mode_items[] = {
{int(InterpolationMode::Nearest), "NEAREST", 0, "Nearest Neighbor", ""},
{int(InterpolationMode::TriLinear), "TRILINEAR", 0, "Trilinear", ""},
{int(InterpolationMode::TriQuadratic), "TRIQUADRATIC", 0, "Triquadratic", ""},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"interpolation_mode",
"Interpolation Mode",
"How to interpolate the values between neighboring voxels",
interpolation_mode_items,
NOD_inline_enum_accessors(custom2),
int(InterpolationMode::TriLinear));
}
static void node_register()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_SAMPLE_GRID, "Sample Grid", NODE_CLASS_CONVERTER);
ntype.initfunc = node_init;
ntype.declare = node_declare;
ntype.gather_link_search_ops = node_gather_link_search_ops;
ntype.geometry_node_execute = node_geo_exec;
ntype.draw_buttons = node_layout;
ntype.geometry_node_execute = node_geo_exec;
nodeRegisterType(&ntype);
node_rna(ntype.rna_ext.srna);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_sample_grid_cc

View File

@ -38,7 +38,7 @@ static void scale_instances(GeoNodeExecParams &params, bke::Instances &instances
const VArray<float3> pivots = evaluator.get_evaluated<float3>(1);
const VArray<bool> local_spaces = evaluator.get_evaluated<bool>(2);
MutableSpan<float4x4> transforms = instances.transforms();
MutableSpan<float4x4> transforms = instances.transforms_for_write();
selection.foreach_index(GrainSize(512), [&](const int64_t i) {
const float3 pivot = pivots[i];

View File

@ -6,6 +6,7 @@
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "node_geometry_util.hh"
@ -20,20 +21,25 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Geometry>("Geometry").propagate_all();
}
static void set_computed_position_and_offset(GeometryComponent &component,
const VArray<float3> &in_positions,
const VArray<float3> &in_offsets,
const IndexMask &selection,
MutableAttributeAccessor attributes)
{
/* Optimize the case when `in_positions` references the original positions array. */
const bke::AttributeReader positions_read_only = attributes.lookup<float3>("position");
bool positions_are_original = false;
if (positions_read_only.varray.is_span() && in_positions.is_span()) {
positions_are_original = positions_read_only.varray.get_internal_span().data() ==
in_positions.get_internal_span().data();
}
constexpr GrainSize grain_size{10000};
static bool check_positions_are_original(const AttributeAccessor &attributes,
const VArray<float3> &in_positions)
{
const bke::AttributeReader positions_read_only = attributes.lookup<float3>("position");
if (positions_read_only.varray.is_span() && in_positions.is_span()) {
return positions_read_only.varray.get_internal_span().data() ==
in_positions.get_internal_span().data();
}
return false;
}
static void write_offset_positions(const bool positions_are_original,
const IndexMask &selection,
const VArray<float3> &in_positions,
const VArray<float3> &in_offsets,
VMutableArray<float3> &out_positions)
{
if (positions_are_original) {
if (const std::optional<float3> offset = in_offsets.get_if_single()) {
if (math::is_zero(*offset)) {
@ -41,8 +47,32 @@ static void set_computed_position_and_offset(GeometryComponent &component,
}
}
}
const GrainSize grain_size{10000};
MutableVArraySpan<float3> out_positions_span = out_positions;
if (positions_are_original) {
devirtualize_varray(in_offsets, [&](const auto in_offsets) {
selection.foreach_index_optimized<int>(
grain_size, [&](const int i) { out_positions_span[i] += in_offsets[i]; });
});
}
else {
devirtualize_varray2(
in_positions, in_offsets, [&](const auto in_positions, const auto in_offsets) {
selection.foreach_index_optimized<int>(grain_size, [&](const int i) {
out_positions_span[i] = in_positions[i] + in_offsets[i];
});
});
}
out_positions_span.save();
}
static void set_computed_position_and_offset(GeometryComponent &component,
const VArray<float3> &in_positions,
const VArray<float3> &in_offsets,
const IndexMask &selection,
MutableAttributeAccessor attributes)
{
/* Optimize the case when `in_positions` references the original positions array. */
switch (component.type()) {
case GeometryComponent::Type::Curve: {
if (attributes.contains("handle_right") && attributes.contains("handle_left")) {
@ -76,26 +106,30 @@ static void set_computed_position_and_offset(GeometryComponent &component,
curves.calculate_bezier_auto_handles();
break;
}
ATTR_FALLTHROUGH;
AttributeWriter<float3> positions = attributes.lookup_for_write<float3>("position");
write_offset_positions(check_positions_are_original(attributes, in_positions),
selection,
in_positions,
in_offsets,
positions.varray);
positions.finish();
break;
}
case GeometryComponent::Type::Instance: {
/* Special case for "position" which is no longer an attribute on instances. */
auto &instances_component = reinterpret_cast<bke::InstancesComponent &>(component);
bke::Instances &instances = *instances_component.get_for_write();
VMutableArray<float3> positions = bke::instance_position_varray_for_write(instances);
write_offset_positions(false, selection, in_positions, in_offsets, positions);
break;
}
default: {
AttributeWriter<float3> positions = attributes.lookup_for_write<float3>("position");
MutableVArraySpan<float3> out_positions_span = positions.varray;
if (positions_are_original) {
devirtualize_varray(in_offsets, [&](const auto in_offsets) {
selection.foreach_index_optimized<int>(
grain_size, [&](const int i) { out_positions_span[i] += in_offsets[i]; });
});
}
else {
devirtualize_varray2(
in_positions, in_offsets, [&](const auto in_positions, const auto in_offsets) {
selection.foreach_index_optimized<int>(grain_size, [&](const int i) {
out_positions_span[i] = in_positions[i] + in_offsets[i];
});
});
}
out_positions_span.save();
write_offset_positions(check_positions_are_original(attributes, in_positions),
selection,
in_positions,
in_offsets,
positions.varray);
positions.finish();
break;
}

View File

@ -246,7 +246,6 @@ static void split_instance_groups(const InstancesComponent &component,
group_instances->add_reference(reference);
}
array_utils::gather(src_instances.transforms(), mask, group_instances->transforms());
bke::gather_attributes(src_instances.attributes(),
AttrDomain::Instance,
propagation_info,
@ -323,7 +322,7 @@ static void node_geo_exec(GeoNodeExecParams params)
dst_group_id.finish();
}
dst_instances->transforms().fill(float4x4::identity());
dst_instances->transforms_for_write().fill(float4x4::identity());
array_utils::fill_index_range(dst_instances->reference_handles_for_write());
for (auto item : geometry_by_group_id.items()) {

View File

@ -10,6 +10,7 @@
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "BKE_instances.hh"
#include "BKE_mesh.hh"
#include "BKE_type_conversions.hh"
@ -119,9 +120,21 @@ static void node_geo_exec(GeoNodeExecParams params)
if (geometry_set.has_instances()) {
GeometryComponent &component = geometry_set.get_component_for_write(
GeometryComponent::Type::Instance);
if (!bke::try_capture_field_on_geometry(component, name, domain, selection, field)) {
if (component.attribute_domain_size(domain) != 0) {
failure.store(true);
if (name == "position" && data_type == CD_PROP_FLOAT3) {
/* Special case for "position" which is no longer an attribute on instances. */
bke::Instances &instances = *geometry_set.get_instances_for_write();
bke::InstancesFieldContext context(instances);
fn::FieldEvaluator evaluator{context, instances.instances_num()};
evaluator.set_selection(selection);
evaluator.add_with_destination(field, bke::instance_position_varray_for_write(instances));
evaluator.evaluate();
}
else {
if (!bke::try_capture_field_on_geometry(component, name, domain, selection, field)) {
if (component.attribute_domain_size(domain) != 0) {
failure.store(true);
}
}
}
}

View File

@ -318,7 +318,7 @@ static void add_instances_from_handles(bke::Instances &instances,
{
instances.resize(layout.positions.size());
MutableSpan<int> handles = instances.reference_handles_for_write();
MutableSpan<float4x4> transforms = instances.transforms();
MutableSpan<float4x4> transforms = instances.transforms_for_write();
threading::parallel_for(IndexRange(layout.positions.size()), 256, [&](IndexRange range) {
for (const int i : range) {

View File

@ -34,7 +34,7 @@ static void translate_instances(GeoNodeExecParams &params, bke::Instances &insta
const VArray<float3> translations = evaluator.get_evaluated<float3>(0);
const VArray<bool> local_spaces = evaluator.get_evaluated<bool>(1);
MutableSpan<float4x4> transforms = instances.transforms();
MutableSpan<float4x4> transforms = instances.transforms_for_write();
selection.foreach_index(GrainSize(1024), [&](const int64_t i) {
if (local_spaces[i]) {

View File

@ -202,134 +202,125 @@ ListBase *SEQ_get_seqbase_from_sequence(Sequence *seq, ListBase **r_channels, in
return seqbase;
}
static void open_anim_filepath(Sequence *seq,
StripAnim *sanim,
const char *filepath,
bool openfile)
{
if (openfile) {
sanim->anim = openanim(filepath,
IB_rect | ((seq->flag & SEQ_FILTERY) ? IB_animdeinterlace : 0),
seq->streamindex,
seq->strip->colorspace_settings.name);
}
else {
sanim->anim = openanim_noload(filepath,
IB_rect | ((seq->flag & SEQ_FILTERY) ? IB_animdeinterlace : 0),
seq->streamindex,
seq->strip->colorspace_settings.name);
}
}
static bool use_proxy(Editing *ed, Sequence *seq)
{
StripProxy *proxy = seq->strip->proxy;
return proxy && ((proxy->storage & SEQ_STORAGE_PROXY_CUSTOM_DIR) != 0 ||
(ed->proxy_storage == SEQ_EDIT_PROXY_DIR_STORAGE));
}
static void proxy_dir_get(Editing *ed, Sequence *seq, size_t str_len, char *r_proxy_dirpath)
{
if (use_proxy(ed, seq)) {
if (ed->proxy_storage == SEQ_EDIT_PROXY_DIR_STORAGE) {
if (ed->proxy_dir[0] == 0) {
BLI_strncpy(r_proxy_dirpath, "//BL_proxy", str_len);
}
else {
BLI_strncpy(r_proxy_dirpath, ed->proxy_dir, str_len);
}
}
else {
BLI_strncpy(r_proxy_dirpath, seq->strip->proxy->dirpath, str_len);
}
BLI_path_abs(r_proxy_dirpath, BKE_main_blendfile_path_from_global());
}
}
static void index_dir_set(Editing *ed, Sequence *seq, StripAnim *sanim)
{
if (sanim->anim == nullptr || !use_proxy(ed, seq)) {
return;
}
char proxy_dirpath[FILE_MAX];
proxy_dir_get(ed, seq, sizeof(proxy_dirpath), proxy_dirpath);
seq_proxy_index_dir_set(sanim->anim, proxy_dirpath);
}
static bool open_anim_file_multiview(Scene *scene, Sequence *seq, char *filepath, bool openfile)
{
char prefix[FILE_MAX];
const char *ext = nullptr;
BKE_scene_multiview_view_prefix_get(scene, filepath, prefix, &ext);
if (seq->views_format != R_IMF_VIEWS_INDIVIDUAL || prefix[0] == '\0') {
return false;
}
Editing *ed = scene->ed;
bool is_multiview_loaded = false;
int totfiles = seq_num_files(scene, seq->views_format, true);
for (int i = 0; i < totfiles; i++) {
const char *suffix = BKE_scene_multiview_view_id_suffix_get(&scene->r, i);
char filepath_view[FILE_MAX];
SNPRINTF(filepath_view, "%s%s%s", prefix, suffix, ext);
StripAnim *sanim = static_cast<StripAnim *>(MEM_mallocN(sizeof(StripAnim), "Strip Anim"));
open_anim_filepath(seq, sanim, filepath_view, openfile);
if (sanim->anim == nullptr) {
MEM_freeN(sanim);
return false; /* Multiview render failed. */
}
index_dir_set(ed, seq, sanim);
BLI_addtail(&seq->anims, sanim);
IMB_suffix_anim(sanim->anim, suffix);
is_multiview_loaded = true;
}
return is_multiview_loaded;
}
void seq_open_anim_file(Scene *scene, Sequence *seq, bool openfile)
{
char dirpath[FILE_MAX];
char filepath[FILE_MAX];
StripProxy *proxy;
bool use_proxy;
bool is_multiview_loaded = false;
Editing *ed = scene->ed;
const bool is_multiview = (seq->flag & SEQ_USE_VIEWS) != 0 &&
(scene->r.scemode & R_MULTIVIEW) != 0;
if ((seq->anims.first != nullptr) && (((StripAnim *)seq->anims.first)->anim != nullptr) &&
!openfile)
{
return;
}
/* reset all the previously created anims */
/* Reset all the previously created anims. */
SEQ_relations_sequence_free_anim(seq);
Editing *ed = scene->ed;
char filepath[FILE_MAX];
BLI_path_join(filepath, sizeof(filepath), seq->strip->dirpath, seq->strip->stripdata->filename);
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&scene->id));
proxy = seq->strip->proxy;
bool is_multiview = (seq->flag & SEQ_USE_VIEWS) != 0 && (scene->r.scemode & R_MULTIVIEW) != 0;
bool multiview_is_loaded = false;
use_proxy = proxy && ((proxy->storage & SEQ_STORAGE_PROXY_CUSTOM_DIR) != 0 ||
(ed->proxy_storage == SEQ_EDIT_PROXY_DIR_STORAGE));
if (use_proxy) {
if (ed->proxy_storage == SEQ_EDIT_PROXY_DIR_STORAGE) {
if (ed->proxy_dir[0] == 0) {
STRNCPY(dirpath, "//BL_proxy");
}
else {
STRNCPY(dirpath, ed->proxy_dir);
}
}
else {
STRNCPY(dirpath, seq->strip->proxy->dirpath);
}
BLI_path_abs(dirpath, BKE_main_blendfile_path_from_global());
if (is_multiview) {
multiview_is_loaded = open_anim_file_multiview(scene, seq, filepath, openfile);
}
if (is_multiview && seq->views_format == R_IMF_VIEWS_INDIVIDUAL) {
int totfiles = seq_num_files(scene, seq->views_format, true);
char prefix[FILE_MAX];
const char *ext = nullptr;
int i;
BKE_scene_multiview_view_prefix_get(scene, filepath, prefix, &ext);
if (prefix[0] != '\0') {
for (i = 0; i < totfiles; i++) {
const char *suffix = BKE_scene_multiview_view_id_suffix_get(&scene->r, i);
char filepath_view[FILE_MAX];
StripAnim *sanim = static_cast<StripAnim *>(MEM_mallocN(sizeof(StripAnim), "Strip Anim"));
BLI_addtail(&seq->anims, sanim);
SNPRINTF(filepath_view, "%s%s%s", prefix, suffix, ext);
if (openfile) {
sanim->anim = openanim(filepath_view,
IB_rect | ((seq->flag & SEQ_FILTERY) ? IB_animdeinterlace : 0),
seq->streamindex,
seq->strip->colorspace_settings.name);
}
else {
sanim->anim = openanim_noload(filepath_view,
IB_rect |
((seq->flag & SEQ_FILTERY) ? IB_animdeinterlace : 0),
seq->streamindex,
seq->strip->colorspace_settings.name);
}
if (sanim->anim) {
/* we already have the suffix */
IMB_suffix_anim(sanim->anim, suffix);
}
else {
if (openfile) {
sanim->anim = openanim(filepath,
IB_rect | ((seq->flag & SEQ_FILTERY) ? IB_animdeinterlace : 0),
seq->streamindex,
seq->strip->colorspace_settings.name);
}
else {
sanim->anim = openanim_noload(filepath,
IB_rect |
((seq->flag & SEQ_FILTERY) ? IB_animdeinterlace : 0),
seq->streamindex,
seq->strip->colorspace_settings.name);
}
/* No individual view files - monoscopic, stereo 3d or EXR multi-view. */
totfiles = 1;
}
if (sanim->anim && use_proxy) {
seq_proxy_index_dir_set(sanim->anim, dirpath);
}
}
is_multiview_loaded = true;
}
}
if (is_multiview_loaded == false) {
StripAnim *sanim;
sanim = static_cast<StripAnim *>(MEM_mallocN(sizeof(StripAnim), "Strip Anim"));
if (!is_multiview || !multiview_is_loaded) {
StripAnim *sanim = static_cast<StripAnim *>(MEM_mallocN(sizeof(StripAnim), "Strip Anim"));
BLI_addtail(&seq->anims, sanim);
if (openfile) {
sanim->anim = openanim(filepath,
IB_rect | ((seq->flag & SEQ_FILTERY) ? IB_animdeinterlace : 0),
seq->streamindex,
seq->strip->colorspace_settings.name);
}
else {
sanim->anim = openanim_noload(filepath,
IB_rect | ((seq->flag & SEQ_FILTERY) ? IB_animdeinterlace : 0),
seq->streamindex,
seq->strip->colorspace_settings.name);
}
if (sanim->anim && use_proxy) {
seq_proxy_index_dir_set(sanim->anim, dirpath);
}
open_anim_filepath(seq, sanim, filepath, openfile);
index_dir_set(ed, seq, sanim);
}
}

@ -1 +1 @@
Subproject commit 74d32aff48a05236adf61a24d8c1b5b4c7c55097
Subproject commit 4f2e16db7ce3bdea79f63c50a9bd5a645f23037f