[Soc-2011-dev] Progress Report 05 - Onion - Sculpt/Paint

Jason Wilkins jason.a.wilkins at gmail.com
Sat Jun 25 08:12:48 CEST 2011


The work of dividing up sculpt.c and paint_stroke.c into smaller more
cohesive modules continues this week.

The central driving idea behind the reorganization is the notion of a
"brush space."  A Brush space (in the sense of mathematical vector
space, not the sense that Blender uses it for UI organization) is the
set of variables that are calculated for a sculpting or paint tool at
each dab.  These had been somewhat scattered and the conditions on how
they were updated was somewhat confusing to say the least.  I'll list
some examples of a problem I saw, and how brush space seeks to fix the
problem.

1) Variables sometimes have the same name, but different values
depending on the context.  An example would be the "grab delta" used
by grab style tools, which has two different meanings.  BrushSpace is
consistent in that grab_delta and grab_total always mean the same
thing and a tool uses the one that it needs.

Another example is the "area normal", which is usually the approximate
normal of the surface being edited, but due to different settings this
can be overwritten.  To deal with this, in BrushSpace the area normal
is always the approximate area normal and "disp_vector" is the user
defined vector.

2) Values in the brush space have always been calculated on an as
needed basis, but the tests for determining what is needed were mixed
with the code for doing the calculations and the code for doing the
calculations was scattered.  This was extremely ad hoc and leads to
esoteric bugs.

BrushSpace separates out the tests for determining what values are
needed for a particular tool from the code that does the calculations
and it does it all up front before any dab is applied.  This makes the
BrushSpace read-only to a tool and it makes the code for updating
BrushSpace clean and not dependent on all the various settings.  In
other words, there is a chunk of code that determines what needs to be
calculated that gathers all the settings together and then BrushSpace
is updated based on the result of that.  This makes it so you only
have to look in one place to determine what is calculated and another
to see how it is calculated.

3) Many variables have "first", "last", "old", "symmetrical", and
"previous" versions for the purpose of caching values for various
purposes.  I've done away with that by, with limited exceptions,
making the ENTIRE BrushSpace structure serve as a copy.  For example,
the "first" BrushSpace is simply saved after the first dab of a stroke
and can be referred to if needed.  The same goes for the previous
BrushSpace.  Symmetry is handled by making a copy of a BrushSpace with
all the symmetrical values changed and a tool that uses it can just
treat it like a normal BrushSpace (and if it needs the "True"
non-symmetric value it can get that too).

Another benefit is that a "brush_space_lerp" function can be written
that interpolates between current and previous BrushSpaces to allow
any of the values to be interpolated from a single function instead of
spreading it out through the code.  It also allows different kinds of
interpolation to be implemented without a multiplicative explosion of
code.

In the future a BrushSpace could be copied into generic integrator
that uses "BrushSpace acceleration" and "BrushSpace velocity" to do
dynamic updates to brush parameters.  Google Baraff and Whitkins notes
on physical simulation for an idea of what I'm talking about.  MyPaint
does something similar.

4) There was no common way to communicate between the cursor drawing
code and the painting code.  My "on-surface" drawing code needs a lot
of the same information as a brush stroke does, but when doing cursor
movements without the mouse button pressed there is no stroke in
progress.  I duplicated a lot of code to calculate some of the same
variables in both contexts.  By separating out BrushSpace now the
cursor drawing code can compute the values it needs to draw the
on-surface cursor while using exactly the same code as sculpt/paint
strokes do.

5) Sculpt tools contain very similar code because they mostly need the
same kinds of values.  By factoring this all out into a single update
function the sculpt tool functions shrink a bit.  Considering the
small differences between tools I still think they each contain too
much code, however we are limited by standard C in how much we can
shrink them and still give the optimizer a good chance to produce
optimal code (i.e., function callbacks for sculpting kernels would be
the best from a software engineering standpoint, but that would doom
all but the most heroic optimizers).  This is the kind of thing C++
templates are best at, but I'm not going there for a while.

6) sculpt.c was a big monolithic chunk of code with several things
going on at once and many of were only in the file because they were
only used by sculpting even though they could be used by a unified
painting system later.  So, breaking up sculpt.c into different
modules (see last weeks update for a list of new files) breaks out the
general stuff from sculpt specific stuff and it makes the whole system
easier to understand.

--

Needless to say this is a lot of work and I've consistently
underestimated how much work it actually is.  I'm pushing back where I
expected to be today to next week.  However I still expect to have all
the re-factoring, merging, and integration work done before the
half-way point because I built this kind of unexpected delay into my
original estimates.


More information about the Soc-2011-dev mailing list