The Heliconius Butterfly Wing Pattern Evolver game is finished and ready for it’s debut as part of the Butterfly Evolution Exhibit at the Royal Society Summer Exhibition 2014. Read more about the scientific context on the researcher’s website, and click the image above to play the game.
The source code is here, it’s the first time I’ve used WebGL for a game, and it’s using the browser version of fluxus. It worked out pretty well, even to the extent that the researchers could edit the code themselves to add new explanation screens for the genetics. Like any production code it has niggles, here’s the function to render a butterfly:
(define (render-butterfly s) (with-state ;; set tex based on index (texture (list-ref test-tex (butterfly-texture s))) ;; move to location (translate (butterfly-pos s)) ;; point towards direction (maim (vnormalise (butterfly-dir s)) (vector 0 0 1)) (rotate (vector 0 90 90)) ;; angle correctly (scale (vector 0.5 0.5 0.5)) ;; make smaller (draw-obj 4) ;; draw the body (with-state ;; draw the wings in a new state (rotate (vector 180 0 0)) (translate (vector 0 0 -0.5)) ;; position and angle right ;; calculate the wing angle based on speed (let ((a (- 90 (* (butterfly-flap-amount s) (+ 1 (sin (* (butterfly-speed s) (+ (butterfly-fuzz s) (time))))))))) (with-state (rotate (vector 0 0 a)) (draw-obj 3)) ;; draw left wing (with-state (scale (vector 1 -1 1)) ;; flip (rotate (vector 0 0 a)) (draw-obj 3)))))) ;; draw right wing
There is only immediate mode rendering at the moment, so the transforms are not optimised and little things like draw-obj takes an id of a preloaded chunk of geometry, rather than specifying it by name need to be fixed. However it works well and the thing that was most successful was welding together the Nightjar Game Engine (HTML5 canvas) with fluxus (WebGL) and using them together. This works by having two canvas elements drawn over each other – all the 2D (text, effects and graphs) are drawn using canvas, and the butterflies are drawn in 3D with WebGL. The render loops are run simultaneously with some extra commands to get the canvas pixel coordinates of objects drawn in 3D space.
I’ve been working lately with the Heliconius research group at the University of Cambridge on a game to explain the evolution of mimicry in butterfly wing patterns. It’s for use at the Summer Science Exhibition at the Royal Society in London, where it’ll be run on a large touch screen for school children and visiting academics to play.
The game models biological processes for education purposes (as opposed to the genetic programming as used on the camouflage egg game), and the process of testing this, and deciding what simplifications are required has become a fascinating part of the design process.
In biosciences, genetics are modelled as frequencies of specific alleles in a given population. An allele is a choice (a bit like a switch) encoded by a gene, so a population can be represented as a list of genes where each gene is a list of frequencies of each allele. In this case the genetics consists of choices of wing patterns. The game is designed to demonstrate the evolution of an edible species mimicking a toxic one – we’ll be publishing the game after the event. A disclaimer, my terminology is probably misaligned in the following code, still working on that.
;; an allele is just a string id and a probability value
(define (allele id probability)
(list id probability))
;; a gene is simply a list of alleles
;; return the id of an allele chosen based on probability
(define (gene-express gene)
(let ((v (rndf)))
(lambda (allele r)
(let ((segment (+ (car r) (allele-probability allele))))
(if (and (not (cadr r))
(< v segment))
(list segment allele)
(list segment (cadr r)))))
(list 0 #f)
;; a chromosome is simple list of genes
;; returns a list of allele ids from the chromosome based on probability
(define (chromosome-express chromo)
(map gene-express chromo))
When an individual is removed from the population, we need to adjust the probabilities by subtracting based on the genetics of the eaten individual, and the adding to the other alleles to keep the probabilities summing to one:
;; prevents the probability from 'fixing' at 0% or 100%
(define (calc-decrease p)
(* (min p (- 1 p)) allele-decrease))
;; remove this genome from the population
(define (gene-remove-expression gene genome)
(let ((dec (calc-decrease (allele-probability (car gene)))))
(let ((inc (allele-increase dec (length gene))))
(if (eq? (allele-id allele) genome)
allele (- (allele-probability allele) dec))
allele (+ (allele-probability allele) inc))))
From sketches to fragment shader tests (unfinished) – see it running in webgl with the source code here. This is for a genetics and mimicry game, more on this project soon…