Sketchpad Tutorial – Selecting objects based on closest line to a point. Prof. Roy Eagleson The following tutorial provides background mathematics on finding the distance from a point to a line segment. The mathematical functions are then captured as expressions in a high level language, and encapsulated in a class structure. The in order to compute the distance between any point P and a line segment, we need a representation of the line segment between two other points I and J. Let’s begin by suggesting that each of these three points can be represented in our implementation by a pair of integers, which we will call x and y. Accordingly this suggests that we create a class for such, objects containing a pair of integers: class ipair { int x,y; ipair(int xx, int yy) { x=xx; y=yy; } } This class, for now, is comprised of the two attributes x and y, and a constructor function that initializes those attributes. Let’s say the coordinates for these three points are I=(136,387), J=(727,160), and P=(261,103). So, to instantiate these three objects, we utilize this constructor function to initialize the objects through declaratives: ipair I=new ipair(136,387), J=new ipair(727,160), P=new ipair(261,103); We now want a representation of the line between I and J. It is a direction vector that starts at I and can be represented by an integer pair V=J-I. Note that this is a compact vector notion, which consists of an x and y component. Accordingly, an ipair can be used to represent this direction vector. In our example V is ((727-136), (160-387)). We can implement the vector subtraction operator using a simple function declaration called “sub”: ipair sub(ipair U, ipair W) { return new ipair( U.x-W.x, U.y-W.y ); } and using this function, ipair V = sub(J,I); We can also consider the line between P and I, as sub(P,I). We will next want to form the dot product between two vectors, which will return an integer: int dot(ipair P, ipair Q) { return P.x*Q.x + P.y*Q.y; } using this function, we can form the dot product of V with the vector (P-I), as follows: dot(V, sub(P,I)) This is a useful construct, since it is the projection of one vector onto the other (not normalized). It is a bit like one vector casting its shadow onto the other. You already appreciate that if the two vectors are in the same direction, the dot product is like multiplication. But if the two vectors are orthogonal, then the dot product will be zero. So, informally, its like two vectors multiplied by each other, but weighted by the cosine of their direction orientations. We can use this to find the distance between the point P and the line between I and J, by normalizing the ‘un-normalized’ projection by dividing by V2, or, in other words: by dot(V,V); int V2 = dot(V,V); To proceed, we need to recognize that there are three cases to consider, (1) if P is situated close to the line between I and J, then on the diagram we will calculate coordinates for N (2) Suppose P is far to the left of this diagram? Then the distance between P and the line segment will simply be the distance between P and I. (3) And on the other hand, if P is far away to the right of this diagram, then the distance between P and the line segment will simply be the distance between P and J. Another way of saying this is, N will vary on the line between I and J, except in the limiting cases where if P is far to the “negative” side, then N is at the point I – but if P is far to the “positive” side of J, then J is at the point N. These contingencies are tested using the following “if” statements: Let’s let int k=dot(V,sub(P,I)); Then, the cases break down as follows: if (k<=0) N = I; // if the projection is negative, I is nearest point else if (k>=V2) N = J; // if the projection too large, J is nearest point else N = add(I, scale(V,(float)k/(float)V2)); // otherwise, N scaled by k/V2 cont’d… / Now we have a minimal set of methods for computing the distance between a point and a line segment, mathematically. Our next job, is to transform from simple expressions, to be encapsulated within a class: Let’s call the class : class mylines { int xi,yi,xj,yj; mylines() { } mylines(int a, int b, int c, int d) { xi=a; yi=b; xj=c; yj=d; } class ipair { int x,y; ipair(int xx, int yy) { x=xx; y=yy; } } ipair add(ipair U, ipair W) { return new ipair(U.x+W.x, U.y+W.y); } ipair sub(ipair U, ipair W) { return new ipair(U.x-W.x, U.y-W.y); } ipair scale(ipair U, float s) { return new ipair((int)(s*(float)U.x), (int)(s*(float)U.y)); } int dist(ipair P, ipair Q) { return (int)Math.sqrt((P.x-Q.x)*(P.x-Q.x) + (P.y-Q.y)*(P.y-Q.y)); } int dot(ipair P, ipair Q) { return P.x*Q.x + P.y*Q.y; } int segdist(int xp,int yp) { // distance from (xp,yp) to line (xi,yi,xj,yj) ipair I=new ipair(xi,yi), J=new ipair(xj,yj), P=new ipair(xp,yp), N; ipair V = sub(J,I); // V is the vector from I to J int k = dot(V, sub(P,I)); // k is the non-normalized projection from P-I to V int V2= dot(V,V); // V2 is the length of V, squared if (k<=0) N = I; // if the projection is negative, I is nearest (N) else if (k>=V2) N = J; // if the projection too large, J is nearest (N) else N = add(I, scale(V,(float)k/(float)V2)); //otherwise scale N to V by k/V2 return dist(P,N); } } A rough demonstration of the use of this class is as follows: class mylinestest { public static void main(String[]s) { java.util.ArrayList<mylines> mylineslist = new java.util.ArrayList<mylines>(); mylineslist.add( new mylines(136,387,727,160) ); System.out.println("distance from point (261,103) to line (136,387,727,160) is " + mylineslist.get(0).segdist(261,103) ); }} $ "/cygdrive/c/Program Files/Java/jdk-11.0.1/bin/javac" mylinestest.java $ "/cygdrive/c/Program Files/Java/jdk-11.0.1/bin/java" mylinestest distance from point (261,103) to line (136,387,727,160) is 220 Now if use this class to hold a list of lines drawn with your sketchpad application, then say, if the user clicks on the screen in the mode of ‘move lines’, then you search through your list of line segments, searching for the shortest distance, which allows you to find the closest line! This can form the basis for your “Model” classes, if you wish to adopt the MVC classical architectural pattern.