Search code examples
3dgame-makergml

GML: 3D vector to 2D point on screen


I have a script that, yes, takes 11 arguments. It converts the mouse coordinates on-screen to a 3D vector to be used for 3D mouse coordinates. It is perfect in accuracy.

{
var mm,dX,dY,dZ,uX,uY,uZ,vX,vY,vZ,mX,mY,mZ, width, height, tFOV;
dX = argument3-argument0;
dY = argument4-argument1;
dZ = argument5-argument2;
mm = sqrt(dX*dX+dY*dY+dZ*dZ);
dX /= mm;
dY /= mm;
dZ /= mm;
uX = argument6;
uY = argument7;
uZ = argument8;
mm = uX*dX+uY*dY+uZ*dZ;
uX -= mm*dX;
uY -= mm*dY;
uZ -= mm*dZ
mm = sqrt(uX*uX+uY*uY+uZ*uZ);
uX /= mm;
uY /= mm;
uZ /= mm;
// v = u x d
vX = uY*dZ-dY*uZ;
vY = uZ*dX-dZ*uX;
vZ = uX*dY-dX*uY;
tFOV = tan(argument9*pi/360);
uX *= tFOV;
uY *= tFOV;
uZ *= tFOV;
vX *= tFOV*argument10;
vY *= tFOV*argument10;
vZ *= tFOV*argument10;
width = window_get_width();
height = window_get_height();
mX = dX+uX*(1-2*mouse_y/height)+vX*(2*mouse_x/width-1);
mY = dY+uY*(1-2*mouse_y/height)+vY*(2*mouse_x/width-1);
mZ = dZ+uZ*(1-2*mouse_y/height)+vZ*(2*mouse_x/width-1);
mm = sqrt(mX*mX+mY*mY+mZ*mZ);
global.mouse_dx = mX/mm;
global.mouse_dy = mY/mm;
global.mouse_dz = mZ/mm;
}

What I'm trying to do is reverse this script so that 14 arguments. The same 11 as this one but with 3 more representing the x, y, and z of the vector.

I know this isn't a coding language many people can really understand. You're probably used to a C, Java, Lua, Python, or something similar. This is NOT the same. The syntax is way different. mouse_x and mouse_y in 3D represent the mouse's x and y in the game window.

Anyway, I've been trying to figure it out for a long time. Can anyone help me reverse this script starting at the end and returning an array of 2 indexes, x and y? It's probably not gonna work that easily but I am trying to convert a 3D point to a 2D point involving the camera.

Here is a list of the original script's arguments:

(xfrom, yfrom, zfrom, xto, yto, zto, xup, yup, zup, angle, aspect)

xfrom, yfrom and zfrom represent the x,y,z of the camera. xto, yto and zto represent the coordinate that the camera is looking towards. xup, yup and zup is the vector in which the camera perceives as "up". In my case, 0,0,1 is used to represent positive z values as being upwards and negative z values as being downwards. angle is the horizontal fov of the camera. aspect is the width of the window divided by the height of the window.


Solution

  • Quite curiously, the author of the script that you are using (which goes by the username "Yourself" on the official forums of the program) had also made a script that does the reverse. In fact, these were usually distributed together, and I'm a little surprised that you had stumbled upon an example/post/tutorial that only had one of them.

    Regardless, there's a pretty popular example based on these scripts, which combines the shared code into a third script and then provides two scripts that modify the variables.

    Alternatively, here's a single-script version that I cleaned up a little. It returns whether the point is visible, since the points can also be at valid XY but behind the camera.

    /// c3d(xfrom, yfrom, zfrom, xto, yto, zto, xup, yup, zup, fov, aspect, px, py, pz)
    var     xfrom, yfrom, zfrom, xto, yto, zto, ux, uy, uz, fov, aspect, px, py, pz;
    xfrom = argument0; yfrom = argument1; zfrom = argument2;
    xto = argument3; yto = argument4; zto = argument5;
    ux = argument6; uy = argument7; uz = argument8;
    fov = argument9; aspect = argument10;
    px = argument11; py = argument12; pz = argument13;
    // delta:
    var dn, dx, dy, n;
    dx = xto - xfrom;
    dy = yto - yfrom;
    dz = zto - zfrom;
    n = sqrt(dx * dx + dy * dy + dz * dz);
    dx /= n; dy /= n; dz /= n; // (normalization)
    // up-vector:
    n = ux * dx + uy * dy + uz * dz;
    ux -= n * dx; uy -= n * dy; uz -= n * dz;
    // up-vector normalization:
    n = sqrt(ux * ux + uy * uy + uz * uz);
    ux /= n; uy /= n; uz /= n;
    // field-of-view:
    var tfov; tfov = tan(fov * pi / 360);
    ux *= tfov; uy *= tfov; uz *= tfov;
    // vxyz = uxyz * dxyz:
    var vx, vy, vz;
    vx = uy * dz - dy * uz;
    vy = uz * dx - dz * ux;
    vz = ux * dy - dx * uy;
    // aspect:
    afov = aspect;
    vx *= afov; vy *= afov; vz *= afov;
    // target coordinates:
    px -= xfrom; py -= yfrom; pz -= zfrom;
    // compute screen Z to ensure that the point is visible
    // (is in front rather than behind the camera):
    n = (px * dx + py * dy + pz * dz);
    if (n <= 0) {
        global.c3d_x = 0;
        global.c3d_y = -1;
        return false;
    }
    // compute and store screen XY (0..1):
    px /= n; py /= n; pz /= n;
    n = (px * vx + py * vy + pz * vz) / sqr(aspect * tan(pi / 8));
    global.c3d_x = (n + 1) / 2;
    n = (px * ux + py * uy + pz * uz) / sqr(tan(pi / 8));
    global.c3d_y = (1 - n) / 2;
    //
    return true;