From 3c3891d892b74b7a49460e6c900b8665915dc756 Mon Sep 17 00:00:00 2001 From: cmiku Date: Wed, 21 Oct 2026 10:32:52 +0200 Subject: [PATCH] =?UTF-8?q?Perspektivische=20Projektion=20mit=20Tests=20er?= =?UTF-8?q?g=C3=A4nzen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/matrixmania/__init__.py | 2 +- src/matrixmania/compute.py | 26 ++++++++ test/test_matrixmania/test_projection.py | 84 ++++++++++++++++++++---- 3 files changed, 98 insertions(+), 14 deletions(-) diff --git a/src/matrixmania/__init__.py b/src/matrixmania/__init__.py index c6b2355..49b637c 100644 --- a/src/matrixmania/__init__.py +++ b/src/matrixmania/__init__.py @@ -1,3 +1,3 @@ -from .compute import matmul, transpose, rot_2D, rot_3D +from .compute import matmul, transpose, rot_2D, rot_3D, project_perspective # Damit project_ortho direkt aus matrixmania importiert werden kann. from .compute import project_ortho \ No newline at end of file diff --git a/src/matrixmania/compute.py b/src/matrixmania/compute.py index 733c45c..74dda89 100644 --- a/src/matrixmania/compute.py +++ b/src/matrixmania/compute.py @@ -201,7 +201,33 @@ def project_ortho( # x und y skalieren und als Tupel zurückgeben. return (x * scale, y * scale) +def project_perspective( + point: tuple[float, float, float], + focal_length: float = 1.0, + scale: float = 1.0, + camera_distance: float = 4.0, +) -> tuple[float, float]: + """Projiziert einen 3D-Punkt perspektivisch auf eine 2D-Ebene.""" + # Wir verteilen die drei Koordinaten auf einzelne Variablen. + x, y, z = point + + # Die Tiefe des Punktes wird um den Kameraabstand verschoben. + depth = z + camera_distance + + # Durch 0 darf nicht geteilt werden. + # Ein Punkt auf der Kameraebene kann so nicht projiziert werden. + if depth == 0: + raise ValueError( + "Der Punkt darf nicht auf der Kameraebene liegen." + ) + + # Wir berechnen die horizontalen und vertikalen Bildschirmkoordinaten. + projected_x = x * focal_length / depth * scale + projected_y = y * focal_length / depth * scale + + # Aus dem 3D-Punkt wird ein 2D-Punkt. + return projected_x, projected_y if __name__ == "__main__": #führe den testcode nur aus wenn compute.py direkt gestartet wird matrix_a = [ diff --git a/test/test_matrixmania/test_projection.py b/test/test_matrixmania/test_projection.py index 65731c1..0e9a38b 100644 --- a/test/test_matrixmania/test_projection.py +++ b/test/test_matrixmania/test_projection.py @@ -1,30 +1,88 @@ -# Wir importieren die Funktion, die wir testen möchten. -from matrixmania import project_ortho +# pytest hilft uns beim Testen und Vergleichen von Kommazahlen. +import pytest + +# Wir importieren die Funktionen, die wir testen möchten. +from matrixmania import project_ortho, project_perspective def test_project_ortho_without_scaling(): - """Prüft die Projektion mit dem Standardwert scale = 1.""" + """Prüft die orthogonale Projektion mit Standardwerten.""" - # Vorbereitung: einen 3D-Punkt festlegen. + # Arrange: Einen 3D-Punkt vorbereiten. point = (1.0, 2.0, 3.0) - # Aufruf ohne scale: Die Funktion verwendet automatisch 1. + # Act: Ohne zusätzliche Skalierung projizieren. result = project_ortho(point) - # Erwartung: x und y bleiben gleich, z fällt weg. + # Assert: x und y bleiben gleich, z fällt weg. assert result == (1.0, 2.0) def test_project_ortho_with_scaling(): - """Prüft die Projektion mit dem Skalierungsfaktor 100.""" + """Prüft die orthogonale Projektion mit Skalierung 100.""" - # Vorbereitung: denselben 3D-Punkt verwenden. + # Arrange: Einen 3D-Punkt vorbereiten. point = (1.0, 2.0, 3.0) - # Aufruf mit scale = 100. + # Act: Mit Skalierungsfaktor 100 projizieren. result = project_ortho(point, scale=100) - # Erwartung: x und y werden mit 100 multipliziert. - # 1.0 * 100 = 100.0 - # 2.0 * 100 = 200.0 - assert result == (100.0, 200.0) \ No newline at end of file + # Assert: x und y werden jeweils mit 100 multipliziert. + assert result == (100.0, 200.0) + + +def test_project_perspective_without_scaling(): + """Prüft die perspektivische Projektion mit Standardwerten.""" + + # Arrange: Einen 3D-Punkt vorbereiten. + point = (1.0, 2.0, 3.0) + + # Act: Die Standardwerte der Funktion verwenden. + result = project_perspective(point) + + # Assert: Die Tiefe ist z + camera_distance = 3 + 4 = 7. + # Deshalb erwarten wir x = 1/7 und y = 2/7. + assert result == pytest.approx((1 / 7, 2 / 7)) + + +def test_project_perspective_with_focal_length_and_scaling(): + """Prüft die Projektion mit Brennweite 2 und Skalierung 100.""" + + # Arrange: Einen 3D-Punkt vorbereiten. + point = (1.0, 2.0, 3.0) + + # Act: Brennweite 2 und Skalierungsfaktor 100 verwenden. + result = project_perspective(point, focal_length=2, scale=100) + + # Assert: Die Tiefe ist weiterhin 7. + # x = 1 * 2 / 7 * 100 = 200/7. + # y = 2 * 2 / 7 * 100 = 400/7. + assert result == pytest.approx((200 / 7, 400 / 7)) + + +def test_project_perspective_with_custom_camera_distance(): + """Prüft die Projektion mit einem anderen Kameraabstand.""" + + # Arrange: Punkt und erwartetes Ergebnis vorbereiten. + # Die Tiefe ist z + camera_distance = 3 + 1 = 4. + point = (1.0, 2.0, 3.0) + expected = (0.25, 0.5) + + # Act: Den Kameraabstand auf 1 setzen. + result = project_perspective(point, camera_distance=1.0) + + # Assert: x = 1/4 und y = 2/4. + assert result == pytest.approx(expected) + + +def test_project_perspective_with_zero_scale(): + """Prüft die Projektion mit Skalierungsfaktor 0.""" + + # Arrange: Einen gültigen 3D-Punkt vorbereiten. + point = (1.0, 2.0, 3.0) + + # Act: Den Skalierungsfaktor auf 0 setzen. + result = project_perspective(point, scale=0.0) + + # Assert: Beide Koordinaten werden mit 0 multipliziert. + assert result == pytest.approx((0.0, 0.0)) \ No newline at end of file