diff --git a/np.py b/np.py index 8092c9f..298653d 100644 --- a/np.py +++ b/np.py @@ -1,16 +1,17 @@ import numpy as np import time import math -import os # Importiert für die Verzeichniserstellung -from PIL import Image, ImageDraw # Importiert für die Bildverarbeitung +import os +from PIL import Image, ImageDraw +import random # Importiert für Zufallszahlen # --- Hilfsfunktionen --- def normalize(v): """ Normalisiert einen Vektor (macht seine Länge zu 1). """ norm = np.linalg.norm(v) - if norm == 0: - return v + if norm < 1e-9: # Sicherer Vergleich mit kleiner Toleranz + return np.zeros_like(v) # Gibt Nullvektor zurück, wenn Norm zu klein ist return v / norm def look_at_matrix(eye, target, up): @@ -21,24 +22,27 @@ def look_at_matrix(eye, target, up): :param up: Up-Vektor der Kamera (zeigt, wo "oben" für die Kamera ist, np.array). :return: 4x4 View-Matrix (NumPy-Array). """ - # Kamerakoordinatensystem berechnen + # Robustere Berechnung, falls eye und target sehr nah sind + if np.linalg.norm(eye - target) < 1e-6: + # Wenn Kamera und Ziel fast am selben Ort sind, ist die Richtung undefiniert. + # Wähle eine Standardrichtung, z.B. Blick entlang -Z der Welt vom 'eye'. + target = eye - np.array([0.0, 0.0, 1.0]) + forward = normalize(eye - target) # Konvention: Kamera schaut entlang -Z - if np.linalg.norm(forward) < 1e-9: # Verhindert Division durch Null oder NaN, wenn eye==target - # Wenn Kamera und Ziel am selben Ort sind, ist die Richtung undefiniert. - # Wähle eine Standardrichtung, z.B. entlang -Z der Welt. - forward = np.array([0.0, 0.0, 1.0]) # Prüfe, ob 'up' und 'forward' (fast) parallel sind - if abs(np.dot(normalize(up), forward)) > 0.999: + # Normalisiere 'up' vor dem Test für mehr Robustheit + up_normalized = normalize(up) + if abs(np.dot(up_normalized, forward)) > 0.999: # 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)) + # Nutze das normalisierte 'up' für die Berechnung + right = normalize(np.cross(up_normalized, forward)) # Den tatsächlichen Up-Vektor der Kamera neu berechnen, damit er orthogonal ist camera_up = normalize(np.cross(forward, right)) @@ -73,7 +77,6 @@ def perspective_projection(point_cam_space, fov_deg, aspect_ratio, near, far): return None # Außerhalb des Sichtfelds (vor near oder hinter far) # Verhindert Division durch Null, falls Punkt genau auf der Kameraebene liegt (z=0) - # Obwohl das Clipping oben dies meist abfängt, ist es eine zusätzliche Sicherheit. if abs(point_cam_space[2]) < 1e-9: return None @@ -85,47 +88,43 @@ def perspective_projection(point_cam_space, fov_deg, aspect_ratio, near, far): y_ndc = f * point_cam_space[1] / -point_cam_space[2] # Überprüfen, ob die projizierten Koordinaten innerhalb des NDC-Bereichs [-1, 1] liegen - # Dies ist ein zusätzlicher Clipping-Schritt für die XY-Ebene if not (-1 <= x_ndc <= 1 and -1 <= y_ndc <= 1): return None # Außerhalb des seitlichen Sichtfelds return np.array([x_ndc, y_ndc]) -# --- NEU: Funktion zur Umwandlung von NDC in Pixelkoordinaten --- def ndc_to_pixel(ndc_coords, width, height): """ Wandelt NDC-Koordinaten (-1 bis 1) in Pixelkoordinaten (0 bis width/height) um. """ - # x_ndc = -1 -> pixel_x = 0 - # x_ndc = +1 -> pixel_x = width pixel_x = (ndc_coords[0] + 1.0) / 2.0 * width - # y_ndc = -1 -> pixel_y = height (unten im Bild) - # y_ndc = +1 -> pixel_y = 0 (oben im Bild) - # Wir müssen die Y-Koordinate invertieren, da NDC +Y oben ist, Pixel +Y aber unten. + # Y-Koordinate invertieren (NDC +Y ist oben, Pixel +Y ist unten) pixel_y = (1.0 - ndc_coords[1]) / 2.0 * height - - # In Integer umwandeln und sicherstellen, dass sie innerhalb der Grenzen liegen px = int(round(pixel_x)) py = int(round(pixel_y)) - - # Clipping auf Bildgrenzen (obwohl NDC-Clipping dies meist unnötig macht) + # Clipping auf Bildgrenzen px = max(0, min(width - 1, px)) py = max(0, min(height - 1, py)) - return px, py # --- Simulationsparameter --- object_start_pos = np.array([0.0, 0.0, 10.0]) # Startposition des Objekts (x, y, z) -radius = 5.0 -angular_speed = math.radians(45) # 45 Grad pro Sekunde -# Kameraeinstellungen +# --- NEU: Parameter für zufällige Bewegung --- +# Einfacher Random Walk: In jedem Schritt eine zufällige Verschiebung +random_step_scale = 0.8 # Maximale Verschiebung pro Achse pro Zeitschritt +# Optional: Begrenzung des Bewegungsraums (Bounding Box) +# Damit das Objekt nicht zu weit wegdriftet +world_bounds_min = np.array([-10.0, -5.0, 5.0]) +world_bounds_max = np.array([ 10.0, 5.0, 15.0]) + + +# Kameraeinstellungen (Namen ohne Leerzeichen für Dateinamen angepasst) cameras = [ { - "name": "Kamera_1_Frontal", # Namen ohne Leerzeichen für Dateinamen + "name": "Kamera_1_Frontal", "pos": np.array([0.0, 0.0, 0.0]), - "target": object_start_pos, + "target": object_start_pos, # Schaut initial auf den Startpunkt "up": np.array([0.0, 1.0, 0.0]), "fov_deg": 60, - "aspect_ratio": 16.0 / 9.0 }, { "name": "Kamera_2_Seitlich_Links", @@ -133,7 +132,6 @@ cameras = [ "target": object_start_pos, "up": np.array([0.0, 1.0, 0.0]), "fov_deg": 45, - "aspect_ratio": 1.0 }, { "name": "Kamera_3_Von_Oben", @@ -141,7 +139,6 @@ cameras = [ "target": object_start_pos, "up": np.array([0.0, 0.0, -1.0]), # Blickrichtung -Y, Z nach unten ist "up" "fov_deg": 70, - "aspect_ratio": 4.0 / 3.0 } ] @@ -152,30 +149,36 @@ far_plane = 100.0 simulation_duration = 10 # Sekunden time_step = 1 # Zeitintervall für "Aufnahmen" in Sekunden -# --- NEU: Bildparameter --- -IMG_WIDTH = 320 # Bildbreite in Pixel -IMG_HEIGHT = 180 # Bildhöhe in Pixel (Beispiel für 16:9) -OUTPUT_DIR = "./bilder" # Verzeichnis für die Bilder +# --- Bildparameter --- +IMG_WIDTH = 320 +IMG_HEIGHT = 180 +OUTPUT_DIR = "./bilder" -# --- NEU: Ausgabeverzeichnis erstellen --- +# --- Ausgabeverzeichnis erstellen --- os.makedirs(OUTPUT_DIR, exist_ok=True) print(f"Bilder werden in '{OUTPUT_DIR}' gespeichert.") - # --- 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 (Kreisbewegung) - angle = angular_speed * current_time - current_object_pos[0] = radius * math.cos(angle) + 0 - current_object_pos[2] = radius * math.sin(angle) + 10 + # --- NEU: Objektposition zufällig aktualisieren --- + # Generiere eine zufällige Verschiebung für diesen Zeitschritt + delta_x = (random.random() - 0.5) * 2 * random_step_scale # Zufallszahl zwischen -scale und +scale + delta_y = (random.random() - 0.5) * 2 * random_step_scale + delta_z = (random.random() - 0.5) * 2 * random_step_scale + random_displacement = np.array([delta_x, delta_y, delta_z]) + + # Wende die Verschiebung an + current_object_pos += random_displacement + + # Optional: Begrenze die Position auf die definierte Bounding Box + current_object_pos = np.clip(current_object_pos, world_bounds_min, world_bounds_max) print(f"Objekt Welt-Position: ({current_object_pos[0]:.2f}, {current_object_pos[1]:.2f}, {current_object_pos[2]:.2f})") @@ -189,22 +192,19 @@ while current_time <= simulation_duration: # Kamera schaut immer auf die aktuelle Objektposition current_target = current_object_pos - # Korrigiere das Seitenverhältnis basierend auf den Bilddimensionen - # Dies überschreibt das manuell gesetzte Seitenverhältnis + # Seitenverhältnis basierend auf Bilddimensionen für die Projektion verwenden effective_aspect_ratio = IMG_WIDTH / IMG_HEIGHT 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 (für Projektion): {effective_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 = np.dot(view_mat, object_pos_h) - # Sicherstellen, dass w positiv ist (sollte nach View-Transform 1 sein) - if point_in_cam_space_h[3] <= 0: - print(" Objekt hinter der Kamera nach View-Transformation (w <= 0).") + if point_in_cam_space_h[3] <= 1e-9: # Sicherer Vergleich mit w + print(" Objekt hinter oder auf der Kameraebene (w <= 0).") projected_point_ndc = None point_in_cam_space = np.array([np.nan, np.nan, np.nan]) # Ungültig else: @@ -220,31 +220,23 @@ while current_time <= simulation_duration: far_plane ) - # --- NEU: Bild erstellen und speichern --- - # Erstelle ein neues schwarzes Bild - # Passe Höhe ggf. an, wenn das Seitenverhältnis der Kamera nicht zum globalen passt - # Hier verwenden wir globale Höhe, aber berechnen den Aspekt neu. - # Alternativ könnte man Höhe/Breite pro Kamera anpassen. + # --- Bild erstellen und speichern --- img = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT), color='black') draw = ImageDraw.Draw(img) if projected_point_ndc is not None: print(f" Projizierte 2D-Koordinaten (NDC): ({projected_point_ndc[0]:.3f}, {projected_point_ndc[1]:.3f})") - # NDC in Pixelkoordinaten umwandeln px, py = ndc_to_pixel(projected_point_ndc, IMG_WIDTH, IMG_HEIGHT) print(f" Pixelkoordinaten (im Bild): ({px}, {py})") - - # Einen weißen Punkt (oder kleines Quadrat für bessere Sichtbarkeit) zeichnen - # draw.point((px, py), fill='white') # Einzelner Pixel - draw.rectangle([(px-1, py-1), (px+1, py+1)], fill='white', outline='white') # 3x3 Quadrat + # Einen weißen Punkt zeichnen (3x3 Quadrat für Sichtbarkeit) + draw.rectangle([(px-1, py-1), (px+1, py+1)], fill='white', outline='white') else: print(" Objekt befindet sich ausserhalb des Sichtbereichs (Clipping).") - # Das Bild bleibt schwarz, da nichts gezeichnet wird. # Bild speichern - timestamp_str = f"{current_time:.1f}".replace('.', '_') # Punkt im Dateinamen vermeiden + timestamp_str = f"{current_time:.1f}".replace('.', '_') filename = f"{cam['name']}_t_{timestamp_str}.png" filepath = os.path.join(OUTPUT_DIR, filename) img.save(filepath) @@ -252,9 +244,8 @@ while current_time <= simulation_duration: # Zum nächsten Zeitschritt gehen current_time += time_step - if current_time <= simulation_duration: - # Kurze Pause zur besseren Lesbarkeit der Ausgabe (optional) - # time.sleep(0.1) # Reduziert, um die Simulation zu beschleunigen - pass # Keine Pause für schnellere Ausführung + # Keine Pause, um die Simulation schneller laufen zu lassen + # if current_time <= simulation_duration: + # time.sleep(0.1) print("\nSimulation beendet.") \ No newline at end of file