sverchok icon indicating copy to clipboard operation
sverchok copied to clipboard

Random Points on Mesh node new functionalities

Open ArpegorPSGH opened this issue 2 years ago • 35 comments

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.

ArpegorPSGH avatar Apr 24 '22 15:04 ArpegorPSGH

Good idea :) It's just a question of someone finding time to implement.

portnov avatar Apr 24 '22 15:04 portnov

So could you label this issue as a proposal?

ArpegorPSGH avatar Apr 24 '22 18:04 ArpegorPSGH

@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.

zeffii avatar Apr 25 '22 08:04 zeffii

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.

ArpegorPSGH avatar Apr 25 '22 11:04 ArpegorPSGH

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.

zeffii avatar Apr 25 '22 17:04 zeffii

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.

ArpegorPSGH avatar Apr 25 '22 18:04 ArpegorPSGH

in that case i look forward to seeing the solution :)

zeffii avatar Apr 25 '22 19:04 zeffii

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.

portnov avatar Apr 26 '22 06:04 portnov

Well, the principle is te same, but indeed using numpy great vectorization performance is more efficient.

ArpegorPSGH avatar Apr 26 '22 07:04 ArpegorPSGH

@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)

image

zeffii avatar Apr 26 '22 10:04 zeffii

@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 avatar Apr 26 '22 13:04 portnov

@portnov sweet :) cobbling this together was relatively painless.

zeffii avatar Apr 26 '22 14:04 zeffii

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)

zeffii avatar Apr 26 '22 14:04 zeffii

There is also bounding_box method in sverchok.utils.geom.

portnov avatar Apr 27 '22 08:04 portnov

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)

zeffii avatar Apr 27 '22 09:04 zeffii

but isn't this kind of ...very much... like image

zeffii avatar Apr 27 '22 16:04 zeffii

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)

Screenshot_20220427_214426

portnov avatar Apr 27 '22 16:04 portnov

yet another field, with the same script. Screenshot_20220427_214849 Screenshot_20220427_221139

portnov avatar Apr 27 '22 16:04 portnov

yes! i see. many possibilities

zeffii avatar Apr 27 '22 17:04 zeffii

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?

ArpegorPSGH avatar Apr 28 '22 08:04 ArpegorPSGH

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)

image

zeffii avatar Apr 28 '22 08:04 zeffii

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.

ArpegorPSGH avatar Apr 28 '22 09:04 ArpegorPSGH

@zeffii In field_random_probe function, you can provide min_r or min_r_field parameter.

portnov avatar Apr 28 '22 09:04 portnov

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

zeffii avatar Apr 28 '22 09:04 zeffii

@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.

zeffii avatar Apr 28 '22 09:04 zeffii

Yeah, obviously, if you set high enough "minimal radius", it will be hard to find a place where you can put another point.

portnov avatar Apr 28 '22 10:04 portnov

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.

ArpegorPSGH avatar Apr 28 '22 10:04 ArpegorPSGH

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.

zeffii avatar Apr 28 '22 11:04 zeffii

i'm probably missing the point, but i'll bow out of this convo now :)

zeffii avatar Apr 28 '22 11:04 zeffii

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.

ArpegorPSGH avatar Apr 28 '22 12:04 ArpegorPSGH