WIP: Brush assets project #106303
|
@ -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" (
|
||||
|
|
|
@ -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=""
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
* \{ */
|
||||
|
|
|
@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
* \{ */
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ static void rotate_instances(GeoNodeExecParams ¶ms, 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];
|
||||
|
|
|
@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms) {
|
||||
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 ¶ms) {
|
||||
bNode &node = params.add_node("GeometryNodeSampleGrid");
|
||||
params.update_and_connect_available_socket(node, "Position");
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
params.add_item(IFACE_("Value"), [node_type](LinkSearchOpParams ¶ms) {
|
||||
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
|
|
@ -38,7 +38,7 @@ static void scale_instances(GeoNodeExecParams ¶ms, 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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -34,7 +34,7 @@ static void translate_instances(GeoNodeExecParams ¶ms, 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]) {
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue