diff --git a/np.py b/np.py new file mode 100644 index 0000000..e18ba67 --- /dev/null +++ b/np.py @@ -0,0 +1,199 @@ +import numpy as np +import time +import math + +# --- Hilfsfunktionen --- + +def normalize(v): + """ Normalisiert einen Vektor (macht seine Länge zu 1). """ + norm = np.linalg.norm(v) + if norm == 0: + return v + return v / norm + +def look_at_matrix(eye, target, up): + """ + Erzeugt eine View-Matrix, die Weltkoordinaten in Kamerakoordinaten transformiert. + :param eye: Position der Kamera (np.array). + :param target: Punkt, auf den die Kamera schaut (np.array). + :param up: Up-Vektor der Kamera (zeigt, wo "oben" für die Kamera ist, np.array). + :return: 4x4 View-Matrix (NumPy-Array). + """ + # Kamerakoordinatensystem berechnen + # z-Achse der Kamera (schaut oft *entgegen* der Blickrichtung) + forward = normalize(eye - target) # Konvention: Kamera schaut entlang -Z + if np.linalg.norm(np.cross(up, forward)) < 1e-6: + # Fallback, wenn 'up' und 'forward' (fast) parallel sind + # Wähle einen anderen temporären Up-Vektor + if abs(forward[1]) < 0.99: # Wenn forward nicht fast vertikal ist + temp_up = np.array([0.0, 1.0, 0.0]) + else: # Wenn forward fast vertikal ist + temp_up = np.array([0.0, 0.0, -1.0 if forward[1] > 0 else 1.0]) + right = normalize(np.cross(temp_up, forward)) + else: + right = normalize(np.cross(up, forward)) + + # Den tatsächlichen Up-Vektor der Kamera neu berechnen, damit er orthogonal ist + camera_up = normalize(np.cross(forward, right)) + + # Rotationsmatrix erstellen (transformiert von Welt zu Kamera-Ausrichtung) + # Dies ist die Inverse (oder Transponierte, da Rotationsmatrix) der Matrix, + # die die Kamera-Basisvektoren in Weltkoordinaten ausdrückt. + rotation = np.identity(4) + rotation[0, 0:3] = right + rotation[1, 0:3] = camera_up + rotation[2, 0:3] = forward # Beachte: forward zeigt von target zu eye + + # Translationsmatrix erstellen (verschiebt die Welt, sodass die Kamera am Ursprung ist) + translation = np.identity(4) + translation[0:3, 3] = -eye + + # View-Matrix: Zuerst Translation, dann Rotation + view_mat = np.dot(rotation, translation) + return view_mat + +def perspective_projection(point_cam_space, fov_deg, aspect_ratio, near, far): + """ + Projiziert einen Punkt aus dem Kamera-Koordinatenraum auf eine 2D-Ebene. + Gibt normalisierte Gerätekoordinaten (NDC) zurück (-1 bis +1). + :param point_cam_space: Punkt in Kamera-Koordinaten (3D NumPy-Array). + :param fov_deg: Field of View (vertikal) in Grad. + :param aspect_ratio: Seitenverhältnis (Breite / Höhe) der Bildebene. + :param near: Nahe Clipping-Ebene. + :param far: Ferne Clipping-Ebene. + :return: Projizierter 2D-Punkt in NDC (NumPy-Array) oder None, wenn außerhalb. + """ + # Überprüfen, ob der Punkt vor der Kamera und innerhalb der Clipping-Ebenen liegt + # Kamera schaut entlang -Z, also muss Z negativ sein. + if point_cam_space[2] > -near or point_cam_space[2] < -far: + return None # Außerhalb des Sichtfelds (vor near oder hinter far) + + # Einfache Perspektivprojektion (vereinfachte Formel) + # Skalierungsfaktor basierend auf FOV + f = 1.0 / math.tan(math.radians(fov_deg) / 2.0) + + # Projizierte Koordinaten auf der Bildebene (vor Normalisierung) + # Annahme: Bildebene bei z = -near (oder -1, je nach Konvention) + # Hier verwenden wir die gebräuchlichere Projektionsmatrix-Formel-Basis: + x_proj = (f / aspect_ratio) * point_cam_space[0] / -point_cam_space[2] + y_proj = f * point_cam_space[1] / -point_cam_space[2] + + # Normalisierte Gerätekoordinaten (NDC) sind typischerweise im Bereich [-1, 1] + # Hier geben wir die projizierten Koordinaten zurück, wie sie auf einer Bildebene + # bei z=-1 erscheinen würden, skaliert durch FOV und Aspekt. + # Eine vollständige Projektionsmatrix würde auch Z für Tiefentests umwandeln. + return np.array([x_proj, y_proj]) + + +# --- Simulationsparameter --- +object_start_pos = np.array([0.0, 0.0, 10.0]) # Startposition des Objekts (x, y, z) +# Einfache lineare Bewegung +# object_velocity = np.array([1.0, 0.5, -0.8]) # Bewegung pro Sekunde +# Alternative: Kreisbewegung in der XZ-Ebene um (0, 0, 10) mit Radius 5 +radius = 5.0 +angular_speed = math.radians(45) # 45 Grad pro Sekunde + +# Kameraeinstellungen +# Jede Kamera hat eine Position ('pos'), einen Punkt, auf den sie schaut ('target'), +# und einen 'up'-Vektor, der die Orientierung definiert. +cameras = [ + { + "name": "Kamera 1 (Frontal)", + "pos": np.array([0.0, 0.0, 0.0]), + "target": object_start_pos, # Schaut initial auf den Startpunkt + "up": np.array([0.0, 1.0, 0.0]), # Standard-Ausrichtung (Y ist oben) + "fov_deg": 60, # Field of View in Grad + "aspect_ratio": 16.0 / 9.0 # Typisches Breitbild + }, + { + "name": "Kamera 2 (Seitlich Links)", + "pos": np.array([-15.0, 0.0, 5.0]), + "target": object_start_pos, + "up": np.array([0.0, 1.0, 0.0]), + "fov_deg": 45, + "aspect_ratio": 1.0 # Quadratisch + }, + { + "name": "Kamera 3 (Von Oben)", + "pos": np.array([0.0, 15.0, 10.0]), + "target": object_start_pos, + "up": np.array([0.0, 0.0, -1.0]), # Z zeigt nach unten, da Y oben war + "fov_deg": 70, + "aspect_ratio": 4.0 / 3.0 + } +] + +# Clipping-Ebenen (gemeinsam für alle Kameras hier, könnte pro Kamera sein) +near_plane = 0.1 +far_plane = 100.0 + +simulation_duration = 10 # Sekunden +time_step = 1 # Zeitintervall für "Aufnahmen" in Sekunden + +# --- Simulationsschleife --- +current_object_pos = object_start_pos.copy() +current_time = 0.0 +angle = 0.0 # Für Kreisbewegung + +print("Starte Simulation...") + +while current_time <= simulation_duration: + print(f"\n--- Zeitpunkt: {current_time:.1f}s ---") + + # Objektposition aktualisieren (Beispiel: Kreisbewegung) + angle = angular_speed * current_time + current_object_pos[0] = radius * math.cos(angle) + 0 # Kreiszentrum X=0 + current_object_pos[2] = radius * math.sin(angle) + 10 # Kreiszentrum Z=10 + # Alternative: Lineare Bewegung + # current_object_pos = object_start_pos + object_velocity * current_time + + print(f"Objekt Welt-Position: ({current_object_pos[0]:.2f}, {current_object_pos[1]:.2f}, {current_object_pos[2]:.2f})") + + # Objektposition in homogene Koordinaten umwandeln (für Matrixmultiplikation) + object_pos_h = np.append(current_object_pos, 1.0) + + # Für jede Kamera die Ansicht berechnen + for i, cam in enumerate(cameras): + print(f"\n Kamera {i+1}: {cam['name']}") + print(f" Position: ({cam['pos'][0]:.2f}, {cam['pos'][1]:.2f}, {cam['pos'][2]:.2f})") + # Kamera schaut immer auf die aktuelle Objektposition + current_target = current_object_pos + print(f" Schaut auf (Target): ({current_target[0]:.2f}, {current_target[1]:.2f}, {current_target[2]:.2f})") + print(f" Up-Vektor: ({cam['up'][0]:.2f}, {cam['up'][1]:.2f}, {cam['up'][2]:.2f})") + print(f" FOV: {cam['fov_deg']} Grad, Aspekt: {cam['aspect_ratio']:.2f}") + + + # View-Matrix berechnen + view_mat = look_at_matrix(cam['pos'], current_target, cam['up']) + + # Objektposition in Kamera-Koordinaten transformieren + # point_in_cam_space_h = view_mat @ object_pos_h # Python 3.5+ für @ Operator + point_in_cam_space_h = np.dot(view_mat, object_pos_h) + + # De-homogenisieren (falls w nicht 1 ist, hier sollte es 1 sein nach View-Transform) + point_in_cam_space = point_in_cam_space_h[:3] / point_in_cam_space_h[3] + + print(f" Objekt in Kamera-Koordinaten: ({point_in_cam_space[0]:.2f}, {point_in_cam_space[1]:.2f}, {point_in_cam_space[2]:.2f})") + + # In 2D projizieren + projected_point_ndc = perspective_projection( + point_in_cam_space, + cam['fov_deg'], + cam['aspect_ratio'], + near_plane, + far_plane + ) + + if projected_point_ndc is not None: + print(f" Projizierte 2D-Koordinaten (NDC): ({projected_point_ndc[0]:.3f}, {projected_point_ndc[1]:.3f})") + # NDC-Koordinaten (-1 bis 1) müssten noch auf Pixelkoordinaten umgerechnet werden, + # wenn man ein echtes Bild rendern wollte (z.B. X_pixel = (X_ndc + 1) * width / 2). + else: + print(" Objekt befindet sich ausserhalb des Sichtbereichs (Clipping).") + + # Zum nächsten Zeitschritt gehen + current_time += time_step + if current_time <= simulation_duration: + time.sleep(1) # Kurze Pause zur besseren Lesbarkeit der Ausgabe + +print("\nSimulation beendet.") \ No newline at end of file