I got an image with a couple of diamond put side by side like on the image below
The only coordinates I know on the image are the top corners (green text).
When I click on the image I get the coordinates of that point, but I'm not able to get which diamond I'm on.
For example I click on the red dot, how do I know that x:260, y:179 = the top diamond ?
And the blue belongs to the left ? etc...
Thank you very much for your help.
EDIT:
I finally used Canvas, but I think SVG would have worked as well for what I needed to do.
I see two possible approaches: direct check whether a point is inside a diamond and using affine transformations. I will describe both.
Direct point position check
To determine whether a point is inside a diamond you have to check its deviation from the middle point of a diamond. You have to put the X and Y deviations in proportion with the X and Y extents of the diamond, you will get two factors. For all points inside the diamond the sum of the modulo values for these factors is smaller or equal 1. In code this looks like this:
var dx = Math.abs(coords[0] - middle[0]);
var dy = Math.abs(coords[1] - middle[1]);
if (dx / size[0] + dy / size[1] <= 1)
alert("Inside diamond");
else
alert("Outside diamond");
So all you have to do now is determining the middle point for each diamond (size is the same in all cases) and checking whether the point you are testing is located inside them.
Working example: http://jsfiddle.net/z98hr/
Affine transformations
Using affine transformations you can change the corner coordinates of your top diamond into (0,0), (1,0), (0,1) and (1,1). If you then apply the same transformation to the point you need to test, determining which diamond it belongs to becomes trivial.
First you will need a translation vector to move the (225,2) point into the origin of coordinates. Let's say that you have four coordinates determining your top diamond (left and right coordinate, top and bottom coordinate):
var topDiamond = [[113, 2], [337, 227]];
Then the translation vector to move the top point of the diamond to the zero coordinate would be:
var translationVector = [-(topDiamond[0][0] + topDiamond[1][0]) / 2,
-topDiamond[0][1]];
You can apply it to the original coordinates like this:
function add(vector1, vector2)
{
return [vector1[0] + vector2[0], vector1[1] + vector2[1]];
}
topDiamond = [add(topDiamond[0], translationVector),
add(topDiamond[1], translationVector)];
Then you will need a rotation matrix:
var angle = -Math.atan2(topDiamond[1][1] - topDiamond[0][1],
topDiamond[1][0] - topDiamond[0][0]);
var rotMatrix = [[Math.cos(angle), -Math.sin(angle)],
[Math.sin(angle), Math.cos(angle)]];
After the multiplication with this matrix the points (225,2) and (337,114.5) are aligned on the X axis. But what you have now is a trapeze, you now need a horizontal shear transformation to get the other side of the diamond aligned on the Y axis:
function multiply(matrix, vector)
{
return [matrix[0][0] * vector[0] + matrix[0][1] * vector[1],
matrix[1][0] * vector[0] + matrix[1][1] * vector[1]];
}
var point = [topDiamond[0][0], (topDiamond[0][1] + topDiamond[1][1]) / 2];
point = multiply(rotMatrix, point);
var shearMatrix = [[1, -point[0] / point[1]], [0, 1]];
After multiplication with this matrix you have a rectangle now. Now you only need a scaling matrix to make sure that the X and Y coordinates of the corners have the value 0 and 1:
point = multiply(shearMatrix, point);
var point2 = [topDiamond[1][0], (topDiamond[0][1] + topDiamond[1][1]) / 2];
point2 = multiply(rotMatrix, point2);
point2 = multiply(shearMatrix, point2);
var scaleMatrix = [[1/point2[0], 0], [0, 1/point[1]]];
And there you have it, now you can apply these transformations to any point:
alert(
multiply(scaleMatrix,
multiply(shearMatrix,
multiply(rotMatrix,
add(translationVector, [260, 179])
)
)
)
);
This gives you 0.94,0.63
- both values are in the (0..1)
range meaning that it is the top diamond. With [420,230]
as input you get 1.88,0.14
- X in (1..2)
range and Y in 0..1
range means right diamond. And so on.
Working example: http://jsfiddle.net/FzWHe/
In the retrospective, this was probably too much work for a simple geometrical figure like a diamond.
Essentially, what you have there is possibly an isometric view of 4 tiles (based on your comment about the diamonds appearing as trapezoids).
One quick way of doing this is to create 2 lines that are parallel with the "axes" of the "diamonds" (but still are crossing with each other...this is important as well). In the example image given, that would mean two lines that are vertical to each other but rotated by 45 degrees. In the isometric case, the lines will not be vertical to each other but at some other angle depending on your view.
Once you have these two lines you can create a "hitTest()" function that will be taking the coordinates of the point that was clicked and will be evaluating the two line equations. You are not really interested on the actual number returned by the line equations but only the signs. The sign shows you which side of the line does your point resides.
This means that your "diamonds" will correspond to these sign pairs (one sign for each line equation) [-,-], [-,+], [+,-], [+,+].
(Please note that the sign depends on the way that the line was defined, in other words for a given point P, the sign from some line equation (L) will be different if the line was defined as running "from left to right" or "from right to left", or more generally the sign will be the reverse for reciprocal directions.)
A bit more information about the form of the line equation you need can be obtained from here
Using matrices, you can derive a quick formula for which diamond is selected.
You want a transformation from (x,y)
into "diamond-space". That is, a coordinate system where (0,0)
is the top diamond, (1,0)
is the one below to the right, and (0,1)
below to the left.
A * x = y
where A
is the transformation, x
is the image coordinates, and y
is the diamond-coordinates. To deal with the translation ((0,0)
not being the same point in both spaces), you can add another row to the vectors, which is always 1
.
You can transform multiple vectors at the same time, by putting them beside each other, so they form a matrix.
[ a b dx ] [ 225 337 113 ] [ 0 1 0 ]
[ c d dy ] * [ 2 114 114 ] = [ 0 0 1 ]
[ 0 0 1 ] [ 1 1 1 ] [ 1 1 1 ]
^ ^ ^-left ^-^-^--- new coordinates for each point
| '-right
'-top diamond
To solve for the coefficients in the first matrix, you need to divide by the second matrix (or multiply by the inverse).
[ a b dx ] [ 0 1 0 ] [ 225 337 113 ]^-1
[ c d dy ] = [ 0 0 1 ] * [ 2 114 114 ]
[ 0 0 1 ] [ 1 1 1 ] [ 1 1 1 ]
The result is:
[ a b dx ] [ (1/224) (1/224) (-227/224) ]
[ c d dy ] = [ (-1/224) (1/224) (223/224) ]
[ 0 0 1 ] [ 0 0 1 ]
To put this into program code:
function getDiamond(x, y) {
return [(x + y - 227) / 224, (-x + y + 223) / 224];
}
Example:
> getDiamond(260,179); // red
[0.9464285714285714, 0.6339285714285714]
> getDiamond(250,230); // green
[1.1294642857142858, 0.90625]
> getDiamond(189,250); // blue
[0.9464285714285714, 1.2678571428571428]
> getDiamond(420,230); // yellow
[1.8883928571428572, 0.14732142857142858]
If you look at the integer parts, you can see which diamond the coordinate corresponds to. The red one is at (0.94, 0.63)
which is in region (0,0)
pretty close to the edge of (1,0)
.
NB. The blue and green points in OP is drawn in the wrong location (or given wrong coordinates), so the result of my function places them in a different relative location.
If you do the calculations symbolically, you end up with this:
[ a b dx ] [ (y2 - y0)/M -(x2 - x0)/M -(x0*y2 - y0*x2)/M ]
[ c d dy ] = [-(y1 - y0)/M (x1 - x0)/M (x0*y1 - y0*x1)/M ]
[ 0 0 1 ] [ 0 0 1 ]
where M = x1*y2 - x2*y1 - y0*x1 + y0*x2 + x0*y1 - x0*y2
.
Point 0 being the position of top diamond, point 1 being the position of right diamond, and point 2 being the position of left diamond.
Here is a function to calculate this:
function DiamondMaker(topx,topy, leftx,lefty, rightx,righty)
{
var M = topx*lefty - topx*righty +
leftx*righty - leftx*topy +
rightx*topy - rightx*lefty;
var a = -(topy - righty)/M;
var b = (topx - rightx)/M;
var dx = -(topx*righty - topy*rightx)/M;
var c = (topy - lefty)/M;
var d = -(topx - leftx)/M;
var dy = (topx*lefty - topy*leftx)/M;
return function(x, y) {
return [a * x + b * y + dx, c * x + d * y + dy];
};
}
var getDiamond = DiamondMaker(225,2, 337,114, 113,114);
// (same example as before)
All you need - just stady what is roration. Here is link: http://en.wikipedia.org/wiki/Rotation_(mathematics)
You should rotate you point in order to make sides of squares in parrallel with coordinate's grid. Point of rotaion should be 1 corner of dimonds you will threat as 0,0 diamond. After rotaion you can easily define how many daimond you point away from 0,0