So halb. Ehrlich gesagt habe ich, als ich das ganze mal umgesetzt habe, auch ein bißchen „rumprobiert“, obwohl man ja eigentlich in der Lage sein sollte, das beginnend mit einem weißen Blatt Papier und einem Bleistift from scratch bis zum Ende herzuleiten und dann nur noch runterzuimplementieren :o
Du hattest jetzt gefragt: Wenn ich nun ein RendeRobject auf position (0, 0) habe, muss ich meine maus also, um diesen Punkt zu treffen, auf (400, 300) bewgen… wie rechne ich das also um?
Das ist etwas ungewöhnlich. Normalerweise sollte man jeden Welt-Punkt einfach durch die Kette aus
projection * camera * model
scheuchen können, um genau die Bildschirmkoordinaten zu bekommen. (Genau das MACHT diese Kette ja - für jeden Vertex).
Bisher (vorher) hatte ich gedacht, dass du genau die umgekehrte Richtung brauchst: Wenn man irgendwo hin klickt - welches Objekt wurde dann getroffen? Das ist etwas frickeliger, weil man den „Picking Ray“ berechnen muss. Also den Strahl, der vom Auge durch den Bildschirmpunkt in die Szene geschossen wird, und dort dann ggf. ein Objekt trifft.
Da kommt jetzt noch dazu, dass ich mich von anderen Threads zu erinnern glaube, dass es unterschiedliche Interpretationen des Wortes „Camera Matrix“ bzw. „View Matrix“ gibt. Meines Wissens sind das getrennte Begriffe, aber erfreulicherweise ist das eine genau das Inverse vom anderen.
Wo ich das mal gebraucht habe
[spoiler]
Ich hatte mal (aus „Langeweile“?) angefangen, so ein paar Rendering-Utilities zu schreiben. Das ganze ist im Moment in einer sehr frühen, rudimentären, unfertigen Version (Version 0.0.0
) auf JavaGL Rendering . Mehr als „das ist NICHT GUT“ will ich da jetzt erstmal nicht dazu sagen, aber vielleicht f(i/ä)ndest du einige Snippets daraus hilfreich, die ich hier mal kommentarlos zusammenkopiere:
Aus PickingUtils.java:
/**
* Compute a picking {@link Ray} given the camera matrix, projection
* matrix, viewport and position. The camera matrix contains the
* rotation and translation for the camera. The projectionMatrix
* contains solely the perspective projection transformation. The
* viewport describes the rectangle that the picking occurs in.
* The x and y coordinates are the position where the picking ray
* should start, in screen coordinates.
*
* @param cameraMatrix The camera matrix
* @param projectionMatrix The projection matrix
* @param viewport The viewport
* @param x The x position
* @param y The y position
* @return The picking ray
*/
public static Ray computePickingRay(
Matrix4f cameraMatrix, Matrix4f projectionMatrix,
Rectangle viewport, int x, int y)
{
int fy = viewport.getHeight() - y;
Point3f p0 = MatrixUtils.unProject(
new Point3f(x,fy,0), cameraMatrix, projectionMatrix, viewport);
Point3f p1 = MatrixUtils.unProject(
new Point3f(x,fy,1), cameraMatrix, projectionMatrix, viewport);
Point3f rayOrigin = p0;
Vector3f rayDirection = new Vector3f();
rayDirection.sub(p1, p0);
rayDirection.normalize();
Ray ray = Rays.create(rayOrigin, rayDirection);
//System.out.println("Ray "+ray);
return ray;
}
Aus MatrixUtils.java:
/**
* Unprojects the given point (in screen coordinates) based on the
* given modelview (camera) and projection matrix and the viewport.
*
* @param point The point
* @param modelview The modelview matrix
* @param projection The projection matrix
* @param viewport The current viewport
* @return The unprojected point
*/
public static Point3f unProject(
Tuple3f point, Matrix4f modelview,
Matrix4f projection, Rectangle viewport)
{
Matrix4f matrix = new Matrix4f();
matrix.mul(projection, modelview);
try
{
matrix.invert();
}
catch (SingularMatrixException e)
{
return new Point3f();
}
Point4f temp = new Point4f(point.x, point.y, point.z, 1);
temp.x = (temp.x - viewport.getX()) / viewport.getWidth();
temp.y = (temp.y - viewport.getY()) / viewport.getHeight();
temp.x = temp.x * 2 - 1;
temp.y = temp.y * 2 - 1;
temp.z = temp.z * 2 - 1;
matrix.transform(temp);
Point3f result = new Point3f(temp.x, temp.y, temp.z);
result.scale(1.0f / temp.w);
return result;
}
(Die beiden Snippets sollten „weitgehend“ standalone sein, abgesehen von den Matrix/Vector-Klassen, die aus https://java.net/projects/vecmath stammen, und solchen „trivialen“ Klassen wie dem „Ray“, der aus einem Punkt und einem Vector besteht).
Nicht mehr standalone ist dann der Teil, wo der Ray dann mit einer „Geometry“ (bzw. ihren Dreiecken) geschnitten wird:
public TrianglePickingResult<T> computePickingResult(Ray ray)
{
Point3f p0 = new Point3f();
Point3f p1 = new Point3f();
Point3f p2 = new Point3f();
Point3f rayOrigin = ray.getOrigin();
Vector3f rayDirection = ray.getDirection();
Point3f result = new Point3f();
Point3f closestResult = new Point3f(0,0,Float.POSITIVE_INFINITY);
Array3f vertices = geometry.getVertices();
IntArray indices = geometry.getIndices();
int closestTriangleIndex = -1;
for (int i = 0; i < indices.getSize()/3; i++)
{
int vi0 = indices.get(i * 3 + 0);
int vi1 = indices.get(i * 3 + 1);
int vi2 = indices.get(i * 3 + 2);
vertices.get3f(vi0, p0);
vertices.get3f(vi1, p1);
vertices.get3f(vi2, p2);
boolean intersect = RayTriangle.intersect(
rayOrigin, rayDirection, p0, p1, p2, result);
if (intersect)
{
if (result.z < closestResult.z)
{
closestResult.set(result);
closestTriangleIndex = i;
}
}
}
if (closestTriangleIndex >= 0)
{
Point3f barycentricCoordinates = new Point3f();
barycentricCoordinates.x = (1-closestResult.x-closestResult.y);
barycentricCoordinates.y = closestResult.x;
barycentricCoordinates.z = closestResult.y;
return PickingResults.create(geometry, closestResult.z,
closestTriangleIndex, barycentricCoordinates);
}
return null;
}
Das „RayTriangle.intersect“ ist dann aber etwas zu viel, um es noch hier zu posten)
Wenn jemand wie @Fancy nichts konkretes dazu sagt, kann ich Mitte/Ende nächster Woche, wenn ich wieder einen Hauch mehr Zeit habe, ggf. nochmal weiterantworten…
[/spoiler]