Hallo,
kennt ihr die
Unit System.Math.Vectors? Ich weiß nicht, in welcher Delphi-Version sie dazugekommen ist, aber ich habe sie erst vor kurzem entdeckt.
Die
Unit enthält eine nette kleine Sammlung von Records mit Methoden für 2D und 3D Vektoren, Matrizen und Quaternionen.
Ich wollte mich schon länger mal in die Grundlagen von 3D-Grafik einarbeiten, da kam diese
Unit gerade richtig.
Allerdings glaube ich ein paar Fehler in der
Unit gefunden zu haben! Ich würde deshalb gerne dazu die Meinung von Leuten hören, die sich mit der Materie auskennen.
Zunächst mal: Die
Unit scheint sich an Microsoft
DirectX / .NET zu orientieren. Zu
OpenGL gibt es da kleine Unterschiede.
Erster Punkt: TMatrix3D.CreateOrthoLH sowie die rechtshändige Version CreateOrthoRH haben mit ziemlicher Sicherheit einen Bug.
Delphi-Quellcode:
class function TMatrix3D.CreateOrthoLH(const AWidth, AHeight, AZNear, AZFar: Single): TMatrix3D;
begin
Result := TMatrix3D.Identity;
Result.m11 := 2 / AWidth;
Result.m22 := 2 / AHeight;
Result.m33 := 1 / (AZFar - AZNear);
Result.m42 := AZNear / (AZNear - AZFar);
end;
Im letzten Befehl wird das Matrixelement m42 befüllt, aber es müsste meiner Meinung nach
m43 sein.
Siehe Microsoft sowie die Funktion CreateOrthoOffCenterLH in der
Unit, praktisch eine Verallgemeinerung von CreateOrthoLH:
Delphi-Quellcode:
class function TMatrix3D.CreateOrthoOffCenterLH(const ALeft, ATop, ARight, ABottom, AZNear, AZFar: Single): TMatrix3D;
begin
Result := TMatrix3D.Identity;
Result.m11 := 2 / (ARight - ALeft);
Result.m22 := 2 / (ATop - ABottom);
Result.m33 := 1 / (AZFar - AZNear);
Result.m41 := (ALeft + ARight) / (ALeft - ARight);
Result.m42 := (ATop + ABottom) / (ABottom - ATop);
Result.m43 := AZNear / (AZNear - AZFar);
end;
Zweiter Punkt: TMatrix3D.CreateRotationYawPitchRoll
Rotationen sind etwas kompliziert, die Reihenfolge, in welcher um die drei Achsen rotiert wird, macht einen Unterschied (Matrixmultiplikation ist nicht kommutativ).
MSDN sagt zu RotationYawPitchRoll:
- Roll = Drehung um Z-Achse
- Pitch = Drehung um X-Achse
- Yaw = Drehung um Y-Achse
Das entspricht der gängingen "YXZ" Konvention, d.h. ich würde erwarten, dass gilt:
TMatrix3D.CreateRotationYawPitchRoll(Yaw, Pitch, Roll) = TMatrix3D.CreateRotationZ(Roll) * TMatrix3D.CreateRotationX(Pitch) * TMatrix3D.CreateRotationY(Yaw)
oder wenn von "rechts nach links" multipliziert wird:
TMatrix3D.CreateRotationYawPitchRoll(Yaw, Pitch, Roll) = TMatrix3D.CreateRotationY(Yaw) * TMatrix3D.CreateRotationX(Pitch) * TMatrix3D.CreateRotationZ(Roll)
Dem ist aber nicht so! Stattdessen bekomme ich:
TMatrix3D.CreateRotationYawPitchRoll(Yaw, Pitch, Roll) = TMatrix3D.CreateRotationY(Roll) * TMatrix3D.CreateRotationX(Pitch) * TMatrix3D.CreateRotationZ(-Yaw)
Was ist da denn los? Die Reihenfolge stimmt (YXZ Konvention). Aber Yaw und Roll sind den falschen Achsen zugewiesen! Oder hat jemand eine andere Erklärung dafür?
Testcode:
Delphi-Quellcode:
uses
System.Math.Vectors;
function MatrixEqual(const M1, M2: TMatrix3D): Boolean;
var
i: Integer;
begin
Result := True;
// Leider ist kein TMatrix3D.Equal Operator definiert. Also reduziere das Problem auf
// TVector3D.Equal und vergleiche die Komponentenvektoren beider Matrizen
for i := Low(M1.M) to High(M1.M) do
Result := Result and (M1.M[i] = M2.M[i]);
end;
procedure RotTest;
const
B2S: array [Boolean] of string = ('false', 'TRUE!');
var
M1, M2, M3, M4: TMatrix3D;
Yaw, Pitch, Roll: Single;
s: string;
begin
Yaw := 1.0;
Pitch := 2.0;
Roll := 3.0;
M1 := TMatrix3D.CreateRotationYawPitchRoll(Yaw, Pitch, Roll);
M2 := TMatrix3D.CreateRotationZ(Roll) * TMatrix3D.CreateRotationX(Pitch) * TMatrix3D.CreateRotationY(Yaw);
M3 := TMatrix3D.CreateRotationY(Yaw) * TMatrix3D.CreateRotationX(Pitch) * TMatrix3D.CreateRotationZ(Roll);
M4 := TMatrix3D.CreateRotationY(Roll) * TMatrix3D.CreateRotationX(Pitch) * TMatrix3D.CreateRotationZ(-Yaw);
s := '';
s := s + 'M1 (YPR) = M2 (RzPxYy): ' + B2S[MatrixEqual(M1, M2)] + sLineBreak;
s := s + 'M1 (YPR) = M3 (YyPxRz): ' + B2S[MatrixEqual(M1, M3)] + sLineBreak;
s := s + 'M1 (YPR) = M4 (RyPx-Yz): ' + B2S[MatrixEqual(M1, M4)] + sLineBreak;
ShowMessage(s);
// Ausgabe:
// M1 (YPR) = M2 (RzPxYy): false
// M1 (YPR) = M3 (YyPxRz): false
// M1 (YPR) = M4 (RyPx-Yz): TRUE!
end;