# Closest point on SVG path

Instead of looking for the closest point to the mouse from all the points of the svg, how do I limit to finding just the closest point on a specific path?

For example If I keyPressed 3, it would just look for the closest point to the mouse from path 3?

``````// https://discourse.processing.org/t/calculate-distance-from-point-to-closest-point-of-shape/7201/7

import geomerative.*;

RShape myshape;
RPoint [] points;
boolean diagpoints = true;
RPoint mouse = new RPoint(0,0);

float mind;
int nearest = -1;

int calc_min_mouse_dist() {
int minpos = -1;
mind = 10000;

for (int i =0; i<points.length; i++) {
float d = points[i].dist(mouse);
if ( d < mind ) {
mind = d;
minpos = i;
}
}
return minpos;
}

void setup () {
size (500,500);
RG.init(this);
points = myshape.getPoints();
println(points.length);
//printArray(points);
}

void draw() {
background(100,200,0);

if(points != null ){
if ( diagpoints )    for(int i=0; i<points.length; i++)   ellipse(points[i].x, points[i].y,2,2);
draw_mouseline();
}
}

void mouseDragged() {
mouse= new RPoint(float(mouseX), float(mouseY));
nearest = calc_min_mouse_dist();
}

void draw_mouseline() {
fill(200, 0, 0);
if (nearest > -1 ){
line(points[nearest].x, points[nearest].y, mouse.x, mouse.y);
text(int(mind), mouse.x+10, mouse.y-10);

circle(points[nearest].x, points[nearest].y, 10);
}
}
``````
1 Like

Hi! Thatâ€™s a great question, could you upload `lines.svg` to make it easier for us to test?

Sure, here it is: https://www.dropbox.com/s/hgh5mp750rl5plg/lines.svg?dl=0

Cool!

Iâ€™m not very used to Geomerative, but I think there is a way of getting â€śchildrenâ€ť paths of the SVG instead of all the points together (this is certainly true on standard Processing loadShape() SVG->PShape loading). And I also know Geomerative does separate paths from typeface characters.

Iâ€™m working on changing your example to use `.getPointsInPaths()`, letâ€™s see if I can do it.

So, my code is very ugly but I guess it demonstrates how to get an array of arrays of points instead of just all points inside the same arrayâ€¦

Use `points = myshape.getPointsInPaths(); // RPoint [][] points`

``````import geomerative.*;

RShape myshape;
RPoint [][] points;
int chosenPath = 0;
boolean diagpoints = true;
RPoint mouse = new RPoint(0, 0);

float mind;
int nearest = -1;

int calc_min_mouse_dist() {
int minpos = -1;
mind = 10000;
int ip = chosenPath;
for (int i =0; i<points[ip].length; i++) {
float d = points[ip][i].dist(mouse);
if ( d < mind  && ip == chosenPath) {
mind = d;
minpos = i;
}
}
return minpos;
}

void setup () {
size (500, 500);
RG.init(this);
points = myshape.getPointsInPaths(); // RPoint [][] points
println(points.length);
//printArray(points);
}

void draw() {
background(100, 200, 0);
text("chosenPath = " + chosenPath, 30, 30);
for (int ip = 0; ip <points.length; ip++) {
if (points[ip] != null) {
if ( diagpoints ) for (int i=0; i<points[ip].length; i++)  ellipse(points[ip][i].x, points[ip][i].y, 2, 2);
draw_mouseline();
}
}
}

void mouseDragged() {
mouse= new RPoint(float(mouseX), float(mouseY));
nearest = calc_min_mouse_dist();
}

void mouseReleased() {
nearest = -1;
}

void draw_mouseline() {
fill(200, 0, 0);
if (nearest > -1) {
int ip = chosenPath;
line(points[ip][nearest].x, points[ip][nearest].y, mouse.x, mouse.y);
text(int(mind), mouse.x+10, mouse.y-10);
circle(points[ip][nearest].x, points[ip][nearest].y, 10);
}
}

void keyPressed() {
chosenPath = (chosenPath + 1) % points.length;
}
``````

EDIT: I have removed some unnecessary `for` loops!

4 Likes

This is great, thank you! And donâ€™t worry about ugly code, Iâ€™m the master of ugly code!
Iâ€™ve just thought, maybe a more initiative way of picking which path to select would be to find the nearest path on mousePressed instead of the keyPressed route?

1 Like

I think you could work out something like this:
On mousePressed, calculate the nearest point for each of the paths, then choose the path which has the smallest distanceâ€¦

1 Like

Hi @sixfeet!

Looking at the Geomerative docs, it seems to have some â€śclosestâ€ť functionality built in, but I didnâ€™t manage to work it out (seems like it is related to intersections and might need a line/shape instead of a point).

``````  // Get the intersection points
RClosest c = shp.getClosest(cuttingLine);
RPoint[] ps = c.distance > 0 ? c.closest : c.intersects;
``````

On the other hand, I have implemented the mousePressed thing, but in Python mode

``````add_library('geomerative')

chosen_path = 0
diag_points = True
mouse = RPoint(0, 0)
nearest_point = None

def setup():
global paths
size(500, 500)
RG.init(this)
paths = myshape.getPointsInPaths()  # RPoint [][] paths

def draw():
background(100, 200, 0)
text("chosen_path = {}".format(chosen_path), 30, 30)
for points in paths:
if points:
if diag_points:
for p in points:
ellipse(p.x, p.y, 2, 2)
draw_mouseline()

def draw_mouseline():
global mouse
mouse = RPoint(float(mouseX), float(mouseY))
fill(200, 0, 0)
if nearest_point:
line(nearest_point.x, nearest_point.y, mouse.x, mouse.y)
text(int(min_d), mouse.x + 10, mouse.y - 10)
circle(nearest_point.x, nearest_point.y, 10)

def mouseDragged():
global nearest_point
nearest_point = calc_nearest_point(paths[chosen_path])

def calc_nearest_point(path):
global min_d
nearest_point = None
min_d = 10000
for i, p in enumerate(path):
d = p.dist(mouse)
if d < min_d:
min_d = d
nearest_point = p
return nearest_point

def mouseReleased():
global nearest_point
nearest_point = None

def mousePressed():
global chosen_path
min_d = 1000
for i, path in enumerate(paths):
p = calc_nearest_point(path)
d = p.dist(mouse)
if d < min_d:
min_d = d
nearest_path = i
chosen_path = nearest_path

def keyPressed():
global chosen_path
chosen_path = (chosen_path + 1) % len(paths)
``````
2 Likes

@villares amazing! Is there a reason why you chose python over java?

Iâ€™ve been enjoying Python best in the last few years, I feel like it flows better for me, and I find the code very readable. But it is unfortunate that it lacks autocomplete in the Processing IDE.

It does read a lot easier. Trying to convert your code to java but the ease of flow in python is tricking me up porting it to java!

If I have the time Iâ€™ll try and port it back. The main tricky thing I introduced, I guess, is that the calculation of the closest point now returns a point and not an index to the point in the array. You might now have to check for `null` instead of `-1` when there is no point.

Iâ€™m close, but the mousePressed doesnâ€™t seem right and is breaking it, it either has to be a really tiny min_d to check against or else it fails.
Not as robust as your python solution for sure!

``````import geomerative.*;

RShape myshape;
RPoint [][] paths;
int chosenPath = 0;
boolean diagpaths = true;
RPoint mouse = new RPoint(0, 0);

float min_d;
int nearest,chosen_path, primid = -1;

int calc_nearest_point() {
int minpos = -1;
min_d = 10000;
int ip = chosenPath;
for (int i =0; i<paths[ip].length; i++) {
float d = paths[ip][i].dist(mouse);
if ( d < min_d  && ip == chosenPath) {
min_d = d;
minpos = i;
}
}
return minpos;
}

void setup () {
size (500, 500);
RG.init(this);
paths = myshape.getPointsInPaths(); // RPoint [][] paths
println(paths.length);
//printArray(paths);
}

void draw() {
background(100, 200, 0);
text("chosenPath = " + chosenPath, 30, 30);
for (int ip = 0; ip <paths.length; ip++) {
if (paths[ip] != null) {
if ( diagpaths ) for (int i=0; i<paths[ip].length; i++)  ellipse(paths[ip][i].x, paths[ip][i].y, 2, 2);
draw_mouseline();
}
}
}

void mousePressed(){
min_d = 50;
//int ip = chosenPath;
for (int i =0; i<paths.length; i++) {
for(int j = 0; j<paths[i].length; j++){
float d = paths[i][j].dist(mouse);
if ( d < min_d) {
chosenPath = i;
}
}
}
}

void mouseDragged() {
mouse= new RPoint(float(mouseX), float(mouseY));
nearest = calc_nearest_point();
}

void mouseReleased() {
nearest = -1;
chosenPath = 0;
}

void draw_mouseline() {
fill(200, 0, 0);
if (nearest > -1) {
int ip = chosenPath;
line(paths[ip][nearest].x, paths[ip][nearest].y, mouse.x, mouse.y);
text(int(min_d), mouse.x+10, mouse.y-10);
circle(paths[ip][nearest].x, paths[ip][nearest].y, 10);
}
}

//void keyPressed() {
//  chosenPath = (chosenPath + 1) % paths.length;
//}
``````

I think I found the problem! Maybe 2 problemsâ€¦

• You forgot to update min_d in the nearest path search
• You have to update the point for the mouse position before `mousePressed()`, otherwise you get an old mouse from the last `mouseDragged()`. I solved this by updating it in `draw()`

I added some Java â€śforEachâ€ť/â€śenhancedâ€ť loops that are similar to the Python loops, I hope youâ€™ll like them!

``````import geomerative.*;

RShape myshape;
RPoint [][] paths;
RPoint mouse = new RPoint(0, 0);
boolean diagpaths = true;
float min_d;
int nearest = -1;
int chosenPath = -1;

int calc_nearest_point() {
int minpos = -1;
min_d = 10000;
int ip = chosenPath;
for (int i =0; i<paths[ip].length; i++) {
float d = paths[ip][i].dist(mouse);
if ( d < min_d) {
min_d = d;
minpos = i;
}
}
return minpos;
}

void setup () {
size (500, 500);
RG.init(this);
// Geomerative picks the sketch folder and not the  /data/ folder as usual
paths = myshape.getPointsInPaths(); // RPoint [][] paths
println(paths.length);
//printArray(paths);
}

void draw() {
background(100, 200, 0);
// this is important to be here, otherwise mousePressed gets the wrong point!!!
mouse= new RPoint(float(mouseX), float(mouseY));
if (chosenPath >=0) text("chosenPath = " + chosenPath, 30, 30);
for (RPoint [] path : paths) {
for (RPoint p : path) {    // I think we might not need to check for null anymore
if (diagpaths) ellipse(p.x, p.y, 2, 2);
if (nearest >= 0) draw_mouseline();
}
}
}
void mousePressed() {
min_d = 1000;
for (int i =0; i<paths.length; i++) {
for (RPoint p : paths[i]) {
float d = p.dist(mouse);
if (d < min_d) {
min_d = d;
chosenPath = i;
}
}
}
}

void mouseDragged() {
nearest = calc_nearest_point();
}

void mouseReleased() {
nearest = -1;
chosenPath = -1;
}

void draw_mouseline() {
fill(200, 0, 0);
if (nearest > -1) {
int ip = chosenPath;
line(paths[ip][nearest].x, paths[ip][nearest].y, mouse.x, mouse.y);
text(int(min_d), mouse.x+10, mouse.y-10);
circle(paths[ip][nearest].x, paths[ip][nearest].y, 10);
}
fill(255);
}
``````
3 Likes

Thanks for this! learned a lot from all your examples so thank you very much!

Iâ€™ve noticed it runs a lot slower when there are more paths in the svg, is this because of the for loops? The python version seems to run a lot faster?

1 Like

Iâ€™ve refactored @villaresâ€™ Java Mode version and now it should be at least as fast (or faster ) as his Python Mode original version.

## â€śClosest_Point_on_SVG_Path.pdeâ€ť:

``````/**
* Closest Point on SVG Path (v1.0.6)
* by SixFeet & Villares
* mod GoToLoop (2020/Nov/25)
* https://Discourse.Processing.org/t/closest-point-on-svg-path/25592/17
*/

import geomerative.RG;
import geomerative.RShape;
import geomerative.RPoint;

static final String FILENAME = "lines.svg", INFO = "chosenPathIndex = ";
static final color BG = #64C800, FILL = #C80000, STROKE = 0;
static final int BOLD = 3, DIAM = 10, INFO_OFFSET = 30, MOUSE_OFFSET = 10;

final RPoint mouse = new RPoint();
RPoint[][] paths;

boolean dragging = true, displayPoints = true;
int farOff, chosenPath = -1, nearestPoint = -1;

void settings() {
RG.init(this);

paths = svg.getPointsInPaths();

println("\npaths:", paths.length);
println("x, y, w, h:", svg.getX(), svg.getY(), svg.width, svg.height);

final RPoint center = svg.getCenter();
size((int) center.x << 1, (int) center.y << 1);

center.print();
println("width, height:", width, height);
}

void setup() {
fill(FILL);
stroke(STROKE);
strokeWeight(BOLD);
}

void draw() {
mouse.x = mouseX;
mouse.y = mouseY;

background(BG);

if (displayPoints)      displayPoints();
if (nearestPoint >= 0)  drawMouseLine();
if (chosenPath >= 0)    text(INFO + chosenPath, INFO_OFFSET, INFO_OFFSET);
}

void keyPressed() {
dragging = false;
chosenPath = (chosenPath + 1) % paths.length;
findNearestPointIndex();
}

void mousePressed() {
dragging = true;
displayPoints ^= mouseButton == RIGHT;
findChosenPathIndex();
}

void mouseMoved() {
if (!dragging)  mouseDragged();
}

void mouseDragged() {
if (chosenPath >= 0)  findNearestPointIndex();
else                  findChosenPathIndex();
}

void mouseReleased() {
chosenPath = nearestPoint = -1;
}

void displayPoints() {
for (final RPoint[] pts : paths)  for (final RPoint p : pts)  point(p.x, p.y);
}

void findChosenPathIndex() {
final int len = paths.length;
int nearest = MAX_INT, lastChosenPath = -1;

for (int i = 0; (chosenPath = i) < len; ++i) {
findNearestPointIndex();

if (farOff < nearest) {
nearest = farOff;
lastChosenPath = i;
}
}

if ((chosenPath = lastChosenPath) >= 0)  findNearestPointIndex();
}

void findNearestPointIndex() {
final RPoint[] pts = paths[chosenPath];
final int len = pts.length;

farOff = MAX_INT;
nearestPoint = -1;

for (int i = 0; i < len; ++i) {
final int d = round(pts[i].dist(mouse));

if (d < farOff) {
farOff = d;
nearestPoint = i;
}
}
}

void drawMouseLine() {
final RPoint p = paths[chosenPath][nearestPoint];

strokeWeight(1);
line(p.x, p.y, mouse.x, mouse.y);
circle(p.x, p.y, DIAM);
text(farOff, mouse.x + MOUSE_OFFSET, mouse.y - MOUSE_OFFSET);
strokeWeight(BOLD);
}
``````

## â€ślines.svgâ€ť:

``````<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->