Dienstag, 19. Januar 2010

Terrain Picking

Riemers Tutorial Serie 4 war für mich der Einstieg in die XNA Welt. Ich arbeitete das Tutorial durch und da hatte ich nun mein Terrain. Super. Aber was nun? Ich hatte vorher einiges mit "Pathfinding" und bewegten Objekten auf Pfaden mir angeschaut, also sollte ich als nächsten Schritt das integrieren.
Was brauchte ich dafür:

  • eine Funktion die mir die entsprechende Höhe zu einem Punkt auf dem Terrain zuückgibt. Damit sollte ich die Objekte auf dem Terrain platzieren und bewegen können. Da das Terrain bereits "Normal"-Werte hat, sollte ein Object mit Hilfer dieser auf dem Terrain justiert werden.  
  • eine Funktion die mir die Punkte die das Triangle bilden, auf das ich gerade klicke, zurückgibt -"Terrain Picking". Was fürn Satz :). Das sollte sich als schwerer herausstellen, als es sich zuerst anhörte... Hier in dem Bild sieht man, wie ich die Kanten des Dreiecks das ich angeklickt habe rot einfärbe...
Fangen wir mit der zweiten Funktion an: Wir übergeben der Funktion die aktuellen Maus-Koordinaten.
Die Funktion gibt uns als Rückgabewert einen Vector3 zurück. Dieser Vector 3 definiert einen der drei Punkte des Dreiecks auf das wir drückten. Ich sollte hier einen Mittelwert aus den 3 Punkten des Dreiecks(Triangel) nehmen, das wäre besser. Aber fürs erste geht es auch so.

private Vector3 PickTerrain(Point point)
{

// Hiflsvariable: speichert die kürzeste bisher gefundene Entfernung
float? closestIntersection = null;

// Hier wird es interresant. Wir bilden einen Strahl von der Mitte der Camera bis zur MausSpitze
// Hier müsst ihr euren CurrentView, oder wie es auch immer genannt habt übergeben...

Ray ray = CalculateCursorRay(camera.mProjection, camera.mView);

// Anstatt jetzt alle einzelnen Triangle des Terrains mit der ObjectMatrix des Strahls zu transformieren,
// transformieren wir den Strahl mit der umgedrehten ObjectMatrix (im Code-> "TerrainWorldMatrix" ;( ) des Terrains.

Matrix inverseTransform = Matrix.Invert(TerrainWorldMatrix);
ray.Position = Vector3.Transform(ray.Position, inverseTransform);
ray.Direction = Vector3.TransformNormal(ray.Direction, inverseTransform);
testedcounter = 0;


// Nun durchlaufen wir in einer Schleife das Index Array (muss "global" gemacht werden, sonst ist es hier nicht sichtbar, genauso wie das Vertices Array), greifen dabei auf das Vertices Array zu (*1-3) und bilden
mit der Eigenschaft "Position" einen neuen Punkt(*1-3).

for (int i = 0; i < terrainIndices.GetLength(0); i += 3)
{
float? intersection = null;
Vector3 p1 = terrainVertices[terrainIndices[i]].Position;  // *1
Vector3 p2 = terrainVertices[terrainIndices[i + 1]].Position; // *2
Vector3 p3 = terrainVertices[terrainIndices[i + 2]].Position; // *3
// Nun erstellen wir aus den drei Punkten ein Dreieck und prüfen ob der Strahl dieses trifft.
// Dazu übergeben wir die drei Punkte und den Strahl der Funktion "RayIntersectsTriangle".
RayIntersectsTriangle(ref ray,
                                   ref p1,
                                   ref p2,
                                   ref p3,
                                   out intersection);
// Die Funktion gibt uns bei einer Kollision die Entfernung zwischen dem Ausgangspunkt des Strahls
// und dem Dreieck zurück. Wir müssen uns die kürzeste Entfernung merken.
// So hier prüfen wir nun ob eine Kollision stattgefunden hat
if (intersection != null)
{
// Wenn das so sein sollte, prüfen wir ob die Kollision näher war als die vermeindlich zu letzt gespeicherte. Vermeindlich deshalb, weil könnte ja auch die erste Kollision sein.

if ((closestIntersection == null)
(intersection < closestIntersection))
{

closestIntersection = intersection;

// Wir geben den ersten der drei Punkte des Dreiecks zurück.
// An der Stelle könnte man auch den Mittelpunkt des Dreiecks berechnen und diesen zurückgeben...

Vector3.Transform(ref p1,
ref TerrainWorldMatrix, out SP1);


}
}
}
return SP1;
}


// Hier noch die zwei "Hilfsfunktionen"
public Ray CalculateCursorRay(Matrix projectionMatrix, Matrix viewMatrix)
{
// create 2 positions in screenspace using the cursor position. 0 is as
// close as possible to the camera, 1 is as far away as possible.
Vector3 nearSource = new Vector3(MouseCurrentPosition.X, MouseCurrentPosition.Y, 0f);
Vector3 farSource = new Vector3(MouseCurrentPosition.X, MouseCurrentPosition.Y, 1f);

// use Viewport.Unproject to tell what those two screen space positions
// would be in world space. we'll need the projection matrix and view
// matrix, which we have saved as member variables. We also need a world
// matrix, which can just be identity.

Vector3 nearPoint = GraphicsDevice.Viewport.Unproject(nearSource, projectionMatrix, viewMatrix, Matrix.Identity);
Vector3 farPoint = GraphicsDevice.Viewport.Unproject(farSource,
projectionMatrix, viewMatrix, Matrix.Identity);

// find the direction vector that goes from the nearPoint to the farPoint
// and normalize it....

Vector3 direction = farPoint - nearPoint;
direction.Normalize();

// and then create a new ray using nearPoint as the source.
return new Ray(nearPoint, direction);
}

// Zweite Hilfsfunktion prüft ob ein Strahl(ray) dein Dreick(gebildet von vertex 1-3) trifft. Gibt bei Kollison die Entfernung zurück.
static void RayIntersectsTriangle(ref Ray ray,ref Vector3 vertex1,ref Vector3 vertex2,ref Vector3 vertex3, out float? result)
{
  // Compute vectors along two edges of the triangle.
  Vector3 edge1, edge2;
  Vector3.Subtract(ref vertex2, ref vertex1, out edge1);
  Vector3.Subtract(ref vertex3, ref vertex1, out edge2);

  // Compute the determinant.
  Vector3 directionCrossEdge2;
  Vector3.Cross(ref ray.Direction, ref edge2, out directionCrossEdge2);
  float determinant;

  Vector3.Dot(ref edge1, ref directionCrossEdge2, out determinant);
  // If the ray is parallel to the triangle plane, there is no collision.

  if (determinant > -float.Epsilon && determinant < float.Epsilon)
  {
    result = null;
    return;
  }
  float inverseDeterminant = 1.0f / determinant;

  // Calculate the U parameter of the intersection point.
  Vector3 distanceVector;
  Vector3.Subtract(ref ray.Position, ref vertex1, out distanceVector);
  float triangleU;
  Vector3.Dot(ref distanceVector, ref directionCrossEdge2, out triangleU);
  triangleU *= inverseDeterminant;
  // Make sure it is inside the triangle.
  if (triangleU < 0
      triangleU > 1)

{
result = null;
return;
}



// Calculate the V parameter of the intersection point.
Vector3 distanceCrossEdge1;
Vector3.Cross(ref distanceVector, ref edge1, out distanceCrossEdge1);
float triangleV;
Vector3.Dot(ref ray.Direction, ref distanceCrossEdge1, out triangleV);
triangleV *= inverseDeterminant;

// Make sure it is inside the triangle.
if (triangleV < 0
triangleU + triangleV > 1)
{
  result = null;
return;
}
// Compute the distance along the ray to the triangle.
float rayDistance;
Vector3.Dot(ref edge2, ref distanceCrossEdge1, out rayDistance);
rayDistance *= inverseDeterminant;
// Is the triangle behind the ray origin?
if (rayDistance < 0)
{
result = null;
return;
}
result = rayDistance;
}
 
So wer noch Fragen hat immer her damit. Ich weiss ist nicht komplett aber ich denke wer soweit ist, kriegt das schon hin, ansonsten bitte Fragen.
 
jdr
//Wenn das so ist speichern wir die Distance

Meine kleine Engine...


hallo,


möchte hier meine aktuelle Spielwiese vorstellen: eim kleines Projekt zu und mit XNA.

Was ist bereits alles integriert, ist meistens die erste Frage die gestellt wird. Ich fang mal an:


  • Terrain Generator / aus BitmapLader nach Lehrbuch von Riemer ( wonach den sonst...)
  • d.h alle Features wie Multitexturierung, Detailmap usw. ist implementiert.
  • deformierbares Terrain
  • Möglichkeit das Terrain zu exportieren / laden (not implemented)
  • sogenanntes Terrainpicking funktioniert
  • Skybox etc.
  • Lensflare Effekt
  • "Skinned Model" mit eigenem Model aus Blender (der komplette Workflow steht hier :)) erstellt, importiert und läuft in XNA
  • Pathfinding
  • Pysikengine (JigLibX, XNAPysiks)
  • Partikelengine (in GPU gehandelt)
  • Partikelengine (Snow, Smoke, Fire, Explosions usw)

Soweit sogut. Nun bin ich an einer Stelle amgekommen, wo es darum geht sich zu entscheiden. Ich würde gerne das neue Diablo 3, jetzt schon in den Händen halten...Selber proggen. Aber nicht allein. Mit ein paar Helfern wäre da bestimmt einiges Möglich...Da kommst du ins Spiel? Ich suche Menschen die einfach Spass haben so ein Projekt zu machen. Wir brauchen einen Plan und Motivation...