update documentation to include reticles and vector math
It's probably not very good though.
This commit is contained in:
parent
37c575748b
commit
fd3f37d744
2 changed files with 339 additions and 170 deletions
372
docs/rkgk.dj
372
docs/rkgk.dj
|
@ -86,8 +86,8 @@ If you want to draw multiple scribbles, you can wrap them into a list, which we
|
|||
-- Draw two colorful dots instead of one!
|
||||
withDotter \d ->
|
||||
[
|
||||
stroke 8 #F00 (vec ((vecX (d To)) + 4) (vecY (d To)))
|
||||
stroke 8 #00F (vec ((vecX (d To)) - 4) (vecY (d To)))
|
||||
stroke 8 #F00 (d To + vec 4 0)
|
||||
stroke 8 #00F (d To + vec (-4) 0)
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -108,26 +108,23 @@ It'll draw the first inner list, which contains two scribbles, and then it'll dr
|
|||
withDotter \d ->
|
||||
[
|
||||
[
|
||||
stroke 8 #F00 (vec ((vecX (d To)) + 4) (vecY (d To)))
|
||||
stroke 8 #00F (vec ((vecX (d To)) - 4) (vecY (d To)))
|
||||
stroke 8 #F00 (d To + vec 4 0)
|
||||
stroke 8 #00F (d To + vec (-4) 0)
|
||||
]
|
||||
[
|
||||
stroke 8 #FF0 (vec (vecX (d To)) ((vecY (d To)) + 4))
|
||||
stroke 8 #0FF (vec (vecX (d To)) ((vecY (d To)) - 4))
|
||||
stroke 8 #FF0 (d To + vec 0 4)
|
||||
stroke 8 #0FF (d To + vec 0 (-4))
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
::: aside
|
||||
|
||||
I know this example is kind of horrendous to read right now.
|
||||
Another weird thing: when negating a number, you have to put it in parentheses.
|
||||
|
||||
This is because haku currently does not have vector math, which means we have to disassemble and reassemble vectors manually.
|
||||
|
||||
{% Another weird thing: when negating a number, you have to put it in parentheses. %}
|
||||
{% This is because haku does not see your spaces---`vec -4`, `vec - 4`, and `vec-4` all mean the same thing! %}
|
||||
{% In this case, it will always choose the 2nd interpretation---vec minus four. %}
|
||||
{% So to make it interpret our minus four as, well, _minus four_, we need to enclose it in parentheses. %}
|
||||
This is because haku does not see your spaces---`vec -4`, `vec - 4`, and `vec-4` all mean the same thing!
|
||||
In this case, it will always choose the 2nd interpretation---vec minus four.
|
||||
So to make it interpret our minus four as, well, _minus four_, we need to enclose it in parentheses.
|
||||
|
||||
:::
|
||||
|
||||
|
@ -163,7 +160,7 @@ If you reorder or remove any one of them, your brush isn't going to work!
|
|||
- Then, `\d ->` lets us name the data we get back from the UI.
|
||||
`d` ends up containing a few useful properties, but the most useful one for us is `To`, which contains the current mouse position (where *`To`* draw).
|
||||
|
||||
- We'll get to what all these sigils mean to haku later!
|
||||
- We'll get to what all these `\` and `->` sigils mean to haku later!
|
||||
|
||||
- On the next line we have a `stroke`.
|
||||
`stroke` is a _function_---a recipe for producing data!\
|
||||
|
@ -200,11 +197,18 @@ Likewise, negative X coordinates go leftwards, and negative Y coordinates go upw
|
|||
|
||||
---
|
||||
|
||||
Going back to the example though, `vec` is yet another function, except instead of producing strokes, it produces vectors!
|
||||
Vectors in haku are obtained with another function---`vec`---though we don't use it in the basic example, because `d To` already is a vector.
|
||||
Vectors support all the usual math operators though, so if we wanted to, we could, for example, add a vector to `d To`, thus moving the position of the dot relative to the mouse cursor:
|
||||
|
||||
Note how it's parenthesized though---recall that function arguments are separated with spaces, so if we didn't parenthesize the `vec`, we'd end up passing `vec`, `0`, and `0` back to `stroke`---which is far from what we want!
|
||||
```haku
|
||||
withDotter \d ->
|
||||
stroke 8 #000 (d To + vec 10 0) -- moved 10 pixels rightwards
|
||||
```
|
||||
|
||||
And with all that, we let haku mix all the ingredients together, and get a black dot under the cursor.
|
||||
Also note how the `d To` expression is parenthesized.
|
||||
This is because otherwise, its individual parts would be interpreted as separate arguments to `stroke`, which is not what we want!
|
||||
|
||||
Anyways, with all that, we let haku mix all the ingredients together, and get a black dot under the cursor.
|
||||
|
||||
```haku
|
||||
withDotter \d ->
|
||||
|
@ -233,21 +237,23 @@ withDotter \d ->
|
|||
We replace the singular position `d To` with a `line`. `line` expects two arguments, which are vectors defining the line's start and end points.
|
||||
For the starting position we use a _different_ property of `d`, which is `From`---this is the _previous_ value of `To`, which allows us to draw a continuous line.
|
||||
|
||||
```haku
|
||||
[
|
||||
stroke 8 #F00 (circle (-16) 0 16)
|
||||
stroke 8 #00F (rect 0 (-16) 32 32)
|
||||
]
|
||||
```
|
||||
|
||||
::: aside
|
||||
|
||||
In haku, by adding thickness to a point, it becomes a square.
|
||||
In theory it could also become a circle...
|
||||
But let's not go down that rabbit hole.
|
||||
In haku, by adding thickness to a point, it becomes a circle.
|
||||
In theory it could also become a square... but if line caps are rounded, they connect together much more nicely!
|
||||
|
||||
:::
|
||||
|
||||
haku also supports other kinds of shapes: circles and rectangles.
|
||||
|
||||
```haku
|
||||
withDotter \d ->
|
||||
[
|
||||
stroke 8 #F00 (circle (d To + vec (-16) 0) 16)
|
||||
stroke 8 #00F (rect (d To + vec 0 (-16)) 32 32)
|
||||
]
|
||||
```
|
||||
|
||||
- `circle`s are made up of an X position, Y position, and radius.
|
||||
|
||||
- `rect`s are made up of the (X and Y) position of their top-left corner, and a size (width and height).\
|
||||
|
@ -261,9 +267,10 @@ But if describing data was all we ever wanted, we could've just used any ol' dra
|
|||
Remember that example from before?
|
||||
|
||||
```haku
|
||||
withDotter \d ->
|
||||
[
|
||||
stroke 8 #F00 (vec 4 0)
|
||||
stroke 8 #00F (vec (-4) 0)
|
||||
stroke 8 #F00 (d To + vec 4 0)
|
||||
stroke 8 #00F (d To + vec (-4) 0)
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -271,9 +278,10 @@ It has quite a bit of repetition in it.
|
|||
If we wanted to change the size of the points, we'd need to first update the stroke thickness...
|
||||
|
||||
```haku
|
||||
withDotter \d ->
|
||||
[
|
||||
stroke 4 #F00 (vec 4 0)
|
||||
stroke 4 #00F (vec (-4) 0)
|
||||
stroke 4 #F00 (d To + vec 4 0)
|
||||
stroke 4 #00F (d To + vec (-4) 0)
|
||||
---
|
||||
]
|
||||
```
|
||||
|
@ -284,9 +292,9 @@ So we also have to update their positions.
|
|||
|
||||
```haku
|
||||
[
|
||||
stroke 4 #F00 (vec 2 0)
|
||||
stroke 4 #F00 (d To + vec 2 0)
|
||||
---
|
||||
stroke 4 #00F (vec (-2) 0)
|
||||
stroke 4 #00F (d To + vec (-2) 0)
|
||||
--
|
||||
]
|
||||
```
|
||||
|
@ -311,9 +319,10 @@ So we can define `thickness` to be `4`, and then use it in our scribbles.
|
|||
```haku
|
||||
thickness = 4
|
||||
|
||||
withDotter \d ->
|
||||
[
|
||||
stroke thickness #F00 (vec 2 0)
|
||||
stroke thickness #00F (vec (-2) 0)
|
||||
stroke thickness #F00 (d To + vec 2 0)
|
||||
stroke thickness #00F (d To + vec (-2) 0)
|
||||
---------
|
||||
]
|
||||
```
|
||||
|
@ -327,15 +336,25 @@ To keep a consistent program structure, haku also forces all your defs to appear
|
|||
You can think of the defs as a list of ingredients for the final scribble.
|
||||
Reading the ingredients can give you context as to what you're gonna be cooking, so it's useful to have them first!
|
||||
|
||||
If you find this hard to grasp, just know that _defs usually go before `withDotter`_.
|
||||
|
||||
::: aside
|
||||
|
||||
We cannot refer to `d` from defs, because `d` is only visible on the `withDotter` line and below.
|
||||
We'll get to why soon!
|
||||
|
||||
:::
|
||||
|
||||
Anyways, we can likewise replace our `2` constants with a def:
|
||||
|
||||
```haku
|
||||
thickness = 4
|
||||
xOffset = 2
|
||||
|
||||
withDotter \d ->
|
||||
[
|
||||
stroke thickness #F00 (vec xOffset 0)
|
||||
stroke thickness #00F (vec (-xOffset) 0)
|
||||
stroke thickness #F00 (d To + vec xOffset 0)
|
||||
stroke thickness #00F (d To + vec (-xOffset) 0)
|
||||
---------
|
||||
]
|
||||
```
|
||||
|
@ -347,9 +366,13 @@ This naming convention is known as `camelCase`, and is used everywhere throughou
|
|||
::: aside
|
||||
|
||||
Of note is that haku names also cannot start with an uppercase letter.
|
||||
It's reserved syntax for the future.
|
||||
Uppercase names are special values we call _tags_.
|
||||
|
||||
Right now the only names that start with an uppercase letter are the two booleans, `True` and `False`.
|
||||
Tags are values which represent names.
|
||||
For example, the `To` in `d To` is a tag.
|
||||
It represents the name of the piece of data we're extracting from `d`.
|
||||
|
||||
There are also two special tags, `True` and `False`, which represent [Boolean](https://en.wikipedia.org/wiki/Boolean_algebra) truth and falsehood.
|
||||
|
||||
:::
|
||||
|
||||
|
@ -361,9 +384,10 @@ thickness = 8
|
|||
---
|
||||
xOffset = 2
|
||||
|
||||
withDotter \d ->
|
||||
[
|
||||
stroke thickness #F00 (vec xOffset 0)
|
||||
stroke thickness #00F (vec (-xOffset) 0)
|
||||
stroke thickness #F00 (d To + vec xOffset 0)
|
||||
stroke thickness #00F (d To + vec (-xOffset) 0)
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -374,9 +398,10 @@ thickness = 8
|
|||
xOffset = thickness / 2
|
||||
-------------
|
||||
|
||||
withDotter \d ->
|
||||
[
|
||||
stroke thickness #F00 (vec xOffset 0)
|
||||
stroke thickness #00F (vec (-xOffset) 0)
|
||||
stroke thickness #F00 (d To + vec xOffset 0)
|
||||
stroke thickness #00F (d To + vec (-xOffset) 0)
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -389,14 +414,16 @@ So far we've only been dealing with strokes.
|
|||
So why not switch it up a little and _fill in_ a shape?
|
||||
|
||||
```haku
|
||||
fill #000 (circle 0 0 16)
|
||||
withDotter \d ->
|
||||
fill #000 (circle (d To) 16)
|
||||
```
|
||||
|
||||
How about... some transparency?
|
||||
Recall that colors can have an alpha component, so let's try using that!
|
||||
|
||||
```haku
|
||||
fill #0001 (circle 0 0 16)
|
||||
withDotter \d ->
|
||||
fill #0001 (circle (d To) 16)
|
||||
```
|
||||
|
||||
If you play around with this brush, you'll notice how the circles blend together really nicely.
|
||||
|
@ -405,20 +432,22 @@ That's the power of Alpha!
|
|||
Now let's see what happens if we draw two such circles on top of each other---one bigger, one smaller.
|
||||
|
||||
```haku
|
||||
withDotter \d ->
|
||||
[
|
||||
fill #0001 (circle 0 0 16)
|
||||
fill #0001 (circle 0 0 32)
|
||||
fill #0001 (circle (d To) 16)
|
||||
fill #0001 (circle (d To) 32)
|
||||
]
|
||||
```
|
||||
|
||||
How about four?
|
||||
|
||||
```haku
|
||||
withDotter \d ->
|
||||
[
|
||||
fill #0001 (circle 0 0 8)
|
||||
fill #0001 (circle 0 0 16)
|
||||
fill #0001 (circle 0 0 24)
|
||||
fill #0001 (circle 0 0 32)
|
||||
fill #0001 (circle (d To) 8)
|
||||
fill #0001 (circle (d To) 16)
|
||||
fill #0001 (circle (d To) 24)
|
||||
fill #0001 (circle (d To) 32)
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -455,14 +484,15 @@ That'll need fixing!
|
|||
Either way, let's define a function that'll make us those circles!
|
||||
|
||||
```haku
|
||||
splat = \radius ->
|
||||
fill #0001 (circle 0 0 radius)
|
||||
splat = \d, radius ->
|
||||
fill #0001 (circle (d To) radius)
|
||||
|
||||
withDotter \d ->
|
||||
[
|
||||
splat 8
|
||||
splat 16
|
||||
splat 24
|
||||
splat 32
|
||||
splat d 8
|
||||
splat d 16
|
||||
splat d 24
|
||||
splat d 32
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -481,6 +511,10 @@ To dismantle that weird `\` syntax...
|
|||
A function can have an arbitrary number of parameters listed, separated by commas, and that many parameters _must_ be passed to it.
|
||||
Otherwise your brush will fail with an error!
|
||||
|
||||
- Note how also pass `d` into `splat`.
|
||||
If you're a keen-eyed observer, you will have already noticed that the `\d ->` after `withDotter` is a function, too!
|
||||
And the values a function uses, such as `d To`, have to come from somewhere.
|
||||
|
||||
- And lastly, after an arrow `->`, we have the function's result.
|
||||
|
||||
Note that a function can only have _one_ result, just like a brush can only have one scribble.
|
||||
|
@ -500,18 +534,19 @@ haku limits the use of overloading to system functions for simplicity---adding o
|
|||
Since these transparent circles are so much easier to draw now, let's make a few more of them!
|
||||
|
||||
```haku
|
||||
splat = \radius ->
|
||||
fill #0001 (circle 0 0 radius)
|
||||
splat = \d, radius ->
|
||||
fill #0001 (circle (d To) radius)
|
||||
|
||||
withDotter \d ->
|
||||
[
|
||||
splat 8
|
||||
splat 16
|
||||
splat 24
|
||||
splat 32
|
||||
splat 40
|
||||
splat 48
|
||||
splat 56
|
||||
splat 64
|
||||
splat d 8
|
||||
splat d 16
|
||||
splat d 24
|
||||
splat d 32
|
||||
splat d 40
|
||||
splat d 48
|
||||
splat d 56
|
||||
splat d 64
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -545,16 +580,17 @@ Until some threshold is reached, in which case we just make a single circle.
|
|||
The first part is easy to do: haku allows us to define a function that calls itself without making any fuss.
|
||||
|
||||
```haku
|
||||
splat = \radius ->
|
||||
fill #0001 (circle 0 0 radius)
|
||||
splat = \d, radius ->
|
||||
fill #0001 (circle (d To) radius)
|
||||
|
||||
airbrush = \size ->
|
||||
airbrush = \d, size ->
|
||||
[
|
||||
splat size
|
||||
airbrush (size - 8)
|
||||
splat d size
|
||||
airbrush d (size - 8)
|
||||
]
|
||||
|
||||
airbrush 64 -- sounds like some Nintendo 64 game about graffiti, lol.
|
||||
withDotter \d ->
|
||||
airbrush d 64
|
||||
```
|
||||
|
||||
But...
|
||||
|
@ -588,34 +624,37 @@ color =
|
|||
else
|
||||
#F00
|
||||
|
||||
fill color (circle 0 0 radius)
|
||||
withDotter \d ->
|
||||
fill color (circle (d To) radius)
|
||||
```
|
||||
|
||||
- `<` is a function that produces `true` if the second argument is a smaller number than the first argument.
|
||||
|
||||
Truth and falsehood are data too, and are represented with the values `true` and `false`.
|
||||
Truth and falsehood are data too, and are represented with the values `True` and `False`.
|
||||
|
||||
- We need three arguments to execute an `if`: the condition, the data to use when the condition is `true`, and the data to use when the condition is `false`.
|
||||
- We need three arguments to execute an `if`: the condition, the data to use when the condition is `True`, and the data to use when the condition is `False`.
|
||||
|
||||
What's magical about an `if` is that _only one branch is executed_.
|
||||
|
||||
In a function call, all arguments will always be calculated.
|
||||
An `if` only calculates the argument it needs to produce the result.
|
||||
This allows us to use it to prevent unbounded recursion in our `airbrush` example.
|
||||
|
||||
```haku
|
||||
splat = \radius ->
|
||||
fill #0001 (circle 0 0 radius)
|
||||
splat = \d, radius ->
|
||||
fill #0001 (circle (d To) radius)
|
||||
|
||||
airbrush = \size ->
|
||||
airbrush = \d, size ->
|
||||
if (size > 0)
|
||||
[
|
||||
splat size
|
||||
airbrush (size - 8)
|
||||
splat d size
|
||||
airbrush d (size - 8)
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
||||
airbrush 64
|
||||
withDotter \d ->
|
||||
airbrush d 64
|
||||
```
|
||||
|
||||
Neat!
|
||||
|
@ -628,39 +667,41 @@ But the airbrush still looks super primitive.
|
|||
Let's try increasing the fidelity by doing smaller steps!
|
||||
|
||||
```haku
|
||||
splat = \radius ->
|
||||
fill #0001 (circle 0 0 radius)
|
||||
splat = \d, radius ->
|
||||
fill #0001 (circle (d To) radius)
|
||||
|
||||
airbrush = \size ->
|
||||
airbrush = \d, size ->
|
||||
if (size > 0)
|
||||
[
|
||||
splat size
|
||||
airbrush (size - 1)
|
||||
splat d size
|
||||
airbrush d (size - 1)
|
||||
---
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
||||
airbrush 64
|
||||
withDotter \d ->
|
||||
airbrush d 64
|
||||
```
|
||||
|
||||
Well... sure, that's just a black blob with a slight gradient on the outer edge, so let's decrease the opacity.
|
||||
|
||||
```haku
|
||||
splat = \radius ->
|
||||
fill #00000004 (circle 0 0 radius)
|
||||
splat = \d, radius ->
|
||||
fill #00000004 (circle (d To) radius)
|
||||
---------
|
||||
|
||||
airbrush = \size ->
|
||||
airbrush = \d, size ->
|
||||
if (size > 0)
|
||||
[
|
||||
splat size
|
||||
airbrush (size - 1)
|
||||
splat d size
|
||||
airbrush d (size - 1)
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
||||
airbrush 64
|
||||
withDotter \d ->
|
||||
airbrush d 64
|
||||
```
|
||||
|
||||
Looks good as a single dot, but if you try drawing with it... it's gray??
|
||||
|
@ -698,8 +739,9 @@ Most commonly, colors are blended using _linear interpolation_---which is essent
|
|||
|
||||
Mathematically, linear interpolation is defined using this formula:
|
||||
|
||||
```
|
||||
lerp(a, b, t) = a + (b - a) * t
|
||||
```haku
|
||||
lerp = \a, b, t ->
|
||||
a + (b - a) * t
|
||||
```
|
||||
|
||||
What we're doing when blending colors, is mixing between a _source_ color (the wall), and a _destination_ color (the brush) on each channel.
|
||||
|
@ -710,8 +752,8 @@ But due to this reduced precision on the wall, we have to convert from a real nu
|
|||
Consider that we're drawing circles of opacity 0.01 every single time.
|
||||
Now let's look what happens when we try to blend each circle on top of a single pixel...
|
||||
|
||||
```
|
||||
lerp(0, 255, 0.01) = 0 + (255 - 0) * 0.01 = 255 * 0.01 = 2.55
|
||||
```haku
|
||||
lerp 0 255 0.01 = 0 + (255 - 0) * 0.01 = 255 * 0.01 = 2.55
|
||||
```
|
||||
|
||||
That's one circle.
|
||||
|
@ -727,33 +769,33 @@ For negative results, it gives different results: `floor(-1.5)` would be `-2`, w
|
|||
|
||||
So for the next step, we'll be interpolating from `2`, and not `2.55`...
|
||||
|
||||
```
|
||||
lerp(2, 255, 0.01) = 4.53
|
||||
lerp(4, 255, 0.01) = 6.51
|
||||
lerp(6, 255, 0.01) = 8.49
|
||||
lerp(8, 255, 0.01) = 10.47
|
||||
```haku
|
||||
lerp 2 255 0.01 = 4.53
|
||||
lerp 4 255 0.01 = 6.51
|
||||
lerp 6 255 0.01 = 8.49
|
||||
lerp 8 255 0.01 = 10.47
|
||||
...
|
||||
```
|
||||
|
||||
I think you can see the pattern here.
|
||||
This continues until around 52, where the decimal point finally goes below zero, and now we're incrementing by one instead.
|
||||
|
||||
```
|
||||
```haku
|
||||
...
|
||||
lerp(52, 255, 0.01) = 54.03
|
||||
lerp(54, 255, 0.01) = 56.01
|
||||
lerp(56, 255, 0.01) = 57.99 -- !!
|
||||
lerp(57, 255, 0.01) = 58.98
|
||||
lerp 52 255 0.01 = 54.03
|
||||
lerp 54 255 0.01 = 56.01
|
||||
lerp 56 255 0.01 = 57.99 -- !!
|
||||
lerp 57 255 0.01 = 58.98
|
||||
...
|
||||
```
|
||||
|
||||
...and at one point, we get to this:
|
||||
|
||||
```
|
||||
lerp(153, 255, 0.01) = 154.02
|
||||
lerp(154, 255, 0.01) = 155.01
|
||||
lerp(155, 255, 0.01) = 156
|
||||
lerp(156, 255, 0.01) = 156.99 -- !!
|
||||
```haku
|
||||
lerp 153 255 0.01 = 154.02
|
||||
lerp 154 255 0.01 = 155.01
|
||||
lerp 155 255 0.01 = 156
|
||||
lerp 156 255 0.01 = 156.99 -- !!
|
||||
```
|
||||
|
||||
Truncating 156.99 will get us to 156 again, which means we're stuck!
|
||||
|
@ -762,7 +804,7 @@ This precision limitation is quite unfortunate, but I don't have a solution for
|
|||
Maybe one day.
|
||||
For now you'll have to construct your brushes with this in mind.
|
||||
|
||||
## And more limits
|
||||
### And more limits
|
||||
|
||||
There are more limits on top of this, which stem from haku's design.
|
||||
Since it's running _your_ code on _my_ server, it has some arbitrary limits set to prevent it from causing much harm.
|
||||
|
@ -771,14 +813,108 @@ haku code cannot be too long, and it cannot execute too long.
|
|||
It cannot consume too much memory---you cannot have too many definitions, or too many temporary values at once.
|
||||
There are also memory usage limits on "heavyweight" data, such as functions or lists.
|
||||
|
||||
Basically, don't DoS me with it ^^'
|
||||
|
||||
I'm not specifying the precise limits here, because the app will show these to you in the future.
|
||||
There's no point in documenting them if you can't inspect your brush's resource usage easily.
|
||||
## Reticles
|
||||
|
||||
Having basic knowledge of functions and scribbles, you may be wondering: _what does that `withDotter` function do?_
|
||||
It surely looks a bit magical, conjuring that `d` parameter from nowhere; and `d` contains everything we need to draw!
|
||||
|
||||
Put simply, `withDotter` is what rakugaki calls a *reticle*.
|
||||
Reticles are pieces of data representing _interactions with the wall_.
|
||||
|
||||
A reticle is usually composed of two parts: the reticle data, defining how the interaction is meant to be initiated, and a _continuation function_.
|
||||
When your brush gives rakugaki a reticle, it will take the reticle data, and let the user perform an interaction.
|
||||
Once the interaction is performed, it will give whatever user input it has gathered as an argument to the continuation function, which can return a scribble to draw on the wall, or another reticle.
|
||||
|
||||
rakugaki will continue performing interactions until the brush gives back a scribble.
|
||||
|
||||
`withDotter` is the simplest, most direct reticle for interacting with the wall.
|
||||
It allows you to paint freely, and gives the brush information on your current mouse position, previous mouse position, and _number of steps_ performed thus far.
|
||||
|
||||
You've already seen the former two properties---those are `d To` and `d From`.
|
||||
The number of steps is available as `d Num`.
|
||||
|
||||
This is an integer starting at 0, incremented by 1 with each execution of the brush, until you release your mouse cursor.
|
||||
This allows you to _animate_ your brushes over time!
|
||||
For example, this brush draws a rainbow.
|
||||
|
||||
```haku
|
||||
colorCurve = \n ->
|
||||
abs (cos n)
|
||||
|
||||
pi = 3.14159265
|
||||
l = 0.1 -- wavelength
|
||||
|
||||
withDotter \d ->
|
||||
let r = colorCurve (d Num * l)
|
||||
let g = colorCurve (d Num * l + pi/3)
|
||||
let b = colorCurve (d Num * l + 2*pi/3)
|
||||
let color = rgba r g b 1
|
||||
stroke 8 color (line (d From) (d To))
|
||||
```
|
||||
|
||||
Currently, `withDotter` is the only reticle available in rakugaki, and it cannot be chained due to its immediate nature:
|
||||
`withDotter` continues executing immediately after you move your mouse by the _tiniest_ bit, so it's unclear how to even continue after that!
|
||||
|
||||
In the future rakugaki might get reticles that let you select lines, rectangles, ellipses, curves... but today is not that day.
|
||||
|
||||
|
||||
### What's that, `let`?
|
||||
|
||||
I mentioned before that you cannot have defs inside functions.
|
||||
What you _can_ have though, is `let`s, which define _variables_.
|
||||
|
||||
Unlike defs, which are constant and cannot vary, variables' values can depend on function parameters---and a function can be called with a different set of parameters each time, thus making them variable!
|
||||
|
||||
A `let` always takes the following form.
|
||||
|
||||
```haku
|
||||
let name = value
|
||||
then
|
||||
```
|
||||
|
||||
It's very similar to a def, with one major difference.
|
||||
Because a `let` by itself only _names a value_ and does not have a result, it must be followed by another expression on the following line---and that expression determines the result.
|
||||
The magic is that this continuing expression can refer to the `name` we had previously assigned in the `let` expression.
|
||||
|
||||
::: aside
|
||||
|
||||
Here's a bit of trivia: the variable defined by a `let` is exactly the same as a function parameter.
|
||||
The `let` above is equivalent to applying the argument `value` to a function taking in the parameter `name`, and returning `then` as the result.
|
||||
|
||||
```haku
|
||||
(\name -> then) value
|
||||
```
|
||||
|
||||
This basic little trick with immediately applying a function stands the basis of a formal mathematical system called [_lambda calculus_](https://en.wikipedia.org/wiki/Lambda_calculus).
|
||||
It underpins a large number of functional programming languages, including haku, and more famously [Haskell](https://www.haskell.org/)!
|
||||
|
||||
That's right. haku is a cute little Haskell for artists.
|
||||
|
||||
:::
|
||||
|
||||
`let`s aren't only useful for reusability---they're also helpful for breaking your brushes into smaller, more digestible pieces!
|
||||
Compare the above version of the rainbow brush to this version, where all the `let`s are written inline:
|
||||
|
||||
```haku
|
||||
colorCurve = \n ->
|
||||
abs (cos n)
|
||||
|
||||
pi = 3.14159265
|
||||
l = 0.1 -- wavelength
|
||||
|
||||
withDotter \d ->
|
||||
stroke 8 (rgba (colorCurve (d Num * l)) (colorCurve (d Num * l + pi/3)) (colorCurve (d Num * l + 2*pi/3)) 1) (line (d From) (d To))
|
||||
```
|
||||
|
||||
That's one hard to read beast of a `stroke`!
|
||||
|
||||
Generally, if a line is so long it wraps around rakugaki's narrow little text editor, it's probably a good idea to split it into variables.
|
||||
|
||||
|
||||
## Have fun
|
||||
|
||||
With that said, I hope you can have fun with rakugaki despite its flaws.
|
||||
With that said, I hope you can have fun with rakugaki despite it being in its infancy!
|
||||
|
||||
You may want to check out the [system library reference](/docs/system.html) now, to know what else you can do with the language---this little introduction barely even scratched the surface of what's possible!
|
||||
|
||||
|
|
|
@ -63,45 +63,96 @@ Additionally, the syntax `a | b` may be used to signify that one of the listed t
|
|||
-
|
||||
a : number
|
||||
-> number
|
||||
|
||||
-
|
||||
a : vector
|
||||
-> vector
|
||||
```
|
||||
|
||||
`-`, when used in its unary form `-x`, returns the number `x` with the opposite sign.
|
||||
When used on vectors, returns the same vector facing the reverse direction (the individual components are negated.)
|
||||
|
||||
This operation is not defined for colors, because it doesn't make sense to have a color with negative RGBA values.
|
||||
|
||||
```haku
|
||||
+
|
||||
a : number
|
||||
b : number
|
||||
-> number
|
||||
|
||||
+
|
||||
a : vector
|
||||
b : vector
|
||||
-> vector
|
||||
|
||||
+
|
||||
a : rgba
|
||||
b : rgba
|
||||
-> rgba
|
||||
```
|
||||
|
||||
`+` adds two numbers together.
|
||||
`+` adds two numbers, vectors, or colors together.
|
||||
|
||||
```haku
|
||||
-
|
||||
a : number
|
||||
b : number
|
||||
-> number
|
||||
|
||||
-
|
||||
a : vector
|
||||
b : vector
|
||||
-> vector
|
||||
|
||||
-
|
||||
a : rgba
|
||||
b : rgba
|
||||
-> rgba
|
||||
```
|
||||
|
||||
`-`, when used in its binary form `x - y`, subtracts two numbers from one another.
|
||||
`-`, when used in its binary form `x - y`, subtracts two numbers, vectors, or colors from one another.
|
||||
|
||||
```haku
|
||||
*
|
||||
a : number
|
||||
b : number
|
||||
-> number
|
||||
|
||||
*
|
||||
a : vector
|
||||
b : vector
|
||||
-> vector
|
||||
|
||||
*
|
||||
a : rgba
|
||||
b : rgba
|
||||
-> rgba
|
||||
```
|
||||
|
||||
`*` multiplies two numbers together.
|
||||
When used on vectors, it scales them component-wise.
|
||||
Likewise for colors.
|
||||
|
||||
```haku
|
||||
/
|
||||
a : number
|
||||
b : number
|
||||
-> number
|
||||
|
||||
/
|
||||
a : vector
|
||||
b : vector
|
||||
-> vector
|
||||
|
||||
/
|
||||
a : rgba
|
||||
b : rgba
|
||||
-> rgba
|
||||
```
|
||||
|
||||
`/` divides a number by another number.
|
||||
When used on vectors, it divides them component-wise.
|
||||
Likewise for colors.
|
||||
|
||||
```haku
|
||||
floor
|
||||
|
@ -324,7 +375,7 @@ The following functions are used to compare values and work with `boolean`s.
|
|||
-> boolean
|
||||
```
|
||||
|
||||
If `b` is `()` or `False`, `not` returns `true`.
|
||||
If `b` is `()` or `False`, `not` returns `True`.
|
||||
Otherwise it returns `False`.
|
||||
|
||||
```haku
|
||||
|
@ -459,25 +510,6 @@ vecW
|
|||
|
||||
---
|
||||
|
||||
Note that mathematical operations are currently not defined for vectors.
|
||||
You may define your own vector operations like so:
|
||||
|
||||
```haku
|
||||
-- Vector addition
|
||||
addv = \a, b ->
|
||||
vec (vecX a + vecX b) (vecY a + vecY b) (vecZ a + vecZ b) (vecW a + vecW b)
|
||||
|
||||
-- Likewise for subtraction, multiplication, and division.
|
||||
```
|
||||
|
||||
Note that haku-defined vector operations like these are more costly the more components they operate on.
|
||||
Therefore, it's recommended to only define them for two dimensions, unless you really need more.
|
||||
|
||||
```haku
|
||||
addv2 = \a, b ->
|
||||
vec (vecX a + vecX b) (vecY a + vecY b)
|
||||
```
|
||||
|
||||
## Colors
|
||||
|
||||
```haku
|
||||
|
@ -520,6 +552,7 @@ For example, consider multiplicatively blending two colors.
|
|||
|
||||
```haku
|
||||
-- This is how you can multiply two colors together.
|
||||
-- Note that the `*` operator works for colors, so you don't need to define this in your brushes.
|
||||
mulRgba = \a, b ->
|
||||
rgba (rgbaR a * rgbaR b) (rgbaG a * rgbaG b) (rgbaB a * rgbaB b) (rgbaA a * rgbaA b)
|
||||
```
|
||||
|
@ -539,12 +572,12 @@ mulRgba = \a, b ->
|
|||
Note that haku does not clamp colors to the `0` to `1` range.
|
||||
It is perfectly valid to have a color that is out of range or even `NaN`, but when drawing scribbles:
|
||||
|
||||
- any number less than `0` is clamped to `0`.
|
||||
- any number greater than `1` is clamped to `1`.
|
||||
- `∞` is clamped back to `1`.
|
||||
- `-∞` is clamped back to `0`.
|
||||
- any scribble with a `NaN` color is ignored.
|
||||
|
||||
Note that just like vectors, arithmetic operations on colors are currently not defined.
|
||||
|
||||
Before scribbles are drawn to the wall, colors are converted to 8-bit integers for more efficient rasterization and storage.
|
||||
This means some loss of precision will happen, which may cause issues with brushes like this one:
|
||||
|
||||
|
@ -625,7 +658,7 @@ stroke
|
|||
|
||||
Creates a stroke scribble, which outlines the provided shape with a stroke of the given thickness and color.
|
||||
|
||||
Point shapes are drawn as squares, and `line` shapes have square caps at the line's endpoints.
|
||||
Point shapes are drawn as circles, and `line` shapes have round caps at the line's endpoints.
|
||||
|
||||
```haku
|
||||
fill
|
||||
|
|
Loading…
Reference in a new issue