AndrewT's Stuff [Image-Heavy]
Community Forums/Showcase/AndrewT's Stuff [Image-Heavy]
| ||
This is some math-related stuff I've been working on recently that I thought I'd show you guys. Mandelbrot Explorer Parametric Systems Plotter Butterfly Curve: Epicycloid: Lissajous: DFS Maze Generation L-Systems Vector Fields Feedback is welcome. If you'd like to see any of the code, feel free to ask. |
| ||
That stuff is awesome I love looking at math creations... |
| ||
I like the vectorfields, do the dots move in realtime? |
| ||
Yea. There are two systems: one based on cells, and one based on functions. In cellular system, the field is divided into cells which are assigned angles, and a particle's velocity will change depending on the cell it is currently in. It results in a more organic, random look. That's the system you see in the last shot I posted. In the function-based system, the velocity of a particle is determined by a function of its position. It results in a more formulaic appearance. This is a screenshot of the field (x, y) -> (y, -x). |
| ||
Thats really interesting... I should try something like this using my tilemax engine... each cell already has a vector based force configurable per cell... as well as surface and spacial friction. Do the particles in this have energy as well or are they just dragged around by the forces? Cells going off the edges, are they wrapped? |
| ||
Nice! |
| ||
Thats really interesting... I should try something like this using my tilemax engine... each cell already has a vector based force configurable per cell... as well as surface and spacial friction. Do the particles in this have energy as well or are they just dragged around by the forces? Cells going off the edges, are they wrapped? The particles are just dragged around by forces in the cellular system--it's a very simple system. If they go off the edge then they do get wrapped around. In the function-based system, however, they are not wrapped when the go over the edge, instead every frame ten random particles are deleted, and ten are added. |
| ||
Very interesting stuff you've got there! May I know how many particles there are on the first "Vector fields" example and how many fps you got? |
| ||
That is really interesting. I would like to see the code for the Parametric System Plotter and everything below except for the maze generator if that's possible. I have the DFS Maze Generator. |
| ||
Very interesting stuff you've got there! May I know how many particles there are on the first "Vector fields" example and how many fps you got? I've since changed the settings so I can't give you an exact number, but in a test I just did I'm getting a solid 60FPS up to about 26000 particles, then it starts decreasing. This is on a Geforce GTS 250, AMD Phenom II X4 820, 6GB RAM. That is really interesting. I would like to see the code for the Parametric System Plotter and everything below except for the maze generator if that's possible. I have the DFS Maze Generator. No problem, I'll try and post it tonight--I'm cleaning up the parametric plotter code and working on an expression evaluator so you can edit the system in real-time. |
| ||
Vector Fields gets my vote if you are posting code :-) |
| ||
Parametric Plotter - Sorry, no editing in realtime--the expression evaluator was far too slow to perform the tens of thousands of operations required every second. Change the TX and TY expressions on lines 80 and 81 to change the system. I also have several systems commented out that you can look at.SuperStrict Global MinX:Float = -6.0, MinY:Float = -4.0, MaxX:Float = 6.0, MaxY:Float = 4.0 Graphics 1024, 768, 0 Local TX:Float, TY:Float, GX:Float = Float(GraphicsWidth()), GY:Float = Float(GraphicsHeight()), X:Float, Y:Float, OX:Float, OY:Float Local T:Double, TStart:Float, TEnd:Float, Ticks:Int Local e:Float = 2.71828183 T = 0.0 TStart = 0.0 TEnd = 2500.0 Ticks = 1000 Local A:Double, AInc:Float A = 0.0 Local MZS:Float Local MXS:Float, MYS:Float Local Animation:Int = 1 Repeat SetClsColor(20, 20, 20) Cls MXS = MouseXSpeed() MYS = MouseYSpeed() MZS = MouseZSpeed() If KeyHit(KEY_SPACE) Animation = 1 - Animation EndIf If KeyHit(KEY_LSHIFT) A = 0.0 EndIf If MZS > 0.0 Local R:Float = MaxX - MinX MinX :+ R / 10.0 MaxX :- R / 10.0 R = MaxY - MinY MinY :+ R / 10.0 MaxY :- R / 10.0 EndIf If MZS < 0.0 Local R:Float = MaxX - MinX MinX :- R / 10.0 MaxX :+ R / 10.0 R = MaxY - MinY MinY :- R / 10.0 MaxY :+ R / 10.0 EndIf If MouseDown(3) Local OMinX:Float = MinX, OMinY:Float = MinY MinX :- MXS * 0.001 * (MaxX - MinX) MinY :+ MYS * 0.001 * (MaxY - MinY) MaxX :- MXS * 0.001 * (MaxX - OMinX) MaxY :+ MYS * 0.001 * (MaxY - OMinY) EndIf If Animation A :+ 0.01 EndIf For Local I:Int = 1 To Ticks OX = X OY = Y T = Float(I) / Float(Ticks) * (TEnd - TStart) + TStart ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' TX = Sin(T) * (e^Cos(T) - 2 * Cos(4 * T) - Sin(T / 12) ^ 5) TY = Cos(T) * (e^Cos(T) - 2 * Cos(4 * T) - Sin(T / 12) ^ 5) ''''''''''''''''''''''''''''''''''''''''' 'Local R:Float, K:Float 'R = 1.0 'K = Sin(A/10.0) 'TX = R * (K + 1.0) * Cos(T) - R * Cos((K + 1.0) * T) 'TY = R * (K + 1.0) * Sin(T) - R * Sin((K + 1.0) * T) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'Local A2:Float, B:Float, Ang:Float 'A2 = Sin(A / 30.0) 'B = Cos(A / 30.0) 'Ang = 90.0 'TX = A2 * Sin(A2 * T + Ang) 'TY = B * Sin(B * T) 'TX = Expression("cos(t)*cos(a*t)") 'TY = Sin(T) * Cos(A*T) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' X = ((TX - MinX) / (MaxX - MinX)) * (GX - 1) Y = GY - ((TY - MinY) / (MaxY - MinY)) * (GY - 1) If I = 1 OX = X OY = Y EndIf SetColor(Abs(Sin(T * 6.0)) * 255.0, Abs(Cos(T * 4.0)) * 255.0, Abs(Sin(T * 3.4)) * 255.0) DrawLine(OX, OY, X, Y) Next SetColor(255, 255, 255) DrawText("Use the mouse wheel to zoom.", 10, 10) DrawText("Hold down the middle mouse button and move the mouse to move the curve.", 10, 25) DrawText("Press the space key to toggle animation.", 10, 40) DrawText("Press the shift key to reset animation.", 10, 55) DrawText("A: " + A, 10, 100) DrawText("Ticks: " + Ticks, 10, 115) DrawText("Start: " + TStart, 10, 130) DrawText("End: " + TEnd, 10, 145) Flip Until KeyHit(KEY_ESCAPE) Or AppTerminate() Function Equal:Int(Num:Float, Num2:Float, Margin:Float = 0.005) If Num > Num2 - (Margin / 2) And Num < Num2 + (Margin / 2) Return 1 EndIf Return 0 EndFunction Function Pow:Double(Base:Double, Exponent:Double) Return Base ^ Exponent EndFunction Function TextInput:String(Prompt:String, X:Int, Y:Int) Local I:String While KeyHit(KEY_ENTER) = 0 Cls Local C:Int = GetChar() If C > 0 If C = 8 I = I[..I.length - 1] Else I :+ Chr(c) EndIf EndIf DrawText(Prompt + I, X, Y) Flip EndWhile Return I EndFunction L-Systems SuperStrict Global DrawLength:Float = 20.0 Global AngleChange:Float = 60.0 Global StartX:Float = 500.0 Global StartY:Float = 500.0 Global StartA:Float = 180.0 Global SavedX:Float[10000] Global SavedY:Float[10000] Global SavedAngle:Float[10000] Global NumSaved:Int = 0 Graphics 1024, 768, 1 SeedRnd(MilliSecs()) Local Scroll:Float Local MoveX:Float, MoveY:Float Local Iterations:Int = 1 Local Axiom:String = "A" Local ProdRule:String = "A[-A-A+A++A]A[+A+A--A-A]A" Local SystemString:String = CreateLSystemString(Axiom, ProdRule, Iterations) SetClsColor(0, 0, 30) SetColor(220, 220, 255) Repeat Cls DrawText("Scroll Mouse Wheel to change DrawLength", 0, 0) DrawText("SHIFT + Scroll Mouse Wheel to change AngleChange", 0, 12) DrawText("UP and DOWN keys to increase/decrease number of iterations.", 0, 24) DrawText("LEFT and RIGHT keys to rotate L-system.", 0, 36) DrawText("CONTROL to change production rule.", 0, 48) Scroll = MouseZSpeed() MoveX = MouseXSpeed() MoveY = MouseYSpeed() If Scroll If KeyDown(KEY_LSHIFT) AngleChange = AngleChange + Scroll Else DrawLength = DrawLength + (Scroll * (DrawLength / 20.0)) EndIf EndIf If MouseDown(3) StartX = StartX + MoveX StartY = StartY + MoveY EndIf If KeyHit(KEY_UP) Iterations :+ 1 SystemString = CreateLSystemString(Axiom, ProdRule, Iterations) EndIf If KeyHit(KEY_DOWN) Iterations :- 1 SystemString = CreateLSystemString(Axiom, ProdRule, Iterations) EndIf If KeyDown(KEY_LEFT) StartA = StartA - 1 EndIf If KeyDown(KEY_RIGHT) StartA = StartA + 1 EndIf If KeyHit(KEY_LCONTROL) ProdRule = TextInput("A -> ", 10, 10) SystemString = CreateLSystemString(Axiom, ProdRule, Iterations) EndIf LSystem(SystemString) Flip Until KeyHit(KEY_ESCAPE) Or AppTerminate() Function CreateLSystemString:String(Axiom:String, ProductionRule:String, Iterations:Int) Local _LSystemString:String = Lower(Axiom) ProductionRule = Lower(ProductionRule) For Local I:Int = 1 To Iterations _LSystemString = Replace(_LSystemString, "a", ProductionRule) Next Return _LSystemString EndFunction Function LSystem(LS:String) Local X:Float, Y:Float, A:Float X = StartX Y = StartY A = StartA Local OX:Float, OY:Float OX = X OY = Y For Local I:Int = 1 To Len(LS) Local Char:String = Mid(LS, I, 1) If Char = "a" X = X + Sin(A) * DrawLength Y = Y + Cos(A) * DrawLength DrawLine(OX, OY, X, Y) OX = X OY = Y EndIf If Char = "+" A = A + AngleChange EndIf If Char = "-" A = A - AngleChange EndIf If Char = "[" SavedX[NumSaved] = X SavedY[NumSaved] = Y SavedAngle[NumSaved] = A NumSaved :+ 1 EndIf If Char = "]" X = SavedX[NumSaved - 1] Y = SavedY[NumSaved - 1] A = SavedAngle[NumSaved - 1] OX = X OY = Y NumSaved :- 1 EndIf Next EndFunction Function TextInput:String(Prompt:String, X:Int, Y:Int) Local I:String While KeyHit(KEY_ENTER) = 0 Cls Local C:Int = GetChar() If C > 0 If C = 8 I = I[..I.length - 1] Else I :+ Chr(c) EndIf EndIf DrawText(Prompt + I, X, Y) Flip EndWhile Return I EndFunction Cellular Vector Fields SuperStrict Global Cells:Float[64, 64] For Local X:Int = 0 To 63 For Local Y:Int = 0 To 63 Cells[X, Y] = Rnd() * 360.0 Next Next Function DrawField() SetScale(2.0, 2.0) For Local X:Int = 0 To 63 For Local Y:Int = 0 To 63 Local TX:Float = (Float(X) / 63.0) * Float(GraphicsWidth()) + Float(GraphicsWidth()) / 128.0 Local TY:Float = (Float(Y) / 63.0) * Float(GraphicsHeight()) + Float(GraphicsHeight()) / 128.0 SetColor(100, 100, 100) DrawLine(TX + Cos(Cells[X, Y]) * 4, TY + Sin(Cells[X, Y]) * 4, TX, TY) Next Next EndFunction Function SetCell(X:Int, Y:Int, Ang:Float) If X > 63 X = X Mod 63 EndIf If Y > 63 Y = Y Mod 63 EndIf If X < 0 X = 64 + X EndIf If Y < 0 Y = 64 + Y EndIf Cells[X, Y] = Ang EndFunction Type Particle Field X:Float Field Y:Float Field Ang:Float Global List:TList Global TurnSpeed:Float = 3.0 Global Velocity:Float = 1.2 Function Init() List = CreateList() EndFunction Function AddParticles(Num:Int) For Local I:Int = 1 To Num Local P:Particle = New Particle P.X = Rand(0, GraphicsWidth() - 1) P.Y = Rand(0, GraphicsHeight() - 1) List.AddLast(P) Next EndFunction Function RemoveParticles(Num:Int) For Local I:Int = 1 To Num List.RemoveLast() Next EndFunction Function UpdateAll() Local TX:Float, TY:Float For Local P:Particle = EachIn List If P.X > GraphicsWidth() P.X = 0 EndIf If P.Y > GraphicsHeight() P.Y = 0 EndIf If P.X < 0 P.X = GraphicsWidth() EndIf If P.Y < 0 P.Y = GraphicsHeight() EndIf Local Angle:Float = Cells[Int((P.X / Float(GraphicsWidth())) * 63.0), Int((P.Y / Float(GraphicsHeight())) * 63.0)] If Angle > P.Ang + TurnSpeed P.Ang :+ TurnSpeed EndIf If Angle < P.Ang - TurnSpeed P.Ang :- TurnSpeed EndIf If P.Ang > 360.0 P.Ang = P.Ang Mod 360.0 EndIf P.X :+ Cos(P.Ang) * Velocity P.Y :+ Sin(P.Ang) * Velocity SetColor(255, 255, 255) Plot(P.X, P.Y) Next EndFunction EndType Graphics 640, 480, 1 Particle.Init() Particle.AddParticles(2000) Local MZS:Float Local MXS:Float, MYS:Float Local Arrows:Int = 0 Repeat Cls SetColor(255, 255, 255) SetScale(1.0, 1.0) DrawText("Press the space key to toggle the indicators.", 10, 10) DrawText("Scroll the mouse wheel to add/remove particles.", 10, 25) DrawText("Hold the left mouse button and drag the mouse to change the angle values.", 10, 40) If KeyHit(KEY_SPACE) Arrows = 1 - Arrows EndIf MXS = MouseXSpeed() MYS = MouseYSpeed() MZS = MouseZSpeed() If MZS > 0 Particle.AddParticles(MZS * 200) Else If MZS < 0 Particle.RemoveParticles(-MZS * 200) EndIf EndIf If MouseDown(1) If MXS And MYS Local Ang:Float = ATan2(Float(MYS), Float(MXS)) Local CX:Int = Int((Float(MouseX()) / Float(GraphicsWidth())) * 63.0) Local CY:Int = Int((Float(MouseY()) / Float(GraphicsHeight())) * 63.0) SetCell(CX, CY, Ang) SetCell(CX - 1, CY, Ang) SetCell(CX + 1, CY, Ang) SetCell(CX, CY - 1, Ang) SetCell(CX, CY + 1, Ang) SetCell(CX - 1, CY - 1, Ang) SetCell(CX + 1, CY - 1, Ang) SetCell(CX - 1, CY + 1, Ang) SetCell(CX + 1, CY + 1, Ang) EndIf EndIf If Arrows DrawField() EndIf Particle.UpdateAll() Flip Until KeyHit(KEY_ESCAPE) Or AppTerminate() Function-Based Vector Fields SuperStrict Global MinX:Float = -1.0, MinY:Float = 1.0, MaxX:Float = 1.0, MaxY:Float = -1.0 Type Particle Field X:Float Field Y:Float Field VX:Float Field VY:Float Global List:TList Function Init() List = CreateList() EndFunction Function AddParticles(Num:Int) For Local I:Int = 1 To Num Local P:Particle = New Particle P.X = Rand(0, GraphicsWidth() - 1) P.Y = Rand(0, GraphicsHeight() - 1) List.AddLast(P) Next EndFunction Function RemoveParticles(Num:Int) For Local I:Int = 1 To Num List.RemoveLast() Next EndFunction Function RemoveFirstParticles(Num:Int) For Local I:Int = 1 To Num List.RemoveFirst() Next EndFunction Function RemoveRandomParticles(Num:Int) For Local I:Int = 1 To Num List.Remove(List.ValueAtIndex(Rand(0, List.Count() - 1))) Next EndFunction Function UpdateAll() RemoveRandomParticles(10) AddParticles(10) Local TX:Float, TY:Float, R:Float For Local P:Particle = EachIn List If P.X > GraphicsWidth() P.X = 1 EndIf If P.Y > GraphicsHeight() P.Y = 1 EndIf If P.X < 0 P.X = GraphicsWidth() EndIf If P.Y < 0 P.Y = GraphicsHeight() EndIf TX = (P.X - (GraphicsWidth() / 2)) TX = TX / (Float(GraphicsWidth()) / 2) TX = TX * (MaxX - MinX) TY = (P.Y - (GraphicsHeight() / 2)) TY = TY / (Float(GraphicsHeight()) / 2) TY = TY * (MaxY - MinY) R = Sqr(TX^2 + TY^2) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'EDIT THIS PART TO CHANGE THE APPEARANCE OF THE FIELD 'TX = the particle's X position from -1.0 to 1.0 'TY = the particle's Y position from -1.0 to 1.0 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' P.VX = TY P.VY = -TX ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' P.X :+ P.VX P.Y :+ P.VY Local Ang:Float = ATan2(P.VY, P.VX) Local Vel:Float = Sqr(P.VX * P.VX + P.VY * P.VY) DrawLine(P.X + Cos(Ang) * Vel * 4, P.Y + Sin(Ang) * Vel * 4, P.X, P.Y) 'Plot(P.X, P.Y) Next EndFunction EndType Graphics 1024, 768, 32 Particle.Init() Particle.AddParticles(5000) Local MZS:Float Repeat Cls MZS = MouseZSpeed() If MZS MinX :+ Float(MZS) / 10.0 MinY :- Float(MZS) / 10.0 MaxX :- Float(MZS) / 10.0 MaxY :+ Float(MZS) / 10.0 EndIf Particle.UpdateAll() Flip Until KeyHit(KEY_ESCAPE) Or AppTerminate() |
| ||
Beautiful stuff! |
| ||
This is all really fascinating to me, Andrew! I do have a coupla little questions though: Mathematically, can the vector fields be considered continuous? Does that work with the code or is it only possible to constrain them to the individual pixels/ var coords? and, how are you dealing with i or j for SQR(0-1) with respect to your fractal stuff? I was looking into something kinda related a while back, and after trawling the net for methods of working with complex no's and i or j even what I could find was all in other languages which I couldn't easily convert... If it's a 'trade secret' for you, no probs :) |
| ||
Good Stuff! Look at the last one (Function-Based Vector Fields) with a litte higher speed for a while, and your vision will be messed up for a while :D |
| ||
Mathematically, can the vector fields be considered continuous? Does that work with the code or is it only possible to constrain them to the individual pixels/ var coords? I'm not quite sure what you mean--can you clarify a bit? Keep in mind that my knowledge of math is currently limited to Honors Pre-Calc :P and, how are you dealing with i or j for SQR(0-1) with respect to your fractal stuff? I was looking into something kinda related a while back, and after trawling the net for methods of working with complex no's and i or j even what I could find was all in other languages which I couldn't easily convert... I came up with the following complex number UDT: Type Complex Field A:Float 'real part Field B:Float 'imaginary part coefficient Method Multiply(C:Complex) Local Copy:Complex = New Complex Copy.A = C.A Copy.B = C.B Local TempA:Float = A A = (A * Copy.A) + (-1 * B * Copy.B) B = (TempA * Copy.B) + (B * Copy.A) '(a + bi)(c + di) = ac + i(ad + bc) + (-1)bd EndMethod Method Add(C:Complex) Local Copy:Complex = New Complex Copy.A = C.A Copy.B = C.B A = A + C.A B = B + C.B '(a + bi) + (c + di) = (a + c) + (b + d)i EndMethod Method Absolute:Float() Return Sqr(A * A + B * B) EndMethod Method AbsoluteSq:Float() Return A * A + B * B EndMethod EndType Because the concept of i in this case is purely abstract, there's no need to explicitly define its value. The only operation which will actually take its value into account is multiplication when we have to square it, and we know that i * i is -1, so that doesn't present a problem either. Hope that helps, and if you have any other questions I'll do my best to answer them. I'm going to try and clean up these demos a bit by adding comments and implementing some more features so that they can be posted on my website. If you have any requests for demos feel free to ask and I'll see what I can do. :) |
| ||
this is really nice. thanks for posting the code. I can spend hours messing with this. Hopefully I can do some creative stuff with it. Thanks a lot. If you care here is some code for a sphere with nice effects. It may be down your alley and it's quite interesting to play with. Strict Global dots_in_ball# = 4440.0 ' How many dots are in the ball? ' Globals For the 3d. Type Tnode Field threeDx# Field threeDy# Field threeDz# Field color Global twoDx1# Global twoDy1# Global twoDx2# Global twoDy2# End Type Global numpoints# = dots_in_ball ' Number of points in the point table. Global distance# = 400.0 ' Needed For perspective. Global vx# ' X location. Global vy# ' Y location. Global vz# ' Z location. Global multiplier!=150.0 ' Arrays used by 3d code. Global points#[numpoints, 3] ' Holds the point locations of the game over 3d. Graphics 640,480,32 initdots() While Not KeyDown(KEY_ESCAPE) threed() ' Call the threed thing. SetColor 255,255,255 DrawText "Depth Colored Real 3D Dot Ball",0,0 DrawText "origina Coding by Tracer For the Blitz CD.",0,15 DrawText "modified by me",0,30 DrawText "Use up/down arrow Keys To adjust multiplier",0,45 DrawText "multiplier = "+multiplier,0,60 Local dummy = MouseX() ' Removes the mouse from fullscreen. If KeyDown(KEY_DOWN) Then multiplier =multiplier-0.00005 initdots() EndIf If KeyDown(KEY_UP) Then multiplier=multiplier+0.00005 initdots() EndIf Flip(0) ' Flip the screen. Cls ' Clear the screen. Wend Function initdots() For Local t= 0 To dots_in_ball-1 Local xd# = ((t*multiplier) Mod 180.0)-90.0 'Rnd(-90,90) points[t,0] = (Cos(xd) * 10.0) * (Cos(t) * 10.0) points[t,1] = (Cos(xd) * 10.0) * (Sin(t) * 10.0) points[t,2] = Sin(xd) * 100.0 Next End Function Function threed() vx# = vx# + 0.5 ' X rotation speed of ball. vy# = vy# + 0.5 ' Y rotation speed of ball. vz# = vz# + 0.5 ' Z rotation speed of ball. For Local n = 0 To numpoints-1 Local x3d = points[n, 0] Local y3d = points[n, 1] Local z3d = points[n, 2] Local ty# = ((y3d * Cos(vx#)) - (z3d * Sin(vx#))) Local tz# = ((y3d * Sin(vx#)) + (z3d * Cos(vx#))) Local tx# = ((x3d * Cos(vy#)) - (tz# * Sin(vy#))) tz# = ((x3d * Sin(vy#)) + (tz# * Cos(vy#))) Local ox# = tx# tx# = ((tx# * Cos(vz#)) - (ty# * Sin(vz#))) ty# = ((ox# * Sin(vz#)) + (ty# * Cos(vz#))) Local nx = Int(512 * (tx#) / (distance - (tz#))) + 320 Local ny = Int(240 - (512 * ty#) / (distance - (tz#))) SetColor 0,tz+100,(tz#+100) Plot nx,ny' Next End Function ' This Function looks at the z-value of the pixel ' And sets the color accoordingly. Function Color(t#) SetColor 0,0,(t+200.0)',(t+200.0),(t+200.0) End Function |
| ||
Sorry I haven't posted anything in awhile, I've been busy. That's an awesome demo jesse, thanks! I've improved upon the fractal explorer a bit, I'll post the source sometime in the next week. Burning Ship (zoomed in): Mandelbrot (zoomed in): |
| ||
Sorry about the double post, but I've been working on some more fractals, this time using Iterated Function Systems (IFS). Here are a couple randomly generated fractals I've made thus far: |
| ||
Some really cool looking stuff there. :) |
| ||
Woah, if you stare in the center of the Function-Based Vector Fields example, then look around, everything moves... it's like a trippy optical illusion |