A few days ago I wrote about a possible strategy for getting more interesting generative results. Basically I suggested that you need a way to introduce errors into your system, as well as a way for the system to correct itself.
Below I will illustrate one possible interpretation of this using my
experimental generative system called snek
.
SNEK
is a data structure for dealing with geometry (vertices
and edges), combined with a method for generating and applying
alterations to that structure.
By design these alterations do not depend on the current state of
the snek
instance. At the end of a context the
alterations are applied to the instance according to a set of
rules designed to keep the data structure intact. Specifically this means
that nonsensical alterations are discarded. This feature is
important, as we will see now.
Consider the following example, which is a part of the rules that make up the "Drift" algorithm from @sandpaintbot.
; context start
(snek:with (snk); pick a random vertex, v, and create
; a new edge between v and xy
(snek:with-rnd-vert (snk v); xy placed relative to position of v
(snek:append-edge? v xy :rel t)) (snek:with-rnd-vert (snk v) (snek:with-rnd-vert (snk w); create an edge between arbitrary
; vertices v and w
(snek:join-verts? w v))))); context end
; alterations have been applied
If the (snek:join-verts? ...)
alteration happens to be
created with w
equal to v
then that
alteration won't make sense. In this case it will be gracefully
discarded.
Since the alterations are independent of the snek
instance, and since the snek
structure will gracefully ignore
faulty alterations, there is nothing stopping us from arbitrarily
changing the alterations before they are applied.
It is not a stretch to imagine this as mutating the
alterations. For instance we can randomly mutate an
alteration by a given probability. In code it can look like the
example below. Here mut
contains the existing
mutations as well as the mutation probability.
; context start
(snek:with (snk); mutate alterations
(snek:mutate (mut); remaining code is exactly as above
(snek:with-rnd-vert (snk v) (snek:append-edge? v xy :rel t)) (snek:with-rnd-vert (snk v) (snek:with-rnd-vert (snk w) (snek:join-verts? w v))))))
The mutations can be controlled by whatever rules you can come up with.
For instance, you can randomly change the index (or indices) affected by an
alteration. In the above example this will probably not have much
of a visible effect—we are already selecting indices at random.
However, if we were to say that all mutated alterations would have
(one of) their affected indices replaced by 0
, then we would
certainly see something.
In the below image we can see an example of a slightly more complex mutation rule. I will leave it up to those who are interested to speculate on what is going on.
All things considered, there is no real reason why this "error process" can't be incorporated into the original algorithm. As such, what we have really done is create a new algorithm by combining the original with an error process.
This somewhat basic example might seem a little contrived, but I do believe there is a value in being able to apply these kinds of mutations. And as such, I will explore it further.
- I recommend reading the post before continuing.
- Alterations are immutable, so technically we do not change alterations, we create new ones.
- It is my opinion that "the algorithm" must be the combination of all the components that created a given result.