Skip to content

Commit

Permalink
Chapter 1 Examples 9, 10, and Exercises 6, 7, and 8
Browse files Browse the repository at this point in the history
  • Loading branch information
Ramblurr committed Jun 5, 2024
1 parent 72a029c commit 233062a
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 22 deletions.
10 changes: 9 additions & 1 deletion dev/user.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
[nextjournal.clerk.viewer :as cv]
[shadow.cljs.devtools.server :as shadow.server]
[shadow.cljs.devtools.server.npm-deps :as npm-deps]
[shadow.cljs.devtools.api :as shadow.api]))
[shadow.cljs.devtools.api :as shadow.api]
[thi.ng.math.core :as tm]))

(def build-target
{:git/url "https://github.com/ramblurr/nature-of-code"})
Expand Down Expand Up @@ -137,6 +138,13 @@

;;
)
(tm/limit (v/vec2 1 1) 0.5)
(mod (dec 0) 10)

(let [v (v/vec2 1 5)
u (tm/* v 2)
w (tm/- v u)]
(tm/div w 3))

;;
)
112 changes: 93 additions & 19 deletions notebooks/chapter_1.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
(ns chapter-1
{:nextjournal.clerk/visibility {:code :hide} :nextjournal.clerk/toc true}
(:require
[thi.ng.math.core :as tm]
[thi.ng.geom.vector :as v]
[clojure.java.io :as io]
[nextjournal.clerk :as clerk]
[noc.quil-clerk :refer [show-sketch]]))
Expand Down Expand Up @@ -91,25 +93,25 @@ In the following table the namespaces referenced are:
* [`[thi.ng.math.core :as tm]`](https://github.com/thi-ng/math/blob/0.3.2/src/core.org)
* [`[thi.ng.geom.core :as g]`](https://cljdoc.org/d/thi.ng/geom/1.0.1/api/thi.ng.geom.core)

| p5.js Method | `thi.ng` Function | Task |
|------------------|--------------------------|----------------------------------------------------------------------------------|
| `add()` | `(tm/+ a b)` | Adds vector `a` to vector `b` |
| `sub()` | `(tm/- a b` | Subtracts vector `a` from vector `b` |
| `mult()` | `(tm/* a b)` | Scales this vector with multiplication |
| `div()` | `(tm/div a b)` | Scales this vector with division |
| `mag()` | `(tm/mag a)` | Returns the magnitude of the vector `a` |
| `setMag()` | `-` | Sets the magnitude of this vector- Not available AFAICT |
| `normalize()` | `(tm/normalize a)` | Normalizes this vector to a unit length of 1 |
| `limit()` | `(tm/limit a)` | Limits the magnitude of this vector |
| `heading()` | `(g/heading-xy a)` | Returns the 2D heading of this vector expressed as an angle |
| `rotate()` | `(g/rotate a angle)` | Rotates this 2D vector by `angle` (in radians) |
| `lerp()` | `(tm/mix a b amt)` | Linear interpolates vector `a` towards vector `b` by `amt` |
| `dist()` | `(g/dist a b)` | Returns the Euclidean distance between vector `a` and `b` (considered as points) |
| `angleBetween()` | `(g/angle-between a b )` | Finds the angle between vector `a` and vector `b` |
| `dot()` | `(tm/dot a b)` | Returns $$\vec{a} \cdot \vec{b} $$ (the dot product) |
| `cross()` | `(tm/cross a b)` | Returns $$\vec{a} \times \vec{b} $$ (the cross product) |
| `random2D()` | `-` | Returns a random 2D vector - Not available AFAICT |
| `random3D()` | `-` | Returns a random 3D vector - Not available AFAICT |
| p5.js Method | `thi.ng` Function | Task |
|------------------|-----------------------------|----------------------------------------------------------------------------------|
| `add()` | `(tm/+ a b)` | Adds vector `a` to vector `b` |
| `sub()` | `(tm/- a b` | Subtracts vector `a` from vector `b` |
| `mult()` | `(tm/* a b)` | Scales this vector with multiplication |
| `div()` | `(tm/div a b)` | Scales this vector with division |
| `mag()` | `(tm/mag a)` | Returns the magnitude of the vector `a` |
| `normalize()` | `(tm/normalize a)` | Normalizes this vector to a unit length of 1 |
| `setMag()` | `(tm/* (tm/normalize a) x)` | Sets the magnitude of this vector `a` to `x` |
| `limit()` | `(tm/limit a)` | Limits the magnitude of this vector |
| `heading()` | `(g/heading-xy a)` | Returns the 2D heading of this vector expressed as an angle |
| `rotate()` | `(g/rotate a angle)` | Rotates this 2D vector by `angle` (in radians) |
| `lerp()` | `(tm/mix a b amt)` | Linear interpolates vector `a` towards vector `b` by `amt` |
| `dist()` | `(g/dist a b)` | Returns the Euclidean distance between vector `a` and `b` (considered as points) |
| `angleBetween()` | `(g/angle-between a b )` | Finds the angle between vector `a` and vector `b` |
| `dot()` | `(tm/dot a b)` | Returns $$\vec{a} \cdot \vec{b} $$ (the dot product) |
| `cross()` | `(tm/cross a b)` | Returns $$\vec{a} \times \vec{b} $$ (the cross product) |
| `random2D()` | `-` | Returns a random 2D vector - Not available AFAICT |
| `random3D()` | `-` | Returns a random 3D vector - Not available AFAICT |

Some extra notes:

Expand Down Expand Up @@ -207,3 +209,75 @@ The sprite is from [senderin](https://senderin.itch.io/car). The entire sheet is
```
(clerk/image (io/resource "assets/img/car-side-right.png"))
```


## [Example 1.9: Motion 101 (Velocity and Random Acceleration)](https://natureofcode.com/vectors/#example-19-motion-101-velocity-and-random-acceleration)


```clojure
^{::clerk/no-cache true ::clerk/viewer clerk/code}
(slurp "src/noc/chapter_1_9.cljs")
(show-sketch :c1.9)
```

## [Exercise: 1.6: A Perlin Noise Accelerated Walker](https://natureofcode.com/vectors/#exercise-16)


```clojure
^{::clerk/no-cache true ::clerk/viewer clerk/code}
(slurp "src/noc/chapter_1_6e.cljs")
(show-sketch :c1.6e)
```

This one is very nice, the contrast between this exercise and Example 1.9 is
stark. This one feels much more natural.

## [Exercise: 1.7: Pseudocode Translation](https://natureofcode.com/vectors/#exercise-17)

Of course here in Clojure we don't have any of the confusion that this section of the chapter is addressing (of course, being Clojure we have our own other sorts of confusion).

Our function calls do not mutate any of the arguments, rather, they return a new `vec2` as the result.

```
^{:nextjournal.clerk/visibility {:code :show} :nextjournal.clerk/auto-expand-results? true}
(let [v (v/vec2 1 5)
u (tm/* v 2)
w (tm/- v u)]
{:result (tm/div w 3)
:v v
:u u
:w w})
```

We can see that none of the arguments were changed.

## [Example 1.10: Accelerating Toward the Mouse](https://natureofcode.com/vectors/#example-110-accelerating-toward-the-mouse)


```clojure
^{::clerk/no-cache true ::clerk/viewer clerk/code}
(slurp "src/noc/chapter_1_10.cljs")
(show-sketch :c1.10)
```

Hover your mouse over the sketch!


## [Exercise 1.8e: Variable Magnitude of Acceleration](https://natureofcode.com/vectors/#exercise-18)


```clojure
^{::clerk/no-cache true ::clerk/viewer clerk/code}
(slurp "src/noc/chapter_1_8e.cljs")
(show-sketch :c1.8e)
```

Once again, hover your mouse over the sketch.

I used `thi.ng.geom.core/dist` to calculate the euclidean distance and then
calculate the acceleration as `2 / distance`. Why 2? Well, first I tried `1 /
distance` but the effect was rather slow, increasing the numerator speeds it up.

One problem with the sketch was that it would follow the mouse off-screen. So
I've added a check that stops the mover when the mouse is not contained in the
canvas.
38 changes: 38 additions & 0 deletions src/noc/chapter_1_10.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
(ns noc.chapter-1-10
(:require
[thi.ng.math.core :as tm]
[thi.ng.geom.vector :as v]
[quil.core :as q]))

(def size [640 240])

(defn init-state [{:keys [width height] :as state}]
{:mover {:position (v/vec2 (quot width 2) (quot height 2))
:velocity (v/vec2 0 0)
:top-speed 5}})

(defn setup! [{:keys [width height]}]
(q/background 255))

(defn tick-mover [mouse-x mouse-y {:keys [position top-speed velocity] :as mover}]
(let [mouse (v/vec2 mouse-x mouse-y)
direction (tm/- mouse position)
acceleration (tm/* (tm/normalize direction) 0.2)
new-vel (tm/limit (tm/+ velocity acceleration) top-speed)]
(-> mover
(assoc :velocity new-vel)
(update :position #(tm/+ % new-vel)))))

(defn tick [{:keys [mouse-x mouse-y] :as state}]
(-> state
(update :mover #(tick-mover mouse-x mouse-y %))))

(defn draw-mover [{:keys [position]}]
(q/stroke 0)
(q/stroke-weight 2)
(q/fill 127)
(q/ellipse (v/x position) (v/y position) 48 48))

(defn draw! [{:keys [mover]}]
(q/background 255)
(draw-mover mover))
53 changes: 53 additions & 0 deletions src/noc/chapter_1_6e.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
(ns noc.chapter-1-6e
(:require
[thi.ng.math.core :as tm]
[thi.ng.geom.vector :as v]
[quil.core :as q]))

(def size [640 240])

(defn init-state [{:keys [width height] :as state}]
{:mover {:position (v/vec2 (quot width 2) (quot height 2))
:velocity (v/vec2 0 0)
:top-speed 5
:tx 0
:ty 10000}})

(defn setup! [_]
(q/background 255))

(defn random-vec2 [tx ty]
(v/vec2
(q/map-range (q/noise tx) 0 1 -1 1)
(q/map-range (q/noise ty) 0 1 -1 1)))

(defn enforce-bound [dimension component position]
(let [value (component position)]
(cond (>= value dimension) (assoc position component 0)
(<= value 0) (assoc position component dimension)
:else position)))

(defn tick-mover [width height {:keys [tx ty top-speed velocity] :as mover}]
(let [acceleration (tm/* (random-vec2 tx ty) 2)
new-vel (tm/limit (tm/+ velocity acceleration) top-speed)]
(-> mover
(assoc :velocity new-vel)
(update :position #(tm/+ % velocity))
(update :position #(enforce-bound width :x %))
(update :position #(enforce-bound height :y %))
(update :tx #(+ % 0.01))
(update :ty #(+ % 0.01)))))

(defn tick [{:keys [width height] :as state}]
(-> state
(update :mover #(tick-mover width height %))))

(defn draw-mover [{:keys [position]}]
(q/stroke 0)
(q/stroke-weight 2)
(q/fill 127)
(q/ellipse (v/x position) (v/y position) 48 48))

(defn draw! [{:keys [mover]}]
(q/background 255)
(draw-mover mover))
43 changes: 43 additions & 0 deletions src/noc/chapter_1_8e.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
(ns noc.chapter-1-8e
(:require
[thi.ng.geom.rect :as r]
[thi.ng.math.core :as tm]
[thi.ng.geom.core :as g]
[thi.ng.geom.vector :as v]
[quil.core :as q]))

(def size [640 240])

(defn init-state [{:keys [width height] :as state}]
{:mover {:position (v/vec2 (quot width 2) (quot height 2))
:velocity (v/vec2 0 0)
:top-speed 5}})

(defn setup! [{:keys [width height]}]
(q/background 255))

(defn tick-mover [canvas-rect mouse {:keys [position top-speed velocity] :as mover}]
(if (g/contains-point? canvas-rect mouse)
(let [direction (tm/- mouse position)
dist (g/dist position mouse)
acceleration (tm/* (tm/normalize direction) (/ 2 dist))
new-vel (tm/limit (tm/+ velocity acceleration) top-speed)]
(-> mover
(assoc :velocity new-vel)
(update :position #(tm/+ % new-vel))))
(-> mover
(assoc :velocity (v/vec2 0 0)))))

(defn tick [{:keys [width height mouse-x mouse-y] :as state}]
(-> state
(update :mover #(tick-mover (r/rect 0 0 width height) (v/vec2 mouse-x mouse-y) %))))

(defn draw-mover [{:keys [position]}]
(q/stroke 0)
(q/stroke-weight 2)
(q/fill 127)
(q/ellipse (v/x position) (v/y position) 48 48))

(defn draw! [{:keys [mover]}]
(q/background 255)
(draw-mover mover))
48 changes: 48 additions & 0 deletions src/noc/chapter_1_9.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
(ns noc.chapter-1-9
(:require
[thi.ng.math.core :as tm]
[thi.ng.geom.vector :as v]
[quil.core :as q]))

(def size [640 240])

(defn init-state [{:keys [width height] :as state}]
{:mover {:position (v/vec2 (quot width 2) (quot height 2))
:velocity (v/vec2 0 0)
:top-speed 5}})

(defn setup! [{:keys [width height]}]
(q/background 255))

(defn random-vec2 []
(let [[x y] (q/random-2d)]
(v/vec2 x y)))

(defn enforce-bound [dimension component position]
(let [value (component position)]
(cond (>= value dimension) (assoc position component 0)
(<= value 0) (assoc position component dimension)
:else position)))

(defn tick-mover [width height {:keys [top-speed velocity] :as mover}]
(let [acceleration (tm/* (random-vec2) 2)
new-vel (tm/limit (tm/+ velocity acceleration) top-speed)]
(-> mover
(assoc :velocity new-vel)
(update :position #(tm/+ % velocity))
(update :position #(enforce-bound width :x %))
(update :position #(enforce-bound height :y %)))))

(defn tick [{:keys [width height] :as state}]
(-> state
(update :mover #(tick-mover width height %))))

(defn draw-mover [{:keys [position]}]
(q/stroke 0)
(q/stroke-weight 2)
(q/fill 127)
(q/ellipse (v/x position) (v/y position) 48 48))

(defn draw! [{:keys [mover]}]
(q/background 255)
(draw-mover mover))
12 changes: 10 additions & 2 deletions src/noc/sketch.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@
[noc.chapter-1-6 :as c1.6]
[noc.chapter-1-7 :as c1.7]
[noc.chapter-1-8 :as c1.8]
[noc.chapter-1-5e :as c1.5e]))
[noc.chapter-1-5e :as c1.5e]
[noc.chapter-1-9 :as c1.9]
[noc.chapter-1-6e :as c1.6e]
[noc.chapter-1-10 :as c1.10]
[noc.chapter-1-8e :as c1.8e]))

(def sketches
{:walker (sketch-> c0.1)
Expand All @@ -55,7 +59,11 @@
:c1.6 (sketch-> c1.6)
:c1.7 (sketch-> c1.7)
:c1.8 (sketch-> c1.8)
:c1.5e (sketch-> c1.5e)})
:c1.5e (sketch-> c1.5e)
:c1.9 (sketch-> c1.9)
:c1.6e (sketch-> c1.6e)
:c1.10 (sketch-> c1.10)
:c1.8e (sketch-> c1.8e)})

(defn load-sketch [s]
(when-let [sk (get sketches s)]
Expand Down

0 comments on commit 233062a

Please sign in to comment.