Einzelnen Beitrag anzeigen

SMO

Registriert seit: 20. Jul 2005
178 Beiträge
 
Delphi XE6 Professional
 
#1

Unit System.Math.Vectors

  Alt 28. Feb 2015, 18:32
Delphi-Version: XE6
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:
  1. Roll = Drehung um Z-Achse
  2. Pitch = Drehung um X-Achse
  3. 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;

Geändert von SMO (28. Feb 2015 um 21:09 Uhr)
  Mit Zitat antworten Zitat