class_name CameraRig extends Node3D ## Kamera über dem Brett: passt sich der freien Bildmitte an, lässt sich ## zoomen und um die Brettmitte drehen. const FOV := 44.0 const PITCH := 52.0 ## Grad über der Waagerechten const ZOOM_MIN := 0.6 const ZOOM_MAX := 1.5 var camera: Camera3D var zoom := 1.0 var yaw := 0.0 var _center := Vector3.ZERO var _dir := Vector3.ZERO var _dist := 20.0 func _ready() -> void: camera = Camera3D.new() camera.fov = FOV camera.look_at_from_position(Vector3(0.0, 16.0, 13.0), Vector3.ZERO, Vector3.UP) add_child(camera) ## Legt die Kamera so, dass alle Punkte in das Zielrechteck passen. func fit(points: Array, target: Rect2) -> void: if target.size.x <= 0.0 or target.size.y <= 0.0 or points.is_empty(): return var pitch := deg_to_rad(PITCH) var dir := Vector3(0.0, sin(pitch), cos(pitch)) var dist := 20.0 var center := Vector3.ZERO for i in 8: camera.look_at_from_position(center + dir * dist, center, Vector3.UP) var r := _project_rect(points) if r.size.x <= 0.0 or r.size.y <= 0.0: return dist *= maxf(r.size.x / target.size.x, r.size.y / target.size.y) camera.look_at_from_position(center + dir * dist, center, Vector3.UP) r = _project_rect(points) var err := r.get_center() - target.get_center() var o := camera.unproject_position(center) var kx := camera.unproject_position(center + Vector3(1.0, 0.0, 0.0)).x - o.x var kz := camera.unproject_position(center + Vector3(0.0, 0.0, 1.0)).y - o.y if absf(kx) < 0.001 or absf(kz) < 0.001: break center += Vector3(err.x / kx, 0.0, err.y / kz) _center = center _dir = dir _dist = dist apply() ## Nutzeranteil: Zoom und Drehung um die Brettmitte. func apply() -> void: var dir := _dir.rotated(Vector3.UP, deg_to_rad(yaw)) camera.look_at_from_position(_center + dir * _dist * zoom, _center, Vector3.UP) func zoom_by(delta: float) -> void: zoom = clampf(zoom + delta, ZOOM_MIN, ZOOM_MAX) apply() func rotate_by(deg: float) -> void: yaw = fmod(yaw + deg, 360.0) apply() func _project_rect(points: Array) -> Rect2: var lo := Vector2(INF, INF) var hi := Vector2(-INF, -INF) for p in points: if camera.is_position_behind(p): return Rect2() var q := camera.unproject_position(p) lo = Vector2(minf(lo.x, q.x), minf(lo.y, q.y)) hi = Vector2(maxf(hi.x, q.x), maxf(hi.y, q.y)) return Rect2(lo, hi - lo) ## Punkt auf der Feldebene unter einer Bildschirmposition, oder null. func ground_point(screen_pos: Vector2, plane_y: float): var from := camera.project_ray_origin(screen_pos) var dir := camera.project_ray_normal(screen_pos) if absf(dir.y) < 0.0001: return null var t := (plane_y - from.y) / dir.y if t < 0.0: return null return from + dir * t