94 lines
2.7 KiB
GDScript
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
|