PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Direct3D: Zylinder rotieren dass er zwei Punkte verbindet


MrCompton
2010-10-14, 13:49:03
Hallo,
ich habe folgendes Problem: In der dreidimensionalen Szene in meinem C#-Programm ist ein Zylinder, der so gedreht werden muss, dass er zwei Punkte verbindet. Der Zylinder ist genau so lang, wie die Entfernung der Punkte voneinander.

Beispiel:
Punkt A (0,0,0)
Punkt B (1,1,1)
Länge des Zylinders: sqrt(3)
Lage des Zylinders: ein Ende liegt im Ursprung, das andere bei (sqrt(3), 0,0)

Den Zylinder zuerst an der z-Achse und anschließend an der y-Achse jeweils um 45° zu drehen funktioniert nicht. Hier der entsprechende Code:
zylinderobjekt.TransformationMatrix = Matrix.RotationZ((float)Math.PI/4) * Matrix.RotationY((float)Math.PI/4)

Hat jemand eine Idee wie man den Zylinder nun rotieren muss bzw. wie sich die Transformationsmatrix berechnen lässt?

Neomi
2010-10-14, 15:35:09
Dein Zylinder liegt entlang der X-Achse, deshalb mußt du zuerst die X-Achse ausrichten. Du nimmst dazu die Richtung von Punkt A zu Punkt B und normierst diese. Dann generierst du daraus die anderen Achsen, alle drei stehen jeweils orthogonal zueinander. Also nimmst du einen beliebigen Vektor, der nicht parallel zur berechneten X-Achse ist, und bildest per normiertem Kreuzprodukt aus der X-Achse und diesem Vektor eine weitere Achse. Meist nimmt man da den globalen Up-Vektor (0, 1, 0), aber hier muß man eben aufpassen. Hat das Kreuzprodukt eine Länge von 0 (das passiert, wenn die beiden Vektoren parallel sind), nimmst du einen anderen. Wenn du erstmal zwei Achsen hast, bildest du die dritte wieder per Kreuzprodukt.

Vector3 XAxis = Normalize(B - A);
Vector3 ZAxix = Normalize(CrossProd(XAxis, Vector3(0.0f, 1.0f, 0.0f)));
Vector3 YAxis = Normalize(CrossProd(ZAxis, XAxis));

Ist zwar jetzt wohl nicht 1:1 der C#-Code, den du brauchen wirst, dürfte dir aber trotzdem helfen. Die drei Achsen entsprechen einer 3x3-Matrix, die zusammenzubauen oder eine 4x4-Matrix daraus zu basteln dürfte kein Problem bereiten. Eine Normierung eines Kreuzproduktes zwischen zwei orthogonalen Vektoren der Länge 1 (3. Zeile im Code) ist eigentlich mathematisch unnötig, da das Kreuzprodukt bereits die Länge 1 hat. Da es aber Rundungsfehler bei Fließkommaoperationen gibt, macht man das trotzdem, solange es nicht auf maximale Performance ankommt. In welcher Reihenfolge die Achsen per Kreuzprodukt verrechnet werden, ist nicht egal, das Beispiel gilt für ein linkshändiges Koordinatensystem (bei D3D üblich).

ScottManDeath
2010-10-14, 19:20:35
Folgender C++ Code macht das Gleiche was Neomi geschrieben hat, jedoch wird der "up" Vektor automatisch aus dem Eingangsvektor v1 abgeleitet, und damit werden dann v2 und v3 bestimmt.

Die Vektoren v1, v2 und v3 entsprechen dann den Zeilen/Spalten der Transformationsmatrix.


template < typenameT>
void OrthonormalBasis(const Vector3<T>& v1, Vector3<T>& v2, Vector3<T>& v3)
{

if( (abs(v1.x) > abs(v1.y)) && (abs(v1.x) > abs(v1.z)))
{
T inv_length = 1 / sqrt(v1.x * v1.x + v1.z * v1.z);
v2 = Vector3<T>(-v1.z * inv_length,0, v1.x * inv_length);
}
else
{
T inv_length = 1 / sqrt(v1.y * v1.y + v1.z * v1.z);
v2 = Vector3<T>(0,v1.z * inv_length,-v1.y * inv_length);
}
v3 = Cross(v1,v2);
}

MrCompton
2010-10-15, 11:08:34
Danke euch beiden für eure schnellen Antworten!
Ich habe beide Varianten ausprobiert, aber irgendwie funktioniert das nicht so ganz und bei beiden kommen unterschiedliche Ergebnisse raus...

Könntet ihr mal bitte im Programmcode schauen, was ich da falsch gemacht habe?
Kleine Anmerkung: Zur besseren Auswertung habe ich keinen Zylinder verschoben, sondern dessen nicht im Ursprung liegendes Ende transformiert (TransformCoordinate-Aufruf am Ende). Bei der Variante von Neomi liegt er am Ende in (1,-0.71, -1.22), bei der von ScottManDeat in (1, 0, -1.41)


Vector3 a = new Vector3(0, 0, 0);
Vector3 b = new Vector3(1, 1, 1);
Vector3 zylinderEnd = new Vector3(b.Length(), 0, 0);
Vector3 XAxis, YAxis, ZAxis;
Matrix m = Matrix.Identity;

XAxis = Vector3.Normalize(b - a);

// Variante von Neomi
ZAxis = Vector3.Normalize(CrossProd(XAxis, new Vector3(0.0f, 1.0f, 0.0f)));
YAxis = Vector3.Normalize(CrossProd(ZAxis, XAxis));
m.M11 = XAxis.X; m.M12 = YAxis.X; m.M13 = ZAxis.X;
m.M21 = XAxis.Y; m.M22 = YAxis.Y; m.M23 = ZAxis.Y;
m.M31 = XAxis.Z; m.M32 = YAxis.Z; m.M33 = ZAxis.Z;

// Variante von ScottManDeath
OrthonormalBasis(XAxis, out YAxis, out ZAxis);
m.M11 = XAxis.X; m.M12 = YAxis.X; m.M13 = ZAxis.X;
m.M21 = XAxis.Y; m.M22 = YAxis.Y; m.M23 = ZAxis.Y;
m.M31 = XAxis.Z; m.M32 = YAxis.Z; m.M33 = ZAxis.Z;

zylinderEnd = Vector3.TransformCoordinate(zylinderEnd, m);

private void OrthonormalBasis(Vector3 v1, out Vector3 v2, out Vector3 v3)
{
if ((Math.Abs(v1.X) > Math.Abs(v1.Y)) && (Math.Abs(v1.X) > Math.Abs(v1.Z)))
{
float inv_length = 1 / (float)Math.Sqrt(v1.X * v1.X + v1.Z * v1.Z);
v2 = new Vector3(-v1.Z * inv_length, 0, v1.X * inv_length);
}
else
{
float inv_length = 1 / (float)Math.Sqrt(v1.Y * v1.Y + v1.Z * v1.Z);
v2 = new Vector3(0, v1.Z * inv_length, -v1.Y * inv_length);
}
v3 = Vector3.Cross(v1, v2);
}

Neomi
2010-10-15, 13:31:21
Bei einem linkshändigen Koordinatensystem stehen die Achsen einer Transformationsmatrix in den Zeilen, nicht in den Spalten.

// falsch
m.M11 = XAxis.X; m.M12 = YAxis.X; m.M13 = ZAxis.X;
m.M21 = XAxis.Y; m.M22 = YAxis.Y; m.M23 = ZAxis.Y;
m.M31 = XAxis.Z; m.M32 = YAxis.Z; m.M33 = ZAxis.Z;

// richtig
m.M11 = XAxis.X; m.M12 = XAxis.Y; m.M13 = XAxis.Z;
m.M21 = YAxis.X; m.M22 = YAxis.Y; m.M23 = YAxis.Z;
m.M31 = ZAxis.X; m.M32 = ZAxis.Y; m.M33 = ZAxis.Z;

Versuch es mal mit der unteren Variante, deine war transponiert abgelegt. Die Column Major Anordnung (Spaltenwerte gruppiert) wird erst dann relevant, wenn du Matrizen in Vertexshader-Konstanten haben willst.

MrCompton
2010-10-15, 14:37:22
Und kaum macht man es richtig, funktioniert es ;-)

Danke nochmal für die Hilfe!

Gast
2010-10-15, 23:19:00
zeig mal ein screenshot davon :)

kann zwat programmieren, aber d3d und so grafikzeug sind für mich ein rotes tuch. lieber ein windows dienst proggen ohne grafik ;)