diff --git a/src/matrixmania/compute.py b/src/matrixmania/compute.py index 2757397..ea84c3f 100644 --- a/src/matrixmania/compute.py +++ b/src/matrixmania/compute.py @@ -120,6 +120,64 @@ def matmul(A: Matrix, B: Matrix) -> Matrix: return result +def rot_3D(angle: float, axis: str) -> list[list[float]]: + """Berechnet die 3D-Rotationsmatrix für einen gegebenen Winkel und eine Achse. + + Die Rotationsmatrizen lauten: + + Rotation um die x-Achse: + R_x(θ) = [[1, 0, 0], + [0, cos(θ), -sin(θ)], + [0, sin(θ), cos(θ)]] + + Rotation um die y-Achse: + R_y(θ) = [[ cos(θ), 0, sin(θ)], + [ 0, 1, 0], + [-sin(θ), 0, cos(θ)]] + + Rotation um die z-Achse: + R_z(θ) = [[cos(θ), -sin(θ), 0], + [sin(θ), cos(θ), 0], + [ 0, 0, 1]] + + Args: + angle (float): Der Rotationswinkel in Bogenmaß (Radians). + axis (str): Die Rotationsachse ('x', 'y' oder 'z'). + + Returns: + list[list[float]]: Die 3x3-Rotationsmatrix als geschachtelte Liste. + + Raises: + ValueError: Wenn ein ungültiger Achsenwert übergeben wird. + """ + cos_a = math.cos(angle) + sin_a = math.sin(angle) + + if axis == "x": + return [[1.0, 0.0, 0.0], [0.0, cos_a, -sin_a], [0.0, sin_a, cos_a]] + elif axis == "y": + return [[cos_a, 0.0, sin_a], [0.0, 1.0, 0.0], [-sin_a, 0.0, cos_a]] + elif axis == "z": + return [[cos_a, -sin_a, 0.0], [sin_a, cos_a, 0.0], [0.0, 0.0, 1.0]] + else: + raise ValueError( + f"Ungültige Achse '{axis}'. Gültige Achsen sind 'x', 'y' oder 'z'." + ) + +def project_ortho(point: tuple[float, float, float], scale: float = 1) -> tuple[float, float]: + """Projiziert einen 3D-Punkt (x, y, z) orthogonal auf eine 2D-Fläche (x, y), + indem die z-Koordinate ignoriert und der Punkt mit scale multipliziert wird. + + Args: + point (tuple[float, float, float]): Der 3D-Punkt als (x, y, z). + scale (float, optional): Skalierungsfaktor für die Ausgabe. Standard ist 1. + + Returns: + tuple[float, float]: Der 2D-Punkt als (x * scale, y * scale). + """ + x, y, _ = point + return (x * scale, y * scale) + if __name__ == "__main__": # Test für rot_2D (z. B. Rotation um 90 Grad = pi / 2) theta = math.pi / 2 diff --git a/test/test_matrixmania/__init__.py b/test/test_matrixmania/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_matrixmania/test_projection.py b/test/test_matrixmania/test_projection.py new file mode 100644 index 0000000..7e801cb --- /dev/null +++ b/test/test_matrixmania/test_projection.py @@ -0,0 +1,16 @@ +import pytest +from matrixmania.compute import project_ortho + + +def test_project_ortho_default_scale(): + # Test ohne Angabe von scale (Standardwert = 1) + point = (1.0, 2.0, 3.0) + result = project_ortho(point) + assert result == (1.0, 2.0) + + +def test_project_ortho_with_scale(): + # Test mit scale = 100 + point = (1.0, 2.0, 3.0) + result = project_ortho(point, scale=100) + assert result == (100.0, 200.0) \ No newline at end of file diff --git a/test/test_matrixmania/test_rot.py b/test/test_matrixmania/test_rot.py new file mode 100644 index 0000000..1e25c6c --- /dev/null +++ b/test/test_matrixmania/test_rot.py @@ -0,0 +1,32 @@ +import math +import pytest +from matrixmania.compute import rot_3D + + +def test_rot_3D_x_axis(): + # Test bei 0 Radian (Identitätsmatrix) + res = rot_3D(0.0, "x") + assert res == [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] + + # Test bei 90 Grad (PI / 2) + res_90 = rot_3D(math.pi / 2, "x") + assert pytest.approx(res_90[1][1]) == 0.0 + assert pytest.approx(res_90[1][2]) == -1.0 + assert pytest.approx(res_90[2][1]) == 1.0 + assert pytest.approx(res_90[2][2]) == 0.0 + + +def test_rot_3D_y_axis(): + res = rot_3D(0.0, "y") + assert res == [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] + + +def test_rot_3D_z_axis(): + res = rot_3D(0.0, "z") + assert res == [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] + + +def test_rot_3D_invalid_axis(): + # Prüft, ob bei ungültiger Achse ein ValueError geworfen wird + with pytest.raises(ValueError): + rot_3D(1.0, "w") \ No newline at end of file