• Check out the results of the Techtree Contest #19!
  • Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • Create a void inspired texture for Warcraft 3 and enter Hive's 34th Texturing Contest: Void! Click here to enter!
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!

Fast World2Screen Transform (Synced/Async)

Fast World2Screen Transform transforms world coordinates to screen coordinates and back, i.e. determine where an object at point (x, y, z) on the map is drawn for a player on the screen.

Because this transformation is dependent on a player's camera position, which is not synced, the resulting screen position will not be synced either. You have, however, the option to use the sync version, which continuously syncs the camera parameters between players, so that the synchronized screen positions can be retrieved without a delay.

World2Screen is extremely fast (~300 nanoseconds) and 99.9% accurate. Screen2World is a bit slower as it has to search for the intersection point of a ray through the world. The Screen2World function will fail if there are multiple intersection points (which usually does not happen for normal camera angles).
Contents

SyncedCamera (Binary)

World2Screen (Binary)

World2ScreenSync (Binary)

World2ScreenTransform (Map)

Reviews
Wrda
Math beyond me, magic numbers, a recipe for great explosion of excitedness. Looks optimized, and works spectacularly. Approved
I've decided to refactor the old mouse position library and made this small snippet that concerns itself only with transforming from world to screen coordinates and back. Because, for purely async mouse screen positions, @moddiemads' system (which I'm sure he'll update any minute now) is superior, my system should be used in a context where either you need to know the screen positions of objects other than the mouse or the mouse positions need to be synced.
 
The function works for every camera angle and rotation. The frame not following is probably because of how the mouse natives work; the mouse position only updates when you move the mouse cursor, not when the camera moves. The frame can also not move into the widescreen area.
 
The function works for every camera angle and rotation. The frame not following is probably because of how the mouse natives work; the mouse position only updates when you move the mouse cursor, not when the camera moves. The frame can also not move into the widescreen area.
With a fixed camera, fully zoomed in, these are the results I am getting in the test map:

middle of the screen OK

WC3ScrnShot_093024_220406_000.png

top right (not widescreen area) Invalid
WC3ScrnShot_093024_220401_000.png
bottom left (not widescreen area) Invalid

WC3ScrnShot_093024_220404_000.png

the margin of error increases the further I move from the center of the screen
 
The Field of View is in fact different in Reforged than it is in Classic (77° vs. 70°). I tested it with the Reforged field of view and I get an error, but it's actually the opposite of the direction of your error, so I'm even more confused now.

View attachment 490547
That’s interesting and indeed very confusing, also is there a way to detect if the player is using SD or HD graphics?
 
The field of view changes for the Reforged camera as you zoom in and out. It is constant for the SD graphics camera. So, the HD camera zooms in, the SD camera moves the eye closer to the target. So I can set the field of view to different values and try to find the correct magic numbers and then derive the general solution for it.
 
Nice, can you share it?
I shared it to two people on hive dms because they know I use wurst and they do too, so the "conversion" is not really to Jass but to Wurst xD (sorry for confusion) so doubt it will be useful to share :necry:

And I had AI help with the math because I suck at math except basics (used ur works as reference), once I tested it thoroughly and the other guys too, then I will be confident to share it if still desired. But it works for now at least.

For example on Sample Code 1, I have absolutely no clue what is going on here

Wurst:
public function screen2WorldSynced(real x, real y, player whichPlayer) returns WorldIntersection
    let pid = ensureCameraState(whichPlayer)
    if pid < 0
        return WorldIntersection(0., 0., 0., false)
    let a = (x - 0.4) * scaleFactor[pid]
    let b = (0.42625 - yCenterScreenShift[pid] - y) * scaleFactor[pid]
    let denominator = safeSqrt(1. + a * a + b * b)
    if denominator <= SQRT_EPSILON
        return WorldIntersection(eyeX[pid], eyeY[pid], eyeZ[pid], false)
    let nx = 1. / denominator
    var ny = safeSqrt(1. - (1. + b * b) * nx * nx)
    var nz = safeSqrt(1. - nx * nx - ny * ny)
    if a > 0.
        ny = -ny
    if b < 0.
        nz = -nz
    let nxPrime = cosAttackCosRot[pid] * nx - sinRot[pid] * ny + sinAttackCosRot[pid] * nz
    let nyPrime = cosAttackSinRot[pid] * nx + cosRot[pid] * ny + sinAttackSinRot[pid] * nz
    let nzPrime = -sinAttack[pid] * nx + cosAttack[pid] * nz
    if nzPrime.abs() < SQRT_EPSILON
        return WorldIntersection(eyeX[pid], eyeY[pid], eyeZ[pid], false)
    let terrainGuess = getTerrainZ(eyeX[pid], eyeY[pid])
    var xGuess = eyeX[pid] + nxPrime * (eyeZ[pid] - terrainGuess) / nzPrime
    var yGuess = eyeY[pid] + nyPrime * (eyeZ[pid] - terrainGuess) / nzPrime
    var zWorld = getTerrainZ(xGuess, yGuess)
    var deltaZ = zWorld - terrainGuess
    var zGuess = zWorld
    int i = 0
    while (deltaZ > 1. or deltaZ < -1.) and i < 50
        let zWorldOld = zWorld
        let deltaZOld = deltaZ
        xGuess = eyeX[pid] + nxPrime * (eyeZ[pid] - zGuess) / nzPrime
        yGuess = eyeY[pid] + nyPrime * (eyeZ[pid] - zGuess) / nzPrime
        zWorld = getTerrainZ(xGuess, yGuess)
        deltaZ = zWorld - zGuess
        if (deltaZOld - deltaZ).abs() < SQRT_EPSILON
            return WorldIntersection(xGuess, yGuess, zWorld, false)
        zGuess = (deltaZOld * zWorld - deltaZ * zWorldOld) / (deltaZOld - deltaZ)
        i += 1
    return WorldIntersection(xGuess, yGuess, zWorld, i < 50)

Wurst:
public class SyncedCamera
    static player localPlayer = null
    static boolean array ready
    static real array eyeX
    static real array eyeY
    static real array eyeZ
    static real array angleOfAttack
    static real array rotation
    static real array fieldOfView
    static real array targetDistance

    static function getIndex(player whichPlayer) returns int
        return whichPlayer != null ? whichPlayer.getId() : -1

    static function differenceExceeds(real currentValue, real storedValue) returns boolean
        return realAbs(currentValue - storedValue) > EPSILON

    static function onSync()
        let data = BlzGetTriggerSyncData()
        var parsedPlayerIndex = -1
        var fieldIndex = 0
        for string part in data.split(",")
            if fieldIndex == 0
                parsedPlayerIndex = part.toInt() - 1
            else if parsedPlayerIndex >= 0 and parsedPlayerIndex < bj_MAX_PLAYER_SLOTS
                switch fieldIndex
                    case 1
                        eyeX[parsedPlayerIndex] = part.toReal()
                    case 2
                        eyeY[parsedPlayerIndex] = part.toReal()
                    case 3
                        eyeZ[parsedPlayerIndex] = part.toReal()
                    case 4
                        angleOfAttack[parsedPlayerIndex] = part.toReal()
                    case 5
                        rotation[parsedPlayerIndex] = part.toReal()
                    case 6
                        fieldOfView[parsedPlayerIndex] = part.toReal()
                    case 7
                        targetDistance[parsedPlayerIndex] = part.toReal()
            fieldIndex += 1
        if parsedPlayerIndex >= 0 and parsedPlayerIndex < bj_MAX_PLAYER_SLOTS
            ready[parsedPlayerIndex] = true
 
Back
Top