与三角形背面碰撞时如何忽略相机碰撞?

How to ignore camera collision when colliding with the back face of a triangle?

我想知道是否有人可以启发我如何在相机与三角形的背面碰撞时忽略相机碰撞。我想实现这一点,这样如果我或玩家以某种方式发现自己处于模型中,我的相机就不会卡住。这是我的相机系统的视频:

A Video My Camera System

这里是碰撞检测的代码:

private void CheckKeyboardInput(float deltaTime)
        {
            Vector3 previousCameraLocation = Camera.Location;
            KeyboardState state = Keyboard.GetCurrentState();
            Camera.IsAccelerating = state.IsPressed(Key.LeftShift);
            bool isCameraMoving = false;
            if (state.IsPressed(Key.W))
            {
                Camera.TranslateForward(deltaTime);
                isCameraMoving = true;
            }
            if (state.IsPressed(Key.S))
            {
                Camera.TranslateBackward(deltaTime);
                isCameraMoving = true;
            }
            if (state.IsPressed(Key.A))
            {
                Camera.TranslateLeft(deltaTime);
                isCameraMoving = true;
            }
            if (state.IsPressed(Key.D))
            {
                Camera.TranslateRight(deltaTime);
                isCameraMoving = true;
            }
            if (state.IsPressed(Key.E))
            {
                Camera.TranslateUp(deltaTime);
                isCameraMoving = true;
            }
            if (state.IsPressed(Key.Q))
            {
                Camera.TranslateDown(deltaTime);
                isCameraMoving = true;
            }
            /**
             * Process Camera Collisions
             */
            if (Camera.IsCollisionEnabled)
            {
                if (isCameraMoving)
                {
                    Vector3 currentCameraLocation = this.Camera.Location;
                    int totalCollisions = 0;
                    float finalCameraLocationX = Camera.Location.X;
                    float finalCameraLocationY = Camera.Location.Y;
                    float finalCameraLocationZ = Camera.Location.Z;
                    foreach (StaticGeometry geometry in this.StaticGeometry)
                    {
                        if (geometry.IsCameraCollidable)
                        {
                            if (Vector3.Distance(geometry.Location, Camera.Location) <= Camera.CollisionDistance)
                            {
                                for (int i = 0; i < geometry.Mesh.Vertices.Length; i += 3)
                                {
                                    // Get the Vertex Positions for the current Triangle
                                    Vector3 position1 = geometry.Mesh.Vertices[i].Location;
                                    Vector3 position2 = geometry.Mesh.Vertices[i + 1].Location;
                                    Vector3 position3 = geometry.Mesh.Vertices[i + 2].Location;

                                    // Create the rotation matrix using the geometry's current rotation setting.
                                    Matrix rotationMatrix = VoidwalkerMath.CreateRotationMatrix(geometry.Rotation);

                                    // Transform the Coordinate with the Rotation Matrix, then add the geometry's location
                                    Vector3 finalVertexLocation1 = Vector3.TransformCoordinate(position1, rotationMatrix) + geometry.Location;
                                    Vector3 finalVertexLocation2 = Vector3.TransformCoordinate(position2, rotationMatrix) + geometry.Location;
                                    Vector3 finalVertexLocation3 = Vector3.TransformCoordinate(position3, rotationMatrix) + geometry.Location;

                                    Vector3 translationX = new Vector3(currentCameraLocation.X, previousCameraLocation.Y, previousCameraLocation.Z);
                                    Vector3 translationY = new Vector3(previousCameraLocation.X, currentCameraLocation.Y, previousCameraLocation.Z);
                                    Vector3 translationZ = new Vector3(previousCameraLocation.X, previousCameraLocation.Y, currentCameraLocation.Z);
                                    // Test X
                                    BoundingSphere sphereX = new BoundingSphere(translationX, Camera.HardBoundsRadius);
                                    if (sphereX.Intersects(ref finalVertexLocation1, ref finalVertexLocation2, ref finalVertexLocation3))
                                    {
                                        finalCameraLocationX = previousCameraLocation.X;
                                        totalCollisions++;
                                    }
                                    // Test Y
                                    BoundingSphere sphereY = new BoundingSphere(translationY, Camera.HardBoundsRadius);
                                    if (sphereY.Intersects(ref finalVertexLocation1, ref finalVertexLocation2, ref finalVertexLocation3))
                                    {
                                        finalCameraLocationY = previousCameraLocation.Y;
                                        totalCollisions++;
                                    }
                                    // Test Z
                                    BoundingSphere sphereZ = new BoundingSphere(translationZ, Camera.HardBoundsRadius);
                                    if (sphereZ.Intersects(ref finalVertexLocation1, ref finalVertexLocation2, ref finalVertexLocation3))
                                    {
                                        finalCameraLocationZ = previousCameraLocation.Z;
                                        totalCollisions++;
                                    }

                                    /**
                                     * Edge case early termination. If the camera is already colliding
                                     * on all axis, we don't need to test for any further collisions. This seems
                                     * to only trigger when the player is wedged in a corner.
                                     */
                                    if (totalCollisions > 0) // Might not need this.
                                    {
                                        if (
                                            finalCameraLocationX == previousCameraLocation.X &&
                                            finalCameraLocationY == previousCameraLocation.Y &&
                                            finalCameraLocationZ == previousCameraLocation.Z)
                                        {
                                            this.Camera.Location = new Vector3(
                                                finalCameraLocationX,
                                                finalCameraLocationY,
                                                finalCameraLocationZ);
                                            return;
                                        }
                                    }

                                }
                            }
                        }
                    }
                    this.Camera.Location = new Vector3(finalCameraLocationX, finalCameraLocationY, finalCameraLocationZ);
                }
            }
        }

还有我的 Vertex class,只是为了向大家展示我是如何设置它的:

using SharpDX;
using System.Runtime.InteropServices;

namespace VoidwalkerEngine.Framework.DirectX.Rendering
{
    [StructLayout(LayoutKind.Sequential)]
    public struct Vertex
    {

        public static Vertex Zero = new Vertex(Vector3.Zero,Vector2.Zero,Vector3.Zero);

        public Vector3 Location;
        public Vector2 TexCoords;
        public Vector3 Normal;

        public const int Size = 32;


        public Vertex(Vector3 position, Vector2 texCoords, Vector3 normal)
        {
            this.Location = position;
            this.Normal = normal;
            this.TexCoords = texCoords;
        }

        public Vertex(Vertex other)
        {
            this.Location = other.Location;
            this.Normal = other.Normal;
            this.TexCoords = other.TexCoords;
        }

        public override string ToString()
        {
            return
                "Location: " + Location.ToString() +
                ", TexCoords: " + TexCoords.ToString() +
                ", Normal: " + Normal.ToString();
        }

    }
}

已更新:相机 Class

using SharpDX;
using System;

namespace VoidwalkerEngine.Framework.DirectX
{
    public enum CameraMode
    {
        FreeLook,
        Orbit
    }

    public class Camera
    {
        /// <summary>
        /// The name of this camera
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// The eye location of this camera
        /// </summary>
        public Vector3 Location { get; set; }
        /// <summary>
        /// The Pitch of this Camera, as Radians
        /// </summary>
        public float Pitch { get; set; }
        /// <summary>
        /// The Yaw of this Camera, as Radians
        /// </summary>
        public float Yaw { get; set; }
        /// <summary>
        /// The Roll of this Camera, as Radians
        /// </summary>
        public float Roll { get; set; }
        /// <summary>
        /// The NearZ of this Camera
        /// </summary>
        public float NearZ { get; set; }
        /// <summary>
        /// The FarZ of this Camera
        /// </summary>
        public float FarZ { get; set; }
        /// <summary>
        /// The Field of View of this Camera, value should be
        /// between 0.70 and 1.20
        /// </summary>
        public float FieldOfView { get; set; }
        public float AspectRatio { get; set; }
        public float LookSpeed { get; set; }
        public float MoveSpeed { get; set; }
        /// <summary>
        /// Determines if this Camera is currently accelerating.
        /// </summary>
        public bool IsAccelerating { get; set; }
        /// <summary>
        /// The acceleration speed multiplier of this Camera.
        /// </summary>
        public float AccelerationMultiplier { get; set; }
        public CameraMode Mode { get; set; }
        public float ViewportWidth;
        public float ViewportHeight;
        public float HardBoundsRadius { get; set; } = 2.0f;
        public float CollisionDistance { get; set; } = 128;
        public bool IsCollisionEnabled { get; set; } = true;

        /// <summary>
        /// The BoundingSphere of this Camera
        /// </summary>
        public BoundingSphere HardBounds
        {
            get
            {
                return new BoundingSphere()
                {
                    Center = this.Location,
                    Radius = HardBoundsRadius
                };
            }
        }

        /// <summary>
        /// The Target Vector of this Camera
        /// </summary>
        public Vector3 Target
        {
            get
            {
                return new Vector3(
                    (float)Math.Sin(this.Yaw),
                    (float)Math.Tan(this.Pitch),
                    (float)Math.Cos(this.Yaw));
            }
        }

        /// <summary>
        /// The Frustum of this Camera
        /// </summary>
        public BoundingFrustum Frustum
        {
            get
            {
                return new BoundingFrustum(this.ModelViewProjectionMatrix);
            }
        }

        public Matrix ModelViewMatrix
        {
            get
            {
                return Matrix.LookAtLH(this.Location, Location + Target, Up);
            }
        }

        public Matrix ProjectionMatrix
        {
            get
            {
                return Matrix.PerspectiveFovRH(FieldOfView, AspectRatio, NearZ, FarZ);
            }
        }

        public Matrix ModelViewProjectionMatrix
        {
            get
            {
                return ModelViewMatrix * ProjectionMatrix;
            }
        }

        //public CardinalDirectionType Direction
        //{
        //    get
        //    {
        //        return VoidwalkerMath.GetCardinalDirection(VoidwalkerMath.ToDegrees(Yaw));
        //    }
        //}

        public Vector3 Forward
        {
            get
            {
                return new Vector3((float)Math.Cos(Pitch), 0, (float)Math.Sin(Pitch));
            }
        }

        public Vector3 Right
        {
            get
            {
                return new Vector3(Forward.Z, 0, -Forward.Z);
            }
        }

        public Vector3 Up
        {
            get
            {
                return new Vector3(-(float)Math.Sin(Roll), (float)Math.Cos(Roll), 0);
            }
        }

        public Camera()
        {

        }

        public Camera(string name)
            : this()
        {
            this.Name = name;
            this.Location = new Vector3();
        }

        public void ToOrigin()
        {
            Transform(Vector3.Zero, 0, 0, 0);
        }

        public void Transform(Vector3 location, float pitch, float yaw, float roll)
        {
            this.Location = location;
            this.Pitch = pitch;
            this.Yaw = yaw;
            this.Roll = roll;
        }

        public float GetCurrentMoveSpeed()
        {
            if (IsAccelerating)
            {
                return this.MoveSpeed * this.AccelerationMultiplier;
            }
            return this.MoveSpeed;
        }

        public void TranslateLeft(float deltaTime)
        {
            float moveSpeed = GetCurrentMoveSpeed();
            this.Location = new Vector3(
                Location.X - (float)Math.Sin(Yaw + MathUtil.PiOverTwo) * moveSpeed * deltaTime,
                Location.Y,
                Location.Z - (float)Math.Cos(Yaw + MathUtil.PiOverTwo) * moveSpeed * deltaTime);
        }

        public void TranslateRight(float deltaTime)
        {
            float moveSpeed = GetCurrentMoveSpeed();
            this.Location = new Vector3(
                Location.X + (float)Math.Sin(Yaw + MathUtil.PiOverTwo) * moveSpeed * deltaTime,
                Location.Y,
                Location.Z + (float)Math.Cos(Yaw + MathUtil.PiOverTwo) * moveSpeed * deltaTime);
        }

        public void TranslateForward(float deltaTime)
        {
            float degreesX = MathUtil.RadiansToDegrees(Pitch) * 0.01745329F; // X rotation
            float degreesY = MathUtil.RadiansToDegrees(Yaw) * 0.01745329F; // Y rotation
            float moveSpeed = GetCurrentMoveSpeed();
            this.Location = new Vector3(
                this.Location.X - (float)(moveSpeed * Math.Sin(degreesY) * Math.Cos(degreesX)) * deltaTime,
                this.Location.Y - (float)(moveSpeed * Math.Sin(degreesX)) * deltaTime,
                this.Location.Z - (float)(moveSpeed * Math.Cos(degreesY) * Math.Cos(degreesX)) * deltaTime);
        }

        public void TranslateBackward(float deltaTime)
        {

            float degreesX = MathUtil.RadiansToDegrees(Pitch) * 0.01745329F; // X rotation
            float degreesY = MathUtil.RadiansToDegrees(Yaw) * 0.01745329F; // Y rotation
            float moveSpeed = GetCurrentMoveSpeed();
            this.Location = new Vector3(
                this.Location.X + (float)(moveSpeed * Math.Sin(degreesY) * Math.Cos(degreesX)) * deltaTime,
                this.Location.Y + (float)(moveSpeed * Math.Sin(degreesX)) * deltaTime,
                this.Location.Z + (float)(moveSpeed * Math.Cos(degreesY) * Math.Cos(degreesX)) * deltaTime);
        }

        public void TransformYawPitch(float dx, float dy)
        {
            Yaw -= dx * LookSpeed;
            Pitch += dy * LookSpeed;
            const float pitchClamp = 1.56f;
            if (Pitch <= -pitchClamp)
            {
                Pitch = -pitchClamp;
            }
            if (Pitch >= pitchClamp)
            {
                Pitch = pitchClamp;
            }
        }

        public void TranslateUp(float deltaTime)
        {
            this.Location = new Vector3(
                this.Location.X,
                this.Location.Y + GetCurrentMoveSpeed() * deltaTime,
                this.Location.Z);
        }

        public void TranslateDown(float deltaTime)
        {
            this.Location = new Vector3(
                this.Location.X,
                this.Location.Y - GetCurrentMoveSpeed() * deltaTime,
                this.Location.Z);
        }

        public void LookAt(Vector3 location, float pitch, float yaw, float roll)
        {
            this.Location = location;
            this.Pitch = pitch;
            this.Yaw = yaw;
            this.Roll = roll;
        }

        public void SetAspectRatio(int width, int height)
        {
            this.ViewportWidth = width;
            this.ViewportHeight = height;
            this.AspectRatio = width / (float)height;
        }
    }
}

我已经知道我需要以某种形式比较三角形的法线。我只是不知道该怎么做。如果有人知道答案,我将不胜感激。如果需要任何其他代码,请告诉我。谢谢!

您可以使用 currentCameraLocation - previousCameraLocation 计算相机的速度,并使用简单的点积来判断您是否击中了三角形的背面:

var cameraVelocity = currentCameraLocation - previousCameraLocation;
bool hitBackFace = Vector3.Dot(cameraVelocity, triangleNormal) > 0;

您可能想用叉积计算三角形法线:

var triangleNormal = Vector3.Cross(finalVertexLocation2 - finalVertexLocation1, 
                                   finalVertexLocation3 - finalVertexLocation1);

原因是顶点法线不一定与三角形法线相同。如果您想到 phong 阴影球体上的三角形,则该三角形的 3 个顶点中的每一个都将具有不同的法向量,并且其中 none 将与三角形法向量相同。