OPENRNDR & Processing - Closest object to mouse

Lets share simple programs written both in OPENRNDR + Kotlin and in Processing + Java.

Before the first example I want to note that I can count at least 4 different approaches to developing with these two platforms. I did go through these steps myself one by one:

  1. Programming in the Processing IDE: launches very fast and lets you iterate with your experiments quickly but limited to older versions of Java.
  2. Using Processing in a more advanced IDE: allows you to use new versions of Java with simplified and more expressive syntax (for example streams, var, lambdas, etc.). Such IDEs make life easier when programs grow to several code files.
  3. Programming Processing using Kotlin: a language that still compiles to Java bytecode, but less verbose. Good option to learn Kotlin while staying in the familiar territory of the Processing API.
  4. Programming using Kotlin and OPENRNDR. For those who want to explore new worlds :slight_smile:

Now, let’s get to example 1.

1 Like

Find closest object to mouse position in a collection

A program that sets random positions for 30 circles, and highlights in red the one closest to the mouse cursor.

closest

Processing / Java

ArrayList<PVector> points = new ArrayList<PVector>();
void setup() {
  size(640, 480); 
  for(int i=0; i<30; i++) {
    points.add(new PVector(random(width), random(height)));
  }
}
void draw() {
  background(255);
  float mind = Float.MAX_VALUE;
  PVector found = null;
  for(PVector p : points) {
    float d = p.dist(new PVector(mouseX, mouseY));
    if(d < mind) {
      found = p;
      mind = d;
    }
  }  
  for(PVector p : points) {
    fill(p == found ? #FF0000 : 255);
    ellipse(p.x, p.y, 20, 20);
  }
}

OPENRNDR / Kotlin

fun main() = application {
    program {
        val points = List(30) { drawer.bounds.center + drawer.bounds.center * Random.vector2() }
        extend {
            drawer.clear(ColorRGBa.WHITE)
            val closest = points.minByOrNull { (mouse.position - it).squaredLength }
            points.forEach {
                drawer.fill = if (it == closest) ColorRGBa.RED else ColorRGBa.WHITE
                drawer.circle(it, 20.0)
            }
        }
    }
}

I did not include three imports in the Kotlin program as they are handled automatically by the IDE.
My favorite part here is finding the closest Vector2 to the mouse position which goes from 9 lines of code to just 1. One thing that would make that line more readable though is if Vector2 implemented a method called .squaredDistance so you could write it.squaredDistance(mouse.position)

I also struggled a bit in figuring out the best way to generate the random points because I couldn’t find a simple method to generate random values between (0.0, 0.0) and (width, height).

This is another way of doing it

    val points = List(30) {
        Vector2.uniform(Vector2.ZERO, drawer.bounds.position(1.0, 1.0))
    }

which gives you 30 points, each one sampled randomly using a uniform distribution between the point (0.0, 0.0) and the point (640.0, 480.0), which happens to be the default size of the window. drawer.bounds is the Rectangle that defines the visible area and .position(1.0, 1.0) refers to the bottom right corner of that area.

:point_down: Share your questions and comments below | :mag_right: Find other OPENRNDR & Processing posts

3 Likes

After writing the post I realized that

drawer.bounds.dimensions

is equivalent to

drawer.bounds.position(1.0, 1.0)

Still, in some cases you want the flexibility of the second approach to do something like this:

2020-06-15-162310_640x480_scrot

fun main() = application {
    program {
        extend {
            backgroundColor = ColorRGBa.WHITE
            drawer.fill = ColorRGBa.PINK
            drawer.circle(drawer.bounds.position(0.333, 0.333), 60.0)
            drawer.circle(drawer.bounds.position(0.666, 0.333), 60.0)
            drawer.circle(drawer.bounds.position(0.666, 0.666), 60.0)
            drawer.circle(drawer.bounds.position(0.333, 0.666), 60.0)
        }
    }
}

Or using .run to avoid some repeating yourself:

fun main() = application {
    program {
        extend {
            backgroundColor = ColorRGBa.WHITE
            drawer.run {
                fill = ColorRGBa.PINK
                circle(bounds.position(0.333, 0.333), 60.0)
                circle(bounds.position(0.666, 0.333), 60.0)
                circle(bounds.position(0.666, 0.666), 60.0)
                circle(bounds.position(0.333, 0.666), 60.0)
            }
        }
    }
}

Almost 3 years later, a few things I would write differently now:

imports
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.scatter
import org.openrndr.extra.noise.uniform
fun main() = application {
    program {
        val points = List(30) { drawer.bounds.uniform() }
        //val points = drawer.bounds.scatter(40.0)
        extend {
            drawer.clear(ColorRGBa.WHITE)
            val closest = points.minBy { mouse.position.squaredDistanceTo(it) }
            points.forEach {
                drawer.fill = if (it == closest) ColorRGBa.RED else ColorRGBa.WHITE
                drawer.circle(it, 20.0)
            }
        }
    }
}
  • Rectangle.uniform() returns a random (with a uniform distribution) point from a Rectangle. Simpler than the original solution.
  • Vector2 now has .distanceTo() and .squaredDistanceto() methods which I find clearer.
  • As an alternative to Rectangle.uniform() we can use Rectangle.scatter(radius) which returns random points making sure a minimum distance is kept between them. See how no overlap happens between the points:

image

To set a maximum number of points with .scatter() we could use .take(n):

val points = drawer.bounds.scatter(40.0).take(10)

Exercise: try notice the difference with and without .shuffled() in the following code:

val points = drawer.bounds.scatter(20.0).shuffled().take(20)