Hexagonal tiles and finding their adjacent neighbours - dictionary

I'm developing a simple 2D board game using hexagonal tile maps, I've read several articles (including the gamedev one's, which are linked every time there's a question on hexagonal tiles) on how to draw hexes on the screen and how to manage the movement (though much of it I had already done before). My main problem is finding the adjacent tiles based on a given radius.
This is how my map system works:
(0,0) (0,1) (0,2) (0,3) (0,4)
(1,0) (1,1) (1,2) (1,3) (1,4)
(2,0) (2,1) (2,2) (2,3) (2,4)
(3,0) (3,1) (3,2) (3,3) (3,4)
etc...
What I'm struggling with is the fact that I cant just 'select' the adjacent tiles by using for(x-range;x+range;x++); for(y-range;y+range;y++); because it selects unwanted tiles (in the example I gave, selecting the (1,1) tile and giving a range of 1 would also give me the (3,0) tile (the ones I actually need being (0,1)(0,2)(1,0)(1,2)(2,1)(2,2) ), which is kinda adjacent to the tile (because of the way the array is structured) but it's not really what I want to select. I could just brute force it, but that wouldn't be beautiful and would probably not cover every aspect of 'selecting radius thing'.
Can someone point me in the right direction here?

What is a hexagonal grid?
What you can see above are the two grids. It's all in the way you number your tiles and the way you understand what a hexagonal grid is. The way I see it, a hexagonal grid is nothing more than a deformed orthogonal one.
The two hex tiles I've circled in purple are theoretically still adjacent to 0,0. However, due to the deformation we've gone through to obtain the hex-tile grid from the orthogonal one, the two are no longer visually adjacent.
Deformation
What we need to understand is the deformation happened in a certain direction, along a [(-1,1) (1,-1)] imaginary line in my example. To be more precise, it is as if the grid has been elongated along that line, and squashed along a line perpendicular to that. So naturally, the two tiles on that line got spread out and are no longer visually adjacent. Conversely, the tiles (1, 1) and (-1, -1) which were diagonal to (0, 0) are now unusually close to (0, 0), so close in fact that they are now visually adjacent to (0, 0). Mathematically however, they are still diagonals and it helps to treat them that way in your code.
Selection
The image I show illustrates a radius of 1. For a radius of two, you'll notice (2, -2) and (-2, 2) are the tiles that should not be included in the selection. And so on. So, for any selection of radius r, the points (r, -r) and (-r, r) should not be selected. Other than that, your selection algorithm should be the same as a square-tiled grid.
Just make sure you have your axis set up properly on the hexagonal grid, and that you are numbering your tiles accordingly.
Implementation
Let's expand on this for a bit. We now know that movement along any direction in the grid costs us 1. And movement along the stretched direction costs us 2. See (0, 0) to (-1, 1) for example.
Knowing this, we can compute the shortest distance between any two tiles on such a grid, by decomposing the distance into two components: a diagonal movement and a straight movement along one of the axis.
For example, for the distance between (1, 1) and (-2, 5) on a normal grid we have:
Normal distance = (1, 1) - (-2, 5) = (3, -4)
That would be the distance vector between the two tiles were they on a square grid. However we need to compensate for the grid deformation so we decompose like this:
(3, -4) = (3, -3) + (0, -1)
As you can see, we've decomposed the vector into one diagonal one (3, -3) and one straight along an axis (0, -1).
We now check to see if the diagonal one is along the deformation axis which is any point (n, -n) where n is an integer that can be either positive or negative.
(3, -3) does indeed satisfy that condition, so this diagonal vector is along the deformation. This means that the length (or cost) of this vector instead of being 3, it will be double, that is 6.
So to recap. The distance between (1, 1) and (-2, 5) is the length of (3, -3) plus the length of (0, -1). That is distance = 3 * 2 + 1 = 7.
Implementation in C++
Below is the implementation in C++ of the algorithm I have explained above:
int ComputeDistanceHexGrid(const Point & A, const Point & B)
{
// compute distance as we would on a normal grid
Point distance;
distance.x = A.x - B.x;
distance.y = A.y - B.y;
// compensate for grid deformation
// grid is stretched along (-n, n) line so points along that line have
// a distance of 2 between them instead of 1
// to calculate the shortest path, we decompose it into one diagonal movement(shortcut)
// and one straight movement along an axis
Point diagonalMovement;
int lesserCoord = abs(distance.x) < abs(distance.y) ? abs(distance.x) : abs(distance.y);
diagonalMovement.x = (distance.x < 0) ? -lesserCoord : lesserCoord; // keep the sign
diagonalMovement.y = (distance.y < 0) ? -lesserCoord : lesserCoord; // keep the sign
Point straightMovement;
// one of x or y should always be 0 because we are calculating a straight
// line along one of the axis
straightMovement.x = distance.x - diagonalMovement.x;
straightMovement.y = distance.y - diagonalMovement.y;
// calculate distance
size_t straightDistance = abs(straightMovement.x) + abs(straightMovement.y);
size_t diagonalDistance = abs(diagonalMovement.x);
// if we are traveling diagonally along the stretch deformation we double
// the diagonal distance
if ( (diagonalMovement.x < 0 && diagonalMovement.y > 0) ||
(diagonalMovement.x > 0 && diagonalMovement.y < 0) )
{
diagonalDistance *= 2;
}
return straightDistance + diagonalDistance;
}
Now, given the above implemented ComputeDistanceHexGrid function, you can now have a naive, unoptimized implementation of a selection algorithm that will ignore any tiles further than the specified selection range:
int _tmain(int argc, _TCHAR* argv[])
{
// your radius selection now becomes your usual orthogonal algorithm
// except you eliminate hex tiles too far away from your selection center
// for(x-range;x+range;x++); for(y-range;y+range;y++);
Point selectionCenter = {1, 1};
int range = 1;
for ( int x = selectionCenter.x - range;
x <= selectionCenter.x + range;
++x )
{
for ( int y = selectionCenter.y - range;
y <= selectionCenter.y + range;
++y )
{
Point p = {x, y};
if ( ComputeDistanceHexGrid(selectionCenter, p) <= range )
cout << "(" << x << ", " << y << ")" << endl;
else
{
// do nothing, skip this tile since it is out of selection range
}
}
}
return 0;
}
For a selection point (1, 1) and a range of 1, the above code will display the expected result:
(0, 0)
(0, 1)
(1, 0)
(1, 1)
(1, 2)
(2, 1)
(2, 2)
Possible optimization
For optimizing this, you can include the logic of knowing how far a tile is from the selection point (logic found in ComputeDistanceHexGrid) directly into your selection loop, so you can iterate the grid in a way that avoids out of range tiles altogether.

Simplest method i can think of...
minX = x-range; maxX = x+range
select (minX,y) to (maxX, y), excluding (x,y) if that's what you want to do
for each i from 1 to range:
if y+i is odd then maxX -= 1, otherwise minX += 1
select (minX, y+i) to (maxX, y+i)
select (minX, y-i) to (maxX, y-i)
It may be a little off; i just worked it through in my head. But at the very least, it's an idea of what you need to do.
In C'ish:
void select(int x, int y) {
/* todo: implement this */
/* should ignore coordinates that are out of bounds */
}
void selectRange(int x, int y, int range) {
int minX = x - range, maxX = x + range;
for (int i = minX; i <= maxX; ++i) {
if (i != x) select(i, y);
}
for (int yOff = 1; yOff <= range; ++yOff) {
if ((y+yOff) % 2 == 1) --maxX; else ++minX;
for (int i=minX; i<=maxX; ++i) {
select(i, y+yOff);
select(i, y-yOff);
}
}
}

Related

Piechart on a Hexagon

I wanna to produce a Pie Chart on a Hexagon. There are probably several solutions for this. In the picture are my Hexagon and two Ideas:
My Hexagon (6 vertices, 4 faces)
How it should look at the end (without the gray lines)
Math: Can I get some informations from the object to dynamically calculate new vertices (from the center to each point) to add colored faces?
Clipping: On a sphere a Pie-Chart is easy, maybe I can clip the THREE Object (WITHOUT SVG.js!) so I just see the Hexagon with the clipped Chart?
Well the whole clipping thing in three.js is already solved here : Object Overflow Clipping Three JS, with a fiddle that shows it works and all.
So I'll go for the "vertices" option, or rather, a function that, given a list of values gives back a list of polygons, one for each value, that are portions of the hexagon, such that
they all have the centre point as a vertex
the angle they have at that point is proportional to the value
they form a partition the hexagon
Let us suppose the hexagon is inscribed in a circle of radius R, and defined by the vertices :
{(R sqrt(3)/2, R/2), (0,R), (-R sqrt(3)/2, R/2), (-R sqrt(3)/2, -R/2), (0,-R), (R sqrt(3)/2, -R/2)}
This comes easily from the values cos(Pi/6), sin(Pi/6) and various symmetries.
Getting the angles at the centre for each polygon is pretty simple, since it is the same as for a circle. Now we need to know the position of the points that are on the hexagon.
Note that if you use the symmetries of the coordinate axes, there are only two cases : [0,Pi/6] and [Pi/6,Pi/2], and you then get your result by mirroring. If you use the rotational symmetry by Pi/3, you only have one case : [-Pi/6,Pi/6], and you get the result by rotation.
Using rotational symmetry
Thus for every point, you can consider it's angle to be between [-Pi/6,Pi/6]. Any point on the hexagon in that part has x=R sqrt(3)/2, which simplifies the problem a lot : we only have to find it's y value.
Now we assumed that we know the polar coordinate angle for our point, since it is the same as for a circle. Let us call it beta, and alpha its value in [-Pi/6,Pi/6] (modulo Pi/3). We don't know at what distance d it is from the centre, and thus we have the following system :
Which is trivially solved since cos is never 0 in the range [-Pi/6,Pi/6].
Thus d=R sqrt(3)/( 2 cos(alpha) ), and y=d sin(alpha)
So now we know
the angle from the centre beta
it's distance d from the centre, thanks to rotational symmetry
So our point is (d cos(beta), d sin(beta))
Code
Yeah, I got curious, so I ended up coding it. Sorry if you wanted to play with it yourself. It's working, and pretty ugly in the end (at least with this dataset), see the jsfiddle : http://jsfiddle.net/vb7on8vo/5/
var R = 100;
var hexagon = [{x:R*Math.sqrt(3)/2, y:R/2}, {x:0, y:R}, {x:-R*Math.sqrt(3)/2, y:R/2}, {x:-R*Math.sqrt(3)/2, y:-R/2}, {x:0, y:-R}, {x:R*Math.sqrt(3)/2, y:-R/2}];
var hex_angles = [Math.PI / 6, Math.PI / 2, 5*Math.PI / 6, 7*Math.PI / 6, 3*Math.PI / 2, 11*Math.PI / 6];
function regions(values)
{
var i, total = 0, regions = [];
for(i=0; i<values.length; i++)
total += values[i];
// first (0 rad) and last (2Pi rad) points are always at x=R Math.sqrt(3)/2, y=0
var prev_point = {x:hexagon[0].x, y:0}, last_angle = 0;
for(i=0; i<values.length; i++)
{
var j, theta, p = [{x:0,y:0}, prev_point], beta = last_angle + values[i] * 2 * Math.PI / total;
for( j=0; j<hexagon.length; j++)
{
theta = hex_angles[j];
if( theta <= last_angle )
continue;
else if( theta >= beta )
break;
else
p.push( hexagon[j] );
}
var alpha = beta - (Math.PI * (j % 6) / 3); // segment 6 is segment 0
var d = hexagon[0].x / Math.cos(alpha);
var point = {x:d*Math.cos(beta), y:d*Math.sin(beta)};
p.push( point );
regions.push(p.slice(0));
last_angle = beta;
prev_point = {x:point.x, y:point.y};
}
return regions;
}

Given a segmented circle and a point of impact, calculate the collided segment

I have a circle with a rotation. See images below for example. The circle is divided into segments of varying degrees, for this example I've divided the circle into three equal 120 degree segments.
Given a point of impact (a point on the exterior radius of the circle) I calculate the degree between the center of the circle and the point of impact. I then need to determine which segment was impacted.
My current solution went something like this:
var circleRotation = 270;
var segments = [120, 120, 120];
function segmentAtAngle(angle) {
var sumTo = circleRotation;
for (var i = 0, l = segments.length; l > i; i++) {
if (sumTo <= angle && sumTo + segments[i] >= angle) {
// return the segment
return i;
}
sumTo += segments[i];
}
}
My solution does not work in all cases, given a large offset of say 270 and when requesting the segment at impact degree 45 I currently faultily provide nothing.
Note: Provided angle to segmentAtAngle and circleRotation will also never be negative or above 360. I standardize the degrees by { degrees = degrees % 360; if (degrees < 0) degrees += 360; return degrees; }
What would be the proper way to calculate the hit segment of a circle given an offset rotation?
A simple ad-hoc solution would be duplicating your lists of segments. Then you have the whole range from 0° to 2·360°=720° covered. If angle and circleRotation is between 0° and 360°, as you say they are, then their sum will be between 0° and 720°, and having twice the list of segments will yield a match in all cases. If the resulting index is greater or equal to the length of the original unduplicated list, you can subtract that length to obtain an index from that original list.
First, the conditions of your for loop looks kind of weird. l will always be larger than zero, so the loop will never execute at all. Secondly, you should probably standardize sumTo each time you add to it. Third, you return angle within the loop, which never changes. Do you want to return the index of the impacted segment?
var circleRotation = 270;
var segments = [120, 120, 120];
function standardize(degrees){
degrees = degrees % 360;
if (degrees < 0) degrees += 360;
return degrees;
}
function segmentAtAngle(angle) {
var sumTo = circleRotation;
for (var i = 0; i<segments.length; i++) {
if (sumTo <= angle && sumTo + segments[i] >= angle) {
return i;
}
sumTo = standardize(sumTo + segments[i]);
}
}
The function atan2(DY, DX) will give you the angle from the center to any point. This angle will be in range -pi to +pi. For the sake of the discussion, let us convert this to the -180..+180° range.
Now consider the delimiting angles of your segments, as if obtained by the same function: they will correspond to the ranges [-120..0], [0..120] and [120, -120]. All is fine, except that the third interval straddles the discontinuity, and it should be split into [120..180] and [-180..-120].
In the end, you should consider this list of bounds, with corresponding sectors:
-180 -120 0 120 180
Yellow | Red | Green | Yellow
With N colors, you will need to consider N+1 intervals and compare to N bounds (no need to check against the extreme values, they are implicitly fulfilled). You will do this by linear or dichotomic search (or simple rescaling in case of equidistant bounds).

Math Help: 3d Modeling / Three.js - Rotating figure dynamically

This is a big one for any math/3d geometry lovers. Thank you in advance.
Overview
I have a figure created by extruding faces around twisting spline curves in space. I'm trying to place a "loop" (torus) oriented along the spline path at a given segment of the curve, so that it is "aligned" with the spline. By that I mean the torus's width is parallel to the spline path at the given extrusion segment, and it's height is perpendicular to the face that is selected (see below for picture).
Data I know:
I am given one of the faces of the figure. From that I can also glean that face's centroid (center point), the vertices that compose it, the surrounding faces, and the normal vector of the face.
Current (Non-working) solution outcome:
I can correctly create a torus loop around the centroid of the face that is clicked. However, it does not rotate properly to "align" with the face. See how they look a bit "off" below.
Here's a picture with the material around it:
and here's a picture with it in wireframe mode. You can see the extrusion segments pretty clearly.
Current (Non-working) methodology:
I am attempting to do two calculations. First, I'm calculating the the angle between two planes (the selected face and the horizontal plane at the origin). Second, I'm calculating the angle between the face and a vertical plane at the point of origin. With those two angles, I am then doing two rotations - an X and a Y rotation on the torus to what I hope would be the correct orientation. It's rotating the torus at a variable amount, but not in the place I want it to be.
Formulas:
In doing the above, I'm using the following to calculate the angle between two planes using their normal vectors:
Dot product of normal vector 1 and normal vector 2 = Magnitude of vector 1 * Magnitude of vector 2 * Cos (theta)
Or:
(n1)(n2) = || n1 || * || n2 || * cos (theta)
Or:
Angle = ArcCos { ( n1 * n2 ) / ( || n1 || * || n2 || ) }
To determine the magnitude of a vector, the formula is:
The square root of the sum of the components squared.
Or:
Sqrt { n1.x^2 + n1.y^2 + n1.z^2 }
Also, I'm using the following for the normal vectors of the "origin" planes:
Normal vector of horizontal plane: (1, 0, 0)
Normal vector of Vertical plane: (0, 1, 0)
I've thought through the above normal vectors a couple times... and I think(?) they are right?
Current Implementation:
Below is the code that I'm currently using to implement it. Any thoughts would be much appreciated. I have a sinking feeling that I'm taking a wrong approach in trying to calculate the angles between the planes. Any advice / ideas / suggestions would be much appreciated. Thank you very much in advance for any suggestions.
Function to calculate the angles:
this.toRadians = function (face, isX)
{
//Normal of the face
var n1 = face.normal;
//Normal of the vertical plane
if (isX)
var n2 = new THREE.Vector3(1, 0, 0); // Vector normal for vertical plane. Use for Y rotation.
else
var n2 = new THREE.Vector3(0, 1, 0); // Vector normal for horizontal plane. Use for X rotation.
//Equation to find the cosin of the angle. (n1)(n2) = ||n1|| * ||n2|| (cos theta)
//Find the dot product of n1 and n2.
var dotProduct = (n1.x * n2.x) + (n1.y * n2.y) + (n1.z * n2.z);
// Calculate the magnitude of each vector
var mag1 = Math.sqrt (Math.pow(n1.x, 2) + Math.pow(n1.y, 2) + Math.pow(n1.z, 2));
var mag2 = Math.sqrt (Math.pow(n2.x, 2) + Math.pow(n2.y, 2) + Math.pow(n2.z, 2));
//Calculate the angle of the two planes. Returns value in radians.
var a = (dotProduct)/(mag1 * mag2);
var result = Math.acos(a);
return result;
}
Function to create and rotate the torus loop:
this.createTorus = function (tubeMeshParams)
{
var torus = new THREE.TorusGeometry(5, 1.5, segments/10, 50);
fIndex = this.calculateFaceIndex();
//run the equation twice to calculate the angles
var xRadian = this.toRadians(geometry.faces[fIndex], false);
var yRadian = this.toRadians(geometry.faces[fIndex], true);
//Rotate the Torus
torus.applyMatrix(new THREE.Matrix4().makeRotationX(xRadian));
torus.applyMatrix(new THREE.Matrix4().makeRotationY(yRadian));
torusLoop = new THREE.Mesh(torus, this.m);
torusLoop.scale.x = torusLoop.scale.y = torusLoop.scale.z = tubeMeshParams['Scale'];
//Create the torus around the centroid
posx = geometry.faces[fIndex].centroid.x;
posy = geometry.faces[fIndex].centroid.y;
posz = geometry.faces[fIndex].centroid.z;
torusLoop.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(posx, posy, posz));
torusLoop.geometry.computeCentroids();
torusLoop.geometry.computeFaceNormals();
torusLoop.geometry.computeVertexNormals();
return torusLoop;
}
I found I was using an incorrect approach to do this. Instead of trying to calculate each angle and do a RotationX and a RotationY, I should have done a rotation by axis. Definitely was over thinking it.
makeRotationAxis(); is a function built into three.js.

How to compute normals for a segment line in 3D

I have exported some hair particules from Blender (a hairstyle). These are composed of several lines (GL_LINES). My openGL program displays these particules without any problem. Now I just want to apply light properties on these particules. Blender does not export the normals vectors so I need to compute them by myself. I know the following rule :
If we define a line segment as [AB] in two dimensions,
we have dx = xB - xA and dy = yB - yA, then the normals are N1(-dy, dx) and N2(dy, -dx).
I hope I did not make any mistake.
But I don't know the rule for a 3D space line segment definition if I add the z dimention in my line segment coordinates (for instance A(5, 2, 3) and B(0, 0, -5)).
Does anyone can help me?
Since Aki forgot that comments aren't answers:
Lines in 3D space don't have a normal. Technically, lines in 2D space don't have a normal either; they have two normals.
There are an infinite number of directions that are perpendicular to a line in 3D space. All of these normals are in the same plane, but with different directions. Without some more advanced algorithm (likely based on adjacent lines), there is no way to pick one of these normals over another.
If you assume that you can get two vectors to begin with, and it looks like that's what you are saying, call them v, w, to get a normal vector take the cross product. It's not a bad idea to normalize v, w to begin with, depending on the situation. The cross product can be given by:
v x w =(v_2w_3 - v_3w_2, v_3x_1 - v_1w_3, v_1w_2 - v_3w_1),
Here v_i is the ith component of v and so on. The numbers next to each other represent multiplication. You, of course, have plus or minus this vector giving two possibilities.
I had a similar question, and even used the indefinite article "a". Some have suggested there is no norm to a 3D line segment by saying there is an infinite number of them. Yet, miss the indefinite article "a" --- which I assume could mean any 1 of infinite.
What happens when someone does not have two vectors to start with?
vector is the unit vector of the line segment or vector.
create a rotation matrix around vector to obtain 1 of infinite norms
It took some time, but using Eigen template library and 10000 random test samples. Here is the code:
#include <Eigen/Core>
#include <Eigen/Geometry>
Eigen::MatrixXd samples = Eigen::MatrixXd::Random(10000, 3); // 3x3 Matrix filled with random numbers between (-1,1)
for (int i = 0; i < 10000; ++i)
{
Eigen::Vector3d vector(samples(i, 0), samples(i, 1), samples(i, 2));
vector.normalize();
Eigen::Vector3d zaxis(0, 0, 1);
Eigen::Vector3d xaxis = zaxis.cross(vector);
xaxis.normalize();
Eigen::Vector3d yaxis = vector.cross(xaxis);
yaxis.normalize();
Eigen::Matrix3d m;
m(0, 0) = xaxis(0);
m(0, 1) = yaxis(0);
m(0, 2) = vector(0);
m(1, 0) = xaxis(1);
m(1, 1) = yaxis(1);
m(1, 2) = vector(1);
m(2, 0) = xaxis(2);
m(2, 1) = yaxis(2);
m(2, 2) = vector(2);
// one of two easy points to use to get 1 of infinite norms --- the other being (1, 0, 0)
Eigen::Vector3d point(0, 1, 0);
point = m * point;
point.normalize();
auto norm = point.cross(vector);
norm.normalize(); // 1 of an infinite number of norms
auto check = norm.dot(vector); // verify with dot product
if (std::abs(check) >= 1e-12)
{
//complain
}
}

Implementing Ray Picking

I have a renderer using directx and openGL, and a 3d scene. The viewport and the window are of the same dimensions.
How do I implement picking given mouse coordinates x and y in a platform independent way?
If you can, do the picking on the CPU by calculating a ray from the eye through the mouse pointer and intersect it with your models.
If this isn't an option I would go with some type of ID rendering. Assign each object you want to pick a unique color, render the objects with these colors and finally read out the color from the framebuffer under the mouse pointer.
EDIT: If the question is how to construct the ray from the mouse coordinates you need the following: a projection matrix P and the camera transform C. If the coordinates of the mouse pointer is (x, y) and the size of the viewport is (width, height) one position in clip space along the ray is:
mouse_clip = [
float(x) * 2 / float(width) - 1,
1 - float(y) * 2 / float(height),
0,
1]
(Notice that I flipped the y-axis since often the origin of the mouse coordinates are in the upper left corner)
The following is also true:
mouse_clip = P * C * mouse_worldspace
Which gives:
mouse_worldspace = inverse(C) * inverse(P) * mouse_clip
We now have:
p = C.position(); //origin of camera in worldspace
n = normalize(mouse_worldspace - p); //unit vector from p through mouse pos in worldspace
Here's the viewing frustum:
First you need to determine where on the nearplane the mouse click happened:
rescale the window coordinates (0..640,0..480) to [-1,1], with (-1,-1) at the bottom-left corner and (1,1) at the top-right.
'undo' the projection by multiplying the scaled coordinates by what I call the 'unview' matrix: unview = (P * M).inverse() = M.inverse() * P.inverse(), where M is the ModelView matrix and P is the projection matrix.
Then determine where the camera is in worldspace, and draw a ray starting at the camera and passing through the point you found on the nearplane.
The camera is at M.inverse().col(4), i.e. the final column of the inverse ModelView matrix.
Final pseudocode:
normalised_x = 2 * mouse_x / win_width - 1
normalised_y = 1 - 2 * mouse_y / win_height
// note the y pos is inverted, so +y is at the top of the screen
unviewMat = (projectionMat * modelViewMat).inverse()
near_point = unviewMat * Vec(normalised_x, normalised_y, 0, 1)
camera_pos = ray_origin = modelViewMat.inverse().col(4)
ray_dir = near_point - camera_pos
Well, pretty simple, the theory behind this is always the same
1) Unproject two times your 2D coordinate onto the 3D space. (each API has its own function, but you can implement your own if you want). One at Min Z, one at Max Z.
2) With these two values calculate the vector that goes from Min Z and point to Max Z.
3) With the vector and a point calculate the ray that goes from Min Z to MaxZ
4) Now you have a ray, with this you can do a ray-triangle/ray-plane/ray-something intersection and get your result...
I have little DirectX experience, but I'm sure it's similar to OpenGL. What you want is the gluUnproject call.
Assuming you have a valid Z buffer you can query the contents of the Z buffer at a mouse position with:
// obtain the viewport, modelview matrix and projection matrix
// you may keep the viewport and projection matrices throughout the program if you don't change them
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
glGetIntegerv(GL_VIEWPORT, viewport);
glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
glGetDoublev(GL_PROJECTION_MATRIX, projection);
// obtain the Z position (not world coordinates but in range 0 - 1)
GLfloat z_cursor;
glReadPixels(x_cursor, y_cursor, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z_cursor);
// obtain the world coordinates
GLdouble x, y, z;
gluUnProject(x_cursor, y_cursor, z_cursor, modelview, projection, viewport, &x, &y, &z);
if you don't want to use glu you can also implement the gluUnProject you could also implement it yourself, it's functionality is relatively simple and is described at opengl.org
Ok, this topic is old but it was the best I found on the topic, and it helped me a bit, so I'll post here for those who are are following ;-)
This is the way I got it to work without having to compute the inverse of Projection matrix:
void Application::leftButtonPress(u32 x, u32 y){
GL::Viewport vp = GL::getViewport(); // just a call to glGet GL_VIEWPORT
vec3f p = vec3f::from(
((float)(vp.width - x) / (float)vp.width),
((float)y / (float)vp.height),
1.);
// alternatively vec3f p = vec3f::from(
// ((float)x / (float)vp.width),
// ((float)(vp.height - y) / (float)vp.height),
// 1.);
p *= vec3f::from(APP_FRUSTUM_WIDTH, APP_FRUSTUM_HEIGHT, 1.);
p += vec3f::from(APP_FRUSTUM_LEFT, APP_FRUSTUM_BOTTOM, 0.);
// now p elements are in (-1, 1)
vec3f near = p * vec3f::from(APP_FRUSTUM_NEAR);
vec3f far = p * vec3f::from(APP_FRUSTUM_FAR);
// ray in world coordinates
Ray ray = { _camera->getPos(), -(_camera->getBasis() * (far - near).normalize()) };
_ray->set(ray.origin, ray.dir, 10000.); // this is a debugging vertex array to see the Ray on screen
Node* node = _scene->collide(ray, Transform());
cout << "node is : " << node << endl;
}
This assumes a perspective projection, but the question never arises for the orthographic one in the first place.
I've got the same situation with ordinary ray picking, but something is wrong. I've performed the unproject operation the proper way, but it just doesn't work. I think, I've made some mistake, but can't figure out where. My matix multiplication , inverse and vector by matix multiplications all seen to work fine, I've tested them.
In my code I'm reacting on WM_LBUTTONDOWN. So lParam returns [Y][X] coordinates as 2 words in a dword. I extract them, then convert to normalized space, I've checked this part also works fine. When I click the lower left corner - I'm getting close values to -1 -1 and good values for all 3 other corners. I'm then using linepoins.vtx array for debug and It's not even close to reality.
unsigned int x_coord=lParam&0x0000ffff; //X RAW COORD
unsigned int y_coord=client_area.bottom-(lParam>>16); //Y RAW COORD
double xn=((double)x_coord/client_area.right)*2-1; //X [-1 +1]
double yn=1-((double)y_coord/client_area.bottom)*2;//Y [-1 +1]
_declspec(align(16))gl_vec4 pt_eye(xn,yn,0.0,1.0);
gl_mat4 view_matrix_inversed;
gl_mat4 projection_matrix_inversed;
cam.matrixProjection.inverse(&projection_matrix_inversed);
cam.matrixView.inverse(&view_matrix_inversed);
gl_mat4::vec4_multiply_by_matrix4(&pt_eye,&projection_matrix_inversed);
gl_mat4::vec4_multiply_by_matrix4(&pt_eye,&view_matrix_inversed);
line_points.vtx[line_points.count*4]=pt_eye.x-cam.pos.x;
line_points.vtx[line_points.count*4+1]=pt_eye.y-cam.pos.y;
line_points.vtx[line_points.count*4+2]=pt_eye.z-cam.pos.z;
line_points.vtx[line_points.count*4+3]=1.0;

Resources