Files
2026-07-13 13:13:17 +08:00

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
}