143 lines
5.6 KiB
C#
143 lines
5.6 KiB
C#
using System;
|
|
using T3.Core.Utils;
|
|
using T3.Core.Utils.Geometry;
|
|
|
|
namespace T3.Core.Operator.Interfaces;
|
|
|
|
public interface ICamera
|
|
{
|
|
Vector3 CameraPosition { get; set; }
|
|
Vector3 CameraTarget { get; set; }
|
|
float CameraRoll { get; set; }
|
|
|
|
CameraDefinition CameraDefinition { get; }
|
|
Matrix4x4 WorldToCamera { get; }
|
|
Matrix4x4 CameraToClipSpace { get; }
|
|
}
|
|
|
|
public struct CameraDefinition
|
|
{
|
|
public CameraDefinition()
|
|
{
|
|
|
|
}
|
|
|
|
public Vector2 NearFarClip;
|
|
public Vector2 LensShift;
|
|
public Vector3 PositionOffset;
|
|
public Vector3 Position = new(0, 0, GraphicsMath.DefaultCameraDistance);
|
|
public Vector3 Target;
|
|
public Vector3 Up = Vector3.UnitY;
|
|
public float AspectRatio = -1; // Unclear, how this plays together with flexible aspect ratio defined by Output targets
|
|
public float FieldOfView = GraphicsMath.DefaultCamFovDegrees.ToRadians();
|
|
public float Roll;
|
|
public Vector3 RotationOffset;
|
|
public bool OffsetAffectsTarget;
|
|
|
|
|
|
public static CameraDefinition Blend(CameraDefinition a, CameraDefinition b, float f)
|
|
{
|
|
f = Math.Clamp(f, 0f, 1f);
|
|
|
|
var blendedPosition = MathUtils.Lerp(a.Position, b.Position, f);
|
|
var blendedTarget = MathUtils.Lerp(a.Target, b.Target, f);
|
|
|
|
var qa = ExtractCameraQuaternion(a);
|
|
var qb = ExtractCameraQuaternion(b);
|
|
|
|
// shortest path
|
|
if (Quaternion.Dot(qa, qb) < 0)
|
|
qb = Quaternion.Negate(qb);
|
|
|
|
var q = Quaternion.Normalize(Quaternion.Slerp(qa, qb, f));
|
|
|
|
Vector3 forward = Vector3.Transform(-Vector3.UnitZ, q);
|
|
Vector3 up = Vector3.Transform(Vector3.UnitY, q);
|
|
|
|
return new CameraDefinition
|
|
{
|
|
Position = blendedPosition,
|
|
Target = blendedPosition + forward, // RH camera
|
|
Up = Vector3.Normalize(up),
|
|
Roll = 0,
|
|
|
|
NearFarClip = MathUtils.Lerp(a.NearFarClip, b.NearFarClip, f),
|
|
LensShift = MathUtils.Lerp(a.LensShift, b.LensShift, f),
|
|
PositionOffset = MathUtils.Lerp(a.PositionOffset, b.PositionOffset, f),
|
|
AspectRatio = MathUtils.Lerp(a.AspectRatio, b.AspectRatio, f),
|
|
FieldOfView = MathUtils.Lerp(a.FieldOfView, b.FieldOfView, f),
|
|
RotationOffset = MathUtils.Lerp(a.RotationOffset, b.RotationOffset, f),
|
|
|
|
OffsetAffectsTarget = f < 0.5f ? a.OffsetAffectsTarget : b.OffsetAffectsTarget,
|
|
};
|
|
}
|
|
|
|
|
|
private static Quaternion ExtractCameraQuaternion(CameraDefinition cam)
|
|
{
|
|
cam.BuildProjectionMatrices(out _, out var worldToCamera);
|
|
|
|
// Convert view -> world transform
|
|
Matrix4x4.Invert(worldToCamera, out var cameraToWorld);
|
|
|
|
// Extract the 3x3 rotation block
|
|
var rot = new Matrix4x4(
|
|
cameraToWorld.M11, cameraToWorld.M12, cameraToWorld.M13, 0,
|
|
cameraToWorld.M21, cameraToWorld.M22, cameraToWorld.M23, 0,
|
|
cameraToWorld.M31, cameraToWorld.M32, cameraToWorld.M33, 0,
|
|
0, 0, 0, 1);
|
|
|
|
// Orthonormalize to remove numerical drift
|
|
var right = Vector3.Normalize(new Vector3(rot.M11, rot.M12, rot.M13));
|
|
var up = Vector3.Normalize(new Vector3(rot.M21, rot.M22, rot.M23));
|
|
var forward = Vector3.Normalize(new Vector3(rot.M31, rot.M32, rot.M33));
|
|
|
|
// Rebuild orthogonal basis
|
|
forward = Vector3.Normalize(forward);
|
|
right = Vector3.Normalize(Vector3.Cross(up, forward));
|
|
up = Vector3.Cross(forward, right);
|
|
|
|
var clean = new Matrix4x4(
|
|
right.X, right.Y, right.Z, 0,
|
|
up.X, up.Y, up.Z, 0,
|
|
forward.X, forward.Y, forward.Z, 0,
|
|
0, 0, 0, 1);
|
|
|
|
return Quaternion.CreateFromRotationMatrix(clean);
|
|
}
|
|
|
|
|
|
public void BuildProjectionMatrices(out Matrix4x4 camToClipSpace, out Matrix4x4 worldToCamera)
|
|
{
|
|
camToClipSpace = GraphicsMath.PerspectiveFovRH(FieldOfView, AspectRatio, NearFarClip.X, NearFarClip.Y);
|
|
camToClipSpace.M31 = LensShift.X;
|
|
camToClipSpace.M32 = LensShift.Y;
|
|
|
|
var eye = Position;
|
|
if (!OffsetAffectsTarget)
|
|
eye += PositionOffset;
|
|
|
|
var worldToCameraRoot = GraphicsMath.LookAtRH(eye, Target, Up);
|
|
var rollRotation = Matrix4x4.CreateFromAxisAngle(Vector3.UnitZ, -Roll * MathUtils.ToRad);
|
|
var additionalTranslation =
|
|
OffsetAffectsTarget ? Matrix4x4.CreateTranslation(PositionOffset.X, PositionOffset.Y, PositionOffset.Z) : Matrix4x4.Identity;
|
|
|
|
var additionalRotation = Matrix4x4.CreateFromYawPitchRoll(MathUtils.ToRad * RotationOffset.Y,
|
|
MathUtils.ToRad * RotationOffset.X,
|
|
MathUtils.ToRad * RotationOffset.Z);
|
|
|
|
worldToCamera = worldToCameraRoot * rollRotation * additionalRotation * additionalTranslation;
|
|
}
|
|
}
|
|
|
|
// Mock view internal fallback camera (if no operator selected)
|
|
// Todo: Find a better location of this class
|
|
public class ViewCamera : ICamera
|
|
{
|
|
public Vector3 CameraPosition { get; set; } = new(0, 0, GraphicsMath.DefaultCameraDistance);
|
|
public Vector3 CameraTarget { get; set; }
|
|
public float CameraRoll { get; set; }
|
|
public Matrix4x4 WorldToCamera { get; }
|
|
public Matrix4x4 CameraToClipSpace { get; }
|
|
public CameraDefinition CameraDefinition => new(); // Not implemented
|
|
} |