- Notifications
You must be signed in to change notification settings - Fork31
(deprecated) A system for making generative systems
License
inconvergent/weir
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
This library is specifically written to be useful for a broad range of ways inwhich I create art using various generative algorithms. It is the nextiteration ofsnek. I made a new versionbecause I wanted to make some significant changes.
Main components:
2d and 3d vectors with corresponding operations:
; create a 2d vector(vec:vec1d03d0); create a 3d vector(vec:3vec1d02d03d0)
vec
supports common vector operations such asadd
,mult
,div
,sub
,as well as cross productscross
, dot products,dot
.Note: Functions that operate on 3d vectors are prefixed with
3
.Furthermore there are corresponding functions for scalars, lists of vectors,and broadcasting. They are indicated by prefix
l
ands
:; add scalar s to a, return result(vec:sadd va s); add vectors in two lists, returns list of new vectors(vec:ladd aa bb); add b to elements of aa, return list of new vectors(vec:ladd* aa vb)
Most of these functions also have a corresponding function postfixed with
!
,which performs the same operation, but assigns the result to the firstargument. This is faster since there is no need to create a newvec
struct.Note: This can cause strange behaviour since you can inadvertently change theinternal state of a struct. Use with caution.
Example:
; add b to a, store result in a; also returns a(vec:add! va vb)
There are also some common geometric operations, such as line interpolation,line-line intersection, angles, and line-plane intersection.
; find the position s between a and b. s should be between 0 and 1(vec:on-line s va vb); find intersection of two lines, returns; a bool, and interpolation value along line 1, line 2(vec:segx line1 line2)
See the code in the package for more details.
Random numbers, some examples:
; random double 0d0 and a (default: 1d0)(rnd:rnd a); ditto, between -a and a(rnd:rnd* a); between a and b(rnd:rndrng a b); random fixnum(rnd:rndi10); return n random numbers between a and b(rnd:rndspace n a b); random number from normal distribution(rnd:norm:mu0d0:sigma1d0); uniform in circle of radius a at centered at p(rnd:in-circ a:xy p); uniform numbers in a rectangle(rnd:in-rect w h:xy p); pick a random point between points va and vb(rnd:on-line va vb); execute a form with a certain probability; second form is optional(rnd:prob0.1d0 (print"10% hi") (print"90% oh no")); perform either form 1 or 2(rnd:either (print"form 1") (print"form 2")); repeat the form at most n-1 times(rnd:rep n (print1))
There are also some utils for random 3d vector numbers, see
src/rnd/3rnd.lisp
.A simple graph data structure,
weir
, for working with vertices and edges.The structure is combined with a programming pattern for applying changes tothe structure. The pattern relies onalterations
, see below. You can alsomanipulate the structure directly.Below is a small example:
(let ((wer (weir:make))); use :dim 3 for 3d; add three edges (loop repeat3do (weir:add-edge! wer (weir:add-vert! wer (rnd:in-circ200d0)) (weir:add-vert! wer (rnd:in-circ200d0:xy (vec:rep500d0))))); iterate verts (weir:itr-verts (wer v) (print (weir:get-vert wer v))); move a vert relativ to current position: (weir:move-vert! wer0 (vec:vec1d02d0)); move a vert to an absolute position (weir:move-vert! wer1 (vec:vec1d02d0):relnil); iterate edges (weir:itr-edges (wer vv) (print (weir:get-verts wer vv))); edges are represented as lists of verts, and they are always; sorted with the smallest vert index first, so both (weir:edge-exists wer'(01)); and (weir:edge-exists wer'(10)); returns t; get edges incident to vert 0 (weir:get-incident-edges wer0))
See the
examples
folder for more.A series of other useful data structures and tools. E.g. a package forhandling colors (
pigment
), splines (bzspl
), and various vector and pathfunctionality. Eg.math
,lin-path
andsimplify-path
.(let* ((points (rnd:nin-circ5400d0)) (bz (bzspl:make points)) (lp (lin-path:make points))); sample a point on the spline (bzspl:pos bz (rnd:rnd)); sample a point on path (lin-path:pos lp (rnd:rnd)); represent the spline with a limited number of points (bzspl:adaptive-pos bz:lim1d0)); return n numbers evenly spaced between a and b, inclusive(math:linspace n a b:endt); all fixnums from a to b-1(math:range a b); repeat the form n times(math:nrep n (rnd:rnd))
Orthogonal projection
ortho
:(let ((proj (ortho:make:s1d0:xy (vec:rep500d0):cam (vec:3vec1000d01000d00d0):look (vec:3zero)))) (multiple-value-bind (v d) (ortho:project proj (rnd:3in-sphere:rad300d0)); point in 2d (print v); distance from 3d point to camera plane (print d)); update cam position and look at something else (ortho:update proj:cam (vec:3vec3d04d01d0):look (vec:3rep79d0)))
A tool for drawing
svg
files:draw-svg
. Mainly files that are good forplotting.(let ((psvg (draw-svg:make:stroke"black")) (pts (list (vec:vec10d020d0) (vec:vec20d030d0) (vec:vec10d050d0)))); sw is the stroke width (draw-svg:path psvg pts:sw3d0) (draw-svg:bzspl psvg pts:sw3d0:so0.5d0) (draw-svg:circ psvg (vec:rep30d0)10d0:sw3d0:stroke"red") (draw-svg:save psvg"filename"))
A tool for drawing
pngs
calledsandpaint
. This package uses randomsampling to draw. This creates a fairly distinct and gritty look in manycases. Also supports direct pixel manipulations and a few filters.
Analteration
is a change that will be applied to the structure at the end ofa given context. In practical terms, an alteration is a function that returns alambda
(or just alambda
).
The main motivation behid this is that this makes it possible to "queue" up anumber of changes that will be applied at a later time. This makes it possibleto access the state in theweir
instance while you are creating thealterations. Without there being any changes made to the state of theweir
instance while the alterations are being created. Once all alterations arecreated, they will be applied.
Existing alterations inweir
are postfixed?
by convention, and it mightlook like this:
(weir:with (wer %) (; some code (% (weir:add-vert? ...)); more code (% (weir:add-edge? ...))))
all(% ...)
forms inside the weir context will cause the alteration inside tobe created and collected. They will be executed at the end of the context. ifan alteration evaluates to nil, nothing will happen.
You can assign a name (:res
) to the result of an alteration. This makes itpossible to create alterations that depend (:arg
) on the result of otheralterations:
(weir:with (wer %) (let ((pt (...))) (% (weir:add-vert? pt):res:a); alteration result is named :a (% (weir:add-vert? (vec:vec1d02d0)):res'b); result is named 'b (% (weir:add-edge?:a'b):arg (:a'b)))); uses :a and 'b
Note thatres
must be a keyword, symbol, or a variable with keyword or symbolvalue. Similarly,arg
must be a list ofres
elements that exist inside thesame context. make sure that all elements in the:arg
are present in thecontext, or the code will loop infinitely.
it is always possible to both reference future results, and assign the result aname. The order the order of:res
and:arg
does not matter:
(% (some-alteration?:a:b):res:x:arg (:a:b)); is equivalent to(% (some-alteration?:a:b):arg (:a:b):res:x)
Results will be available after the(with:weir ...)
context. See(get-alteration-result-list)
or(get-alteration-result-map)
. Also, notethat using the same name for multiple alterationswill result in undefinedbehaviour.
You can consider a named alteration as something akin to afuture; the valueof:res
is a reference to a value that does not yet exist. For this to work,any alteration that depends on a future that fails to be fulfilled will beskipped.
As an example, we can make the alterationprob-add-edge?
like this:
(defunprob-add-edge? (l a b)'add edge (a b) with probability p' (lambda (w) (when (< (rnd:rnd100d0) l) (add-edge! w a b))))
This will attempt to create edge(a b)
, but only if the random number is lessthanl
. This is to illustrate that the alteration may or may not attempt toweir
instance. If no edge is created, the above lambda will returnnil
.
Here is a an example of use:
; context start(let (wer (weir:make)); add some data to wer here ...; (% ...) is used to accumulate alterations; alterations are applied at the end of (weir:with ...) (weir:with (wer %); iterate all vertices in wer (weir:itr-verts (wer v) (% (move-vert? v (rnd:in-circ10d0))); w will be an arbitrary vertex in wer (weir:with-rnd-vert (wer w) (% (prob-add-edge? (weir:edge-length wer v w) v w))))))
The important thing to note here is that it iscrucial that thelength of edge(v w)
is calculated outside(defun prob-add-edge? ...)
.This ensure that(weir:edge-length wer v w)
is the length of the edgebefore the calls to(move-vert? ...)
have a chance to move eitherv
orw
.
Naturally, you can construct an alteration that checks the length of the edgeinside thelambda
in(prob-add-edge? ...)
, but this will result indifferent behaviour. In this case, any edge length will be calculated whileall the other vertices are moving around. Thus resulting in what can beconsidered "unexpected side effects".
This becomes more clear if you consider an n-body simulation. If any singlebody in the simulation moves before you have calculated the force between eachpair of bodies, you will get an incorrect result.
As we have mentioned, arguments to an alteration may, or may not, exist rightaway. To handle this, arguments to an alteration will be shadowed before thealteration is collected. This applies to arguments that are atoms, or formsthat do not contain a reference to a future alteration result. This is thebehaviour you will usually want in an example such as the one above. But itmight cause unexpected behaviour.
As an example of what happes, consider the alteration:
(% (my-alteration? (first var-1):a (my-function (rnd:rnd):b (second var-2))):arg (:a:b))
This will be expanded by the macro to something similar to:
(LET ((#:|non-atom:91| (FIRST VAR-1)); values are evaluated at time of collection (#:|non-atom:92| (RND:RND)) (#:|non-atom:93| (SECOND VAR-2))) (LAMBDA (#:WNAME90) (CASE (WEIR::-IF-ALL-RESOLVED#:ALT-RES88 (LIST:A:B)) (:OK; :A and :B both have a value (VALUEST (FUNCALL (THE FUNCTION (MY-ALTERATION?#:|non-atom:91| (GETHASH:A#:ALT-RES88) (MY-FUNCTION#:|non-atom:92| (GETHASH:B#:ALT-RES88)#:|non-atom:93|)))#:WNAME90))) (:BAIL (VALUESTNIL)); either :A or :B returned nil. skip alteration (:WAIT (VALUESNILNIL))))); :A or :B does not yet exist
As you can see, the variables/forms that will be shadowed here are:(first var-1)
,(second var-2)
and(rnd:rnd)
.
You can use(weir:with ... :bd t)
to see how an alteration is expanded. Thismight make it easier to find issues with shadowed/non-shadowed variables. Also,you can usually solve some problems you might encounter by defining customalterations locally (but outsideweir:with
) using(labels ())
.
It is possible to use:ref
and:arg
inside loops as well. but it requiresa bit more careful consideration. Here is an example:
(weir:with (wer %:dbt) (loop for x in (math:linspace20-20d020d0)do (loop for z in (list1d02d0)do (let ((xy (vec:vec x y z)) (s (vec:vec1d080d0)) (g (gensym"g"))); create a distinct name (% (weir:add-grp?:name (gensym"line")):res g) (% (weir:add-path? (list (vec:sub xy s) (vec:add xy s)):g g):arg (g))))))
The second alteration will be expanded to:
(LET ((#:|non-atom:8| (LIST (VEC:SUB XY S) (VEC:ADD XY S)))) (LAMBDA (#:WNAME7); every G is now a distinct future (CASE (WEIR::-IF-ALL-RESOLVED#:ALT-RES3 (LIST G)) (:OK (VALUEST (FUNCALL (THE FUNCTION (WEIR:ADD-PATH?#:|non-atom:8|:G (GETHASH G#:ALT-RES3)))#:WNAME7))) (:BAIL (VALUESTNIL)) (:WAIT (VALUESNILNIL)))))
You can define your own arbitrary alterations. There is an example ofcustom alterations and references inexamples/custom-alt.lisp
.
In the previous implementations of the(weir:with ...)
context,(% ...)
wasa function. This ensured that the arguments to the alteration, and indeed thelambda inside the alteration, was created before the end of the context. Thiseradicated the need for these complex shadowing rules. I'm not currently surewhether it is possible to avoid shadowing as long as some arguments do notexist at the time the alteration is collected.
Also, I use the term "shadowing" above. Not sure if this is really appropriate,but I failed to think of a better term
I useweir
for most of the work that I post online(https://inconvergent.net/,https://img.inconvergent.net/,https://twitter.com/inconvergent). Both for raster images, as well as vectorimages for plotter drawings.
Here are some plotted examples:
- https://inconvergent.net/2017/spline-script-plots/
- https://inconvergent.net/mechanical-plotter-drawings/
- https://inconvergent.net/mechanical-plotter-drawings/3/
- https://inconvergent.net/mechanical-plotter-drawings/5/
I have written about things related to this code (when it was calledsnek
) at:
- https://inconvergent.net/2017/snek-is-not-an-acronym/
- https://inconvergent.net/2017/a-method-for-mistakes/
- https://inconvergent.net/2017/arbitrary-alterations/
- https://inconvergent.net/2017/grains-of-sand/
- https://inconvergent.net/2017/a-propensity-for-mistakes/
And recently at:
This code is written for my personal use, and parts of it is ratherexperimental. Also, it is likely to change at my whim. For this reason I don'trecommend depending on this library for anything.
I release it publicly in case people find it useful or interesting. It is not,however, intended as a collaboration/Open Source project. As such I am unlikelyto accept PRs, reply to issues, or take requests.
This code requires Quicklisp to install dependencies (which are listed inweir.asd
). To install and load Weir, do:
(ql:quickload:weir)
If this does not work, Weir may not be in a place Quicklisp or ASDF can seethem. To fix this, either
(load"weir.asd")
or, for a long term solution, push the directory in which Weir sits to thevariablequicklisp:*local-project-directories*
:
; in your .sbclrc, for example:#+quicklisp(push"/path/to/dir/containing/weir"ql:*local-project-directories*)
Thefn
package (for generating file names) depends on thefn
command fromhttps://github.com/inconvergent/fn, but this is not necessary to use any of theother packages.
The code has only been tested inUbuntu 18.04 LTS
withSBCL 2.0.1
. I'vebeen told that examples work withSBCL
inmacOS
.
Run:
(asdf:test-system:weir)
Seehttp://blog.quicklisp.org/2011/08/going-back-in-dist-time.html
Summary:
(use-package:ql-dist); see versions(available-versions (dist"quicklisp")); select a dist version(install-dist"http://beta.quicklisp.org/dist/quicklisp/2019-03-07/distinfo.txt":replacet)
I would like to thank:
- https://twitter.com/RainerJoswig
- https://twitter.com/jackrusher
- https://twitter.com/paulg
- https://twitter.com/porglezomp
- https://twitter.com/stylewarning
- https://github.com/Hellseher
Who have provided me with useful hints and code feedback.
The ASDF config and test setup was kindly suggested and implemented by RobertSmith (https://twitter.com/stylewarning). The remaining weirdness in the testsystem is my fault. Hope to fix it properly later.
Also, many thanks tohttps://twitter.com/xach for making Quicklisp.
About
(deprecated) A system for making generative systems