using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using global::System.Windows.Forms; using global::OpenTK; namespace Pilz.Drawing.Drawing3D.OpenGLFactory.CameraN { public class Camera { public event NeedSelectedObjectEventHandler NeedSelectedObject; public delegate void NeedSelectedObjectEventHandler(object sender, NeedSelectedObjectEventArgs e); public event PerspectiveChangedEventHandler PerspectiveChanged; public delegate void PerspectiveChangedEventHandler(object sender, EventArgs e); // P R I V A T E F I E L D S private readonly float TAU = (float)(Math.PI * 2d); private CameraMode myCamMode = CameraMode.FLY; private Vector3 pos = new Vector3(-5000.0f, 3000.0f, 4000.0f); private Vector3 myLookat = new Vector3(0f, 0f, 0f); private Vector3 myFarPoint = new Vector3(0f, 0f, 0f); private Vector3 myNearPoint = new Vector3(0f, 0f, 0f); private int lastMouseX = -1; private int lastMouseY = -1; private float CamAngleX = 0f; private float CamAngleY = (float)-(Math.PI / 2d); private bool resetMouse = true; private float orbitDistance = 500.0f; private float orbitTheta = 0.0f; private float orbitPhi = 0.0f; private LookDirection currentLookDirection; private Vector3[] lookPositions = new Vector3[] { new Vector3(0f, 12500f, 0f), new Vector3(0f, -12500, 0f), new Vector3(-12500, 0f, 0f), new Vector3(12500f, 0f, 0f), new Vector3(0f, 0f, 12500f), new Vector3(0f, 0f, -12500) }; // A U T O M A T I C P R O P E R T I E S public float CamSpeedMultiplier { get; set; } = 1f; // P R O P E R T I E S public CameraMode CamMode { get { return myCamMode; } } public float Yaw { get { return CamAngleX; } } public float Pitch { get { return CamAngleY; } } public float Yaw_Degrees { get { return CamAngleX * (180.0f / 3.14159274f); } } public float Pitch_Degrees { get { return CamAngleY * (180.0f / 3.14159274f); } } public Vector3 Position { get { return pos; } set { pos = value; } } public Vector3 LookAt { get { return myLookat; } set { myLookat = value; } } public Vector3 NearPoint { get { return myNearPoint; } set { myNearPoint = value; } } public Vector3 FarPoint { get { return myFarPoint; } set { myFarPoint = value; } } // C O N S T R U C T O R public Camera() { SetRotationFromLookAt(); } // F E A T U R E S private float Clampf(float value, float min, float max) { return value > max ? max : value < min ? min : value; } private void OrientateCam(float ang, float ang2) { float CamLX = (float)Math.Sin(ang) * (float)Math.Sin(-ang2); float CamLY = (float)Math.Cos(ang2); float CamLZ = (float)-Math.Cos(ang) * (float)Math.Sin(-ang2); myLookat.X = pos.X + -CamLX * 100.0f; myLookat.Y = pos.Y + -CamLY * 100.0f; myLookat.Z = pos.Z + -CamLZ * 100.0f; } private void OffsetCam(int xAmt, int yAmt, int zAmt) { double pitch_Renamed = CamAngleY - Math.PI / 2d; float CamLX = (float)Math.Sin(CamAngleX) * (float)Math.Cos(-pitch_Renamed); float CamLY = (float)Math.Sin(pitch_Renamed); float CamLZ = (float)-Math.Cos(CamAngleX) * (float)Math.Cos(-pitch_Renamed); pos.X = pos.X + xAmt * CamLX * CamSpeedMultiplier; pos.Y = pos.Y + yAmt * CamLY * CamSpeedMultiplier; pos.Z = pos.Z + zAmt * CamLZ * CamSpeedMultiplier; } public void Move(float y, ref Matrix4 camMtx) { OffsetCam(0, (int)y, 0); OrientateCam(CamAngleX, CamAngleY); UpdateMatrix(ref camMtx); } public void Move(float x, float z, ref Matrix4 camMtx) { UpdateCameraOffsetDirectly((int)x, (int)z, ref camMtx); OrientateCam(CamAngleX, CamAngleY); UpdateMatrix(ref camMtx); } public void SetRotationFromLookAt() { float x_diff = myLookat.X - pos.X; float y_diff = myLookat.Y - pos.Y; float z_diff = myLookat.Z - pos.Z; float dist = (float)Math.Sqrt(x_diff * x_diff + y_diff * y_diff + z_diff * z_diff); if (z_diff == 0f) { z_diff = 0.001f; } float nxz_ratio = -x_diff / z_diff; if (z_diff < 0f) { CamAngleX = (float)(Math.Atan(nxz_ratio) + Math.PI); } else { CamAngleX = (float)Math.Atan(nxz_ratio); } CamAngleY = -3.1459f - ((float)Math.Asin(y_diff / dist) - 1.57f); } public void ResetOrbitToSelectedObject() { var objs = GetSelectedObject(); if (objs?.Length > 0 == true) { orbitTheta = -(CalculateCenterYRotationOfObjects(objs) * ((float)Math.PI / 180.0f)); orbitPhi = -0.3f; orbitDistance = 1200.0f; } } public void UpdateOrbitCamera(ref Matrix4 cameraMatrix) { if (myCamMode == CameraMode.ORBIT) { var objs = GetSelectedObject(); if (objs?.Length > 0 == true) { var centerPos = CalculateCenterPositionOfObjects(objs); pos.X = centerPos.X + (float)(Math.Cos(orbitPhi) * -Math.Sin(orbitTheta) * orbitDistance); pos.Y = centerPos.Y + (float)(-Math.Sin(orbitPhi) * orbitDistance); pos.Z = centerPos.Z + (float)(Math.Cos(orbitPhi) * Math.Cos(orbitTheta) * orbitDistance); myLookat.X = centerPos.X; myLookat.Y = centerPos.Y; myLookat.Z = centerPos.Z; UpdateMatrix(ref cameraMatrix); SetRotationFromLookAt(); } } } public bool IsOrbitCamera() { return myCamMode == CameraMode.ORBIT; } public void SetCameraMode(CameraMode mode, ref Matrix4 cameraMatrix) { myCamMode = mode; if (IsOrbitCamera()) { ResetOrbitToSelectedObject(); UpdateOrbitCamera(ref cameraMatrix); } } public void SetCameraMode_LookDirection(LookDirection dir, ref Matrix4 cameraMatrix) { myCamMode = CameraMode.LOOK_DIRECTION; currentLookDirection = dir; switch (currentLookDirection) { case LookDirection.Top: { pos = lookPositions[(int)LookDirection.Top]; myLookat = new Vector3(pos.X, -25000, pos.Z - 1f); UpdateMatrix(ref cameraMatrix); SetRotationFromLookAt(); break; } case LookDirection.Bottom: { pos = lookPositions[(int)LookDirection.Bottom]; myLookat = new Vector3(pos.X, 25000f, pos.Z + 1f); UpdateMatrix(ref cameraMatrix); SetRotationFromLookAt(); break; } case LookDirection.Left: { pos = lookPositions[(int)LookDirection.Left]; myLookat = new Vector3(25000f, pos.Y, pos.Z); UpdateMatrix(ref cameraMatrix); SetRotationFromLookAt(); break; } case LookDirection.Right: { pos = lookPositions[(int)LookDirection.Right]; myLookat = new Vector3(-25000, pos.Y, pos.Z); UpdateMatrix(ref cameraMatrix); SetRotationFromLookAt(); break; } case LookDirection.Front: { pos = lookPositions[(int)LookDirection.Front]; myLookat = new Vector3(pos.X, pos.Y, -25000); UpdateMatrix(ref cameraMatrix); SetRotationFromLookAt(); break; } case LookDirection.Back: { pos = lookPositions[(int)LookDirection.Back]; myLookat = new Vector3(pos.X, pos.Y, 25000f); UpdateMatrix(ref cameraMatrix); SetRotationFromLookAt(); break; } } } public void UpdateCameraMatrixWithMouse(int mouseX, int mouseY, ref Matrix4 cameraMatrix) { if (myCamMode == CameraMode.ORBIT && GetSelectedObject() is object) { UpdateCameraMatrixWithMouse_ORBIT(mouseX, mouseY, ref cameraMatrix); } else if (myCamMode == CameraMode.LOOK_DIRECTION) { UpdateCameraMatrixWithMouse_LOOK(pos, mouseX, mouseY, ref cameraMatrix); } else { UpdateCameraMatrixWithMouse_FLY(mouseX, mouseY, ref cameraMatrix); } } public void UpdateCameraOffsetWithMouse(Vector3 orgPos, int mouseX, int mouseY, int w, int h, ref Matrix4 cameraMatrix) { if (myCamMode == CameraMode.ORBIT && GetSelectedObject() is object) { UpdateCameraOffsetWithMouse_ORBIT(mouseX, mouseY, ref cameraMatrix); } else if (myCamMode == CameraMode.LOOK_DIRECTION) { UpdateCameraMatrixWithMouse_LOOK(pos, mouseX, mouseY, ref cameraMatrix); } else { UpdateCameraOffsetWithMouse_FLY(orgPos, mouseX, mouseY, w, h, ref cameraMatrix); } } public void UpdateCameraMatrixWithScrollWheel(int amt, ref Matrix4 cameraMatrix) { if (myCamMode == CameraMode.ORBIT && GetSelectedObject() is object) { UpdateCameraMatrixWithScrollWheel_ORBIT(amt, ref cameraMatrix); } else if (myCamMode == CameraMode.LOOK_DIRECTION) { UpdateCameraMatrixWithScrollWheel_LOOK(amt, ref cameraMatrix); } else { UpdateCameraMatrixWithScrollWheel_FLY(amt, ref cameraMatrix); } } private void UpdateCameraMatrixWithScrollWheel_FLY(int amt, ref Matrix4 cameraMatrix) { OffsetCam(amt, amt, amt); OrientateCam(CamAngleX, CamAngleY); UpdateMatrix(ref cameraMatrix); } public void RaisePerspectiveChanged() { PerspectiveChanged?.Invoke(this, new EventArgs()); } public void UpdateMatrix(ref Matrix4 cameraMatrix) { cameraMatrix = Matrix4.LookAt(pos.X, pos.Y, pos.Z, myLookat.X, myLookat.Y, myLookat.Z, 0f, 1f, 0f); RaisePerspectiveChanged(); } private void UpdateCameraMatrixWithScrollWheel_LOOK(int amt, ref Matrix4 cameraMatrix) { OffsetCam(amt, amt, amt); OrientateCam(CamAngleX, CamAngleY); switch (currentLookDirection) { case LookDirection.Top: { cameraMatrix = Matrix4.LookAt(pos.X, pos.Y, pos.Z, myLookat.X, myLookat.Y, myLookat.Z - 1f, 0f, 1f, 0f); RaisePerspectiveChanged(); break; } case LookDirection.Bottom: { cameraMatrix = Matrix4.LookAt(pos.X, pos.Y, pos.Z, myLookat.X, myLookat.Y, myLookat.Z + 1f, 0f, 1f, 0f); RaisePerspectiveChanged(); break; } default: { UpdateMatrix(ref cameraMatrix); break; } } } private void UpdateCameraMatrixWithMouse_LOOK(Vector3 orgPos, int mouseX, int mouseY, ref Matrix4 cameraMatrix) { if (resetMouse) { lastMouseX = mouseX; lastMouseY = mouseY; resetMouse = false; } int MousePosX = mouseX - lastMouseX; int MousePosY = mouseY - lastMouseY; double pitch_Renamed = CamAngleY - Math.PI / 2d; double yaw_Renamed = CamAngleX - Math.PI / 2d; float CamLX = (float)Math.Sin(yaw_Renamed); float CamLY = (float)Math.Cos(pitch_Renamed); float CamLZ = (float)-Math.Cos(yaw_Renamed); float m = 8.0f; switch (currentLookDirection) { case LookDirection.Top: { pos.X = orgPos.X - MousePosX * CamSpeedMultiplier * CamLX * m - MousePosY * CamSpeedMultiplier * CamLZ * m; pos.Z = orgPos.Z - MousePosX * CamSpeedMultiplier * CamLZ * m - MousePosY * CamSpeedMultiplier * CamLX * m; cameraMatrix = Matrix4.LookAt(pos.X, pos.Y, pos.Z, pos.X, pos.Y - 1000f, pos.Z - 1f, 0f, 1f, 0f); lookPositions[(int)currentLookDirection] = pos; break; } case LookDirection.Bottom: { pos.X = orgPos.X - MousePosX * CamSpeedMultiplier * CamLX * m + MousePosY * CamSpeedMultiplier * CamLZ * m; pos.Z = orgPos.Z - MousePosX * CamSpeedMultiplier * CamLZ * m + MousePosY * CamSpeedMultiplier * CamLX * m; cameraMatrix = Matrix4.LookAt(pos.X, pos.Y, pos.Z, pos.X, pos.Y + 1000f, pos.Z + 1f, 0f, 1f, 0f); lookPositions[(int)currentLookDirection] = pos; break; } case LookDirection.Left: { pos.X = orgPos.X - MousePosX * CamSpeedMultiplier * CamLX * m; pos.Y = orgPos.Y - MousePosY * CamSpeedMultiplier * -1.0f * m; pos.Z = orgPos.Z - MousePosX * CamSpeedMultiplier * CamLZ * m; cameraMatrix = Matrix4.LookAt(pos.X, pos.Y, pos.Z, pos.X + 12500f, pos.Y, pos.Z, 0f, 1f, 0f); lookPositions[(int)currentLookDirection] = pos; break; } case LookDirection.Right: { pos.X = orgPos.X - MousePosX * CamSpeedMultiplier * CamLX * m; pos.Y = orgPos.Y - MousePosY * CamSpeedMultiplier * -1.0f * m; pos.Z = orgPos.Z - MousePosX * CamSpeedMultiplier * CamLZ * m; cameraMatrix = Matrix4.LookAt(pos.X, pos.Y, pos.Z, pos.X - 12500f, pos.Y, pos.Z, 0f, 1f, 0f); lookPositions[(int)currentLookDirection] = pos; break; } case LookDirection.Front: { pos.X = orgPos.X - MousePosX * CamSpeedMultiplier * CamLX * m; pos.Y = orgPos.Y - MousePosY * CamSpeedMultiplier * -1.0f * m; pos.Z = orgPos.Z - MousePosX * CamSpeedMultiplier * CamLZ * m; cameraMatrix = Matrix4.LookAt(pos.X, pos.Y, pos.Z, pos.X, pos.Y, pos.Z - 12500f, 0f, 1f, 0f); lookPositions[(int)currentLookDirection] = pos; break; } case LookDirection.Back: { pos.X = orgPos.X - MousePosX * CamSpeedMultiplier * CamLX * m; pos.Y = orgPos.Y - MousePosY * CamSpeedMultiplier * -1.0f * m; pos.Z = orgPos.Z - MousePosX * CamSpeedMultiplier * CamLZ * m; cameraMatrix = Matrix4.LookAt(pos.X, pos.Y, pos.Z, pos.X, pos.Y, pos.Z + 12500f, 0f, 1f, 0f); lookPositions[(int)currentLookDirection] = pos; break; } } RaisePerspectiveChanged(); lastMouseX = mouseX; lastMouseY = mouseY; // Console.WriteLine("CamAngleX = " + CamAngleX + ", CamAngleY = " + CamAngleY); // setRotationFromLookAt(); } private void UpdateCameraMatrixWithMouse_FLY(int mouseX, int mouseY, ref Matrix4 cameraMatrix) { if (resetMouse) { lastMouseX = mouseX; lastMouseY = mouseY; resetMouse = false; } int MousePosX = mouseX - lastMouseX; int MousePosY = mouseY - lastMouseY; CamAngleX = CamAngleX + 0.01f * MousePosX; // This next part isn't neccessary, but it keeps the Yaw rotation value within [0, 2*pi] which makes debugging simpler. if (CamAngleX > TAU) { CamAngleX -= TAU; } else if (CamAngleX < 0f) { CamAngleX += TAU; } // Lock pitch rotation within the bounds of [-3.1399.0, -0.0001], otherwise the LookAt function will snap to the // opposite side and reverse mouse looking controls. CamAngleY = Clampf(CamAngleY + 0.01f * MousePosY, -3.1399f, -0.0001f); lastMouseX = mouseX; lastMouseY = mouseY; OrientateCam(CamAngleX, CamAngleY); UpdateMatrix(ref cameraMatrix); // Console.WriteLine("CamAngleX = " + CamAngleX + ", CamAngleY = " + CamAngleY); // setRotationFromLookAt(); } private void UpdateCameraOffsetWithMouse_FLY(Vector3 orgPos, int mouseX, int mouseY, int w, int h, ref Matrix4 cameraMatrix) { if (resetMouse) { lastMouseX = mouseX; lastMouseY = mouseY; resetMouse = false; } int MousePosX = -mouseX + lastMouseX; int MousePosY = -mouseY + lastMouseY; double pitch_Renamed = CamAngleY - Math.PI / 2d; double yaw_Renamed = CamAngleX - Math.PI / 2d; float CamLX = (float)Math.Sin(yaw_Renamed); float CamLZ = (float)-Math.Cos(yaw_Renamed); pos.X = orgPos.X - MousePosX * CamSpeedMultiplier * CamLX * 6.0f; pos.Y = orgPos.Y - MousePosY * CamSpeedMultiplier * -1.0f * 6.0f; pos.Z = orgPos.Z - MousePosX * CamSpeedMultiplier * CamLZ * 6.0f; OrientateCam(CamAngleX, CamAngleY); UpdateMatrix(ref cameraMatrix); } public void UpdateCameraOffsetDirectly(int horz_amount, int vert_amount, ref Matrix4 cameraMatrix) { if (myCamMode == CameraMode.ORBIT) { UpdateCameraOffsetDirectly_ORBIT((int)(horz_amount / 5d), (int)(vert_amount / 5d), ref cameraMatrix); } else { // Console.WriteLine(MousePosX+","+ MousePosY); double pitch_Renamed = CamAngleY - Math.PI / 2d; double yaw_Renamed = CamAngleX - Math.PI / 2d; float CamLX = (float)Math.Sin(yaw_Renamed); // float CamLY = (float)Math.Cos(pitch); float CamLZ = (float)-Math.Cos(yaw_Renamed); pos.X += horz_amount * CamSpeedMultiplier * CamLX; pos.Y += vert_amount * CamSpeedMultiplier * -1.0f; pos.Z += horz_amount * CamSpeedMultiplier * CamLZ; OrientateCam(CamAngleX, CamAngleY); UpdateMatrix(ref cameraMatrix); } } private void UpdateCameraOffsetDirectly_ORBIT(int moveSpeedX, int moveSpeedY, ref Matrix4 cameraMatrix) { int MousePosX = moveSpeedX; int MousePosY = moveSpeedY; orbitTheta += MousePosX * 0.01f * CamSpeedMultiplier; orbitPhi -= MousePosY * 0.01f * CamSpeedMultiplier; orbitPhi = Clampf(orbitPhi, -1.57f, 1.57f); UpdateOrbitCamera(ref cameraMatrix); } private void UpdateCameraMatrixWithMouse_ORBIT(int mouseX, int mouseY, ref Matrix4 cameraMatrix) { UpdateCameraOffsetWithMouse_ORBIT(mouseX, mouseY, ref cameraMatrix); } private void UpdateCameraOffsetWithMouse_ORBIT(int mouseX, int mouseY, ref Matrix4 cameraMatrix) { if (resetMouse) { lastMouseX = mouseX; lastMouseY = mouseY; resetMouse = false; } int MousePosX = -mouseX + lastMouseX; int MousePosY = -mouseY + lastMouseY; orbitTheta += MousePosX * 0.01f * CamSpeedMultiplier; orbitPhi -= MousePosY * 0.01f * CamSpeedMultiplier; orbitPhi = Clampf(orbitPhi, -1.57f, 1.57f); UpdateOrbitCamera(ref cameraMatrix); lastMouseX = mouseX; lastMouseY = mouseY; } private void UpdateCameraMatrixWithScrollWheel_ORBIT(int amt, ref Matrix4 cameraMatrix) { orbitDistance -= amt; if (orbitDistance < 300.0f) { orbitDistance = 300.0f; } UpdateOrbitCamera(ref cameraMatrix); } public void ResetMouseStuff() { resetMouse = true; } private System.Numerics.Vector3 CalculateCenterPositionOfObjects(ICameraPoint[] objs) { if (objs.Length <= 1) { var obj = objs.FirstOrDefault(); if (obj is null) { return System.Numerics.Vector3.Zero; } else { return new System.Numerics.Vector3(obj.Position.X, obj.Position.Y, obj.Position.Z); } } float? maxX = default; float? maxY = default; float? maxZ = default; float? minX = default; float? minY = default; float? minZ = default; foreach (ICameraPoint obj in objs) { var pos = obj.Position; if (maxX is null || pos.X > maxX) maxX = pos.X; if (maxY is null || pos.Y > maxY) maxY = pos.Y; if (maxZ is null || pos.Z > maxZ) maxZ = pos.Z; if (minX is null || pos.X < minX) minX = pos.X; if (minY is null || pos.Y < minY) minY = pos.Y; if (minZ is null || pos.Z < minZ) minZ = pos.Z; } if (maxX is null) maxX = 0; if (maxY is null) maxY = 0; if (maxZ is null) maxZ = 0; if (minX is null) minX = 0; if (minY is null) minY = 0; if (minZ is null) minZ = 0; var upper = new System.Numerics.Vector3((float)maxX, (float)maxY, (float)maxZ); var lower = new System.Numerics.Vector3((float)minX, (float)minY, (float)minZ); var middle = (upper + lower) / 2f; return middle; } private float CalculateCenterYRotationOfObjects(ICameraPoint[] objs) { if (objs.Length <= 1) { var obj = objs.FirstOrDefault(); if (obj is null) { return 0f; } else { return obj.Rotation.Y; } } var yRot = new List(); foreach (ICameraPoint obj in objs) yRot.Add(obj.Rotation.Y); return yRot.Average(); } private ICameraPoint[] GetSelectedObject() { var e = new NeedSelectedObjectEventArgs(); NeedSelectedObject?.Invoke(this, e); var stopw = new Stopwatch(); stopw.Start(); while (!e.HasObjectSetted && stopw.ElapsedMilliseconds <= 1000L) Application.DoEvents(); stopw.Stop(); return e.Points; } // C A P S E L T C L A S S E S public class NeedSelectedObjectEventArgs : EventArgs { private bool _HasObjectSetted = false; public bool HasObjectSetted { get { return _HasObjectSetted; } } private ICameraPoint[] _Points = null; public ICameraPoint[] Points { get { return _Points; } set { _Points = value; _HasObjectSetted = true; } } } } }