title: Spirographs author: "@am\_i\_tom" patat: incrementalLists: true
images: backend: auto
wrap: true
margins: left: 10 right: 10
pandocExtensions: - emoji - patat_extensions ...
-
🎩 Tom Harding
-
👔 Habito (always hiring!)
-
💬 twitter.com/am_i_tom
-
🌱 github.com/i-am-tom
-
📚 tomharding.me
-
Start with a (fixed) circle (as a perimeter).
-
Pick a point on a smaller, rolling circle.
-
Roll the second circle around the edge of the first.
-
Trace the path of the chosen point.
-
Repeat until Mum's off the phone.
. 'kKd'
'okkkkOOOOOOx:. .,xXXd'
.. .:llllllllllc' .,xXXd'
.o0k' .xWNo.
.oKNk;. .;dxxxxxxxxxxo' .c0NO:.
.oKNk;. 'cooooooooooc' .c0NO:.
.oWWx. 'dk:.
.c0NO:. 'lddddddddddo,. .
.c0NO:. .,lddddddddddl'
.cOx'
..
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
main :: Effect Unit
main = do
log "Hello sailor!"
newtype Coordinate
= Coordinate
{ x :: Number
, y :: Number
}
derive instance eqCoordinate
:: Eq Coordinate
derive newtype instance semiringCoordinate
:: Semiring Coordinate
derive newtype instance ringCoordinate
:: Ring Coordinate
derive newtype instance showCoordinate
:: Show Coordinate
rotate
:: Number -> Coordinate
-> Coordinate
rotate angle (Coordinate { x, y })
= Coordinate
{ x: cos angle * x - sin angle * y
, y: sin angle * x + cos angle * y
}
rollingCirclePosition
:: Number -> Number
-> Coordinate
rollingCirclePosition sizeRatio time
= rotate time initial
where
initial = Coordinate
{ x: 0.0
, y: 1.0 - sizeRatio
}
rollingCircleRotation
:: Number -> Number
-> Number
rollingCircleRotation sizeRatio time
= -time / sizeRatio
penOffset
:: Number -> Number -> Number
-> Coordinate
penOffset sizeRatio offsetRatio rotation
= rotate rotation
$ Coordinate
{ x: 0.0
, y: sizeRatio * offsetRatio
}
mark
:: Configuration -> Seconds
-> Drawing
mark { sizeRatio, offsetRatio } (Seconds time)
= filled (fillColor colour)
$ circle x y 2.0
where
rollingCentre
= rollingCirclePosition sizeRatio time
angle
= rollingCircleRotation sizeRatio time
Coordinate { x, y }
= centreForCanvas
$ rollingCentre
+ penPosition sizeRatio offsetRatio angle
colour
= hsv (time * 180.0 % 360.0) 0.8 0.8
canvas <- lift (getCanvasElementById "spirograph")
>>= case _ of
Just canvas -> pure canvas
Nothing -> throwError "No canvas :("
lift $ setCanvasDimensions canvas
{ width: 400.0, height: 400.0 }
context <- lift (getContext2D canvas)
-- Current time as a stream vvvvvvv
stopDrawing <- lift $ animate seconds \time -> do
let config = { sizeRatio, offsetRatio }
render context (mark config time)
let crossover
= toNumber
$ numerator
$ simplify sizeRatioAsFraction
completion
= 2000.0 * pi * crossover
void
$ lift
$ setTimeout (ceil completion) stopDrawing
getX
:: forall wrapper output anythingElse
. Newtype wrapper { x :: output | anythingElse }
=> wrapper
-> output
getX
= _.x <<< unwrap
class Coordinate (object :: Type) where
transform
:: (Number -> Number)
-> (object -> object)
fold
:: forall m. Monoid m
=> (Number -> m)
-> (object -> m)
class GCoordinate
(row :: # Type)
(list :: RowList) where
transform'
:: RLProxy list -> (Number -> Number)
-> (Record row -> Record row)
fold'
:: forall m. Monoid m
=> RLProxy list -> (Number -> m)
-> (Record row -> m)
instance gcoordinateNil
:: GCoordinate row Nil where
transform' _ _ = identity
fold' _ _ _ = mempty
instance gcoordinateCons
:: ( GCoordinate row tail, IsSymbol key
, Row.Cons key Number xyz row )
=> GCoordinate row (Cons key Number tail) where
transform' _ f record
= modify (SProxy :: SProxy key) f
$ transform' (RLProxy :: RLProxy tail) f record
fold' _ f record
= f (get (SProxy :: SProxy key) record)
<> fold' (RLProxy :: RLProxy tail) f record
instance coordinateImpl
:: ( RowToList row list
, GCoordinate row list )
=> Coordinate (Record row) where
transform = transform' (RLProxy :: RLProxy list)
fold = fold' (RLProxy :: RLProxy list)
offset
:: forall row. Coordinate row
=> row -> Number
offset record
= sqrt total
where
folder x = Additive (x `pow` 2.0)
Additive total = fold folder record
-
Floating point precision!
-
(a * b ) % b === 0
-
(a * 2 ) % 2 === 0
-
(a * 3 ) % 3 === 0
-
(a * π ) % π === 0
-
(a * (π / 4)) % (π / 4) === 1.2566
-
show excuse.png
-
But, with a better number type, yes!
-
Stateful animation with
FRP.Behavior.fixB
. -
purescript-super-circles
-
Continuous lines (better laptops).
-
Interactive controls.
-
Other types of ellipses to roll.
-
Present animations on a newer laptop.
-
Simple canvas drawing with
purescript-drawing
. -
Simple animation with
purescript-behaviors
. -
More examples with
purescript-super-circles
. -
There was life before the Internet.
- 🎩 Tom Harding
- 👔 Habito (always hiring!)
- 💬 twitter.com/am_i_tom
- 🌱 github.com/i-am-tom
- 📚 tomharding.me
- 👑 github.com/jaspervdj/patat