0
\$\begingroup\$

Using DX11, SimpleMath I am building a isometric game like Diablo 3 in 3D and I want to use a perspective camera that emulates their top down view.

Projection Matrix: CreatePerspectiveFieldOfView(1, width/height, 0.1f, 100.0f);

But after this I am a bit unsure how I am suppose to rotate the camera. I assume I could just do p - Vector3(10, 10, 10) to get a 45 degree angle at any given p.

How do I properly position, and rotate the camera and point it at position p, to mimic Diablo 3 view?

\$\endgroup\$
2
  • \$\begingroup\$ This answer may be of help with the angles. As for the use of a perspective projection instead of an orthographic, I have not tried this. \$\endgroup\$
    – Vaillancourt
    Commented May 31, 2018 at 4:02
  • \$\begingroup\$ IIRC Diablo 3 camera was perspective, Diablo 2 offered switch between perspective and isometric, and Diablo 1 was isometric. \$\endgroup\$
    – Kromster
    Commented May 31, 2018 at 6:31

1 Answer 1

1
\$\begingroup\$

As mentioned, D3 is not in true Isometric view, it's merely at an angle. Isometric (orthographic) ignores the Z axis and thus and no true depth in the shot. It's hard to describe without just playing with it.

I find it much easier to work in 3d because the math for everything being displayed makes more sense to me.

Regardless, I posted a similar answer to another post of my camera code:

public class UlmerCam3D
    {
        public Vector3 CameraTarget { get { return tar; } set { tar = value; } }
        public Vector3 CameraPosition { get { return pos; } set { pos = value; } }
        private Vector3 Up { get; } = new Vector3(0.0f, 1.0f, 0.0f);

    // a variable is modified easier in this case than a property
    private Vector3 pos;
    private Vector3 tar;

    // phi is the angle from the vertical axis (Y-axis) expressed as radians
    // NOTE: Because of the way it is oriented and the fact we don't want an upside-down camera,
    // phi is *always* negative, between 0 and Pi. It does not "roll over" when changed beyond this, but instead "sticks"
    public float phi;

    // theta is the angle from the horizontal axis (X-axis) expressed as radians
    // because of it's orientation, theta is always positive, between 0 and 2-Pi
    // This min and max value roll into eachother when changed
    public float theta;

    // the distance between the Camera Position and Camera Target, used to calculate where the target is
    public float radius;

    // The graphics device and the member variables that are related to it
    private static GraphicsDevice gd;


    public UlmerCam3D(GraphicsDevice ingd)
    {
        gd = ingd;
        pos = new Vector3(0f, 100f, 0f);
        tar = new Vector3(0f, 0f, 0f);

        theta = Utilities.halfPi;
        phi = -Utilities.halfPi * .97f;

        radius = 100;

        //TODO : eventually move this copy-code somewhere
        //Target is based on Position, that way they are always synchronus
        tar.X = pos.X + radius * (float)Math.Sin(theta) * (float)Math.Cos(phi);
        tar.Z = pos.Z + radius * (float)Math.Cos(theta) * (float)Math.Cos(phi);
        tar.Y = pos.Y + radius * (float)Math.Sin(phi);
    }

    const float FIELD_OF_VIEW = Utilities.halfPi;
    public Matrix BuildProjection()
    {
        // TODO: Only Calculate if it needs updating
        return Matrix.CreatePerspectiveFieldOfView(
                           FIELD_OF_VIEW,
                           gd.DisplayMode.AspectRatio,
                           1f, 500f);
    }

    public Matrix BuildView()
    {
        // TODO: Only Calculate if it needs updating
        return Matrix.CreateLookAt(pos, tar, new Vector3(0f, 1f, 0f));
    }

    public Matrix BuildWorld()
    {
        return Matrix.Identity;// CreateWorld(tar, Vector3.Forward, Vector3.Up);
    }


    private bool updateCamera = true;
    public void HandleKeyboardInput()
    {
        // TODO: Mark that the camera pos or target changed when it does

        bool w = Keyboard.GetState().IsKeyDown(Keys.W);
        bool a = Keyboard.GetState().IsKeyDown(Keys.A);
        bool s = Keyboard.GetState().IsKeyDown(Keys.S);
        bool d = Keyboard.GetState().IsKeyDown(Keys.D);

        if (!(w || a || s || d))
        {
            return;
        }

        updateCamera = true;
        bool shift = Keyboard.GetState().IsKeyDown(Keys.LeftShift);

        if (!shift)
        {
            if (w)
            {
                pos.X += (float)Math.Sin(theta);
                pos.Z += (float)Math.Cos(theta);
            }
            if (s)
            {
                pos.X -= (float)Math.Sin(theta);
                pos.Z -= (float)Math.Cos(theta);
            }

            if (a)
            {
                pos.X += (float)Math.Cos(theta);
                pos.Z -= (float)Math.Sin(theta);
            }
            if (d)
            {
                pos.X -= (float)Math.Cos(theta);
                pos.Z += (float)Math.Sin(theta);
            }
        }
        else
        {
            if (w && phi < -Utilities.thirdPi)
            {
                phi += (float)Math.PI / 180;
            }
            if (s && phi > Utilities.halfPi * -0.97)
            {
                phi -= (float)Math.PI / 180;
            }

            if (a)
            {
                theta += (float)Math.PI / 180;
                if (theta > Math.PI * 2)
                {
                    theta = 0;
                }
            }
            if (d)
            {
                theta -= (float)Math.PI / 180;
                if (theta < 0)
                {
                    theta = (float)Math.PI * 2;
                }
            }
        }

        //Target is based on Position, that way they are always synchronus
        tar.X = pos.X + radius * (float)Math.Sin(theta) * (float)Math.Cos(phi);
        tar.Z = pos.Z + radius * (float)Math.Cos(theta) * (float)Math.Cos(phi);
        tar.Y = pos.Y + radius * (float)Math.Sin(phi);
    }

This is technically in my monogame C# project, but is based on old DirectX 9 code I wrote way back when. Unfortunately this is way more readable than my C++ code from them and it doesn't require any hacks.

For the most part you just want the math for the Target based on the Position; then simply set Phi to -Pi/4 and Theta to Pi/4 (any multiple of it) and you'll be looking at a 45 degree plane down at 45 degrees.

Side note: You should stop thinking in degrees. I highly recommend memorizing the Unit Circle (https://www.mathsisfun.com/geometry/unit-circle.html) and getting used to thinking of Angles in terms of P (http://math.rice.edu/~pcmi/sphere/drg_txt.html.)

In your case, Pi/4 is 45 degrees, which means you want to adjust the camera "down" Pi/4, and then adjust it "over" or "around" Pi/4.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you! Now I will have to play with the math to try and mimic D3 camera position/degrees. \$\endgroup\$
    – Chris
    Commented May 31, 2018 at 17:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .