Simple World2Screen

This code implements a world-to-screen coordinate transformation system, essential for converting 3D game world positions into 2D screen coordinates. Here’s what it does:

The WorldToScreen method takes a 3D vector and window handle, performing these operations:

  1. Reads camera data (position, FOV, view matrix) from game memory using Windows API calls
  2. Calculates relative position between target and camera
  3. Applies view matrix transformations to convert world space to camera space
  4. Projects the transformed coordinates to screen space using FOV and aspect ratio calculations

The code includes memory reading utilities and error handling for invalid transformations (objects behind camera or outside screen bounds).

Technical note: The transformation pipeline uses matrix operations and perspective projection, with the final output being screen coordinates normalized to the window dimensions. Failed transformations return Vector2.Zero.

The layout of the memory and offsets will be different, depending on the source used. In this example, we use WoW. But the calculation will stay the same.

I left in all Logger calls, as this are good points in the code to debug the code and check for correct functionality.

C#
using System;
using System.Numerics;
using System.Drawing;
using System.Runtime.InteropServices;

namespace VisObj.Utils
{
    public static class WorldToScreenUtils
    {
        private const float DEG_2_RAD = 0.0174532925f;

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr GetLastError();

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        private static (int width, int height) GetWindowDimensions(int windowHandle)
        {
            RECT clientRect;
            if (!GetClientRect((IntPtr)windowHandle, out clientRect))
            {
                //int error = GetLastError();
                //Logger.Log($"GetClientRect failed. Error: {error}");
                return (800, 600); // Fallback values
            }

            int width = clientRect.Right - clientRect.Left;
            int height = clientRect.Bottom - clientRect.Top;

            //Logger.Log($"Client RECT: Left={clientRect.Left}, Top={clientRect.Top}, Right={clientRect.Right}, Bottom={clientRect.Bottom}");
            //Logger.Log($"Calculated dimensions: Width={width}, Height={height}");

            return (width, height);
        }

        public static Vector2 WorldToScreen(Vector3 worldPosition, int windowHandle)
        {
            //Logger.Log($"Input Position: X={worldPosition.X}, Y={worldPosition.Y}, Z={worldPosition.Z}");

            var (windowWidth, windowHeight) = GetWindowDimensions(windowHandle);
            //Logger.Log($"Window: Width={windowWidth}, Height={windowHeight}");

            var (cameraPosition, cameraFOV, viewMatrix) = GetCameraData();
            //Logger.Log($"Camera Position: X={cameraPosition.X}, Y={cameraPosition.Y}, Z={cameraPosition.Z}");
            //Logger.Log($"Camera FOV: {cameraFOV}");
            //Logger.Log($"View Matrix:\n" +
             //         $"[{viewMatrix.M11}, {viewMatrix.M12}, {viewMatrix.M13}]\n" +
             //         $"[{viewMatrix.M21}, {viewMatrix.M22}, {viewMatrix.M23}]\n" +
             //         $"[{viewMatrix.M31}, {viewMatrix.M32}, {viewMatrix.M33}]");

            // Calculate position difference
            Vector3 difference = new Vector3(
                worldPosition.X - cameraPosition.X,
                worldPosition.Y - cameraPosition.Y,
                worldPosition.Z - cameraPosition.Z
            );
            //Logger.Log($"Difference Vector: X={difference.X}, Y={difference.Y}, Z={difference.Z}");

            // Check if object is in front of camera
            float product = difference.X * viewMatrix.M11 +
                          difference.Y * viewMatrix.M12 +
                          difference.Z * viewMatrix.M13;
            //Logger.Log($"Product: {product}");

            if (product < 0)
            {
                //Logger.Log("Object behind camera");
                return Vector2.Zero;
            }

            // Get inverted view matrix
            if (!Matrix4x4.Invert(viewMatrix, out Matrix4x4 invertedMatrix))
            {
                //Logger.Log("Matrix inversion failed");
                return Vector2.Zero;
            }
            //Logger.Log($"Inverted Matrix:\n" +
            //          $"[{invertedMatrix.M11}, {invertedMatrix.M12}, {invertedMatrix.M13}]\n" +
            //          $"[{invertedMatrix.M21}, {invertedMatrix.M22}, {invertedMatrix.M23}]\n" +
            //          $"[{invertedMatrix.M31}, {invertedMatrix.M32}, {invertedMatrix.M33}]");

            // Calculate view vector
            Vector3 view = new Vector3(
                invertedMatrix.M11 * difference.X + invertedMatrix.M21 * difference.Y + invertedMatrix.M31 * difference.Z,
                invertedMatrix.M12 * difference.X + invertedMatrix.M22 * difference.Y + invertedMatrix.M32 * difference.Z,
                invertedMatrix.M13 * difference.X + invertedMatrix.M23 * difference.Y + invertedMatrix.M33 * difference.Z
            );
            //Logger.Log($"View Vector: X={view.X}, Y={view.Y}, Z={view.Z}");

            // Calculate camera vector and screen center
            Vector3 camera = new Vector3(-view.Y, -view.Z, view.X);
            Vector2 screenCenter = new Vector2(windowWidth / 2.0f, windowHeight / 2.0f);
            //Logger.Log($"Camera Vector: X={camera.X}, Y={camera.Y}, Z={camera.Z}");
            //Logger.Log($"Screen Center: X={screenCenter.X}, Y={screenCenter.Y}");

            // Calculate aspect ratio and FOV
            Vector2 aspect = new Vector2(
                screenCenter.X / (float)Math.Tan((cameraFOV * 54.0f / 2.0f) * DEG_2_RAD),
                screenCenter.Y / (float)Math.Tan((cameraFOV * 34.0f / 2.0f) * DEG_2_RAD)
            );
            //Logger.Log($"Aspect: X={aspect.X}, Y={aspect.Y}");

            // Calculate screen position
            Vector2 screenPos = new Vector2(
                screenCenter.X + camera.X * aspect.X / camera.Z,
                screenCenter.Y + camera.Y * aspect.Y / camera.Z
            );
            //Logger.Log($"Screen Position: X={screenPos.X}, Y={screenPos.Y}");

            // Check if position is within screen bounds
            if (screenPos.X < 0 || screenPos.Y < 0 ||
                screenPos.X > windowWidth || screenPos.Y > windowHeight)
            {
                //Logger.Log("Position outside screen bounds");
                return Vector2.Zero;
            }

            return screenPos;
        }

        private static (Vector3 position, float fov, Matrix4x4 viewMatrix) GetCameraData()
        {
            var baseAddr = MemoryReader.BaseAddress;
            //Logger.Log($"Base Address: 0x{baseAddr.ToInt64():X}");

            // Get camera pointer using offsets from MemoryReader
            var cameraBase = (IntPtr)MemoryReader.ReadUInt64(IntPtr.Add(baseAddr, (int)MemoryReader.Offsets.CAMERA));
            //Logger.Log($"Camera Base: 0x{cameraBase.ToInt64():X}");

            var camera = (IntPtr)MemoryReader.ReadUInt64(IntPtr.Add(cameraBase, (int)MemoryReader.Offsets.CAMERA_2));
            //Logger.Log($"Camera: 0x{camera.ToInt64():X}");

            // Read camera position
            Vector3 position = new Vector3(
                MemoryReader.ReadFloat(IntPtr.Add(camera, 0x10)),
                MemoryReader.ReadFloat(IntPtr.Add(camera, 0x14)),
                MemoryReader.ReadFloat(IntPtr.Add(camera, 0x18))
            );

            // Read FOV
            float fov = MemoryReader.ReadFloat(IntPtr.Add(camera, 0x40));

            // Read the view matrix data
            var matrixData = MemoryReader.ReadBytes(IntPtr.Add(camera, 0x1C), 36);
            //Logger.Log($"Matrix Data Length: {matrixData.Length}");
            //Logger.Log($"First few bytes: {BitConverter.ToString(matrixData, 0, 12)}");

            // Construct view matrix
            Matrix4x4 viewMatrix = new Matrix4x4
            {
                M11 = BitConverter.ToSingle(matrixData, 0),
                M12 = BitConverter.ToSingle(matrixData, 4),
                M13 = BitConverter.ToSingle(matrixData, 8),
                M21 = BitConverter.ToSingle(matrixData, 12),
                M22 = BitConverter.ToSingle(matrixData, 16),
                M23 = BitConverter.ToSingle(matrixData, 20),
                M31 = BitConverter.ToSingle(matrixData, 24),
                M32 = BitConverter.ToSingle(matrixData, 28),
                M33 = BitConverter.ToSingle(matrixData, 32),
                M44 = 1
            };

            return (position, fov, viewMatrix);
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *