Moving the Object

This commit is contained in:
2025-04-16 08:39:42 +02:00
parent 7351e3ed08
commit e9f87276de

123
np.py
View File

@@ -1,16 +1,17 @@
import numpy as np import numpy as np
import time import time
import math import math
import os # Importiert für die Verzeichniserstellung import os
from PIL import Image, ImageDraw # Importiert für die Bildverarbeitung from PIL import Image, ImageDraw
import random # Importiert für Zufallszahlen
# --- Hilfsfunktionen --- # --- Hilfsfunktionen ---
def normalize(v): def normalize(v):
""" Normalisiert einen Vektor (macht seine Länge zu 1). """ """ Normalisiert einen Vektor (macht seine Länge zu 1). """
norm = np.linalg.norm(v) norm = np.linalg.norm(v)
if norm == 0: if norm < 1e-9: # Sicherer Vergleich mit kleiner Toleranz
return v return np.zeros_like(v) # Gibt Nullvektor zurück, wenn Norm zu klein ist
return v / norm return v / norm
def look_at_matrix(eye, target, up): 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). :param up: Up-Vektor der Kamera (zeigt, wo "oben" für die Kamera ist, np.array).
:return: 4x4 View-Matrix (NumPy-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 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 # 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 # 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 if abs(forward[1]) < 0.99: # Wenn forward nicht fast vertikal ist
temp_up = np.array([0.0, 1.0, 0.0]) temp_up = np.array([0.0, 1.0, 0.0])
else: # Wenn forward fast vertikal ist else: # Wenn forward fast vertikal ist
temp_up = np.array([0.0, 0.0, -1.0 if forward[1] > 0 else 1.0]) temp_up = np.array([0.0, 0.0, -1.0 if forward[1] > 0 else 1.0])
right = normalize(np.cross(temp_up, forward)) right = normalize(np.cross(temp_up, forward))
else: 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 # Den tatsächlichen Up-Vektor der Kamera neu berechnen, damit er orthogonal ist
camera_up = normalize(np.cross(forward, right)) 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) return None # Außerhalb des Sichtfelds (vor near oder hinter far)
# Verhindert Division durch Null, falls Punkt genau auf der Kameraebene liegt (z=0) # 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: if abs(point_cam_space[2]) < 1e-9:
return None 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] y_ndc = f * point_cam_space[1] / -point_cam_space[2]
# Überprüfen, ob die projizierten Koordinaten innerhalb des NDC-Bereichs [-1, 1] liegen # Ü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): if not (-1 <= x_ndc <= 1 and -1 <= y_ndc <= 1):
return None # Außerhalb des seitlichen Sichtfelds return None # Außerhalb des seitlichen Sichtfelds
return np.array([x_ndc, y_ndc]) return np.array([x_ndc, y_ndc])
# --- NEU: Funktion zur Umwandlung von NDC in Pixelkoordinaten ---
def ndc_to_pixel(ndc_coords, width, height): def ndc_to_pixel(ndc_coords, width, height):
""" Wandelt NDC-Koordinaten (-1 bis 1) in Pixelkoordinaten (0 bis width/height) um. """ """ 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 pixel_x = (ndc_coords[0] + 1.0) / 2.0 * width
# y_ndc = -1 -> pixel_y = height (unten im Bild) # Y-Koordinate invertieren (NDC +Y ist oben, Pixel +Y ist unten)
# 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.
pixel_y = (1.0 - ndc_coords[1]) / 2.0 * height 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)) px = int(round(pixel_x))
py = int(round(pixel_y)) py = int(round(pixel_y))
# Clipping auf Bildgrenzen
# Clipping auf Bildgrenzen (obwohl NDC-Clipping dies meist unnötig macht)
px = max(0, min(width - 1, px)) px = max(0, min(width - 1, px))
py = max(0, min(height - 1, py)) py = max(0, min(height - 1, py))
return px, py return px, py
# --- Simulationsparameter --- # --- Simulationsparameter ---
object_start_pos = np.array([0.0, 0.0, 10.0]) # Startposition des Objekts (x, y, z) 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 = [ cameras = [
{ {
"name": "Kamera_1_Frontal", # Namen ohne Leerzeichen für Dateinamen "name": "Kamera_1_Frontal",
"pos": np.array([0.0, 0.0, 0.0]), "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]), "up": np.array([0.0, 1.0, 0.0]),
"fov_deg": 60, "fov_deg": 60,
"aspect_ratio": 16.0 / 9.0
}, },
{ {
"name": "Kamera_2_Seitlich_Links", "name": "Kamera_2_Seitlich_Links",
@@ -133,7 +132,6 @@ cameras = [
"target": object_start_pos, "target": object_start_pos,
"up": np.array([0.0, 1.0, 0.0]), "up": np.array([0.0, 1.0, 0.0]),
"fov_deg": 45, "fov_deg": 45,
"aspect_ratio": 1.0
}, },
{ {
"name": "Kamera_3_Von_Oben", "name": "Kamera_3_Von_Oben",
@@ -141,7 +139,6 @@ cameras = [
"target": object_start_pos, "target": object_start_pos,
"up": np.array([0.0, 0.0, -1.0]), # Blickrichtung -Y, Z nach unten ist "up" "up": np.array([0.0, 0.0, -1.0]), # Blickrichtung -Y, Z nach unten ist "up"
"fov_deg": 70, "fov_deg": 70,
"aspect_ratio": 4.0 / 3.0
} }
] ]
@@ -152,30 +149,36 @@ far_plane = 100.0
simulation_duration = 10 # Sekunden simulation_duration = 10 # Sekunden
time_step = 1 # Zeitintervall für "Aufnahmen" in Sekunden time_step = 1 # Zeitintervall für "Aufnahmen" in Sekunden
# --- NEU: Bildparameter --- # --- Bildparameter ---
IMG_WIDTH = 320 # Bildbreite in Pixel IMG_WIDTH = 320
IMG_HEIGHT = 180 # Bildhöhe in Pixel (Beispiel für 16:9) IMG_HEIGHT = 180
OUTPUT_DIR = "./bilder" # Verzeichnis für die Bilder OUTPUT_DIR = "./bilder"
# --- NEU: Ausgabeverzeichnis erstellen --- # --- Ausgabeverzeichnis erstellen ---
os.makedirs(OUTPUT_DIR, exist_ok=True) os.makedirs(OUTPUT_DIR, exist_ok=True)
print(f"Bilder werden in '{OUTPUT_DIR}' gespeichert.") print(f"Bilder werden in '{OUTPUT_DIR}' gespeichert.")
# --- Simulationsschleife --- # --- Simulationsschleife ---
current_object_pos = object_start_pos.copy() current_object_pos = object_start_pos.copy()
current_time = 0.0 current_time = 0.0
angle = 0.0 # Für Kreisbewegung
print("Starte Simulation...") print("Starte Simulation...")
while current_time <= simulation_duration: while current_time <= simulation_duration:
print(f"\n--- Zeitpunkt: {current_time:.1f}s ---") print(f"\n--- Zeitpunkt: {current_time:.1f}s ---")
# Objektposition aktualisieren (Kreisbewegung) # --- NEU: Objektposition zufällig aktualisieren ---
angle = angular_speed * current_time # Generiere eine zufällige Verschiebung für diesen Zeitschritt
current_object_pos[0] = radius * math.cos(angle) + 0 delta_x = (random.random() - 0.5) * 2 * random_step_scale # Zufallszahl zwischen -scale und +scale
current_object_pos[2] = radius * math.sin(angle) + 10 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})") 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 # Kamera schaut immer auf die aktuelle Objektposition
current_target = current_object_pos current_target = current_object_pos
# Korrigiere das Seitenverhältnis basierend auf den Bilddimensionen # Seitenverhältnis basierend auf Bilddimensionen für die Projektion verwenden
# Dies überschreibt das manuell gesetzte Seitenverhältnis
effective_aspect_ratio = IMG_WIDTH / IMG_HEIGHT 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" 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" 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}") print(f" FOV: {cam['fov_deg']} Grad, Aspekt (für Projektion): {effective_aspect_ratio:.2f}")
# View-Matrix berechnen # View-Matrix berechnen
view_mat = look_at_matrix(cam['pos'], current_target, cam['up']) view_mat = look_at_matrix(cam['pos'], current_target, cam['up'])
# Objektposition in Kamera-Koordinaten transformieren # Objektposition in Kamera-Koordinaten transformieren
point_in_cam_space_h = np.dot(view_mat, object_pos_h) 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] <= 1e-9: # Sicherer Vergleich mit w
if point_in_cam_space_h[3] <= 0: print(" Objekt hinter oder auf der Kameraebene (w <= 0).")
print(" Objekt hinter der Kamera nach View-Transformation (w <= 0).")
projected_point_ndc = None projected_point_ndc = None
point_in_cam_space = np.array([np.nan, np.nan, np.nan]) # Ungültig point_in_cam_space = np.array([np.nan, np.nan, np.nan]) # Ungültig
else: else:
@@ -220,31 +220,23 @@ while current_time <= simulation_duration:
far_plane far_plane
) )
# --- NEU: Bild erstellen und speichern --- # --- 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.
img = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT), color='black') img = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT), color='black')
draw = ImageDraw.Draw(img) draw = ImageDraw.Draw(img)
if projected_point_ndc is not None: if projected_point_ndc is not None:
print(f" Projizierte 2D-Koordinaten (NDC): ({projected_point_ndc[0]:.3f}, {projected_point_ndc[1]:.3f})") print(f" Projizierte 2D-Koordinaten (NDC): ({projected_point_ndc[0]:.3f}, {projected_point_ndc[1]:.3f})")
# NDC in Pixelkoordinaten umwandeln # NDC in Pixelkoordinaten umwandeln
px, py = ndc_to_pixel(projected_point_ndc, IMG_WIDTH, IMG_HEIGHT) px, py = ndc_to_pixel(projected_point_ndc, IMG_WIDTH, IMG_HEIGHT)
print(f" Pixelkoordinaten (im Bild): ({px}, {py})") print(f" Pixelkoordinaten (im Bild): ({px}, {py})")
# Einen weißen Punkt zeichnen (3x3 Quadrat für Sichtbarkeit)
# Einen weißen Punkt (oder kleines Quadrat für bessere Sichtbarkeit) zeichnen draw.rectangle([(px-1, py-1), (px+1, py+1)], fill='white', outline='white')
# draw.point((px, py), fill='white') # Einzelner Pixel
draw.rectangle([(px-1, py-1), (px+1, py+1)], fill='white', outline='white') # 3x3 Quadrat
else: else:
print(" Objekt befindet sich ausserhalb des Sichtbereichs (Clipping).") print(" Objekt befindet sich ausserhalb des Sichtbereichs (Clipping).")
# Das Bild bleibt schwarz, da nichts gezeichnet wird.
# Bild speichern # 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" filename = f"{cam['name']}_t_{timestamp_str}.png"
filepath = os.path.join(OUTPUT_DIR, filename) filepath = os.path.join(OUTPUT_DIR, filename)
img.save(filepath) img.save(filepath)
@@ -252,9 +244,8 @@ while current_time <= simulation_duration:
# Zum nächsten Zeitschritt gehen # Zum nächsten Zeitschritt gehen
current_time += time_step current_time += time_step
if current_time <= simulation_duration: # Keine Pause, um die Simulation schneller laufen zu lassen
# Kurze Pause zur besseren Lesbarkeit der Ausgabe (optional) # if current_time <= simulation_duration:
# time.sleep(0.1) # Reduziert, um die Simulation zu beschleunigen # time.sleep(0.1)
pass # Keine Pause für schnellere Ausführung
print("\nSimulation beendet.") print("\nSimulation beendet.")