1
1

Compare commits

...

40 Commits

Author SHA1 Message Date
93c9f1d22d Progress on read only point separate node, doesn't work right now 2021-03-17 23:30:14 -04:00
40c2938209 Further cleanup 2021-03-17 20:54:12 -04:00
7bbd24e1d5 Merge branch 'master' into geometry-nodes-read-only-instances 2021-03-17 20:32:17 -04:00
9683976b02 Merge branch 'master' into geometry-nodes-read-only-instances 2021-03-02 14:41:28 -06:00
f0c9c7c200 Fix conflicts and build error from merge 2021-02-26 17:10:51 -06:00
109da73b98 Merge branch 'master' into temp-geometry-nodes-instances-api-v2 2021-02-26 16:24:56 -06:00
a108f2462f Rewrite point separate node to support instances (untested) 2021-02-14 21:12:04 -06:00
0357d70b8f Merge branch 'master' into temp-geometry-nodes-instances-api-v2 2021-02-14 15:32:10 -06:00
4b3e796a06 Merge branch 'master' into temp-geometry-nodes-instances-api-v2 2021-02-13 21:30:54 -06:00
ab1366464a Merge branch 'master' into temp-geometry-nodes-instances-api-v2 2021-02-13 21:06:44 -06:00
8de38bf42c Merge branch 'master' into temp-geometry-nodes-instances-api-v2 2021-02-07 14:33:52 -06:00
d7d853193e Support collection instances, completely break transforms 2021-02-07 00:14:13 -06:00
eb302e2501 Fix bug in instance node 2021-02-06 23:17:48 -06:00
a83abdd155 Remove callback instances API to focus on other approach 2021-02-06 22:54:06 -06:00
3b9c5a8f34 Remove extra function call 2021-02-06 21:28:42 -06:00
382047a3f5 WIP conversion of the point separate node to work with instances 2021-02-06 15:03:19 -06:00
e736453304 Support instances in the point instance node
This can use a read-only input since it just creates new data
2021-02-06 09:21:14 -06:00
a8a4d12d9a Support instances in the boolean node 2021-02-06 09:00:29 -06:00
80c3f12c08 Support instances in the points to volume node 2021-02-06 08:54:18 -06:00
63b9d7c365 Rename function 2021-02-06 08:54:08 -06:00
7b8fa71e70 Correct transform of instances 2021-02-05 18:30:49 -06:00
1f477c14d8 Merge branch 'master' into temp-geometry-nodes-instances-api-v2 2021-02-05 18:00:05 -06:00
8d7e8c0446 Add implicit make instances real operation for "write" nodes
Other nodes that create new geometry instead of modifying existing geometry
should read the geometry like the point distribute node does in this branch.
2021-02-05 17:56:14 -06:00
c8db750cf3 Merge branch 'temp-geometry-nodes-instances-api-v2' of git.blender.org:blender into temp-geometry-nodes-instances-api-v2 2021-02-05 11:39:27 -06:00
38dd9db9af Geometry Nodes: Instances API, support in point distribute node
This patch changes the object info node to always output an instance.
Theoretically, this change should be invisible to the user, because
we plan to make instances real on demand. The benefit is improved
performance because we can avoiding copying the input geometry when
it's only needed for read operations.

To make this work, we need a proper API to enable recursive reading
of instanced geometry. This is essential, because we can even have
collection instances in the `InstancesComponent`, which have no
geometry of their own. And then any object in that collection could
also contain an instance with a reference to another collection, etc.
That might seem crazy, but it's essential for many more complicated
workflows like the tree sample file.

Another consideration is that currently, every single instance contains
a reference to its data. This is inefficient since most of the time
there are many locations and only a few sets of unique data. When we
add the ability to instance a geometry set directly, this will be
more important. So this patch adds a `GeometryInstanceGroup` to support
this future optimization.

Two APIs to the instances component are provided here. First is a
callback approach that executes a function for every "final" geometry
set. This can be used to optimize for many instances, to avoid heap
allocations for each instance.

The second API is one that returns a vector of `GeometryInstanceGroup`.
This may be less efficient when there are many instances, but it makes
more complicated operations like point distribution that need to iterate
over input geometry multiple times much simpler.

Even then, this patch makes the point distribute code much more
complicated. It may be possible to simplify it further.

The last TODO is support for making instances real on demand, which
I am currently working on. The change to the object info node can't
land without it. The transform spaces may be incorrect too.

Differential Revision: https://developer.blender.org/D10327
2021-02-05 09:40:28 -06:00
488b50fb88 Merge branch 'master' into temp-geometry-nodes-instances-api-v2 2021-02-05 09:13:26 -06:00
134d55a458 Add comments 2021-02-04 21:26:06 -06:00
e53201f442 Remove debugging code 2021-02-04 21:20:49 -06:00
cec9331b24 Geometry Nodes: Point distribute support for instances
This completes the work to implement instancing on instances.
The density attribute works, and attributes are interpolated to the
output like before.

The code is much more complicated than before, and harder to understand.
Some cleanup is likely possible to help, but to an extent it is unavoidable.
2021-02-04 21:18:14 -06:00
6a7fd51180 Geometry Nodes: Better support instances in point instance node
This does the elimination by distance in a single step for all instances
instead of treating each input mesh separately.

This needs some cleanup, but the bigger TODO is that attribute interpolation
gets a fair amount more complex with this implementation. Needs more thought.
2021-02-03 23:56:56 -06:00
7ccfe67c9b WIP implementation, double vectors, sort of weird 2021-02-03 17:51:16 -06:00
8b11d36cda More progress converting the point distribute node to operations
I might not continue with this approach, it seems like building a
temporary vector of geometry components will be much simpler
2021-02-03 12:58:11 -06:00
d726aaec13 Merge branch 'master' into temp-geometry-nodes-instances-api 2021-02-03 11:59:12 -06:00
c407647469 WIP: Rewrite point distribute node to correctly support instances
This does not compile, and isn't close to finished.
2021-02-02 23:27:58 -06:00
c9383993f8 Add a second idea for the instances API 2021-02-02 23:22:35 -06:00
89d5710830 Merge branch 'master' into temp-geometry-nodes-instances-api 2021-02-02 22:16:42 -06:00
7746c562a4 Merge branch 'master' into temp-geometry-nodes-instances-api 2021-02-02 07:47:09 -06:00
525d36813c Geometry Nodes: Support instances in the point instance node 2021-02-01 17:18:32 -06:00
5494ad43fa Geometry Nodes: Output instanced geometry from the object info node 2021-02-01 17:17:39 -06:00
8268e733f6 Geometry Nodes: First pass on instance geometry set API
I think a fair amount of this will change, but this API uses a callback on
each component of a geometry set and its instances recursively.

Example uses will come in a following commit.
2021-02-01 17:17:15 -06:00
2 changed files with 285 additions and 145 deletions

View File

@@ -14,6 +14,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "BKE_attribute_math.hh"
#include "BKE_mesh.h"
#include "BKE_persistent_data_handle.hh"
#include "BKE_pointcloud.h"
@@ -36,165 +37,276 @@ static bNodeSocketTemplate geo_node_point_instance_out[] = {
{-1, ""},
};
using blender::bke::AttributeKind;
using blender::bke::GeometryInstanceGroup;
namespace blender::nodes {
static void fill_new_attribute_from_input(const ReadAttribute &input_attribute,
WriteAttribute &out_attribute_a,
WriteAttribute &out_attribute_b,
Span<bool> a_or_b)
static void gather_positions_from_component_instances(const GeometryComponent &component,
const StringRef mask_attribute_name,
Span<float4x4> transforms,
MutableSpan<Vector<float3>> r_positions_a,
MutableSpan<Vector<float3>> r_positions_b,
int &instance_index)
{
fn::GSpan in_span = input_attribute.get_span();
int i_a = 0;
int i_b = 0;
for (int i_in = 0; i_in < in_span.size(); i_in++) {
const bool move_to_b = a_or_b[i_in];
if (move_to_b) {
out_attribute_b.set(i_b, in_span[i_in]);
i_b++;
if (component.attribute_domain_size(ATTR_DOMAIN_POINT) == 0) {
return;
}
const BooleanReadAttribute mask_attribute = component.attribute_get_for_read<bool>(
mask_attribute_name, ATTR_DOMAIN_POINT, false);
const Float3ReadAttribute position_attribute = component.attribute_get_for_read<float3>(
"position", ATTR_DOMAIN_POINT, {0.0f, 0.0f, 0.0f});
Span<bool> masks = mask_attribute.get_span();
Span<float3> source_positions = position_attribute.get_span();
for (const int i_set_instance : transforms.index_range()) {
const float4x4 &transform = transforms[i_set_instance];
Vector<float3> &instance_result_positions_a = r_positions_a[instance_index];
Vector<float3> &instance_result_positions_b = r_positions_b[instance_index];
for (const int i : masks.index_range()) {
if (masks[i]) {
instance_result_positions_b.append(transform * source_positions[i]);
}
else {
instance_result_positions_a.append(transform * source_positions[i]);
}
}
instance_index++;
}
}
static void get_positions_from_instances(Span<GeometryInstanceGroup> set_groups,
const StringRef mask_attribute_name,
MutableSpan<Vector<float3>> r_positions_a,
MutableSpan<Vector<float3>> r_positions_b)
{
int instance_index = 0;
for (const GeometryInstanceGroup &set_group : set_groups) {
const GeometrySet &set = set_group.geometry_set;
if (set.has<MeshComponent>()) {
gather_positions_from_component_instances(*set.get_component_for_read<MeshComponent>(),
mask_attribute_name,
set_group.transforms,
r_positions_a,
r_positions_b,
instance_index);
}
if (set.has<PointCloudComponent>()) {
gather_positions_from_component_instances(*set.get_component_for_read<PointCloudComponent>(),
mask_attribute_name,
set_group.transforms,
r_positions_a,
r_positions_b,
instance_index);
}
}
}
static PointCloud *create_point_cloud(Span<Vector<float3>> positions)
{
int points_len = 0;
for (const Vector<float3> &instance_positions : positions) {
points_len += instance_positions.size();
}
PointCloud *pointcloud = BKE_pointcloud_new_nomain(points_len);
int offset = 0;
for (const Vector<float3> &instance_positions : positions) {
memcpy(pointcloud->co + offset, positions.data(), sizeof(float3) * instance_positions.size());
offset += instance_positions.size();
}
uninitialized_fill_n(pointcloud->radius, pointcloud->totpoint, 0.05f);
return pointcloud;
}
template<typename T>
static void copy_data_based_on_mask(Span<T> data,
Span<bool> masks,
MutableSpan<T> out_data_a,
MutableSpan<T> out_data_b,
int &offset_a,
int &offset_b)
{
for (const int i : data.index_range()) {
if (masks[i]) {
out_data_b[offset_b] = data[i];
offset_b++;
}
else {
out_attribute_a.set(i_a, in_span[i_in]);
i_a++;
out_data_a[offset_a] = data[i];
offset_a++;
}
}
}
/**
* Move the original attribute values to the two output components.
*
* \note This assumes a consistent ordering of indices before and after the split,
* which is true for points and a simple vertex array.
*/
static void move_split_attributes(const GeometryComponent &in_component,
GeometryComponent &out_component_a,
GeometryComponent &out_component_b,
Span<bool> a_or_b)
static void copy_attribute_from_component_instances(const GeometryComponent &component,
Span<float4x4> transforms,
const StringRef mask_attribute_name,
const StringRef attribute_name,
const CustomDataType data_type,
fn::GMutableSpan out_data_a,
fn::GMutableSpan out_data_b,
int &offset_a,
int &offset_b)
{
Set<std::string> attribute_names = in_component.attribute_names();
for (const std::string &name : attribute_names) {
ReadAttributePtr attribute = in_component.attribute_try_get_for_read(name);
BLI_assert(attribute);
/* Since this node only creates points and vertices, don't copy other attributes. */
if (attribute->domain() != ATTR_DOMAIN_POINT) {
continue;
}
const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute->cpp_type());
const AttributeDomain domain = attribute->domain();
/* Don't try to create the attribute on the new component if it already exists (i.e. has been
* initialized by someone else). */
if (!out_component_a.attribute_exists(name)) {
if (!out_component_a.attribute_try_create(name, domain, data_type)) {
continue;
}
}
if (!out_component_b.attribute_exists(name)) {
if (!out_component_b.attribute_try_create(name, domain, data_type)) {
continue;
}
}
WriteAttributePtr out_attribute_a = out_component_a.attribute_try_get_for_write(name);
WriteAttributePtr out_attribute_b = out_component_b.attribute_try_get_for_write(name);
if (!out_attribute_a || !out_attribute_b) {
BLI_assert(false);
continue;
}
fill_new_attribute_from_input(*attribute, *out_attribute_a, *out_attribute_b, a_or_b);
}
}
/**
* Find total in each new set and find which of the output sets each point will belong to.
*/
static Array<bool> count_point_splits(const GeometryComponent &component,
const GeoNodeExecParams &params,
int *r_a_total,
int *r_b_total)
{
const BooleanReadAttribute mask_attribute = params.get_input_attribute<bool>(
"Mask", component, ATTR_DOMAIN_POINT, false);
Array<bool> masks = mask_attribute.get_span();
const int in_total = masks.size();
*r_b_total = 0;
for (const bool mask : masks) {
if (mask) {
*r_b_total += 1;
}
}
*r_a_total = in_total - *r_b_total;
return masks;
}
static void separate_mesh(const MeshComponent &in_component,
const GeoNodeExecParams &params,
MeshComponent &out_component_a,
MeshComponent &out_component_b)
{
const int size = in_component.attribute_domain_size(ATTR_DOMAIN_POINT);
if (size == 0) {
if (component.attribute_domain_size(ATTR_DOMAIN_POINT) == 0) {
return;
}
int a_total;
int b_total;
Array<bool> a_or_b = count_point_splits(in_component, params, &a_total, &b_total);
const BooleanReadAttribute mask_attribute = component.attribute_get_for_read<bool>(
mask_attribute_name, ATTR_DOMAIN_POINT, false);
Span<bool> masks = mask_attribute.get_span();
out_component_a.replace(BKE_mesh_new_nomain(a_total, 0, 0, 0, 0));
out_component_b.replace(BKE_mesh_new_nomain(b_total, 0, 0, 0, 0));
const ReadAttributePtr attribute = component.attribute_try_get_for_read(
attribute_name, ATTR_DOMAIN_POINT, data_type);
move_split_attributes(in_component, out_component_a, out_component_b, a_or_b);
}
static void separate_point_cloud(const PointCloudComponent &in_component,
const GeoNodeExecParams &params,
PointCloudComponent &out_component_a,
PointCloudComponent &out_component_b)
{
const int size = in_component.attribute_domain_size(ATTR_DOMAIN_POINT);
if (size == 0) {
/* Advance offsets if the attribute doesn't exist. Note that this is inefficient since we already
* have this information, but we would need to keep track of the size of each component's result
* points. */
if (!attribute) {
for (const int i : masks.index_range()) {
if (masks[i]) {
offset_b++;
}
else {
offset_a++;
}
}
return;
}
int a_total;
int b_total;
Array<bool> a_or_b = count_point_splits(in_component, params, &a_total, &b_total);
const int start_offset_a = offset_a;
const int start_offset_b = offset_b;
out_component_a.replace(BKE_pointcloud_new_nomain(a_total));
out_component_b.replace(BKE_pointcloud_new_nomain(b_total));
attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
using T = decltype(dummy);
Span<T> span = attribute->get_span<T>();
MutableSpan<T> out_span_a = out_data_a.typed<T>();
MutableSpan<T> out_span_b = out_data_b.typed<T>();
copy_data_based_on_mask(span, masks, out_span_a, out_span_b, offset_a, offset_b);
const int copied_len_a = offset_a - start_offset_a;
const int copied_len_b = offset_b - start_offset_b;
move_split_attributes(in_component, out_component_a, out_component_b, a_or_b);
/* The data is the same for every instance of the same geometry, so just copy it. */
for (int i = 1; i < transforms.size(); i++) {
memcpy(out_span_a.data() + offset_a,
out_span_a.data() + start_offset_a,
sizeof(T) * copied_len_a);
memcpy(out_span_b.data() + offset_b,
out_span_b.data() + start_offset_b,
sizeof(T) * copied_len_b);
offset_a += copied_len_a;
offset_b += copied_len_b;
}
});
}
static void copy_attributes_to_output(Span<GeometryInstanceGroup> set_groups,
Map<std::string, AttributeKind> &result_attributes_info,
const StringRef mask_attribute_name,
PointCloudComponent &out_component_a,
PointCloudComponent &out_component_b)
{
for (Map<std::string, AttributeKind>::Item entry : result_attributes_info.items()) {
const StringRef attribute_name = entry.key;
/* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */
const CustomDataType output_data_type = entry.value.data_type;
OutputAttributePtr attribute_out_a = out_component_a.attribute_try_get_for_output(
attribute_name, ATTR_DOMAIN_POINT, output_data_type);
OutputAttributePtr attribute_out_b = out_component_b.attribute_try_get_for_output(
attribute_name, ATTR_DOMAIN_POINT, output_data_type);
BLI_assert(attribute_out_a && attribute_out_b);
if (!attribute_out_a || !attribute_out_b) {
continue;
}
fn::GMutableSpan out_span_a = attribute_out_a->get_span_for_write_only();
fn::GMutableSpan out_span_b = attribute_out_b->get_span_for_write_only();
int offset_a = 0;
int offset_b = 0;
for (const GeometryInstanceGroup &set_group : set_groups) {
const GeometrySet &set = set_group.geometry_set;
Span<float4x4> transforms = set_group.transforms;
if (set.has<MeshComponent>()) {
copy_attribute_from_component_instances(*set.get_component_for_read<MeshComponent>(),
transforms,
mask_attribute_name,
attribute_name,
output_data_type,
out_span_a,
out_span_b,
offset_a,
offset_b);
}
if (set.has<PointCloudComponent>()) {
copy_attribute_from_component_instances(*set.get_component_for_read<PointCloudComponent>(),
transforms,
mask_attribute_name,
attribute_name,
output_data_type,
out_span_a,
out_span_b,
offset_a,
offset_b);
}
}
attribute_out_a.apply_span_and_save();
attribute_out_b.apply_span_and_save();
}
}
static void geo_node_point_separate_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
GeometrySet out_set_a(geometry_set);
GeometrySet out_set_b;
const std::string mask_attribute_name = params.extract_input<std::string>("Mask");
/* TODO: This is not necessary-- the input geometry set can be read only,
* but it must be rewritten to handle instance groups. */
geometry_set = geometry_set_realize_instances(geometry_set);
Vector<GeometryInstanceGroup> set_groups = bke::geometry_set_gather_instances(geometry_set);
if (geometry_set.has<PointCloudComponent>()) {
separate_point_cloud(*geometry_set.get_component_for_read<PointCloudComponent>(),
params,
out_set_a.get_component_for_write<PointCloudComponent>(),
out_set_b.get_component_for_write<PointCloudComponent>());
/* Remove any set inputs that don't contain points, to avoid checking later on. */
for (int i = set_groups.size() - 1; i >= 0; i--) {
const GeometrySet &set = set_groups[i].geometry_set;
if (!set.has_mesh() && !set.has_pointcloud()) {
set_groups.remove_and_reorder(i);
}
}
if (geometry_set.has<MeshComponent>()) {
separate_mesh(*geometry_set.get_component_for_read<MeshComponent>(),
params,
out_set_a.get_component_for_write<MeshComponent>(),
out_set_b.get_component_for_write<MeshComponent>());
if (set_groups.is_empty()) {
params.set_output("Geometry 1", std::move(GeometrySet()));
params.set_output("Geometry 2", std::move(GeometrySet()));
return;
}
int instances_len = 0;
for (const GeometryInstanceGroup &set_group : set_groups) {
instances_len += set_group.transforms.size();
}
Array<Vector<float3>> positions_a(instances_len);
Array<Vector<float3>> positions_b(instances_len);
get_positions_from_instances(set_groups, mask_attribute_name, positions_a, positions_b);
GeometrySet out_set_a = GeometrySet::create_with_pointcloud(create_point_cloud(positions_a));
GeometrySet out_set_b = GeometrySet::create_with_pointcloud(create_point_cloud(positions_b));
Map<std::string, AttributeKind> result_attributes_info;
bke::gather_attribute_info(result_attributes_info,
{GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD},
set_groups,
{"position"});
copy_attributes_to_output(set_groups,
result_attributes_info,
mask_attribute_name,
out_set_a.get_component_for_write<PointCloudComponent>(),
out_set_b.get_component_for_write<PointCloudComponent>());
params.set_output("Geometry 1", std::move(out_set_a));
params.set_output("Geometry 2", std::move(out_set_b));
}

View File

@@ -28,6 +28,9 @@
#include "UI_interface.h"
#include "UI_resources.h"
using blender::bke::AttributeKind;
using blender::bke::GeometryInstanceGroup;
static bNodeSocketTemplate geo_node_points_to_volume_in[] = {
{SOCK_GEOMETRY, N_("Geometry")},
{SOCK_FLOAT, N_("Density"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX},
@@ -142,18 +145,31 @@ static float compute_voxel_size(const GeoNodeExecParams &params,
return voxel_size;
}
static void gather_point_data_from_component(const GeoNodeExecParams &params,
const GeometryComponent &component,
Vector<float3> &r_positions,
Vector<float> &r_radii)
static void gather_point_data_from_component_transforms(const GeoNodeExecParams &params,
const GeometryComponent &component,
Vector<float3> &r_positions,
Vector<float> &r_radii,
Span<float4x4> transforms)
{
Float3ReadAttribute positions = component.attribute_get_for_read<float3>(
"position", ATTR_DOMAIN_POINT, {0, 0, 0});
FloatReadAttribute radii = params.get_input_attribute<float>(
ReadAttributePtr positions_attribte = component.attribute_try_get_for_read(
"position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3);
if (!positions_attribte) {
return;
}
FloatReadAttribute radii_attribute = params.get_input_attribute<float>(
"Radius", component, ATTR_DOMAIN_POINT, 0.0f);
Span<float> radii = radii_attribute.get_span();
Span<float3> positions = positions_attribte->get_span<float3>();
r_positions.extend(positions.get_span());
r_radii.extend(radii.get_span());
r_positions.reserve(r_positions.size() +
component.attribute_domain_size(ATTR_DOMAIN_POINT) * transforms.size());
for (const float4x4 &transform : transforms) {
for (const float3 position : positions) {
r_positions.append(transform * position);
}
r_radii.extend(radii);
}
}
static void convert_to_grid_index_space(const float voxel_size,
@@ -176,13 +192,25 @@ static void initialize_volume_component_from_points(const GeometrySet &geometry_
Vector<float3> positions;
Vector<float> radii;
if (geometry_set_in.has<MeshComponent>()) {
gather_point_data_from_component(
params, *geometry_set_in.get_component_for_read<MeshComponent>(), positions, radii);
}
if (geometry_set_in.has<PointCloudComponent>()) {
gather_point_data_from_component(
params, *geometry_set_in.get_component_for_read<PointCloudComponent>(), positions, radii);
Vector<GeometryInstanceGroup> set_groups = bke::geometry_set_gather_instances(geometry_set_in);
for (const GeometryInstanceGroup &set_group : set_groups) {
const GeometrySet &set = set_group.geometry_set;
if (set.has<MeshComponent>()) {
gather_point_data_from_component_transforms(params,
*set.get_component_for_read<MeshComponent>(),
positions,
radii,
set_group.transforms);
}
if (set.has<PointCloudComponent>()) {
gather_point_data_from_component_transforms(
params,
*set.get_component_for_read<PointCloudComponent>(),
positions,
radii,
set_group.transforms);
}
}
const float max_radius = *std::max_element(radii.begin(), radii.end());