using Luneraria.Core;
using UnityEngine;
namespace Luneraria.View
{
///
/// The MOBA camera of the reference (lune_camera.gd): WASD or arrows pan, mouse wheel zooms,
/// Q/E turn in 45 degree steps, R resets, O shows the whole map, middle mouse drags. The focus
/// follows the ground. Uses the classic input manager until the HUD port (U3) moves input over.
///
public sealed class LuneCameraRig : MonoBehaviour
{
public LuneGround Ground;
public bool Interactive = true;
/// True while the pointer is over the HUD: no wheel zoom, no middle-mouse drag (U3).
public System.Func PointerBlocked;
/// True while a window takes the keyboard (options): no panning or turning keys.
public System.Func KeysBlocked;
/// R: the game controller's reset (own lane, Godot reset_camera); without it R resets yaw and zoom.
public System.Action ResetRequested;
public float Zoom = LuneCameraMath.DefaultZoom;
public float Yaw;
Vec2 _focus;
Vec2 _goalFocus;
float _goalZoom = LuneCameraMath.DefaultZoom;
float _goalYaw;
Vector3 _dragAnchor;
bool _dragging;
public Vec2 Focus { get { return _focus; } }
public void RotateStep(int direction)
{
_goalYaw += direction * Mathf.PI * .25f;
}
/// Yaw back to 0 and the default zoom at a point (Godot reset_view).
public void ResetView(Vec2 point, bool instant = false)
{
_goalYaw = 0f;
LookAt(point, LuneCameraMath.DefaultZoom, instant);
}
///
/// Ground outline of the view (the four screen corners on the ground plane at the focus
/// height), in map coordinates; rays above the horizon end at 120 m.
///
public Vec2[] Footprint(Camera camera)
{
float height = Ground != null ? Ground.StandHeight(_focus) : 0f;
var plane = new Plane(Vector3.up, new Vector3(0f, height, 0f));
var result = new Vec2[4];
Vector2[] corners = { new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(1f, 0f), new Vector2(0f, 0f) };
for (int index = 0; index < 4; index++)
{
Ray ray = camera.ViewportPointToRay(new Vector3(corners[index].x, corners[index].y, 0f));
Vector3 point = plane.Raycast(ray, out float distance) && distance < 120f ? ray.GetPoint(distance) : ray.GetPoint(120f);
result[index] = LuneSpace.Map(point);
}
return result;
}
public void LookAt(Vec2 point, float zoom = -1f, bool instant = false)
{
_goalFocus = new Vec2(Mathf.Clamp((float)point.X, -LuneCameraMath.FocusLimit, LuneCameraMath.FocusLimit),
Mathf.Clamp((float)point.Y, -LuneCameraMath.FocusLimit, LuneCameraMath.FocusLimit));
if (zoom > 0f)
_goalZoom = Mathf.Clamp(zoom, LuneCameraMath.MinZoom, LuneCameraMath.MaxZoom);
if (instant)
{
_focus = _goalFocus;
Zoom = _goalZoom;
Yaw = _goalYaw;
Apply();
}
}
public void SetYawSteps(int steps, bool instant = false)
{
_goalYaw = steps * Mathf.PI * .25f;
if (instant)
{
Yaw = _goalYaw;
Apply();
}
}
/// Camera heading in radians (Godot yaw: 0 looks from the map's +y side).
public void SetYaw(float radians, bool instant = false)
{
_goalYaw = radians;
if (instant)
{
Yaw = _goalYaw;
Apply();
}
}
/// Yaw that puts the camera on the side of `from` that faces `towards`.
public static float YawTowards(Vec2 from, Vec2 towards)
{
Vec2 d = towards - from;
return Mathf.Atan2((float)d.X, (float)d.Y);
}
public void Overview(bool instant = false)
{
LookAt(new Vec2(0, 0), LuneCameraMath.OverviewZoom, instant);
}
void Apply()
{
float height = Ground != null ? Ground.StandHeight(_focus) : 0f;
LuneCameraMath.Pose(LuneSpace.World(_focus, height), Zoom, Yaw, out Vector3 position, out Quaternion rotation);
transform.SetPositionAndRotation(position, rotation);
}
void LateUpdate()
{
float dt = Time.unscaledDeltaTime;
if (Interactive)
HandleInput(dt);
float t = Mathf.Min(1f, dt * 7f);
_focus = _focus.Lerp(_goalFocus, t);
Zoom = Mathf.Lerp(Zoom, _goalZoom, t);
Yaw = Mathf.LerpAngle(Yaw * Mathf.Rad2Deg, _goalYaw * Mathf.Rad2Deg, t) * Mathf.Deg2Rad;
Apply();
}
void HandleInput(float dt)
{
bool keys = KeysBlocked == null || !KeysBlocked();
bool pointer = PointerBlocked == null || !PointerBlocked();
var pan = !keys ? Vector2.zero : new Vector2(Input.GetAxisRaw("Horizontal"), -Input.GetAxisRaw("Vertical"));
if (pan != Vector2.zero)
{
// Godot: right = (cos yaw, 0, -sin yaw), forward = (-sin yaw, 0, -cos yaw), map frame.
var right = new Vec2(Mathf.Cos(Yaw), -Mathf.Sin(Yaw));
var forward = new Vec2(-Mathf.Sin(Yaw), -Mathf.Cos(Yaw));
Vec2 move = right * (pan.x * Zoom * .9f * dt) - forward * (pan.y * Zoom * .9f * dt);
LookAt(_goalFocus + move);
}
float wheel = pointer ? Input.mouseScrollDelta.y : 0f;
if (wheel > 0f)
_goalZoom = Mathf.Max(LuneCameraMath.MinZoom, _goalZoom - Mathf.Max(2.5f, _goalZoom * .09f));
else if (wheel < 0f)
_goalZoom = Mathf.Min(LuneCameraMath.MaxZoom, _goalZoom + Mathf.Max(2.5f, _goalZoom * .09f));
if (keys && Input.GetKeyDown(KeyCode.Q))
_goalYaw -= Mathf.PI * .25f;
if (keys && Input.GetKeyDown(KeyCode.E))
_goalYaw += Mathf.PI * .25f;
if (keys && Input.GetKeyDown(KeyCode.R))
{
if (ResetRequested != null)
ResetRequested();
else
{
_goalYaw = 0f;
LookAt(_goalFocus, LuneCameraMath.DefaultZoom);
}
}
if (keys && Input.GetKeyDown(KeyCode.O))
Overview();
if (Input.GetMouseButtonDown(2) && pointer)
{
_dragAnchor = Input.mousePosition;
_dragging = true;
}
if (!Input.GetMouseButton(2))
_dragging = false;
if (_dragging)
{
Vector3 delta = Input.mousePosition - _dragAnchor;
_dragAnchor = Input.mousePosition;
var right = new Vec2(Mathf.Cos(Yaw), -Mathf.Sin(Yaw));
var forward = new Vec2(-Mathf.Sin(Yaw), -Mathf.Cos(Yaw));
// Screen y grows upwards in Unity (downwards in Godot).
LookAt(_goalFocus - (right * delta.x + forward * delta.y) * (Zoom / 700f));
}
}
}
}