diff --git a/content/posts/2021-07-27-procedural-meshes.org b/content/posts/2021-07-27-procedural-meshes.org new file mode 100644 index 0000000..1c4309f --- /dev/null +++ b/content/posts/2021-07-27-procedural-meshes.org @@ -0,0 +1,170 @@ +--- +title: "(post on procedural meshes needs a title)" +author: Chris Hodapp +date: "2021-07-27" +tags: +- procedural graphics +draft: true +--- + +(TODO: a note to me, reading later: you don't need to give your entire +life story here.) + +(TODO: pictures will make this post make a *lot* more sense, and it +may need a lot of them) + +Context Free is one of my favorite projects since I discovered it +about 2010. It's one I've written about before (TODO: link to my +posts), played around in (TODO: link to images), presented on, as well +as re-implemented myself in different ways (see: [[https://github.com/hodapp87/contextual][Contextual]]). That is +sometimes because I wanted to do something Context Free couldn't, such +as make it realtime and interactive, and sometimes because +implementing its system of recursive grammars and replacement rules +can be an excellent way to learn things in a new language. (I think +it's similar to [[https://en.wikipedia.org/wiki/L-system][L-systems]], but I haven't yet learned those very well.) + +I've also played around in 3D graphics, particularly raytracing, since +about 1999 in PolyRay and POV-Ray. POV-Ray is probably what led me to +learn about things like implicit surfaces, parametric surfaces, and +procedural geometry - its scene language is full of constructs for +that. Naturally, this led me to wonder how I might extend Context +Free's model to work more generally with 3D geometry, and let me use +it to produce procedural geometry. + +[[http://structuresynth.sourceforge.net/index.php][Structure Synth]] of course already exists as a straightforward +generalization of Context Free's model to 3D (thank you to Mikael +Hvidtfeldt Christensen's blog [[http://blog.hvidtfeldts.net/][Syntopia]], another of my favorite things +ever, for introducing me to it awhile ago). See also [[https://kronpano.github.io/BrowserSynth/][BrowserSynth]]. +However, at some point I realized they weren't exactly what I wanted. +Structure Synth lets you combine together 3D primitives to build up a +more complex scene - but doesn't try to properly handle any sort of +*joining* of these primitives in a way that respects many of the +'rules' of geometry that are necessary for a lot of tools, like having +a well-defined inside/outside, not being self-intersecting, being +manifold, and so forth. + +Tools like [[https://openscad.org/][OpenSCAD]], based on [[https://www.cgal.org/][CGAL]], handle the details of this, and I +suspect that [[https://www.opencascade.com/][Open CASCADE]] (thus [[https://www.freecadweb.org/][FreeCAD]]) also does. In CAD work, it's +crucial. I experimented with similar recursive systems with some of +these, but I quickly ran into a problem: they were made for actual +practical applications in CAD, not for my nonsensical generative art, +and they scaled quite poorly with the sort of recursion I was asking +for. + +Implicit surfaces (or one of the many +equivalent-except-for-when-it's-not names for this, e.g. F-Reps or +distance bounds or SDFs or isosurfaces) handle almost all of this +well! They express CSG (TODO: link to CSG) operations, they can be +rendered directly on the GPU via shaders, operations like blending +shapes or twisting them are easy... for more on this, see [[http://blog.hvidtfeldts.net/][Syntopia]] +again, or nearly anything by [[https://iquilezles.org/][Inigo Quilez]], or look up raymarching and +sphere tracing, or see [[https://ntopology.com/][nTopology]], or Matt Keeter's work with [[https://www.libfive.com/][libfive]] +and [[https://www.mattkeeter.com/research/mpr/][MPR]]. They're pure magic and they're wonderfully elegant and I'll +probably have many other posts on them. + +However, there is one big issue: turning implicit surfaces to good +meshes for rendering /is a huge pain/, and while many renderers can +handle implicit surfaces directly, Blender's renderers cannot. I have +other posts on this as well, but for now, take it on faith. This is +why I did not try to use implicit surfaces for this project. (TODO: +Make those posts.) + +With these limitations in mind, around 2018 June I had started jotting +some ideas down. The gist is that I wanted to create +"correct-by-construction" meshes from these recursive grammars. By +that, I meant: incrementally producing the desired geometry as a mesh, +triangle-by-triangle, in such a way that guaranteed that the resultant +mesh had the desired detail level, was a manifold surface, and that it +was otherwise a well-behaved mesh (e.g. no degenerate triangles, no +self-intersection, no high-degree vertices, no triangles of extreme +angles) - rather than attempting to patch up the mesh after its +creation, or subdividing it to the necessary detail level. For +something similar to what I mean (though I didn't have this in mind at +the start), consider the [[https://en.wikipedia.org/wiki/Marching_squares][marching squares]] algorithm, which is +guaranteed to produce closed, manifold meshes. + +(TODO: Illustrate this somehow) + +The form it took in my notes was in sort of "growing" or "extruding" a +mesh per these recursive rules, building in these guarantees (some of +them at least) by way of inductive steps. + +My meandering path to implementing it went something like this: + +- Write some very ad-hoc Python to generate a mesh of a parametric + conversion of my annoying spiral isosurface from 2005 by breaking it + into planar "slices" or "frames", which move along the geometry and + then are connected together at corresponding vertices. +- Explore [[https://github.com/thi-ng/geom][thi.ng/geom]] and pretty quickly give up - but in the process, + discover [[https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.42.8103][Parallel Transport Approach to Curve Framing]]. +- Implement that paper in Python, reusing the basic model from my + prior code. (See [[https://github.com/Hodapp87/parallel_transport][parallel_transport]]) +- Again continue with this model, allowing more arbitrary operations + than parallel frame transport, eventually integrating most of what I + wanted with the recursive grammars. (See + [[https://github.com/Hodapp87/automata_scratch/tree/master/python_extrude_meshgen][automata_scratch/python_extrude_meshgen]]) +- Keep running into limitations in python_extrude_meshgen, and start + [[https://github.com/Hodapp87/prosha][Prosha]] in Rust - partly as a redesign/rewrite to avoid these + limitations, and partly because I just wanted to learn Rust. +- Realize that Rust is the wrong tool for the job, and rewrite *again* + in Python but with a rather different design and mindset. + +(this is, of course, ignoring projects on various other tangents) + +(TODO: Maybe split these off into sections for each one?) + +Somewhere in here, I concluded that my fundamental idea was +half-broken. It half-worked: I was able to produce closed, manifold +meshes this way, and it could be tedious, but not *that* difficult. +However, all of my attempts to also produce "good" meshes this way +failed miserably. + +(TODO: Can I find examples of this?) + +A few of the same fundamental issues kept cropping up. One is that +the recursive rules I used for generating geometry (inspired heavily +by those in Context Free) were inherently based around discrete steps, +generating discrete entities, like vertices, edges, and face; it made +no sense to "partially" apply a rule, especially if that rule involved +some kind of branching. I kept trying to treat it as something +continuous for the sake of being able to "refine" the mesh to as fine +of detail as I wanted. Further, I was almost never consistent with +the nature of this continuity: sometimes I wanted to treat it like a +parametric curve (one continuous parameter), sometimes I wanted to +treat it like a parametric surface (two continuous parameters), +sometimes I wanted to treat it like an implicit surface +(with... theoretically two continuous parameters, just not explicit +ones?). It was a mess, and it's part of why my Prosha repository is a +graveyard of branches. + +The recursive rules were still excellent at expressing arbitrarily +complex, branching geometry - and I really wanted to keep this basic +model around somehow. After some reflection, I believed that the only +way to do this was to completely separate the process of +meshing/refinement/subdivision from the recursive rules. + +This would have been obvious if I read the guides from [[https://graphics.pixar.com/opensubdiv/overview.html][OpenSubdiv]] +instead of reimplementing it badly. Their [[https://graphics.pixar.com/opensubdiv/docs/subdivision_surfaces.html][subdivision surface]] +documentation covers a lot, but I found it incredibly clear and +readable. Once I understood how OpenSubdiv was meant to be used, it +made a lot of sense: I shouldn't be trying to generate the "final" +mesh, I should be generating a mesh as the /control cage/, which +guides the final mesh. Further, I didn't even need to bother with +OpenSubdiv's C++ API, I just needed to get the geometry into Blender, +and Blender would handle the subdivision via OpenSubdiv. + +One minor issue is that this control cage isn't just a triangle mesh, +but a triangle mesh plus edge creases. I needed a way to get this +data into Blender. However, the only format Blender can read edge +creases from is [[http://www.alembic.io/][Alembic]]. Annoyingly, its [[http://docs.alembic.io/reference/index.html#alembic-intro][documentation]] is almost +completely nonexistent, the [[https://alembic.github.io/cask/][Cask]] bindings still have spotty Python 3.x +support, and when I tried to run their example code to produce some +files, and Blender was crashing when importing them. Until I shave +that yak, I am instead generating the mesh data directly in Blender +(via its Python interpreter), adding it to the scene, and then setting +its creases via its Python API. + +TODO while I'm not so tired: + +What is the aim of this post? To explain Prosha? To explain current +work, including Prosha?