WIP: Basic support for registering asset shelf as a type in BPY #104991
|
@ -69,6 +69,7 @@ Thanks to Tyler Alden Gubala for maintaining the original version of this packag
|
|||
# ------------------------------------------------------------------------------
|
||||
# Generic Functions
|
||||
|
||||
|
||||
def find_dominating_file(
|
||||
path: str,
|
||||
search: Sequence[str],
|
||||
|
|
|
@ -204,7 +204,6 @@ def list_render_passes(scene, srl):
|
|||
if crl.use_pass_volume_indirect: yield ("VolumeInd", "RGB", 'COLOR')
|
||||
if srl.use_pass_emit: yield ("Emit", "RGB", 'COLOR')
|
||||
if srl.use_pass_environment: yield ("Env", "RGB", 'COLOR')
|
||||
if srl.use_pass_shadow: yield ("Shadow", "RGB", 'COLOR')
|
||||
if srl.use_pass_ambient_occlusion: yield ("AO", "RGB", 'COLOR')
|
||||
if crl.use_pass_shadow_catcher: yield ("Shadow Catcher", "RGB", 'COLOR')
|
||||
# autopep8: on
|
||||
|
|
|
@ -87,11 +87,26 @@ enum_sampling_pattern = (
|
|||
)
|
||||
|
||||
enum_emission_sampling = (
|
||||
('NONE', 'None', "Do not use this surface as a light for sampling", 0),
|
||||
('AUTO', 'Auto', "Automatically determine if the surface should be treated as a light for sampling, based on estimated emission intensity", 1),
|
||||
('FRONT', 'Front', "Treat only front side of the surface as a light, usually for closed meshes whose interior is not visible", 2),
|
||||
('BACK', 'Back', "Treat only back side of the surface as a light for sampling", 3),
|
||||
('FRONT_BACK', 'Front and Back', "Treat surface as a light for sampling, emitting from both the front and back side", 4),
|
||||
('NONE',
|
||||
'None',
|
||||
"Do not use this surface as a light for sampling",
|
||||
0),
|
||||
('AUTO',
|
||||
'Auto',
|
||||
"Automatically determine if the surface should be treated as a light for sampling, based on estimated emission intensity",
|
||||
1),
|
||||
('FRONT',
|
||||
'Front',
|
||||
"Treat only front side of the surface as a light, usually for closed meshes whose interior is not visible",
|
||||
2),
|
||||
('BACK',
|
||||
'Back',
|
||||
"Treat only back side of the surface as a light for sampling",
|
||||
3),
|
||||
('FRONT_BACK',
|
||||
'Front and Back',
|
||||
"Treat surface as a light for sampling, emitting from both the front and back side",
|
||||
4),
|
||||
)
|
||||
|
||||
enum_volume_sampling = (
|
||||
|
@ -155,7 +170,6 @@ enum_view3d_shading_render_pass = (
|
|||
('EMISSION', "Emission", "Show the Emission render pass"),
|
||||
('BACKGROUND', "Background", "Show the Background render pass"),
|
||||
('AO', "Ambient Occlusion", "Show the Ambient Occlusion render pass"),
|
||||
('SHADOW', "Shadow", "Show the Shadow render pass"),
|
||||
('SHADOW_CATCHER', "Shadow Catcher", "Show the Shadow Catcher render pass"),
|
||||
|
||||
('', "Light", ""),
|
||||
|
@ -489,6 +503,12 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
|
|||
default='MULTIPLE_IMPORTANCE_SAMPLING',
|
||||
)
|
||||
|
||||
use_light_tree: BoolProperty(
|
||||
name="Light Tree",
|
||||
description="Sample multiple lights more efficiently based on estimated contribution at every shading point",
|
||||
default=True,
|
||||
)
|
||||
|
||||
min_light_bounces: IntProperty(
|
||||
name="Min Light Bounces",
|
||||
description="Minimum number of light bounces. Setting this higher reduces noise in the first bounces, "
|
||||
|
@ -630,7 +650,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
|
|||
|
||||
transparent_max_bounces: IntProperty(
|
||||
name="Transparent Max Bounces",
|
||||
description="Maximum number of transparent bounces. This is independent of maximum number of other bounces ",
|
||||
description="Maximum number of transparent bounces. This is independent of maximum number of other bounces",
|
||||
min=0, max=1024,
|
||||
default=8,
|
||||
)
|
||||
|
|
|
@ -383,7 +383,6 @@ class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel):
|
|||
col = layout.column(align=True)
|
||||
col.prop(cscene, "min_light_bounces")
|
||||
col.prop(cscene, "min_transparent_bounces")
|
||||
col.prop(cscene, "light_sampling_threshold", text="Light Threshold")
|
||||
|
||||
for view_layer in scene.view_layers:
|
||||
if view_layer.samples > 0:
|
||||
|
@ -392,6 +391,31 @@ class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel):
|
|||
break
|
||||
|
||||
|
||||
class CYCLES_RENDER_PT_sampling_lights(CyclesButtonsPanel, Panel):
|
||||
bl_label = "Lights"
|
||||
bl_parent_id = "CYCLES_RENDER_PT_sampling"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw_header(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
cscene = scene.cycles
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
scene = context.scene
|
||||
cscene = scene.cycles
|
||||
|
||||
col = layout.column(align=True)
|
||||
#col.prop(cscene, "use_light_tree")
|
||||
sub = col.row()
|
||||
sub.prop(cscene, "light_sampling_threshold", text="Light Threshold")
|
||||
#sub.active = not cscene.use_light_tree
|
||||
|
||||
|
||||
class CYCLES_RENDER_PT_subdivision(CyclesButtonsPanel, Panel):
|
||||
bl_label = "Subdivision"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
@ -954,7 +978,6 @@ class CYCLES_RENDER_PT_passes_light(CyclesButtonsPanel, Panel):
|
|||
col = layout.column(heading="Other", align=True)
|
||||
col.prop(view_layer, "use_pass_emit", text="Emission")
|
||||
col.prop(view_layer, "use_pass_environment")
|
||||
col.prop(view_layer, "use_pass_shadow")
|
||||
col.prop(view_layer, "use_pass_ambient_occlusion", text="Ambient Occlusion")
|
||||
col.prop(cycles_view_layer, "use_pass_shadow_catcher")
|
||||
|
||||
|
@ -2366,6 +2389,7 @@ classes = (
|
|||
CYCLES_RENDER_PT_sampling_render_denoise,
|
||||
CYCLES_RENDER_PT_sampling_path_guiding,
|
||||
CYCLES_RENDER_PT_sampling_path_guiding_debug,
|
||||
CYCLES_RENDER_PT_sampling_lights,
|
||||
CYCLES_RENDER_PT_sampling_advanced,
|
||||
CYCLES_RENDER_PT_light_paths,
|
||||
CYCLES_RENDER_PT_light_paths_max_bounces,
|
||||
|
|
|
@ -803,7 +803,7 @@ void BlenderDisplayDriver::draw(const Params ¶ms)
|
|||
const int position_attribute = GPU_vertformat_attr_add(
|
||||
format, display_shader_->position_attribute_name, GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
|
||||
|
||||
/* Note: Shader is bound again through IMM to register this shader with the imm module
|
||||
/* Note: Shader is bound again through IMM to register this shader with the IMM module
|
||||
* and perform required setup for IMM rendering. This is required as the IMM module
|
||||
* needs to be aware of which shader is bound, and the main display shader
|
||||
* is bound externally. */
|
||||
|
|
|
@ -126,7 +126,7 @@ class BlenderDisplayDriver : public DisplayDriver {
|
|||
void gpu_context_lock();
|
||||
void gpu_context_unlock();
|
||||
|
||||
/* Create GPU resources used by the dispaly driver. */
|
||||
/* Create GPU resources used by the display driver. */
|
||||
bool gpu_resources_create();
|
||||
|
||||
/* Destroy all GPU resources which are being used by this object. */
|
||||
|
|
|
@ -559,11 +559,6 @@ static bool bake_setup_pass(Scene *scene, const string &bake_type_str, const int
|
|||
0);
|
||||
integrator->set_use_emission((bake_filter & BL::BakeSettings::pass_filter_EMIT) != 0);
|
||||
}
|
||||
/* Shadow pass. */
|
||||
else if (strcmp(bake_type, "SHADOW") == 0) {
|
||||
type = PASS_SHADOW;
|
||||
use_direct_light = true;
|
||||
}
|
||||
/* Light component passes. */
|
||||
else if (strcmp(bake_type, "DIFFUSE") == 0) {
|
||||
if ((bake_filter & BL::BakeSettings::pass_filter_DIRECT) &&
|
||||
|
|
|
@ -347,7 +347,14 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer, bool background)
|
|||
integrator->set_motion_blur(view_layer.use_motion_blur);
|
||||
}
|
||||
|
||||
integrator->set_light_sampling_threshold(get_float(cscene, "light_sampling_threshold"));
|
||||
bool use_light_tree = false; // get_boolean(cscene, "use_light_tree");
|
||||
integrator->set_use_light_tree(use_light_tree);
|
||||
integrator->set_light_sampling_threshold(
|
||||
(use_light_tree) ? 0.0f : get_float(cscene, "light_sampling_threshold"));
|
||||
|
||||
if (integrator->use_light_tree_is_modified()) {
|
||||
scene->light_manager->tag_update(scene, LightManager::UPDATE_ALL);
|
||||
}
|
||||
|
||||
SamplingPattern sampling_pattern = (SamplingPattern)get_enum(
|
||||
cscene, "sampling_pattern", SAMPLING_NUM_PATTERNS, SAMPLING_PATTERN_PMJ);
|
||||
|
@ -616,7 +623,6 @@ static bool get_known_pass_type(BL::RenderPass &b_pass, PassType &type, PassMode
|
|||
MAP_PASS("Emit", PASS_EMISSION, false);
|
||||
MAP_PASS("Env", PASS_BACKGROUND, false);
|
||||
MAP_PASS("AO", PASS_AO, false);
|
||||
MAP_PASS("Shadow", PASS_SHADOW, false);
|
||||
|
||||
MAP_PASS("BakePrimitive", PASS_BAKE_PRIMITIVE, false);
|
||||
MAP_PASS("BakeDifferential", PASS_BAKE_DIFFERENTIAL, false);
|
||||
|
|
|
@ -351,6 +351,7 @@ DeviceInfo Device::get_multi_device(const vector<DeviceInfo> &subdevices,
|
|||
info.num = 0;
|
||||
|
||||
info.has_nanovdb = true;
|
||||
info.has_light_tree = true;
|
||||
info.has_osl = true;
|
||||
info.has_guiding = true;
|
||||
info.has_profiling = true;
|
||||
|
@ -399,6 +400,7 @@ DeviceInfo Device::get_multi_device(const vector<DeviceInfo> &subdevices,
|
|||
|
||||
/* Accumulate device info. */
|
||||
info.has_nanovdb &= device.has_nanovdb;
|
||||
info.has_light_tree &= device.has_light_tree;
|
||||
info.has_osl &= device.has_osl;
|
||||
info.has_guiding &= device.has_guiding;
|
||||
info.has_profiling &= device.has_profiling;
|
||||
|
|
|
@ -65,6 +65,7 @@ class DeviceInfo {
|
|||
int num;
|
||||
bool display_device; /* GPU is used as a display device. */
|
||||
bool has_nanovdb; /* Support NanoVDB volumes. */
|
||||
bool has_light_tree; /* Support light tree. */
|
||||
bool has_osl; /* Support Open Shading Language. */
|
||||
bool has_guiding; /* Support path guiding. */
|
||||
bool has_profiling; /* Supports runtime collection of profiling info. */
|
||||
|
@ -84,6 +85,7 @@ class DeviceInfo {
|
|||
cpu_threads = 0;
|
||||
display_device = false;
|
||||
has_nanovdb = false;
|
||||
has_light_tree = true;
|
||||
has_osl = false;
|
||||
has_guiding = false;
|
||||
has_profiling = false;
|
||||
|
|
|
@ -137,6 +137,7 @@ void device_hip_info(vector<DeviceInfo> &devices)
|
|||
info.num = num;
|
||||
|
||||
info.has_nanovdb = true;
|
||||
info.has_light_tree = false;
|
||||
info.denoisers = 0;
|
||||
|
||||
info.has_gpu_queue = true;
|
||||
|
|
|
@ -294,6 +294,7 @@ set(SRC_KERNEL_LIGHT_HEADERS
|
|||
light/point.h
|
||||
light/sample.h
|
||||
light/spot.h
|
||||
light/tree.h
|
||||
light/triangle.h
|
||||
)
|
||||
|
||||
|
|
|
@ -60,6 +60,13 @@ KERNEL_DATA_ARRAY(KernelLight, lights)
|
|||
KERNEL_DATA_ARRAY(float2, light_background_marginal_cdf)
|
||||
KERNEL_DATA_ARRAY(float2, light_background_conditional_cdf)
|
||||
|
||||
/* light tree */
|
||||
KERNEL_DATA_ARRAY(KernelLightTreeNode, light_tree_nodes)
|
||||
KERNEL_DATA_ARRAY(KernelLightTreeEmitter, light_tree_emitters)
|
||||
KERNEL_DATA_ARRAY(uint, light_to_tree)
|
||||
KERNEL_DATA_ARRAY(uint, object_lookup_offset)
|
||||
KERNEL_DATA_ARRAY(uint, triangle_to_tree)
|
||||
|
||||
/* particles */
|
||||
KERNEL_DATA_ARRAY(KernelParticle, particles)
|
||||
|
||||
|
|
|
@ -97,8 +97,6 @@ KERNEL_STRUCT_MEMBER(film, int, pass_emission)
|
|||
KERNEL_STRUCT_MEMBER(film, int, pass_background)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_ao)
|
||||
KERNEL_STRUCT_MEMBER(film, float, pass_alpha_threshold)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_shadow)
|
||||
KERNEL_STRUCT_MEMBER(film, float, pass_shadow_scale)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_sample_count)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_matte)
|
||||
|
@ -132,9 +130,6 @@ KERNEL_STRUCT_MEMBER(film, int, use_approximate_shadow_catcher)
|
|||
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_color)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_probability)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_avg_roughness)
|
||||
/* Padding. */
|
||||
KERNEL_STRUCT_MEMBER(film, int, pad1)
|
||||
KERNEL_STRUCT_MEMBER(film, int, pad2)
|
||||
KERNEL_STRUCT_END(KernelFilm)
|
||||
|
||||
/* Integrator. */
|
||||
|
@ -143,6 +138,7 @@ KERNEL_STRUCT_BEGIN(KernelIntegrator, integrator)
|
|||
/* Emission. */
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, use_direct_light)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, use_light_mis)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, use_light_tree)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, num_lights)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, num_distant_lights)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, num_background_lights)
|
||||
|
@ -209,7 +205,6 @@ KERNEL_STRUCT_MEMBER(integrator, int, use_guiding_mis_weights)
|
|||
/* Padding. */
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, pad1)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, pad2)
|
||||
KERNEL_STRUCT_MEMBER(integrator, int, pad3)
|
||||
KERNEL_STRUCT_END(KernelIntegrator)
|
||||
|
||||
/* SVM. For shader specialization. */
|
||||
|
|
|
@ -527,17 +527,6 @@ ccl_device_inline void film_write_direct_light(KernelGlobals kg,
|
|||
film_write_pass_spectrum(buffer + pass_offset, contribution);
|
||||
}
|
||||
}
|
||||
|
||||
/* Write shadow pass. */
|
||||
if (kernel_data.film.pass_shadow != PASS_UNUSED && (path_flag & PATH_RAY_SHADOW_FOR_LIGHT) &&
|
||||
(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
|
||||
const Spectrum unshadowed_throughput = INTEGRATOR_STATE(
|
||||
state, shadow_path, unshadowed_throughput);
|
||||
const Spectrum shadowed_throughput = INTEGRATOR_STATE(state, shadow_path, throughput);
|
||||
const Spectrum shadow = safe_divide(shadowed_throughput, unshadowed_throughput) *
|
||||
kernel_data.film.pass_shadow_scale;
|
||||
film_write_pass_spectrum(buffer + kernel_data.film.pass_shadow, shadow);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
|
|||
|
||||
/* Background MIS weights. */
|
||||
float mis_weight = 1.0f;
|
||||
/* Check if background light exists or if we should skip pdf. */
|
||||
/* Check if background light exists or if we should skip PDF. */
|
||||
if (!(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_MIS_SKIP) &&
|
||||
kernel_data.background.use_mis) {
|
||||
mis_weight = light_sample_mis_weight_forward_background(kg, state, path_flag);
|
||||
|
|
|
@ -326,10 +326,6 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
|
|||
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, throughput) = throughput;
|
||||
|
||||
if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_PASS) {
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unshadowed_throughput) = throughput;
|
||||
}
|
||||
|
||||
/* Write Lightgroup, +1 as lightgroup is int but we need to encode into a uint8_t. */
|
||||
INTEGRATOR_STATE_WRITE(
|
||||
shadow_state, shadow_path, lightgroup) = (ls.type != LIGHT_BACKGROUND) ?
|
||||
|
@ -445,6 +441,7 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
|
|||
/* Update path state */
|
||||
if (!(label & LABEL_TRANSPARENT)) {
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = bsdf_pdf;
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = sd->N;
|
||||
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
|
||||
unguided_bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
|
||||
}
|
||||
|
|
|
@ -685,15 +685,14 @@ ccl_device_forceinline void volume_integrate_heterogeneous(
|
|||
# endif /* __DENOISING_FEATURES__ */
|
||||
}
|
||||
|
||||
/* Path tracing: sample point on light and evaluate light shader, then
|
||||
* queue shadow ray to be traced. */
|
||||
ccl_device_forceinline bool integrate_volume_sample_light(
|
||||
/* Path tracing: sample point on light for equiangular sampling. */
|
||||
ccl_device_forceinline bool integrate_volume_equiangular_sample_light(
|
||||
KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
ccl_private const Ray *ccl_restrict ray,
|
||||
ccl_private const ShaderData *ccl_restrict sd,
|
||||
ccl_private const RNGState *ccl_restrict rng_state,
|
||||
ccl_private LightSample *ccl_restrict ls)
|
||||
ccl_private float3 *ccl_restrict P)
|
||||
{
|
||||
/* Test if there is a light or BSDF that needs direct light. */
|
||||
if (!kernel_data.integrator.use_direct_light) {
|
||||
|
@ -705,6 +704,7 @@ ccl_device_forceinline bool integrate_volume_sample_light(
|
|||
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
|
||||
const float2 rand_light = path_state_rng_2D(kg, rng_state, PRNG_LIGHT);
|
||||
|
||||
LightSample ls ccl_optional_struct_init;
|
||||
if (!light_sample_from_volume_segment(kg,
|
||||
rand_light.x,
|
||||
rand_light.y,
|
||||
|
@ -714,14 +714,20 @@ ccl_device_forceinline bool integrate_volume_sample_light(
|
|||
ray->tmax - ray->tmin,
|
||||
bounce,
|
||||
path_flag,
|
||||
ls)) {
|
||||
&ls)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ls->shader & SHADER_EXCLUDE_SCATTER) {
|
||||
if (ls.shader & SHADER_EXCLUDE_SCATTER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ls.t == FLT_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*P = ls.P;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -737,8 +743,7 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
|||
# ifdef __PATH_GUIDING__
|
||||
ccl_private const Spectrum unlit_throughput,
|
||||
# endif
|
||||
ccl_private const Spectrum throughput,
|
||||
ccl_private LightSample *ccl_restrict ls)
|
||||
ccl_private const Spectrum throughput)
|
||||
{
|
||||
PROFILING_INIT(kg, PROFILING_SHADE_VOLUME_DIRECT_LIGHT);
|
||||
|
||||
|
@ -756,6 +761,7 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
|||
* Additionally we could end up behind the light or outside a spot light cone, which might
|
||||
* waste a sample. Though on the other hand it would be possible to prevent that with
|
||||
* equiangular sampling restricted to a smaller sub-segment where the light has influence. */
|
||||
LightSample ls ccl_optional_struct_init;
|
||||
{
|
||||
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
|
||||
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
|
||||
|
@ -771,12 +777,12 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
|||
SD_BSDF_HAS_TRANSMISSION,
|
||||
bounce,
|
||||
path_flag,
|
||||
ls)) {
|
||||
&ls)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ls->shader & SHADER_EXCLUDE_SCATTER) {
|
||||
if (ls.shader & SHADER_EXCLUDE_SCATTER) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -788,32 +794,32 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
|||
* non-constant light sources. */
|
||||
ShaderDataTinyStorage emission_sd_storage;
|
||||
ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage);
|
||||
const Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, ls, sd->time);
|
||||
const Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, &ls, sd->time);
|
||||
if (is_zero(light_eval)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Evaluate BSDF. */
|
||||
BsdfEval phase_eval ccl_optional_struct_init;
|
||||
float phase_pdf = volume_shader_phase_eval(kg, state, sd, phases, ls->D, &phase_eval);
|
||||
float phase_pdf = volume_shader_phase_eval(kg, state, sd, phases, ls.D, &phase_eval);
|
||||
|
||||
if (ls->shader & SHADER_USE_MIS) {
|
||||
float mis_weight = light_sample_mis_weight_nee(kg, ls->pdf, phase_pdf);
|
||||
if (ls.shader & SHADER_USE_MIS) {
|
||||
float mis_weight = light_sample_mis_weight_nee(kg, ls.pdf, phase_pdf);
|
||||
bsdf_eval_mul(&phase_eval, mis_weight);
|
||||
}
|
||||
|
||||
bsdf_eval_mul(&phase_eval, light_eval / ls->pdf);
|
||||
bsdf_eval_mul(&phase_eval, light_eval / ls.pdf);
|
||||
|
||||
/* Path termination. */
|
||||
const float terminate = path_state_rng_light_termination(kg, rng_state);
|
||||
if (light_sample_terminate(kg, ls, &phase_eval, terminate)) {
|
||||
if (light_sample_terminate(kg, &ls, &phase_eval, terminate)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create shadow ray. */
|
||||
Ray ray ccl_optional_struct_init;
|
||||
light_sample_to_volume_shadow_ray(kg, sd, ls, P, &ray);
|
||||
const bool is_light = light_sample_is_light(ls);
|
||||
light_sample_to_volume_shadow_ray(kg, sd, &ls, P, &ray);
|
||||
const bool is_light = light_sample_is_light(&ls);
|
||||
|
||||
/* Branch off shadow kernel. */
|
||||
IntegratorShadowState shadow_state = integrator_shadow_path_init(
|
||||
|
@ -872,14 +878,10 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
|||
state, path, transmission_bounce);
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, throughput) = throughput_phase;
|
||||
|
||||
if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_PASS) {
|
||||
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unshadowed_throughput) = throughput;
|
||||
}
|
||||
|
||||
/* Write Lightgroup, +1 as lightgroup is int but we need to encode into a uint8_t. */
|
||||
INTEGRATOR_STATE_WRITE(
|
||||
shadow_state, shadow_path, lightgroup) = (ls->type != LIGHT_BACKGROUND) ?
|
||||
ls->group + 1 :
|
||||
shadow_state, shadow_path, lightgroup) = (ls.type != LIGHT_BACKGROUND) ?
|
||||
ls.group + 1 :
|
||||
kernel_data.background.lightgroup + 1;
|
||||
|
||||
# ifdef __PATH_GUIDING__
|
||||
|
@ -981,6 +983,7 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
|
|||
|
||||
/* Update path state */
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = phase_pdf;
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = zero_float3();
|
||||
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
|
||||
unguided_phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
|
||||
|
||||
|
@ -1006,12 +1009,11 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
|||
|
||||
/* Sample light ahead of volume stepping, for equiangular sampling. */
|
||||
/* TODO: distant lights are ignored now, but could instead use even distribution. */
|
||||
LightSample ls ccl_optional_struct_init;
|
||||
const bool need_light_sample = !(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_TERMINATE);
|
||||
float3 equiangular_P = zero_float3();
|
||||
const bool have_equiangular_sample = need_light_sample &&
|
||||
integrate_volume_sample_light(
|
||||
kg, state, ray, &sd, &rng_state, &ls) &&
|
||||
(ls.t != FLT_MAX);
|
||||
integrate_volume_equiangular_sample_light(
|
||||
kg, state, ray, &sd, &rng_state, &equiangular_P);
|
||||
|
||||
VolumeSampleMethod direct_sample_method = (have_equiangular_sample) ?
|
||||
volume_stack_sample_method(kg, state) :
|
||||
|
@ -1041,7 +1043,7 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
|||
render_buffer,
|
||||
step_size,
|
||||
direct_sample_method,
|
||||
ls.P,
|
||||
equiangular_P,
|
||||
result);
|
||||
|
||||
/* Perform path termination. The intersect_closest will have already marked this path
|
||||
|
@ -1108,8 +1110,7 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
|||
# ifdef __PATH_GUIDING__
|
||||
unlit_throughput,
|
||||
# endif
|
||||
result.direct_throughput,
|
||||
&ls);
|
||||
result.direct_throughput);
|
||||
}
|
||||
|
||||
/* Indirect light.
|
||||
|
|
|
@ -32,7 +32,7 @@ KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, throughput, KERNEL_FEATURE_PAT
|
|||
KERNEL_STRUCT_MEMBER(shadow_path,
|
||||
PackedSpectrum,
|
||||
unshadowed_throughput,
|
||||
KERNEL_FEATURE_SHADOW_PASS | KERNEL_FEATURE_AO_ADDITIVE)
|
||||
KERNEL_FEATURE_AO_ADDITIVE)
|
||||
/* Ratio of throughput to distinguish diffuse / glossy / transmission render passes. */
|
||||
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_diffuse_weight, KERNEL_FEATURE_LIGHT_PASSES)
|
||||
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_glossy_weight, KERNEL_FEATURE_LIGHT_PASSES)
|
||||
|
|
|
@ -41,6 +41,7 @@ KERNEL_STRUCT_MEMBER(path, uint8_t, mnee, KERNEL_FEATURE_PATH_TRACING)
|
|||
* zero and distance. Note that transparency and volume attenuation increase
|
||||
* the ray tmin but keep P unmodified so that this works. */
|
||||
KERNEL_STRUCT_MEMBER(path, float, mis_ray_pdf, KERNEL_FEATURE_PATH_TRACING)
|
||||
KERNEL_STRUCT_MEMBER(path, packed_float3, mis_origin_n, KERNEL_FEATURE_PATH_TRACING)
|
||||
/* Filter glossy. */
|
||||
KERNEL_STRUCT_MEMBER(path, float, min_ray_pdf, KERNEL_FEATURE_PATH_TRACING)
|
||||
/* Continuation probability for path termination. */
|
||||
|
|
|
@ -91,7 +91,7 @@ ccl_device_inline float area_light_rect_sample(float3 P,
|
|||
|
||||
ccl_device float area_light_spread_attenuation(const float3 D,
|
||||
const float3 lightNg,
|
||||
const float tan_spread,
|
||||
const float cot_half_spread,
|
||||
const float normalize_spread)
|
||||
{
|
||||
/* Model a soft-box grid, computing the ratio of light not hidden by the
|
||||
|
@ -99,7 +99,7 @@ ccl_device float area_light_spread_attenuation(const float3 D,
|
|||
const float cos_a = -dot(D, lightNg);
|
||||
const float sin_a = safe_sqrtf(1.0f - sqr(cos_a));
|
||||
const float tan_a = sin_a / cos_a;
|
||||
return max((1.0f - (tan_spread * tan_a)) * normalize_spread, 0.0f);
|
||||
return max((1.0f - (cot_half_spread * tan_a)) * normalize_spread, 0.0f);
|
||||
}
|
||||
|
||||
/* Compute subset of area light that actually has an influence on the shading point, to
|
||||
|
@ -111,14 +111,14 @@ ccl_device bool area_light_spread_clamp_area_light(const float3 P,
|
|||
ccl_private float *len_u,
|
||||
const float3 axis_v,
|
||||
ccl_private float *len_v,
|
||||
const float tan_spread)
|
||||
const float cot_half_spread)
|
||||
{
|
||||
/* Closest point in area light plane and distance to that plane. */
|
||||
const float3 closest_P = P - dot(lightNg, P - *lightP) * lightNg;
|
||||
const float t = len(closest_P - P);
|
||||
|
||||
/* Radius of circle on area light that actually affects the shading point. */
|
||||
const float radius = t / tan_spread;
|
||||
const float radius = t / cot_half_spread;
|
||||
|
||||
/* Local uv coordinates of closest point. */
|
||||
const float closest_u = dot(axis_u, closest_P - *lightP);
|
||||
|
@ -186,7 +186,7 @@ ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
|
|||
float sample_len_u = len_u;
|
||||
float sample_len_v = len_v;
|
||||
|
||||
if (!in_volume_segment && klight->area.tan_spread > 0.0f) {
|
||||
if (!in_volume_segment && klight->area.cot_half_spread > 0.0f) {
|
||||
if (!area_light_spread_clamp_area_light(P,
|
||||
Ng,
|
||||
&ls->P,
|
||||
|
@ -194,7 +194,7 @@ ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
|
|||
&sample_len_u,
|
||||
axis_v,
|
||||
&sample_len_v,
|
||||
klight->area.tan_spread)) {
|
||||
klight->area.cot_half_spread)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -216,10 +216,10 @@ ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
|
|||
|
||||
ls->eval_fac = 0.25f * invarea;
|
||||
|
||||
if (klight->area.tan_spread > 0.0f) {
|
||||
if (klight->area.cot_half_spread > 0.0f) {
|
||||
/* Area Light spread angle attenuation */
|
||||
ls->eval_fac *= area_light_spread_attenuation(
|
||||
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
|
||||
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
|
||||
}
|
||||
|
||||
if (is_round) {
|
||||
|
@ -237,10 +237,10 @@ ccl_device_forceinline void area_light_update_position(const ccl_global KernelLi
|
|||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
ls->pdf = invarea;
|
||||
|
||||
if (klight->area.tan_spread > 0.f) {
|
||||
if (klight->area.cot_half_spread > 0.f) {
|
||||
ls->eval_fac = 0.25f * invarea;
|
||||
ls->eval_fac *= area_light_spread_attenuation(
|
||||
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
|
||||
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -313,7 +313,7 @@ ccl_device_inline bool area_light_sample_from_intersection(
|
|||
float sample_len_u = klight->area.len_u;
|
||||
float sample_len_v = klight->area.len_v;
|
||||
|
||||
if (klight->area.tan_spread > 0.0f) {
|
||||
if (klight->area.cot_half_spread > 0.0f) {
|
||||
if (!area_light_spread_clamp_area_light(ray_P,
|
||||
Ng,
|
||||
&light_P,
|
||||
|
@ -321,7 +321,7 @@ ccl_device_inline bool area_light_sample_from_intersection(
|
|||
&sample_len_u,
|
||||
axis_v,
|
||||
&sample_len_v,
|
||||
klight->area.tan_spread)) {
|
||||
klight->area.cot_half_spread)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -331,10 +331,10 @@ ccl_device_inline bool area_light_sample_from_intersection(
|
|||
}
|
||||
ls->eval_fac = 0.25f * invarea;
|
||||
|
||||
if (klight->area.tan_spread > 0.0f) {
|
||||
if (klight->area.cot_half_spread > 0.0f) {
|
||||
/* Area Light spread angle attenuation */
|
||||
ls->eval_fac *= area_light_spread_attenuation(
|
||||
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
|
||||
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
|
||||
if (ls->eval_fac == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
@ -342,4 +342,46 @@ ccl_device_inline bool area_light_sample_from_intersection(
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool area_light_tree_parameters(const ccl_global KernelLight *klight,
|
||||
const float3 centroid,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const float3 bcone_axis,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
if (!in_volume_segment) {
|
||||
/* TODO: a cheap substitute for minimal distance between point and primitive. Does it
|
||||
* worth the overhead to compute the accurate minimal distance? */
|
||||
float min_distance;
|
||||
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
|
||||
distance = make_float2(min_distance, min_distance);
|
||||
}
|
||||
|
||||
cos_theta_u = FLT_MAX;
|
||||
|
||||
const float3 extentu = klight->area.axis_u * klight->area.len_u;
|
||||
const float3 extentv = klight->area.axis_v * klight->area.len_v;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
const float3 corner = ((i & 1) - 0.5f) * extentu + 0.5f * ((i & 2) - 1) * extentv + centroid;
|
||||
float distance_point_to_corner;
|
||||
const float3 point_to_corner = safe_normalize_len(corner - P, &distance_point_to_corner);
|
||||
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
|
||||
if (!in_volume_segment) {
|
||||
distance.x = fmaxf(distance.x, distance_point_to_corner);
|
||||
}
|
||||
}
|
||||
|
||||
const bool front_facing = dot(bcone_axis, point_to_centroid) < 0;
|
||||
const bool shape_above_surface = dot(N, centroid - P) + fabsf(dot(N, extentu)) +
|
||||
fabsf(dot(N, extentv)) >
|
||||
0;
|
||||
const bool in_volume = is_zero(N);
|
||||
|
||||
return (front_facing && shape_above_surface) || in_volume;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
|
|
@ -439,4 +439,19 @@ ccl_device float background_light_pdf(KernelGlobals kg, float3 P, float3 directi
|
|||
|
||||
return pdf;
|
||||
}
|
||||
|
||||
ccl_device_forceinline bool background_light_tree_parameters(const float3 centroid,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
/* Cover the whole sphere */
|
||||
cos_theta_u = -1.0f;
|
||||
|
||||
distance = make_float2(1.0f, 1.0f);
|
||||
point_to_centroid = -centroid;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
|
|
@ -107,4 +107,21 @@ ccl_device bool distant_light_sample_from_intersection(KernelGlobals kg,
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device_forceinline bool distant_light_tree_parameters(const float3 centroid,
|
||||
const float theta_e,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
/* Treating it as a disk light 1 unit away */
|
||||
cos_theta_u = fast_cosf(theta_e);
|
||||
|
||||
distance = make_float2(1.0f / cos_theta_u, 1.0f);
|
||||
|
||||
point_to_centroid = -centroid;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
|
|
@ -11,7 +11,7 @@ CCL_NAMESPACE_BEGIN
|
|||
/* Simple CDF based sampling over all lights in the scene, without taking into
|
||||
* account shading position or normal. */
|
||||
|
||||
ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float *randu)
|
||||
ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float &randu)
|
||||
{
|
||||
/* This is basically std::upper_bound as used by PBRT, to find a point light or
|
||||
* triangle to emit from, proportional to area. a good improvement would be to
|
||||
|
@ -19,7 +19,7 @@ ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float *ra
|
|||
* arbitrary shaders. */
|
||||
int first = 0;
|
||||
int len = kernel_data.integrator.num_distribution + 1;
|
||||
float r = *randu;
|
||||
float r = randu;
|
||||
|
||||
do {
|
||||
int half_len = len >> 1;
|
||||
|
@ -42,55 +42,32 @@ ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float *ra
|
|||
* each area light be stratified as well. */
|
||||
float distr_min = kernel_data_fetch(light_distribution, index).totarea;
|
||||
float distr_max = kernel_data_fetch(light_distribution, index + 1).totarea;
|
||||
*randu = (r - distr_min) / (distr_max - distr_min);
|
||||
randu = (r - distr_min) / (distr_max - distr_min);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_noinline bool light_distribution_sample(KernelGlobals kg,
|
||||
float randu,
|
||||
ccl_private float &randu,
|
||||
const float randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
ccl_private int &emitter_object,
|
||||
ccl_private int &emitter_prim,
|
||||
ccl_private int &emitter_shader_flag,
|
||||
ccl_private float &emitter_pdf_selection)
|
||||
{
|
||||
/* Sample light index from distribution. */
|
||||
const int index = light_distribution_sample(kg, &randu);
|
||||
const int index = light_distribution_sample(kg, randu);
|
||||
ccl_global const KernelLightDistribution *kdistribution = &kernel_data_fetch(light_distribution,
|
||||
index);
|
||||
const int prim = kdistribution->prim;
|
||||
|
||||
if (prim >= 0) {
|
||||
/* Mesh light. */
|
||||
const int object = kdistribution->mesh_light.object_id;
|
||||
|
||||
/* Exclude synthetic meshes from shadow catcher pass. */
|
||||
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
|
||||
!(kernel_data_fetch(object_flag, object) & SD_OBJECT_SHADOW_CATCHER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int shader_flag = kdistribution->mesh_light.shader_flag;
|
||||
triangle_light_sample<in_volume_segment>(kg, prim, object, randu, randv, time, ls, P);
|
||||
ls->shader |= shader_flag;
|
||||
return (ls->pdf > 0.0f);
|
||||
}
|
||||
|
||||
const int lamp = -prim - 1;
|
||||
|
||||
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!light_sample<in_volume_segment>(kg, lamp, randu, randv, P, path_flag, ls)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ls->pdf_selection = kernel_data.integrator.distribution_pdf_lights;
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
emitter_object = kdistribution->mesh_light.object_id;
|
||||
emitter_prim = kdistribution->prim;
|
||||
emitter_shader_flag = kdistribution->mesh_light.shader_flag;
|
||||
emitter_pdf_selection = kernel_data.integrator.distribution_pdf_lights;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -108,4 +108,29 @@ ccl_device_inline bool point_light_sample_from_intersection(
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool point_light_tree_parameters(const ccl_global KernelLight *klight,
|
||||
const float3 centroid,
|
||||
const float3 P,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
if (in_volume_segment) {
|
||||
cos_theta_u = 1.0f; /* Any value in [-1, 1], irrelevant since theta = 0 */
|
||||
return true;
|
||||
}
|
||||
float min_distance;
|
||||
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
|
||||
|
||||
const float radius = klight->spot.radius;
|
||||
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
|
||||
cos_theta_u = min_distance / hypotenus;
|
||||
|
||||
distance = make_float2(hypotenus, min_distance);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "kernel/light/distribution.h"
|
||||
#include "kernel/light/light.h"
|
||||
#include "kernel/light/tree.h"
|
||||
|
||||
#include "kernel/sample/mapping.h"
|
||||
#include "kernel/sample/mis.h"
|
||||
|
@ -315,11 +316,13 @@ ccl_device_inline float light_sample_mis_weight_nee(KernelGlobals kg,
|
|||
/* Next event estimation sampling.
|
||||
*
|
||||
* Sample a position on a light in the scene, from a position on a surface or
|
||||
* from a volume segment. */
|
||||
* from a volume segment.
|
||||
*
|
||||
* Uses either a flat distribution or light tree. */
|
||||
|
||||
ccl_device_inline bool light_sample_from_volume_segment(KernelGlobals kg,
|
||||
float randu,
|
||||
const float randv,
|
||||
float randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const float3 D,
|
||||
|
@ -328,13 +331,89 @@ ccl_device_inline bool light_sample_from_volume_segment(KernelGlobals kg,
|
|||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
return light_distribution_sample<true>(kg, randu, randv, time, P, bounce, path_flag, ls);
|
||||
/* Select an emitter. */
|
||||
int emitter_object = 0;
|
||||
int emitter_prim = 0;
|
||||
int emitter_shader_flag = 0;
|
||||
float emitter_pdf_selection = 0.0f;
|
||||
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
if (!light_tree_sample<true>(kg,
|
||||
randu,
|
||||
randv,
|
||||
time,
|
||||
P,
|
||||
D,
|
||||
t,
|
||||
SD_BSDF_HAS_TRANSMISSION,
|
||||
bounce,
|
||||
path_flag,
|
||||
emitter_object,
|
||||
emitter_prim,
|
||||
emitter_shader_flag,
|
||||
emitter_pdf_selection)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (!light_distribution_sample(kg,
|
||||
randu,
|
||||
randv,
|
||||
time,
|
||||
P,
|
||||
bounce,
|
||||
path_flag,
|
||||
emitter_object,
|
||||
emitter_prim,
|
||||
emitter_shader_flag,
|
||||
emitter_pdf_selection)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set first, triangle light sampling from flat distribution will override. */
|
||||
ls->pdf_selection = emitter_pdf_selection;
|
||||
|
||||
/* Sample a point on the chosen emitter. */
|
||||
if (emitter_prim >= 0) {
|
||||
/* Mesh light. */
|
||||
/* Exclude synthetic meshes from shadow catcher pass. */
|
||||
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
|
||||
!(kernel_data_fetch(object_flag, emitter_object) & SD_OBJECT_SHADOW_CATCHER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!triangle_light_sample<true>(
|
||||
kg, emitter_prim, emitter_object, randu, randv, time, ls, P)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Light object. */
|
||||
const int lamp = ~emitter_prim;
|
||||
|
||||
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!light_sample<true>(kg, lamp, randu, randv, P, path_flag, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
ls->shader |= emitter_shader_flag;
|
||||
|
||||
return (ls->pdf > 0);
|
||||
}
|
||||
|
||||
ccl_device bool light_sample_from_position(KernelGlobals kg,
|
||||
ccl_private const RNGState *rng_state,
|
||||
const float randu,
|
||||
const float randv,
|
||||
float randu,
|
||||
float randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
|
@ -343,7 +422,84 @@ ccl_device bool light_sample_from_position(KernelGlobals kg,
|
|||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
return light_distribution_sample<false>(kg, randu, randv, time, P, bounce, path_flag, ls);
|
||||
/* Select an emitter. */
|
||||
int emitter_object = 0;
|
||||
int emitter_prim = 0;
|
||||
int emitter_shader_flag = 0;
|
||||
float emitter_pdf_selection = 0.0f;
|
||||
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
if (!light_tree_sample<false>(kg,
|
||||
randu,
|
||||
randv,
|
||||
time,
|
||||
P,
|
||||
N,
|
||||
0,
|
||||
shader_flags,
|
||||
bounce,
|
||||
path_flag,
|
||||
emitter_object,
|
||||
emitter_prim,
|
||||
emitter_shader_flag,
|
||||
emitter_pdf_selection)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (!light_distribution_sample(kg,
|
||||
randu,
|
||||
randv,
|
||||
time,
|
||||
P,
|
||||
bounce,
|
||||
path_flag,
|
||||
emitter_object,
|
||||
emitter_prim,
|
||||
emitter_shader_flag,
|
||||
emitter_pdf_selection)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set first, triangle light sampling from flat distribution will override. */
|
||||
ls->pdf_selection = emitter_pdf_selection;
|
||||
|
||||
/* Sample a point on the chosen emitter.
|
||||
* TODO: deduplicate code with light_sample_from_volume_segment? */
|
||||
if (emitter_prim >= 0) {
|
||||
/* Mesh light. */
|
||||
/* Exclude synthetic meshes from shadow catcher pass. */
|
||||
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
|
||||
!(kernel_data_fetch(object_flag, emitter_object) & SD_OBJECT_SHADOW_CATCHER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!triangle_light_sample<false>(
|
||||
kg, emitter_prim, emitter_object, randu, randv, time, ls, P)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Light object. */
|
||||
const int lamp = ~emitter_prim;
|
||||
|
||||
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!light_sample<false>(kg, lamp, randu, randv, P, path_flag, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
ls->shader |= emitter_shader_flag;
|
||||
|
||||
return (ls->pdf > 0);
|
||||
}
|
||||
|
||||
ccl_device_inline bool light_sample_new_position(KernelGlobals kg,
|
||||
|
@ -358,6 +514,16 @@ ccl_device_inline bool light_sample_new_position(KernelGlobals kg,
|
|||
if (!triangle_light_sample<false>(kg, ls->prim, ls->object, randu, randv, time, ls, P)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
/* Handled in triangle_light_sample for effeciency. */
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
|
@ -401,7 +567,19 @@ ccl_device_inline float light_sample_mis_weight_forward_surface(KernelGlobals kg
|
|||
float pdf = triangle_light_pdf(kg, sd, t);
|
||||
|
||||
/* Light selection pdf. */
|
||||
/* Handled in triangle_light_pdf for effeciency. */
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
float3 ray_P = INTEGRATOR_STATE(state, ray, P);
|
||||
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
|
||||
uint lookup_offset = kernel_data_fetch(object_lookup_offset, sd->object);
|
||||
uint prim_offset = kernel_data_fetch(object_prim_offset, sd->object);
|
||||
pdf *= light_tree_pdf(kg, ray_P, N, path_flag, sd->prim - prim_offset + lookup_offset);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
/* Handled in triangle_light_pdf for efficiency. */
|
||||
}
|
||||
|
||||
return light_sample_mis_weight_forward(kg, bsdf_pdf, pdf);
|
||||
}
|
||||
|
@ -416,7 +594,16 @@ ccl_device_inline float light_sample_mis_weight_forward_lamp(KernelGlobals kg,
|
|||
float pdf = ls->pdf;
|
||||
|
||||
/* Light selection pdf. */
|
||||
pdf *= light_distribution_pdf_lamp(kg);
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
|
||||
pdf *= light_tree_pdf(kg, P, N, path_flag, ~ls->lamp);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
pdf *= light_distribution_pdf_lamp(kg);
|
||||
}
|
||||
|
||||
return light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
|
||||
}
|
||||
|
@ -441,7 +628,16 @@ ccl_device_inline float light_sample_mis_weight_forward_background(KernelGlobals
|
|||
float pdf = background_light_pdf(kg, ray_P, ray_D);
|
||||
|
||||
/* Light selection pdf. */
|
||||
pdf *= light_distribution_pdf_lamp(kg);
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
|
||||
pdf *= light_tree_pdf(kg, ray_P, N, path_flag, ~kernel_data.background.light_index);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
pdf *= light_distribution_pdf_lamp(kg);
|
||||
}
|
||||
|
||||
return light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
|
||||
}
|
||||
|
|
|
@ -150,4 +150,30 @@ ccl_device_inline bool spot_light_sample_from_intersection(
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool spot_light_tree_parameters(const ccl_global KernelLight *klight,
|
||||
const float3 centroid,
|
||||
const float3 P,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
float min_distance;
|
||||
const float3 point_to_centroid_ = safe_normalize_len(centroid - P, &min_distance);
|
||||
|
||||
const float radius = klight->spot.radius;
|
||||
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
|
||||
cos_theta_u = min_distance / hypotenus;
|
||||
|
||||
if (in_volume_segment) {
|
||||
return true;
|
||||
}
|
||||
|
||||
distance = make_float2(hypotenus, min_distance);
|
||||
point_to_centroid = point_to_centroid_;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
|
|
@ -0,0 +1,691 @@
|
|||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/light/area.h"
|
||||
#include "kernel/light/common.h"
|
||||
#include "kernel/light/light.h"
|
||||
#include "kernel/light/spot.h"
|
||||
#include "kernel/light/triangle.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* TODO: this seems like a relative expensive computation, and we can make it a lot cheaper
|
||||
* by using a bounding sphere instead of a bounding box. This will be more inaccurate, but it
|
||||
* might be fine when used along with the adaptive splitting. */
|
||||
ccl_device float light_tree_cos_bounding_box_angle(const BoundingBox bbox,
|
||||
const float3 P,
|
||||
const float3 point_to_centroid)
|
||||
{
|
||||
if (P.x > bbox.min.x && P.y > bbox.min.y && P.z > bbox.min.z && P.x < bbox.max.x &&
|
||||
P.y < bbox.max.y && P.z < bbox.max.z) {
|
||||
/* If P is inside the bbox, `theta_u` covers the whole sphere */
|
||||
return -1.0f;
|
||||
}
|
||||
float cos_theta_u = 1.0f;
|
||||
/* Iterate through all 8 possible points of the bounding box. */
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
const float3 corner = make_float3((i & 1) ? bbox.max.x : bbox.min.x,
|
||||
(i & 2) ? bbox.max.y : bbox.min.y,
|
||||
(i & 4) ? bbox.max.z : bbox.min.z);
|
||||
|
||||
/* Caculate the bounding box angle. */
|
||||
float3 point_to_corner = normalize(corner - P);
|
||||
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
|
||||
}
|
||||
return cos_theta_u;
|
||||
}
|
||||
|
||||
ccl_device_forceinline float sin_from_cos(const float c)
|
||||
{
|
||||
return safe_sqrtf(1.0f - sqr(c));
|
||||
}
|
||||
|
||||
/* Compute vector v as in Fig .8. P_v is the corresponding point along the ray ccl_device float3 */
|
||||
ccl_device float3 compute_v(
|
||||
const float3 centroid, const float3 P, const float3 D, const float3 bcone_axis, const float t)
|
||||
{
|
||||
const float3 unnormalized_v0 = P - centroid;
|
||||
float len_v0;
|
||||
const float3 unnormalized_v1 = unnormalized_v0 + D * fminf(t, 1e12f);
|
||||
const float3 v0 = normalize_len(unnormalized_v0, &len_v0);
|
||||
const float3 v1 = normalize(unnormalized_v1);
|
||||
|
||||
const float3 o0 = v0;
|
||||
float3 o1, o2;
|
||||
make_orthonormals_tangent(o0, v1, &o1, &o2);
|
||||
|
||||
const float dot_o0_a = dot(o0, bcone_axis);
|
||||
const float dot_o1_a = dot(o1, bcone_axis);
|
||||
const float cos_phi0 = dot_o0_a / sqrtf(sqr(dot_o0_a) + sqr(dot_o1_a));
|
||||
|
||||
return (dot_o1_a < 0 || dot(v0, v1) > cos_phi0) ? (dot_o0_a > dot(v1, bcone_axis) ? v0 : v1) :
|
||||
cos_phi0 * o0 + sin_from_cos(cos_phi0) * o1;
|
||||
}
|
||||
|
||||
/* This is the general function for calculating the importance of either a cluster or an emitter.
|
||||
* Both of the specialized functions obtain the necessary data before calling this function. */
|
||||
template<bool in_volume_segment>
|
||||
ccl_device void light_tree_importance(const float3 N_or_D,
|
||||
const bool has_transmission,
|
||||
const float3 point_to_centroid,
|
||||
const float cos_theta_u,
|
||||
const BoundingCone bcone,
|
||||
const float max_distance,
|
||||
const float min_distance,
|
||||
const float t,
|
||||
const float energy,
|
||||
ccl_private float &max_importance,
|
||||
ccl_private float &min_importance)
|
||||
{
|
||||
max_importance = 0.0f;
|
||||
min_importance = 0.0f;
|
||||
|
||||
const float sin_theta_u = sin_from_cos(cos_theta_u);
|
||||
|
||||
/* cos(theta_i') in the paper, omitted for volume */
|
||||
float cos_min_incidence_angle = 1.0f;
|
||||
float cos_max_incidence_angle = 1.0f;
|
||||
|
||||
/* when sampling the light tree for the second time in `shade_volume.h` and when query the pdf in
|
||||
* `sample.h` */
|
||||
const bool in_volume = is_zero(N_or_D);
|
||||
if (!in_volume_segment && !in_volume) {
|
||||
const float3 N = N_or_D;
|
||||
const float cos_theta_i = has_transmission ? fabsf(dot(point_to_centroid, N)) :
|
||||
dot(point_to_centroid, N);
|
||||
const float sin_theta_i = sin_from_cos(cos_theta_i);
|
||||
|
||||
/* cos_min_incidence_angle = cos(max{theta_i - theta_u, 0}) = cos(theta_i') in the paper */
|
||||
cos_min_incidence_angle = cos_theta_i >= cos_theta_u ?
|
||||
1.0f :
|
||||
cos_theta_i * cos_theta_u + sin_theta_i * sin_theta_u;
|
||||
|
||||
/* If the node is guaranteed to be behind the surface we're sampling, and the surface is
|
||||
* opaque, then we can give the node an importance of 0 as it contributes nothing to the
|
||||
* surface. This is more accurate than the bbox test if we are calculating the importance of
|
||||
* an emitter with radius */
|
||||
if (!has_transmission && cos_min_incidence_angle < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* cos_max_incidence_angle = cos(min{theta_i + theta_u, pi}) */
|
||||
cos_max_incidence_angle = fmaxf(cos_theta_i * cos_theta_u - sin_theta_i * sin_theta_u, 0.0f);
|
||||
}
|
||||
|
||||
/* cos(theta - theta_u) */
|
||||
const float cos_theta = dot(bcone.axis, -point_to_centroid);
|
||||
const float sin_theta = sin_from_cos(cos_theta);
|
||||
const float cos_theta_minus_theta_u = cos_theta * cos_theta_u + sin_theta * sin_theta_u;
|
||||
|
||||
float cos_theta_o, sin_theta_o;
|
||||
fast_sincosf(bcone.theta_o, &sin_theta_o, &cos_theta_o);
|
||||
|
||||
/* minimum angle an emitter’s axis would form with the direction to the shading point,
|
||||
* cos(theta') in the paper */
|
||||
float cos_min_outgoing_angle;
|
||||
if ((cos_theta >= cos_theta_u) || (cos_theta_minus_theta_u >= cos_theta_o)) {
|
||||
/* theta - theta_o - theta_u <= 0 */
|
||||
kernel_assert((fast_acosf(cos_theta) - bcone.theta_o - fast_acosf(cos_theta_u)) < 5e-4f);
|
||||
cos_min_outgoing_angle = 1.0f;
|
||||
}
|
||||
else if ((bcone.theta_o + bcone.theta_e > M_PI_F) ||
|
||||
(cos_theta_minus_theta_u > cos(bcone.theta_o + bcone.theta_e))) {
|
||||
/* theta' = theta - theta_o - theta_u < theta_e */
|
||||
kernel_assert(
|
||||
(fast_acosf(cos_theta) - bcone.theta_o - fast_acosf(cos_theta_u) - bcone.theta_e) < 5e-4f);
|
||||
const float sin_theta_minus_theta_u = sin_from_cos(cos_theta_minus_theta_u);
|
||||
cos_min_outgoing_angle = cos_theta_minus_theta_u * cos_theta_o +
|
||||
sin_theta_minus_theta_u * sin_theta_o;
|
||||
}
|
||||
else {
|
||||
/* cluster invisible */
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: find a good approximation for f_a. */
|
||||
const float f_a = 1.0f;
|
||||
/* TODO: also consider t (or theta_a, theta_b) for volume */
|
||||
max_importance = fabsf(f_a * cos_min_incidence_angle * energy * cos_min_outgoing_angle /
|
||||
(in_volume_segment ? min_distance : sqr(min_distance)));
|
||||
|
||||
/* TODO: also min importance for volume? */
|
||||
if (in_volume_segment) {
|
||||
min_importance = max_importance;
|
||||
return;
|
||||
}
|
||||
|
||||
/* cos(theta + theta_o + theta_u) if theta + theta_o + theta_u < theta_e, 0 otherwise */
|
||||
float cos_max_outgoing_angle;
|
||||
const float cos_theta_plus_theta_u = cos_theta * cos_theta_u - sin_theta * sin_theta_u;
|
||||
if (bcone.theta_e - bcone.theta_o < 0 || cos_theta < 0 || cos_theta_u < 0 ||
|
||||
cos_theta_plus_theta_u < cos(bcone.theta_e - bcone.theta_o)) {
|
||||
min_importance = 0.0f;
|
||||
}
|
||||
else {
|
||||
const float sin_theta_plus_theta_u = sin_from_cos(cos_theta_plus_theta_u);
|
||||
cos_max_outgoing_angle = cos_theta_plus_theta_u * cos_theta_o -
|
||||
sin_theta_plus_theta_u * sin_theta_o;
|
||||
min_importance = fabsf(f_a * cos_max_incidence_angle * energy * cos_max_outgoing_angle /
|
||||
sqr(max_distance));
|
||||
}
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device bool compute_emitter_centroid_and_dir(KernelGlobals kg,
|
||||
ccl_global const KernelLightTreeEmitter *kemitter,
|
||||
const float3 P,
|
||||
ccl_private float3 ¢roid,
|
||||
ccl_private packed_float3 &dir)
|
||||
{
|
||||
const int prim_id = kemitter->prim_id;
|
||||
if (prim_id < 0) {
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ~prim_id);
|
||||
centroid = klight->co;
|
||||
|
||||
switch (klight->type) {
|
||||
case LIGHT_SPOT:
|
||||
dir = klight->spot.dir;
|
||||
break;
|
||||
case LIGHT_POINT:
|
||||
/* Disk-oriented normal */
|
||||
dir = safe_normalize(P - centroid);
|
||||
break;
|
||||
case LIGHT_AREA:
|
||||
dir = klight->area.dir;
|
||||
break;
|
||||
case LIGHT_BACKGROUND:
|
||||
/* Aarbitrary centroid and direction */
|
||||
centroid = make_float3(0.0f, 0.0f, 1.0f);
|
||||
dir = make_float3(0.0f, 0.0f, -1.0f);
|
||||
return !in_volume_segment;
|
||||
case LIGHT_DISTANT:
|
||||
dir = centroid;
|
||||
return !in_volume_segment;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const int object = kemitter->mesh_light.object_id;
|
||||
float3 vertices[3];
|
||||
triangle_world_space_vertices(kg, object, prim_id, -1.0f, vertices);
|
||||
centroid = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
|
||||
|
||||
if (kemitter->mesh_light.emission_sampling == EMISSION_SAMPLING_FRONT) {
|
||||
dir = safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
}
|
||||
else if (kemitter->mesh_light.emission_sampling == EMISSION_SAMPLING_BACK) {
|
||||
dir = -safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
}
|
||||
else {
|
||||
/* Double sided: any vector in the plane. */
|
||||
dir = safe_normalize(vertices[0] - vertices[1]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device void light_tree_emitter_importance(KernelGlobals kg,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
int emitter_index,
|
||||
ccl_private float &max_importance,
|
||||
ccl_private float &min_importance)
|
||||
{
|
||||
const ccl_global KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
|
||||
emitter_index);
|
||||
|
||||
max_importance = 0.0f;
|
||||
min_importance = 0.0f;
|
||||
BoundingCone bcone;
|
||||
bcone.theta_o = kemitter->theta_o;
|
||||
bcone.theta_e = kemitter->theta_e;
|
||||
float cos_theta_u;
|
||||
float2 distance; /* distance.x = max_distance, distance.y = mix_distance */
|
||||
float3 centroid, point_to_centroid, P_c;
|
||||
|
||||
if (!compute_emitter_centroid_and_dir<in_volume_segment>(
|
||||
kg, kemitter, P, centroid, bcone.axis)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int prim_id = kemitter->prim_id;
|
||||
|
||||
if (in_volume_segment) {
|
||||
const float3 D = N_or_D;
|
||||
/* Closest point */
|
||||
P_c = P + dot(centroid - P, D) * D;
|
||||
/* minimal distance of the ray to the cluster */
|
||||
distance.x = len(centroid - P_c);
|
||||
distance.y = distance.x;
|
||||
point_to_centroid = -compute_v(centroid, P, D, bcone.axis, t);
|
||||
}
|
||||
else {
|
||||
P_c = P;
|
||||
}
|
||||
|
||||
bool is_visible;
|
||||
if (prim_id < 0) {
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ~prim_id);
|
||||
switch (klight->type) {
|
||||
/* Function templates only modifies cos_theta_u when in_volume_segment = true */
|
||||
case LIGHT_SPOT:
|
||||
is_visible = spot_light_tree_parameters<in_volume_segment>(
|
||||
klight, centroid, P_c, cos_theta_u, distance, point_to_centroid);
|
||||
break;
|
||||
case LIGHT_POINT:
|
||||
is_visible = point_light_tree_parameters<in_volume_segment>(
|
||||
klight, centroid, P_c, cos_theta_u, distance, point_to_centroid);
|
||||
bcone.theta_o = 0.0f;
|
||||
break;
|
||||
case LIGHT_AREA:
|
||||
is_visible = area_light_tree_parameters<in_volume_segment>(
|
||||
klight, centroid, P_c, N_or_D, bcone.axis, cos_theta_u, distance, point_to_centroid);
|
||||
break;
|
||||
case LIGHT_BACKGROUND:
|
||||
is_visible = background_light_tree_parameters(
|
||||
centroid, cos_theta_u, distance, point_to_centroid);
|
||||
break;
|
||||
case LIGHT_DISTANT:
|
||||
is_visible = distant_light_tree_parameters(
|
||||
centroid, bcone.theta_e, cos_theta_u, distance, point_to_centroid);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
else { /* mesh light */
|
||||
is_visible = triangle_light_tree_parameters<in_volume_segment>(
|
||||
kg, kemitter, centroid, P_c, N_or_D, bcone, cos_theta_u, distance, point_to_centroid);
|
||||
}
|
||||
|
||||
is_visible |= has_transmission;
|
||||
if (!is_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
light_tree_importance<in_volume_segment>(N_or_D,
|
||||
has_transmission,
|
||||
point_to_centroid,
|
||||
cos_theta_u,
|
||||
bcone,
|
||||
distance.x,
|
||||
distance.y,
|
||||
t,
|
||||
kemitter->energy,
|
||||
max_importance,
|
||||
min_importance);
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device void light_tree_node_importance(KernelGlobals kg,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
const ccl_global KernelLightTreeNode *knode,
|
||||
ccl_private float &max_importance,
|
||||
ccl_private float &min_importance)
|
||||
{
|
||||
max_importance = 0.0f;
|
||||
min_importance = 0.0f;
|
||||
if (knode->num_prims == 1) {
|
||||
/* At a leaf node with only one emitter */
|
||||
light_tree_emitter_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, -knode->child_index, max_importance, min_importance);
|
||||
}
|
||||
else if (knode->num_prims != 0) {
|
||||
const BoundingCone bcone = knode->bcone;
|
||||
const BoundingBox bbox = knode->bbox;
|
||||
|
||||
float3 point_to_centroid;
|
||||
float cos_theta_u;
|
||||
float distance;
|
||||
if (knode->bit_trail == 1) {
|
||||
/* distant light node */
|
||||
if (in_volume_segment) {
|
||||
return;
|
||||
}
|
||||
point_to_centroid = -bcone.axis;
|
||||
cos_theta_u = fast_cosf(bcone.theta_o);
|
||||
distance = 1.0f;
|
||||
}
|
||||
else {
|
||||
const float3 centroid = 0.5f * (bbox.min + bbox.max);
|
||||
|
||||
if (in_volume_segment) {
|
||||
const float3 D = N_or_D;
|
||||
const float3 closest_point = P + dot(centroid - P, D) * D;
|
||||
/* minimal distance of the ray to the cluster */
|
||||
distance = len(centroid - closest_point);
|
||||
point_to_centroid = -compute_v(centroid, P, D, bcone.axis, t);
|
||||
cos_theta_u = light_tree_cos_bounding_box_angle(bbox, closest_point, point_to_centroid);
|
||||
}
|
||||
else {
|
||||
const float3 N = N_or_D;
|
||||
const float3 bbox_extent = bbox.max - centroid;
|
||||
const bool bbox_is_visible = has_transmission |
|
||||
(dot(N, centroid - P) + dot(fabs(N), fabs(bbox_extent)) > 0);
|
||||
|
||||
/* If the node is guaranteed to be behind the surface we're sampling, and the surface is
|
||||
* opaque, then we can give the node an importance of 0 as it contributes nothing to the
|
||||
* surface. */
|
||||
if (!bbox_is_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
point_to_centroid = normalize_len(centroid - P, &distance);
|
||||
cos_theta_u = light_tree_cos_bounding_box_angle(bbox, P, point_to_centroid);
|
||||
}
|
||||
/* clamp distance to half the radius of the cluster when splitting is disabled */
|
||||
distance = fmaxf(0.5f * len(centroid - bbox.max), distance);
|
||||
}
|
||||
/* TODO: currently max_distance = min_distance, max_importance = min_importance for the
|
||||
* nodes. Do we need better weights for complex scenes? */
|
||||
light_tree_importance<in_volume_segment>(N_or_D,
|
||||
has_transmission,
|
||||
point_to_centroid,
|
||||
cos_theta_u,
|
||||
bcone,
|
||||
distance,
|
||||
distance,
|
||||
t,
|
||||
knode->energy,
|
||||
max_importance,
|
||||
min_importance);
|
||||
}
|
||||
}
|
||||
|
||||
ccl_device void sample_resevoir(const int current_index,
|
||||
const float current_weight,
|
||||
ccl_private int &selected_index,
|
||||
ccl_private float &selected_weight,
|
||||
ccl_private float &total_weight,
|
||||
ccl_private float &rand)
|
||||
{
|
||||
if (current_weight == 0.0f) {
|
||||
return;
|
||||
}
|
||||
total_weight += current_weight;
|
||||
float thresh = current_weight / total_weight;
|
||||
if (rand <= thresh) {
|
||||
selected_index = current_index;
|
||||
selected_weight = current_weight;
|
||||
rand = rand / thresh;
|
||||
}
|
||||
else {
|
||||
rand = (rand - thresh) / (1.0f - thresh);
|
||||
}
|
||||
kernel_assert(rand >= 0.0f && rand <= 1.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
/* pick an emitter from a leaf node using resevoir sampling, keep two reservoirs for upper and
|
||||
* lower bounds */
|
||||
template<bool in_volume_segment>
|
||||
ccl_device int light_tree_cluster_select_emitter(KernelGlobals kg,
|
||||
ccl_private float &rand,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
const ccl_global KernelLightTreeNode *knode,
|
||||
ccl_private float *pdf_factor)
|
||||
{
|
||||
float selected_importance[2] = {0.0f, 0.0f};
|
||||
float total_importance[2] = {0.0f, 0.0f};
|
||||
int selected_index = -1;
|
||||
|
||||
/* Mark emitters with zero importance. Used for resevoir when total minimum importance = 0 */
|
||||
kernel_assert(knode->num_prims <= sizeof(uint) * 8);
|
||||
uint has_importance = 0;
|
||||
|
||||
const bool sample_max = (rand > 0.5f); /* sampling using the maximum importance */
|
||||
rand = rand * 2.0f - float(sample_max);
|
||||
|
||||
for (int i = 0; i < knode->num_prims; i++) {
|
||||
int current_index = -knode->child_index + i;
|
||||
/* maximum importance = importance[0], mininum importance = importance[1] */
|
||||
float importance[2];
|
||||
light_tree_emitter_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, current_index, importance[0], importance[1]);
|
||||
|
||||
sample_resevoir(current_index,
|
||||
importance[!sample_max],
|
||||
selected_index,
|
||||
selected_importance[!sample_max],
|
||||
total_importance[!sample_max],
|
||||
rand);
|
||||
if (selected_index == current_index) {
|
||||
selected_importance[sample_max] = importance[sample_max];
|
||||
}
|
||||
total_importance[sample_max] += importance[sample_max];
|
||||
|
||||
has_importance |= ((importance[0] > 0) << i);
|
||||
}
|
||||
|
||||
if (total_importance[0] == 0.0f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (total_importance[1] == 0.0f) {
|
||||
/* uniformly sample emitters with positive maximum importance */
|
||||
if (sample_max) {
|
||||
selected_importance[1] = 1.0f;
|
||||
total_importance[1] = float(popcount(has_importance));
|
||||
}
|
||||
else {
|
||||
selected_index = -1;
|
||||
for (int i = 0; i < knode->num_prims; i++) {
|
||||
int current_index = -knode->child_index + i;
|
||||
sample_resevoir(current_index,
|
||||
float(has_importance & 1),
|
||||
selected_index,
|
||||
selected_importance[1],
|
||||
total_importance[1],
|
||||
rand);
|
||||
has_importance >>= 1;
|
||||
}
|
||||
|
||||
float discard;
|
||||
light_tree_emitter_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, selected_index, selected_importance[0], discard);
|
||||
}
|
||||
}
|
||||
|
||||
*pdf_factor = 0.5f * (selected_importance[0] / total_importance[0] +
|
||||
selected_importance[1] / total_importance[1]);
|
||||
|
||||
return selected_index;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device bool get_left_probability(KernelGlobals kg,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
const int left_index,
|
||||
const int right_index,
|
||||
ccl_private float &left_probability)
|
||||
{
|
||||
const ccl_global KernelLightTreeNode *left = &kernel_data_fetch(light_tree_nodes, left_index);
|
||||
const ccl_global KernelLightTreeNode *right = &kernel_data_fetch(light_tree_nodes, right_index);
|
||||
|
||||
float min_left_importance, max_left_importance, min_right_importance, max_right_importance;
|
||||
light_tree_node_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, left, max_left_importance, min_left_importance);
|
||||
light_tree_node_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, right, max_right_importance, min_right_importance);
|
||||
|
||||
const float total_max_importance = max_left_importance + max_right_importance;
|
||||
if (total_max_importance == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
const float total_min_importance = min_left_importance + min_right_importance;
|
||||
|
||||
/* average two probabilities of picking the left child node using lower and upper bounds */
|
||||
const float probability_max = max_left_importance / total_max_importance;
|
||||
const float probability_min = total_min_importance > 0 ?
|
||||
min_left_importance / total_min_importance :
|
||||
0.5f * (float(max_left_importance > 0) +
|
||||
float(max_right_importance == 0.0f));
|
||||
left_probability = 0.5f * (probability_max + probability_min);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
|
||||
ccl_private float &randu,
|
||||
ccl_private float &randv,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const int shader_flags,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
ccl_private int &emitter_object,
|
||||
ccl_private int &emitter_prim,
|
||||
ccl_private int &emitter_shader_flag,
|
||||
ccl_private float &emitter_pdf_selection)
|
||||
{
|
||||
if (!kernel_data.integrator.use_direct_light) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION);
|
||||
float pdf_leaf = 1.0f;
|
||||
float pdf_emitter_from_leaf = 1.0f;
|
||||
int selected_light = -1;
|
||||
|
||||
int node_index = 0; /* root node */
|
||||
|
||||
/* Traverse the light tree until a leaf node is reached. */
|
||||
while (true) {
|
||||
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes, node_index);
|
||||
|
||||
if (knode->child_index <= 0) {
|
||||
/* At a leaf node, we pick an emitter */
|
||||
selected_light = light_tree_cluster_select_emitter<in_volume_segment>(
|
||||
kg, randv, P, N_or_D, t, has_transmission, knode, &pdf_emitter_from_leaf);
|
||||
break;
|
||||
}
|
||||
|
||||
/* At an interior node, the left child is directly after the parent,
|
||||
* while the right child is stored as the child index. */
|
||||
const int left_index = node_index + 1;
|
||||
const int right_index = knode->child_index;
|
||||
|
||||
float left_prob;
|
||||
if (!get_left_probability<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, left_index, right_index, left_prob)) {
|
||||
return false; /* both child nodes have zero importance */
|
||||
}
|
||||
|
||||
float discard;
|
||||
float total_prob = left_prob;
|
||||
node_index = left_index;
|
||||
sample_resevoir(right_index, 1.0f - left_prob, node_index, discard, total_prob, randu);
|
||||
pdf_leaf *= (node_index == left_index) ? left_prob : (1.0f - left_prob);
|
||||
}
|
||||
|
||||
if (selected_light < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Return info about chosen emitter. */
|
||||
ccl_global const KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
|
||||
selected_light);
|
||||
|
||||
emitter_object = kemitter->mesh_light.object_id;
|
||||
emitter_prim = kemitter->prim_id;
|
||||
emitter_shader_flag = kemitter->mesh_light.shader_flag;
|
||||
emitter_pdf_selection = pdf_leaf * pdf_emitter_from_leaf;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* We need to be able to find the probability of selecting a given light for MIS. */
|
||||
ccl_device float light_tree_pdf(
|
||||
KernelGlobals kg, const float3 P, const float3 N, const int path_flag, const int prim)
|
||||
{
|
||||
const bool has_transmission = (path_flag & PATH_RAY_MIS_HAD_TRANSMISSION);
|
||||
/* Target emitter info */
|
||||
const int target_emitter = (prim >= 0) ? kernel_data_fetch(triangle_to_tree, prim) :
|
||||
kernel_data_fetch(light_to_tree, ~prim);
|
||||
ccl_global const KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
|
||||
target_emitter);
|
||||
const int target_leaf = kemitter->parent_index;
|
||||
ccl_global const KernelLightTreeNode *kleaf = &kernel_data_fetch(light_tree_nodes, target_leaf);
|
||||
uint bit_trail = kleaf->bit_trail;
|
||||
|
||||
int node_index = 0; /* root node */
|
||||
|
||||
float pdf = 1.0f;
|
||||
|
||||
/* Traverse the light tree until we reach the target leaf node */
|
||||
while (true) {
|
||||
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes, node_index);
|
||||
|
||||
if (knode->child_index <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Interior node */
|
||||
const int left_index = node_index + 1;
|
||||
const int right_index = knode->child_index;
|
||||
|
||||
float left_prob;
|
||||
if (!get_left_probability<false>(
|
||||
kg, P, N, 0, has_transmission, left_index, right_index, left_prob)) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const bool go_left = (bit_trail & 1) == 0;
|
||||
bit_trail >>= 1;
|
||||
pdf *= go_left ? left_prob : (1.0f - left_prob);
|
||||
node_index = go_left ? left_index : right_index;
|
||||
|
||||
if (pdf == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
kernel_assert(node_index == target_leaf);
|
||||
|
||||
/* Iterate through leaf node to find the probability of sampling the target emitter. */
|
||||
float target_max_importance = 0.0f;
|
||||
float target_min_importance = 0.0f;
|
||||
float total_max_importance = 0.0f;
|
||||
float total_min_importance = 0.0f;
|
||||
int num_has_importance = 0;
|
||||
for (int i = 0; i < kleaf->num_prims; i++) {
|
||||
const int emitter = -kleaf->child_index + i;
|
||||
float max_importance, min_importance;
|
||||
light_tree_emitter_importance<false>(
|
||||
kg, P, N, 0, has_transmission, emitter, max_importance, min_importance);
|
||||
num_has_importance += (max_importance > 0);
|
||||
if (emitter == target_emitter) {
|
||||
target_max_importance = max_importance;
|
||||
target_min_importance = min_importance;
|
||||
}
|
||||
total_max_importance += max_importance;
|
||||
total_min_importance += min_importance;
|
||||
}
|
||||
|
||||
if (target_max_importance > 0.0f) {
|
||||
return pdf * 0.5f *
|
||||
(target_max_importance / total_max_importance +
|
||||
(total_min_importance > 0 ? target_min_importance / total_min_importance :
|
||||
1.0f / num_has_importance));
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
|
@ -103,16 +103,18 @@ ccl_device_forceinline float triangle_light_pdf(KernelGlobals kg,
|
|||
}
|
||||
|
||||
/* Belongs in distribution.h but can reuse computations here. */
|
||||
float distribution_area = area;
|
||||
if (!kernel_data.integrator.use_light_tree) {
|
||||
float distribution_area = area;
|
||||
|
||||
if (has_motion && area != 0.0f) {
|
||||
/* For motion blur need area of triangle at fixed time as used in the CDF. */
|
||||
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
|
||||
distribution_area = triangle_area(V[0], V[1], V[2]);
|
||||
if (has_motion && area != 0.0f) {
|
||||
/* For motion blur need area of triangle at fixed time as used in the CDF. */
|
||||
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
|
||||
distribution_area = triangle_area(V[0], V[1], V[2]);
|
||||
}
|
||||
|
||||
pdf *= distribution_area * kernel_data.integrator.distribution_pdf_triangles;
|
||||
}
|
||||
|
||||
pdf *= distribution_area * kernel_data.integrator.distribution_pdf_triangles;
|
||||
|
||||
return pdf;
|
||||
}
|
||||
|
||||
|
@ -265,17 +267,63 @@ ccl_device_forceinline bool triangle_light_sample(KernelGlobals kg,
|
|||
}
|
||||
|
||||
/* Belongs in distribution.h but can reuse computations here. */
|
||||
float distribution_area = area;
|
||||
if (!kernel_data.integrator.use_light_tree) {
|
||||
float distribution_area = area;
|
||||
|
||||
if (has_motion && area != 0.0f) {
|
||||
/* For motion blur need area of triangle at fixed time as used in the CDF. */
|
||||
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
|
||||
distribution_area = triangle_area(V[0], V[1], V[2]);
|
||||
if (has_motion && area != 0.0f) {
|
||||
/* For motion blur need area of triangle at fixed time as used in the CDF. */
|
||||
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
|
||||
distribution_area = triangle_area(V[0], V[1], V[2]);
|
||||
}
|
||||
|
||||
ls->pdf_selection = distribution_area * kernel_data.integrator.distribution_pdf_triangles;
|
||||
}
|
||||
|
||||
ls->pdf_selection = distribution_area * kernel_data.integrator.distribution_pdf_triangles;
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
|
||||
return (ls->pdf > 0.0f);
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool triangle_light_tree_parameters(
|
||||
KernelGlobals kg,
|
||||
const ccl_global KernelLightTreeEmitter *kemitter,
|
||||
const float3 centroid,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const BoundingCone bcone,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
if (!in_volume_segment) {
|
||||
/* TODO: a cheap substitute for minimal distance between point and primitive. Does it
|
||||
* worth the overhead to compute the accurate minimal distance? */
|
||||
float min_distance;
|
||||
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
|
||||
distance = make_float2(min_distance, min_distance);
|
||||
}
|
||||
|
||||
cos_theta_u = FLT_MAX;
|
||||
|
||||
const int object = kemitter->mesh_light.object_id;
|
||||
float3 vertices[3];
|
||||
triangle_world_space_vertices(kg, object, kemitter->prim_id, -1.0f, vertices);
|
||||
|
||||
bool shape_above_surface = false;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
const float3 corner = vertices[i];
|
||||
float distance_point_to_corner;
|
||||
const float3 point_to_corner = safe_normalize_len(corner - P, &distance_point_to_corner);
|
||||
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
|
||||
shape_above_surface |= dot(point_to_corner, N) > 0;
|
||||
if (!in_volume_segment) {
|
||||
distance.x = fmaxf(distance.x, distance_point_to_corner);
|
||||
}
|
||||
}
|
||||
|
||||
const bool front_facing = bcone.theta_o != 0.0f || dot(bcone.axis, point_to_centroid) < 0;
|
||||
const bool in_volume = is_zero(N);
|
||||
|
||||
return (front_facing && shape_above_surface) || in_volume;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
|
|
@ -60,6 +60,7 @@ CCL_NAMESPACE_BEGIN
|
|||
#define __DENOISING_FEATURES__
|
||||
#define __DPDU__
|
||||
#define __HAIR__
|
||||
#define __LIGHT_TREE__
|
||||
#define __OBJECT_MOTION__
|
||||
#define __PASSES__
|
||||
#define __PATCH_EVAL__
|
||||
|
@ -74,6 +75,11 @@ CCL_NAMESPACE_BEGIN
|
|||
#define __VISIBILITY_FLAG__
|
||||
#define __VOLUME__
|
||||
|
||||
/* TODO: solve internal compiler errors and enable light tree on HIP. */
|
||||
#ifdef __KERNEL_HIP__
|
||||
# undef __LIGHT_TREE__
|
||||
#endif
|
||||
|
||||
/* Device specific features */
|
||||
#ifdef WITH_OSL
|
||||
# define __OSL__
|
||||
|
@ -226,7 +232,7 @@ enum PathRayFlag : uint32_t {
|
|||
*/
|
||||
|
||||
/* Surface had transmission component at previous bounce. Used for light tree
|
||||
* traversal and culling to be consistent with MIS pdf at the next bounce. */
|
||||
* traversal and culling to be consistent with MIS PDF at the next bounce. */
|
||||
PATH_RAY_MIS_HAD_TRANSMISSION = (1U << 10U),
|
||||
|
||||
/* Don't apply multiple importance sampling weights to emission from
|
||||
|
@ -348,7 +354,6 @@ typedef enum PassType {
|
|||
PASS_EMISSION,
|
||||
PASS_BACKGROUND,
|
||||
PASS_AO,
|
||||
PASS_SHADOW,
|
||||
PASS_DIFFUSE,
|
||||
PASS_DIFFUSE_DIRECT,
|
||||
PASS_DIFFUSE_INDIRECT,
|
||||
|
@ -1302,7 +1307,7 @@ typedef struct KernelAreaLight {
|
|||
float len_v;
|
||||
packed_float3 dir;
|
||||
float invarea;
|
||||
float tan_spread;
|
||||
float cot_half_spread;
|
||||
float normalize_spread;
|
||||
float pad[2];
|
||||
} KernelAreaLight;
|
||||
|
@ -1336,19 +1341,70 @@ static_assert_align(KernelLight, 16);
|
|||
typedef struct KernelLightDistribution {
|
||||
float totarea;
|
||||
int prim;
|
||||
union {
|
||||
struct {
|
||||
int shader_flag;
|
||||
int object_id;
|
||||
} mesh_light;
|
||||
struct {
|
||||
float pad;
|
||||
float size;
|
||||
} lamp;
|
||||
};
|
||||
struct {
|
||||
int shader_flag;
|
||||
int object_id;
|
||||
} mesh_light;
|
||||
} KernelLightDistribution;
|
||||
static_assert_align(KernelLightDistribution, 16);
|
||||
|
||||
/* Bounding box. */
|
||||
using BoundingBox = struct BoundingBox {
|
||||
packed_float3 min;
|
||||
packed_float3 max;
|
||||
};
|
||||
|
||||
using BoundingCone = struct BoundingCone {
|
||||
packed_float3 axis;
|
||||
float theta_o;
|
||||
float theta_e;
|
||||
};
|
||||
|
||||
typedef struct KernelLightTreeNode {
|
||||
/* Bounding box. */
|
||||
BoundingBox bbox;
|
||||
|
||||
/* Bounding cone. */
|
||||
BoundingCone bcone;
|
||||
|
||||
/* Energy. */
|
||||
float energy;
|
||||
|
||||
/* If this is 0 or less, we're at a leaf node
|
||||
* and the negative value indexes into the first child of the light array.
|
||||
* Otherwise, it's an index to the node's second child. */
|
||||
int child_index;
|
||||
int num_prims; /* leaf nodes need to know the number of primitives stored. */
|
||||
|
||||
/* Bit trail. */
|
||||
uint bit_trail;
|
||||
|
||||
/* Padding. */
|
||||
int pad;
|
||||
} KernelLightTreeNode;
|
||||
static_assert_align(KernelLightTreeNode, 16);
|
||||
|
||||
typedef struct KernelLightTreeEmitter {
|
||||
/* Bounding cone. */
|
||||
float theta_o;
|
||||
float theta_e;
|
||||
|
||||
/* Energy. */
|
||||
float energy;
|
||||
|
||||
/* prim_id denotes the location in the lights or triangles array. */
|
||||
int prim_id;
|
||||
struct {
|
||||
int shader_flag;
|
||||
int object_id;
|
||||
EmissionSampling emission_sampling;
|
||||
} mesh_light;
|
||||
|
||||
/* Parent. */
|
||||
int parent_index;
|
||||
} KernelLightTreeEmitter;
|
||||
static_assert_align(KernelLightTreeEmitter, 16);
|
||||
|
||||
typedef struct KernelParticle {
|
||||
int index;
|
||||
float age;
|
||||
|
@ -1548,22 +1604,19 @@ enum KernelFeatureFlag : uint32_t {
|
|||
/* Light render passes. */
|
||||
KERNEL_FEATURE_LIGHT_PASSES = (1U << 21U),
|
||||
|
||||
/* Shadow render pass. */
|
||||
KERNEL_FEATURE_SHADOW_PASS = (1U << 22U),
|
||||
|
||||
/* AO. */
|
||||
KERNEL_FEATURE_AO_PASS = (1U << 23U),
|
||||
KERNEL_FEATURE_AO_ADDITIVE = (1U << 24U),
|
||||
KERNEL_FEATURE_AO_PASS = (1U << 22U),
|
||||
KERNEL_FEATURE_AO_ADDITIVE = (1U << 23U),
|
||||
KERNEL_FEATURE_AO = (KERNEL_FEATURE_AO_PASS | KERNEL_FEATURE_AO_ADDITIVE),
|
||||
|
||||
/* MNEE. */
|
||||
KERNEL_FEATURE_MNEE = (1U << 25U),
|
||||
KERNEL_FEATURE_MNEE = (1U << 24U),
|
||||
|
||||
/* Path guiding. */
|
||||
KERNEL_FEATURE_PATH_GUIDING = (1U << 26U),
|
||||
KERNEL_FEATURE_PATH_GUIDING = (1U << 25U),
|
||||
|
||||
/* OSL. */
|
||||
KERNEL_FEATURE_OSL = (1U << 27U),
|
||||
KERNEL_FEATURE_OSL = (1U << 26U),
|
||||
};
|
||||
|
||||
/* Shader node feature mask, to specialize shader evaluation for kernels. */
|
||||
|
|
|
@ -25,6 +25,7 @@ set(SRC
|
|||
integrator.cpp
|
||||
jitter.cpp
|
||||
light.cpp
|
||||
light_tree.cpp
|
||||
mesh.cpp
|
||||
mesh_displace.cpp
|
||||
mesh_subdivision.cpp
|
||||
|
@ -63,6 +64,7 @@ set(SRC_HEADERS
|
|||
image_vdb.h
|
||||
integrator.h
|
||||
light.h
|
||||
light_tree.h
|
||||
jitter.h
|
||||
mesh.h
|
||||
object.h
|
||||
|
|
|
@ -187,7 +187,6 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene)
|
|||
kfilm->pass_transmission_indirect = PASS_UNUSED;
|
||||
kfilm->pass_volume_direct = PASS_UNUSED;
|
||||
kfilm->pass_volume_indirect = PASS_UNUSED;
|
||||
kfilm->pass_shadow = PASS_UNUSED;
|
||||
kfilm->pass_lightgroup = PASS_UNUSED;
|
||||
|
||||
/* Mark passes as unused so that the kernel knows the pass is inaccessible. */
|
||||
|
@ -295,9 +294,6 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene)
|
|||
case PASS_AO:
|
||||
kfilm->pass_ao = kfilm->pass_stride;
|
||||
break;
|
||||
case PASS_SHADOW:
|
||||
kfilm->pass_shadow = kfilm->pass_stride;
|
||||
break;
|
||||
|
||||
case PASS_DIFFUSE_COLOR:
|
||||
kfilm->pass_diffuse_color = kfilm->pass_stride;
|
||||
|
@ -727,10 +723,6 @@ uint Film::get_kernel_features(const Scene *scene) const
|
|||
kernel_features |= KERNEL_FEATURE_LIGHT_PASSES;
|
||||
}
|
||||
|
||||
if (pass_type == PASS_SHADOW) {
|
||||
kernel_features |= KERNEL_FEATURE_SHADOW_PASS;
|
||||
}
|
||||
|
||||
if (pass_type == PASS_AO) {
|
||||
kernel_features |= KERNEL_FEATURE_AO_PASS;
|
||||
}
|
||||
|
|
|
@ -102,7 +102,8 @@ NODE_DEFINE(Integrator)
|
|||
SOCKET_FLOAT(adaptive_threshold, "Adaptive Threshold", 0.01f);
|
||||
SOCKET_INT(adaptive_min_samples, "Adaptive Min Samples", 0);
|
||||
|
||||
SOCKET_FLOAT(light_sampling_threshold, "Light Sampling Threshold", 0.01f);
|
||||
SOCKET_BOOLEAN(use_light_tree, "Use light tree to optimize many light sampling", true);
|
||||
SOCKET_FLOAT(light_sampling_threshold, "Light Sampling Threshold", 0.0f);
|
||||
|
||||
static NodeEnum sampling_pattern_enum;
|
||||
sampling_pattern_enum.insert("sobol_burley", SAMPLING_PATTERN_SOBOL_BURLEY);
|
||||
|
@ -250,6 +251,7 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
|
|||
kintegrator->sampling_pattern = sampling_pattern;
|
||||
kintegrator->scrambling_distance = scrambling_distance;
|
||||
|
||||
kintegrator->use_light_tree = scene->integrator->use_light_tree;
|
||||
if (light_sampling_threshold > 0.0f) {
|
||||
kintegrator->light_inv_rr_threshold = 1.0f / light_sampling_threshold;
|
||||
}
|
||||
|
|
|
@ -79,6 +79,7 @@ class Integrator : public Node {
|
|||
NODE_SOCKET_API(int, aa_samples)
|
||||
NODE_SOCKET_API(int, start_sample)
|
||||
|
||||
NODE_SOCKET_API(bool, use_light_tree)
|
||||
NODE_SOCKET_API(float, light_sampling_threshold)
|
||||
|
||||
NODE_SOCKET_API(bool, use_adaptive_sampling)
|
||||
|
|
|
@ -110,6 +110,7 @@ NODE_DEFINE(Light)
|
|||
SOCKET_FLOAT(spread, "Spread", M_PI_F);
|
||||
|
||||
SOCKET_INT(map_resolution, "Map Resolution", 0);
|
||||
SOCKET_FLOAT(average_radiance, "Average Radiance", 0.0f);
|
||||
|
||||
SOCKET_FLOAT(spot_angle, "Spot Angle", M_PI_4_F);
|
||||
SOCKET_FLOAT(spot_smooth, "Spot Smooth", 0.0f);
|
||||
|
@ -265,13 +266,12 @@ bool LightManager::object_usable_as_light(Object *object)
|
|||
return false;
|
||||
}
|
||||
|
||||
void LightManager::device_update_distribution(Device *device,
|
||||
void LightManager::device_update_distribution(Device *,
|
||||
DeviceScene *dscene,
|
||||
Scene *scene,
|
||||
Progress &progress)
|
||||
{
|
||||
KernelIntegrator *kintegrator = &dscene->data.integrator;
|
||||
KernelFilm *kfilm = &dscene->data.film;
|
||||
|
||||
/* Update CDF over lights. */
|
||||
progress.set_status("Updating Lights", "Computing distribution");
|
||||
|
@ -304,7 +304,6 @@ void LightManager::device_update_distribution(Device *device,
|
|||
}
|
||||
|
||||
const size_t num_lights = kintegrator->num_lights;
|
||||
const size_t num_background_lights = kintegrator->num_background_lights;
|
||||
const size_t num_distribution = num_triangles + num_lights;
|
||||
|
||||
/* Distribution size. */
|
||||
|
@ -312,6 +311,11 @@ void LightManager::device_update_distribution(Device *device,
|
|||
|
||||
VLOG_INFO << "Total " << num_distribution << " of light distribution primitives.";
|
||||
|
||||
if (kintegrator->use_light_tree) {
|
||||
dscene->light_distribution.free();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Emission area. */
|
||||
KernelLightDistribution *distribution = dscene->light_distribution.alloc(num_distribution + 1);
|
||||
float totarea = 0.0f;
|
||||
|
@ -402,8 +406,8 @@ void LightManager::device_update_distribution(Device *device,
|
|||
|
||||
distribution[offset].totarea = totarea;
|
||||
distribution[offset].prim = ~light_index;
|
||||
distribution[offset].lamp.pad = 1.0f;
|
||||
distribution[offset].lamp.size = light->size;
|
||||
distribution[offset].mesh_light.object_id = OBJECT_NONE;
|
||||
distribution[offset].mesh_light.shader_flag = 0;
|
||||
totarea += lightarea;
|
||||
|
||||
light_index++;
|
||||
|
@ -413,9 +417,9 @@ void LightManager::device_update_distribution(Device *device,
|
|||
|
||||
/* normalize cumulative distribution functions */
|
||||
distribution[num_distribution].totarea = totarea;
|
||||
distribution[num_distribution].prim = 0.0f;
|
||||
distribution[num_distribution].lamp.pad = 0.0f;
|
||||
distribution[num_distribution].lamp.size = 0.0f;
|
||||
distribution[num_distribution].prim = 0;
|
||||
distribution[num_distribution].mesh_light.object_id = OBJECT_NONE;
|
||||
distribution[num_distribution].mesh_light.shader_flag = 0;
|
||||
|
||||
if (totarea > 0.0f) {
|
||||
for (size_t i = 0; i < num_distribution; i++)
|
||||
|
@ -448,22 +452,201 @@ void LightManager::device_update_distribution(Device *device,
|
|||
}
|
||||
}
|
||||
|
||||
/* bit of an ugly hack to compensate for emitting triangles influencing
|
||||
* amount of samples we get for this pass */
|
||||
kfilm->pass_shadow_scale = 1.0f;
|
||||
|
||||
if (kintegrator->distribution_pdf_triangles != 0.0f) {
|
||||
kfilm->pass_shadow_scale /= 0.5f;
|
||||
}
|
||||
|
||||
if (num_background_lights < num_lights) {
|
||||
kfilm->pass_shadow_scale /= (float)(num_lights - num_background_lights) / (float)num_lights;
|
||||
}
|
||||
|
||||
/* Copy distribution to device. */
|
||||
dscene->light_distribution.copy_to_device();
|
||||
}
|
||||
|
||||
void LightManager::device_update_tree(Device *,
|
||||
DeviceScene *dscene,
|
||||
Scene *scene,
|
||||
Progress &progress)
|
||||
{
|
||||
KernelIntegrator *kintegrator = &dscene->data.integrator;
|
||||
|
||||
if (!kintegrator->use_light_tree) {
|
||||
dscene->light_tree_nodes.free();
|
||||
dscene->light_tree_emitters.free();
|
||||
dscene->light_to_tree.free();
|
||||
dscene->object_lookup_offset.free();
|
||||
dscene->triangle_to_tree.free();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update light tree. */
|
||||
progress.set_status("Updating Lights", "Computing tree");
|
||||
|
||||
/* Add both lights and emissive triangles to this vector for light tree construction. */
|
||||
vector<LightTreePrimitive> light_prims;
|
||||
light_prims.reserve(kintegrator->num_distribution);
|
||||
vector<LightTreePrimitive> distant_lights;
|
||||
distant_lights.reserve(kintegrator->num_distant_lights);
|
||||
vector<uint> object_lookup_offsets(scene->objects.size());
|
||||
|
||||
/* When we keep track of the light index, only contributing lights will be added to the device.
|
||||
* Therefore, we want to keep track of the light's index on the device.
|
||||
* However, we also need the light's index in the scene when we're constructing the tree. */
|
||||
int device_light_index = 0;
|
||||
int scene_light_index = 0;
|
||||
foreach (Light *light, scene->lights) {
|
||||
if (light->is_enabled) {
|
||||
if (light->light_type == LIGHT_BACKGROUND || light->light_type == LIGHT_DISTANT) {
|
||||
distant_lights.emplace_back(scene, ~device_light_index, scene_light_index);
|
||||
}
|
||||
else {
|
||||
light_prims.emplace_back(scene, ~device_light_index, scene_light_index);
|
||||
}
|
||||
|
||||
device_light_index++;
|
||||
}
|
||||
|
||||
scene_light_index++;
|
||||
}
|
||||
|
||||
/* Similarly, we also want to keep track of the index of triangles that are emissive. */
|
||||
size_t total_triangles = 0;
|
||||
int object_id = 0;
|
||||
foreach (Object *object, scene->objects) {
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
|
||||
if (!object_usable_as_light(object)) {
|
||||
object_id++;
|
||||
continue;
|
||||
}
|
||||
|
||||
object_lookup_offsets[object_id] = total_triangles;
|
||||
|
||||
/* Count emissive triangles. */
|
||||
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
|
||||
size_t mesh_num_triangles = mesh->num_triangles();
|
||||
|
||||
for (size_t i = 0; i < mesh_num_triangles; i++) {
|
||||
int shader_index = mesh->get_shader()[i];
|
||||
Shader *shader = (shader_index < mesh->get_used_shaders().size()) ?
|
||||
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
|
||||
scene->default_surface;
|
||||
|
||||
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
|
||||
light_prims.emplace_back(scene, i, object_id);
|
||||
}
|
||||
}
|
||||
|
||||
total_triangles += mesh_num_triangles;
|
||||
object_id++;
|
||||
}
|
||||
|
||||
/* Append distant lights to the end of `light_prims` */
|
||||
std::move(distant_lights.begin(), distant_lights.end(), std::back_inserter(light_prims));
|
||||
|
||||
/* Update integrator state. */
|
||||
kintegrator->use_direct_light = !light_prims.empty();
|
||||
|
||||
/* TODO: For now, we'll start with a smaller number of max lights in a node.
|
||||
* More benchmarking is needed to determine what number works best. */
|
||||
LightTree light_tree(light_prims, kintegrator->num_distant_lights, 8);
|
||||
|
||||
/* We want to create separate arrays corresponding to triangles and lights,
|
||||
* which will be used to index back into the light tree for PDF calculations. */
|
||||
const size_t num_lights = kintegrator->num_lights;
|
||||
uint *light_array = dscene->light_to_tree.alloc(num_lights);
|
||||
uint *object_offsets = dscene->object_lookup_offset.alloc(object_lookup_offsets.size());
|
||||
uint *triangle_array = dscene->triangle_to_tree.alloc(total_triangles);
|
||||
|
||||
for (int i = 0; i < object_lookup_offsets.size(); i++) {
|
||||
object_offsets[i] = object_lookup_offsets[i];
|
||||
}
|
||||
|
||||
/* First initialize the light tree's nodes. */
|
||||
const vector<LightTreeNode> &linearized_bvh = light_tree.get_nodes();
|
||||
KernelLightTreeNode *light_tree_nodes = dscene->light_tree_nodes.alloc(linearized_bvh.size());
|
||||
KernelLightTreeEmitter *light_tree_emitters = dscene->light_tree_emitters.alloc(
|
||||
light_prims.size());
|
||||
for (int index = 0; index < linearized_bvh.size(); index++) {
|
||||
const LightTreeNode &node = linearized_bvh[index];
|
||||
|
||||
light_tree_nodes[index].energy = node.energy;
|
||||
|
||||
light_tree_nodes[index].bbox.min = node.bbox.min;
|
||||
light_tree_nodes[index].bbox.max = node.bbox.max;
|
||||
|
||||
light_tree_nodes[index].bcone.axis = node.bcone.axis;
|
||||
light_tree_nodes[index].bcone.theta_o = node.bcone.theta_o;
|
||||
light_tree_nodes[index].bcone.theta_e = node.bcone.theta_e;
|
||||
|
||||
light_tree_nodes[index].bit_trail = node.bit_trail;
|
||||
light_tree_nodes[index].num_prims = node.num_prims;
|
||||
|
||||
/* Here we need to make a distinction between interior and leaf nodes. */
|
||||
if (node.is_leaf()) {
|
||||
light_tree_nodes[index].child_index = -node.first_prim_index;
|
||||
|
||||
for (int i = 0; i < node.num_prims; i++) {
|
||||
int emitter_index = i + node.first_prim_index;
|
||||
LightTreePrimitive &prim = light_prims[emitter_index];
|
||||
|
||||
light_tree_emitters[emitter_index].energy = prim.energy;
|
||||
light_tree_emitters[emitter_index].theta_o = prim.bcone.theta_o;
|
||||
light_tree_emitters[emitter_index].theta_e = prim.bcone.theta_e;
|
||||
|
||||
if (prim.is_triangle()) {
|
||||
light_tree_emitters[emitter_index].mesh_light.object_id = prim.object_id;
|
||||
|
||||
int shader_flag = 0;
|
||||
Object *object = scene->objects[prim.object_id];
|
||||
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
|
||||
Shader *shader = static_cast<Shader *>(
|
||||
mesh->get_used_shaders()[mesh->get_shader()[prim.prim_id]]);
|
||||
|
||||
if (!(object->get_visibility() & PATH_RAY_CAMERA)) {
|
||||
shader_flag |= SHADER_EXCLUDE_CAMERA;
|
||||
}
|
||||
if (!(object->get_visibility() & PATH_RAY_DIFFUSE)) {
|
||||
shader_flag |= SHADER_EXCLUDE_DIFFUSE;
|
||||
}
|
||||
if (!(object->get_visibility() & PATH_RAY_GLOSSY)) {
|
||||
shader_flag |= SHADER_EXCLUDE_GLOSSY;
|
||||
}
|
||||
if (!(object->get_visibility() & PATH_RAY_TRANSMIT)) {
|
||||
shader_flag |= SHADER_EXCLUDE_TRANSMIT;
|
||||
}
|
||||
if (!(object->get_visibility() & PATH_RAY_VOLUME_SCATTER)) {
|
||||
shader_flag |= SHADER_EXCLUDE_SCATTER;
|
||||
}
|
||||
if (!(object->get_is_shadow_catcher())) {
|
||||
shader_flag |= SHADER_EXCLUDE_SHADOW_CATCHER;
|
||||
}
|
||||
|
||||
light_tree_emitters[emitter_index].prim_id = prim.prim_id + mesh->prim_offset;
|
||||
light_tree_emitters[emitter_index].mesh_light.shader_flag = shader_flag;
|
||||
light_tree_emitters[emitter_index].mesh_light.emission_sampling =
|
||||
shader->emission_sampling;
|
||||
triangle_array[prim.prim_id + object_lookup_offsets[prim.object_id]] = emitter_index;
|
||||
}
|
||||
else {
|
||||
light_tree_emitters[emitter_index].prim_id = prim.prim_id;
|
||||
light_tree_emitters[emitter_index].mesh_light.shader_flag = 0;
|
||||
light_tree_emitters[emitter_index].mesh_light.object_id = OBJECT_NONE;
|
||||
light_tree_emitters[emitter_index].mesh_light.emission_sampling =
|
||||
EMISSION_SAMPLING_FRONT_BACK;
|
||||
light_array[~prim.prim_id] = emitter_index;
|
||||
}
|
||||
|
||||
light_tree_emitters[emitter_index].parent_index = index;
|
||||
}
|
||||
}
|
||||
else {
|
||||
light_tree_nodes[index].child_index = node.right_child_index;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy arrays to device. */
|
||||
dscene->light_tree_nodes.copy_to_device();
|
||||
dscene->light_tree_emitters.copy_to_device();
|
||||
dscene->light_to_tree.copy_to_device();
|
||||
dscene->object_lookup_offset.copy_to_device();
|
||||
dscene->triangle_to_tree.copy_to_device();
|
||||
}
|
||||
|
||||
static void background_cdf(
|
||||
int start, int end, int res_x, int res_y, const vector<float3> *pixels, float2 *cond_cdf)
|
||||
{
|
||||
|
@ -650,6 +833,8 @@ void LightManager::device_update_background(Device *device,
|
|||
float cdf_total = marg_cdf[res.y - 1].y + marg_cdf[res.y - 1].x / res.y;
|
||||
marg_cdf[res.y].x = cdf_total;
|
||||
|
||||
background_light->set_average_radiance(cdf_total * M_PI_2_F);
|
||||
|
||||
if (cdf_total > 0.0f)
|
||||
for (int i = 1; i < res.y; i++)
|
||||
marg_cdf[i].y /= cdf_total;
|
||||
|
@ -663,7 +848,7 @@ void LightManager::device_update_background(Device *device,
|
|||
dscene->light_background_conditional_cdf.copy_to_device();
|
||||
}
|
||||
|
||||
void LightManager::device_update_lights(Device *, DeviceScene *dscene, Scene *scene)
|
||||
void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Scene *scene)
|
||||
{
|
||||
/* Counts lights in the scene. */
|
||||
size_t num_lights = 0;
|
||||
|
@ -697,6 +882,8 @@ void LightManager::device_update_lights(Device *, DeviceScene *dscene, Scene *sc
|
|||
|
||||
/* Update integrator settings. */
|
||||
KernelIntegrator *kintegrator = &dscene->data.integrator;
|
||||
kintegrator->use_light_tree = scene->integrator->get_use_light_tree() &&
|
||||
device->info.has_light_tree;
|
||||
kintegrator->num_lights = num_lights;
|
||||
kintegrator->num_distant_lights = num_distant_lights;
|
||||
kintegrator->num_background_lights = num_background_lights;
|
||||
|
@ -851,14 +1038,14 @@ void LightManager::device_update_lights(Device *, DeviceScene *dscene, Scene *sc
|
|||
float invarea = (area != 0.0f) ? 1.0f / area : 1.0f;
|
||||
float3 dir = light->dir;
|
||||
|
||||
/* Convert from spread angle 0..180 to 90..0, clamping to a minimum
|
||||
* angle to avoid excessive noise. */
|
||||
const float min_spread_angle = 1.0f * M_PI_F / 180.0f;
|
||||
const float spread_angle = 0.5f * (M_PI_F - max(light->spread, min_spread_angle));
|
||||
/* Clamping to a minimum angle to avoid excessive noise. */
|
||||
const float min_spread = 1.0f * M_PI_F / 180.0f;
|
||||
const float half_spread = 0.5f * max(light->spread, min_spread);
|
||||
/* cot_half_spread is h in D10594#269626 */
|
||||
const float cot_half_spread = tanf(M_PI_2_F - half_spread);
|
||||
/* Normalization computed using:
|
||||
* integrate cos(x) * (1 - tan(x) * tan(a)) * sin(x) from x = 0 to pi/2 - a. */
|
||||
const float tan_spread = tanf(spread_angle);
|
||||
const float normalize_spread = 2.0f / (2.0f + (2.0f * spread_angle - M_PI_F) * tan_spread);
|
||||
* integrate cos(x) * (1 - tan(x) / tan(a)) * sin(x) from x = 0 to a, a being half_spread */
|
||||
const float normalize_spread = 1.0f / (1.0f - half_spread * cot_half_spread);
|
||||
|
||||
dir = safe_normalize(dir);
|
||||
|
||||
|
@ -872,7 +1059,7 @@ void LightManager::device_update_lights(Device *, DeviceScene *dscene, Scene *sc
|
|||
klights[light_index].area.len_v = len_v;
|
||||
klights[light_index].area.invarea = invarea;
|
||||
klights[light_index].area.dir = dir;
|
||||
klights[light_index].area.tan_spread = tan_spread;
|
||||
klights[light_index].area.cot_half_spread = cot_half_spread;
|
||||
klights[light_index].area.normalize_spread = normalize_spread;
|
||||
}
|
||||
else if (light->light_type == LIGHT_SPOT) {
|
||||
|
@ -957,6 +1144,10 @@ void LightManager::device_update(Device *device,
|
|||
if (progress.get_cancel())
|
||||
return;
|
||||
|
||||
device_update_tree(device, dscene, scene, progress);
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
|
||||
device_update_ies(dscene);
|
||||
if (progress.get_cancel())
|
||||
return;
|
||||
|
@ -967,6 +1158,12 @@ void LightManager::device_update(Device *device,
|
|||
|
||||
void LightManager::device_free(Device *, DeviceScene *dscene, const bool free_background)
|
||||
{
|
||||
/* to-do: check if the light tree member variables need to be wrapped in a conditional too*/
|
||||
dscene->light_tree_nodes.free();
|
||||
dscene->light_tree_emitters.free();
|
||||
dscene->light_to_tree.free();
|
||||
dscene->triangle_to_tree.free();
|
||||
|
||||
dscene->light_distribution.free();
|
||||
dscene->lights.free();
|
||||
if (free_background) {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
/* included as Light::set_shader defined through NODE_SOCKET_API does not select
|
||||
* the right Node::set overload as it does not know that Shader is a Node */
|
||||
#include "scene/light_tree.h"
|
||||
#include "scene/shader.h"
|
||||
|
||||
#include "util/ies.h"
|
||||
|
@ -50,6 +51,7 @@ class Light : public Node {
|
|||
NODE_SOCKET_API(Transform, tfm)
|
||||
|
||||
NODE_SOCKET_API(int, map_resolution)
|
||||
NODE_SOCKET_API(float, average_radiance)
|
||||
|
||||
NODE_SOCKET_API(float, spot_angle)
|
||||
NODE_SOCKET_API(float, spot_smooth)
|
||||
|
|
|
@ -0,0 +1,390 @@
|
|||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#include "scene/light_tree.h"
|
||||
#include "scene/mesh.h"
|
||||
#include "scene/object.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
float OrientationBounds::calculate_measure() const
|
||||
{
|
||||
float theta_w = fminf(M_PI_F, theta_o + theta_e);
|
||||
float cos_theta_o = cosf(theta_o);
|
||||
float sin_theta_o = sinf(theta_o);
|
||||
|
||||
return M_2PI_F * (1 - cos_theta_o) +
|
||||
M_PI_2_F * (2 * theta_w * sin_theta_o - cosf(theta_o - 2 * theta_w) -
|
||||
2 * theta_o * sin_theta_o + cos_theta_o);
|
||||
}
|
||||
|
||||
OrientationBounds merge(const OrientationBounds &cone_a, const OrientationBounds &cone_b)
|
||||
{
|
||||
if (is_zero(cone_a.axis)) {
|
||||
return cone_b;
|
||||
}
|
||||
if (is_zero(cone_b.axis)) {
|
||||
return cone_a;
|
||||
}
|
||||
|
||||
/* Set cone a to always have the greater theta_o. */
|
||||
const OrientationBounds *a = &cone_a;
|
||||
const OrientationBounds *b = &cone_b;
|
||||
if (cone_b.theta_o > cone_a.theta_o) {
|
||||
a = &cone_b;
|
||||
b = &cone_a;
|
||||
}
|
||||
|
||||
float theta_d = safe_acosf(dot(a->axis, b->axis));
|
||||
float theta_e = fmaxf(a->theta_e, b->theta_e);
|
||||
|
||||
/* Return axis and theta_o of a if it already contains b. */
|
||||
/* This should also be called when b is empty. */
|
||||
if (a->theta_o >= fminf(M_PI_F, theta_d + b->theta_o)) {
|
||||
return OrientationBounds({a->axis, a->theta_o, theta_e});
|
||||
}
|
||||
|
||||
/* Compute new theta_o that contains both a and b. */
|
||||
float theta_o = (theta_d + a->theta_o + b->theta_o) * 0.5f;
|
||||
|
||||
if (theta_o >= M_PI_F) {
|
||||
return OrientationBounds({a->axis, M_PI_F, theta_e});
|
||||
}
|
||||
|
||||
/* Rotate new axis to be between a and b. */
|
||||
float theta_r = theta_o - a->theta_o;
|
||||
float3 new_axis = rotate_around_axis(a->axis, cross(a->axis, b->axis), theta_r);
|
||||
new_axis = normalize(new_axis);
|
||||
|
||||
return OrientationBounds({new_axis, theta_o, theta_e});
|
||||
}
|
||||
|
||||
LightTreePrimitive::LightTreePrimitive(Scene *scene, int prim_id, int object_id)
|
||||
: prim_id(prim_id), object_id(object_id)
|
||||
{
|
||||
bcone = OrientationBounds::empty;
|
||||
bbox = BoundBox::empty;
|
||||
|
||||
if (is_triangle()) {
|
||||
float3 vertices[3];
|
||||
Object *object = scene->objects[object_id];
|
||||
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
|
||||
Mesh::Triangle triangle = mesh->get_triangle(prim_id);
|
||||
Shader *shader = static_cast<Shader *>(mesh->get_used_shaders()[mesh->get_shader()[prim_id]]);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
vertices[i] = mesh->get_verts()[triangle.v[i]];
|
||||
}
|
||||
|
||||
/* instanced mesh lights have not applied their transform at this point.
|
||||
* in this case, these points have to be transformed to get the proper
|
||||
* spatial bound. */
|
||||
if (!mesh->transform_applied) {
|
||||
const Transform &tfm = object->get_tfm();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
vertices[i] = transform_point(&tfm, vertices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: need a better way to handle this when textures are used. */
|
||||
float area = triangle_area(vertices[0], vertices[1], vertices[2]);
|
||||
energy = area * average(shader->emission_estimate);
|
||||
|
||||
/* NOTE: the original implementation used the bounding box centroid, but primitive centroid
|
||||
* seems to work fine */
|
||||
centroid = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
|
||||
|
||||
if (shader->emission_sampling == EMISSION_SAMPLING_FRONT) {
|
||||
/* Front only. */
|
||||
bcone.axis = safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
bcone.theta_o = 0;
|
||||
}
|
||||
else if (shader->emission_sampling == EMISSION_SAMPLING_BACK) {
|
||||
/* Back only. */
|
||||
bcone.axis = -safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
bcone.theta_o = 0;
|
||||
}
|
||||
else {
|
||||
/* Double sided: any vector in the plane. */
|
||||
bcone.axis = safe_normalize(vertices[0] - vertices[1]);
|
||||
bcone.theta_o = M_PI_2_F;
|
||||
}
|
||||
bcone.theta_e = M_PI_2_F;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
bbox.grow(vertices[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Light *lamp = scene->lights[object_id];
|
||||
LightType type = lamp->get_light_type();
|
||||
const float size = lamp->get_size();
|
||||
float3 strength = lamp->get_strength();
|
||||
|
||||
centroid = scene->lights[object_id]->get_co();
|
||||
bcone.axis = normalize(lamp->get_dir());
|
||||
|
||||
if (type == LIGHT_AREA) {
|
||||
bcone.theta_o = 0;
|
||||
bcone.theta_e = lamp->get_spread() * 0.5f;
|
||||
|
||||
/* For an area light, sizeu and sizev determine the 2 dimensions of the area light,
|
||||
* while axisu and axisv determine the orientation of the 2 dimensions.
|
||||
* We want to add all 4 corners to our bounding box. */
|
||||
const float3 half_extentu = 0.5f * lamp->get_sizeu() * lamp->get_axisu() * size;
|
||||
const float3 half_extentv = 0.5f * lamp->get_sizev() * lamp->get_axisv() * size;
|
||||
bbox.grow(centroid + half_extentu + half_extentv);
|
||||
bbox.grow(centroid + half_extentu - half_extentv);
|
||||
bbox.grow(centroid - half_extentu + half_extentv);
|
||||
bbox.grow(centroid - half_extentu - half_extentv);
|
||||
|
||||
strength *= 0.25f; /* eval_fac scaling in `area.h` */
|
||||
}
|
||||
else if (type == LIGHT_POINT) {
|
||||
bcone.theta_o = M_PI_F;
|
||||
bcone.theta_e = M_PI_2_F;
|
||||
|
||||
/* Point and spot lights can emit light from any point within its radius. */
|
||||
const float3 radius = make_float3(size);
|
||||
bbox.grow(centroid - radius);
|
||||
bbox.grow(centroid + radius);
|
||||
|
||||
strength *= 0.25f * M_1_PI_F; /* eval_fac scaling in `spot.h` and `point.h` */
|
||||
}
|
||||
else if (type == LIGHT_SPOT) {
|
||||
bcone.theta_o = 0;
|
||||
bcone.theta_e = lamp->get_spot_angle() * 0.5f;
|
||||
|
||||
/* Point and spot lights can emit light from any point within its radius. */
|
||||
const float3 radius = make_float3(size);
|
||||
bbox.grow(centroid - radius);
|
||||
bbox.grow(centroid + radius);
|
||||
|
||||
strength *= 0.25f * M_1_PI_F; /* eval_fac scaling in `spot.h` and `point.h` */
|
||||
}
|
||||
else if (type == LIGHT_BACKGROUND) {
|
||||
/* Set an arbitrary direction for the background light. */
|
||||
bcone.axis = make_float3(0.0f, 0.0f, 1.0f);
|
||||
/* TODO: this may depend on portal lights as well. */
|
||||
bcone.theta_o = M_PI_F;
|
||||
bcone.theta_e = 0;
|
||||
|
||||
/* integrate over cosine-weighted hemisphere */
|
||||
strength *= lamp->get_average_radiance() * M_PI_F;
|
||||
}
|
||||
else if (type == LIGHT_DISTANT) {
|
||||
bcone.theta_o = 0;
|
||||
bcone.theta_e = 0.5f * lamp->get_angle();
|
||||
}
|
||||
|
||||
if (lamp->get_shader()) {
|
||||
strength *= lamp->get_shader()->emission_estimate;
|
||||
}
|
||||
|
||||
energy = average(strength);
|
||||
}
|
||||
}
|
||||
|
||||
LightTree::LightTree(vector<LightTreePrimitive> &prims,
|
||||
const int &num_distant_lights,
|
||||
uint max_lights_in_leaf)
|
||||
{
|
||||
if (prims.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
max_lights_in_leaf_ = max_lights_in_leaf;
|
||||
int num_prims = prims.size();
|
||||
int num_local_lights = num_prims - num_distant_lights;
|
||||
/* The amount of nodes is estimated to be twice the amount of primitives */
|
||||
nodes_.reserve(2 * num_prims);
|
||||
|
||||
nodes_.emplace_back(); /* root node */
|
||||
recursive_build(0, num_local_lights, prims, 0, 1); /* build tree */
|
||||
nodes_[0].make_interior(nodes_.size());
|
||||
|
||||
/* All distant lights are grouped to one node (right child of the root node) */
|
||||
OrientationBounds bcone = OrientationBounds::empty;
|
||||
float energy_total = 0.0;
|
||||
for (int i = num_local_lights; i < num_prims; i++) {
|
||||
const LightTreePrimitive &prim = prims.at(i);
|
||||
bcone = merge(bcone, prim.bcone);
|
||||
energy_total += prim.energy;
|
||||
}
|
||||
nodes_.emplace_back(BoundBox::empty, bcone, energy_total, 1);
|
||||
nodes_.back().make_leaf(num_local_lights, num_distant_lights);
|
||||
|
||||
nodes_.shrink_to_fit();
|
||||
}
|
||||
|
||||
const vector<LightTreeNode> &LightTree::get_nodes() const
|
||||
{
|
||||
return nodes_;
|
||||
}
|
||||
|
||||
int LightTree::recursive_build(
|
||||
int start, int end, vector<LightTreePrimitive> &prims, uint bit_trail, int depth)
|
||||
{
|
||||
BoundBox bbox = BoundBox::empty;
|
||||
OrientationBounds bcone = OrientationBounds::empty;
|
||||
BoundBox centroid_bounds = BoundBox::empty;
|
||||
float energy_total = 0.0;
|
||||
int num_prims = end - start;
|
||||
int current_index = nodes_.size();
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
const LightTreePrimitive &prim = prims.at(i);
|
||||
bbox.grow(prim.bbox);
|
||||
bcone = merge(bcone, prim.bcone);
|
||||
centroid_bounds.grow(prim.centroid);
|
||||
|
||||
energy_total += prim.energy;
|
||||
}
|
||||
|
||||
nodes_.emplace_back(bbox, bcone, energy_total, bit_trail);
|
||||
|
||||
bool try_splitting = num_prims > 1 && len(centroid_bounds.size()) > 0.0f;
|
||||
int split_dim = -1, split_bucket = 0, num_left_prims = 0;
|
||||
bool should_split = false;
|
||||
if (try_splitting) {
|
||||
/* Find the best place to split the primitives into 2 nodes.
|
||||
* If the best split cost is no better than making a leaf node, make a leaf instead.*/
|
||||
float min_cost = min_split_saoh(
|
||||
centroid_bounds, start, end, bbox, bcone, split_dim, split_bucket, num_left_prims, prims);
|
||||
should_split = num_prims > max_lights_in_leaf_ || min_cost < energy_total;
|
||||
}
|
||||
if (should_split) {
|
||||
int middle;
|
||||
|
||||
if (split_dim != -1) {
|
||||
/* Partition the primitives between start and end based on the split dimension and bucket
|
||||
* calculated by `split_saoh` */
|
||||
middle = start + num_left_prims;
|
||||
std::nth_element(prims.begin() + start,
|
||||
prims.begin() + middle,
|
||||
prims.begin() + end,
|
||||
[split_dim](const LightTreePrimitive &l, const LightTreePrimitive &r) {
|
||||
return l.centroid[split_dim] < r.centroid[split_dim];
|
||||
});
|
||||
}
|
||||
else {
|
||||
/* Degenerate case with many lights in the same place. */
|
||||
middle = (start + end) / 2;
|
||||
}
|
||||
|
||||
[[maybe_unused]] int left_index = recursive_build(start, middle, prims, bit_trail, depth + 1);
|
||||
int right_index = recursive_build(middle, end, prims, bit_trail | (1u << depth), depth + 1);
|
||||
assert(left_index == current_index + 1);
|
||||
nodes_[current_index].make_interior(right_index);
|
||||
}
|
||||
else {
|
||||
nodes_[current_index].make_leaf(start, num_prims);
|
||||
}
|
||||
return current_index;
|
||||
}
|
||||
|
||||
float LightTree::min_split_saoh(const BoundBox ¢roid_bbox,
|
||||
int start,
|
||||
int end,
|
||||
const BoundBox &bbox,
|
||||
const OrientationBounds &bcone,
|
||||
int &split_dim,
|
||||
int &split_bucket,
|
||||
int &num_left_prims,
|
||||
const vector<LightTreePrimitive> &prims)
|
||||
{
|
||||
/* Even though this factor is used for every bucket, we use it to compare
|
||||
* the min_cost and total_energy (when deciding between creating a leaf or interior node. */
|
||||
const float bbox_area = bbox.area();
|
||||
const bool has_area = bbox_area != 0.0f;
|
||||
const float total_area = has_area ? bbox_area : len(bbox.size());
|
||||
const float total_cost = total_area * bcone.calculate_measure();
|
||||
if (total_cost == 0.0f) {
|
||||
return FLT_MAX;
|
||||
}
|
||||
|
||||
const float inv_total_cost = 1.0f / total_cost;
|
||||
const float3 extent = centroid_bbox.size();
|
||||
const float max_extent = max4(extent.x, extent.y, extent.z, 0.0f);
|
||||
|
||||
/* Check each dimension to find the minimum splitting cost. */
|
||||
float min_cost = FLT_MAX;
|
||||
for (int dim = 0; dim < 3; dim++) {
|
||||
/* If the centroid bounding box is 0 along a given dimension, skip it. */
|
||||
if (centroid_bbox.size()[dim] == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float inv_extent = 1 / (centroid_bbox.size()[dim]);
|
||||
|
||||
/* Fill in buckets with primitives. */
|
||||
vector<LightTreeBucketInfo> buckets(LightTreeBucketInfo::num_buckets);
|
||||
for (int i = start; i < end; i++) {
|
||||
const LightTreePrimitive &prim = prims[i];
|
||||
|
||||
/* Place primitive into the appropriate bucket,
|
||||
* where the centroid box is split into equal partitions. */
|
||||
int bucket_idx = LightTreeBucketInfo::num_buckets *
|
||||
(prim.centroid[dim] - centroid_bbox.min[dim]) * inv_extent;
|
||||
if (bucket_idx == LightTreeBucketInfo::num_buckets) {
|
||||
bucket_idx = LightTreeBucketInfo::num_buckets - 1;
|
||||
}
|
||||
|
||||
buckets[bucket_idx].count++;
|
||||
buckets[bucket_idx].energy += prim.energy;
|
||||
buckets[bucket_idx].bbox.grow(prim.bbox);
|
||||
buckets[bucket_idx].bcone = merge(buckets[bucket_idx].bcone, prim.bcone);
|
||||
}
|
||||
|
||||
/* Calculate the cost of splitting at each point between partitions. */
|
||||
vector<float> bucket_costs(LightTreeBucketInfo::num_buckets - 1);
|
||||
float energy_L, energy_R;
|
||||
BoundBox bbox_L, bbox_R;
|
||||
OrientationBounds bcone_L, bcone_R;
|
||||
for (int split = 0; split < LightTreeBucketInfo::num_buckets - 1; split++) {
|
||||
energy_L = 0;
|
||||
energy_R = 0;
|
||||
bbox_L = BoundBox::empty;
|
||||
bbox_R = BoundBox::empty;
|
||||
bcone_L = OrientationBounds::empty;
|
||||
bcone_R = OrientationBounds::empty;
|
||||
|
||||
for (int left = 0; left <= split; left++) {
|
||||
if (buckets[left].bbox.valid()) {
|
||||
energy_L += buckets[left].energy;
|
||||
bbox_L.grow(buckets[left].bbox);
|
||||
bcone_L = merge(bcone_L, buckets[left].bcone);
|
||||
}
|
||||
}
|
||||
|
||||
for (int right = split + 1; right < LightTreeBucketInfo::num_buckets; right++) {
|
||||
if (buckets[right].bbox.valid()) {
|
||||
energy_R += buckets[right].energy;
|
||||
bbox_R.grow(buckets[right].bbox);
|
||||
bcone_R = merge(bcone_R, buckets[right].bcone);
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate the cost of splitting using the heuristic as described in the paper. */
|
||||
const float area_L = has_area ? bbox_L.area() : len(bbox_L.size());
|
||||
const float area_R = has_area ? bbox_R.area() : len(bbox_R.size());
|
||||
float left = (bbox_L.valid()) ? energy_L * area_L * bcone_L.calculate_measure() : 0.0f;
|
||||
float right = (bbox_R.valid()) ? energy_R * area_R * bcone_R.calculate_measure() : 0.0f;
|
||||
float regularization = max_extent * inv_extent;
|
||||
bucket_costs[split] = regularization * (left + right) * inv_total_cost;
|
||||
|
||||
if (bucket_costs[split] < min_cost) {
|
||||
min_cost = bucket_costs[split];
|
||||
split_dim = dim;
|
||||
split_bucket = split;
|
||||
num_left_prims = 0;
|
||||
for (int i = 0; i <= split_bucket; i++) {
|
||||
num_left_prims += buckets[i].count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return min_cost;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
|
@ -0,0 +1,160 @@
|
|||
/* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2011-2022 Blender Foundation */
|
||||
|
||||
#ifndef __LIGHT_TREE_H__
|
||||
#define __LIGHT_TREE_H__
|
||||
|
||||
#include "scene/light.h"
|
||||
#include "scene/scene.h"
|
||||
|
||||
#include "util/boundbox.h"
|
||||
#include "util/types.h"
|
||||
#include "util/vector.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Orientation Bounds
|
||||
*
|
||||
* Bounds the normal axis of the lights,
|
||||
* along with their emission profiles */
|
||||
struct OrientationBounds {
|
||||
float3 axis; /* normal axis of the light */
|
||||
float theta_o; /* angle bounding the normals */
|
||||
float theta_e; /* angle bounding the light emissions */
|
||||
|
||||
__forceinline OrientationBounds()
|
||||
{
|
||||
}
|
||||
|
||||
__forceinline OrientationBounds(const float3 &axis_, float theta_o_, float theta_e_)
|
||||
: axis(axis_), theta_o(theta_o_), theta_e(theta_e_)
|
||||
{
|
||||
}
|
||||
|
||||
enum empty_t { empty = 0 };
|
||||
|
||||
/* If the orientation bound is set to empty, the values are set to minumums
|
||||
* so that merging it with another non-empty orientation bound guarantees that
|
||||
* the return value is equal to non-empty orientation bound. */
|
||||
__forceinline OrientationBounds(empty_t)
|
||||
: axis(make_float3(0, 0, 0)), theta_o(FLT_MIN), theta_e(FLT_MIN)
|
||||
{
|
||||
}
|
||||
|
||||
float calculate_measure() const;
|
||||
};
|
||||
|
||||
OrientationBounds merge(const OrientationBounds &cone_a, const OrientationBounds &cone_b);
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* Light Tree Construction
|
||||
*
|
||||
* The light tree construction is based on PBRT's BVH construction.
|
||||
*/
|
||||
|
||||
/* Light Tree Primitive
|
||||
* Struct that indexes into the scene's triangle and light arrays. */
|
||||
struct LightTreePrimitive {
|
||||
/* `prim_id >= 0` is an index into an object's local triangle index,
|
||||
* otherwise `-prim_id-1`(`~prim`) is an index into device lights array. */
|
||||
int prim_id;
|
||||
int object_id;
|
||||
|
||||
float energy;
|
||||
float3 centroid;
|
||||
OrientationBounds bcone;
|
||||
BoundBox bbox;
|
||||
|
||||
LightTreePrimitive(Scene *scene, int prim_id, int object_id);
|
||||
|
||||
inline bool is_triangle() const
|
||||
{
|
||||
return prim_id >= 0;
|
||||
};
|
||||
};
|
||||
|
||||
/* Light Tree Bucket Info
|
||||
* Struct used to determine splitting costs in the light BVH. */
|
||||
struct LightTreeBucketInfo {
|
||||
LightTreeBucketInfo()
|
||||
: energy(0.0f), bbox(BoundBox::empty), bcone(OrientationBounds::empty), count(0)
|
||||
{
|
||||
}
|
||||
|
||||
float energy; /* Total energy in the partition */
|
||||
BoundBox bbox;
|
||||
OrientationBounds bcone;
|
||||
int count;
|
||||
|
||||
static const int num_buckets = 12;
|
||||
};
|
||||
|
||||
/* Light Tree Node */
|
||||
struct LightTreeNode {
|
||||
BoundBox bbox;
|
||||
OrientationBounds bcone;
|
||||
float energy;
|
||||
uint bit_trail;
|
||||
int num_prims = -1;
|
||||
union {
|
||||
int first_prim_index; /* leaf nodes contain an index to first primitive. */
|
||||
int right_child_index; /* interior nodes contain an index to second child. */
|
||||
};
|
||||
LightTreeNode() = default;
|
||||
|
||||
LightTreeNode(const BoundBox &bbox,
|
||||
const OrientationBounds &bcone,
|
||||
const float &energy,
|
||||
const uint &bit_trial)
|
||||
: bbox(bbox), bcone(bcone), energy(energy), bit_trail(bit_trial)
|
||||
{
|
||||
}
|
||||
|
||||
void make_leaf(const uint &first_prim_index, const int &num_prims)
|
||||
{
|
||||
this->first_prim_index = first_prim_index;
|
||||
this->num_prims = num_prims;
|
||||
}
|
||||
void make_interior(const int &right_child_index)
|
||||
{
|
||||
this->right_child_index = right_child_index;
|
||||
}
|
||||
|
||||
inline bool is_leaf() const
|
||||
{
|
||||
return num_prims >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/* Light BVH
|
||||
*
|
||||
* BVH-like data structure that keeps track of lights
|
||||
* and considers additional orientation and energy information */
|
||||
class LightTree {
|
||||
vector<LightTreeNode> nodes_;
|
||||
uint max_lights_in_leaf_;
|
||||
|
||||
public:
|
||||
LightTree(vector<LightTreePrimitive> &prims,
|
||||
const int &num_distant_lights,
|
||||
uint max_lights_in_leaf);
|
||||
|
||||
const vector<LightTreeNode> &get_nodes() const;
|
||||
|
||||
private:
|
||||
int recursive_build(
|
||||
int start, int end, vector<LightTreePrimitive> &prims, uint bit_trail, int depth);
|
||||
float min_split_saoh(const BoundBox ¢roid_bbox,
|
||||
int start,
|
||||
int end,
|
||||
const BoundBox &bbox,
|
||||
const OrientationBounds &bcone,
|
||||
int &split_dim,
|
||||
int &split_bucket,
|
||||
int &num_left_prims,
|
||||
const vector<LightTreePrimitive> &prims);
|
||||
};
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
#endif /* __LIGHT_TREE_H__ */
|
|
@ -565,10 +565,12 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s
|
|||
|
||||
void ObjectManager::device_update_prim_offsets(Device *device, DeviceScene *dscene, Scene *scene)
|
||||
{
|
||||
BVHLayoutMask layout_mask = device->get_bvh_layout_mask();
|
||||
if (layout_mask != BVH_LAYOUT_METAL && layout_mask != BVH_LAYOUT_MULTI_METAL &&
|
||||
layout_mask != BVH_LAYOUT_MULTI_METAL_EMBREE) {
|
||||
return;
|
||||
if (!scene->integrator->get_use_light_tree()) {
|
||||
BVHLayoutMask layout_mask = device->get_bvh_layout_mask();
|
||||
if (layout_mask != BVH_LAYOUT_METAL && layout_mask != BVH_LAYOUT_MULTI_METAL &&
|
||||
layout_mask != BVH_LAYOUT_MULTI_METAL_EMBREE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* On MetalRT, primitive / curve segment offsets can't be baked at BVH build time. Intersection
|
||||
|
|
|
@ -52,7 +52,6 @@ const NodeEnum *Pass::get_type_enum()
|
|||
pass_type_enum.insert("emission", PASS_EMISSION);
|
||||
pass_type_enum.insert("background", PASS_BACKGROUND);
|
||||
pass_type_enum.insert("ao", PASS_AO);
|
||||
pass_type_enum.insert("shadow", PASS_SHADOW);
|
||||
pass_type_enum.insert("diffuse", PASS_DIFFUSE);
|
||||
pass_type_enum.insert("diffuse_direct", PASS_DIFFUSE_DIRECT);
|
||||
pass_type_enum.insert("diffuse_indirect", PASS_DIFFUSE_INDIRECT);
|
||||
|
@ -209,10 +208,6 @@ PassInfo Pass::get_info(const PassType type, const bool include_albedo, const bo
|
|||
case PASS_AO:
|
||||
pass_info.num_components = 3;
|
||||
break;
|
||||
case PASS_SHADOW:
|
||||
pass_info.num_components = 3;
|
||||
pass_info.use_exposure = false;
|
||||
break;
|
||||
|
||||
case PASS_DIFFUSE_COLOR:
|
||||
case PASS_GLOSSY_COLOR:
|
||||
|
|
|
@ -71,6 +71,11 @@ DeviceScene::DeviceScene(Device *device)
|
|||
lights(device, "lights", MEM_GLOBAL),
|
||||
light_background_marginal_cdf(device, "light_background_marginal_cdf", MEM_GLOBAL),
|
||||
light_background_conditional_cdf(device, "light_background_conditional_cdf", MEM_GLOBAL),
|
||||
light_tree_nodes(device, "light_tree_nodes", MEM_GLOBAL),
|
||||
light_tree_emitters(device, "light_tree_emitters", MEM_GLOBAL),
|
||||
light_to_tree(device, "light_to_tree", MEM_GLOBAL),
|
||||
object_lookup_offset(device, "object_lookup_offset", MEM_GLOBAL),
|
||||
triangle_to_tree(device, "triangle_to_tree", MEM_GLOBAL),
|
||||
particles(device, "particles", MEM_GLOBAL),
|
||||
svm_nodes(device, "svm_nodes", MEM_GLOBAL),
|
||||
shaders(device, "shaders", MEM_GLOBAL),
|
||||
|
|
|
@ -111,6 +111,13 @@ class DeviceScene {
|
|||
device_vector<float2> light_background_marginal_cdf;
|
||||
device_vector<float2> light_background_conditional_cdf;
|
||||
|
||||
/* light tree */
|
||||
device_vector<KernelLightTreeNode> light_tree_nodes;
|
||||
device_vector<KernelLightTreeEmitter> light_tree_emitters;
|
||||
device_vector<uint> light_to_tree;
|
||||
device_vector<uint> object_lookup_offset;
|
||||
device_vector<uint> triangle_to_tree;
|
||||
|
||||
/* particles */
|
||||
device_vector<KernelParticle> particles;
|
||||
|
||||
|
|
|
@ -1195,7 +1195,7 @@ int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_context,
|
|||
#ifdef WITH_VULKAN_BACKEND
|
||||
|
||||
/**
|
||||
* Return vulkan handles for the given context.
|
||||
* Return VULKAN handles for the given context.
|
||||
*/
|
||||
void GHOST_GetVulkanHandles(GHOST_ContextHandle context,
|
||||
void *r_instance,
|
||||
|
@ -1204,7 +1204,7 @@ void GHOST_GetVulkanHandles(GHOST_ContextHandle context,
|
|||
uint32_t *r_graphic_queue_familly);
|
||||
|
||||
/**
|
||||
* Return vulkan backbuffer resources handles for the given window.
|
||||
* Return VULKAN back-buffer resources handles for the given window.
|
||||
*/
|
||||
void GHOST_GetVulkanBackbuffer(GHOST_WindowHandle windowhandle,
|
||||
void *image,
|
||||
|
|
|
@ -1199,9 +1199,6 @@ int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_contexthandle,
|
|||
|
||||
#ifdef WITH_VULKAN_BACKEND
|
||||
|
||||
/**
|
||||
* Return vulkan handles for the given context.
|
||||
*/
|
||||
void GHOST_GetVulkanHandles(GHOST_ContextHandle contexthandle,
|
||||
void *r_instance,
|
||||
void *r_physical_device,
|
||||
|
@ -1212,9 +1209,6 @@ void GHOST_GetVulkanHandles(GHOST_ContextHandle contexthandle,
|
|||
context->getVulkanHandles(r_instance, r_physical_device, r_device, r_graphic_queue_familly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return vulkan backbuffer resources handles for the given window.
|
||||
*/
|
||||
void GHOST_GetVulkanBackbuffer(GHOST_WindowHandle windowhandle,
|
||||
void *image,
|
||||
void *framebuffer,
|
||||
|
@ -1227,4 +1221,4 @@ void GHOST_GetVulkanBackbuffer(GHOST_WindowHandle windowhandle,
|
|||
window->getVulkanBackbuffer(image, framebuffer, command_buffer, render_pass, extent, fb_id);
|
||||
}
|
||||
|
||||
#endif /* WITH_VULKAN */
|
||||
#endif /* WITH_VULKAN */
|
||||
|
|
|
@ -101,7 +101,7 @@ bool win32_chk(bool result, const char *file, int line, const char *text)
|
|||
|
||||
# ifndef NDEBUG
|
||||
_ftprintf(
|
||||
stderr, "%s(%d):[%s] -> Win32 Error# (%lu): %s", file, line, text, ulong(error), msg);
|
||||
stderr, "%s:%d: [%s] -> Win32 Error# (%lu): %s", file, line, text, ulong(error), msg);
|
||||
# else
|
||||
_ftprintf(stderr, "Win32 Error# (%lu): %s", ulong(error), msg);
|
||||
# endif
|
||||
|
|
|
@ -123,7 +123,7 @@ static bool egl_chk(bool result,
|
|||
|
||||
#ifndef NDEBUG
|
||||
fprintf(stderr,
|
||||
"%s(%d):[%s] -> EGL Error (0x%04X): %s: %s\n",
|
||||
"%s:%d: [%s] -> EGL Error (0x%04X): %s: %s\n",
|
||||
file,
|
||||
line,
|
||||
text,
|
||||
|
|
|
@ -100,7 +100,7 @@ static const char *vulkan_error_as_string(VkResult result)
|
|||
printf(__VA_ARGS__); \
|
||||
}
|
||||
|
||||
/* Tripple buffering. */
|
||||
/* Triple buffering. */
|
||||
const int MAX_FRAMES_IN_FLIGHT = 2;
|
||||
|
||||
GHOST_ContextVK::GHOST_ContextVK(bool stereoVisual,
|
||||
|
@ -220,7 +220,7 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers()
|
|||
&m_currentImage);
|
||||
|
||||
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
|
||||
/* Swapchain is out of date. Recreate swapchain and skip this frame. */
|
||||
/* Swap-chain is out of date. Recreate swap-chain and skip this frame. */
|
||||
destroySwapchain();
|
||||
createSwapchain();
|
||||
return GHOST_kSuccess;
|
||||
|
@ -271,7 +271,7 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers()
|
|||
result = vkQueuePresentKHR(m_present_queue, &present_info);
|
||||
|
||||
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
|
||||
/* Swapchain is out of date. Recreate swapchain and skip this frame. */
|
||||
/* Swap-chain is out of date. Recreate swap-chain and skip this frame. */
|
||||
destroySwapchain();
|
||||
createSwapchain();
|
||||
return GHOST_kSuccess;
|
||||
|
@ -932,7 +932,7 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext()
|
|||
present_queue_create_info.queueCount = 1;
|
||||
present_queue_create_info.pQueuePriorities = queue_priorities;
|
||||
|
||||
/* Eash queue must be unique. */
|
||||
/* Each queue must be unique. */
|
||||
if (m_queue_family_graphic != m_queue_family_present) {
|
||||
queue_create_infos.push_back(present_queue_create_info);
|
||||
}
|
||||
|
|
|
@ -1049,9 +1049,9 @@ void GHOST_SystemWin32::processPointerEvent(
|
|||
}
|
||||
}
|
||||
|
||||
GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window)
|
||||
GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window,
|
||||
const int32_t screen_co[2])
|
||||
{
|
||||
int32_t x_screen, y_screen;
|
||||
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
|
||||
|
||||
if (window->getTabletData().Active != GHOST_kTabletModeNone) {
|
||||
|
@ -1059,8 +1059,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
|
|||
return NULL;
|
||||
}
|
||||
|
||||
system->getCursorPosition(x_screen, y_screen);
|
||||
|
||||
int32_t x_screen = screen_co[0], y_screen = screen_co[1];
|
||||
if (window->getCursorGrabModeIsWarp()) {
|
||||
/* WORKAROUND:
|
||||
* Sometimes Windows ignores `SetCursorPos()` or `SendInput()` calls or the mouse event is
|
||||
|
@ -1834,7 +1833,10 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam,
|
|||
}
|
||||
}
|
||||
|
||||
event = processCursorEvent(window);
|
||||
const int32_t window_co[2] = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
|
||||
int32_t screen_co[2];
|
||||
window->clientToScreen(UNPACK2(window_co), UNPACK2(screen_co));
|
||||
event = processCursorEvent(window, screen_co);
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -1876,7 +1878,10 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam,
|
|||
WINTAB_PRINTF("HWND %p mouse leave\n", window->getHWND());
|
||||
window->m_mousePresent = false;
|
||||
if (window->getTabletData().Active == GHOST_kTabletModeNone) {
|
||||
event = processCursorEvent(window);
|
||||
/* FIXME: document why the cursor motion event on mouse leave is needed. */
|
||||
int32_t screen_co[2] = {0, 0};
|
||||
system->getCursorPosition(screen_co[0], screen_co[1]);
|
||||
event = processCursorEvent(window, screen_co);
|
||||
}
|
||||
GHOST_Wintab *wt = window->getWintab();
|
||||
if (wt) {
|
||||
|
|
|
@ -331,7 +331,8 @@ class GHOST_SystemWin32 : public GHOST_System {
|
|||
* \param window: The window receiving the event (the active window).
|
||||
* \return The event created.
|
||||
*/
|
||||
static GHOST_EventCursor *processCursorEvent(GHOST_WindowWin32 *window);
|
||||
static GHOST_EventCursor *processCursorEvent(GHOST_WindowWin32 *window,
|
||||
const int32_t screen_co[2]);
|
||||
|
||||
/**
|
||||
* Handles a mouse wheel event.
|
||||
|
|
|
@ -9,7 +9,7 @@ __all__ = (
|
|||
)
|
||||
|
||||
import bpy
|
||||
from typing import Mapping, List, Tuple, Sequence
|
||||
from typing import Mapping, List, Tuple, Sequence
|
||||
|
||||
# (fcurve.data_path, fcurve.array_index)
|
||||
FCurveKey = Tuple[str, int]
|
||||
|
@ -17,6 +17,7 @@ FCurveKey = Tuple[str, int]
|
|||
ListKeyframes = List[float]
|
||||
Action = bpy.types.Action
|
||||
|
||||
|
||||
def bake_action(
|
||||
obj,
|
||||
*,
|
||||
|
@ -430,6 +431,7 @@ def bake_action_iter(
|
|||
|
||||
yield action
|
||||
|
||||
|
||||
class KeyframesCo:
|
||||
"""A buffer for keyframe Co unpacked values per FCurveKey. FCurveKeys are added using
|
||||
add_paths(), Co values stored using extend_co_values(), then finally use
|
||||
|
|
|
@ -2137,9 +2137,6 @@ def km_node_editor(params):
|
|||
)),
|
||||
("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
|
||||
("transform.resize", {"type": 'S', "value": 'PRESS'}, None),
|
||||
("node.move_detach_links",
|
||||
{"type": 'D', "value": 'PRESS', "alt": True},
|
||||
{"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
|
||||
("node.move_detach_links_release",
|
||||
{"type": params.action_mouse, "value": 'CLICK_DRAG', "alt": True},
|
||||
{"properties": [("NODE_OT_translate_attach", [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])])]}),
|
||||
|
|
|
@ -112,7 +112,10 @@ class ANIM_OT_keying_set_export(Operator):
|
|||
break
|
||||
|
||||
if not found:
|
||||
self.report({'WARN'}, tip_("Could not find material or light using Shader Node Tree - %s") % (ksp.id))
|
||||
self.report(
|
||||
{'WARN'},
|
||||
tip_("Could not find material or light using Shader Node Tree - %s") %
|
||||
(ksp.id))
|
||||
elif ksp.id.bl_rna.identifier.startswith("CompositorNodeTree"):
|
||||
# Find compositor nodetree using this node tree...
|
||||
for scene in bpy.data.scenes:
|
||||
|
@ -335,7 +338,7 @@ class ClearUselessActions(Operator):
|
|||
removed += 1
|
||||
|
||||
self.report({'INFO'}, tip_("Removed %d empty and/or fake-user only Actions")
|
||||
% removed)
|
||||
% removed)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
|
|
|
@ -3083,7 +3083,13 @@ class WM_MT_splash_quick_setup(Menu):
|
|||
|
||||
old_version = bpy.types.PREFERENCES_OT_copy_prev.previous_version()
|
||||
if bpy.types.PREFERENCES_OT_copy_prev.poll(context) and old_version:
|
||||
sub.operator("preferences.copy_prev", text=iface_("Load %d.%d Settings", "Operator") % old_version, translate=False)
|
||||
sub.operator(
|
||||
"preferences.copy_prev",
|
||||
text=iface_(
|
||||
"Load %d.%d Settings",
|
||||
"Operator") %
|
||||
old_version,
|
||||
translate=False)
|
||||
sub.operator("wm.save_userpref", text="Save New Settings")
|
||||
else:
|
||||
sub.label()
|
||||
|
|
|
@ -88,8 +88,16 @@ class CURVES_UL_attributes(UIList):
|
|||
flags = []
|
||||
indices = [i for i in range(len(attributes))]
|
||||
|
||||
for item in attributes:
|
||||
flags.append(0 if item.is_internal else self.bitflag_filter_item)
|
||||
# Filtering by name
|
||||
if self.filter_name:
|
||||
flags = bpy.types.UI_UL_list.filter_items_by_name(
|
||||
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
|
||||
if not flags:
|
||||
flags = [self.bitflag_filter_item] * len(attributes)
|
||||
|
||||
# Filtering internal attributes
|
||||
for idx, item in enumerate(attributes):
|
||||
flags[idx] = 0 if item.is_internal else flags[idx]
|
||||
|
||||
return flags, indices
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ from bpy.app.translations import (
|
|||
pgettext_tip as tip_,
|
||||
)
|
||||
|
||||
|
||||
class MESH_MT_vertex_group_context_menu(Menu):
|
||||
bl_label = "Vertex Group Specials"
|
||||
|
||||
|
@ -536,8 +537,16 @@ class MESH_UL_attributes(UIList):
|
|||
flags = []
|
||||
indices = [i for i in range(len(attributes))]
|
||||
|
||||
for item in attributes:
|
||||
flags.append(0 if item.is_internal else self.bitflag_filter_item)
|
||||
# Filtering by name
|
||||
if self.filter_name:
|
||||
flags = bpy.types.UI_UL_list.filter_items_by_name(
|
||||
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
|
||||
if not flags:
|
||||
flags = [self.bitflag_filter_item] * len(attributes)
|
||||
|
||||
# Filtering internal attributes
|
||||
for idx, item in enumerate(attributes):
|
||||
flags[idx] = 0 if item.is_internal else flags[idx]
|
||||
|
||||
return flags, indices
|
||||
|
||||
|
@ -625,20 +634,26 @@ class ColorAttributesListBase():
|
|||
}
|
||||
|
||||
def filter_items(self, _context, data, property):
|
||||
attrs = getattr(data, property)
|
||||
ret = []
|
||||
idxs = []
|
||||
attributes = getattr(data, property)
|
||||
flags = []
|
||||
indices = [i for i in range(len(attributes))]
|
||||
|
||||
for idx, item in enumerate(attrs):
|
||||
# Filtering by name
|
||||
if self.filter_name:
|
||||
flags = bpy.types.UI_UL_list.filter_items_by_name(
|
||||
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
|
||||
if not flags:
|
||||
flags = [self.bitflag_filter_item] * len(attributes)
|
||||
|
||||
for idx, item in enumerate(attributes):
|
||||
skip = (
|
||||
(item.domain not in {"POINT", "CORNER"}) or
|
||||
(item.data_type not in {"FLOAT_COLOR", "BYTE_COLOR"}) or
|
||||
item.is_internal
|
||||
)
|
||||
ret.append(0 if skip else self.bitflag_filter_item)
|
||||
idxs.append(idx)
|
||||
flags[idx] = 0 if skip else flags[idx]
|
||||
|
||||
return ret, idxs
|
||||
return flags, indices
|
||||
|
||||
|
||||
class MESH_UL_color_attributes(UIList, ColorAttributesListBase):
|
||||
|
|
|
@ -70,8 +70,16 @@ class POINTCLOUD_UL_attributes(UIList):
|
|||
flags = []
|
||||
indices = [i for i in range(len(attributes))]
|
||||
|
||||
for item in attributes:
|
||||
flags.append(0 if item.is_internal else self.bitflag_filter_item)
|
||||
# Filtering by name
|
||||
if self.filter_name:
|
||||
flags = bpy.types.UI_UL_list.filter_items_by_name(
|
||||
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
|
||||
if not flags:
|
||||
flags = [self.bitflag_filter_item] * len(attributes)
|
||||
|
||||
# Filtering internal attributes
|
||||
for idx, item in enumerate(attributes):
|
||||
flags[idx] = 0 if item.is_internal else flags[idx]
|
||||
|
||||
return flags, indices
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ class WORKSPACE_PT_addons(WorkSpaceButtonsPanel, Panel):
|
|||
row.operator(
|
||||
"wm.owner_disable" if is_enabled else "wm.owner_enable",
|
||||
icon='CHECKBOX_HLT' if is_enabled else 'CHECKBOX_DEHLT',
|
||||
text=iface_("%s: %s" ) % (iface_(info["category"]), iface_(info["name"])),
|
||||
text=iface_("%s: %s") % (iface_(info["category"]), iface_(info["name"])),
|
||||
translate=False,
|
||||
emboss=False,
|
||||
).owner_id = module_name
|
||||
|
|
|
@ -307,8 +307,10 @@ class NODE_MT_select(Menu):
|
|||
class NODE_MT_node(Menu):
|
||||
bl_label = "Node"
|
||||
|
||||
def draw(self, _context):
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
snode = context.space_data
|
||||
is_compositor = snode.tree_type == 'CompositorNodeTree'
|
||||
|
||||
layout.operator("transform.translate")
|
||||
layout.operator("transform.rotate")
|
||||
|
@ -346,14 +348,16 @@ class NODE_MT_node(Menu):
|
|||
|
||||
layout.operator("node.hide_toggle")
|
||||
layout.operator("node.mute_toggle")
|
||||
layout.operator("node.preview_toggle")
|
||||
if is_compositor:
|
||||
layout.operator("node.preview_toggle")
|
||||
layout.operator("node.hide_socket_toggle")
|
||||
layout.operator("node.options_toggle")
|
||||
layout.operator("node.collapse_hide_unused_toggle")
|
||||
|
||||
layout.separator()
|
||||
if is_compositor:
|
||||
layout.separator()
|
||||
|
||||
layout.operator("node.read_viewlayers")
|
||||
layout.operator("node.read_viewlayers")
|
||||
|
||||
|
||||
class NODE_MT_view_pie(Menu):
|
||||
|
|
|
@ -285,7 +285,6 @@ template<typename T> class SimpleMixer {
|
|||
*/
|
||||
void set(const int64_t index, const T &value, const float weight = 1.0f)
|
||||
{
|
||||
BLI_assert(weight >= 0.0f);
|
||||
buffer_[index] = value * weight;
|
||||
total_weights_[index] = weight;
|
||||
}
|
||||
|
@ -295,7 +294,6 @@ template<typename T> class SimpleMixer {
|
|||
*/
|
||||
void mix_in(const int64_t index, const T &value, const float weight = 1.0f)
|
||||
{
|
||||
BLI_assert(weight >= 0.0f);
|
||||
buffer_[index] += value * weight;
|
||||
total_weights_[index] += weight;
|
||||
}
|
||||
|
|
|
@ -507,7 +507,7 @@ struct bNodeTree *ntreeFromID(struct ID *id);
|
|||
|
||||
void ntreeFreeLocalNode(struct bNodeTree *ntree, struct bNode *node);
|
||||
void ntreeFreeLocalTree(struct bNodeTree *ntree);
|
||||
struct bNode *ntreeFindType(const struct bNodeTree *ntree, int type);
|
||||
struct bNode *ntreeFindType(struct bNodeTree *ntree, int type);
|
||||
bool ntreeHasTree(const struct bNodeTree *ntree, const struct bNodeTree *lookup);
|
||||
void ntreeUpdateAllNew(struct Main *main);
|
||||
void ntreeUpdateAllUsers(struct Main *main, struct ID *id);
|
||||
|
@ -629,7 +629,7 @@ const char *nodeStaticSocketLabel(int type, int subtype);
|
|||
} \
|
||||
((void)0)
|
||||
|
||||
struct bNodeSocket *nodeFindSocket(const struct bNode *node,
|
||||
struct bNodeSocket *nodeFindSocket(struct bNode *node,
|
||||
eNodeSocketInOut in_out,
|
||||
const char *identifier);
|
||||
struct bNodeSocket *nodeAddSocket(struct bNodeTree *ntree,
|
||||
|
@ -670,6 +670,13 @@ void nodeUnlinkNode(struct bNodeTree *ntree, struct bNode *node);
|
|||
void nodeUniqueName(struct bNodeTree *ntree, struct bNode *node);
|
||||
void nodeUniqueID(struct bNodeTree *ntree, struct bNode *node);
|
||||
|
||||
/**
|
||||
* Rebuild the `node_by_id` runtime vector set. Call after removing a node if not handled
|
||||
* separately. This is important instead of just using `nodes_by_id.remove()` since it maintains
|
||||
* the node order.
|
||||
*/
|
||||
void nodeRebuildIDVector(struct bNodeTree *node_tree);
|
||||
|
||||
/**
|
||||
* Delete node, associated animation data and ID user count.
|
||||
*/
|
||||
|
|
|
@ -212,9 +212,6 @@ class bNodeRuntime : NonCopyable, NonMovable {
|
|||
/** #eNodeTreeChangedFlag. */
|
||||
uint32_t changed_flag = 0;
|
||||
|
||||
/** For dependency and sorting. */
|
||||
short done = 0;
|
||||
|
||||
/** Used as a boolean for execution. */
|
||||
uint8_t need_exec = 0;
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ void ColorGeometry4fMixer::set(const int64_t index,
|
|||
const ColorGeometry4f &color,
|
||||
const float weight)
|
||||
{
|
||||
BLI_assert(weight >= 0.0f);
|
||||
buffer_[index].r = color.r * weight;
|
||||
buffer_[index].g = color.g * weight;
|
||||
buffer_[index].b = color.b * weight;
|
||||
|
@ -35,7 +34,6 @@ void ColorGeometry4fMixer::mix_in(const int64_t index,
|
|||
const ColorGeometry4f &color,
|
||||
const float weight)
|
||||
{
|
||||
BLI_assert(weight >= 0.0f);
|
||||
ColorGeometry4f &output_color = buffer_[index];
|
||||
output_color.r += color.r * weight;
|
||||
output_color.g += color.g * weight;
|
||||
|
@ -89,7 +87,6 @@ void ColorGeometry4bMixer::ColorGeometry4bMixer::set(int64_t index,
|
|||
const ColorGeometry4b &color,
|
||||
const float weight)
|
||||
{
|
||||
BLI_assert(weight >= 0.0f);
|
||||
accumulation_buffer_[index][0] = color.r * weight;
|
||||
accumulation_buffer_[index][1] = color.g * weight;
|
||||
accumulation_buffer_[index][2] = color.b * weight;
|
||||
|
@ -99,7 +96,6 @@ void ColorGeometry4bMixer::ColorGeometry4bMixer::set(int64_t index,
|
|||
|
||||
void ColorGeometry4bMixer::mix_in(int64_t index, const ColorGeometry4b &color, float weight)
|
||||
{
|
||||
BLI_assert(weight >= 0.0f);
|
||||
float4 &accum_value = accumulation_buffer_[index];
|
||||
accum_value[0] += color.r * weight;
|
||||
accum_value[1] += color.g * weight;
|
||||
|
|
|
@ -242,8 +242,8 @@ static void movieclip_blend_write(BlendWriter *writer, ID *id, const void *id_ad
|
|||
|
||||
LISTBASE_FOREACH (MovieTrackingObject *, object, &tracking->objects) {
|
||||
#if USE_LEGACY_CAMERA_OBJECT_FORMAT_ON_SAVE
|
||||
/* When saving camers object in the legacy format clear the list of tracks. This is because the
|
||||
* tracking object code is generic and assumes object owns the tracks in the list. For the
|
||||
/* When saving cameras object in the legacy format clear the list of tracks. This is because
|
||||
* the tracking object code is generic and assumes object owns the tracks in the list. For the
|
||||
* camera tracks that is not the case in the legacy format. */
|
||||
if (!is_undo && (object->flag & TRACKING_OBJECT_CAMERA)) {
|
||||
MovieTrackingObject legacy_object = *object;
|
||||
|
|
|
@ -1455,7 +1455,7 @@ const char *nodeSocketTypeLabel(const bNodeSocketType *stype)
|
|||
return stype->label[0] != '\0' ? stype->label : RNA_struct_ui_name(stype->ext_socket.srna);
|
||||
}
|
||||
|
||||
bNodeSocket *nodeFindSocket(const bNode *node, eNodeSocketInOut in_out, const char *identifier)
|
||||
bNodeSocket *nodeFindSocket(bNode *node, eNodeSocketInOut in_out, const char *identifier)
|
||||
{
|
||||
const ListBase *sockets = (in_out == SOCK_IN) ? &node->inputs : &node->outputs;
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, sockets) {
|
||||
|
@ -2933,12 +2933,12 @@ static void node_unlink_attached(bNodeTree *ntree, bNode *parent)
|
|||
}
|
||||
}
|
||||
|
||||
static void rebuild_nodes_vector(bNodeTree &node_tree)
|
||||
void nodeRebuildIDVector(bNodeTree *node_tree)
|
||||
{
|
||||
/* Rebuild nodes #VectorSet which must have the same order as the list. */
|
||||
node_tree.runtime->nodes_by_id.clear();
|
||||
LISTBASE_FOREACH (bNode *, node, &node_tree.nodes) {
|
||||
node_tree.runtime->nodes_by_id.add_new(node);
|
||||
node_tree->runtime->nodes_by_id.clear();
|
||||
LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) {
|
||||
node_tree->runtime->nodes_by_id.add_new(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2955,7 +2955,7 @@ static void node_free_node(bNodeTree *ntree, bNode *node)
|
|||
if (ntree) {
|
||||
BLI_remlink(&ntree->nodes, node);
|
||||
/* Rebuild nodes #VectorSet which must have the same order as the list. */
|
||||
rebuild_nodes_vector(*ntree);
|
||||
nodeRebuildIDVector(ntree);
|
||||
|
||||
/* texture node has bad habit of keeping exec data around */
|
||||
if (ntree->type == NTREE_TEXTURE && ntree->runtime->execdata) {
|
||||
|
@ -3013,7 +3013,7 @@ void ntreeFreeLocalNode(bNodeTree *ntree, bNode *node)
|
|||
node_unlink_attached(ntree, node);
|
||||
|
||||
node_free_node(ntree, node);
|
||||
rebuild_nodes_vector(*ntree);
|
||||
nodeRebuildIDVector(ntree);
|
||||
}
|
||||
|
||||
void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user)
|
||||
|
@ -3073,7 +3073,7 @@ void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user)
|
|||
|
||||
/* Free node itself. */
|
||||
node_free_node(ntree, node);
|
||||
rebuild_nodes_vector(*ntree);
|
||||
nodeRebuildIDVector(ntree);
|
||||
}
|
||||
|
||||
static void node_socket_interface_free(bNodeTree * /*ntree*/,
|
||||
|
@ -3437,7 +3437,7 @@ void ntreeRemoveSocketInterface(bNodeTree *ntree, bNodeSocket *sock)
|
|||
|
||||
/* ************ find stuff *************** */
|
||||
|
||||
bNode *ntreeFindType(const bNodeTree *ntree, int type)
|
||||
bNode *ntreeFindType(bNodeTree *ntree, int type)
|
||||
{
|
||||
if (ntree) {
|
||||
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
|
||||
|
|
|
@ -499,7 +499,7 @@ static std::optional<UVBorderCorner> sharpest_border_corner(UVIsland &island)
|
|||
/** The inner edge of a fan. */
|
||||
struct InnerEdge {
|
||||
MeshPrimitive *primitive;
|
||||
/* UVs order are already applied. So uvs[0] mathes primitive->vertices[vert_order[0]]/ */
|
||||
/* UVs order are already applied. So `uvs[0]` matches `primitive->vertices[vert_order[0]]`. */
|
||||
float2 uvs[3];
|
||||
int vert_order[3];
|
||||
|
||||
|
@ -1198,7 +1198,7 @@ UVBorder UVPrimitive::extract_border() const
|
|||
{
|
||||
Vector<UVBorderEdge> border_edges;
|
||||
for (UVEdge *edge : edges) {
|
||||
/* TODO remove const cast. only needed for debugging atm. */
|
||||
/* TODO remove const cast. only needed for debugging ATM. */
|
||||
UVBorderEdge border_edge(edge, const_cast<UVPrimitive *>(this));
|
||||
border_edges.append(border_edge);
|
||||
}
|
||||
|
@ -1227,9 +1227,6 @@ const UVVertex *UVBorderEdge::get_uv_vertex(int index) const
|
|||
return edge->vertices[actual_index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the uv vertex from the primitive that is not part of the edge.
|
||||
*/
|
||||
const UVVertex *UVBorderEdge::get_other_uv_vertex() const
|
||||
{
|
||||
return uv_primitive->get_other_uv_vertex(edge->vertices[0], edge->vertices[1]);
|
||||
|
|
|
@ -83,11 +83,11 @@ void tracking_set_marker_coords_from_tracking(int frame_width,
|
|||
* Convert the lens principal point (optical center) between normalized and pixel spaces.
|
||||
*
|
||||
* The normalized space stores principal point relative to the frame center which has normalized
|
||||
* princibal coordinate of (0, 0). The right top corder of the frame corresponds to a notmalized
|
||||
* principal coordinate of (1, 1), and the left bottom cornder corresponds to coordinate of
|
||||
* principal coordinate of (0, 0). The right top corner of the frame corresponds to a normalized
|
||||
* principal coordinate of (1, 1), and the left bottom corner corresponds to coordinate of
|
||||
* (-1, -1).
|
||||
*
|
||||
* The pixel space is measured in pixels, with the reference being the left bottom cornder of
|
||||
* The pixel space is measured in pixels, with the reference being the left bottom corner of
|
||||
* the frame.
|
||||
*/
|
||||
void tracking_principal_point_normalized_to_pixel(const float principal_point_normalized[2],
|
||||
|
|
|
@ -132,7 +132,7 @@ class AtomicDisjointSet {
|
|||
|
||||
/**
|
||||
* Get an identifier for each id. This is deterministic and does not depend on the order of
|
||||
* joins. The ids are ordered by their first occurence. Consequently, `result[0]` is always zero
|
||||
* joins. The ids are ordered by their first occurrence. Consequently, `result[0]` is always zero
|
||||
* (unless there are no elements).
|
||||
*/
|
||||
void calc_reduced_ids(MutableSpan<int> result) const;
|
||||
|
|
|
@ -36,9 +36,9 @@ uint BLI_ghashutil_ptrhash(const void *key)
|
|||
|
||||
size_t y = (size_t)key;
|
||||
/* bottom 3 or 4 bits are likely to be 0; rotate y by 4 to avoid
|
||||
* excessive hash collisions for dicts and sets */
|
||||
* excessive hash collisions for dictionaries and sets */
|
||||
|
||||
/* NOTE: Unlike Python 'sizeof(uint)' is used instead of 'sizeof(void *)',
|
||||
/* NOTE: Unlike Python `sizeof(uint)` is used instead of `sizeof(void *)`,
|
||||
* Otherwise casting to 'uint' ignores the upper bits on 64bit platforms. */
|
||||
return (uint)(y >> 4) | ((uint)y << (sizeof(uint[8]) - 4));
|
||||
}
|
||||
|
|
|
@ -17,14 +17,14 @@ AtomicDisjointSet::AtomicDisjointSet(const int size) : items_(size)
|
|||
});
|
||||
}
|
||||
|
||||
static void update_first_occurence(Map<int, int> &map, const int root, const int index)
|
||||
static void update_first_occurrence(Map<int, int> &map, const int root, const int index)
|
||||
{
|
||||
map.add_or_modify(
|
||||
root,
|
||||
[&](int *first_occurence) { *first_occurence = index; },
|
||||
[&](int *first_occurence) {
|
||||
if (index < *first_occurence) {
|
||||
*first_occurence = index;
|
||||
[&](int *first_occurrence) { *first_occurrence = index; },
|
||||
[&](int *first_occurrence) {
|
||||
if (index < *first_occurrence) {
|
||||
*first_occurrence = index;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -37,49 +37,49 @@ void AtomicDisjointSet::calc_reduced_ids(MutableSpan<int> result) const
|
|||
|
||||
/* Find the root for element. With multi-threading, this root is not deterministic. So
|
||||
* some postprocessing has to be done to make it deterministic. */
|
||||
threading::EnumerableThreadSpecific<Map<int, int>> first_occurence_by_root_per_thread;
|
||||
threading::EnumerableThreadSpecific<Map<int, int>> first_occurrence_by_root_per_thread;
|
||||
threading::parallel_for(IndexRange(size), 1024, [&](const IndexRange range) {
|
||||
Map<int, int> &first_occurence_by_root = first_occurence_by_root_per_thread.local();
|
||||
Map<int, int> &first_occurrence_by_root = first_occurrence_by_root_per_thread.local();
|
||||
for (const int i : range) {
|
||||
const int root = this->find_root(i);
|
||||
result[i] = root;
|
||||
update_first_occurence(first_occurence_by_root, root, i);
|
||||
update_first_occurrence(first_occurrence_by_root, root, i);
|
||||
}
|
||||
});
|
||||
|
||||
/* Build a map that contains the first element index that has a certain root. */
|
||||
Map<int, int> &combined_map = first_occurence_by_root_per_thread.local();
|
||||
for (const Map<int, int> &other_map : first_occurence_by_root_per_thread) {
|
||||
Map<int, int> &combined_map = first_occurrence_by_root_per_thread.local();
|
||||
for (const Map<int, int> &other_map : first_occurrence_by_root_per_thread) {
|
||||
if (&combined_map == &other_map) {
|
||||
continue;
|
||||
}
|
||||
for (const auto item : other_map.items()) {
|
||||
update_first_occurence(combined_map, item.key, item.value);
|
||||
update_first_occurrence(combined_map, item.key, item.value);
|
||||
}
|
||||
}
|
||||
|
||||
struct RootOccurence {
|
||||
int root;
|
||||
int first_occurence;
|
||||
int first_occurrence;
|
||||
};
|
||||
|
||||
/* Sort roots by first occurence. This removes the non-determinism above. */
|
||||
Vector<RootOccurence, 16> root_occurences;
|
||||
root_occurences.reserve(combined_map.size());
|
||||
/* Sort roots by first occurrence. This removes the non-determinism above. */
|
||||
Vector<RootOccurence, 16> root_occurrences;
|
||||
root_occurrences.reserve(combined_map.size());
|
||||
for (const auto item : combined_map.items()) {
|
||||
root_occurences.append({item.key, item.value});
|
||||
root_occurrences.append({item.key, item.value});
|
||||
}
|
||||
parallel_sort(root_occurences.begin(),
|
||||
root_occurences.end(),
|
||||
parallel_sort(root_occurrences.begin(),
|
||||
root_occurrences.end(),
|
||||
[](const RootOccurence &a, const RootOccurence &b) {
|
||||
return a.first_occurence < b.first_occurence;
|
||||
return a.first_occurrence < b.first_occurrence;
|
||||
});
|
||||
|
||||
/* Remap original root values with deterministic values. */
|
||||
Map<int, int> id_by_root;
|
||||
id_by_root.reserve(root_occurences.size());
|
||||
for (const int i : root_occurences.index_range()) {
|
||||
id_by_root.add_new(root_occurences[i].root, i);
|
||||
id_by_root.reserve(root_occurrences.size());
|
||||
for (const int i : root_occurrences.index_range()) {
|
||||
id_by_root.add_new(root_occurrences[i].root, i);
|
||||
}
|
||||
threading::parallel_for(IndexRange(size), 1024, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
|
|
|
@ -51,6 +51,23 @@ static bool BLI_path_is_abs(const char *name);
|
|||
|
||||
// #define DEBUG_STRSIZE
|
||||
|
||||
/**
|
||||
* On UNIX it only makes sense to treat `/` as a path separator.
|
||||
* On WIN32 either may be used.
|
||||
*/
|
||||
static bool is_sep_native_compat(const char ch)
|
||||
{
|
||||
if (ch == SEP) {
|
||||
return true;
|
||||
}
|
||||
#ifdef WIN32
|
||||
if (ch == ALTSEP) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
/* implementation */
|
||||
|
||||
int BLI_path_sequence_decode(const char *string, char *head, char *tail, ushort *r_digits_len)
|
||||
|
@ -1450,7 +1467,7 @@ size_t BLI_path_append(char *__restrict dst, const size_t maxlen, const char *__
|
|||
size_t dirlen = BLI_strnlen(dst, maxlen);
|
||||
|
||||
/* Inline #BLI_path_slash_ensure. */
|
||||
if ((dirlen > 0) && (dst[dirlen - 1] != SEP)) {
|
||||
if ((dirlen > 0) && !is_sep_native_compat(dst[dirlen - 1])) {
|
||||
dst[dirlen++] = SEP;
|
||||
dst[dirlen] = '\0';
|
||||
}
|
||||
|
@ -1467,7 +1484,7 @@ size_t BLI_path_append_dir(char *__restrict dst, const size_t maxlen, const char
|
|||
size_t dirlen = BLI_path_append(dst, maxlen, dir);
|
||||
if (dirlen + 1 < maxlen) {
|
||||
/* Inline #BLI_path_slash_ensure. */
|
||||
if ((dirlen > 0) && (dst[dirlen - 1] != SEP)) {
|
||||
if ((dirlen > 0) && !is_sep_native_compat(dst[dirlen - 1])) {
|
||||
dst[dirlen++] = SEP;
|
||||
dst[dirlen] = '\0';
|
||||
}
|
||||
|
@ -1522,7 +1539,7 @@ size_t BLI_path_join_array(char *__restrict dst,
|
|||
bool has_trailing_slash = false;
|
||||
if (ofs != 0) {
|
||||
size_t len = ofs;
|
||||
while ((len != 0) && (path[len - 1] == SEP)) {
|
||||
while ((len != 0) && is_sep_native_compat(path[len - 1])) {
|
||||
len -= 1;
|
||||
}
|
||||
|
||||
|
@ -1536,18 +1553,18 @@ size_t BLI_path_join_array(char *__restrict dst,
|
|||
path = path_array[path_index];
|
||||
has_trailing_slash = false;
|
||||
const char *path_init = path;
|
||||
while (path[0] == SEP) {
|
||||
while (is_sep_native_compat(path[0])) {
|
||||
path++;
|
||||
}
|
||||
size_t len = strlen(path);
|
||||
if (len != 0) {
|
||||
while ((len != 0) && (path[len - 1] == SEP)) {
|
||||
while ((len != 0) && is_sep_native_compat(path[len - 1])) {
|
||||
len -= 1;
|
||||
}
|
||||
|
||||
if (len != 0) {
|
||||
/* the very first path may have a slash at the end */
|
||||
if (ofs && (dst[ofs - 1] != SEP)) {
|
||||
if (ofs && !is_sep_native_compat(dst[ofs - 1])) {
|
||||
dst[ofs++] = SEP;
|
||||
if (ofs == dst_last) {
|
||||
break;
|
||||
|
@ -1570,7 +1587,7 @@ size_t BLI_path_join_array(char *__restrict dst,
|
|||
}
|
||||
|
||||
if (has_trailing_slash) {
|
||||
if ((ofs != dst_last) && (ofs != 0) && (dst[ofs - 1] != SEP)) {
|
||||
if ((ofs != dst_last) && (ofs != 0) && !is_sep_native_compat(dst[ofs - 1])) {
|
||||
dst[ofs++] = SEP;
|
||||
}
|
||||
}
|
||||
|
@ -1598,7 +1615,7 @@ static bool path_name_at_index_forward(const char *__restrict path,
|
|||
int i = 0;
|
||||
while (true) {
|
||||
const char c = path[i];
|
||||
if (ELEM(c, SEP, '\0')) {
|
||||
if ((c == '\0') || is_sep_native_compat(c)) {
|
||||
if (prev + 1 != i) {
|
||||
prev += 1;
|
||||
/* Skip '/./' (behave as if they don't exist). */
|
||||
|
@ -1633,7 +1650,7 @@ static bool path_name_at_index_backward(const char *__restrict path,
|
|||
int i = prev - 1;
|
||||
while (true) {
|
||||
const char c = i >= 0 ? path[i] : '\0';
|
||||
if (ELEM(c, SEP, '\0')) {
|
||||
if ((c == '\0') || is_sep_native_compat(c)) {
|
||||
if (prev - 1 != i) {
|
||||
i += 1;
|
||||
/* Skip '/./' (behave as if they don't exist). */
|
||||
|
@ -1732,7 +1749,7 @@ int BLI_path_slash_ensure(char *string, size_t string_maxlen)
|
|||
{
|
||||
int len = strlen(string);
|
||||
BLI_assert(len < string_maxlen);
|
||||
if (len == 0 || string[len - 1] != SEP) {
|
||||
if (len == 0 || !is_sep_native_compat(string[len - 1])) {
|
||||
/* Avoid unlikely buffer overflow. */
|
||||
if (len + 1 < string_maxlen) {
|
||||
string[len] = SEP;
|
||||
|
@ -1747,7 +1764,7 @@ void BLI_path_slash_rstrip(char *string)
|
|||
{
|
||||
int len = strlen(string);
|
||||
while (len) {
|
||||
if (string[len - 1] == SEP) {
|
||||
if (is_sep_native_compat(string[len - 1])) {
|
||||
string[len - 1] = '\0';
|
||||
len--;
|
||||
}
|
||||
|
|
|
@ -2001,6 +2001,7 @@ void blo_do_versions_250(FileData *fd, Library *lib, Main *bmain)
|
|||
*/
|
||||
link = MEM_callocN(sizeof(bNodeLink), "link");
|
||||
BLI_addtail(&ntree->links, link);
|
||||
nodeUniqueID(ntree, node);
|
||||
link->fromnode = NULL;
|
||||
link->fromsock = gsock;
|
||||
link->tonode = node;
|
||||
|
@ -2024,6 +2025,7 @@ void blo_do_versions_250(FileData *fd, Library *lib, Main *bmain)
|
|||
*/
|
||||
link = MEM_callocN(sizeof(bNodeLink), "link");
|
||||
BLI_addtail(&ntree->links, link);
|
||||
nodeUniqueID(ntree, node);
|
||||
link->fromnode = node;
|
||||
link->fromsock = sock;
|
||||
link->tonode = NULL;
|
||||
|
|
|
@ -581,9 +581,9 @@ static BMVert **bm_to_mesh_vertex_map(BMesh *bm, int ototvert)
|
|||
* Also not correct but it's better then having it zeroed for e.g.
|
||||
*
|
||||
* - Missing key-index layer.
|
||||
* In this case the basis key wont apply it's deltas to other keys and in the case
|
||||
* a shape-key layer is missing, its coordinates will be initialized from the edit-mesh
|
||||
* vertex locations instead of attempting to remap the shape-keys coordinates.
|
||||
* In this case the basis key won't apply its deltas to other keys and if a shape-key layer is
|
||||
* missing, its coordinates will be initialized from the edit-mesh vertex locations instead of
|
||||
* attempting to remap the shape-keys coordinates.
|
||||
*
|
||||
* \note These cases are considered abnormal and shouldn't occur in typical usage.
|
||||
* A warning is logged in this case to help troubleshooting bugs with shape-keys.
|
||||
|
|
|
@ -20,7 +20,7 @@ class NodeOperationBuilder;
|
|||
/**
|
||||
* \brief Wraps a bNode in its Node instance.
|
||||
*
|
||||
* For all nodetypes a wrapper class is created.
|
||||
* For all node-types a wrapper class is created.
|
||||
*
|
||||
* \note When adding a new node to blender, this method needs to be changed to return the correct
|
||||
* Node instance.
|
||||
|
|
|
@ -132,7 +132,7 @@ static GPUVertCompType get_comp_type_for_type(eCustomDataType type)
|
|||
return GPU_COMP_I32;
|
||||
case CD_PROP_BYTE_COLOR:
|
||||
/* This should be u8,
|
||||
* but u16 is required to store the color in linear space without precission loss */
|
||||
* but u16 is required to store the color in linear space without precision loss */
|
||||
return GPU_COMP_U16;
|
||||
default:
|
||||
return GPU_COMP_F32;
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "UI_resources.h"
|
||||
|
||||
#include "ED_geometry.h"
|
||||
#include "ED_mesh.h"
|
||||
#include "ED_object.h"
|
||||
|
||||
#include "geometry_intern.hh"
|
||||
|
@ -559,6 +560,28 @@ void GEOMETRY_OT_color_attribute_duplicate(wmOperatorType *ot)
|
|||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
static int geometry_attribute_convert_invoke(bContext *C,
|
||||
wmOperator *op,
|
||||
const wmEvent * /*event*/)
|
||||
{
|
||||
Object *ob = ED_object_context(C);
|
||||
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
||||
|
||||
const bke::AttributeMetaData meta_data = *mesh->attributes().lookup_meta_data(
|
||||
BKE_id_attributes_active_get(&mesh->id)->name);
|
||||
|
||||
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "domain");
|
||||
if (!RNA_property_is_set(op->ptr, prop)) {
|
||||
RNA_enum_set(op->ptr, "domain", meta_data.domain);
|
||||
}
|
||||
prop = RNA_struct_find_property(op->ptr, "data_type");
|
||||
if (!RNA_property_is_set(op->ptr, prop)) {
|
||||
RNA_enum_set(op->ptr, "data_type", meta_data.data_type);
|
||||
}
|
||||
|
||||
return WM_operator_props_dialog_popup(C, op, 300);
|
||||
}
|
||||
|
||||
static void geometry_attribute_convert_ui(bContext * /*C*/, wmOperator *op)
|
||||
{
|
||||
uiLayout *layout = op->layout;
|
||||
|
@ -576,13 +599,6 @@ static void geometry_attribute_convert_ui(bContext * /*C*/, wmOperator *op)
|
|||
}
|
||||
}
|
||||
|
||||
static int geometry_attribute_convert_invoke(bContext *C,
|
||||
wmOperator *op,
|
||||
const wmEvent * /*event*/)
|
||||
{
|
||||
return WM_operator_props_dialog_popup(C, op, 300);
|
||||
}
|
||||
|
||||
static bool geometry_color_attribute_convert_poll(bContext *C)
|
||||
{
|
||||
if (!geometry_attributes_poll(C)) {
|
||||
|
@ -614,6 +630,28 @@ static int geometry_color_attribute_convert_exec(bContext *C, wmOperator *op)
|
|||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static int geometry_color_attribute_convert_invoke(bContext *C,
|
||||
wmOperator *op,
|
||||
const wmEvent * /*event*/)
|
||||
{
|
||||
Object *ob = ED_object_context(C);
|
||||
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
||||
|
||||
const bke::AttributeMetaData meta_data = *mesh->attributes().lookup_meta_data(
|
||||
BKE_id_attributes_active_color_get(&mesh->id)->name);
|
||||
|
||||
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "domain");
|
||||
if (!RNA_property_is_set(op->ptr, prop)) {
|
||||
RNA_enum_set(op->ptr, "domain", meta_data.domain);
|
||||
}
|
||||
prop = RNA_struct_find_property(op->ptr, "data_type");
|
||||
if (!RNA_property_is_set(op->ptr, prop)) {
|
||||
RNA_enum_set(op->ptr, "data_type", meta_data.data_type);
|
||||
}
|
||||
|
||||
return WM_operator_props_dialog_popup(C, op, 300);
|
||||
}
|
||||
|
||||
static void geometry_color_attribute_convert_ui(bContext *UNUSED(C), wmOperator *op)
|
||||
{
|
||||
uiLayout *layout = op->layout;
|
||||
|
@ -630,7 +668,7 @@ void GEOMETRY_OT_color_attribute_convert(wmOperatorType *ot)
|
|||
ot->description = "Change how the color attribute is stored";
|
||||
ot->idname = "GEOMETRY_OT_color_attribute_convert";
|
||||
|
||||
ot->invoke = geometry_attribute_convert_invoke;
|
||||
ot->invoke = geometry_color_attribute_convert_invoke;
|
||||
ot->exec = geometry_color_attribute_convert_exec;
|
||||
ot->poll = geometry_color_attribute_convert_poll;
|
||||
ot->ui = geometry_color_attribute_convert_ui;
|
||||
|
|
|
@ -401,7 +401,7 @@ static bool id_search_add(const bContext *C, TemplateID *template_ui, uiSearchIt
|
|||
name_ui,
|
||||
id,
|
||||
iconid,
|
||||
has_sep_char ? UI_BUT_HAS_SEP_CHAR : 0,
|
||||
has_sep_char ? int(UI_BUT_HAS_SEP_CHAR) : 0,
|
||||
name_prefix_offset)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -4812,19 +4812,28 @@ bScreen *ED_screen_animation_no_scrub(const wmWindowManager *wm)
|
|||
int ED_screen_animation_play(bContext *C, int sync, int mode)
|
||||
{
|
||||
bScreen *screen = CTX_wm_screen(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
Scene *scene_eval = DEG_get_evaluated_scene(CTX_data_ensure_evaluated_depsgraph(C));
|
||||
|
||||
if (ED_screen_animation_playing(CTX_wm_manager(C))) {
|
||||
/* stop playback now */
|
||||
ED_screen_animation_timer(C, 0, 0, 0);
|
||||
BKE_sound_stop_scene(scene_eval);
|
||||
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
|
||||
Main *bmain = CTX_data_main(C);
|
||||
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
|
||||
LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) {
|
||||
Depsgraph *graph = BKE_scene_get_depsgraph(scene, view_layer);
|
||||
if (graph) {
|
||||
Scene *scene_eval = DEG_get_evaluated_scene(graph);
|
||||
/* The audio handles are preserved throughout the dependency graph evaluation.
|
||||
* Checking for scene->playback_handle even for non-evaluated scene should be okay. */
|
||||
BKE_sound_stop_scene(scene_eval);
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* these settings are currently only available from a menu in the TimeLine */
|
||||
if (mode == 1) { /* XXX only play audio forwards!? */
|
||||
Scene *scene_eval = DEG_get_evaluated_scene(CTX_data_ensure_evaluated_depsgraph(C));
|
||||
BKE_sound_play_scene(scene_eval);
|
||||
}
|
||||
|
||||
|
|
|
@ -1400,7 +1400,7 @@ static void insert_seam_vert_array(const ProjPaintState *ps,
|
|||
vec[1] *= ibuf_y;
|
||||
vseam->angle = atan2f(vec[1], vec[0]);
|
||||
|
||||
/* If face windings are not initialized, something must be wrong. */
|
||||
/* If the face winding data is not initialized, something must be wrong. */
|
||||
BLI_assert((ps->faceWindingFlags[tri_index] & PROJ_FACE_WINDING_INIT) != 0);
|
||||
vseam->normal_cw = (ps->faceWindingFlags[tri_index] & PROJ_FACE_WINDING_CW);
|
||||
|
||||
|
|
|
@ -5854,7 +5854,7 @@ void SCULPT_OT_brush_stroke(wmOperatorType *ot)
|
|||
ot->ui = sculpt_redo_empty_ui;
|
||||
|
||||
/* Flags (sculpt does own undo? (ton)). */
|
||||
ot->flag = OPTYPE_BLOCKING | OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
ot->flag = OPTYPE_BLOCKING;
|
||||
|
||||
/* Properties. */
|
||||
|
||||
|
|
|
@ -508,9 +508,8 @@ float SCULPT_automasking_factor_get(AutomaskingCache *automasking,
|
|||
|
||||
float mask = 1.0f;
|
||||
|
||||
/* Since brush normal mode depends on the current mirror symmery pass
|
||||
* it is not folded into the factor cache (when it exists).
|
||||
*/
|
||||
/* Since brush normal mode depends on the current mirror symmetry pass
|
||||
* it is not folded into the factor cache (when it exists). */
|
||||
if ((ss->cache || ss->filter_cache) &&
|
||||
(automasking->settings.flags & BRUSH_AUTOMASKING_BRUSH_NORMAL)) {
|
||||
mask *= automasking_brush_normal_factor(automasking, ss, vert, automask_data);
|
||||
|
|
|
@ -85,7 +85,7 @@ typedef struct PointTrackPick {
|
|||
} PointTrackPick;
|
||||
|
||||
/* Pick point track which is closest to the given coordinate.
|
||||
* Operates in the original non-stabilized and non-un-distored coordinates. */
|
||||
* Operates in the original non-stabilized and non-un-distorted coordinates. */
|
||||
PointTrackPick ed_tracking_pick_point_track(const TrackPickOptions *options,
|
||||
struct bContext *C,
|
||||
const float co[2]);
|
||||
|
@ -114,7 +114,7 @@ typedef struct PlaneTrackPick {
|
|||
} PlaneTrackPick;
|
||||
|
||||
/* Pick plane track which is closest to the given coordinate.
|
||||
* Operates in the original non-stabilized and non-un-distored coordinates. */
|
||||
* Operates in the original non-stabilized and non-un-distorted coordinates. */
|
||||
PlaneTrackPick ed_tracking_pick_plane_track(const TrackPickOptions *options,
|
||||
struct bContext *C,
|
||||
const float co[2]);
|
||||
|
@ -136,7 +136,7 @@ typedef struct TrackingPick {
|
|||
} TrackingPick;
|
||||
|
||||
/* Pick closest point or plane track (whichever is the closest to the given coordinate).
|
||||
* Operates in the original non-stabilized and non-un-distored coordinates. */
|
||||
* Operates in the original non-stabilized and non-un-distorted coordinates. */
|
||||
TrackingPick ed_tracking_pick_closest(const TrackPickOptions *options,
|
||||
bContext *C,
|
||||
const float co[2]);
|
||||
|
|
|
@ -277,7 +277,7 @@ PointTrackPick ed_tracking_pick_point_track(const TrackPickOptions *options,
|
|||
|
||||
/* Here we also check whether the mouse is actually closer to the widget which controls scale
|
||||
* and tilt.
|
||||
* NOTE: The tilt contorl is only visible for selected tracks. */
|
||||
* NOTE: The tilt control is only visible for selected tracks. */
|
||||
if (is_track_selected) {
|
||||
distance_squared = mouse_to_tilt_distance_squared(marker, co, width, height);
|
||||
if (distance_squared < current_pick.distance_px_squared) {
|
||||
|
|
|
@ -1709,7 +1709,7 @@ void NODE_OT_preview_toggle(wmOperatorType *ot)
|
|||
|
||||
/* callbacks */
|
||||
ot->exec = node_preview_toggle_exec;
|
||||
ot->poll = ED_operator_node_active;
|
||||
ot->poll = composite_node_active;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
@ -2795,7 +2795,7 @@ static bool node_shader_script_update_text_recursive(RenderEngine *engine,
|
|||
RenderEngineType *type,
|
||||
bNodeTree *ntree,
|
||||
Text *text,
|
||||
Set<bNodeTree *> &done_trees)
|
||||
VectorSet<bNodeTree *> &done_trees)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
|
@ -2855,7 +2855,7 @@ static int node_shader_script_update_exec(bContext *C, wmOperator *op)
|
|||
|
||||
if (text) {
|
||||
|
||||
Set<bNodeTree *> done_trees;
|
||||
VectorSet<bNodeTree *> done_trees;
|
||||
|
||||
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
|
||||
if (ntree->type == NTREE_SHADER) {
|
||||
|
|
|
@ -269,6 +269,7 @@ static bool node_group_ungroup(Main *bmain, bNodeTree *ntree, bNode *gnode)
|
|||
|
||||
node->flag |= NODE_SELECT;
|
||||
}
|
||||
wgroup->runtime->nodes_by_id.clear();
|
||||
|
||||
bNodeLink *glinks_first = (bNodeLink *)ntree->links.last;
|
||||
|
||||
|
@ -446,30 +447,24 @@ static bool node_group_separate_selected(
|
|||
|
||||
ListBase anim_basepaths = {nullptr, nullptr};
|
||||
|
||||
Map<const bNode *, bNode *> node_map;
|
||||
Map<const bNodeSocket *, bNodeSocket *> socket_map;
|
||||
|
||||
/* add selected nodes into the ntree */
|
||||
LISTBASE_FOREACH_MUTABLE (bNode *, node, &ngroup.nodes) {
|
||||
if (!(node->flag & NODE_SELECT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ignore interface nodes */
|
||||
if (ELEM(node->type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT)) {
|
||||
nodeSetSelected(node, false);
|
||||
continue;
|
||||
}
|
||||
/* Add selected nodes into the ntree, ignoring interface nodes. */
|
||||
VectorSet<bNode *> nodes_to_move = get_selected_nodes(ngroup);
|
||||
nodes_to_move.remove_if(
|
||||
[](const bNode *node) { return node->is_group_input() || node->is_group_output(); });
|
||||
|
||||
for (bNode *node : nodes_to_move) {
|
||||
bNode *newnode;
|
||||
if (make_copy) {
|
||||
/* make a copy */
|
||||
newnode = bke::node_copy_with_mapping(&ngroup, *node, LIB_ID_COPY_DEFAULT, true, socket_map);
|
||||
node_map.add_new(node, newnode);
|
||||
newnode = bke::node_copy_with_mapping(&ntree, *node, LIB_ID_COPY_DEFAULT, true, socket_map);
|
||||
}
|
||||
else {
|
||||
/* use the existing node */
|
||||
newnode = node;
|
||||
BLI_remlink(&ngroup.nodes, newnode);
|
||||
BLI_addtail(&ntree.nodes, newnode);
|
||||
nodeUniqueID(&ntree, newnode);
|
||||
nodeUniqueName(&ntree, newnode);
|
||||
}
|
||||
|
||||
/* Keep track of this node's RNA "base" path (the part of the path identifying the node)
|
||||
|
@ -491,17 +486,14 @@ static bool node_group_separate_selected(
|
|||
nodeDetachNode(&ngroup, newnode);
|
||||
}
|
||||
|
||||
/* migrate node */
|
||||
BLI_remlink(&ngroup.nodes, newnode);
|
||||
BLI_addtail(&ntree.nodes, newnode);
|
||||
nodeUniqueID(&ntree, newnode);
|
||||
nodeUniqueName(&ntree, newnode);
|
||||
|
||||
if (!newnode->parent) {
|
||||
newnode->locx += offset.x;
|
||||
newnode->locy += offset.y;
|
||||
}
|
||||
}
|
||||
if (!make_copy) {
|
||||
nodeRebuildIDVector(&ngroup);
|
||||
}
|
||||
|
||||
/* add internal links to the ntree */
|
||||
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ngroup.links) {
|
||||
|
@ -512,9 +504,9 @@ static bool node_group_separate_selected(
|
|||
/* make a copy of internal links */
|
||||
if (fromselect && toselect) {
|
||||
nodeAddLink(&ntree,
|
||||
node_map.lookup(link->fromnode),
|
||||
ntree.node_by_id(link->fromnode->identifier),
|
||||
socket_map.lookup(link->fromsock),
|
||||
node_map.lookup(link->tonode),
|
||||
ntree.node_by_id(link->tonode->identifier),
|
||||
socket_map.lookup(link->tosock));
|
||||
}
|
||||
}
|
||||
|
@ -642,97 +634,97 @@ void NODE_OT_group_separate(wmOperatorType *ot)
|
|||
/** \name Make Group Operator
|
||||
* \{ */
|
||||
|
||||
static bool node_group_make_use_node(const bNode &node, bNode *gnode)
|
||||
static VectorSet<bNode *> get_nodes_to_group(bNodeTree &node_tree, bNode *group_node)
|
||||
{
|
||||
return (&node != gnode && !ELEM(node.type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT) &&
|
||||
(node.flag & NODE_SELECT));
|
||||
VectorSet<bNode *> nodes_to_group = get_selected_nodes(node_tree);
|
||||
nodes_to_group.remove_if(
|
||||
[](bNode *node) { return node->is_group_input() || node->is_group_output(); });
|
||||
nodes_to_group.remove(group_node);
|
||||
return nodes_to_group;
|
||||
}
|
||||
|
||||
static bool node_group_make_test_selected(bNodeTree &ntree,
|
||||
bNode *gnode,
|
||||
const VectorSet<bNode *> &nodes_to_group,
|
||||
const char *ntree_idname,
|
||||
ReportList &reports)
|
||||
{
|
||||
int ok = true;
|
||||
|
||||
if (nodes_to_group.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
/* make a local pseudo node tree to pass to the node poll functions */
|
||||
bNodeTree *ngroup = ntreeAddTree(nullptr, "Pseudo Node Group", ntree_idname);
|
||||
BLI_SCOPED_DEFER([&]() {
|
||||
ntreeFreeTree(ngroup);
|
||||
MEM_freeN(ngroup);
|
||||
});
|
||||
|
||||
/* check poll functions for selected nodes */
|
||||
for (bNode *node : ntree.all_nodes()) {
|
||||
if (node_group_make_use_node(*node, gnode)) {
|
||||
const char *disabled_hint = nullptr;
|
||||
if (node->typeinfo->poll_instance &&
|
||||
!node->typeinfo->poll_instance(node, ngroup, &disabled_hint)) {
|
||||
if (disabled_hint) {
|
||||
BKE_reportf(&reports,
|
||||
RPT_WARNING,
|
||||
"Can not add node '%s' in a group:\n %s",
|
||||
node->name,
|
||||
disabled_hint);
|
||||
}
|
||||
else {
|
||||
BKE_reportf(&reports, RPT_WARNING, "Can not add node '%s' in a group", node->name);
|
||||
}
|
||||
ok = false;
|
||||
break;
|
||||
for (bNode *node : nodes_to_group) {
|
||||
const char *disabled_hint = nullptr;
|
||||
if (node->typeinfo->poll_instance &&
|
||||
!node->typeinfo->poll_instance(node, ngroup, &disabled_hint)) {
|
||||
if (disabled_hint) {
|
||||
BKE_reportf(&reports,
|
||||
RPT_WARNING,
|
||||
"Can not add node '%s' in a group:\n %s",
|
||||
node->name,
|
||||
disabled_hint);
|
||||
}
|
||||
else {
|
||||
BKE_reportf(&reports, RPT_WARNING, "Can not add node '%s' in a group", node->name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
node->runtime->done = 0;
|
||||
}
|
||||
|
||||
/* free local pseudo node tree again */
|
||||
ntreeFreeTree(ngroup);
|
||||
MEM_freeN(ngroup);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* check if all connections are OK, no unselected node has both
|
||||
* inputs and outputs to a selection */
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
|
||||
if (node_group_make_use_node(*link->fromnode, gnode)) {
|
||||
link->tonode->runtime->done |= 1;
|
||||
}
|
||||
if (node_group_make_use_node(*link->tonode, gnode)) {
|
||||
link->fromnode->runtime->done |= 2;
|
||||
}
|
||||
}
|
||||
ntree.ensure_topology_cache();
|
||||
for (bNode *node : ntree.all_nodes()) {
|
||||
if (!(node->flag & NODE_SELECT) && node != gnode && node->runtime->done == 3) {
|
||||
if (nodes_to_group.contains(node)) {
|
||||
continue;
|
||||
}
|
||||
auto sockets_connected_to_group = [&](const Span<bNodeSocket *> sockets) {
|
||||
for (const bNodeSocket *socket : sockets) {
|
||||
for (const bNodeSocket *other_socket : socket->directly_linked_sockets()) {
|
||||
if (nodes_to_group.contains(const_cast<bNode *>(&other_socket->owner_node()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (sockets_connected_to_group(node->input_sockets()) &&
|
||||
sockets_connected_to_group(node->output_sockets())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int node_get_selected_minmax(
|
||||
bNodeTree &ntree, bNode *gnode, float2 &min, float2 &max, bool use_size)
|
||||
static void get_min_max_of_nodes(const Span<bNode *> nodes,
|
||||
const bool use_size,
|
||||
float2 &min,
|
||||
float2 &max)
|
||||
{
|
||||
int totselect = 0;
|
||||
if (nodes.is_empty()) {
|
||||
min = float2(0);
|
||||
max = float2(0);
|
||||
return;
|
||||
}
|
||||
|
||||
INIT_MINMAX2(min, max);
|
||||
for (const bNode *node : ntree.all_nodes()) {
|
||||
if (node_group_make_use_node(*node, gnode)) {
|
||||
float2 loc;
|
||||
nodeToView(node, node->offsetx, node->offsety, &loc.x, &loc.y);
|
||||
for (const bNode *node : nodes) {
|
||||
float2 loc;
|
||||
nodeToView(node, node->offsetx, node->offsety, &loc.x, &loc.y);
|
||||
math::min_max(loc, min, max);
|
||||
if (use_size) {
|
||||
loc.x += node->width;
|
||||
loc.y -= node->height;
|
||||
math::min_max(loc, min, max);
|
||||
if (use_size) {
|
||||
loc.x += node->width;
|
||||
loc.y -= node->height;
|
||||
math::min_max(loc, min, max);
|
||||
}
|
||||
totselect++;
|
||||
}
|
||||
}
|
||||
|
||||
/* sane min/max if no selected nodes */
|
||||
if (totselect == 0) {
|
||||
min[0] = min[1] = max[0] = max[1] = 0.0f;
|
||||
}
|
||||
|
||||
return totselect;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -803,11 +795,14 @@ static void node_group_make_redirect_incoming_link(
|
|||
}
|
||||
}
|
||||
|
||||
static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree, bNode *gnode)
|
||||
static void node_group_make_insert_selected(const bContext &C,
|
||||
bNodeTree &ntree,
|
||||
bNode *gnode,
|
||||
const VectorSet<bNode *> &nodes_to_move)
|
||||
{
|
||||
Main *bmain = CTX_data_main(&C);
|
||||
bNodeTree *ngroup = (bNodeTree *)gnode->id;
|
||||
bool expose_visible = false;
|
||||
BLI_assert(!nodes_to_move.contains(gnode));
|
||||
|
||||
/* XXX rough guess, not nice but we don't have access to UI constants here ... */
|
||||
static const float offsetx = 200;
|
||||
|
@ -818,18 +813,16 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
|
|||
nodeSetSelected(node, false);
|
||||
}
|
||||
|
||||
/* auto-add interface for "solo" nodes */
|
||||
const bool expose_visible = nodes_to_move.size() == 1;
|
||||
|
||||
float2 center, min, max;
|
||||
const int totselect = node_get_selected_minmax(ntree, gnode, min, max, false);
|
||||
get_min_max_of_nodes(nodes_to_move, false, min, max);
|
||||
add_v2_v2v2(center, min, max);
|
||||
mul_v2_fl(center, 0.5f);
|
||||
|
||||
float2 real_min, real_max;
|
||||
node_get_selected_minmax(ntree, gnode, real_min, real_max, true);
|
||||
|
||||
/* auto-add interface for "solo" nodes */
|
||||
if (totselect == 1) {
|
||||
expose_visible = true;
|
||||
}
|
||||
get_min_max_of_nodes(nodes_to_move, true, real_min, real_max);
|
||||
|
||||
ListBase anim_basepaths = {nullptr, nullptr};
|
||||
|
||||
|
@ -839,44 +832,42 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
|
|||
if (node->parent == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (node_group_make_use_node(*node->parent, gnode) &&
|
||||
!node_group_make_use_node(*node, gnode)) {
|
||||
if (nodes_to_move.contains(node->parent) && nodes_to_move.contains(node)) {
|
||||
nodeDetachNode(&ntree, node);
|
||||
}
|
||||
}
|
||||
|
||||
/* move nodes over */
|
||||
LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree.nodes) {
|
||||
if (node_group_make_use_node(*node, gnode)) {
|
||||
/* Keep track of this node's RNA "base" path (the part of the pat identifying the node)
|
||||
* if the old node-tree has animation data which potentially covers this node. */
|
||||
if (ntree.adt) {
|
||||
PointerRNA ptr;
|
||||
char *path;
|
||||
for (bNode *node : nodes_to_move) {
|
||||
/* Keep track of this node's RNA "base" path (the part of the pat identifying the node)
|
||||
* if the old node-tree has animation data which potentially covers this node. */
|
||||
if (ntree.adt) {
|
||||
PointerRNA ptr;
|
||||
char *path;
|
||||
|
||||
RNA_pointer_create(&ntree.id, &RNA_Node, node, &ptr);
|
||||
path = RNA_path_from_ID_to_struct(&ptr);
|
||||
RNA_pointer_create(&ntree.id, &RNA_Node, node, &ptr);
|
||||
path = RNA_path_from_ID_to_struct(&ptr);
|
||||
|
||||
if (path) {
|
||||
BLI_addtail(&anim_basepaths, animation_basepath_change_new(path, path));
|
||||
}
|
||||
if (path) {
|
||||
BLI_addtail(&anim_basepaths, animation_basepath_change_new(path, path));
|
||||
}
|
||||
|
||||
/* ensure valid parent pointers, detach if parent stays outside the group */
|
||||
if (node->parent && !(node->parent->flag & NODE_SELECT)) {
|
||||
nodeDetachNode(&ntree, node);
|
||||
}
|
||||
|
||||
/* change node-collection membership */
|
||||
BLI_remlink(&ntree.nodes, node);
|
||||
BLI_addtail(&ngroup->nodes, node);
|
||||
nodeUniqueID(ngroup, node);
|
||||
nodeUniqueName(ngroup, node);
|
||||
|
||||
BKE_ntree_update_tag_node_removed(&ntree);
|
||||
BKE_ntree_update_tag_node_new(ngroup, node);
|
||||
}
|
||||
|
||||
/* ensure valid parent pointers, detach if parent stays outside the group */
|
||||
if (node->parent && !(node->parent->flag & NODE_SELECT)) {
|
||||
nodeDetachNode(&ntree, node);
|
||||
}
|
||||
|
||||
/* change node-collection membership */
|
||||
BLI_remlink(&ntree.nodes, node);
|
||||
BLI_addtail(&ngroup->nodes, node);
|
||||
nodeUniqueID(ngroup, node);
|
||||
nodeUniqueName(ngroup, node);
|
||||
|
||||
BKE_ntree_update_tag_node_removed(&ntree);
|
||||
BKE_ntree_update_tag_node_new(ngroup, node);
|
||||
}
|
||||
nodeRebuildIDVector(&ntree);
|
||||
|
||||
/* move animation data over */
|
||||
if (ntree.adt) {
|
||||
|
@ -904,8 +895,8 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
|
|||
Map<bNodeSocket *, bNodeSocket *> reusable_sockets;
|
||||
|
||||
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree.links) {
|
||||
const bool fromselect = node_group_make_use_node(*link->fromnode, gnode);
|
||||
const bool toselect = node_group_make_use_node(*link->tonode, gnode);
|
||||
const bool fromselect = nodes_to_move.contains(link->fromnode);
|
||||
const bool toselect = nodes_to_move.contains(link->tonode);
|
||||
|
||||
if ((fromselect && link->tonode == gnode) || (toselect && link->fromnode == gnode)) {
|
||||
/* remove all links to/from the gnode.
|
||||
|
@ -969,8 +960,8 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
|
|||
|
||||
/* move internal links */
|
||||
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree.links) {
|
||||
const bool fromselect = node_group_make_use_node(*link->fromnode, gnode);
|
||||
const bool toselect = node_group_make_use_node(*link->tonode, gnode);
|
||||
const bool fromselect = nodes_to_move.contains(link->fromnode);
|
||||
const bool toselect = nodes_to_move.contains(link->tonode);
|
||||
|
||||
if (fromselect && toselect) {
|
||||
BLI_remlink(&ntree.links, link);
|
||||
|
@ -979,8 +970,8 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
|
|||
}
|
||||
|
||||
/* move nodes in the group to the center */
|
||||
for (bNode *node : ngroup->all_nodes()) {
|
||||
if (node_group_make_use_node(*node, gnode) && !node->parent) {
|
||||
for (bNode *node : nodes_to_move) {
|
||||
if (!node->parent) {
|
||||
node->locx -= center[0];
|
||||
node->locy -= center[1];
|
||||
}
|
||||
|
@ -988,73 +979,67 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
|
|||
|
||||
/* Expose all unlinked sockets too but only the visible ones. */
|
||||
if (expose_visible) {
|
||||
for (bNode *node : ngroup->all_nodes()) {
|
||||
if (node_group_make_use_node(*node, gnode)) {
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
|
||||
bool skip = false;
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ngroup->links) {
|
||||
if (link->tosock == sock) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) {
|
||||
for (bNode *node : nodes_to_move) {
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
|
||||
bool skip = false;
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ngroup->links) {
|
||||
if (link->tosock == sock) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
if (skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
|
||||
|
||||
node_group_input_update(ngroup, input_node);
|
||||
|
||||
/* create new internal link */
|
||||
bNodeSocket *input_sock = node_group_input_find_socket(input_node, iosock->identifier);
|
||||
nodeAddLink(ngroup, input_node, input_sock, node, sock);
|
||||
}
|
||||
if (sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) {
|
||||
skip = true;
|
||||
}
|
||||
if (skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
|
||||
bool skip = false;
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ngroup->links) {
|
||||
if (link->fromsock == sock) {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
if (sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) {
|
||||
bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
|
||||
|
||||
node_group_input_update(ngroup, input_node);
|
||||
|
||||
/* create new internal link */
|
||||
bNodeSocket *input_sock = node_group_input_find_socket(input_node, iosock->identifier);
|
||||
nodeAddLink(ngroup, input_node, input_sock, node, sock);
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
|
||||
bool skip = false;
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ngroup->links) {
|
||||
if (link->fromsock == sock) {
|
||||
skip = true;
|
||||
}
|
||||
if (skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
|
||||
|
||||
node_group_output_update(ngroup, output_node);
|
||||
|
||||
/* create new internal link */
|
||||
bNodeSocket *output_sock = node_group_output_find_socket(output_node,
|
||||
iosock->identifier);
|
||||
nodeAddLink(ngroup, node, sock, output_node, output_sock);
|
||||
}
|
||||
if (sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) {
|
||||
skip = true;
|
||||
}
|
||||
if (skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
|
||||
|
||||
node_group_output_update(ngroup, output_node);
|
||||
|
||||
/* create new internal link */
|
||||
bNodeSocket *output_sock = node_group_output_find_socket(output_node, iosock->identifier);
|
||||
nodeAddLink(ngroup, node, sock, output_node, output_sock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bNode *node_group_make_from_selected(const bContext &C,
|
||||
bNodeTree &ntree,
|
||||
const char *ntype,
|
||||
const char *ntreetype)
|
||||
static bNode *node_group_make_from_nodes(const bContext &C,
|
||||
bNodeTree &ntree,
|
||||
const VectorSet<bNode *> &nodes_to_group,
|
||||
const char *ntype,
|
||||
const char *ntreetype)
|
||||
{
|
||||
Main *bmain = CTX_data_main(&C);
|
||||
|
||||
float2 min, max;
|
||||
const int totselect = node_get_selected_minmax(ntree, nullptr, min, max, false);
|
||||
/* don't make empty group */
|
||||
if (totselect == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
get_min_max_of_nodes(nodes_to_group, false, min, max);
|
||||
|
||||
/* New node-tree. */
|
||||
bNodeTree *ngroup = ntreeAddTree(bmain, "NodeGroup", ntreetype);
|
||||
|
@ -1066,7 +1051,7 @@ static bNode *node_group_make_from_selected(const bContext &C,
|
|||
gnode->locx = 0.5f * (min[0] + max[0]);
|
||||
gnode->locy = 0.5f * (min[1] + max[1]);
|
||||
|
||||
node_group_make_insert_selected(C, ntree, gnode);
|
||||
node_group_make_insert_selected(C, ntree, gnode, nodes_to_group);
|
||||
|
||||
return gnode;
|
||||
}
|
||||
|
@ -1081,11 +1066,12 @@ static int node_group_make_exec(bContext *C, wmOperator *op)
|
|||
|
||||
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
|
||||
|
||||
if (!node_group_make_test_selected(ntree, nullptr, ntree_idname, *op->reports)) {
|
||||
VectorSet<bNode *> nodes_to_group = get_nodes_to_group(ntree, nullptr);
|
||||
if (!node_group_make_test_selected(ntree, nodes_to_group, ntree_idname, *op->reports)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
bNode *gnode = node_group_make_from_selected(*C, ntree, node_idname, ntree_idname);
|
||||
bNode *gnode = node_group_make_from_nodes(*C, ntree, nodes_to_group, node_idname, ntree_idname);
|
||||
|
||||
if (gnode) {
|
||||
bNodeTree *ngroup = (bNodeTree *)gnode->id;
|
||||
|
@ -1137,17 +1123,17 @@ static int node_group_insert_exec(bContext *C, wmOperator *op)
|
|||
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
|
||||
|
||||
bNode *gnode = node_group_get_active(C, node_idname);
|
||||
|
||||
if (!gnode || !gnode->id) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
bNodeTree *ngroup = (bNodeTree *)gnode->id;
|
||||
if (!node_group_make_test_selected(*ntree, gnode, ngroup->idname, *op->reports)) {
|
||||
VectorSet<bNode *> nodes_to_group = get_nodes_to_group(*ntree, gnode);
|
||||
if (!node_group_make_test_selected(*ntree, nodes_to_group, ngroup->idname, *op->reports)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
node_group_make_insert_selected(*C, *ntree, gnode);
|
||||
node_group_make_insert_selected(*C, *ntree, gnode, nodes_to_group);
|
||||
|
||||
nodeSetActive(ntree, gnode);
|
||||
ED_node_tree_push(snode, ngroup, gnode);
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_vector.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "BKE_node.h"
|
||||
|
||||
|
@ -183,7 +183,7 @@ void node_keymap(wmKeyConfig *keyconf);
|
|||
rctf node_frame_rect_inside(const bNode &node);
|
||||
bool node_or_socket_isect_event(const bContext &C, const wmEvent &event);
|
||||
|
||||
Set<bNode *> get_selected_nodes(bNodeTree &node_tree);
|
||||
VectorSet<bNode *> get_selected_nodes(bNodeTree &node_tree);
|
||||
void node_deselect_all(SpaceNode &snode);
|
||||
void node_socket_select(bNode *node, bNodeSocket &sock);
|
||||
void node_socket_deselect(bNode *node, bNodeSocket &sock, bool deselect_node);
|
||||
|
|
|
@ -1630,40 +1630,42 @@ void NODE_OT_parent_set(wmOperatorType *ot)
|
|||
/** \name Join Nodes Operator
|
||||
* \{ */
|
||||
|
||||
/* tags for depth-first search */
|
||||
#define NODE_JOIN_DONE 1
|
||||
#define NODE_JOIN_IS_DESCENDANT 2
|
||||
struct NodeJoinState {
|
||||
bool done;
|
||||
bool descendent;
|
||||
};
|
||||
|
||||
static void node_join_attach_recursive(bNodeTree &ntree,
|
||||
MutableSpan<NodeJoinState> join_states,
|
||||
bNode *node,
|
||||
bNode *frame,
|
||||
const Set<bNode *> &selected_nodes)
|
||||
const VectorSet<bNode *> &selected_nodes)
|
||||
{
|
||||
node->runtime->done |= NODE_JOIN_DONE;
|
||||
join_states[node->runtime->index_in_tree].done = true;
|
||||
|
||||
if (node == frame) {
|
||||
node->runtime->done |= NODE_JOIN_IS_DESCENDANT;
|
||||
join_states[node->runtime->index_in_tree].descendent = true;
|
||||
}
|
||||
else if (node->parent) {
|
||||
/* call recursively */
|
||||
if (!(node->parent->runtime->done & NODE_JOIN_DONE)) {
|
||||
node_join_attach_recursive(ntree, node->parent, frame, selected_nodes);
|
||||
if (!join_states[node->parent->runtime->index_in_tree].done) {
|
||||
node_join_attach_recursive(ntree, join_states, node->parent, frame, selected_nodes);
|
||||
}
|
||||
|
||||
/* in any case: if the parent is a descendant, so is the child */
|
||||
if (node->parent->runtime->done & NODE_JOIN_IS_DESCENDANT) {
|
||||
node->runtime->done |= NODE_JOIN_IS_DESCENDANT;
|
||||
if (join_states[node->parent->runtime->index_in_tree].descendent) {
|
||||
join_states[node->runtime->index_in_tree].descendent = true;
|
||||
}
|
||||
else if (selected_nodes.contains(node)) {
|
||||
/* if parent is not an descendant of the frame, reattach the node */
|
||||
nodeDetachNode(&ntree, node);
|
||||
nodeAttachNode(&ntree, node, frame);
|
||||
node->runtime->done |= NODE_JOIN_IS_DESCENDANT;
|
||||
join_states[node->runtime->index_in_tree].descendent = true;
|
||||
}
|
||||
}
|
||||
else if (selected_nodes.contains(node)) {
|
||||
nodeAttachNode(&ntree, node, frame);
|
||||
node->runtime->done |= NODE_JOIN_IS_DESCENDANT;
|
||||
join_states[node->runtime->index_in_tree].descendent = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1673,19 +1675,16 @@ static int node_join_exec(bContext *C, wmOperator * /*op*/)
|
|||
SpaceNode &snode = *CTX_wm_space_node(C);
|
||||
bNodeTree &ntree = *snode.edittree;
|
||||
|
||||
const Set<bNode *> selected_nodes = get_selected_nodes(ntree);
|
||||
const VectorSet<bNode *> selected_nodes = get_selected_nodes(ntree);
|
||||
|
||||
bNode *frame_node = nodeAddStaticNode(C, &ntree, NODE_FRAME);
|
||||
nodeSetActive(&ntree, frame_node);
|
||||
|
||||
/* reset tags */
|
||||
for (bNode *node : ntree.all_nodes()) {
|
||||
node->runtime->done = 0;
|
||||
}
|
||||
Array<NodeJoinState> join_states(ntree.all_nodes().size(), NodeJoinState{false, false});
|
||||
|
||||
for (bNode *node : ntree.all_nodes()) {
|
||||
if (!(node->runtime->done & NODE_JOIN_DONE)) {
|
||||
node_join_attach_recursive(ntree, node, frame_node, selected_nodes);
|
||||
if (!join_states[node->runtime->index_in_tree].done) {
|
||||
node_join_attach_recursive(ntree, join_states, node, frame_node, selected_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1808,32 +1807,35 @@ void NODE_OT_attach(wmOperatorType *ot)
|
|||
/** \name Detach Operator
|
||||
* \{ */
|
||||
|
||||
/* tags for depth-first search */
|
||||
#define NODE_DETACH_DONE 1
|
||||
#define NODE_DETACH_IS_DESCENDANT 2
|
||||
struct NodeDetachstate {
|
||||
bool done;
|
||||
bool descendent;
|
||||
};
|
||||
|
||||
static void node_detach_recursive(bNodeTree &ntree, bNode *node)
|
||||
static void node_detach_recursive(bNodeTree &ntree,
|
||||
MutableSpan<NodeDetachstate> detach_states,
|
||||
bNode *node)
|
||||
{
|
||||
node->runtime->done |= NODE_DETACH_DONE;
|
||||
detach_states[node->runtime->index_in_tree].done = true;
|
||||
|
||||
if (node->parent) {
|
||||
/* call recursively */
|
||||
if (!(node->parent->runtime->done & NODE_DETACH_DONE)) {
|
||||
node_detach_recursive(ntree, node->parent);
|
||||
if (!detach_states[node->parent->runtime->index_in_tree].done) {
|
||||
node_detach_recursive(ntree, detach_states, node->parent);
|
||||
}
|
||||
|
||||
/* in any case: if the parent is a descendant, so is the child */
|
||||
if (node->parent->runtime->done & NODE_DETACH_IS_DESCENDANT) {
|
||||
node->runtime->done |= NODE_DETACH_IS_DESCENDANT;
|
||||
if (detach_states[node->parent->runtime->index_in_tree].descendent) {
|
||||
detach_states[node->runtime->index_in_tree].descendent = true;
|
||||
}
|
||||
else if (node->flag & NODE_SELECT) {
|
||||
/* if parent is not a descendant of a selected node, detach */
|
||||
nodeDetachNode(&ntree, node);
|
||||
node->runtime->done |= NODE_DETACH_IS_DESCENDANT;
|
||||
detach_states[node->runtime->index_in_tree].descendent = true;
|
||||
}
|
||||
}
|
||||
else if (node->flag & NODE_SELECT) {
|
||||
node->runtime->done |= NODE_DETACH_IS_DESCENDANT;
|
||||
detach_states[node->runtime->index_in_tree].descendent = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1843,16 +1845,14 @@ static int node_detach_exec(bContext *C, wmOperator * /*op*/)
|
|||
SpaceNode &snode = *CTX_wm_space_node(C);
|
||||
bNodeTree &ntree = *snode.edittree;
|
||||
|
||||
/* reset tags */
|
||||
for (bNode *node : ntree.all_nodes()) {
|
||||
node->runtime->done = 0;
|
||||
}
|
||||
Array<NodeDetachstate> detach_states(ntree.all_nodes().size(), NodeDetachstate{false, false});
|
||||
|
||||
/* detach nodes recursively
|
||||
* relative order is preserved here!
|
||||
*/
|
||||
for (bNode *node : ntree.all_nodes()) {
|
||||
if (!(node->runtime->done & NODE_DETACH_DONE)) {
|
||||
node_detach_recursive(ntree, node);
|
||||
if (!detach_states[node->runtime->index_in_tree].done) {
|
||||
node_detach_recursive(ntree, detach_states, node);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -312,9 +312,9 @@ void node_deselect_all_output_sockets(SpaceNode &snode, const bool deselect_node
|
|||
}
|
||||
}
|
||||
|
||||
Set<bNode *> get_selected_nodes(bNodeTree &node_tree)
|
||||
VectorSet<bNode *> get_selected_nodes(bNodeTree &node_tree)
|
||||
{
|
||||
Set<bNode *> selected_nodes;
|
||||
VectorSet<bNode *> selected_nodes;
|
||||
for (bNode *node : node_tree.all_nodes()) {
|
||||
if (node->flag & NODE_SELECT) {
|
||||
selected_nodes.add(node);
|
||||
|
@ -1142,7 +1142,7 @@ static int node_select_linked_to_exec(bContext *C, wmOperator * /*op*/)
|
|||
|
||||
node_tree.ensure_topology_cache();
|
||||
|
||||
Set<bNode *> initial_selection = get_selected_nodes(node_tree);
|
||||
VectorSet<bNode *> initial_selection = get_selected_nodes(node_tree);
|
||||
|
||||
for (bNode *node : initial_selection) {
|
||||
for (bNodeSocket *output_socket : node->output_sockets()) {
|
||||
|
@ -1192,7 +1192,7 @@ static int node_select_linked_from_exec(bContext *C, wmOperator * /*op*/)
|
|||
|
||||
node_tree.ensure_topology_cache();
|
||||
|
||||
Set<bNode *> initial_selection = get_selected_nodes(node_tree);
|
||||
VectorSet<bNode *> initial_selection = get_selected_nodes(node_tree);
|
||||
|
||||
for (bNode *node : initial_selection) {
|
||||
for (bNodeSocket *input_socket : node->input_sockets()) {
|
||||
|
|
|
@ -106,16 +106,22 @@ static int viewroll_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|||
break;
|
||||
}
|
||||
}
|
||||
else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
|
||||
/* Note this does not remove auto-keys on locked cameras. */
|
||||
copy_qt_qt(vod->rv3d->viewquat, vod->init.quat);
|
||||
ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
|
||||
viewops_data_free(C, op->customdata);
|
||||
op->customdata = NULL;
|
||||
return OPERATOR_CANCELLED;
|
||||
else if (event->type == vod->init.event_type) {
|
||||
/* Check `vod->init.event_type` first in case RMB was used to invoke.
|
||||
* in this case confirming takes precedence over canceling, see: T102937. */
|
||||
if (event->val == KM_RELEASE) {
|
||||
event_code = VIEW_CONFIRM;
|
||||
}
|
||||
}
|
||||
else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
|
||||
event_code = VIEW_CONFIRM;
|
||||
else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
|
||||
if (event->val == KM_PRESS) {
|
||||
/* Note this does not remove auto-keys on locked cameras. */
|
||||
copy_qt_qt(vod->rv3d->viewquat, vod->init.quat);
|
||||
ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
|
||||
viewops_data_free(C, op->customdata);
|
||||
op->customdata = NULL;
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
if (event_code == VIEW_APPLY) {
|
||||
|
|
|
@ -1231,6 +1231,12 @@ void transform_convert_mesh_mirrordata_calc(struct BMEditMesh *em,
|
|||
* It can happen when vertices occupy the same position. */
|
||||
continue;
|
||||
}
|
||||
if (vert_map[i].flag & flag) {
|
||||
/* It's already a mirror.
|
||||
* Avoid a mirror vertex dependency cycle.
|
||||
* This can happen when the vertices are within the mirror threshold. */
|
||||
continue;
|
||||
}
|
||||
|
||||
vert_map[i_mirr] = (struct MirrorDataVert){i, flag};
|
||||
mirror_elem_len++;
|
||||
|
|
|
@ -239,7 +239,7 @@ static bool uv_shear_in_clip_bounds_test(const TransInfo *t, const float value)
|
|||
sub_v2_v2v2(uv, td->iloc, center);
|
||||
uv[axis] = uv[axis] + value * uv[1 - axis] * (2 * axis - 1);
|
||||
add_v2_v2(uv, center);
|
||||
/* TODO: udim support. */
|
||||
/* TODO: UDIM support. */
|
||||
if (uv[axis] < 0.0f || 1.0f < uv[axis]) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -469,7 +469,9 @@ Vector<GVArray> evaluate_fields(ResourceScope &scope,
|
|||
}
|
||||
/* Still have to copy over the data in the destination provided by the caller. */
|
||||
if (dst_varray.is_span()) {
|
||||
array_utils::copy(computed_varray, mask, dst_varray.get_internal_span());
|
||||
array_utils::copy(computed_varray,
|
||||
mask,
|
||||
dst_varray.get_internal_span().take_front(mask.min_array_size()));
|
||||
}
|
||||
else {
|
||||
/* Slower materialize into a different structure. */
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_boxpack_2d.h"
|
||||
#include "BLI_convexhull_2d.h"
|
||||
#include "BLI_ghash.h"
|
||||
|
@ -16,6 +17,7 @@
|
|||
#include "BLI_polyfill_2d.h"
|
||||
#include "BLI_polyfill_2d_beautify.h"
|
||||
#include "BLI_rand.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "eigen_capi.h"
|
||||
|
||||
|
@ -1122,32 +1124,6 @@ static PFace *p_face_add_fill(ParamHandle *handle, PChart *chart, PVert *v1, PVe
|
|||
return f;
|
||||
}
|
||||
|
||||
static bool p_quad_split_direction(ParamHandle *handle, const float **co, const ParamKey *vkeys)
|
||||
{
|
||||
/* Slight bias to prefer one edge over the other in case they are equal, so
|
||||
* that in symmetric models we choose the same split direction instead of
|
||||
* depending on floating point errors to decide. */
|
||||
float bias = 1.0f + 1e-6f;
|
||||
float fac = len_v3v3(co[0], co[2]) * bias - len_v3v3(co[1], co[3]);
|
||||
bool dir = (fac <= 0.0f);
|
||||
|
||||
/* The face exists check is there because of a special case:
|
||||
* when two quads share three vertices, they can each be split into two triangles,
|
||||
* resulting in two identical triangles. For example in Suzanne's nose. */
|
||||
if (dir) {
|
||||
if (p_face_exists(handle, vkeys, 0, 1, 2) || p_face_exists(handle, vkeys, 0, 2, 3)) {
|
||||
return !dir;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (p_face_exists(handle, vkeys, 0, 1, 3) || p_face_exists(handle, vkeys, 1, 2, 3)) {
|
||||
return !dir;
|
||||
}
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
/* Construction: boundary filling */
|
||||
|
||||
static void p_chart_boundaries(PChart *chart, PEdge **r_outer)
|
||||
|
@ -3895,21 +3871,75 @@ void GEO_uv_parametrizer_face_add(ParamHandle *phandle,
|
|||
BLI_assert(nverts >= 3);
|
||||
param_assert(phandle->state == PHANDLE_STATE_ALLOCATED);
|
||||
|
||||
if (nverts > 4) {
|
||||
if (nverts > 3) {
|
||||
/* Protect against (manifold) geometry which has a non-manifold triangulation.
|
||||
* See T102543. */
|
||||
|
||||
blender::Vector<int, 32> permute;
|
||||
permute.reserve(nverts);
|
||||
for (int i = 0; i < nverts; i++) {
|
||||
permute.append_unchecked(i);
|
||||
}
|
||||
|
||||
int i = nverts - 1;
|
||||
while (i >= 0) {
|
||||
/* Just check the "ears" of the n-gon.
|
||||
* For quads, this is sufficient.
|
||||
* For pents and higher, we might miss internal duplicate triangles, but note
|
||||
* that such cases are rare if the source geometry is manifold and non-intersecting. */
|
||||
int pm = permute.size();
|
||||
BLI_assert(pm > 3);
|
||||
int i0 = permute[i];
|
||||
int i1 = permute[(i + 1) % pm];
|
||||
int i2 = permute[(i + 2) % pm];
|
||||
if (!p_face_exists(phandle, vkeys, i0, i1, i2)) {
|
||||
i--; /* ...All good...*/
|
||||
continue;
|
||||
}
|
||||
|
||||
/* An existing triangle has already been inserted. As a heuristic, attempt to add the
|
||||
* *previous* triangle. \note: Should probably call `GEO_uv_parametrizer_face_add` instead of
|
||||
* `p_face_add_construct`. */
|
||||
int iprev = permute[(i + pm - 1) % pm];
|
||||
p_face_add_construct(phandle, key, vkeys, co, uv, iprev, i0, i1, pin, select);
|
||||
|
||||
permute.remove(i);
|
||||
if (permute.size() == 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (permute.size() != nverts) {
|
||||
int pm = permute.size();
|
||||
/* Add the remaining pm-gon. */
|
||||
blender::Array<ParamKey> vkeys_sub(pm);
|
||||
blender::Array<const float *> co_sub(pm);
|
||||
blender::Array<float *> uv_sub(pm);
|
||||
blender::Array<bool> pin_sub(pm);
|
||||
blender::Array<bool> select_sub(pm);
|
||||
for (int i = 0; i < pm; i++) {
|
||||
int j = permute[i];
|
||||
vkeys_sub[i] = vkeys[j];
|
||||
co_sub[i] = co[j];
|
||||
uv_sub[i] = uv[j];
|
||||
pin_sub[i] = pin && pin[j];
|
||||
select_sub[i] = select && select[j];
|
||||
}
|
||||
p_add_ngon(phandle,
|
||||
key,
|
||||
pm,
|
||||
&vkeys_sub.first(),
|
||||
&co_sub.first(),
|
||||
&uv_sub.first(),
|
||||
&pin_sub.first(),
|
||||
&select_sub.first());
|
||||
return; /* Nothing more to do. */
|
||||
}
|
||||
/* No "ears" have previously been inserted. Continue as normal. */
|
||||
}
|
||||
if (nverts > 3) {
|
||||
/* ngon */
|
||||
p_add_ngon(phandle, key, nverts, vkeys, co, uv, pin, select);
|
||||
}
|
||||
else if (nverts == 4) {
|
||||
/* quad */
|
||||
if (p_quad_split_direction(phandle, co, vkeys)) {
|
||||
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 2, pin, select);
|
||||
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 2, 3, pin, select);
|
||||
}
|
||||
else {
|
||||
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 3, pin, select);
|
||||
p_face_add_construct(phandle, key, vkeys, co, uv, 1, 2, 3, pin, select);
|
||||
}
|
||||
}
|
||||
else if (!p_face_exists(phandle, vkeys, 0, 1, 2)) {
|
||||
/* triangle */
|
||||
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 2, pin, select);
|
||||
|
|
|
@ -847,7 +847,7 @@ void GPU_texture_get_mipmap_size(GPUTexture *tex, int lvl, int *r_size)
|
|||
|
||||
GPUPixelBuffer *GPU_pixel_buffer_create(uint size)
|
||||
{
|
||||
/* Ensure buffer satifies the alignment of 256 bytes for copying
|
||||
/* Ensure buffer satisfies the alignment of 256 bytes for copying
|
||||
* data between buffers and textures. As specified in:
|
||||
* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
|
||||
*
|
||||
|
|
|
@ -88,7 +88,7 @@ class MTLStateManager : public StateManager {
|
|||
class MTLFence : public Fence {
|
||||
private:
|
||||
/* Using an event in this instance, as this is global for the command stream, rather than being
|
||||
* inserted at the encoder level. This has the behaviour to match the GL functionality. */
|
||||
* inserted at the encoder level. This has the behavior to match the GL functionality. */
|
||||
id<MTLEvent> mtl_event_ = nil;
|
||||
/* Events can be re-used multiple times. We can track a counter flagging the latest value
|
||||
* signalled. */
|
||||
|
|
|
@ -1860,7 +1860,7 @@ MTLPixelBuffer::MTLPixelBuffer(uint size) : PixelBuffer(size)
|
|||
{
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
/* Ensure buffer satifies the alignment of 256 bytes for copying
|
||||
/* Ensure buffer satisfies the alignment of 256 bytes for copying
|
||||
* data between buffers and textures. As specified in:
|
||||
* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
|
||||
BLI_assert(size >= 256);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
namespace blender::gpu {
|
||||
|
||||
VKPixelBuffer::VKPixelBuffer(int64_t size): PixelBuffer(size)
|
||||
VKPixelBuffer::VKPixelBuffer(int64_t size) : PixelBuffer(size)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ static void copy_property_from_node(const eNodeSocketDatatype property_type,
|
|||
if (!node) {
|
||||
return;
|
||||
}
|
||||
bNodeSocket *socket{nodeFindSocket(node, SOCK_IN, identifier)};
|
||||
const bNodeSocket *socket = nodeFindSocket(const_cast<bNode *>(node), SOCK_IN, identifier);
|
||||
BLI_assert(socket && socket->type == property_type);
|
||||
if (!socket) {
|
||||
return;
|
||||
|
@ -55,21 +55,21 @@ static void copy_property_from_node(const eNodeSocketDatatype property_type,
|
|||
switch (property_type) {
|
||||
case SOCK_FLOAT: {
|
||||
BLI_assert(r_property.size() == 1);
|
||||
bNodeSocketValueFloat *socket_def_value = static_cast<bNodeSocketValueFloat *>(
|
||||
const bNodeSocketValueFloat *socket_def_value = static_cast<const bNodeSocketValueFloat *>(
|
||||
socket->default_value);
|
||||
r_property[0] = socket_def_value->value;
|
||||
break;
|
||||
}
|
||||
case SOCK_RGBA: {
|
||||
BLI_assert(r_property.size() == 3);
|
||||
bNodeSocketValueRGBA *socket_def_value = static_cast<bNodeSocketValueRGBA *>(
|
||||
const bNodeSocketValueRGBA *socket_def_value = static_cast<const bNodeSocketValueRGBA *>(
|
||||
socket->default_value);
|
||||
copy_v3_v3(r_property.data(), socket_def_value->value);
|
||||
break;
|
||||
}
|
||||
case SOCK_VECTOR: {
|
||||
BLI_assert(r_property.size() == 3);
|
||||
bNodeSocketValueVector *socket_def_value = static_cast<bNodeSocketValueVector *>(
|
||||
const bNodeSocketValueVector *socket_def_value = static_cast<const bNodeSocketValueVector *>(
|
||||
socket->default_value);
|
||||
copy_v3_v3(r_property.data(), socket_def_value->value);
|
||||
break;
|
||||
|
|
|
@ -49,12 +49,14 @@ typedef struct MovieTrackingCamera {
|
|||
short units;
|
||||
char _pad1[2];
|
||||
|
||||
/* Principal point (optical center) stored in normalized coordinates.
|
||||
/**
|
||||
* Principal point (optical center) stored in normalized coordinates.
|
||||
*
|
||||
* The normalized space stores principal point relative to the frame center which has normalized
|
||||
* princibal coordinate of (0, 0). The right top corder of the frame corresponds to a notmalized
|
||||
* principal coordinate of (1, 1), and the left bottom cornder corresponds to coordinate of
|
||||
* (-1, -1). */
|
||||
* principal coordinate of (0, 0). The right top corer of the frame corresponds to a normalized
|
||||
* principal coordinate of (1, 1), and the left bottom corner corresponds to coordinate of
|
||||
* (-1, -1).
|
||||
*/
|
||||
float principal_point[2];
|
||||
|
||||
/** Legacy principal point in pixel space. */
|
||||
|
|
|
@ -1033,6 +1033,7 @@ static void rna_def_tex_slot(BlenderRNA *brna)
|
|||
RNA_def_property_string_funcs(
|
||||
prop, "rna_TexPaintSlot_name_get", "rna_TexPaintSlot_name_length", NULL);
|
||||
RNA_def_property_ui_text(prop, "Name", "Name of the slot");
|
||||
RNA_def_struct_name_property(srna, prop);
|
||||
|
||||
prop = RNA_def_property(srna, "icon_value", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
|
||||
#include "node_common.h"
|
||||
|
||||
#include "node_geometry_register.hh"
|
||||
|
||||
bNodeTreeType *ntreeType_Geometry;
|
||||
|
||||
static void geometry_node_tree_get_from_context(
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue