PDA

View Full Version : Avoiding unnessecary if/else choice in loop for Virtual Camera



tmason
08-14-2014, 02:27 PM
Hello,

So I have more of a best practice question that I am wondering what is the best approach for.

I have a working virtual "camera" for OpenGL that builds the correct view and projection matrices for upload into OpenGL.

Let's represent that with the psuedo-function UpdateCamera(). This camera is inside a class OpenGLCamera which also has another function to set whether we want to display a perspective view or orthogonal view. Let's call this function SwitchView() and the flag isPerspectiveOn.

Now, inside the update camera function, I have code that looks something like this:

void OpenGLCamera::UpdateCamera() {
/*

Calculate position, direction, determine rotation, etc.

View Matrix is built.

*/

if (isPerspectiveOn == true)
{
ProjectionMatrix = glm::perspective(FieldOfView, AspectRatio, NearPlane, FarPlane);
} else {
Projection = glm::ortho(CurrentOrthoParameters.LeftPlane, CurrentOrthoParameters.RightPlane,
CurrentOrthoParameters.BottomPlane, CurrentOrthoParameters.TopPlane);
}

ProjectionViewMatrix = ProjectionMatrix * ViewMatrix;

}

What I am wondering is if there is a way to simply switch which projection function I am using without needing to perform if/else test all of the time?

In other words, perhaps a way to set the isPerspectiveOn flag somewhere else and with a change in how I write the code I don't have to perform this test with every loop or per frame.

I feel it may seem trivial but the least amount of testing I am doing per frame the better.

Thank you for your time and if I need to clarify further let me know.

MtRoad
08-14-2014, 03:27 PM
I applaud you recognizing an additional test you are doing per frame. However, I would really doubt that a single IF statement could be that much of a performance problem. The penalty from recalculating matrices with glm:: perspective and glm:: ortho and then a 4x4 matrix multiplication to get the projection-view matrix is where you can save time (cache these within functions you use to modify the eye, look, FOV, etc).

Idea 1: Subclass OpenGLCamera and create a virtual method to call the appropriate projection function, but you won't be able to turn perspective off and on.

Idea 2: Create a strategy object (google design patterns/strategy) for two objects, one to use perspective, the other not to. Then swap out the designated object.

Idea 3: If you really want to get rid of the IF statement you could use a class-method pointer and then call it.
WARNING: You are entering the Black-Magic Devilish Incantations that make C++ an extremely interesting language to use. If you have flashbacks to the days of vomiting punctuation to write Perl, I vehemently apologize. Using this language feature may cause your code to be much more confusing to use than this technique merits.


class OpenGLCamera {
mat4 calcPerspectiveProjection();
mat4 calcOrthoProjection();

// private member of the class. This is a method pointer (reminiscent of a function pointer).
// Make sure you point it somewhere when you create your instance of the class.
mat4 (OpenGLCamera::*projectionFunction)();
};

void OpenGLCamera::setPerspectiveEnabled( bool enabled ) {
if( enabled ) {
projectionFunction = &OpenGLCamera.calcPerspectiveProjection;
}
else {
projectionFunction = &OpenGLCamera.calcOrthoProjection;
}
}

// Inside updateCamera()
(this->*projectionFunction)();


I'm sure Carmine or Dark Photon will suggest a significantly easier way to do this ;)

tmason
08-14-2014, 03:32 PM
I would really doubt that a single IF statement could be that much of a performance problem. The penalty from recalculating matrices with glm::perspective and glm::ortho and then a 4x4 matrix multiplication to get the projection-view matrix is where you can save time (cache these within functions you use to modify the eye, look, FOV, etc).

If you really want to get rid of the IF statement you could use a class-method pointer and then call it.



class OpenGLCamera {
mat4 calcPerspectiveProjection();
mat4 calcOrthoProjection();

// private member of the class. This is a method pointer (reminiscent of a function pointer).
// Make sure you point it somewhere when you create your instance of the class.
mat4 (OpenGLCamera::*projectionFunction)();
};


void OpenGLCamera::setPerspectiveEnabled( bool enabled ) {
if( enabled ) {
projectionFunction = &OpenGLCamera.calcPerspectiveProjection;
}
else {
projectionFunction = &OpenGLCamera.calcOrthoProjection;
}
}

// Inside updateCamera()
(this->*projectionFunction)();


Thanks; that seems correct. I will try that.

tmason
08-14-2014, 03:36 PM
Meant to say I would try both suggestions.

MtRoad
08-14-2014, 04:09 PM
You can get a significant boost of time savings from caching everything you can and using it as much as possible before you move onto something else or change something. The same concept works with OpenGL context state (changing programs, uniforms, textures, etc.).

Here's how my camera class caches everything. Don't hate me, it's objective-c, so it will look a little weird. NOTE: I take a performance hit by recalculating the matrix every time I adjust ANY parameter individually, but I did that so the transform is always what I'd expect and I don't modify one parameter at a time (typically I do multiple and then build the transform).



- ( instancetype ) initWithFOVYDegrees:( float )fovyDegrees
aspectRatio:( float )aspectRatio
near:( float )near
far:( float )far {
if( self = [ super init ] ) {
// Set properties first, then build matrices to prevent from building
// them multiple times.
_fovyDegrees = fovyDegrees;
_aspectRatio = aspectRatio;
_near = near;
_far = far;
_up = GLKVector3Make(0,1,0);
[ self makeViewTransform ];
[ self makeProjectionTransform ];
}
return self;
}

// extra stuff you don't care about...

/** Updates the projection transform after far has been changed. */
- ( void )setFar:( float )far {
_far = far;
[ self makeProjectionTransform ];
}


/**
* Rebuilds the projection matrix from fovY, aspectRatio and near and far.
*/
- ( void )makeProjectionTransform {
_projectionTransform = GLKMatrix4MakePerspective(GLKMathDegreesToRadians( _fovyDegrees),_aspectRatio,_near,_far);
}

Then whenever I pull my transform out of the class it has already been calculated for that frame and is consistent with any property I query (eye and look positions, etc.).

tmason
08-14-2014, 05:26 PM
Thanks for your responses; I really appreciate it.

I assume from the above code that you don't dampen the camera?

MtRoad
08-14-2014, 05:50 PM
Thanks for your responses; I really appreciate it.

I assume from the above code that you don't dampen the camera?

This is a really basic camera class that I made for basic use. I've done animations between positions before, but I'm trying to keep my code as simple and easy to understand as possible right now since I'm only using a fixed camera in my main project (right now). I don't get paid to build programs (yet) and I have a day-job so I try to keep things quick and simple. (Anyone hiring graphics programmers?)

Come to think of it, a better way to do it might be this, so you could animate parameters and then do update when it is requested:


/** Updates the projection transform after far has been changed. */
- ( void )setFar:( float )far {
_far = far;
_projectionCached = NO; // << and similar for all set methods
}


/**
* Rebuilds the projection matrix from fovY, aspectRatio and near and far.
*/
- ( void )makeProjectionTransform {
_projectionTransform = GLKMatrix4MakePerspective(GLKMathDegreesToRadians( _fovyDegrees),_aspectRatio,_near,_far);
_projectionCached = YES;
}


/** Getter method. */
- ( GLKMatrix4 )projectionTransform {
if( _projectionCached == NO ) {
[ self makeProjectionTransform ];
}
return _projectionTransform;
}




I've had problems with my camera class getting way too complicated when I try to handle the animations within the class. I did a camera class in C++ using a generic animation class I wrote using templates and function pointers (handling multiple types, vecs with multiple interpolation types), but it was excessive for what I need (and significantly overcomplicated obviously).

To give you an idea, here's some of my old code. With my interpolator template (I also did non-linear interpolators) anything that properly overrides * for scalars and + for two similar components can be interpolated (i.e. floats and vecs). Note how my code style has significantly simplified. No more systems hungarian for me! :)


// Allowed me to create arbitrary interpolators
template<typename tn_interpolated>
tn_interpolated
linearInterpolate(tn_interpolated start_, tn_interpolated end_, float fT_) {
// Clamp to values... return here if needed.
if(fT_ < 0.0f) return start_;
if(fT_ > 1.0f) return end_;

// ----------------------MAGIC HAPPENS HERE --------------------------
// Use * and + for vec3 and floats to allow interpolation
return start_*(1.0f-fT_) + end_*(fT_);
}


// Generic animation code which could handle vec's, floats, and matrices (by me... :( )
/**
* \brief
* Animates transition between two interpolatable types.
*/
template <typename tn_interpolated>
class west_InterpolatorAnimation {
float m_fElapsed; // Elapsed seconds of animation
float m_fDuration; // Total seconds of animation
tn_interpolated m_start; // Starting value
tn_interpolated m_end; // Ending value

tn_interpolated(*m_interpolator)(float, const tn_interpolated&, const tn_interpolated&);
// Interpolator function

public:
/**
* Creates an interpolated animation.
*/
west_InterpolatorAnimation(float fSecondsDuration_,
const tn_interpolated &c_rStart_,
const tn_interpolated &c_rEnd_,
tn_interpolated(*interpolator_)(float, const tn_interpolated&, const tn_interpolated&)
):
m_fElapsed(0),
m_fDuration(fSecondsDuration_),
m_start(c_rStart_),
m_end(c_rEnd_),
m_interpolator(interpolator_)
{
}

/**
* Updates this animation for a duration of elapsed seconds.
*/
void update(float fElapsedSeconds_) {
m_fElapsed += fElapsedSeconds_;
}


/**
* Returns duration of the animation, in seconds.
*/
float duration() const {
return m_fDuration;
}


float elapsed() const {
return m_fElapsed;
}

/**
*
* -------------------- THE MAGIC HAPPENS HERE ------------------
*
*
* Returns the current position of the animation. Starting position
* if animation has not started, and ending position if isFinished().
*/
tn_interpolated currentValue() const {
return m_interpolator(m_fElapsed / m_fDuration, m_start, m_end);
}

tn_interpolated start() const {
return m_start;
}
// Starting value for the animation, when t = 0.0f

tn_interpolated end() const {
return m_end;
}
// Ending value for the animation when t = 1.0f

bool isRunning() const {
return m_fElapsed < m_fDuration;
}
// Animation is between start and end times.

bool isFinished() const {
return m_fElapsed >= m_fDuration;
}
// Animation is at or past its end time.
};


Sample interpolation:


vec3 from( 1, 5, 0 );
vec3 to( 20, 50, 40 );
west_InterpolatorAnimation animatedParameter( 5.0, from, to, linearInterpolation<vec3> );
// .. also works for floats!