LookAt Implementation Misbehaviors

2019-07-30 16:42发布

问题:

I'm looking forward to implement a LookAt( forward, up ) method where:

Vector3 forward = eyePosition - targetPosition;
Vector3 up;

I've seen some implementations over the web, but analizing the code, I've found potencial misbehaviors...

I list them from easiest to hardest:

1) This won't work if UP is a zero vector.

2) This won't work if FORWARD is a zero vector.

3) This won't work if UP and FORWARD are parallels.

...

So.. How should this method correctly be implemented?

Here is a code example I've found.

/*
 *  gluLookAt.h
 *
 *  This is a modified version of the function of the same name from 
 *  the Mesa3D project ( http://mesa3d.org/ ), which is  licensed
 *  under the MIT license, which allows use, modification, and 
 *  redistribution
 *
 *  In order to work under OpenGL ES, all instances of GLdouble
 *  had to be changed to GLfloat, and all "d" function calls had
 *  to be changed to the "f" versions.
 *
 *  Original developer's comments have been left in place.
 *
 *  Out of respect for the original authors, this is licensed under
 *  the Mesa (MIT) license. Original license follows:
 *  
 *  -----------------------------------------------------------------------
 *
 *  Copyright (C) 1999-2007  Brian Paul   All Rights Reserved.
 *  
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.

 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 *  BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 *  AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
#import <Foundation/Foundation.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

void gluLookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez,
          GLfloat centerx, GLfloat centery, GLfloat centerz,
          GLfloat upx, GLfloat upy, GLfloat upz);



gluLookAt.m
/*
 *  gluLookAt.c
 */

#include "gluLookAt.h"

void gluLookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez,
          GLfloat centerx, GLfloat centery, GLfloat centerz,
          GLfloat upx, GLfloat upy, GLfloat upz)
{
    GLfloat m[16];
    GLfloat x[3], y[3], z[3];
    GLfloat mag;

    /* Make rotation matrix */

    /* Z vector */
    z[0] = eyex - centerx;
    z[1] = eyey - centery;
    z[2] = eyez - centerz;
    mag = sqrt(z[0] * z[0] + z[1] * z[1] + z[2] * z[2]);
    if (mag) {          /* mpichler, 19950515 */
        z[0] /= mag;
        z[1] /= mag;
        z[2] /= mag;
    }

    /* Y vector */
    y[0] = upx;
    y[1] = upy;
    y[2] = upz;

    /* X vector = Y cross Z */
    x[0] = y[1] * z[2] - y[2] * z[1];
    x[1] = -y[0] * z[2] + y[2] * z[0];
    x[2] = y[0] * z[1] - y[1] * z[0];

    /* Recompute Y = Z cross X */
    y[0] = z[1] * x[2] - z[2] * x[1];
    y[1] = -z[0] * x[2] + z[2] * x[0];
    y[2] = z[0] * x[1] - z[1] * x[0];

    /* mpichler, 19950515 */
    /* cross product gives area of parallelogram, which is < 1.0 for
     * non-perpendicular unit-length vectors; so normalize x, y here
     */

    mag = sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
    if (mag) {
        x[0] /= mag;
        x[1] /= mag;
        x[2] /= mag;
    }

    mag = sqrt(y[0] * y[0] + y[1] * y[1] + y[2] * y[2]);
    if (mag) {
        y[0] /= mag;
        y[1] /= mag;
        y[2] /= mag;
    }

#define M(row,col)  m[col*4+row]
    M(0, 0) = x[0];
    M(0, 1) = x[1];
    M(0, 2) = x[2];
    M(0, 3) = 0.0;
    M(1, 0) = y[0];
    M(1, 1) = y[1];
    M(1, 2) = y[2];
    M(1, 3) = 0.0;
    M(2, 0) = z[0];
    M(2, 1) = z[1];
    M(2, 2) = z[2];
    M(2, 3) = 0.0;
    M(3, 0) = 0.0;
    M(3, 1) = 0.0;
    M(3, 2) = 0.0;
    M(3, 3) = 1.0;
#undef M
    glMultMatrixf(m);

    /* Translate Eye to Origin */
    glTranslatef(-eyex, -eyey, -eyez);

}

回答1:

Ok.. I've just got to an idea to solve this problem. I haven't test it, not even code it yet, but I share the idea to discuss it, I appreciate your opinion!!

Pseudo code: (using column major matrix and right-handed coordinate system)

// transformation matrix (column major)
float matrix[16];

void crossProduct( const flaot a[3], const float b[3], float result[3] ) {
    result[0] = a[1] * b[2] - a[2] * b[1];
    result[1] = -a[0] * b[2] + a[2] * b[0];
    result[2] = a[0] * b[1] - a[1] * b[0];
} 

void LookAt( float forward[3], float up[3] ) {
    // get forward length
    int length = sqrt( forward[0] * forward[0] + forward[1] * forward[1] + forward[2] * forward[2] );
    // if not zero, normalize it
    if ( length ) {
        forward[0] /= length;
        forward[1] /= length;
        forward[2] /= length;
    } else {
        // get last forward direction
        forward[0] = matrix[8];
        forward[1] = matrix[9];
        forward[2] = matrix[10];
    }

    // get up length
    length = sqrt( up[0] * up[0] + up[1] * up[1] + up[2] * up[2] );
    // if not zero, normalize it
    if ( length ) {
        up[0] /= length;
        up[1] /= length;
        up[2] /= length;
    } else {
        // get last up direction
        up[0] = matrix[4];
        up[1] = matrix[5];
        up[2] = matrix[6];
    }

    float left[3];
    crossProduct( up, forward, left);
    // if cross product is zero, forward and up are parallels. get last left direction
    if ( left[0]==0 && left[1]==0 && left[2]==0 ) {
        left[0] = matrix[0];
        left[1] = matrix[1];
        left[2] = matrix[2];
    }

    // recalculate orthogonal up
    crossProduct( forward, left, up );

    // update matrix values
    matrix[0] = left[0];
    matrix[1] = left[1];
    matrix[2] = left[2];
    matrix[3] = 0;
    matrix[4] = up[0];
    matrix[5] = up[1];
    matrix[6] = up[2];
    matrix[7] = 0;
    matrix[8] = forward[0];
    matrix[9] = forward[1];
    matrix[10] = forward[2];
    matrix[11] = 0;
    matrix[12] = 0;
    matrix[13] = 0;
    matrix[14] = 0;
    matrix[15] = 1;
}