2

I am trying to get world Coordinates from screen coordinates with OpenGl. I made some research but values returned are uncoherent. In fact in my world I have a surface at Z=0 and I want to have the 3d coordinates of a point in that surface so with Z=0. Here my research :

float cursZ = 1;
long posX, posY, posZ = 0;

int viewport[4];
viewport[0] = Originex;
viewport[1] = Originey;
viewport[2] = largeFen;
viewport[3] = hautFen;
CMatrix4 modelView;
CMatrix4 proj;
CMatrix4 A;
CMatrix4 out;

typeCoorDepl proche = 100.;
typeCoorDepl loin = largeFen + hautFen;

Renderer.GetMatrix(MAT_MODELVIEW, modelView);
Renderer.GetMatrix(MAT_PROJECTION, proj);

modelView.LookAt(TVector3T(sendAngleX, angleY, SendAngleZ), TVector3T(0, 0, 0));

switch (modeVue)// get MAT_PROJECTION
{
case 1: // mode isometrique rotation centree sur l'ecran
    proj.OrthoOffCenter(-(largeFen / 2.0f), (hautFen / 2.0f), (largeFen / 2.0f), -(hautFen / 2.0f), proche, loin);
    break;
case 2: // mode conique rotation centree sur l'ecran
    proj.PerspectiveFOV((70.0*pi / 180) / 2, largeFen / hautFen, proche, loin);
    break;
case 3: // mode isometrique rotation centree sur l'ecran
    proj.OrthoOffCenter(-(largeFen / 2.0f), (hautFen / 2.0f), (largeFen / 2.0f), -(hautFen / 2.0f), proche, loin);
    break;
case 4: // mode conique rotation centree sur l'objet et non sur l'ecran
    proj.PerspectiveFOV((70.0*pi / 180), largeFen / hautFen, proche, loin);
    break;
default:
    break;
}

cursY = hautFen - cursY; // windows start from top-left as openGL from bottom-left
double posx, posy, posz;

float m[16];//, A[16];
float in[4];//, out[4];

A = proj*modelView;

A.Inverse();

in[0] = cursX;
in[1] = cursY;
in[2] = cursZ;
in[3] = 1.0;

/* Map x and y from window coordinates */
in[0] = (in[0] - viewport[0]) / viewport[2];
in[1] = (in[1] - viewport[1]) / viewport[3];

/* Map to range -1 to 1 */
in[0] = in[0] * 2 - 1;
in[1] = in[1] * 2 - 1;
in[2] = in[2] * 2 - 1;

CMatrix4 forTestIn;
forTestIn.a11 = in[0];
forTestIn.a12 = in[1];
forTestIn.a13 = in[2];
forTestIn.a14 = in[3];

out = A*forTestIn;
out.a11 /= out.a14;
out.a12 /= out.a14;
out.a13 /= out.a14;

*finPosX = out.a11;
*finPosY = out.a12;
*finPosZ = out.a13;

Here my OrthoOffCenter and PerspectiveFOV function :

inline void CMatrix4::OrthoOffCenter(typeCoorDepl Left, typeCoorDepl Top, typeCoorDepl Right, typeCoorDepl Bottom, typeCoorDepl Near, typeCoorDepl Far)
{
    a11 = 2 / (Right - Left); a12 = 0.0f;               a13 = 0.0f;           a14 = (Left + Right) / (Left - Right);
    a21 = 0.0f;               a22 = 2 / (Top - Bottom); a23 = 0.0f;           a24 = (Bottom + Top) / (Bottom - Top);
    // a31 = 0.0f;               a32 = 0.0f;               a33 = -2/(Far - Near); a34 = (Near + Far) / (Far - Near);
    // substitution Far/Near cause pb depth buffer
    a31 = 0.0f;               a32 = 0.0f;               a33 = -2/(Near - Far); a34 = (Far + Near) / (Near - Far);
    a41 = 0.0f;               a42 = 0.0f;               a43 = 0.0f; a44 = 1.0f;    
}

inline void CMatrix4::PerspectiveFOV(typeCoorDepl Fov, typeCoorDepl Ratio, typeCoorDepl Near, typeCoorDepl Far)
{
    typeCoorDepl YScale = 1.0f / std::tan(Fov / 2);
    typeCoorDepl XScale = YScale / Ratio;
    typeCoorDepl Coeff  = Far / (Far - Near);

    a11 = XScale; a12 = 0.0f;   a13 = 0.0f;  a14 = 0.0f;
    a21 = 0.0f;   a22 = YScale; a23 = 0.0f;  a24 = 0.0f;
    a31 = 0.0f;   a32 = 0.0f;   a33 = Coeff; a34 = Near * -Coeff;
    a41 = 0.0f;   a42 = 0.0f;   a43 = 1.0f;  a44 = 0.0f;
    // nb : agir sur a14, a24 déplace le centre "de rotation"
}
3
  • show your implementations for OrthoOffCenter and PerspectiveFOV Commented Jul 7, 2017 at 8:02
  • assuming that all your computation of modelview and projection are correct, you wrote A = projmodelView; instead A = modelViewproj;, and after you need to apply the inverse (as you put it).
    – esmitt
    Commented Jul 7, 2017 at 8:31
  • If this isn't a homework assignment and/or you don't care about the math you can leverage gluUnproject.
    – Aeluned
    Commented Jul 8, 2017 at 15:30

1 Answer 1

2

If you draw something on the viewport the following transformations are performed:

  1. Transformation by the modelview matrix (which may consists of a separated model matrix and view matrix).
  2. Transformation by the projection matrix (regardless of whether you have perspective or orthographic projection).

After this transformations the coordinates are in the Normalized Device Coordinates space (NDC). The NDC is in a range from (-1,-1,-1) to (1,1,1).

If you want to convert back a point from the Normalized Device Coordinates (NDC) to the world space you have to do the exactly reverse transformations:

  1. Transform back by the inverse projection matrix.
  2. Transform back by the inverse modelview matrix.

See the answers to the Stack Overflow Question inverting a 4x4 matrix to see how to calculate a inverse matrix:

template < typename T_MD, typename T_MS = T_MD, typename T_SCALAR = float >
void mat44_inverse( T_MD &res, const T_MS &m )
{
    T_SCALAR Coef00 = (T_SCALAR)(m[2][2] * m[3][3] - m[3][2] * m[2][3]);
    T_SCALAR Coef02 = (T_SCALAR)(m[1][2] * m[3][3] - m[3][2] * m[1][3]);
    T_SCALAR Coef03 = (T_SCALAR)(m[1][2] * m[2][3] - m[2][2] * m[1][3]);

    T_SCALAR Coef04 = (T_SCALAR)(m[2][1] * m[3][3] - m[3][1] * m[2][3]);
    T_SCALAR Coef06 = (T_SCALAR)(m[1][1] * m[3][3] - m[3][1] * m[1][3]);
    T_SCALAR Coef07 = (T_SCALAR)(m[1][1] * m[2][3] - m[2][1] * m[1][3]);

    T_SCALAR Coef08 = (T_SCALAR)(m[2][1] * m[3][2] - m[3][1] * m[2][2]);
    T_SCALAR Coef10 = (T_SCALAR)(m[1][1] * m[3][2] - m[3][1] * m[1][2]);
    T_SCALAR Coef11 = (T_SCALAR)(m[1][1] * m[2][2] - m[2][1] * m[1][2]);

    T_SCALAR Coef12 = (T_SCALAR)(m[2][0] * m[3][3] - m[3][0] * m[2][3]);
    T_SCALAR Coef14 = (T_SCALAR)(m[1][0] * m[3][3] - m[3][0] * m[1][3]);
    T_SCALAR Coef15 = (T_SCALAR)(m[1][0] * m[2][3] - m[2][0] * m[1][3]);

    T_SCALAR Coef16 = (T_SCALAR)(m[2][0] * m[3][2] - m[3][0] * m[2][2]);
    T_SCALAR Coef18 = (T_SCALAR)(m[1][0] * m[3][2] - m[3][0] * m[1][2]);
    T_SCALAR Coef19 = (T_SCALAR)(m[1][0] * m[2][2] - m[2][0] * m[1][2]);

    T_SCALAR Coef20 = (T_SCALAR)(m[2][0] * m[3][1] - m[3][0] * m[2][1]);
    T_SCALAR Coef22 = (T_SCALAR)(m[1][0] * m[3][1] - m[3][0] * m[1][1]);
    T_SCALAR Coef23 = (T_SCALAR)(m[1][0] * m[2][1] - m[2][0] * m[1][1]);

    _TMat44Helper<T_SCALAR> Fac0(Coef00, Coef00, Coef02, Coef03);
    _TMat44Helper<T_SCALAR> Fac1(Coef04, Coef04, Coef06, Coef07);
    _TMat44Helper<T_SCALAR> Fac2(Coef08, Coef08, Coef10, Coef11);
    _TMat44Helper<T_SCALAR> Fac3(Coef12, Coef12, Coef14, Coef15);
    _TMat44Helper<T_SCALAR> Fac4(Coef16, Coef16, Coef18, Coef19);
    _TMat44Helper<T_SCALAR> Fac5(Coef20, Coef20, Coef22, Coef23);

    _TMat44Helper<T_SCALAR> Vec0((T_SCALAR)m[1][0], (T_SCALAR)m[0][0], (T_SCALAR)m[0][0], (T_SCALAR)m[0][0]);
    _TMat44Helper<T_SCALAR> Vec1((T_SCALAR)m[1][1], (T_SCALAR)m[0][1], (T_SCALAR)m[0][1], (T_SCALAR)m[0][1]);
    _TMat44Helper<T_SCALAR> Vec2((T_SCALAR)m[1][2], (T_SCALAR)m[0][2], (T_SCALAR)m[0][2], (T_SCALAR)m[0][2]);
    _TMat44Helper<T_SCALAR> Vec3((T_SCALAR)m[1][3], (T_SCALAR)m[0][3], (T_SCALAR)m[0][3], (T_SCALAR)m[0][3]);

    _TMat44Helper<T_SCALAR> Inv0(Vec1 * Fac0 - Vec2 * Fac1 + Vec3 * Fac2);
    _TMat44Helper<T_SCALAR> Inv1(Vec0 * Fac0 - Vec2 * Fac3 + Vec3 * Fac4);
    _TMat44Helper<T_SCALAR> Inv2(Vec0 * Fac1 - Vec1 * Fac3 + Vec3 * Fac5);
    _TMat44Helper<T_SCALAR> Inv3(Vec0 * Fac2 - Vec1 * Fac4 + Vec2 * Fac5);

    _TMat44Helper<T_SCALAR> SignA((T_SCALAR)+1.0, (T_SCALAR)-1.0, (T_SCALAR)+1.0, (T_SCALAR)-1.0);
    _TMat44Helper<T_SCALAR> SignB((T_SCALAR)-1.0, (T_SCALAR)+1.0, (T_SCALAR)-1.0, (T_SCALAR)+1.0);
    std::array< _TMat44Helper<T_SCALAR>, 4 > Inverse{ Inv0 * SignA, Inv1 * SignB, Inv2 * SignA, Inv3 * SignB };

    _TMat44Helper<T_SCALAR> Row0(Inverse[0][0], Inverse[1][0], Inverse[2][0], Inverse[3][0]);

    _TMat44Helper<T_SCALAR> Dot0( Row0 );
    Dot0 = Dot0 * _TMat44Helper<T_SCALAR>( (T_SCALAR)m[0][0], (T_SCALAR)m[0][1], (T_SCALAR)m[0][2], (T_SCALAR)m[0][3] );
    T_SCALAR Dot1 = (Dot0[0] + Dot0[1]) + (Dot0[2] + Dot0[3]);

    T_SCALAR OneOverDeterminant = static_cast<T_SCALAR>(1.0) / Dot1;

    for ( int inx1 = 0; inx1 < 4; inx1 ++ )
    {
        for ( int inx2 = 0; inx2 < 4; inx2 ++ )
            res[inx1][inx2] = Inverse[inx1][inx2] * OneOverDeterminant;
    }
}

template< typename SCALAR_TYPE = float >
struct _TMat44Helper
{
    _TMat44Helper( void ) {}
    _TMat44Helper( SCALAR_TYPE x, SCALAR_TYPE y, SCALAR_TYPE z, SCALAR_TYPE w ) : m_v( { x, y, z, w } ){}
    _TMat44Helper( const std::array< SCALAR_TYPE, 4 > & v ) : m_v( v ) {}
    _TMat44Helper( const SCALAR_TYPE v[4] ) { std::memcpy( m_v.data(), v, 4 * sizeof( SCALAR_TYPE ) ); }
    _TMat44Helper( const _TMat44Helper &src ) : m_v( src.m_v ) {}

    _TMat44Helper & operator = ( const _TMat44Helper<SCALAR_TYPE> &src ) { m_v = src.m_v; return *this; }

    SCALAR_TYPE & operator[](int inx) { return m_v[inx]; }

    template< typename SCALAR_TYPE_OP >
    _TMat44Helper<SCALAR_TYPE> operator *( SCALAR_TYPE_OP s )
    {
        _TMat44Helper res;
        for ( int inx = 0; inx < 4; inx ++ )
            res.m_v[inx] = m_v[inx] * (SCALAR_TYPE)s;
        return res;
    }

    template< typename SCALAR_TYPE_OP >
    _TMat44Helper<SCALAR_TYPE> operator +( const _TMat44Helper<SCALAR_TYPE_OP> &b )
    {
        _TMat44Helper<SCALAR_TYPE> res;
        for ( int inx = 0; inx < 4; inx ++ )
            res.m_v[inx] = m_v[inx] + (SCALAR_TYPE)b.m_v[inx];
        return res;
    }

    template< typename SCALAR_TYPE_OP >
    _TMat44Helper<SCALAR_TYPE> operator -( const _TMat44Helper<SCALAR_TYPE_OP> &b )
    {
        _TMat44Helper<SCALAR_TYPE> res;
        for ( int inx = 0; inx < 4; inx ++ )
            res.m_v[inx] = m_v[inx] - (SCALAR_TYPE)b.m_v[inx];
        return res;
    }

    template< typename SCALAR_TYPE_OP >
    _TMat44Helper<SCALAR_TYPE> operator *( const _TMat44Helper<SCALAR_TYPE_OP> &b )
    {
        _TMat44Helper<SCALAR_TYPE> res;
        for ( int inx = 0; inx < 4; inx ++ )
            res.m_v[inx] = m_v[inx] * (SCALAR_TYPE)b.m_v[inx];
        return res;
    }

    std::array< SCALAR_TYPE, 4 > m_v;
};

To map a point from viewport (screen) coordinates to the NDC you have to map the X-coordinate and the Y-coordinate to the range (-1.0, 1.0). The Z-coordinate is more tricky, for this you have to access the depth buffer (The depth buffer can be rendered to a texture). Read the depth at the XY position and convert it to the NDC:

X_ndc = X_screen * 2.0 / VP_sizeX - 1.0;
Y_ndc = Y_screen * 2.0 / VP_sizeY - 1.0;
Z_ndc = 2.0 * depth - 1.0; 

In your case you have only a 2D geometry and an orthographic projection, so you do not have to care about the depth, because the Z coordinate is always 0.0.

If you transform a point by the projection matrix (or the inverse projection matrix) you will get a point in a homogeneous coordinate system. To transform a point from a homogeneous coordinates system to a cartesian coordinate system you have to devide its X, Y and Z coordinate by it weight. A general transformation function of a point in a cartesian coordinate system with a homogeneous 4*4 matrix may look like this:

using TVec3 = std::array<float, 3>;
using TVec4 = std::array<float, 4>;
using TMat4 = std::array<TVec4, 4>;

TVec3 Transform( const TVec3 &vec, const TMat4 &mat )
{
    h {
        vec[0] * mat[0][0] + vec[1] * mat[1][0] + vec[2] * mat[2][0] + mat[3][0],
        vec[0] * mat[0][1] + vec[1] * mat[1][1] + vec[2] * mat[2][1] + mat[3][1],
        vec[0] * mat[0][2] + vec[1] * mat[1][2] + vec[2] * mat[2][2] + mat[3][2],
        vec[0] * mat[0][3] + vec[1] * mat[1][3] + vec[2] * mat[2][3] + mat[3][3]
    };
    if ( h[3] == 0.0 )
        return TVec3{ 1.0e99 }; // division by zero
    return TVec3{ h[0]/h[3], h[1]/h[3], h[2]/h[3] };
}

See the following WebGL example, where the color of the object, where the mouse hovers, is found by calculating back from the screen coordinates to the model coordinates.

<script type="text/javascript">

draw_vert =
"precision mediump float; \n" +
"attribute vec2 inPos; \n" +
"uniform   mat4 u_projectionMat44;" +
"uniform   mat4 u_modelViewMat44;" +
"void main()" +
"{" +
"    vec4 viewPos  = u_modelViewMat44 * vec4( inPos.xy, 0.0, 1.0 );" +
"    gl_Position   = u_projectionMat44 * viewPos;" +
"}";

draw_frag =
"precision mediump float; \n" +
"uniform vec3 u_color;" +
"void main()" +
"{" +
"    gl_FragColor = vec4( u_color.xyz, 1.0 );" +
"}";

glArrayType = typeof Float32Array !="undefined" ? Float32Array : ( typeof WebGLFloatArray != "undefined" ? WebGLFloatArray : Array );

function IdentityMat44() {
    var m = new glArrayType(16);
    m[0]  = 1; m[1]  = 0; m[2]  = 0; m[3]  = 0;
    m[4]  = 0; m[5]  = 1; m[6]  = 0; m[7]  = 0;
    m[8]  = 0; m[9]  = 0; m[10] = 1; m[11] = 0;
    m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
    return m;
};

function RotateAxis(matA, angRad, axis) {
    var aMap = [ [1, 2], [2, 0], [0, 1] ];
    var a0 = aMap[axis][0], a1 = aMap[axis][1]; 
    var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
    var matB = new glArrayType(16);
    for ( var i = 0; i < 16; ++ i ) matB[i] = matA[i];
    for ( var i = 0; i < 3; ++ i ) {
        matB[a0*4+i] = matA[a0*4+i] * cosAng + matA[a1*4+i] * sinAng;
        matB[a1*4+i] = matA[a0*4+i] * -sinAng + matA[a1*4+i] * cosAng;
    }
    return matB;
}

function Translate( matA, trans ) {
    var matB = new glArrayType(16);
    for ( var i = 0; i < 16; ++ i ) matB[i] = matA[i];
    for ( var i = 0; i < 3; ++ i )
        matB[12+i] = matA[i] * trans[0] + matA[4+i] * trans[1] + matA[8+i] * trans[2] + matA[12+i];
    return matB;
}

function Scale( matA, scale ) {
    var matB = new glArrayType(16);
    for ( var i = 0; i < 16; ++ i ) matB[i] = matA[i];
    for ( var a = 0; a < 4; ++ a )
        for ( var i = 0; i < 3; ++ i )
            matB[a*4+i] = matA[a*4+i] * scale[0];
    return matB;
}

Ortho = function( l, r, t, b, n, f ) {
    var fn  = f + n;
    var f_n = f - n;
    var m = IdentityMat44();
    m[0]  = 2/(r-l); m[1]  = 0;       m[2]  =  0;       m[3]  = 0;
    m[4]  = 0;       m[5]  = 2/(t-b); m[6]  =  0;       m[7]  = 0;
    m[8]  = 0;       m[9]  = 0;       m[10] = -2 / f_n; m[11] = -fn / f_n;
    m[12] = 0;       m[13] = 0;       m[14] = 0;        m[15] = 1;
    return m;
}

vec4_add = function( a, b ) { return [ a[0]+b[0], a[1]+b[1], a[2]+b[2], a[3]+b[3] ]; }
vec4_sub = function( a, b ) { return [ a[0]-b[0], a[1]-b[1], a[2]-b[2], a[3]-b[3] ]; }
vec4_mul = function( a, b ) { return [ a[0]*b[0], a[1]*b[1], a[2]*b[2], a[3]*b[3] ]; }
vec4_scale = function( a, s ) { return [ a[0]*s, a[1]*s, a[2]*s, a[3]*s ]; }

mat44_inverse = function( m ) {

    var Coef00 = m[2*4+2] * m[3*4+3] - m[3*4+2] * m[2*4+3];
    var Coef02 = m[1*4+2] * m[3*4+3] - m[3*4+2] * m[1*4+3];
    var Coef03 = m[1*4+2] * m[2*4+3] - m[2*4+2] * m[1*4+3];    
    var Coef04 = m[2*4+1] * m[3*4+3] - m[3*4+1] * m[2*4+3];
    var Coef06 = m[1*4+1] * m[3*4+3] - m[3*4+1] * m[1*4+3];
    var Coef07 = m[1*4+1] * m[2*4+3] - m[2*4+1] * m[1*4+3];   
    var Coef08 = m[2*4+1] * m[3*4+2] - m[3*4+1] * m[2*4+2];
    var Coef10 = m[1*4+1] * m[3*4+2] - m[3*4+1] * m[1*4+2];
    var Coef11 = m[1*4+1] * m[2*4+2] - m[2*4+1] * m[1*4+2];   
    var Coef12 = m[2*4+0] * m[3*4+3] - m[3*4+0] * m[2*4+3];
    var Coef14 = m[1*4+0] * m[3*4+3] - m[3*4+0] * m[1*4+3];
    var Coef15 = m[1*4+0] * m[2*4+3] - m[2*4+0] * m[1*4+3];   
    var Coef16 = m[2*4+0] * m[3*4+2] - m[3*4+0] * m[2*4+2];
    var Coef18 = m[1*4+0] * m[3*4+2] - m[3*4+0] * m[1*4+2];
    var Coef19 = m[1*4+0] * m[2*4+2] - m[2*4+0] * m[1*4+2];   
    var Coef20 = m[2*4+0] * m[3*4+1] - m[3*4+0] * m[2*4+1];
    var Coef22 = m[1*4+0] * m[3*4+1] - m[3*4+0] * m[1*4+1];
    var Coef23 = m[1*4+0] * m[2*4+1] - m[2*4+0] * m[1*4+1];
      
    var Fac0 = [Coef00, Coef00, Coef02, Coef03];
    var Fac1 = [Coef04, Coef04, Coef06, Coef07];
    var Fac2 = [Coef08, Coef08, Coef10, Coef11];
    var Fac3 = [Coef12, Coef12, Coef14, Coef15];
    var Fac4 = [Coef16, Coef16, Coef18, Coef19];
    var Fac5 = [Coef20, Coef20, Coef22, Coef23];
      
    var Vec0 = [ m[1*4+0], m[0*4+0], m[0*4+0], m[0*4+0] ];
    var Vec1 = [ m[1*4+1], m[0*4+1], m[0*4+1], m[0*4+1] ];
    var Vec2 = [ m[1*4+2], m[0*4+2], m[0*4+2], m[0*4+2] ];
    var Vec3 = [ m[1*4+3], m[0*4+3], m[0*4+3], m[0*4+3] ];
      
    var Inv0 = vec4_add( vec4_sub( vec4_mul(Vec1, Fac0), vec4_mul(Vec2, Fac1) ), vec4_mul( Vec3, Fac2 ) );
    var Inv1 = vec4_add( vec4_sub( vec4_mul(Vec0, Fac0), vec4_mul(Vec2, Fac3) ), vec4_mul( Vec3, Fac4 ) );
    var Inv2 = vec4_add( vec4_sub( vec4_mul(Vec0, Fac1), vec4_mul(Vec1, Fac3) ), vec4_mul( Vec3, Fac5 ) );
    var Inv3 = vec4_add( vec4_sub( vec4_mul(Vec0, Fac2), vec4_mul(Vec1, Fac4) ), vec4_mul( Vec2, Fac5 ) );
      
    var SignA = [+1.0, -1.0, +1.0, -1.0];
    var SignB = [-1.0, +1.0, -1.0, +1.0];
    var Inverse = [ vec4_mul(Inv0, SignA), vec4_mul(Inv1, SignB), vec4_mul(Inv2, SignA), vec4_mul(Inv3, SignB) ];
      
    var Row0 = [Inverse[0][0], Inverse[1][0], Inverse[2][0], Inverse[3][0] ];
      
    var Dot0 = [Row0[0], Row0[1], Row0[2], Row0[3] ];
    Dot0 = vec4_mul( Dot0, [ m[0], m[1], m[2], m[3] ] );
    var Dot1 = (Dot0[0] + Dot0[1]) + (Dot0[2] + Dot0[3]);
      
    var OneOverDeterminant = 1 / Dot1;

    var res = IdentityMat44();  
    for ( var inx1 = 0; inx1 < 4; inx1 ++ ) {
        for ( var inx2 = 0; inx2 < 4; inx2 ++ )
            res[inx1*4+inx2] = Inverse[inx1][inx2] * OneOverDeterminant;
    }
    return res;
}

Transform = function(vec, mat) {
    var h = [
        vec[0] * mat[0*4+0] + vec[1] * mat[1*4+0] + vec[2] * mat[2*4+0] + mat[3*4+0],
        vec[0] * mat[0*4+1] + vec[1] * mat[1*4+1] + vec[2] * mat[2*4+1] + mat[3*4+1],
        vec[0] * mat[0*4+2] + vec[1] * mat[1*4+2] + vec[2] * mat[2*4+2] + mat[3*4+2],
        vec[0] * mat[0*4+3] + vec[1] * mat[1*4+3] + vec[2] * mat[2*4+3] + mat[3*4+3] ]
    if ( h[3] == 0.0 )
        return [0, 0, 0]
    return [ h[0]/h[3], h[1]/h[3], h[2]/h[3] ];
}

// shader program object
var ShaderProgram = {};
ShaderProgram.Create = function( shaderList, uniformNames ) {
    var shaderObjs = [];
    for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) {
        var shderObj = this.CompileShader( shaderList[i_sh].source, shaderList[i_sh].stage );
        if ( shderObj == 0 )
          return 0;
        shaderObjs.push( shderObj );
    }
    var progObj = this.LinkProgram( shaderObjs )
    if ( progObj != 0 ) {
        progObj.unifomLocation = {};
        for ( var i_n = 0; i_n < uniformNames.length; ++ i_n ) {
            var name = uniformNames[i_n];
            progObj.unifomLocation[name] = gl.getUniformLocation( progObj, name );
        }
    }
    return progObj;
}
ShaderProgram.Use = function( progObj ) { gl.useProgram( progObj ); } 
ShaderProgram.SetUniformInt = function( progObj, name, val ) { gl.uniform1i( progObj.unifomLocation[name], val ); }
ShaderProgram.SetUniform2f = function( progObj, name, arr ) { gl.uniform2fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniform3f = function( progObj, name, arr ) { gl.uniform3fv( progObj.unifomLocation[name], arr ); }
ShaderProgram.SetUniformMat44 = function( progObj, name, mat ) { gl.uniformMatrix4fv( progObj.unifomLocation[name], false, mat ); }
ShaderProgram.CompileShader = function( source, shaderStage ) {
    var shaderObj = gl.createShader( shaderStage );
    gl.shaderSource( shaderObj, source );
    gl.compileShader( shaderObj );
    var status = gl.getShaderParameter( shaderObj, gl.COMPILE_STATUS );
    if ( !status ) alert(gl.getShaderInfoLog(shaderObj));
    return status ? shaderObj : 0;
} 
ShaderProgram.LinkProgram = function( shaderObjs ) {
    var prog = gl.createProgram();
    for ( var i_sh = 0; i_sh < shaderObjs.length; ++ i_sh )
        gl.attachShader( prog, shaderObjs[i_sh] );
    gl.linkProgram( prog );
    status = gl.getProgramParameter( prog, gl.LINK_STATUS );
    if ( !status ) alert("Could not initialise shaders");
    gl.useProgram( null );
    return status ? prog : 0;
}
        

function drawScene(){

    var canvas = document.getElementById( "camera-canvas" );
    var currentTime = Date.now();   
    var deltaMS = currentTime - startTime;
    var aspect =  canvas.width / canvas.height;
    var matOrtho = Ortho( -aspect, aspect, 1, -1, -1, 1 );
    var matOrthoInv = mat44_inverse( matOrtho )
        
    gl.viewport( 0, 0, canvas.width, canvas.height );
    gl.enable( gl.DEPTH_TEST );
    gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
    gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
    ShaderProgram.Use( progDraw );
    gl.enableVertexAttribArray( progDraw.inPos );
    gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
    gl.vertexAttribPointer( progDraw.inPos, 2, gl.FLOAT, false, 0, 0 ); 
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
    ShaderProgram.SetUniformMat44( progDraw, "u_projectionMat44",  matOrtho );
        
    var col = [ [1.0,0.0,0.0], [1.0,1.0,0.0], [0.0,0.0,1.0] ];
    var invMat = []
    for ( var i = 0; i < 3; ++ i ) {    
        var modelMat = Scale( IdentityMat44(), [ 0.3, 0.3, 0.3] );
        var angRad = CalcAng( currentTime, 20.0 ) + i * Math.PI * 2 / 3;
        var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
        modelMat[12] = cosAng * 0.6;
        modelMat[13] = sinAng * 0.6;
        invMat.push( mat44_inverse( modelMat ) );
        
        ShaderProgram.SetUniformMat44( progDraw, "u_modelViewMat44", modelMat );
        ShaderProgram.SetUniform3f( progDraw, "u_color", col[i] );
        gl.drawElements( gl.TRIANGLES, bufObj.inx.len, gl.UNSIGNED_SHORT, 0 );
    }
    gl.disableVertexAttribArray( progDraw.pos );

    var newColor = "#000000";
    var colorMap = ["#ff0000", "#ffff00", "#0000ff" ];
    var pos = [-1, -1];
    if (mousePos[0] > 0 && mousePos[1] > 0 ) {
        var pos = [2.0 * mousePos[0] / canvas.width - 1.0, 1.0 - 2.0 * mousePos[1] / canvas.height];
        for ( var i = 0; i < 3; ++ i ) {
            var testVec = [ pos[0], pos[1], 0 ];
            testVec = Transform(testVec, matOrthoInv);
            testVec = Transform(testVec, invMat[i]);
            if (testVec[0] > -1.0 && testVec[0] < 1.0 && testVec[1] > -1.0 && testVec[1] < 1.0 ) {
                newColor = colorMap[i];
            }
        }
    }
    document.getElementById( "color" ).value = newColor;
    document.getElementById( "mouseX" ).innerHTML = pos[0];
    document.getElementById( "mouseY" ).innerHTML = pos[1];    
}

var startTime;
function Fract( val ) { 
    return val - Math.trunc( val );
}
function CalcAng( currentTime, intervall ) {
    return Fract( (currentTime - startTime) / (1000*intervall) ) * 2.0 * Math.PI;
}
function CalcMove( currentTime, intervall, range ) {
    var pos = self.Fract( (currentTime - startTime) / (1000*intervall) ) * 2.0
    var pos = pos < 1.0 ? pos : (2.0-pos)
    return range[0] + (range[1] - range[0]) * pos;
}    

var mousePos = [-1, -1];
var gl;
var prog;
var bufObj = {};
function cameraStart() {

    var canvas = document.getElementById( "camera-canvas");
    gl = canvas.getContext( "experimental-webgl" );
    if ( !gl )
      return;

    progDraw = ShaderProgram.Create( 
      [ { source : draw_vert, stage : gl.VERTEX_SHADER },
        { source : draw_frag, stage : gl.FRAGMENT_SHADER }
      ],
      [ "u_projectionMat44", "u_modelViewMat44", "u_color"] );
    progDraw.inPos = gl.getAttribLocation( progDraw, "inPos" );
    if ( prog == 0 )
        return;

    var pos = [ -1, -1, 1, -1, 1, 1, -1, 1 ];
    var inx = [ 0, 1, 2, 0, 2, 3 ];
    bufObj.pos = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.pos );
    gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( pos ), gl.STATIC_DRAW );
    bufObj.inx = gl.createBuffer();
    bufObj.inx.len = inx.length;
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );
    gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( inx ), gl.STATIC_DRAW );

    startTime = Date.now();
    setInterval(drawScene, 50);
}

(function() {
    document.onmousemove = handleMouseMove;
    function handleMouseMove(event) {
        var dot, eventDoc, doc, body, pageX, pageY;

        event = event || window.event; // IE-ism

        if (event.pageX == null && event.clientX != null) {
            eventDoc = (event.target && event.target.ownerDocument) || document;
            doc = eventDoc.documentElement;
            body = eventDoc.body;

            event.pageX = event.clientX +
              (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
              (doc && doc.clientLeft || body && body.clientLeft || 0);
            event.pageY = event.clientY +
              (doc && doc.scrollTop  || body && body.scrollTop  || 0) -
              (doc && doc.clientTop  || body && body.clientTop  || 0 );
        }

        var canvas = document.getElementById( "camera-canvas");
        var x = event.pageX - canvas.offsetLeft;
        var y = event.pageY - canvas.offsetTop;
        mousePos = [-1, -1];
        if ( x >= 0 && x < canvas.width && y >= 0 && y < canvas.height ) {
            mousePos = [x, y]; 
        }
    }
})();

</script>

<body onload="cameraStart();">
    <div style="margin-left: 260px;">
        <div style="float: right; width: 100%; background-color: #CCF;">
            <form name="inputs">
                <table>
                    <tr> <td> <input type="color" value="#000000" id="color" disabled></td> </tr> 
                    <tr> <td> <span id="mouseX">0</span> </td> </tr>
                    <tr> <td> <span id="mouseY">0</span> </td> </tr>
                </table>
            </form>
        </div>
        <div style="float: right; width: 260px; margin-left: -260px;">
            <canvas id="camera-canvas" style="border: none;" width="256" height="256"></canvas>
        </div>
        <div style="clear: both;"></div>
    </div>
</body>

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