December's calendar image was a reaction-diffusion simulation, part of the RDEX project. If you want to explore the variety of patterns that emerge from this kind of stuff, a static mirror of the Kiblix RDEX server is still online to play with.

]]>Interlocking space filling curves - in the limit the perimeter between adjacent blocks becomes infinite, like a perfect lung for transferring oxygen from air to blood and carbon dioxide in the other direction. Made with the Haskell Diagrams library:

]]>{-# LANGUAGE TypeFamilies, FlexibleContexts #-} import Diagrams.Prelude hiding (E) import Diagrams.Backend.SVG.CmdLine (defaultMain) import Control.Monad.State (state, evalState) import Data.Colour.SRGB (sRGB24) data H = N | E | S | W data T = L | R step L = [L,R,L] step R = [L,R,R,R,L] seed = [R,R,R,R] levels = iterate (concatMap step) seed heading N L = W heading E L = N heading S L = E heading W L = S heading N R = E heading E R = S heading S R = W heading W R = N angle N = 90 @@ deg angle E = 0 @@ deg angle S = -90 @@ deg angle W = 180 @@ deg draw d h = (arcD d a a', h') where a = angleDir $ angle h a' = case d of L -> 90 @@ deg R -> -90 @@ deg h' = heading h d arcD L a b = arc a b arcD R a b = scale (-1) $ arcCW a (rotate b a) draws = mconcat . (`evalState` E) . mapM (state . draw) base = draws (levels !! level) # closeTrail # pathFromTrail # translate (r2 (-1, 0)) # stroke left = base middle = base # scale (sqrt 0.5) # rotate (45 @@ deg) # translateX (2 ^ (level + 1)) right = base # scale 0.5 # translateX (2 ^ level * 3) # translateY (2 ^ level) four = base # scale (sqrt 0.125) # rotate (45 @@ deg) # translateX (3 * 2 ^ level) # translateY (2 ^ (level + 1)) lung = (left # fc r <> middle # fc y <> right # fc g <> four # fc b) # rotate ((atan 5 :: Double) @@ rad) # centerXY # pad 1.01 # lc black # lw 0.1 # bg white # rotate (-90 @@ deg) y = sRGB24 0xff 0xdf 0x80 r = sRGB24 0xff 0x88 0xaa b = sRGB24 0xaa 0xaa 0xff g = sRGB24 0x88 0xff 0xaa level = 4 main = defaultMain lung

I blogged about this before with an animation (Rolling Torus), the only thing missing now is the Fragmentarium source code, so here it is:

]]>#define providesColor #include "Soft-Raytracer.frag" uniform float time; const float pi = 3.141592653; const float s = 2.0; const float ri = s / (sqrt(s * s + 1.0) + 1.0); const float ro = s / (sqrt(s * s + 1.0) - 1.0); const float rc = 0.5 * (ro + ri); const float rt = 0.5 * (ro - ri); const vec3 rgb[5] = vec3[]( vec3(1.0, 0.7, 0.0), vec3(0.7, 1.0, 0.0), vec3(0.0, 0.7, 1.0), vec3(0.7, 0.0, 1.0), vec3(1.0, 0.0, 0.7)); float torus(vec3 z) { return length(z - rc * normalize(vec3(z.xy, 0.0))) - rt; } float plane(vec3 z) { return z.z; } vec3 spin(vec3 z) { float a = 2.0 * pi * time / 3.0; mat2 m = mat2(cos(a), sin(a), -sin(a), cos(a)); z.xy = m * z.xy; return z.xzy - vec3(0.0,ro,0.0); } vec3 baseColor(vec3 q, vec3 n) { vec2 uv = vec2(0.0); if (q.z < 0.01) { uv = q.xy * sqrt(5.0); } else { vec3 p = spin(q); float k = -1.0; float l = 1.0; if (p.z > 0.0) { l = -l; } if (length(p.xy) > rc) { k = -k; } float a = (p.z * p.z * sqrt(s * s + 1.0) + k * sqrt(max(1.0 - p.z * p.z * s * s, 0.0))) / (p.z * p.z + 1.0); float y = l * acos(clamp(a, -1.0, 1.0)) / (2.0 * pi); float x = s * atan(p.x, p.y) / (2.0 * pi); if (x < 0.0) { x = s + x; } if (y < 0.0) { y = 1.0 + y; } x = 5.0 * x; y = 5.0 * y; uv = vec2(y, x); float b = atan(1.0, 2.0); mat2 m = mat2(cos(b), sin(b), -sin(b), cos(b)) * sqrt(5.0); uv = m * uv; } vec3 t; if (mod(uv.x, 1.0) < 0.25 || mod(uv.y, 1.0) < 0.25) { t = vec3(0.0); } else { int k = clamp(int(mod(floor(uv.x) + 2.0 * floor(uv.y), 5.0)), 0, 4); t = rgb[k]; } return vec3(t); } float DE(vec3 z) { return min(torus(spin(z)), plane(z)); } #preset default FOV = 0.3 Eye = -5,-5,2.25 Target = -0.593772,1.17202,1.5097 Up = 0,0,1 EquiRectangular = false FocalPlane = 5 Aperture = 0.01062 Gamma = 1 ToneMapping = 1 Exposure = 1 Brightness = 1 Contrast = 1 Saturation = 1 GaussianWeight = 5.2308 AntiAliasScale = 1.7333 Detail = -5.47582 DetailAO = -3.26669 FudgeFactor = 1 MaxRaySteps = 317 BoundingSphere = 12 Dither = 1 NormalBackStep = 1 AO = 0,0,0,0.32456 Specular = 0.2 SpecularExp = 24.051 SpotLight = 0.737255,0.686275,0.533333,1.8841 SpotLightPos = 10,-5.0684,8.0822 SpotLightSize = 2.3 CamLight = 0.792157,0.556863,0.682353,0.6579 CamLightMin = 3e-05 Glow = 0,0.917647,1,0 GlowMax = 16 Fog = 0 Shadow = 0.82906 NotLocked Sun = -1.29354,1.01588 SunSize = 0.001 Reflection = 0 NotLocked BaseColor = 1,1,1 OrbitStrength = 1 X = 0.5,0.6,0.6,0.7 Y = 1,0.6,0,0.4 Z = 0.8,0.78,1,0.5 R = 0.4,0.7,1,0.12 BackgroundColor = 0.501961,0.501961,0.501961 GradientBackground = 1.66665 CycleColors = false Cycles = 0.1 EnableFloor = false NotLocked FloorNormal = 0,0,0 FloorHeight = 0 FloorColor = 1,1,1 #endpreset

I blogged about fish variations in 2013. This \(\{6,4\}\) version was implemented in Fragmentarium and is coloured in 6 colours. You can download the source code: hyperbolic_fish.frag

]]>June's calendar image is an exponential spiral on the Riemann sphere, rotated so that both poles are visible, and tiled with hexagons that seem to pop into cubes in both directions. I originally implemented it in February 2013, and today I updated it (porting it to Fragmentarium along the way). As a bonus, here's a video of the sphere rotating (flattened to 2D using stereographic projection):

You can download a high resolution Loxodrome video and the Loxodrome Fragmentarium source code.

]]>The classic Sayagata tiling is a Euclidean (flat) plane tesselation based on a square grid. For May's calendar image I thought it would be fun to make a hyperbolic variation, with 5 squares meeting at each vertex instead of 4. Furthermore, to wrap the hyperbolic plane into a repeating ring shape like what Bulatov did (warning, that page is slow loading but worth it in the end).

I implemented it as a GLSL fragment shader in Fragmentarium (download my source code) with a few constants for changing the tiling parameters (the hyperbolic symmetry group, the orientation across the band, number of repetitions, whether to draw a pretty pattern or just triangles). Here are a few variations:

The code has a few comments; the gist of the overall operation is to transform the coordinates from the ring back into the PoincarĂ© disc model of hyperbolic geometry, then repeatedly apply hyperbolic symmetries towards a central fundamental region, which finally gets the Sayagata texture. Note that tweaking some of the values leads to ugly seams with the parts not lining up - still some bugs I guess! (WONTFIX DONTDOTHAT)

]]>I've already blogged a bit about this calendar image for April (yep, late again...). I made a diagram using Fragmentarium to show the principle behind it:

Five points forming a regular pentagon rotate, their vertical coordinates make five sine waves. Each wave is split into a number of rectangles, whose width depends on the height of the wave. Then all five sets of rectangles are overlayed. Because the centroid of the pentagon is fixed no matter the rotation angle, the sum of the waves remains constant - this property is used in windowed overlap add resynthesis of FFT for frequency-domain processing in audio.

But it's not the same as the original, which you can tell by zooming in:

The original (above) has an equal amount of white space between each coloured strip, while in the recreation (below) the amount of white space changes. And the order of the colours in the recreation is flipped from the original, and the coloured strips in the recreation are a bit narrower than the original too.

You can download the source code for the recreation: gradient.frag. For extra fun switch to animation mode in Fragmentarium to see the pentagon rotate with the waves streaming out from it. I did render a video but it looks really horrible compressed, so unless you have Fragmentarium or you wait until I get around to porting this code to WebGL (not very likely..) you'll just have to imagine it.

]]>Distance estimated Newton fractal for a rational function with zeros at {1, i, -1, -i} and poles at {-2, 2}. I already blogged about this calendar image for March: Distance estimation for Newton fractals. The only other thing to mention is dual numbers for differentiation, which I used to save effort computing derivative equations in an OpenGL GLSL fragment shader re-implementation for Fragmentarium:

#include "Progressive2D.frag" const int nzeros = 4; const vec2[4] zeros = vec2[4] ( vec2( 1.0, 0.0) , vec2( 0.0, 1.0) , vec2(-1.0, 0.0) , vec2( 0.0, -1.0) ); const int npoles = 2; const vec2[2] poles = vec2[2] ( vec2( 2.0, 0.0) , vec2(-2.0, 0.0) ); const vec3[4] colours = vec3[4] ( vec3(1.0, 0.7, 0.7) , vec3(0.7, 0.7, 1.0) , vec3(0.7, 1.0, 0.7) , vec3(1.0, 1.0, 0.7) ); const float weight = 0.25; // change this according to render size const float huge = 1.0e6; const float epsSquared = 1.0e-6; const int newtonSteps = 64; float cmag2(vec2 z) { return dot(z,z); } vec2 csqr(vec2 z) { return vec2(z.x*z.x - z.y*z.y, 2.0*z.x*z.y); } vec2 crecip(vec2 z) { float d = cmag2(z); return z / vec2(d, -d); } vec2 cmul(vec2 a, vec2 b) { return vec2(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x); } // dual numbers vec4 constant(vec2 z) { return vec4(z, 0.0, 0.0); } vec4 variable(vec2 z) { return vec4(z, length(vec4(dFdx(z), dFdy(z))), 0.0); } vec4 crecip(vec4 z) { vec2 w = crecip(z.xy); return vec4(w, -cmul(z.zw, csqr(w))); } vec4 newtonStep(vec4 z) { vec4 s = vec4(0.0); for (int i = 0; i < nzeros; ++i) { s += crecip(z - constant(zeros[i])); } for (int i = 0; i < npoles; ++i) { s -= crecip(z - constant(poles[i])); } return z - crecip(s); } vec3 newton(vec4 z00) { vec4 z0 = z00; bool done = false; int count = -1; vec3 rgb = vec3(0.0); float d0 = huge; float d1 = huge; for (int n = 0; n < newtonSteps; ++n) { d1 = d0; z0 = newtonStep(z0); d0 = huge; for (int i = 0; i < nzeros; ++i) { float d = cmag2(z0.xy - zeros[i]); if (d < d0) { d0 = d; rgb = colours[i]; } } if (d0 < epsSquared) { done = true; break; } } float de = 0.0; if (done) { de = 0.5 * abs(log(d0)) * sqrt(d0) / length(z0.zw); } return tanh(clamp(weight * de, 0.0, 8.0)) * rgb; } vec3 color(vec2 z) { return newton(variable(z * 1.5)); }

You can download the above "Two Beetles Meet" Fragmentarium reimplementation.

]]>A grid of squares getting smaller at the edges. I already blogged about this calendar image for February (yes, this post is rather late): Lozenge. The only other thing to mention is the Hermann grid illusion.

]]>The concept for Wedged is "playing Tetris optimally badly". Badly in that no row is complete, and optimally in that each row has at most one empty cell, and the rectangle is filled. The implementation is in Haskell using the Diagrams library for image output. The program takes around 10 minutes to run, generating 189 PNG images totalling around 38 MB. The arrangement of pieces is the same on each run, but the uneven wobbly drawn appearance is randomized and different each time. Here is a small selection of the output:

The full source code is 555 lines long, all in one file, with no comments or documentation. The architectural overview is this:

Starting from an empty rectangle, block off one cell in each row, subject to the constraint that blocked cells in nearby rows shouldn't be too close to each other, and the blocked cells should be roughly evenly distributed between columns. Some of these blockings might be duplicates (taking into account mirroring and rotation), so pick only one from each equivalence class.

Starting from the top left empty cell in each of these boards, fill it with pieces that fit. Fitting means that the piece is entirely within the board, not overlapping any blocked cells or other pieces. There are some additional constraints to improve aesthetic appearance and reduce the amount of output images: there should not be too many pieces of the same colour in the board, all adjacent pieces should be a different colour, and no piece should be able to slide into the space left when blocked cells are removed (this applies only to the long thin blue pieces, the other pieces can't move due to the earlier constraint on nearby blocked cells). The implementation uses MonadPlus [] for tree search with pruning, giving a list of possible fillings.

The filling process has some other aesthetic constraints: the board must be diverse (there must be a wide range of distinct colours in each row and column), the complete board must have a roughly even number of pieces of each colour, and there shouldn't be any long straight line boundaries between multiple pieces. The complete boards might have duplicates under symmetries (in the case that the original blocking arrangement was symmetrical), so pick only one from each equivalence class.

Finally each board is visualized using the Diagrams library. The pieces are extracted from the board using a horrific function 75 lines long, which performs brute force case analysis to determine the orientation of each piece and how to draw it (whether a sequence of lines for most pieces, or a closed loop for the red squares). The actual drawing uses recursive randomized midpoint perturbation to make each stroke wobbly, then uses the painter's algorithm to outline it: first a thick black stroke (actually formed of lots of circles) is drawn, followed by a narrower coloured stroke (again, lots of circles). The coloured pieces are drawn on a white background rectangle, with some margin space, and the result is saved to a numbered PNG file.

You can download the Wedged source code tarball or install from Hackage.

]]>