Merge branch 'master' into sculpt-dev

This commit is contained in:
Joseph Eagar 2021-12-20 14:37:48 -05:00
commit 134eccf5bf
15 changed files with 9274 additions and 33 deletions

View File

@ -0,0 +1,253 @@
/*
* Copyright 2011-2013 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "scene/pointcloud.h"
#include "scene/attribute.h"
#include "scene/scene.h"
#include "blender/sync.h"
#include "blender/util.h"
#include "util/foreach.h"
#include "util/hash.h"
CCL_NAMESPACE_BEGIN
template<typename TypeInCycles, typename GetValueAtIndex>
static void fill_generic_attribute(BL::PointCloud &b_pointcloud,
TypeInCycles *data,
const GetValueAtIndex &get_value_at_index)
{
const int num_points = b_pointcloud.points.length();
for (int i = 0; i < num_points; i++) {
data[i] = get_value_at_index(i);
}
}
static void copy_attributes(PointCloud *pointcloud, BL::PointCloud b_pointcloud)
{
AttributeSet &attributes = pointcloud->attributes;
for (BL::Attribute &b_attribute : b_pointcloud.attributes) {
const ustring name{b_attribute.name().c_str()};
if (attributes.find(name)) {
continue;
}
const AttributeElement element = ATTR_ELEMENT_VERTEX;
const BL::Attribute::data_type_enum b_data_type = b_attribute.data_type();
switch (b_data_type) {
case BL::Attribute::data_type_FLOAT: {
BL::FloatAttribute b_float_attribute{b_attribute};
Attribute *attr = attributes.add(name, TypeFloat, element);
float *data = attr->data_float();
fill_generic_attribute(
b_pointcloud, data, [&](int i) { return b_float_attribute.data[i].value(); });
break;
}
case BL::Attribute::data_type_BOOLEAN: {
BL::BoolAttribute b_bool_attribute{b_attribute};
Attribute *attr = attributes.add(name, TypeFloat, element);
float *data = attr->data_float();
fill_generic_attribute(
b_pointcloud, data, [&](int i) { return (float)b_bool_attribute.data[i].value(); });
break;
}
case BL::Attribute::data_type_INT: {
BL::IntAttribute b_int_attribute{b_attribute};
Attribute *attr = attributes.add(name, TypeFloat, element);
float *data = attr->data_float();
fill_generic_attribute(
b_pointcloud, data, [&](int i) { return (float)b_int_attribute.data[i].value(); });
break;
}
case BL::Attribute::data_type_FLOAT_VECTOR: {
BL::FloatVectorAttribute b_vector_attribute{b_attribute};
Attribute *attr = attributes.add(name, TypeVector, element);
float3 *data = attr->data_float3();
fill_generic_attribute(b_pointcloud, data, [&](int i) {
BL::Array<float, 3> v = b_vector_attribute.data[i].vector();
return make_float3(v[0], v[1], v[2]);
});
break;
}
case BL::Attribute::data_type_FLOAT_COLOR: {
BL::FloatColorAttribute b_color_attribute{b_attribute};
Attribute *attr = attributes.add(name, TypeRGBA, element);
float4 *data = attr->data_float4();
fill_generic_attribute(b_pointcloud, data, [&](int i) {
BL::Array<float, 4> v = b_color_attribute.data[i].color();
return make_float4(v[0], v[1], v[2], v[3]);
});
break;
}
case BL::Attribute::data_type_FLOAT2: {
BL::Float2Attribute b_float2_attribute{b_attribute};
Attribute *attr = attributes.add(name, TypeFloat2, element);
float2 *data = attr->data_float2();
fill_generic_attribute(b_pointcloud, data, [&](int i) {
BL::Array<float, 2> v = b_float2_attribute.data[i].vector();
return make_float2(v[0], v[1]);
});
break;
}
default:
/* Not supported. */
break;
}
}
}
static void export_pointcloud(Scene *scene, PointCloud *pointcloud, BL::PointCloud b_pointcloud)
{
/* TODO: optimize so we can straight memcpy arrays from Blender? */
/* Add requested attributes. */
Attribute *attr_random = NULL;
if (pointcloud->need_attribute(scene, ATTR_STD_POINT_RANDOM)) {
attr_random = pointcloud->attributes.add(ATTR_STD_POINT_RANDOM);
}
/* Reserve memory. */
const int num_points = b_pointcloud.points.length();
pointcloud->reserve(num_points);
/* Export points. */
BL::PointCloud::points_iterator b_point_iter;
for (b_pointcloud.points.begin(b_point_iter); b_point_iter != b_pointcloud.points.end();
++b_point_iter) {
BL::Point b_point = *b_point_iter;
const float3 co = get_float3(b_point.co());
const float radius = b_point.radius();
pointcloud->add_point(co, radius);
/* Random number per point. */
if (attr_random != NULL) {
attr_random->add(hash_uint2_to_float(b_point.index(), 0));
}
}
/* Export attributes */
copy_attributes(pointcloud, b_pointcloud);
}
static void export_pointcloud_motion(PointCloud *pointcloud,
BL::PointCloud b_pointcloud,
int motion_step)
{
/* Find or add attribute. */
Attribute *attr_mP = pointcloud->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
bool new_attribute = false;
if (!attr_mP) {
attr_mP = pointcloud->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION);
new_attribute = true;
}
/* Export motion points. */
const int num_points = pointcloud->num_points();
float3 *mP = attr_mP->data_float3() + motion_step * num_points;
bool have_motion = false;
int num_motion_points = 0;
const array<float3> &pointcloud_points = pointcloud->get_points();
BL::PointCloud::points_iterator b_point_iter;
for (b_pointcloud.points.begin(b_point_iter); b_point_iter != b_pointcloud.points.end();
++b_point_iter) {
BL::Point b_point = *b_point_iter;
if (num_motion_points < num_points) {
float3 P = get_float3(b_point.co());
P.w = b_point.radius();
mP[num_motion_points] = P;
have_motion = have_motion || (P != pointcloud_points[num_motion_points]);
num_motion_points++;
}
}
/* In case of new attribute, we verify if there really was any motion. */
if (new_attribute) {
if (num_motion_points != num_points || !have_motion) {
pointcloud->attributes.remove(ATTR_STD_MOTION_VERTEX_POSITION);
}
else if (motion_step > 0) {
/* Motion, fill up previous steps that we might have skipped because
* they had no motion, but we need them anyway now. */
for (int step = 0; step < motion_step; step++) {
pointcloud->copy_center_to_motion_step(step);
}
}
}
/* Export attributes */
copy_attributes(pointcloud, b_pointcloud);
}
void BlenderSync::sync_pointcloud(PointCloud *pointcloud, BObjectInfo &b_ob_info)
{
size_t old_numpoints = pointcloud->num_points();
array<Node *> used_shaders = pointcloud->get_used_shaders();
PointCloud new_pointcloud;
new_pointcloud.set_used_shaders(used_shaders);
/* TODO: add option to filter out points in the view layer. */
BL::PointCloud b_pointcloud(b_ob_info.object_data);
export_pointcloud(scene, &new_pointcloud, b_pointcloud);
/* update original sockets */
for (const SocketType &socket : new_pointcloud.type->inputs) {
/* Those sockets are updated in sync_object, so do not modify them. */
if (socket.name == "use_motion_blur" || socket.name == "motion_steps" ||
socket.name == "used_shaders") {
continue;
}
pointcloud->set_value(socket, new_pointcloud, socket);
}
pointcloud->attributes.clear();
foreach (Attribute &attr, new_pointcloud.attributes.attributes) {
pointcloud->attributes.attributes.push_back(std::move(attr));
}
/* tag update */
const bool rebuild = (pointcloud && old_numpoints != pointcloud->num_points());
pointcloud->tag_update(scene, rebuild);
}
void BlenderSync::sync_pointcloud_motion(PointCloud *pointcloud,
BObjectInfo &b_ob_info,
int motion_step)
{
/* Skip if nothing exported. */
if (pointcloud->num_points() == 0) {
return;
}
/* Export deformed coordinates. */
if (ccl::BKE_object_is_deform_modified(b_ob_info, b_scene, preview)) {
/* PointCloud object. */
BL::PointCloud b_pointcloud(b_ob_info.object_data);
export_pointcloud_motion(pointcloud, b_pointcloud, motion_step);
}
else {
/* No deformation on this frame, copy coordinates if other frames did have it. */
pointcloud->copy_center_to_motion_step(motion_step);
}
}
CCL_NAMESPACE_END

View File

@ -179,11 +179,14 @@ set(SRC_KERNEL_GEOM_HEADERS
geom/curve.h
geom/curve_intersect.h
geom/motion_curve.h
geom/motion_point.h
geom/motion_triangle.h
geom/motion_triangle_intersect.h
geom/motion_triangle_shader.h
geom/object.h
geom/patch.h
geom/point.h
geom/point_intersect.h
geom/primitive.h
geom/shader_data.h
geom/subd_triangle.h

View File

@ -0,0 +1,74 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
CCL_NAMESPACE_BEGIN
/* Motion Point Primitive
*
* These are stored as regular points, plus extra positions and radii at times
* other than the frame center. Computing the point at a given ray time is
* a matter of interpolation of the two steps between which the ray time lies.
*
* The extra points are stored as ATTR_STD_MOTION_VERTEX_POSITION.
*/
#ifdef __POINTCLOUD__
ccl_device_inline float4
motion_point_for_step(KernelGlobals kg, int offset, int numkeys, int numsteps, int step, int prim)
{
if (step == numsteps) {
/* center step: regular key location */
return kernel_tex_fetch(__points, prim);
}
else {
/* center step is not stored in this array */
if (step > numsteps)
step--;
offset += step * numkeys;
return kernel_tex_fetch(__attributes_float4, offset + prim);
}
}
/* return 2 point key locations */
ccl_device_inline float4 motion_point(KernelGlobals kg, int object, int prim, float time)
{
/* get motion info */
int numsteps, numkeys;
object_motion_info(kg, object, &numsteps, NULL, &numkeys);
/* figure out which steps we need to fetch and their interpolation factor */
int maxstep = numsteps * 2;
int step = min((int)(time * maxstep), maxstep - 1);
float t = time * maxstep - step;
/* find attribute */
int offset = intersection_find_attribute(kg, object, ATTR_STD_MOTION_VERTEX_POSITION);
kernel_assert(offset != ATTR_STD_NOT_FOUND);
/* fetch key coordinates */
float4 point = motion_point_for_step(kg, offset, numkeys, numsteps, step, prim);
float4 next_point = motion_point_for_step(kg, offset, numkeys, numsteps, step + 1, prim);
/* interpolate between steps */
return (1.0f - t) * point + t * next_point;
}
#endif
CCL_NAMESPACE_END

View File

@ -0,0 +1,133 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
CCL_NAMESPACE_BEGIN
/* Point Primitive
*
* Point primitive for rendering point clouds.
*/
#ifdef __POINTCLOUD__
/* Reading attributes on various point elements */
ccl_device float point_attribute_float(KernelGlobals kg,
ccl_private const ShaderData *sd,
const AttributeDescriptor desc,
ccl_private float *dx,
ccl_private float *dy)
{
# ifdef __RAY_DIFFERENTIALS__
if (dx)
*dx = 0.0f;
if (dy)
*dy = 0.0f;
# endif
if (desc.element == ATTR_ELEMENT_VERTEX) {
return kernel_tex_fetch(__attributes_float, desc.offset + sd->prim);
}
else {
return 0.0f;
}
}
ccl_device float2 point_attribute_float2(KernelGlobals kg,
ccl_private const ShaderData *sd,
const AttributeDescriptor desc,
ccl_private float2 *dx,
ccl_private float2 *dy)
{
# ifdef __RAY_DIFFERENTIALS__
if (dx)
*dx = make_float2(0.0f, 0.0f);
if (dy)
*dy = make_float2(0.0f, 0.0f);
# endif
if (desc.element == ATTR_ELEMENT_VERTEX) {
return kernel_tex_fetch(__attributes_float2, desc.offset + sd->prim);
}
else {
return make_float2(0.0f, 0.0f);
}
}
ccl_device float3 point_attribute_float3(KernelGlobals kg,
ccl_private const ShaderData *sd,
const AttributeDescriptor desc,
ccl_private float3 *dx,
ccl_private float3 *dy)
{
# ifdef __RAY_DIFFERENTIALS__
if (dx)
*dx = make_float3(0.0f, 0.0f, 0.0f);
if (dy)
*dy = make_float3(0.0f, 0.0f, 0.0f);
# endif
if (desc.element == ATTR_ELEMENT_VERTEX) {
return float4_to_float3(kernel_tex_fetch(__attributes_float4, desc.offset + sd->prim));
}
else {
return make_float3(0.0f, 0.0f, 0.0f);
}
}
ccl_device float4 point_attribute_float4(KernelGlobals kg,
ccl_private const ShaderData *sd,
const AttributeDescriptor desc,
ccl_private float4 *dx,
ccl_private float4 *dy)
{
# ifdef __RAY_DIFFERENTIALS__
if (dx)
*dx = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
if (dy)
*dy = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
# endif
if (desc.element == ATTR_ELEMENT_VERTEX) {
return kernel_tex_fetch(__attributes_float4, desc.offset + sd->prim);
}
else {
return make_float4(0.0f, 0.0f, 0.0f, 0.0f);
}
}
/* Point radius */
ccl_device float point_radius(KernelGlobals kg, ccl_private const ShaderData *sd)
{
if (sd->type & PRIMITIVE_POINT) {
return kernel_tex_fetch(__points, sd->prim).w;
}
return 0.0f;
}
/* Point location for motion pass, linear interpolation between keys and
* ignoring radius because we do the same for the motion keys */
ccl_device float3 point_motion_center_location(KernelGlobals kg, ccl_private const ShaderData *sd)
{
return float4_to_float3(kernel_tex_fetch(__points, sd->prim));
}
#endif /* __POINTCLOUD__ */
CCL_NAMESPACE_END

View File

@ -0,0 +1,128 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Based on Embree code, copyright 2009-2020 Intel Corporation.
*/
#pragma once
CCL_NAMESPACE_BEGIN
/* Point primitive intersection functions. */
#ifdef __POINTCLOUD__
ccl_device_forceinline bool point_intersect_test(
const float4 point, const float3 P, const float3 dir, const float tmax, ccl_private float *t)
{
const float3 center = float4_to_float3(point);
const float radius = point.w;
const float rd2 = 1.0f / dot(dir, dir);
const float3 c0 = center - P;
const float projC0 = dot(c0, dir) * rd2;
const float3 perp = c0 - projC0 * dir;
const float l2 = dot(perp, perp);
const float r2 = radius * radius;
if (!(l2 <= r2)) {
return false;
}
const float td = sqrt((r2 - l2) * rd2);
const float t_front = projC0 - td;
const bool valid_front = (0.0f <= t_front) & (t_front <= tmax);
/* Always back-face culling for now. */
# if 0
const float t_back = projC0 + td;
const bool valid_back = (0.0f <= t_back) & (t_back <= tmax);
/* check if there is a first hit */
const bool valid_first = valid_front | valid_back;
if (!valid_first) {
return false;
}
*t = (valid_front) ? t_front : t_back;
return true;
# else
if (!valid_front) {
return false;
}
*t = t_front;
return true;
# endif
}
ccl_device_forceinline bool point_intersect(KernelGlobals kg,
ccl_private Intersection *isect,
const float3 P,
const float3 dir,
const float tmax,
const int object,
const int prim,
const float time,
const int type)
{
const float4 point = (type & PRIMITIVE_MOTION) ? motion_point(kg, object, prim, time) :
kernel_tex_fetch(__points, prim);
if (!point_intersect_test(point, P, dir, tmax, &isect->t)) {
return false;
}
isect->prim = prim;
isect->object = object;
isect->type = type;
isect->u = 0.0f;
isect->v = 0.0f;
return true;
}
ccl_device_inline void point_shader_setup(KernelGlobals kg,
ccl_private ShaderData *sd,
ccl_private const Intersection *isect,
ccl_private const Ray *ray)
{
sd->shader = kernel_tex_fetch(__points_shader, isect->prim);
sd->P = ray->P + ray->D * isect->t;
/* Texture coordinates, zero for now. */
# ifdef __UV__
sd->u = isect->u;
sd->v = isect->v;
# endif
/* Compute point center for normal. */
float3 center = float4_to_float3((isect->type & PRIMITIVE_MOTION) ?
motion_point(kg, sd->object, sd->prim, sd->time) :
kernel_tex_fetch(__points, sd->prim));
if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
object_position_transform_auto(kg, sd, &center);
}
/* Normal */
sd->Ng = normalize(sd->P - center);
sd->N = sd->Ng;
# ifdef __DPDU__
/* dPdu/dPdv */
sd->dPdu = make_float3(0.0f, 0.0f, 0.0f);
sd->dPdv = make_float3(0.0f, 0.0f, 0.0f);
# endif
}
#endif
CCL_NAMESPACE_END

View File

@ -0,0 +1,304 @@
/*
* Copyright 2011-2020 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "bvh/bvh.h"
#include "scene/pointcloud.h"
#include "scene/scene.h"
CCL_NAMESPACE_BEGIN
/* PointCloud Point */
void PointCloud::Point::bounds_grow(const float3 *points,
const float *radius,
BoundBox &bounds) const
{
bounds.grow(points[index], radius[index]);
}
void PointCloud::Point::bounds_grow(const float3 *points,
const float *radius,
const Transform &aligned_space,
BoundBox &bounds) const
{
float3 P = transform_point(&aligned_space, points[index]);
bounds.grow(P, radius[index]);
}
void PointCloud::Point::bounds_grow(const float4 &point, BoundBox &bounds) const
{
bounds.grow(float4_to_float3(point), point.w);
}
float4 PointCloud::Point::motion_key(const float3 *points,
const float *radius,
const float3 *point_steps,
size_t num_points,
size_t num_steps,
float time,
size_t p) const
{
/* Figure out which steps we need to fetch and their
* interpolation factor. */
const size_t max_step = num_steps - 1;
const size_t step = min((int)(time * max_step), max_step - 1);
const float t = time * max_step - step;
/* Fetch vertex coordinates. */
const float4 curr_key = point_for_step(
points, radius, point_steps, num_points, num_steps, step, p);
const float4 next_key = point_for_step(
points, radius, point_steps, num_points, num_steps, step + 1, p);
/* Interpolate between steps. */
return (1.0f - t) * curr_key + t * next_key;
}
float4 PointCloud::Point::point_for_step(const float3 *points,
const float *radius,
const float3 *point_steps,
size_t num_points,
size_t num_steps,
size_t step,
size_t p) const
{
const size_t center_step = ((num_steps - 1) / 2);
if (step == center_step) {
/* Center step: regular key location. */
return make_float4(points[p].x, points[p].y, points[p].z, radius[p]);
}
else {
/* Center step is not stored in this array. */
if (step > center_step) {
step--;
}
const size_t offset = step * num_points;
return make_float4(point_steps[offset + p].x,
point_steps[offset + p].y,
point_steps[offset + p].z,
radius[offset + p]);
}
}
/* PointCloud */
NODE_DEFINE(PointCloud)
{
NodeType *type = NodeType::add(
"pointcloud", create, NodeType::NONE, Geometry::get_node_base_type());
SOCKET_POINT_ARRAY(points, "Points", array<float3>());
SOCKET_FLOAT_ARRAY(radius, "Radius", array<float>());
SOCKET_INT_ARRAY(shader, "Shader", array<int>());
return type;
}
PointCloud::PointCloud() : Geometry(node_type, Geometry::POINTCLOUD)
{
}
PointCloud::~PointCloud()
{
}
void PointCloud::resize(int numpoints)
{
points.resize(numpoints);
radius.resize(numpoints);
shader.resize(numpoints);
attributes.resize();
tag_points_modified();
tag_radius_modified();
tag_shader_modified();
}
void PointCloud::reserve(int numpoints)
{
points.reserve(numpoints);
radius.reserve(numpoints);
shader.reserve(numpoints);
attributes.resize(true);
}
void PointCloud::clear(const bool preserve_shaders)
{
Geometry::clear(preserve_shaders);
points.clear();
radius.clear();
shader.clear();
attributes.clear();
tag_points_modified();
tag_radius_modified();
tag_shader_modified();
}
void PointCloud::add_point(float3 co, float r, int shader_index)
{
points.push_back_reserved(co);
radius.push_back_reserved(r);
shader.push_back_reserved(shader_index);
tag_points_modified();
tag_radius_modified();
tag_shader_modified();
}
void PointCloud::copy_center_to_motion_step(const int motion_step)
{
Attribute *attr_mP = attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
if (attr_mP) {
float3 *points_data = points.data();
size_t numpoints = points.size();
memcpy(
attr_mP->data_float3() + motion_step * numpoints, points_data, sizeof(float3) * numpoints);
}
}
void PointCloud::get_uv_tiles(ustring map, unordered_set<int> &tiles)
{
Attribute *attr;
if (map.empty()) {
attr = attributes.find(ATTR_STD_UV);
}
else {
attr = attributes.find(map);
}
if (attr) {
attr->get_uv_tiles(this, ATTR_PRIM_GEOMETRY, tiles);
}
}
void PointCloud::compute_bounds()
{
BoundBox bnds = BoundBox::empty;
size_t numpoints = points.size();
if (numpoints > 0) {
for (size_t i = 0; i < numpoints; i++) {
bnds.grow(points[i], radius[i]);
}
Attribute *attr = attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
if (use_motion_blur && attr) {
size_t steps_size = points.size() * (motion_steps - 1);
float3 *point_steps = attr->data_float3();
for (size_t i = 0; i < steps_size; i++)
bnds.grow(point_steps[i]);
}
if (!bnds.valid()) {
bnds = BoundBox::empty;
/* skip nan or inf coordinates */
for (size_t i = 0; i < numpoints; i++)
bnds.grow_safe(points[i], radius[i]);
if (use_motion_blur && attr) {
size_t steps_size = points.size() * (motion_steps - 1);
float3 *point_steps = attr->data_float3();
for (size_t i = 0; i < steps_size; i++)
bnds.grow_safe(point_steps[i]);
}
}
}
if (!bnds.valid()) {
/* empty mesh */
bnds.grow(make_float3(0.0f, 0.0f, 0.0f));
}
bounds = bnds;
}
void PointCloud::apply_transform(const Transform &tfm, const bool apply_to_motion)
{
/* compute uniform scale */
float3 c0 = transform_get_column(&tfm, 0);
float3 c1 = transform_get_column(&tfm, 1);
float3 c2 = transform_get_column(&tfm, 2);
float scalar = powf(fabsf(dot(cross(c0, c1), c2)), 1.0f / 3.0f);
/* apply transform to curve keys */
for (size_t i = 0; i < points.size(); i++) {
float3 co = transform_point(&tfm, points[i]);
float r = radius[i] * scalar;
/* scale for curve radius is only correct for uniform scale
*/
points[i] = co;
radius[i] = r;
}
if (apply_to_motion) {
Attribute *attr = attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
if (attr) {
/* apply transform to motion curve keys */
size_t steps_size = points.size() * (motion_steps - 1);
float4 *point_steps = attr->data_float4();
for (size_t i = 0; i < steps_size; i++) {
float3 co = transform_point(&tfm, float4_to_float3(point_steps[i]));
float radius = point_steps[i].w * scalar;
/* scale for curve radius is only correct for uniform
* scale */
point_steps[i] = float3_to_float4(co);
point_steps[i].w = radius;
}
}
}
}
void PointCloud::pack(Scene *scene, float4 *packed_points, uint *packed_shader)
{
size_t numpoints = points.size();
float3 *points_data = points.data();
float *radius_data = radius.data();
int *shader_data = shader.data();
for (size_t i = 0; i < numpoints; i++) {
packed_points[i] = make_float4(
points_data[i].x, points_data[i].y, points_data[i].z, radius_data[i]);
}
uint shader_id = 0;
uint last_shader = -1;
for (size_t i = 0; i < numpoints; i++) {
if (last_shader != shader_data[i]) {
last_shader = shader_data[i];
Shader *shader = (last_shader < used_shaders.size()) ?
static_cast<Shader *>(used_shaders[last_shader]) :
scene->default_surface;
shader_id = scene->shader_manager->get_shader_id(shader);
}
packed_shader[i] = shader_id;
}
}
PrimitiveType PointCloud::primitive_type() const
{
return has_motion_blur() ? PRIMITIVE_MOTION_POINT : PRIMITIVE_POINT;
}
CCL_NAMESPACE_END

View File

@ -0,0 +1,114 @@
/*
* Copyright 2011-2020 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#ifndef __POINTCLOUD_H__
# define __POINTCLOUD_H__
# include "scene/geometry.h"
CCL_NAMESPACE_BEGIN
class PointCloud : public Geometry {
public:
NODE_DECLARE
/* PointCloud Point */
struct Point {
int index;
void bounds_grow(const float3 *points, const float *radius, BoundBox &bounds) const;
void bounds_grow(const float3 *points,
const float *radius,
const Transform &aligned_space,
BoundBox &bounds) const;
void bounds_grow(const float4 &point, BoundBox &bounds) const;
float4 motion_key(const float3 *points,
const float *radius,
const float3 *point_steps,
size_t num_points,
size_t num_steps,
float time,
size_t p) const;
float4 point_for_step(const float3 *points,
const float *radius,
const float3 *point_steps,
size_t num_points,
size_t num_steps,
size_t step,
size_t p) const;
};
NODE_SOCKET_API_ARRAY(array<float3>, points)
NODE_SOCKET_API_ARRAY(array<float>, radius)
NODE_SOCKET_API_ARRAY(array<int>, shader)
/* Constructor/Destructor */
PointCloud();
~PointCloud();
/* Geometry */
void clear(const bool preserver_shaders = false) override;
void resize(int numpoints);
void reserve(int numpoints);
void add_point(float3 loc, float radius, int shader = 0);
void copy_center_to_motion_step(const int motion_step);
void compute_bounds() override;
void apply_transform(const Transform &tfm, const bool apply_to_motion) override;
/* Points */
Point get_point(int i) const
{
Point point = {i};
return point;
}
size_t num_points() const
{
return points.size();
}
size_t num_attributes() const
{
return 1;
}
/* UDIM */
void get_uv_tiles(ustring map, unordered_set<int> &tiles) override;
PrimitiveType primitive_type() const override;
/* BVH */
void pack(Scene *scene, float4 *packed_points, uint *packed_shader);
private:
friend class BVH2;
friend class BVHBuild;
friend class BVHSpatialSplit;
friend class DiagSplit;
friend class EdgeDice;
friend class GeometryManager;
friend class ObjectManager;
};
CCL_NAMESPACE_END
#endif /* __POINTCLOUD_H__ */

View File

@ -267,6 +267,49 @@ void MEM_use_guarded_allocator(void);
#endif /* __cplusplus */
#ifdef __cplusplus
# include <type_traits>
# include <utility>
/**
* Allocate new memory for and constructs an object of type #T.
* #MEM_delete should be used to delete the object. Just calling #MEM_freeN is not enough when #T
* is not a trivial type.
*/
template<typename T, typename... Args>
inline T *MEM_new(const char *allocation_name, Args &&...args)
{
void *buffer = MEM_mallocN(sizeof(T), allocation_name);
return new (buffer) T(std::forward<Args>(args)...);
}
/**
* Allocates zero-initialized memory for an object of type #T. The constructor of #T is not called,
* therefor this should only used with trivial types (like all C types).
* It's valid to call #MEM_freeN on a pointer returned by this, because a destructor call is not
* necessary, because the type is trivial.
*/
template<typename T> inline T *MEM_cnew(const char *allocation_name)
{
static_assert(std::is_trivial_v<T>, "For non-trivial types, MEM_new should be used.");
return static_cast<T *>(MEM_callocN(sizeof(T), allocation_name));
}
/**
* Destructs and deallocates an object previously allocated with any `MEM_*` function.
* Passing in null does nothing.
*/
template<typename T> inline void MEM_delete(const T *ptr)
{
if (ptr == nullptr) {
/* Support #ptr being null, because C++ `delete` supports that as well. */
return;
}
/* C++ allows destruction of const objects, so the pointer is allowed to be const. */
ptr->~T();
MEM_freeN(const_cast<T *>(ptr));
}
/* Allocation functions (for C++ only). */
# define MEM_CXX_CLASS_ALLOC_FUNCS(_id) \
public: \
@ -300,36 +343,6 @@ void MEM_use_guarded_allocator(void);
{ \
}
/* Needed when type includes a namespace, then the namespace should not be
* specified after ~, so using a macro fails. */
template<class T> inline void OBJECT_GUARDED_DESTRUCTOR(T *what)
{
what->~T();
}
# if defined __GNUC__
# define OBJECT_GUARDED_NEW(type, args...) new (MEM_mallocN(sizeof(type), __func__)) type(args)
# else
# define OBJECT_GUARDED_NEW(type, ...) \
new (MEM_mallocN(sizeof(type), __FUNCTION__)) type(__VA_ARGS__)
# endif
# define OBJECT_GUARDED_DELETE(what, type) \
{ \
if (what) { \
OBJECT_GUARDED_DESTRUCTOR((type *)what); \
MEM_freeN(what); \
} \
} \
(void)0
# define OBJECT_GUARDED_SAFE_DELETE(what, type) \
{ \
if (what) { \
OBJECT_GUARDED_DESTRUCTOR((type *)what); \
MEM_freeN(what); \
what = NULL; \
} \
} \
(void)0
#endif /* __cplusplus */
#endif /* __MEM_GUARDEDALLOC_H__ */

File diff suppressed because it is too large Load Diff

View File

@ -3084,5 +3084,21 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
*/
{
/* Keep this block, even when empty. */
/* Add node storage for map range node. */
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type == SH_NODE_MAP_RANGE) {
if (node->storage == NULL) {
NodeMapRange *data = MEM_callocN(sizeof(NodeMapRange), __func__);
data->clamp = node->custom1;
data->data_type = CD_PROP_FLOAT;
data->interpolation_type = node->custom2;
node->storage = data;
}
}
}
}
FOREACH_NODETREE_END;
}
}

View File

@ -0,0 +1,291 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "BLI_listbase.h"
#include "BLI_string_search.h"
#include "DNA_space_types.h"
#include "BKE_context.h"
#include "NOD_socket_search_link.hh"
#include "BLT_translation.h"
#include "RNA_access.h"
#include "WM_api.h"
#include "node_intern.hh"
using blender::nodes::SocketLinkOperation;
namespace blender::ed::space_node {
struct LinkDragSearchStorage {
bNode &from_node;
bNodeSocket &from_socket;
float2 cursor;
Vector<SocketLinkOperation> search_link_ops;
char search[256];
eNodeSocketInOut in_out() const
{
return static_cast<eNodeSocketInOut>(from_socket.in_out);
}
};
static void add_reroute_node_fn(nodes::LinkSearchOpParams &params)
{
bNode &reroute = params.add_node("NodeReroute");
if (params.socket.in_out == SOCK_IN) {
nodeAddLink(&params.node_tree,
&reroute,
static_cast<bNodeSocket *>(reroute.outputs.first),
&params.node,
&params.socket);
}
else {
nodeAddLink(&params.node_tree,
&params.node,
&params.socket,
&reroute,
static_cast<bNodeSocket *>(reroute.inputs.first));
}
}
static void add_group_input_node_fn(nodes::LinkSearchOpParams &params)
{
/* Add a group input based on the connected socket, and add a new group input node. */
bNodeSocket *interface_socket = ntreeAddSocketInterfaceFromSocket(
&params.node_tree, &params.node, &params.socket);
const int group_input_index = BLI_findindex(&params.node_tree.inputs, interface_socket);
bNode &group_input = params.add_node("NodeGroupInput");
/* This is necessary to create the new sockets in the other input nodes. */
ntreeUpdateTree(CTX_data_main(&params.C), &params.node_tree);
/* Hide the new input in all other group input nodes, to avoid making them taller. */
LISTBASE_FOREACH (bNode *, node, &params.node_tree.nodes) {
if (node->type == NODE_GROUP_INPUT) {
bNodeSocket *new_group_input_socket = (bNodeSocket *)BLI_findlink(&node->outputs,
group_input_index);
new_group_input_socket->flag |= SOCK_HIDDEN;
}
}
/* Hide all existing inputs in the new group input node, to only display the new one. */
LISTBASE_FOREACH (bNodeSocket *, socket, &group_input.outputs) {
socket->flag |= SOCK_HIDDEN;
}
bNodeSocket *socket = (bNodeSocket *)BLI_findlink(&group_input.outputs, group_input_index);
if (socket == nullptr) {
/* Adding sockets can fail in some cases. There's no good reason not to be safe here. */
return;
}
/* Unhide the socket for the new input in the new node and make a connection to it. */
socket->flag &= ~SOCK_HIDDEN;
nodeAddLink(&params.node_tree, &group_input, socket, &params.node, &params.socket);
}
/**
* Call the callback to gather compatible socket connections for all node types, and the operations
* that will actually make the connections. Also add some custom operations like connecting a group
* output node.
*/
static void gather_socket_link_operations(bNodeTree &node_tree,
const bNodeSocket &socket,
Vector<SocketLinkOperation> &search_link_ops)
{
NODE_TYPES_BEGIN (node_type) {
if (StringRef(node_type->idname).find("Legacy") != StringRef::not_found) {
continue;
}
const char *disabled_hint;
if (!(node_type->poll && node_type->poll(node_type, &node_tree, &disabled_hint))) {
continue;
}
if (node_type->gather_link_search_ops) {
nodes::GatherLinkSearchOpParams params{*node_type, node_tree, socket, search_link_ops};
node_type->gather_link_search_ops(params);
}
}
NODE_TYPES_END;
search_link_ops.append({IFACE_("Reroute"), add_reroute_node_fn});
const bool is_node_group = !(node_tree.id.flag & LIB_EMBEDDED_DATA);
if (is_node_group && socket.in_out == SOCK_IN) {
search_link_ops.append({IFACE_("Group Input"), add_group_input_node_fn});
}
}
static void link_drag_search_update_fn(const bContext *UNUSED(C),
void *arg,
const char *str,
uiSearchItems *items,
const bool is_first)
{
LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg);
StringSearch *search = BLI_string_search_new();
for (SocketLinkOperation &op : storage.search_link_ops) {
BLI_string_search_add(search, op.name.c_str(), &op, op.weight);
}
/* Don't filter when the menu is first opened, but still run the search
* so the items are in the same order they will appear in while searching. */
const char *string = is_first ? "" : str;
SocketLinkOperation **filtered_items;
const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items);
for (const int i : IndexRange(filtered_amount)) {
SocketLinkOperation &item = *filtered_items[i];
if (!UI_search_item_add(items, item.name.c_str(), &item, ICON_NONE, 0, 0)) {
break;
}
}
MEM_freeN(filtered_items);
BLI_string_search_free(search);
}
static void link_drag_search_exec_fn(bContext *C, void *arg1, void *arg2)
{
Main &bmain = *CTX_data_main(C);
SpaceNode &snode = *CTX_wm_space_node(C);
LinkDragSearchStorage &storage = *static_cast<LinkDragSearchStorage *>(arg1);
SocketLinkOperation *item = static_cast<SocketLinkOperation *>(arg2);
if (item == nullptr) {
return;
}
node_deselect_all(snode);
Vector<bNode *> new_nodes;
nodes::LinkSearchOpParams params{
*C, *snode.edittree, storage.from_node, storage.from_socket, new_nodes};
item->fn(params);
if (new_nodes.is_empty()) {
return;
}
/* For now, assume that only one node is created by the callback. */
BLI_assert(new_nodes.size() == 1);
bNode *new_node = new_nodes.first();
new_node->locx = storage.cursor.x / UI_DPI_FAC;
new_node->locy = storage.cursor.y / UI_DPI_FAC + 20 * UI_DPI_FAC;
if (storage.in_out() == SOCK_IN) {
new_node->locx -= new_node->width;
}
nodeSetSelected(new_node, true);
nodeSetActive(snode.edittree, new_node);
/* Ideally it would be possible to tag the node tree in some way so it updates only after the
* translate operation is finished, but normally moving nodes around doesn't cause updates. */
ntreeUpdateTree(&bmain, snode.edittree);
snode_notify(*C, snode);
snode_dag_update(*C, snode);
/* Start translation operator with the new node. */
wmOperatorType *ot = WM_operatortype_find("TRANSFORM_OT_translate", true);
BLI_assert(ot);
PointerRNA ptr;
WM_operator_properties_create_ptr(&ptr, ot);
RNA_boolean_set(&ptr, "view2d_edge_pan", true);
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr);
WM_operator_properties_free(&ptr);
}
static void link_drag_search_free_fn(void *arg)
{
LinkDragSearchStorage *storage = static_cast<LinkDragSearchStorage *>(arg);
delete storage;
}
static uiBlock *create_search_popup_block(bContext *C, ARegion *region, void *arg_op)
{
LinkDragSearchStorage &storage = *(LinkDragSearchStorage *)arg_op;
bNodeTree *node_tree = CTX_wm_space_node(C)->nodetree;
gather_socket_link_operations(*node_tree, storage.from_socket, storage.search_link_ops);
uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS);
UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
uiBut *but = uiDefSearchBut(block,
storage.search,
0,
ICON_VIEWZOOM,
sizeof(storage.search),
storage.in_out() == SOCK_OUT ? 10 : 10 - UI_searchbox_size_x(),
10,
UI_searchbox_size_x(),
UI_UNIT_Y,
0,
0,
"");
UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
UI_but_func_search_set(but,
nullptr,
link_drag_search_update_fn,
&storage,
false,
link_drag_search_free_fn,
link_drag_search_exec_fn,
nullptr);
UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
/* Fake button to hold space for the search items. */
uiDefBut(block,
UI_BTYPE_LABEL,
0,
"",
storage.in_out() == SOCK_OUT ? 10 : 10 - UI_searchbox_size_x(),
10 - UI_searchbox_size_y(),
UI_searchbox_size_x(),
UI_searchbox_size_y(),
nullptr,
0,
0,
0,
0,
nullptr);
const int offset[2] = {0, -UI_UNIT_Y};
UI_block_bounds_set_popup(block, 0.3f * U.widget_unit, offset);
return block;
}
void invoke_node_link_drag_add_menu(bContext &C,
bNode &node,
bNodeSocket &socket,
const float2 &cursor)
{
LinkDragSearchStorage *storage = new LinkDragSearchStorage{node, socket, cursor};
/* Use the "_ex" variant with `can_refresh` false to avoid a double free when closing Blender. */
UI_popup_block_invoke_ex(&C, create_search_popup_block, storage, nullptr, false);
}
} // namespace blender::ed::space_node

View File

@ -969,11 +969,9 @@ typedef struct ParticleEditSettings {
struct Object *shape_object;
} ParticleEditSettings;
/* ------------------------------------------- */
/* Sculpt */
struct BrushChannelSet;
/* ------------------------------------------- */
/** Sculpt. */
typedef struct Sculpt {
Paint paint;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,151 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
#include <functional>
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "DNA_node_types.h" /* Necessary for eNodeSocketInOut. */
#include "NOD_node_declaration.hh"
struct bContext;
namespace blender::nodes {
/**
* Parameters for the operation operation of adding a node after the link drag search menu closes.
*/
class LinkSearchOpParams {
private:
/**
* Keep track of the nodes added by the callback, so they can be selected or moved afterwards.
*/
Vector<bNode *> &added_nodes_;
public:
const bContext &C;
bNodeTree &node_tree;
/**
* The node that contains the #socket.
*/
bNode &node;
/**
* The existing socket to connect any added nodes to. Might be an input or output socket.
*/
bNodeSocket &socket;
LinkSearchOpParams(const bContext &C,
bNodeTree &node_tree,
bNode &node,
bNodeSocket &socket,
Vector<bNode *> &added_nodes)
: added_nodes_(added_nodes), C(C), node_tree(node_tree), node(node), socket(socket)
{
}
bNode &add_node(StringRef idname);
bNode &add_node(const bNodeType &type);
/**
* Find a socket with the given name (correctly checks for inputs and outputs)
* and connect it to the socket the link drag started from (#socket).
*/
void connect_available_socket(bNode &new_node, StringRef socket_name);
/**
* Like #connect_available_socket, but also calls the node's update function.
*/
void update_and_connect_available_socket(bNode &new_node, StringRef socket_name);
};
struct SocketLinkOperation {
using LinkSocketFn = std::function<void(LinkSearchOpParams &link_params)>;
std::string name;
LinkSocketFn fn;
int weight = 0;
};
class GatherLinkSearchOpParams {
/** The current node type. */
const bNodeType &node_type_;
const bNodeTree &node_tree_;
const bNodeSocket &other_socket_;
/* The operations currently being built. Owned by the caller. */
Vector<SocketLinkOperation> &items_;
public:
GatherLinkSearchOpParams(const bNodeType &node_type,
const bNodeTree &node_tree,
const bNodeSocket &other_socket,
Vector<SocketLinkOperation> &items)
: node_type_(node_type), node_tree_(node_tree), other_socket_(other_socket), items_(items)
{
}
/**
* The node on the other side of the dragged link.
*/
const bNodeSocket &other_socket() const;
/**
* The node tree the user is editing when the search menu is created.
*/
const bNodeTree &node_tree() const;
/**
* The type of the node in the current callback.
*/
const bNodeType &node_type() const;
/**
* Whether to list the input or output sockets of the node.
*/
eNodeSocketInOut in_out() const;
/**
* \param weight: Used to customize the order when multiple search items match.
*
* \warning When creating lambdas for the #fn argument, be careful not to capture this class
* itself, since it is temporary. That is why we tend to use the same variable name for this
* class (`params`) that we do for the argument to `LinkSocketFn`.
*/
void add_item(std::string socket_name, SocketLinkOperation::LinkSocketFn fn, int weight = 0);
};
/**
* This callback can be used for a node type when a few things are true about its inputs.
* To avoid creating more boilerplate, it is the default callback for node types.
* - Either all declared sockets are visible in the default state of the node, *OR* the node's
* type's declaration has been extended with #make_available functions for those sockets.
*
* If a node type does not meet these criteria, the function will do nothing in a release build.
* In a debug build, an assert will most likely be hit.
*
* \note For nodes with the deprecated #bNodeSocketTemplate instead of a declaration,
* these criteria do not apply and the function just tries its best without asserting.
*/
void search_link_ops_for_basic_node(GatherLinkSearchOpParams &params);
void search_link_ops_for_declarations(GatherLinkSearchOpParams &params,
Span<SocketDeclarationPtr> declarations);
} // namespace blender::nodes

View File

@ -0,0 +1,199 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "BLI_set.hh"
#include "BKE_node.h"
#include "UI_interface.h"
#include "NOD_node_declaration.hh"
#include "NOD_socket_search_link.hh"
namespace blender::nodes {
void GatherLinkSearchOpParams::add_item(std::string socket_name,
SocketLinkOperation::LinkSocketFn fn,
const int weight)
{
std::string name = std::string(node_type_.ui_name) + " " + UI_MENU_ARROW_SEP + socket_name;
items_.append({std::move(name), std::move(fn), weight});
}
const bNodeSocket &GatherLinkSearchOpParams::other_socket() const
{
return other_socket_;
}
const bNodeTree &GatherLinkSearchOpParams::node_tree() const
{
return node_tree_;
}
const bNodeType &GatherLinkSearchOpParams::node_type() const
{
return node_type_;
}
eNodeSocketInOut GatherLinkSearchOpParams::in_out() const
{
return other_socket_.in_out == SOCK_IN ? SOCK_OUT : SOCK_IN;
}
void LinkSearchOpParams::connect_available_socket(bNode &new_node, StringRef socket_name)
{
const eNodeSocketInOut in_out = socket.in_out == SOCK_IN ? SOCK_OUT : SOCK_IN;
bNodeSocket *new_node_socket = bke::node_find_enabled_socket(new_node, in_out, socket_name);
if (new_node_socket == nullptr) {
/* If the socket isn't found, some node's search gather functions probably aren't configured
* properly. It's likely enough that it's worth avoiding a crash in a release build though. */
BLI_assert_unreachable();
return;
}
nodeAddLink(&node_tree, &new_node, new_node_socket, &node, &socket);
}
bNode &LinkSearchOpParams::add_node(StringRef idname)
{
std::string idname_str = idname;
bNode *node = nodeAddNode(&C, &node_tree, idname_str.c_str());
BLI_assert(node != nullptr);
added_nodes_.append(node);
return *node;
}
bNode &LinkSearchOpParams::add_node(const bNodeType &node_type)
{
return this->add_node(node_type.idname);
}
void LinkSearchOpParams::update_and_connect_available_socket(bNode &new_node,
StringRef socket_name)
{
if (new_node.typeinfo->updatefunc) {
new_node.typeinfo->updatefunc(&node_tree, &new_node);
}
this->connect_available_socket(new_node, socket_name);
}
void search_link_ops_for_declarations(GatherLinkSearchOpParams &params,
Span<SocketDeclarationPtr> declarations)
{
const bNodeType &node_type = params.node_type();
const SocketDeclaration *main_socket = nullptr;
Vector<const SocketDeclaration *> connectable_sockets;
Set<StringRef> socket_names;
for (const int i : declarations.index_range()) {
const SocketDeclaration &socket = *declarations[i];
if (!socket_names.add(socket.name())) {
/* Don't add sockets with the same name to the search. Needed to support being called from
* #search_link_ops_for_basic_node, which should have "okay" behavior for nodes with
* duplicate socket names. */
continue;
}
if (!socket.can_connect(params.other_socket())) {
continue;
}
if (socket.is_default_link_socket() || main_socket == nullptr) {
/* Either the first connectable or explicitly tagged socket is the main socket. */
main_socket = &socket;
}
connectable_sockets.append(&socket);
}
for (const int i : connectable_sockets.index_range()) {
const SocketDeclaration &socket = *connectable_sockets[i];
/* Give non-main sockets a lower weight so that they don't show up at the top of the search
* when they are not explicitly searched for. The -1 is used to make sure that the first socket
* has a smaller weight than zero so that it does not have the same weight as the main socket.
* Negative weights are used to avoid making the highest weight dependent on the number of
* sockets. */
const int weight = (&socket == main_socket) ? 0 : -1 - i;
params.add_item(
socket.name(),
[&node_type, &socket](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
socket.make_available(node);
params.update_and_connect_available_socket(node, socket.name());
},
weight);
}
}
static void search_link_ops_for_socket_templates(GatherLinkSearchOpParams &params,
const bNodeSocketTemplate *templates,
const eNodeSocketInOut in_out)
{
const bNodeType &node_type = params.node_type();
const bNodeTreeType &node_tree_type = *params.node_tree().typeinfo;
Set<StringRef> socket_names;
for (const bNodeSocketTemplate *socket_template = templates; socket_template->type != -1;
socket_template++) {
eNodeSocketDatatype from = (eNodeSocketDatatype)socket_template->type;
eNodeSocketDatatype to = (eNodeSocketDatatype)params.other_socket().type;
if (in_out == SOCK_IN) {
std::swap(from, to);
}
if (node_tree_type.validate_link && !node_tree_type.validate_link(from, to)) {
continue;
}
if (!socket_names.add(socket_template->name)) {
/* See comment in #search_link_ops_for_declarations. */
continue;
}
params.add_item(
socket_template->name, [socket_template, node_type, in_out](LinkSearchOpParams &params) {
bNode &node = params.add_node(node_type);
bNodeSocket *new_node_socket = bke::node_find_enabled_socket(
node, in_out, socket_template->name);
if (new_node_socket != nullptr) {
/* Rely on the way #nodeAddLink switches in/out if necessary. */
nodeAddLink(&params.node_tree, &params.node, &params.socket, &node, new_node_socket);
}
});
}
}
void search_link_ops_for_basic_node(GatherLinkSearchOpParams &params)
{
const bNodeType &node_type = params.node_type();
if (node_type.declare) {
if (node_type.declaration_is_dynamic) {
/* Dynamic declarations (whatever they end up being) aren't supported
* by this function, but still avoid a crash in release builds. */
BLI_assert_unreachable();
return;
}
const NodeDeclaration &declaration = *node_type.fixed_declaration;
search_link_ops_for_declarations(params, declaration.sockets(params.in_out()));
}
else if (node_type.inputs && params.in_out() == SOCK_IN) {
search_link_ops_for_socket_templates(params, node_type.inputs, SOCK_IN);
}
else if (node_type.outputs && params.in_out() == SOCK_OUT) {
search_link_ops_for_socket_templates(params, node_type.outputs, SOCK_OUT);
}
}
} // namespace blender::nodes