algorithmic modeling for Rhino
I've been casually searching for a "normal" metaballs routine for Rhino for years, but what I find are force field based calculations of an isosurface that are spread out in space too much beyond their ideal surface so they add up where lots of points or lines cluster and create rather unintuitive bulges form a 3D modeler's perspective, here done with Millipede's Geometry Wrapper:
I've learned to do marching tetrahedra or cubes in Python to create the surface as needed from a implicit ( f(x,y,z) = 0 ) mathematical equation based on raw trigonometry but am not yet sure how to define an equation for Rhino user created input items like this or find a way to make marching cubes accept such input let alone one that doesn't treat each geometry item as an electric charge with so little decay.
This would afford an old school "organic" modeling paradigm that T-Splines replaced, but the T-Spines pipe command can't do nearby lines right either, which just makes overlapping junk. Metaballs and lines are not as elegant in that there is a real "dumb clay" aspect to the result that affords little natural structure beyond just smoothing, but still, if it works at all that beats T-Splines, and then I can feed the crude mesh result into Kangaroo MeshMachine to afford surface tension relaxation that will add elegant form to it.
I need both quick hacks and some help on how to deeply approach the mathematics of the required isosurface, now that I can think in Python better than ever.
I got a hint the other day here, about using a different power of fall-off but am not sure how to do the overall task mathematically:
"and just as with point based potentials, one can use different power laws for the distance, function, resulting it different amounts of rounding at the junctions. Below is with a 1/d^3 law for comparision with the above 1/d" - Daniel Piker
He also included this link about bulging:
Am I supposed to create an actual implicit equation for my assigned points and lines and use that with marching cubes to surface it? If so, how do I define that equation, at all, and then how to control bulging too?
It's actually an elegant solution to first increase the shell size around the point cloud (or curves or surfaces too), since the necessary smoothing to rid marching cubes aliasing itself shrinks the surface, as smoothing is otherwise notorious for. And fast smoothing allows you to introduce more aliasing so the marching cubes step can be much faster too.
I vastly prefer not-so-fast MeshMachine smoothing than normal methods, since it creates real surface tension that affords minimal surfaces in time that really do look better aesthetically than normal smoothing (wbLaplace). It's better to loose volume in a way that adds elegance than to just lose volume so quickly everywhere without the whole object asserting itself as a tension structure.
The point of this modeling system, as an aid to Rhino via Geometry Pipeline, is that primitive 3D shapes can afford much more "free" order to a model than just metaballs made from points which amount to "dumb clay," but then you hit it with MeshMachine to add elegant tension membrane smoothing, so you get smoothed out order and the result can be automatically beauty enhanced from being a combination of blocky order and tension membrane smoothing.
Here is the first user friendly Nik's Metamodeler, a Rhino modeling aid based on non-bulging metaballs, metalines and pseudo-metasurfaces (via isocurves as more lines), that grabs anything on a layer you can specify in the Geometry Pipeline components, though it's set to grab all layers initially.
The 3D aliasing issue can create real final artifact bulges along angled surfaces which eventually requires turning the resolution (cube size) down lower for a slow final result. With settings that are quite fast, you can leave the Grasshopper solver on instead of disabled, but when the going gets slow, just disable it and initiate single solutions via the Recalculate menu item.
Various display modes may be preferred, and it may help to set Display Options... (viewport menu) to make lines and points display thicker.
Having now nearly modified my port of marching tetrahedra to Grasshopper Python (see: http://www.grasshopper3d.com/forum/topics/meshes ), to do metaballs, the ones that do not cause bulging since they have no mathematical falloff function, just an abrupt switch of field from zero to one in a way that somehow avoids nearly all bulging despite fields still at least arithmetically adding up where balls cluster....
I have newfound appreciation of David Stasiuk's Geometry Wrapper VB script. First of all, marching cubes actually gives a smoother result than tetrahedra, despite tetrahedra having a reputation of being "more uniform," in that tetrahedra can give aliasing that appears not as regular bulges but as sharp pyramids:
But more so, Stasiuk's Geometry Wrapper, rather than bafflingly using a Grasshopper data tree as I thought and was disturbed by since scripting was my escape from those altogether, he is invoking a very fast special feature of Rhino via the Rhinocommon RTree feature that can search 3D space very efficiently, and thus his script is much faster than my Python version that is RTree ignorant. Here is his code:
He also has a sophisticated way to define the bounding box at an angle that is often a lot more efficient than a dumb XYZ world coordinate box. Since Rhinocommon seems to lack a multi-object bounding box command except for points, it makes sense that he is populating curves with a few points to do it all via points. This makes it more complicated for me to adapt it directly to surfaces instead of pseudo-surfaces (via isocurve wireframe extraction) though, but I understand the script better now, so in time, perhaps.
For my application at least, I successfully made his script twice as fast by ridding it of a complex math function of the metaball radius in favor of just using the radius itself:
Nik, I am enjoying your thorough descriptions to your research. Very educational!
I feel like you've found a trove of pictures of me terrifically drunk and keep posting them. Ugh, this old code!
Actually, the reason for that horrible calculation was to ensure that each curve would correctly capture all of the positions from the rTree for evaluating the curve charge (if not all points get captured, the mesh has a risk of getting holes in it).
I am surprised though that it would speed up the script quite that much. I suppose it reduces the number of positions being tested...but those were rendered superfluous in your implementation also because you have eliminated for each charge any effect beyond the radius/3 (where you're assigning the field += 0...you should also speed things up if you just get rid of that altogether, where it's repeatedly performing that division calc for no good reason).
It is a weird script (in a mere three main pages besides helper functions) in that I can't quite figure out the strategy of it, and thus how it works. It populates curves with a few points (with crv.DivideByCount, pictured below) seemingly to find the overall bounding box but then it seems to use the same points to calculate the final result, and yet there are not enough of them to allow for that. Is the RTree being used to eliminate most of the cubes in the bounding box and somehow each curve point contains pointers to the cubes around it? And besides the RTree, there is indeed also a Grasshopper tree being used inside the script (help!), to loop through what seems to be those old bounding box construction points, but that's where I'm stuck understanding, since those points are not being used for the field calculation, for independently in the field function, the closest point on the curve from a given test point is being found to find it's distance to the test point. There's no simple loops I can see doing the work, but that may help explain why it's fast compared to my overly simple Python port to Grasshopper of marching tetrahedra.
Since RTree is a part of Rhino, your fast script is so far the only efficient way to do marching cubes that allows ready modification of the field parameters that create the isosurface, and thus the only way to robustly single mesh wrap geometry without bulging artifacts where geometry clusters.
I want to try adding surfaces to the input types, since manually or Grasshopper populating them with curves seems rather inefficient and quirky, and treating surfaces as Breps affords simple Rhinocommon determination of a closest point.
It actually doesn't use those same subdivision points to calculate the final result for either curves or breps.
The RTree is made from the grid of total sampling points. Then the curves and breps are subdivided into points only to determine which points on the sample grid to affect. That's why I used that weird function to increase the sphere radius...it's only for searching the RTree to identify to points that should be influenced by each charge, and I wanted to ensure (in almost all cases) that each sample grid point that could possibly be affected by any curve/brep geometry would be tested. Then, when those points are identified, they are evaluated through closest point functions for both to actually calculate the distance (I actually convert breps to meshes for speed...the curves are the slowest part easily).
Like I said, it's not really that great a script...it just works. The RTree is essential to limiting the number of points that have to be evaluated for each charge (most implementations evaluate every charge for every grid sample point). This clearly helps with the speed quite a bit. And then it doesn't bother to calculate any cubes that aren't within a search radius for any single element.
I am actually super close to putting out a component-based update to this...it's going to also not be ideal, but I think quite an upgrade. Since I've done this script I've shifted from VB to C#, but if you're interested, I could send you the script for the next one as well.
Send new toys! Here, or nikwillmore @ gmail dot com.
I see you played with Breps already, here:
Your code highlight is from a more recent VB script than I have. It's fantastic that you are optimizing for speed all along.
That script is on the same blog post as the one you're working from...there are a couple there, if you want to look. Here's the GHA for what I'm doing now, plus a quick def for looking at negative charges. You'll find the components under Mesh->Cocoon.
The "refine" component has Plankton embedded in it, and also uses a number of scripts adapted from Daniel Piker's mesh machine component.
Dreams coming true, and you even handled the bulging for curves, using a separate Curve Group Charge component, but I'm still stuck populating surfaces with curves then to avoid bulges from the Brep Charge component:
It's a fun coincidence, since my original Boolean Union-based modeler using MeshMachine I called "Nik's Cocoon Modeler."
I wonder if you could toggle the main component to avoid all bulges, using something like my fixed distance hack to replace the 1/r^2 field? Or else include Group Brep Charge and Group Point Charge components? Yet even then, I want to avoid bulges where curves pass along or points are placed on a brep etc.
This is a big deal, to make this modeling paradigm fast and simple since it was already much more general and robust than any overly specialized or too tweaky thus failure prone alternatives. It helps fulfill my vision of an easier version of T-Splines that doesn't require so much strategizing as you model that can feel like brain surgery. With Geometry Pipeline, you can minimize Grasshopper and just get to work. You could define multiple layers to put objects on, to determine various levels of field strength (radius) for objects you are working with manually. Yet unlike the dumb clay of point metaballs, you can use shape primitives to give order to the design. And MeshMachine tension membrane relaxation can take you out of engineering mode into aesthetics. I'm thrilled.
Actually, your Cocoon system is well behaved about bulging in practical use in my now upgraded Nik's Metamodeler aid to Rhino modeling:
There's some multi-threading going on and even without mysterious refinement (that can blow up easily) the output is incredibly smooth, and even with faster settings, MeshMachine really likes the meshes.
It has tighter behavior, albeit with three main and somewhat overlapping settings: radius, strength and isovalue. And it's much faster and also robust to super fine settings. Magic, achieved, with fast adaptive meshing tension membrane metaballs. As of today T-Splines has serious competition from this paradigm, especially since one can offset the final mesh inwards to match the original geometry better.