Some more rewording/writing in 2021-07-27-procedural-meshes.org

This commit is contained in:
Chris Hodapp 2021-07-27 13:23:46 -04:00
parent 0ab0f9d0ec
commit 808e4df573

View File

@ -31,7 +31,7 @@ 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
[[http://structuresynth.sourceforge.net/index.php][Structure Synth]] of course already exists, and is 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]].
@ -47,9 +47,8 @@ Tools like [[https://openscad.org/][OpenSCAD]], based on [[https://www.cgal.org/
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.
practical applications in CAD, not so much for my 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
@ -59,15 +58,15 @@ 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
and [[https://www.mattkeeter.com/research/mpr/][MPR]]. They're pure magic, 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.)
handle implicit surfaces directly, Blender's renderers cannot. I will
have other posts going into more detail on this subject, 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
@ -94,7 +93,8 @@ 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.
then are connected together at corresponding vertices. (TODO: Add
link to the automata_scratch repo, whatever it's renamed to)
- 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
@ -109,39 +109,58 @@ My meandering path to implementing it went something like this:
- 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)
(this is, of course, ignoring many other tangents with things like
shaders)
(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.
I put some serious effort into [[https://github.com/Hodapp87/prosha][Prosha]] and was conflicted on shelving
the project, but the issues didn't look easily solvable. Part of
those issues were implementation issues with Rust - not that Rust
could have really done anything "better" here, but that it just wasn't
the right tool for what I was doing. In short, I had spent a lot of
time and effort trying to badly and unintentionally implement a Lisp
inside of Rust instead of just picking a Lispier language, or perhaps
using an embeddable Rust-based scripting language like [[https://github.com/koto-lang/koto][Koto]] or [[https://github.com/rhaiscript/rhai][Rhai]]. I
had ignored that many things that functional programming left me very
accustomed to - like first-class functions and closures - were
dependent on garbage collection. When I realized this and did a big
refactor to remove this entire layer of complexity, I was left with
very little "core" code - just a handful of library functions, and the
actual recursive rules for the geometry I was trying to generate.
That's good and bad: things were much simpler and vastly faster, but
also, it felt like I had wasted quite a lot of time and effort. I
have some more detailed notes on this in the Prosha repository.
Part of the issues also weren't Rust implementation issues - they were
deeper issues with my original "correct-by-construction" mesh idea
being 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 crux 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, and it made no sense to "partially" apply a rule,
especially if that rule involved some kind of branching - but 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.
way to do this was to completely separate the process of meshing
(refinement, subdivision, facetization...) 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]]
@ -151,7 +170,7 @@ 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.
and Blender would handle the subdivision on-demand 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
@ -159,12 +178,26 @@ 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.
files, and Blender was crashing when importing them.... and this is
all a yak to shave another day. I instead generated the mesh data
directly in Blender (via its Python interpreter), added it to the
scene, and then set its creases via its Python API.
TODO while I'm not so tired:
After the aforementioned refactor in Prosha, I was able to quickly
translate the Rust code for most of my examples into Python code with
the help of some library code I'd accumulated from the past projects.
Debugging this mostly inside Blender also made the process vastly
faster. Further, because I was letting Blender handle all of the
heavy lifting with mesh processing (and it in turn was using things
like OpenSubdiv), the extra overhead of Python compared to Rust didn't
matter - I was handling so much less data when only producing the
control cage, not the full mesh.
What is the aim of this post? To explain Prosha? To explain current
work, including Prosha?
I'm still a little stuck at how to build higher 'geometric'
abstractions here and compose them. I have felt like most of the
model couples me tightly to low-level mesh constructs - while Context
Free and Structure Synth definitely don't have this problem. This is
particularly annoying because a lot of the power of these recursive
grammars comes from their ability to be abstracted away and composed.
(TODO: Show some examples)