Eli, the code you've settled on is incorrect. A point near the line on which the segment lies but far off one end of the segment would be incorrectly judged near the segment. Update: The incorrect answer mentioned is no longer the accepted one.
Here's some correct code, in C++. It presumes a class 2D-vector class vec2 {float x,y;}
, essentially, with operators to add, subract, scale, etc, and a distance and dot product function (i.e. x1 x2 + y1 y2
).
float minimum_distance(vec2 v, vec2 w, vec2 p) {
// Return minimum distance between line segment vw and point p
const float l2 = length_squared(v, w); // i.e. |w-v|^2 - avoid a sqrt
if (l2 == 0.0) return distance(p, v); // v == w case
// Consider the line extending the segment, parameterized as v + t (w - v).
// We find projection of point p onto the line.
// It falls where t = [(p-v) . (w-v)] / |w-v|^2
// We clamp t from [0,1] to handle points outside the segment vw.
const float t = max(0, min(1, dot(p - v, w - v) / l2));
const vec2 projection = v + t * (w - v); // Projection falls on the segment
return distance(p, projection);
}
EDIT: I needed a Javascript implementation, so here it is, with no dependencies (or comments, but it's a direct port of the above). Points are represented as objects with x
and y
attributes.
function sqr(x) { return x * x }
function dist2(v, w) { return sqr(v.x - w.x) + sqr(v.y - w.y) }
function distToSegmentSquared(p, v, w) {
var l2 = dist2(v, w);
if (l2 == 0) return dist2(p, v);
var t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
t = Math.max(0, Math.min(1, t));
return dist2(p, { x: v.x + t * (w.x - v.x),
y: v.y + t * (w.y - v.y) });
}
function distToSegment(p, v, w) { return Math.sqrt(distToSegmentSquared(p, v, w)); }
EDIT 2: I needed a Java version, but more important, I needed it in 3d instead of 2d.
float dist_to_segment_squared(float px, float py, float pz, float lx1, float ly1, float lz1, float lx2, float ly2, float lz2) {
float line_dist = dist_sq(lx1, ly1, lz1, lx2, ly2, lz2);
if (line_dist == 0) return dist_sq(px, py, pz, lx1, ly1, lz1);
float t = ((px - lx1) * (lx2 - lx1) + (py - ly1) * (ly2 - ly1) + (pz - lz1) * (lz2 - lz1)) / line_dist;
t = constrain(t, 0, 1);
return dist_sq(px, py, pz, lx1 + t * (lx2 - lx1), ly1 + t * (ly2 - ly1), lz1 + t * (lz2 - lz1));
}
Here, in the function parameters, <px,py,pz>
is the point in question and the line segment has the endpoints <lx1,ly1,lz1>
and <lx2,ly2,lz2>
. The function dist_sq
(which is assumed to exist) finds the square of the distance between two points.
You should just need to find out which is the shortest path from starting hue to ending hue. This can be done easily since hue values range from 0 to 255.
You can first subtract the lower hue from the higher one, then add 256 to the lower one to check again the difference with swapped operands.
int maxCCW = higherHue - lowerHue;
int maxCW = (lowerHue+256) - higherHue;
So you'll obtain two values, the greater one decides if you should go clockwise or counterclockwise. Then you'll have to find a way to make the interpolation operate on modulo 256 of the hue, so if you are interpolating from 246
to 20
if the coefficient is >= 0.5f
you should reset hue to 0 (since it reaches 256 and hue = hue%256
in any case).
Actually if you don't care about hue while interpolating over the 0 but just apply modulo operator after calculating the new hue it should work anyway.
Best Answer
First a short warning: Computing the distance of colors does not make sense (in most cases). Without considering the results of 50 years of research in Colorimetry, things like the CIECAM02 Color Space or perceptual linearity of distance measures, the result of such a distance measure will be counterintuitive. Colors that are "similar" according to your distance measure will appear "very different" to a viewer, and other colors, that have a large "distance" will be undistinguishable by viewers. However...
The actual question seems to aim mainly at the "Hue" part, which is a value between 0 and 360. And in fact, the values of 0 and 360 are the same - they both represent "red", as shown in this image:
Now, computing the difference of two of these values boils down to computing the distance of two points on a circle with a circumference of 360. You already know that the values are strictly in the range [0,360). If you did not know that, you would have to use the Floating-Point Modulo Operation to bring them into this range.
Then, you can compute the distance between these hue values,
h0
andh1
, asImagine this as painting both points on a circle, and picking the smaller "piece of the cake" that they describe - that is, the distance between them either in clockwise or in counterclockwise order.
The "Hue" elements are in the range [0,360]. With the above formula, you can compute a distance between two hues. This distance is in the range [0,180]. Dividing the distance by 180.0 will result in a value in [0,1]
The "Saturation" elements are in the range [0,1]. The (absolute) difference between two saturations will also be in the range [0,1].
The "Value" elements are in the range [0,255]. The absolute difference between two values will thus be in the range [0,255] as well. Dividing this difference by 255.0 will result in a value in [0,1].
So imagine you have two HSV tuples. Call them
(h0,s0,v0)
and(h1,s1,v1)
. Then you can compute the distances as follows:Each of these values will be in the range [0,1]. You can compute the length of this tuple:
and this distance will be a metric for the HSV space.