# OPENRNDR & p5.js

**URL:** <https://openrndr.discourse.group/t/openrndr-p5-js/775>\
**Category:** Tutorials\
**Created:** [June 1, 2026, 2:23pm UTC](https://openrndr.discourse.group/t/openrndr-p5-js/775 "2026-06-01T14:23:48Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![abe](https://yyz2.discourse-cdn.com/free1/user_avatar/openrndr.discourse.group/abe/32/700_2.png) [@abe](https://openrndr.discourse.group/u/abe)\
**Post date:** [June 1, 2026, 2:23pm UTC](https://openrndr.discourse.group/t/openrndr-p5-js/775/1 "2026-06-01T14:23:48Z")

</div>

In the past I wrote some posts here showing OPENRNDR and Processing programs side by side. I suddenly felt like doing the same with p5.js 🙂

# 1. Constrain p5.js example

I’ll start with this example that introduces the `constrain` p5.js function (open it in a new window to compare it side by side):

> **[Constrain](https://p5js.org/examples/calculating-values-constrain/)**
>
> Keep a number within a range.

Here’s a similar program it in OPENRNDR:

> **Imports**
>
> ```kotlin
> import org.openrndr.application
> import org.openrndr.color.ColorRGBa
> import org.openrndr.shape.clamp
> 
> ```

```kotlin
fun main() = application {
    configure {
        width = 720
        height = 400
    }
    program {
        val radius = 24.0
        val edge = 100.0
        val visibleRect = drawer.bounds.offsetEdges(-edge)
        val allowedRect = visibleRect.offsetEdges(-radius)

        extend {
            drawer.clear(ColorRGBa.WHITE.shade(0.9))
            drawer.stroke = null

            drawer.fill = ColorRGBa.PINK
            drawer.rectangle(visibleRect)

            drawer.fill = ColorRGBa.WHITE
            drawer.circle(mouse.position.clamp(allowedRect), radius)
        }
    }
}

```

## What’s different in OPENRNDR?

### `ellipseMode` and `rectMode`

There’s no `ellipseMode`. Circles are created by specifying their center and radius, or by using `Circle.fromPoints()` and specify 2 or 3 points.

There’s no `rectMode`. Rectangles are created by specifying their top-left corner, a width, and a height, or by using `Rectangle.fromCenter()`. There’s also `offsetEdges`, which creates a Rectangle based on another Rectangle.

In the program above I create two rectangles by calling `offsetEdges`. First, I create the `visibleRect` by offsetting the main canvas rectangle inwards by `edge` pixels. Then I create a second one, `allowedRect`, by ofsetting the `visibleRect` we just created inwards by `radius` pixels. This way I don’t need to think about positioning the rectangle, only by how much to enlarge or shrink existing rectangles.

I find it better to not have `ellipseMode` and `rectMode`, so the way you are creating a rectangle is explicit at the place of creation. When one puts the creation mode and the actual creation in separate places, it can get confusing. If you set these modes multiple times you need to follow your program to figure what mode is active at different parts of your program.

### `constrain`, `clamp` or `coerce`

Kotlin provides three functions similar to `constrain`, which are `coerceIn(a, b)`, `coerceAtLeast(a)` and `coerceAtMost(b)`. They can be use like this: `mouse.position.x.coerceIn(0.0, 100.0)`. This will take the value of the current horizontal mouse position and clamp it to be in the range 0.0 .. 100.0.

But OPENRNDR provides a clamp function that takes a `Vector2` (the mouse position) and a `Rectangle` (the `allowedRect`), and returns a new `Vector2` making sure it’s within the bounds of the rectangle. This way I don’t need to constrain `circleX` and `circleY` separately like in p5.js.

### Different focus

These small differences may reveal a different focus in each tool. One could argue that p5.js and Processing are lower-level, which is great for learning and figuring out the required logic, sometimes with the cost of being more verbose. This may become apparent when making a small change to the program.

Let’s say we want to change the color of the circle when the mouse pointer is inside the visible rectangle. In OPENRNDR, because we are working with higher abstractions like `Vector2`s and `Rectangle`s, we can easily change the circle’s color like this:

```kotlin
// OPENRNDR
drawer.fill = if(mouse.position in visibleRect) ColorRGBa.GREEN else ColorRGBa.WHITE

```

The equivalent in p5.js might look like this:

```js
// p5.js
if(mouseX > edge && mouseX < width - edge && 
   mouseY > edge && mouseY < height - edge) {
  fill(0, 255, 0);
} else {
  fill(255);
}

```

My impression is that maybe with the first approach we could write more complex programs before we started to get confused with the amount of code. With the JavaScript approach we learn something important about if-statements, math, comparisons and the `&&` operator.

I think it’s valuable to learn and write lower level code before switching to tools that hide the complexity. Learning a bit of C can teach us about how computer memory must be managed. I’m not sure whether I could appreciate Kotlin and OPENRNDR as much without having used other tools before 🙂

---

<div class="post-metadata">

**Author:** ![abe](https://yyz2.discourse-cdn.com/free1/user_avatar/openrndr.discourse.group/abe/32/700_2.png) [@abe](https://openrndr.discourse.group/u/abe)\
**Post date:** [June 9, 2026, 3:49pm UTC](https://openrndr.discourse.group/t/openrndr-p5-js/775/2 "2026-06-09T15:49:28Z")

</div>

# 2. Flocking p5.js example

Today I ported a well known example from the [Nature of Code](https://natureofcode.com/book/chapter-6-autonomous-agents/) book by Daniel Shiffman.

> **[Flocking](https://p5js.org/examples/classes-and-objects-flocking/)**
>
> Simulate bird flocking behavior.

You can open it in another window to see the p5.js and the OPENRNDR programs side by side.

> **Imports**
>
> ```kotlin
> // added automatically by the IDE
> import org.openrndr.application
> import org.openrndr.color.ColorHSVa
> import org.openrndr.color.ColorRGBa
> import org.openrndr.draw.isolated
> import org.openrndr.extra.noise.uniform
> import org.openrndr.math.Polar
> import org.openrndr.math.Vector2
> import org.openrndr.math.mod
> import org.openrndr.shape.Triangle
> 
> ```

> **Helper functions**
>
> ```kotlin
> // Limit a Vector2 to a maximum length
> fun Vector2.limit(maxLen: Double) = this / (length / maxLen).coerceAtLeast(1.0)
> 
> // Get the heading of a Vector2
> val Vector2.heading get() = Polar.fromVector(this).theta
> 
> ```

```kotlin
/**
 * A group of bird-like objects, represented by triangles,
 * moving across the canvas, modeling flocking behavior.
 */
fun main() = application {
    configure {
        width = 640
        height = 360
    }
    program {
        val size = 3.0
        val triangle = Triangle(
            Vector2(0.0, -size * 2),
            Vector2(-size, size * 2),
            Vector2(size, size * 2)
        ).contour

        class Boid(var position: Vector2) {
            var acceleration = Vector2.ZERO
            var velocity = Vector2.uniform(-1.0, 1.0)

            val maxSpeed = 3.0
            val maxForce = 0.05
            val color = ColorHSVa(Double.uniform(360.0), 1.0, 1.0).toRGBa()

            fun update(boids: List<Boid>) {
                flock(boids)
                update()
                wrapAroundBorders()
                render()
            }

            fun applyForce(force: Vector2) {
                // We could add mass here if we want: A = F / M
                acceleration += force
            }

            // We accumulate a new acceleration each time based on three rules
            fun flock(boids: List<Boid>) {
                var separation = separate(boids)
                var alignment = align(boids)
                var cohesion = cohesion(boids)

                // Arbitrarily weight these forces
                separation *= 1.5
                alignment *= 1.0
                cohesion *= 1.0

                // Add the force vectors to acceleration
                applyForce(separation)
                applyForce(alignment)
                applyForce(cohesion)
            }

            // Method to update location
            fun update() {
                // Update velocity
                velocity += acceleration

                // Limit speed
                position += velocity.limit(maxSpeed)

                // Reset acceleration to 0 each cycle
                acceleration *= 0.0
            }

            // A method that calculates and applies a steering force towards a target
            // STEER = DESIRED MINUS VELOCITY
            fun seek(target: Vector2): Vector2 {
                // A vector pointing from the location to the target
                // Normalized and scaled to maximum speed
                val desired = (target - position).normalized * maxSpeed

                // Steering = Desired minus Velocity
                // Limit to maximum steering force
                return (desired - velocity).limit(maxForce)
            }

            fun render() {
                drawer.isolated {
                    fill = color
                    stroke = ColorRGBa.WHITE
                    translate(position)
                    rotate(velocity.heading + 90.0)
                    contour(triangle)
                }
            }

            fun wrapAroundBorders() {
                val canvasSize = drawer.bounds.dimensions
                position = (position + canvasSize).mod(canvasSize)
            }

            // Separation
            // Method checks for nearby boids and steers away
            fun separate(boids: List<Boid>): Vector2 {
                val desiredSeparation = 25.0
                var steer = Vector2.ZERO
                var count = 0

                // For every boid in the system, check if it's too close
                boids.forEach { boid ->
                    val distanceToNeighbor = position.distanceTo(boid.position)

                    // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
                    if (distanceToNeighbor > 0 && distanceToNeighbor < desiredSeparation) {
                        // Calculate vector pointing away from neighbor and scale by distance
                        steer += (position - boid.position).normalized / distanceToNeighbor

                        // Keep track of how many
                        count++
                    }
                }

                // Average -- divide by how many
                if (count > 0) {
                    steer /= count.toDouble()
                }

                // As long as the vector is greater than 0
                if (steer.length > 0.0) {
                    // Implement Reynolds: Steering = Desired - Velocity
                    steer = (steer.normalized * maxSpeed - velocity).limit(maxForce)
                }
                return steer
            }

            // Alignment
            // For every nearby boid in the system, calculate the average velocity
            fun align(boids: List<Boid>): Vector2 {
                val neighborDistance = 50
                var sum = Vector2.ZERO
                var count = 0
                boids.forEach { other ->
                    val d = position.distanceTo(other.position)
                    if (d > 0 && d < neighborDistance) {
                        sum += other.velocity
                        count++
                    }
                }
                return if (count > 0) {
                    ((sum / count.toDouble()).normalized * maxSpeed - velocity).limit(maxForce)
                } else {
                    Vector2.ZERO
                }
            }

            // Cohesion
            // For the average location (i.e., center) of all nearby boids, calculate steering vector towards that location
            fun cohesion(boids: List<Boid>): Vector2 {
                val neighborDistance = 50
                var sum = Vector2.ZERO // Start with empty vector to accumulate all locations
                var count = 0
                boids.forEach { other ->
                    val d = position.distanceTo(other.position)
                    if (d > 0 && d < neighborDistance) {
                        sum += other.position // Add location
                        count++
                    }
                }
                return if (count > 0) {
                    seek(sum / count.toDouble()) // Steer towards the location
                } else Vector2.ZERO
            }
        } // class Boid

        // Flock class to manage the array of all the boids
        class Flock {
            // Initialize the array of boids
            val boids = mutableListOf<Boid>()

            fun update() {
                // Pass the entire list of boids to each boid individually
                boids.forEach { it.update(boids) }
            }

            fun addBoid(b: Boid) {
                boids.add(b)
            }
        }

        println("Drag the mouse to generate new boids.")
        val flock = Flock()

        // Add an initial set of boids into the system
        repeat(100) {
            flock.addBoid(Boid(drawer.bounds.center))
        }

        extend {
            flock.update()
        }

        // On mouse drag, add a new boid to the flock
        mouse.dragged.listen {
            flock.addBoid(Boid(it.position))
        }
    }
}

```

## What’s different in the OPENRNDR version?

### `drawer`

To draw things OPENRNDR uses the `drawer` object. I “cheated” a tiny bit in this program, because I placed the `Boid` and `Flock` classes inside `program { ... }`. I did this to have access to `drawer` in a simple way. If we had those classes at the root level, maybe in separate files, we would need to pass an instance to `drawer` around. For instance, I would do `flock.update(drawer)`, that method should pass it to the Boid class on its `update` method, and then do `render(drawer)`.

On the topic of drawing, notice how I wrap the drawing instructions in `isolated { }`. That way one doesn’t need to do something like `push` and `pop`, and it becomes impossible to forget to match the number of `push` and `pop` calls.

### Drawing triangles

The p5.js sends vertices one by one to draw triangles. In my version, I created a triangular `ShapeContour` when the program starts, and then reuse that same triangle every time. That might be similar to creating and reusing a `PShape` in Processing. I don’t know whether p5.js has an equivalent.

### Operator overloading

Since Kotlin provides operator overloading, we can use `+`, `-`, `/` and `*` with vectors, which allows for a shorter syntax than using `p5.Vector.add()`, `p5.Vector.sub()`, etc. On the topic of vector operators, you can see I added two missing helper one-liner functions: `Vector2.limit` and `Vector2.heading`. This is a nice thing that Kotlin allows: easily adding methods to classes you didn’t write yourself.

### Types and classes

Notice how class methods need to specify the types of their arguments and their return types. Also, notice how the Boid class doesn’t need an explicit constructor. The received `position` constructor argument becomes a parameter thanks to the `var` keyword, and `acceleration`, `velocity`, etc. do not need the `this` keyword.

### Reading and writing code

When I compare

```kotlin
// Kotlin
fun cohesion(boids: List<Boid>): Vector2 {

```

and

```auto
// JavaScript
cohesion(boids) {

```

I see that the first one is obviously more verbose, but also informative. I can see it’s a function, what the function takes as an argument, and what it returns. In this case JavaScript is much simpler to write, but I find it harder to read. To see what the `boids` argument is I need to read the function and notice that it has a `.length` property. I also see `boids[i].position`, which tells me it must be a Boid instance. To figure out what the function returns I would need to read all `return` statements and see what they are. One contains `createVector(0, 0)`. The other `this.seek(sum)`, which forces me to read the `seek` function and see what that returns. I’ve written a lot of JavaScript in the past but I do not remember thinking that it was easier to write than read.

### Wrap around

I greatly simplified the `borders()` method which I renamed `wrapAroundBorders()`. Instead of comparing the `x` and `y` coordinates with all four edges, I simply added the size of the screen to deal with negative values and used a modulo operation between two `Vector2` instances. Another case of hiding complexity that I mentioned in the first post above.

### `run` became `update`

`run` is a reserved keyword in Kotlin. The language actually allows you to use any names for variables and functions by doing something like this `fun `run`() {}` or ever `fun `fun`() {}` or `fun `42`() {}`. I’ll skip such adventures in this post 🙂

### `colorMode()`

The program constructs random colors using the `ColorHSVa` mode. As I mentioned in the first post above, there is no color mode: any time you create a color it’s obvious which color mode is used. The `.toRGBa()` method is used because drawing operations need `ColorRGBa` types. The JavaScript version uses random hues of up to 256. Color types expecting a hue argument in OPENRNDR work with degrees, therefore I used 360.0.

### `for` loops

The original program uses both `for (let boid of boids)` and `for (let i = 0; i < boids.length; i++)`. I decided to use `forEach`, even if there are other types of loops in Kotlin.

### `background(0)`

I didn’t call `drawer.background(ColorRGBa.BLACK)` because that’s the default, and to show that OPENRNDR clears the background automatically. To disable that behavior one can use the [`orx-no-clear`](https://github.com/openrndr/orx/tree/master/orx-no-clear) extension to make it behave like Processing or p5.js.

### Alternative approach in the `cohesion()` function (eg. `filter`, `map`)

Instead of manually finding the average of a list of `Vector2` using `sum` and `count`, we can use the built-in `average` method.

In the following example, I `filter`out the boids that are not the current boid being processed (`d > 0`) and are near enough (`d < neighborDistance`), and get their positions (I `map` boids to `Vector2` instances). If the resulting list with positions is not empty, I call the `average()` method on it.

Now that I have the average position, I calculate a `seek` force so the current boid tries to move towards that position. For lonely boids I return a cohesion force of `ZERO` (no effect).

This approach makes the code somewhat shorter (from ~15 lines down to 5), and could be also used for `separate()` and `align()`.

```kotlin
            fun cohesion(boids: List<Boid>): Vector2 {
                val neighborDistance = 50
                val nearBoidPositions = boids.filter {
                    val d = position.distanceTo(it.position)
                    d > 0 && d < neighborDistance
                }.map { it.position }
                return if (nearBoidPositions.isNotEmpty()) seek(nearBoidPositions.average()) else Vector2.ZERO
            }

```

I mention this approach to present things you can do with Kotlin, not chasing performance or other types of optimizations. If I was after better performance, I would calculate all forces in one pass, instead of calculating separation, alignment, and cohesion one by one. Or even do everything in the GPU, in parallel. But that would be a GLSL tutorial instead 😅

Feel free to comment, ask or suggest 🙂
