Animated Müller-Lyer illusion
-
nautil.us has a nice blogpost of optical illusions. I could not resist the attempt to build one of them in drawbot.
pw = ph = 400 # define variables to set width and height of the canvas cols = 14 # define how many columns there should be rows = 4 # define how many rows there should be pages = 30 # define how many frames the gif should have. more pages mean slower movement angle_diff = 2 * pi/pages # we want the loop to make one full circle. 2 times pi is 360 degrees. Divided by the amount of pages this gives us the angle difference between pages. gap = pw/(cols+1) # calculate the gap between the lines. l = (ph - 2 * gap)/rows # calculate the length of the line. for p in range(pages): newPage(pw, ph) translate(gap, gap) fill(None) strokeWidth(2) for x in range(cols): y_off = cos( 3 * pi * x/cols + angle_diff * p ) * gap/2 # calculate how high or low the arrowhead should be for y in range(rows+1): stroke(1,0 ,0) if y % 2 == 0 else stroke(0, 0, 1) if y < rows: line((x*gap, y*l), (x*gap, y*l+l)) stroke(0) y_off *= -1 # switch to change direction line( (x*gap, y*l), (x*gap - gap/2, y*l - y_off) ) line( (x*gap, y*l), (x*gap + gap/2, y*l - y_off) ) # saveImage('~/Desktop/opti_illu.gif')
this should generate something like this:
-
Here's code for another, the 'intertwining illusion'.
canvas = 500 nFrames = 60 #240 size = canvas/36 num1 = 60 num2 = 44 num3 = 30 num4 = 14 r = 15 def mod(rot): m = BezierPath() m.rect(-size/2, -size/2, size, size) m.closePath() m.rotate(rot) drawPath(m) for n in range(nFrames): newPage(canvas, canvas) frameDuration(1/30) fill(.5) rect(0, 0, canvas, canvas) strokeWidth(3) translate(canvas/2, canvas/2) save() for i in range(num1): if i % 2 == 0: stroke(1) else: stroke(.15) save() translate(.8 * canvas/2, 0) mod(r * (sin(2 * pi * n/nFrames))) restore() rotate(360/num1) restore() save() for j in range(num2): if j % 2 == 0: stroke(1) else: stroke(.15) save() translate(.6 * canvas/2, 0) mod(-r * (sin(2 * pi * n/nFrames))) restore() rotate(360/num2) restore() save() for k in range(num3): if k % 2 == 0: stroke(1) else: stroke(.15) save() translate(.4 * canvas/2, 0) mod(r * (sin(2 * pi * n/nFrames))) restore() rotate(360/num3) restore() save() for l in range(num4): if l % 2 == 0: stroke(1) else: stroke(.15) save() translate(.2 * canvas/2, 0) mod(-r * (sin(2 * pi * n/nFrames))) restore() rotate(360/num4) restore() saveImage('~/Desktop/opart_intertwine.gif')
The more frames, the more subtle the shift.
-
And here's yet another—I forget the name of the illusion just now.
canvas = 500 #750 number = 15 shape = canvas/15 small = shape/2 offset = shape nFrames = 90 #240 r = 5 def semi(pos, rot): s = BezierPath() if pos == 'top': s.arc((0, 0), shape/2, 0, 180, False) elif pos == 'bottom': s.arc((0, 0), shape/2, 0, 180, True) s.closePath() s.rotate(rot) drawPath(s) for n in range(nFrames): newPage(canvas, canvas) frameDuration(1/30) fill(.85) rect(0, 0, canvas, canvas) translate(canvas/2, canvas/2) save() translate(-(number - 1) * offset/2, (number - 1) * offset/2) fill(.2) for i in range(number): save() translate(0, i * -offset) if (i % 2) == 0: v = 1 elif (i % 2) == 1: v = -1 for j in range(number): save() translate(j * offset, 0) if (j % 2) == 0: semi('top', r * (sin((2 * pi) * (n/nFrames))) * v) elif (j % 2) == 1: semi('bottom', r * (sin((2 * pi) * (n/nFrames))) * v) restore() restore() restore() translate(0, (number - 1) * offset/2) for i in range(number): save() translate(0, i * -offset) if (i % 2) == 0: fill(1) oval((cos((2 * pi) * (n/nFrames)) * canvas/2) - small/2, 0, small, small) elif (i % 2) == 1: fill(1) oval((cos((2 * pi) * (n/nFrames)) * -canvas/2) - small/2, 0, small, small) restore() saveImage('~/Desktop/op_semi_lines.gif')
With both sketches, a larger canvas and more frames (240+) makes for a better effect.
-
@mauricemeilleur cool! thanks for sharing
the 4 'rings' could also be made inside a for loop, the variable is in the translate factor of the size of the canvas
-
True—both these sketches are quickies from last year, when I was still learning my way around the code.
-
nice, ones!
here is one more:grid = 12 page_s = 600 frames = 48 tile_s = page_s/grid rhythm = [1, 0, 0, 1, 0, 1, 1, 0] def cross(pos, s): x, y = pos polygon((x - s/2, y), (x - s/2 + s/8, y - s/8), (x - s/8, y - s/8), (x - s/8, y - s/2 + s/8), (x, y - s/2), (x + s/8, y - s/2 + s/8), (x + s/8, y - s/8), (x + s/2 - s/8, y - s/8), (x + s/2, y), (x + s/2 - s/8, y + s/8), (x + s/8, y + s/8), (x + s/8, y + s/2 - s/8), (x, y + s/2), (x - s/8, y + s/2 - s/8), (x - s/8, y + s/8), (x - s/2 + s/8, y + s/8) ) for f in range(frames): newPage(page_s, page_s) frameDuration(.1) # linear alpha = f / frames if f < (frames) else 2 - (f / frames) # sinus wave alpha = .5 + .5 * sin(f/frames * 2 * pi) for y in range(grid): for x in range(grid): fill(.8) if (x+y) % 2 == 0 else fill(.7) rect(x * tile_s, y * tile_s, tile_s, tile_s) shift = (x + y) % len(rhythm) fill(1, alpha) if rhythm[ shift ] else fill(.5, alpha) cross((x * tile_s, y * tile_s), tile_s/3 ) # saveImage('wavy_checkerboard.gif')
-
ok, one more.
very similar to the previous one.page_s = 500 cell_amount = 12 rhythm = [1, 0, 0, 1, 0, 1, 1, 0] cell_s = page_s / cell_amount corner_s = cell_s/4 newPage(page_s, page_s) translate(-cell_s/2, -cell_s/2) def lozenge(pos, s): x, y = pos polygon((x, y), (x + s/2, y + s/2), (x + s, y), (x + s/2, y - s/2)) for x in range(cell_amount+1): for y in range(cell_amount+1): pos_x, pos_y = x * cell_s, y * cell_s fill(0.5) if (x+y) % 2 == 0 else fill(1) rect(pos_x, pos_y, cell_s, cell_s) fill(1) lozenge((pos_x - corner_s, pos_y), corner_s*2) fill(0) shift = (x + y) % len(rhythm) if rhythm[ shift ]: lozenge((pos_x - corner_s, pos_y), corner_s) lozenge((pos_x, pos_y), corner_s) else: lozenge((pos_x - corner_s/2, pos_y + corner_s/2), corner_s) lozenge((pos_x - corner_s/2, pos_y - corner_s/2), corner_s)