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 May 31 '18 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 May 31 '18 at 6:31
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 May 31 '18 at 17:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.