sverchok
sverchok copied to clipboard
Random Points on Mesh node new functionalities
Could it be possible to add to the volumetric mode of Random Points on Mesh a parameterization via a scalar field, like in Popuplate Solid? It is just so slow to convert a mesh to a solid, and then back when you only need to perform one not-so-heavy operation.
Good idea :) It's just a question of someone finding time to implement.
So could you label this issue as a proposal?
@ArpegorPSGH another approach (which may be faster than converting to Solid) is to use a combination of
a Vector P Field
node and a Point inside Mesh
node.
https://github.com/nortikin/sverchok/blob/master/docs/nodes/analyzer/points_inside_mesh.rst
- Make the vector field similar to the bounding box of the Mesh you wish to use for the shape
- In P field make a fine grain xyz field, and use some randomization and "remove doubles distance" to get a fast approximation of a homogenous vector field distribution.
- pass the shapemesh and PField into the Point Inside Mesh node and that way filter out which vectors are not inside the shape mesh.
creating a moderately sized homogenousely spaced vector field is not necessarily a low computation operation, finding if points are inside or outside a mesh is more bruteforce but computation time will depend on complexity of shapemesh and point density of the field.
Well, I already circumvented it, but not elegantly, by generating my points inside a very simple mesh converted to solid, then filtrating with points inside mesh. However, I cannot know how many points I get in the end, I can just compare it to the target, and then regenerate more points to get closer. Being able to get the exact number of points in one pass is what I would like to do.
Being able to get the exact number of points in one pass is what I would like to do
in all likelyhood the operation you describe is relatively heavy. To set a desired number of "inside mesh" points, that might involve an iterative process. (a complex volume will be heavier)
- first the node takes a guess of point distances, and starts at an extremity of the volume.
- then if there are too many points in the volume, then it should increase the radius/distance between points.(and start over)
- a bit like Newtons Method for finding the square root.
- or gradient descent with backtracking. ideal for machine learning .. but still a lot of recomputation until a desired point-count is achieved.
I don't think there is a need for multiple passes, as the node shouldn't start at one extremity of the volume, but instead just generate one point at a time, following the scalar field distribution limited by the mesh bounding box, then if the point is inside, it is accepted, if not it is rejected. So, each point is calculated only once.
in that case i look forward to seeing the solution :)
The algorithm is not very simple, but not too complicated also. https://github.com/nortikin/sverchok/blob/6291c91cc9fb2b90330321262a05695a593471fd/utils/field/probe.py#L40
- Generate a bunch of points
- Drop some of them with probability defined by scalar field
- of those which were not dropped, drop those which are outside of solid (or mesh in this case)
- If there are not enough points, continue with new bunch of points
Points are generated in bunches, not one-by-one, in order to effectively use numpy vectorization, which is implemented in many scalar field classes.
Well, the principle is te same, but indeed using numpy great vectorization performance is more efficient.
@portnov something like
"""
>in shape_v v
>in shape_f s
in count s d=50 n=2
out verts v
"""
from sverchok.utils.field.probe import field_random_probe
from sverchok.utils.field.scalar import SvBvhAttractorScalarField
from sverchok.nodes.analyzer.bbox_mk3 import bounding_box
from sverchok.utils.sv_mesh_utils import point_inside_mesh
from sverchok.utils.bvh_tree import bvh_tree_from_polygons
for geom in zip(shape_v, shape_f):
bbox = bounding_box([geom[0]],
box_dimensions='3D',
output_verts=False,
output_mat=False,
output_mean=False,
output_limits=True)
field = SvBvhAttractorScalarField(verts=geom[0], faces=geom[1]) # ?
# bbox: nested tuple: ((min_x, min_y, min_z), (max_x, max_y, max_z))
n_bbox = tuple(((bbox[4][0][0], bbox[5][0][0], bbox[6][0][0]), (bbox[7][0][0], bbox[8][0][0], bbox[9][0][0])))
new_points, _ = field_random_probe(field, n_bbox, count) #,
# threshold=0, proportional=False, field_min=None, field_max=None,
# min_r=0, min_r_field=None,
# random_radius = False,
# seed=0, predicate=None)
bvh = bvh_tree_from_polygons(geom[0], geom[1], all_triangles=False, epsilon=0.0, safe_check=True)
points = [p for p in new_points if point_inside_mesh(bvh, p)]
verts.append(points)
@zeffii you can pass lambda p: point_inside_mesh(bvh, p)
as predicate
parameter to field_random_probe
. It will be a bit faster, as you remove another pass through all points.
And obviously you can use any other scalar field.
@portnov sweet :) cobbling this together was relatively painless.
something like
"""
>in shape_v v
>in shape_f s
in count s d=50 n=2
out verts v
"""
from sverchok.utils.field.probe import field_random_probe
from sverchok.utils.field.scalar import SvBvhAttractorScalarField
from sverchok.nodes.analyzer.bbox_mk3 import bounding_box
from sverchok.utils.sv_mesh_utils import point_inside_mesh
from sverchok.utils.bvh_tree import bvh_tree_from_polygons
for geom in zip(shape_v, shape_f):
bbox = bounding_box(
[geom[0]],
box_dimensions='3D', output_verts=False, output_mat=False, output_mean=False,
output_limits=True)
co = lambda n: bbox[n][0][0]
# ((min_x, min_y, min_z), (max_x, max_y, max_z))
n_bbox = ((co(4), co(5), co(6)), (co(7), co(8), co(9)))
field = SvBvhAttractorScalarField(verts=geom[0], faces=geom[1])
bvh = bvh_tree_from_polygons(geom[0], geom[1], all_triangles=False, epsilon=0.0, safe_check=True)
points, _ = field_random_probe(field, n_bbox, count, predicate=lambda p: point_inside_mesh(bvh, p))
verts.append(points)
There is also bounding_box
method in sverchok.utils.geom
.
ok. perfect. @portnov
"""
>in shape_v v
>in shape_f s
in count s d=50 n=2
out out_verts v
"""
# - number of points are not guaranteed
# - points created as uniform random, not homogenous (reasonably equidistant from other points)
from sverchok.utils.field.probe import field_random_probe
from sverchok.utils.sv_mesh_utils import point_inside_mesh as pim
from sverchok.utils.field.scalar import SvBvhAttractorScalarField as Field
from sverchok.utils.bvh_tree import bvh_tree_from_polygons as bvh_tree
from sverchok.utils.geom import bounding_box
for verts, faces in zip(shape_v, shape_f):
bbox = bounding_box(verts)
field = Field(verts=verts, faces=faces)
bvh = bvh_tree(verts, faces, all_triangles=False, epsilon=0.0, safe_check=True)
points, _ = field_random_probe(field, (bbox.min, bbox.max), count, predicate=lambda p: pim(bvh, p))
out_verts.append(points)
but isn't this kind of ...very much... like
If you replace the scalar field with another one, the result can be very much unlike :)
"""
>in shape_v v
>in shape_f s
in field_in SF
in threshold_in s
in count s d=50 n=2
out out_verts v
"""
from sverchok.utils.field.probe import field_random_probe
from sverchok.utils.sv_mesh_utils import point_inside_mesh as pim
from sverchok.utils.bvh_tree import bvh_tree_from_polygons as bvh_tree
from sverchok.utils.geom import bounding_box
for verts, faces, field, threshold in zip(shape_v, shape_f, field_in, threshold_in):
bbox = bounding_box(verts)
bvh = bvh_tree(verts, faces, all_triangles=False, epsilon=0.0, safe_check=True)
points, _ = field_random_probe(field,
(bbox.min, bbox.max), count,
threshold = threshold[0],
predicate=lambda p: pim(bvh, p))
out_verts.append(points)
yet another field, with the same script.
yes! i see. many possibilities
Great. Now could it be possible to add a distance parameter to set a minimum distance between points, and to merge that functionalities in the Random Points on Mesh Node?
with a distance parameter, the "number of points" becomes a function of the minimum distance
.
a merge by distance
post processing step would achieve this ( as a cheap solution, otherwise the process is iterative and more computationally intense as i suggest above)
"""
>in shape_v v
>in shape_f s
in count s d=50 n=2
in min_distance s d=0.10 n=2
out out_verts v
"""
import bmesh
from sverchok.utils.field.probe import field_random_probe
from sverchok.utils.sv_mesh_utils import point_inside_mesh as pim
from sverchok.utils.field.scalar import SvBvhAttractorScalarField as Field
from sverchok.utils.bvh_tree import bvh_tree_from_polygons as bvh_tree
from sverchok.utils.geom import bounding_box
for verts, faces in zip(shape_v, shape_f):
bbox = bounding_box(verts)
field = Field(verts=verts, faces=faces)
bvh = bvh_tree(verts, faces, all_triangles=False, epsilon=0.0, safe_check=True)
points, _ = field_random_probe(field, (bbox.min, bbox.max), count, predicate=lambda p: pim(bvh, p))
bm = bmesh_from_pydata(points, [], [])
bm_verts = bm.verts
bmesh.ops.remove_doubles(bm, verts=bm_verts, dist=min_distance)
sparse_verts, _, _ = pydata_from_bmesh(bm)
out_verts.append(sparse_verts or points)
Do you get the exact number wanted with that? Also, adding a min distance does not mean discarding the scalar field, it should still be used for generating points.
@zeffii
In field_random_probe
function, you can provide min_r or min_r_field parameter.
Do you get the exact number wanted with that?
maybe i'm being thick here, but the exact number of points is a function of point distance and vice versa
- if you specify minimum distance you will get a certain number of points in homogenous 3d distribution
- if you specify number of points, that will automatically determine the point distance in a homogenous 3d distribution
@portnov
"""
>in shape_v v
>in shape_f s
in count s d=50 n=2
in min_distance s d=0.10 n=2
out out_verts v
"""
import bmesh
from sverchok.utils.field.probe import field_random_probe
from sverchok.utils.sv_mesh_utils import point_inside_mesh as pim
from sverchok.utils.field.scalar import SvBvhAttractorScalarField as Field
from sverchok.utils.bvh_tree import bvh_tree_from_polygons as bvh_tree
from sverchok.utils.geom import bounding_box
for verts, faces in zip(shape_v, shape_f):
bbox = bounding_box(verts)
field = Field(verts=verts, faces=faces)
bvh = bvh_tree(verts, faces, all_triangles=False, epsilon=0.0, safe_check=True)
points, _ = field_random_probe(field, (bbox.min, bbox.max), count, min_r=min_distance, predicate=lambda p: pim(bvh, p))
out_verts.append(points)
nice. much simpler, but does quickly throw:
sverchok.utils.field.probe 86: Maximum number of iterations (1000) reached, stop.
Yeah, obviously, if you set high enough "minimal radius", it will be hard to find a place where you can put another point.
That error is not a problem, it just means your parameters are not realistic given your mesh. Of course, the minimal radius should be small compared to the size of your mesh and the number of points you want. It is just to not have too large a density of points where the scalar field is the highest.
That error is not a problem, it just means your parameters are not realistic given your mesh.
naturally. exactly my point. it makes little sense to have two parameters (exposed to the user) affecting a system which are in essence eachother's reciprocal.
i'm probably missing the point, but i'll bow out of this convo now :)
They are reciprocal parameters only if you consider homogeneous distribution or if you fill te entire mesh up to the maximal possible density. It definitely makes sense if you have a custom scalar field and just want to be sure you won't get any points too close to each other where the scalar field is the highest.