using OpenTK.GLControl;
|
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Mathematics;
|
using System.Transactions;
|
using System.Windows.Forms;
|
|
namespace Yw.WinFrmUI.Hydro
|
{
|
/// <summary>
|
/// 正交相机辅助类
|
/// </summary>
|
internal class OrthoCameraHelper
|
{
|
|
// 基础参数
|
private List<Vector3> _pts = null; //所有坐标
|
|
|
|
private float _aspect = 1f;//视口宽高比
|
private float _pw = 10f;//投影宽度
|
private float _ph = 10f;//投影高度
|
private Vector3 _rotation = Vector3.Zero;//旋转量
|
private Quaternion _rotationQua = Quaternion.Identity;//
|
private Vector3 _translation = Vector3.Zero;//平移量
|
|
/// <summary>
|
/// 投影矩阵
|
/// </summary>
|
public Matrix4 ProjectionMatrix => _projectionMatrix;
|
private Matrix4 _projectionMatrix;
|
|
/// <summary>
|
/// 视图模型
|
/// </summary>
|
public Matrix4 ViewMatrix => _viewMatrix;
|
private Matrix4 _viewMatrix;
|
|
/// <summary>
|
/// 模型矩阵
|
/// </summary>
|
public Matrix4 ModelMatrix => _modelMatrix;
|
private Matrix4 _modelMatrix;
|
|
/// <summary>
|
/// 世界包围盒
|
/// </summary>
|
public BoundingBox3 Bx => _bx;
|
private BoundingBox3 _bx;
|
|
/// <summary>
|
/// 模型中心
|
/// </summary>
|
public Vector3 MC => _mc;
|
private Vector3 _mc;
|
|
/// <summary>
|
/// 包围球半径
|
/// </summary>
|
public float Radius => _radius;
|
private float _radius;
|
|
|
/// <summary>
|
/// 缩放
|
/// </summary>
|
public float Zoom
|
{
|
get => _zoom;
|
set => _zoom = MathHelper.Clamp(value, 0.01f, 100.0f); // 限制缩放范围
|
}
|
private float _zoom = 1.0f; // 当前缩放系数(1表示原始大小)
|
|
/// <summary>
|
/// 初始化
|
/// </summary>
|
public void Initial(List<Vector3> pts)
|
{
|
_pts = pts;
|
UpdateBoundingBox();
|
UpdateModelCenter();
|
UpdateRadius();
|
UpdateModelMatrix();
|
UpdateViewMatrix();
|
UpdateProjectionMatrix();
|
}
|
|
|
//更新包围盒
|
private void UpdateBoundingBox()
|
{
|
if (_pts == null || _pts.Count < 1)
|
{
|
return;
|
}
|
_bx = new BoundingBox3(_pts);
|
}
|
|
//更新模型中心
|
private void UpdateModelCenter()
|
{
|
_mc = (_bx.Max + _bx.Min) * 0.5f;
|
}
|
|
//更新包围球半径
|
private void UpdateRadius()
|
{
|
_radius = (_bx.Max - _bx.Min).Length / 2f;
|
}
|
|
//更新模型矩阵
|
private void UpdateModelMatrix()
|
{
|
_modelMatrix = Matrix4.CreateTranslation(_translation * this.Zoom);
|
}
|
|
//更新视图矩阵
|
private void UpdateViewMatrix()
|
{
|
var eye = new Vector3(_mc.X, _mc.Y + _radius * this.Zoom * 2, _mc.Z);
|
var target = _mc;
|
var up = -Vector3.UnitZ;
|
var matrix = Matrix4.CreateFromQuaternion(_rotationQua);
|
eye = Vector3.TransformPosition(eye, matrix);
|
up = Vector3.TransformPosition(up, matrix);
|
_viewMatrix = Matrix4.LookAt(eye, target, up);
|
}
|
|
//更新投影矩阵
|
public void UpdateProjectionMatrix()
|
{
|
var size = _radius * 2;
|
_pw = size;
|
_ph = size;
|
|
|
if (_aspect > 1)
|
{
|
_pw *= _aspect;
|
}
|
else
|
{
|
_ph /= _aspect;
|
}
|
|
_projectionMatrix = Matrix4.CreateOrthographic(_pw * this.Zoom, _ph * this.Zoom, _radius * this.Zoom, _radius * this.Zoom * 3);
|
|
}
|
|
/// <summary>
|
/// 更新视口
|
/// </summary>
|
public void UpdateViewport(int width, int height)
|
{
|
var w = width < 1 ? 1 : width;
|
var h = height < 1 ? 1 : height;
|
_aspect = w / (float)h;
|
GL.Viewport(0, 0, w, h);
|
UpdateProjectionMatrix();
|
}
|
|
|
#region 鼠标交互
|
|
private bool _isDragging = false;//是否增在拖动
|
private bool _isRotating = false;//是否正在旋转
|
private Point _lastMousePos;//最近一次鼠标位置
|
|
/// <summary>
|
/// 处理鼠标滚轮
|
/// </summary>
|
public void HandleMouseWheel(GLControl gl, MouseEventArgs e)
|
{
|
this.Zoom *= e.Delta > 0 ? 0.9f : 1.1f;
|
UpdateModelMatrix();
|
UpdateViewMatrix();
|
UpdateProjectionMatrix();
|
gl.Invalidate();
|
}
|
|
/// <summary>
|
/// 处理鼠标按下
|
/// </summary>
|
public void HandleMouseDown(GLControl gl, MouseEventArgs e)
|
{
|
_lastMousePos = e.Location;
|
if (e.Button == MouseButtons.Right)
|
{
|
_isDragging = true;
|
gl.Cursor = Cursors.SizeAll; // 修改光标样式
|
}
|
else if (e.Button == MouseButtons.Left)
|
{
|
_isRotating = true;
|
gl.Cursor = Cursors.SizeAll; // 修改光标样式
|
}
|
}
|
|
/// <summary>
|
/// 处理鼠标弹起
|
/// </summary>
|
public void HandleMouseUp(GLControl gl, MouseEventArgs e)
|
{
|
_lastMousePos = e.Location;
|
if (e.Button == MouseButtons.Right)
|
{
|
_isDragging = false;
|
gl.Cursor = Cursors.Default;
|
}
|
else if (e.Button == MouseButtons.Left)
|
{
|
_isRotating = false;
|
gl.Cursor = Cursors.Default;
|
}
|
}
|
|
/// <summary>
|
/// 处理鼠标移动
|
/// </summary>
|
public void HandleMouseMove(GLControl gl, MouseEventArgs e)
|
{
|
float dx = e.X - _lastMousePos.X;
|
float dy = e.Y - _lastMousePos.Y;
|
|
if (_isRotating)
|
{
|
// 根据鼠标移动量计算旋转角度
|
Quaternion rotationX = Quaternion.FromAxisAngle(Vector3.UnitX, dy / _radius);
|
Quaternion rotationY = Quaternion.FromAxisAngle(Vector3.UnitY, dx / _radius);
|
|
// 更新四元数
|
_rotationQua = rotationY * rotationX * _rotationQua;
|
_rotationQua = Quaternion.Normalize(_rotationQua);
|
UpdateViewMatrix();
|
}
|
else if (_isDragging)
|
{
|
float xratio = _pw / gl.Width;
|
float yratio = _ph / gl.Height;
|
_translation.X += dx * xratio;
|
_translation.Y -= dy * yratio;
|
UpdateModelMatrix();
|
}
|
|
_lastMousePos = e.Location;
|
gl.Invalidate();
|
}
|
|
|
#endregion
|
|
#region 坐标转换
|
|
public Vector3 ScreenToWorld(Vector2 screenPos, GLControl gl)
|
{
|
var proj = _projectionMatrix;
|
var view = _viewMatrix;
|
var model = _modelMatrix;
|
|
var mvp = model * view * proj;
|
|
|
Vector3 clipPos = new Vector3(
|
2.0f * screenPos.X / gl.Width - 1,
|
1 - 2.0f * screenPos.Y / gl.Height,
|
0
|
);
|
|
Matrix4 invMatrix = mvp.Inverted();
|
Vector3 worldPos = Vector3.TransformPosition(clipPos, invMatrix);
|
return new Vector3(worldPos.X, worldPos.Y, worldPos.Z);
|
}
|
|
public Vector2 WorldToScreen(Vector3 worldPos)
|
{
|
var model = _modelMatrix;
|
var view = _viewMatrix;
|
var proj = _projectionMatrix;
|
var mvp = model * view * proj;
|
|
// 1. 将世界坐标转换为齐次坐标 (w=1)
|
|
// 2. 应用视图矩阵和投影矩阵变换
|
Vector3 clipPos = Vector3.TransformPosition(worldPos, mvp);
|
|
// 3. 执行透视除法(正交投影下w=1,此步骤对结果无影响但保留用于兼容性)
|
Vector3 ndcPos = new Vector3(
|
clipPos.X,
|
clipPos.Y,
|
clipPos.Z
|
);
|
|
// 4. 获取当前视口参数
|
int[] viewport = new int[4];
|
GL.GetInteger(GetPName.Viewport, viewport);
|
int screenWidth = viewport[2];
|
int screenHeight = viewport[3];
|
|
// 5. 将NDC坐标 [-1,1] 转换为屏幕坐标 [0, width/height]
|
// 注意:OpenGL视口原点在左下角,屏幕坐标系通常原点在左上角,需要翻转Y轴
|
return new Vector2(
|
(ndcPos.X + 1.0f) * 0.5f * screenWidth,
|
(1.0f - (ndcPos.Y + 1.0f) * 0.5f) * screenHeight // Y轴翻转
|
);
|
}
|
|
#endregion
|
|
|
}
|
}
|