Correcting Perspective Projections in 3D Graphics: 2009Scape Ground Items

One of the most critical operations in computer graphics is the projection of a 3D world onto a 2D plane. This operation allows 3D objects to be displayed on conventional 2D screens, enabling us to play 3D video games or run simulations. This transformation is achieved through perspective projections. However, issues can arise when these transformations don't account for different screen sizes or aspect ratios, resulting in skewed and distorted images. In this article, we will delve into the process of correcting these perspective distortions.

Perspective Projections: A Primer

Perspective projection is a type of projection in which the 3D coordinates of an object are transformed into 2D coordinates. It creates an illusion of depth and distance. The further away an object is from the viewer, the smaller it appears on the screen.

However, the proportions of objects can sometimes be distorted due to different screen sizes and aspect ratios. The goal of our investigation was to correct this distortion.

A typical function for creating a perspective projection looks like this:

// Method to set the projection matrix used in OpenGL rendering
private static void setProjectionMatrix(
    float left,      // arg0
    float right,     // arg1
    float bottom,    // arg2
    float top,       // arg3
    float nearClip,  // arg4
    float farClip    // arg5
) {
    float nearClipDouble = nearClip * 2.0F;

    // Set the elements of the projection matrix
    matrix[0] = nearClipDouble / (right - left);
    matrix[1] = 0.0F;
    matrix[2] = 0.0F;
    matrix[3] = 0.0F;
    matrix[4] = 0.0F;
    matrix[5] = nearClipDouble / (top - bottom);
    matrix[6] = 0.0F;
    matrix[7] = 0.0F;
    matrix[8] = (right + left) / (right - left);
    matrix[9] = (top + bottom) / (top - bottom);
    matrix[10] = -(farClip + nearClip) / (farClip - nearClip);
    matrix[11] = -1.0F;
    matrix[12] = 0.0F;
    matrix[13] = 0.0F;
    matrix[14] = -(nearClipDouble * farClip) / (farClip - nearClip);
    matrix[15] = 0.0F;

    // Load the created matrix into OpenGL
    gl.glLoadMatrixf(matrix, 0);

}

Incorporating Field of View (FOV)

Field of view (FOV) plays a critical role in 3D projections. It represents the extent of the observable world at any given moment. FOV essentially determines how zoomed in or zoomed out the final image appears. Horizontal FOV (hFOV) and Vertical FOV (vFOV) represent the FOV in the horizontal and vertical directions, respectively.

To correct the perspective projection, we started by extracting the hFOV and vFOV from the projection matrix. The projection matrix, in computer graphics, is a matrix that transforms the coordinates in the view space to the clip space. We reverse-engineered this matrix and replaced hardcoded FOV values with the extracted hFOV and vFOV. Here is a simplified example of such a function:

The horizontal and vertical FOVs (field of view) are calculated based on the values of the left, right, bottom, and top clipping planes and the near clipping distance. They represent the extent of the observable world that can be seen from the perspective of the camera at any given moment. The calculations use the formula 2 atan((right - left) / (2 nearClip)) and 2 atan((top - bottom) / (2 nearClip)) respectively, which are derived from simple trigonometry.

// Method to set the projection matrix used in OpenGL rendering
private static void setProjectionMatrix(
    float left,      // arg0
    float right,     // arg1
    float bottom,    // arg2
    float top,       // arg3
    float nearClip,  // arg4
    float farClip    // arg5
) {
    float nearClipDouble = nearClip * 2.0F;

    ...

    // Calculate the horizontal and vertical field of view
    double hFOV = 2 * Math.atan((right - left) / nearClipDouble);
    double vFOV = 2 * Math.atan((top - bottom) / nearClipDouble);

    // Convert to degrees
    hFOV = Math.toDegrees(hFOV);
    vFOV = Math.toDegrees(vFOV);

    // Load the created matrix into OpenGL
    gl.glLoadMatrixf(matrix, 0);
}

Implementing the Solution

With the hFOV and vFOV now available, we could adjust the projection of our 3D world onto the 2D screen accordingly.

Our application involves a function that transforms 3D world coordinates (entityX, entityY, entityZ) into 2D screen coordinates. The challenge was to adjust the entity's screen position (spriteDrawX, spriteDrawY) based on the hFOV and vFOV. Here's how the function looked like:

/**
 * Calculates the 2D screen position for a position in the SceneGraph.
 *
 * @param entityX The x-coordinate of the entity in the scene graph.
 * @param entityZ The z-coordinate of the entity in the scene graph.
 * @param yOffset The vertical displacement for positioning the entity.
 * @return An array containing the calculated screen coordinates [x, y] or [-1, -1] if entity is not visible.
 */
public static int[] CalculateSceneGraphScreenPosition(int entityX, int entityZ, int yOffset) {
    final int HALF_FIXED_WIDTH = 256;
    final int HALF_FIXED_HEIGHT = 167;

    int elevation = SceneGraph.getTileHeight(plane, entityX, entityZ) - yOffset;
    entityX -= SceneGraph.cameraX;
    elevation -= SceneGraph.cameraY;
    entityZ -= SceneGraph.cameraZ;

    int sinPitch = MathUtils.sin[Camera.cameraPitch];
    int cosPitch = MathUtils.cos[Camera.cameraPitch];
    int sinYaw = MathUtils.sin[Camera.cameraYaw];
    int cosYaw = MathUtils.cos[Camera.cameraYaw];

    int rotatedX = (entityZ * sinYaw + entityX * cosYaw) >> 16;
    entityZ = (entityZ * cosYaw - entityX * sinYaw) >> 16;
    entityX = rotatedX;

    int rotatedY = (elevation * cosPitch - entityZ * sinPitch) >> 16;
    entityZ = (elevation * sinPitch + entityZ * cosPitch) >> 16;
    elevation = rotatedY;

    int[] screenPos = new int[2]; // X,Y

    if (entityZ >= 50) {
        if(GetWindowMode() == WindowMode.FIXED) {
            screenPos[0] = HALF_FIXED_WIDTH + ((entityX << 9) / entityZ);
            screenPos[1] = HALF_FIXED_HEIGHT + ((elevation << 9) / entityZ);
        } else {
            Dimension canvas = GetWindowDimensions();
            double newViewDistH = (canvas.width / 2) / Math.tan(Math.toRadians(GlRenderer.hFOV) / 2);
            double newViewDistV = (canvas.height / 2) / Math.tan(Math.toRadians(GlRenderer.vFOV) / 2);
            screenPos[0] = canvas.width / 2 + (int)((entityX * newViewDistH) / entityZ);
            screenPos[1] = canvas.height / 2 + (int)((elevation * newViewDistV) / entityZ);
        }
    } else {
        screenPos[0] = -1;
        screenPos[1] = -1;
    }
    return screenPos;
}

We used the tangent function from trigonometry to calculate the new viewing distance based on hFOV and vFOV, and used it to calculate the new screen positions.

Practical Application: 2009Scape Plugin

This theory becomes practical in the realm of game development. For example, in a plugin developed for the game 2009Scape, this corrected perspective projection is used to draw text above items onscreen. This plugin is a quality of life add-on that helps players determine which item drops are valuable and which should be ignored. Accurate world-to-screen projections are crucial in ensuring the text appears at the correct on-screen location, regardless of the player's perspective or window size.

Wrapping Up

This journey into perspective projections and field of view showcases the importance of understanding fundamental computer graphics concepts. By correctly implementing these concepts, we can create a more enjoyable and realistic gaming experience, improve our computational efficiency, and broaden our understanding of 3D graphics in general. It's a testament to the power of computer graphics when well understood and well applied.

Stay tuned for more deep dives into the world of computer graphics and game development. Until next time!

Gaming in Thailand as a Foreigner

Hey gamers! It's world famous video gamer downthecrop! Back from my Thailand adventure and sharing the wisdom my globe trotting has afforded me!

General Internet Speed

This is going to depend mostly on your accommodation. I had 30/10 internet through Coax in my building which I'd estimate finished construction in the mid 2000's. You can find fiber internet in Bangkok but I'd assume most places are still Coax or DSL instead of fiber. Most internet is routed through Chang Mai anyway from the traceroutes I did while I was there.

Gaming VPN's

While there is a Thai server for League of Legends on Garena a problem you will experience in Thailand is that there just isn't a large enough population to support some games. League of Legends for example I was getting queue times exceeding 15 minutes at peak times (Noon to 11:00PM) in ranked which is extremely long compared to NA/EU/KR. To remedy this you are going to need to select the closest server with an active playerbase for your favorite games! I played mostly on Vietnam for League of Legends. Although you can simply change the region of the Garena Launcher and download any game you want there is IP based region lock for some games. League of Legends falls into these IP locked games. Because of this we are going to need to use a VPN.

Generally VPN's aren't very good for gaming. Due to oversold traffic and a lack of fast localized routers to handle your traffic you are going to be experiencing higher latency playing through a VPN than without one. There are a few VPN providers that SEA use to get their game on!

  • Mudfish
  • VietPN
  • PingBooster

Note: Exitlag and WTFast have very few servers in SEA so I wouldn't recommended them as a first choice for this use case but they will work.

For me I used VietPN and it cost me $5 a month which was a great price. Fast servers to Vietnam which is region locked for League of Legends. I would have preferred to play to China (Ionia probably the worst choice for server because it's so far away but there are many regions in China which are closer and would still have more players than TH or SG server which were the two best choices for ping.

Anyway I'm not spell checking this I just wanted to document for anyone else that is thinking of gaming in Thailand!

Lytical: OP.GG for Garena and China (SEA) – League of Legends Statistics and Profile Search

https://lytical.app/

Lytical is an open source League of Legends statistics and profile analytics tool for all regions including Garena & WeGame

https://lytical.app

Summoner Search and Profile Pages

Lytical offers OP.GG style summoner profiles which can be searched for anyone on your server. Our app offers ranked statistics and champion specific performance data for all players for Garena and WeGame. Lytical is an OP.GG alternative for all Garena regions and all Chinese regions and is an all in one replacement for websites like U.GG, Blitz.GG and Porofessor of course OP.GG.

Think of Lytical as OP.GG for Garena.

Match History & Lobby Multisearch

Lytical provides fast OP.GG style match history for any player. Kills, CS per Minute, Vision Score, and Objectives. Analyze your contribution to your own games or search high ranked players to increase your impact and win more games!

Multisearch will automatically scout your teammates in champion select and your opponents when in game. Profile lookup tools are common on NA/EU/KR servers to help avoid poor quality games. Lytical is the tool for the job to quickly understand your teammates and opponents strengths and weaknesses.

Works on All Regions

Lytical utilizes builtin API's inside of the League of Legends client. This allows our application to function on all regions, including all Garena and WeGame regions.

Supported Servers/Regions

  • Garena
  • The Philippines (PH)
  • Taiwan, Hong Kong, and Macau (TW/HK)
  • Vietnam (VN)
  • Thailand (TH)
  • Singapore, Malaysia, and Indonesia (SG/MY/ID)
  • QQ/WeGame/Tencent (CN)
  • Riot Operated (NA/EU/OCE/RU/TR/BR/LAN/KR/JP)

Open Source and Ad Free

The Lytical app is forever free and open source. It was created as an All-In-One replacement for regions that don't have access to statistics sites like OP.GG or Blitz.gg.

Built Using Vue.JS + Electron Lytical is fast and memory efficient. Any PC can run Lytical in the background with no performance impact in game for League of Legends.

This application is released under the Open Source GPLv2 license. You are welcome to contribute to this project in any way you can.

Nano for Windows (CMD and PowerShell)

Nano is my preferred editor for quick config changes. It's simple to install or even comes standard on many unix systems but what about Windows?

Install Chocolatey (Run as Administrator in PowerShell)

Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

Install Nano (Run as Administrator)

choco install nano

Now you can run nano to open the editor.