Files
elements-autochess/scripts/ui/CameraRig.gd
2026-09-16 13:21:45 +02:00

94 lines
2.7 KiB
GDScript

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