Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pixel coordinates on diamond

I got an image with a couple of diamond put side by side like on the image below

diamond coordinates

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.

like image 506
Shadoweb Avatar asked May 23 '12 08:05

Shadoweb


2 Answers

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.

like image 89
Wladimir Palant Avatar answered Nov 12 '22 02:11

Wladimir Palant


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

like image 4
A_A Avatar answered Nov 12 '22 03:11

A_A