blag/content/posts/2021-07-27-procedural-meshes.org

171 lines
9.7 KiB
Org Mode

---
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?