using System;
using LibRender2.Camera;
using LibRender2.Viewports;
using OpenBveApi.Graphics;
using OpenBveApi.Math;
using OpenBveApi.Runtime;
namespace LibRender2.Cameras
{
public class CameraProperties
{
private readonly BaseRenderer Renderer;
/// The current viewing distance in the forward direction.
public double ForwardViewingDistance;
/// The current viewing distance in the backward direction.
public double BackwardViewingDistance;
/// The current viewing distance
public double ViewingDistance
{
get
{
return Math.Max(ForwardViewingDistance, BackwardViewingDistance);
}
}
/// The extra viewing distance used for determining visibility of animated objects.
public double ExtraViewingDistance;
/// Whether the camera has reached the end of the world
public bool AtWorldEnd;
/// The current horizontal viewing angle in radians
public double HorizontalViewingAngle;
/// The current vertical viewing angle in radians
public double VerticalViewingAngle;
/// The original vertical viewing angle in radians
public double OriginalVerticalViewingAngle;
/// A Matrix4D describing the current camera translation
public Matrix4D TranslationMatrix;
/// The absolute in-world camera position
public Vector3 AbsolutePosition
{
get
{
return absolutePosition;
}
set
{
absolutePosition = value;
TranslationMatrix = Matrix4D.CreateTranslation(-value.X, -value.Y, value.Z);
}
}
/// The absolute in-world camera Direction vector
public Vector3 AbsoluteDirection;
/// The absolute in-world camera Up vector
public Vector3 AbsoluteUp;
/// The absolute in-world camera Side vector
public Vector3 AbsoluteSide;
/// The current relative camera alignment
public CameraAlignment Alignment;
/// The current relative camera Direction
public CameraAlignment AlignmentDirection;
/// The current relative camera Speed
public CameraAlignment AlignmentSpeed;
/// The current camera movement speed
public double CurrentSpeed;
/// The top speed when moving in a straight line in an interior view
public const double InteriorTopSpeed = 5.0;
/// The top speed when moving at an angle in an interior view
public const double InteriorTopAngularSpeed = 5.0;
/// The top speed when moving in a straight line in an exterior view
public const double ExteriorTopSpeed = 50.0;
/// The top speed when moving in an angle in an exterior view
public const double ExteriorTopAngularSpeed = 10.0;
/// The top speed when zooming in or out
public const double ZoomTopSpeed = 2.0;
/// The current camera mode
public CameraViewMode CurrentMode;
/// The current camera restriction mode
public CameraRestrictionMode CurrentRestriction = CameraRestrictionMode.NotAvailable;
/// The saved exterior camera alignment
public CameraAlignment SavedExterior;
/// The saved track camera alignment
public CameraAlignment SavedTrack;
private Vector3 absolutePosition;
internal CameraProperties(BaseRenderer renderer)
{
Renderer = renderer;
}
/// Tests whether the camera may move further in the current direction
public bool PerformRestrictionTest(CameraRestriction Restriction)
{
if (CurrentRestriction == CameraRestrictionMode.On)
{
Vector3[] p = { Restriction.BottomLeft, Restriction.TopRight };
Vector2[] r = new Vector2[2];
for (int j = 0; j < 2; j++)
{
// determine relative world coordinates
p[j].Rotate(AbsoluteDirection, AbsoluteUp, AbsoluteSide);
double rx = -Math.Tan(Alignment.Yaw) - Alignment.Position.X;
double ry = -Math.Tan(Alignment.Pitch) - Alignment.Position.Y;
double rz = -Alignment.Position.Z;
p[j] += rx * AbsoluteSide + ry * AbsoluteUp + rz * AbsoluteDirection;
// determine screen coordinates
double ez = AbsoluteDirection.X * p[j].X + AbsoluteDirection.Y * p[j].Y + AbsoluteDirection.Z * p[j].Z;
if (ez == 0.0)
{
return false;
}
double ex = AbsoluteSide.X * p[j].X + AbsoluteSide.Y * p[j].Y + AbsoluteSide.Z * p[j].Z;
double ey = AbsoluteUp.X * p[j].X + AbsoluteUp.Y * p[j].Y + AbsoluteUp.Z * p[j].Z;
r[j].X = ex / (ez * Math.Tan(0.5 * HorizontalViewingAngle));
r[j].Y = ey / (ez * Math.Tan(0.5 * VerticalViewingAngle));
}
return r[0].X <= -1.0025 & r[1].X >= 1.0025 & r[0].Y <= -1.0025 & r[1].Y >= 1.0025;
}
if (CurrentRestriction == CameraRestrictionMode.Restricted3D)
{
Vector3[] p = { Restriction.BottomLeft, Restriction.TopRight };
for (int j = 0; j < 2; j++)
{
// determine relative world coordinates
p[j].Rotate(AbsoluteDirection, AbsoluteUp, AbsoluteSide);
double rx = -Math.Tan(Alignment.Yaw) - Alignment.Position.X;
double ry = -Math.Tan(Alignment.Pitch) - Alignment.Position.Y;
double rz = -Alignment.Position.Z;
p[j] += rx * AbsoluteSide + ry * AbsoluteUp + rz * AbsoluteDirection;
}
if (AlignmentDirection.Position.X > 0)
{
//moving right
if (AbsolutePosition.X >= Restriction.AbsoluteTopRight.X)
{
return false;
}
}
if (AlignmentDirection.Position.X < 0)
{
//moving left
if (AbsolutePosition.X <= Restriction.AbsoluteBottomLeft.X)
{
return false;
}
}
if (AlignmentDirection.Position.Y > 0)
{
//moving up
if (AbsolutePosition.Y >= Restriction.AbsoluteTopRight.Y)
{
return false;
}
}
if (AlignmentDirection.Position.Y < 0)
{
//moving down
if (AbsolutePosition.Y <= Restriction.AbsoluteBottomLeft.Y)
{
return false;
}
}
if (AlignmentDirection.Position.Z > 0)
{
//moving forwards
if (AbsolutePosition.Z >= Restriction.AbsoluteTopRight.Z)
{
return false;
}
}
if (AlignmentDirection.Position.Z < 0)
{
//moving back
if (AbsolutePosition.Z <= Restriction.AbsoluteBottomLeft.Z)
{
return false;
}
}
}
return true;
}
/// Performs progressive adjustments taking into account the specified camera restriction
public bool PerformProgressiveAdjustmentForCameraRestriction(ref double Source, double Target, bool Zoom, CameraRestriction Restriction)
{
if ((CurrentMode != CameraViewMode.Interior & CurrentMode != CameraViewMode.InteriorLookAhead) | (CurrentRestriction != CameraRestrictionMode.On && CurrentRestriction != CameraRestrictionMode.Restricted3D))
{
Source = Target;
return true;
}
double best = Source;
const int Precision = 8;
double a = Source;
double b = Target;
Source = Target;
if (Zoom) ApplyZoom();
if (PerformRestrictionTest(Restriction))
{
return true;
}
double x = 0.5 * (a + b);
bool q = true;
for (int i = 0; i < Precision; i++)
{
#pragma warning disable IDE0059 //IDE is wrong, best may never be updated if q is never true
Source = x;
#pragma warning restore IDE0059
if (Zoom) ApplyZoom();
q = PerformRestrictionTest(Restriction);
if (q)
{
a = x;
best = x;
}
else
{
b = x;
}
x = 0.5 * (a + b);
}
Source = best;
if (Zoom) ApplyZoom();
return q;
}
/// Adjusts the camera alignment based upon the specified parameters
public void AdjustAlignment(ref double Source, double Direction, ref double Speed, double TimeElapsed, bool Zoom = false, CameraRestriction? Restriction = null) {
if (Direction != 0.0 | Speed != 0.0) {
if (TimeElapsed > 0.0) {
if (Direction == 0.0) {
double d = (0.025 + 5.0 * Math.Abs(Speed)) * TimeElapsed;
if (Speed >= -d & Speed <= d) {
Speed = 0.0;
} else {
Speed -= Math.Sign(Speed) * d;
}
} else {
double t = Math.Abs(Direction);
double d = ((1.15 - 1.0 / (1.0 + 0.025 * Math.Abs(Speed)))) * TimeElapsed;
Speed += Direction * d;
if (Speed < -t) {
Speed = -t;
} else if (Speed > t) {
Speed = t;
}
}
if (Restriction != null)
{
double x = Source + Speed * TimeElapsed;
if (!PerformProgressiveAdjustmentForCameraRestriction(ref Source, x, Zoom, (CameraRestriction)Restriction))
{
Speed = 0.0;
}
}
else
{
Source += Speed * TimeElapsed;
}
}
}
}
/// Applies the current zoom settings after a change
public void ApplyZoom()
{
VerticalViewingAngle = OriginalVerticalViewingAngle * Math.Exp(Alignment.Zoom);
if (VerticalViewingAngle < 0.001) VerticalViewingAngle = 0.001;
if (VerticalViewingAngle > 1.5) VerticalViewingAngle = 1.5;
Renderer.UpdateViewport(ViewportChangeMode.NoChange);
}
/// Resets the camera to an absolute position
public void Reset(Vector3 Position)
{
AbsolutePosition = Position;
AbsoluteDirection = new Vector3(-AbsolutePosition.X, -AbsolutePosition.Y, -AbsolutePosition.Z);
AbsoluteSide = new Vector3(-AbsolutePosition.Z, 0.0, AbsolutePosition.X);
AbsoluteDirection.Normalize();
AbsoluteSide.Normalize();
AbsoluteUp = Vector3.Cross(AbsoluteDirection, AbsoluteSide);
VerticalViewingAngle = 45.0.ToRadians();
HorizontalViewingAngle = 2.0 * Math.Atan(Math.Tan(0.5 * VerticalViewingAngle) * Renderer.Screen.AspectRatio);
OriginalVerticalViewingAngle = VerticalViewingAngle;
}
/// Unconditionally resets the camera
public void Reset()
{
Alignment.Yaw = 0.0;
Alignment.Pitch = 0.0;
Alignment.Roll = 0.0;
Alignment.Position = new Vector3(0.0, 2.5, 0.0);
Alignment.Zoom = 0.0;
AlignmentDirection = new CameraAlignment();
AlignmentSpeed = new CameraAlignment();
VerticalViewingAngle = OriginalVerticalViewingAngle;
}
}
}