[SOLVED] Animated iTunes like CoverFlow

Hey,
I’m fairly new to Panda3D and am trying to build a CoverFlow like effect in Panda with C++.

I got the scene already set up and everything is working, except for the animation.
I somehow can’t get it to work with LerpIntervals.

Here’s what I have so far (without animation):

#include "stdafx.h"

#include "pandaFramework.h"
#include "pandaSystem.h"

#include "pointLight.h"
#include "texturePool.h"

PandaFramework framework;
WindowFramework *window;

//----------------------- Settings ------------------------
int number = 9;
float offset = 0.7;
float angle = 65;
//---------------------------------------------------------
int position = ceilf(number / 2);

NodePath objects[9];
PT(Texture) textures[9];
NodePath camera;
NodePath plnp;

void createObjects(void);
void posObjects(int);
void goLeft(const Event *, void *);
void goRight(const Event *, void *);
AsyncTask::DoneStatus rotLeft(GenericAsyncTask*, void*);

int _tmain(int argc, char* argv[])
{
//------------------- Window Framework --------------------
	framework.open_framework(argc, argv);
	framework.set_window_title("3D-Walze Test");

	window = framework.open_window();
//---------------------------------------------------------

//------------------------ Camera -------------------------
	camera = window->get_camera_group();
//---------------------------------------------------------

//----------------------- Lighting ------------------------
	PT(PointLight) pLight;
	pLight = new PointLight("Point light");

	plnp = window->get_render().attach_new_node(pLight);
	window->get_render().set_light(plnp);
//---------------------------------------------------------

//-------------------- Create/Position --------------------
	createObjects();
	posObjects(position);
//---------------------------------------------------------

//----------------------- Controls ------------------------
//	window->setup_trackball();
	window->enable_keyboard();

	framework.define_key("arrow_left-up", "Go left", goLeft, 0);
	framework.define_key("arrow_right-up", "Go right", goRight, 0);
//---------------------------------------------------------

//----------------------- Main Loop -----------------------
	framework.main_loop();
	framework.close_framework();
//---------------------------------------------------------

	return 0;
}

//----------------------- Functions -----------------------
void createObjects() {
		textures[0] = TexturePool::load_texture("maps/cover/1.jpg");
		textures[1] = TexturePool::load_texture("maps/cover/2.jpg");
		textures[2] = TexturePool::load_texture("maps/cover/3.jpg");
		textures[3] = TexturePool::load_texture("maps/cover/4.jpg");
		textures[4] = TexturePool::load_texture("maps/cover/5.jpg");
		textures[5] = TexturePool::load_texture("maps/cover/6.jpg");
		textures[6] = TexturePool::load_texture("maps/cover/7.jpg");
		textures[7] = TexturePool::load_texture("maps/cover/8.jpg");
		textures[8] = TexturePool::load_texture("maps/cover/9.jpg");

	int i = 0;
	for (i=0;i<number;i++) {
		objects[i] = window->load_model(framework.get_models(), "models/cover");
		objects[i].set_texture(textures[i],1);
		objects[i].reparent_to(window->get_render());
	}
}

void posObjects(int selected) {
	int i = 0;
	for (i;i<number;i++) {
		if (i<selected) {
			objects[i].set_pos(i*offset,0,0);
			objects[i].set_hpr(angle,0,0);
		}
		else if (i==selected) {
			objects[i].set_pos(i*offset+offset,-0.5,0);
			objects[i].set_hpr(0,0,0);
		}
		else if (i==(selected+1)) {
			objects[i].set_pos(i*offset+2*offset,0,0);
			objects[i].set_hpr(-angle,0,0);
		}
		else if (i>selected) {
			objects[i].set_pos(i*offset+2*offset,0,0);
			objects[i].set_hpr(-angle,0,0);
		}
	}
	plnp.set_pos(selected*offset+offset,-7,1);
	camera.set_pos(selected*offset+offset,-7,0);
}

void goLeft(const Event * theEvent, void * data) {
	position--;
	if (position<0) position = 0;
	posObjects(position);
}

void goRight(const Event * theEvent, void * data) {
	position++;
	if (position>=number) position = number-1;
	posObjects(position);
}
//---------------------------------------------------------

I need to replace the set_pos and set_hpr with an animation but can’t figure out a proper way to do so.

Any help is appreciated.^^

Using intervals in C++ requires a little bit more setup than the equivalent in Python. First, you’ll need to create a task to step the IntervalManager each frame:

   framework.get_task_mgr().add(new GenericAsyncTask("ivalLoop", ivalLoop, NULL)
AsyncTask::DoneStatus ivalLoop(GenericAsyncTask*, void*) {
  CIntervalManager::get_global_ptr()->step();
  return AsyncTask::DS_cont;
}

Then you can write your posObjects() function like this:

void posObjects(int selected) {
   int i = 0;
   LPoint3f pos;
   LVecBase3f hpr;
   static const double duration = 1.0;

   for (i;i<number;i++) {
      if (i<selected) {
         pos.set(i*offset,0,0);
         hpr.set(angle,0,0);
      }
      else if (i==selected) {
         pos.set(i*offset+offset,-0.5,0);
         hpr.set(0,0,0);
      }
      else if (i==(selected+1)) {
         pos.set(i*offset+2*offset,0,0);
         hpr.set(-angle,0,0);
      }
      else if (i>selected) {
         pos.set(i*offset+2*offset,0,0);
         hpr.set(-angle,0,0);
      }

      // Each interval needs a unique name; it will automatically
      // replace any other intervals of the same name.
      ostringstream strm;
      strm << "ipos_" << i;
      
      // Create a lerp interval to apply the new pos and hpr.
      PT(CLerpNodePathInterval) ipos = new CLerpNodePathInterval(strm.str(), duration, CLerpInterval::BT_ease_in_out, true, false, objects[i], NodePath());
      ipos->set_end_pos(pos);
      ipos->set_end_hpr(hpr);

      // Run the interval.
      ipos->start();
   }
   plnp.set_pos(selected*offset+offset,-7,1);
   camera.set_pos(selected*offset+offset,-7,0);
}

This is just one approach. Another approach is to bind all of the intervals into a single MetaInterval (like a Parallel) and then play that one interval, instead of playing each interval individually. This avoids the need to name them each uniquely, because you’ve really got only one interval playing (the MetaInterval), so its name is already unique.

void posObjects(int selected) {
   int i = 0;
   LPoint3f pos;
   LVecBase3f hpr;
   static const double duration = 1.0;
   PT(CMetaInterval) ival = new CMetaInterval("posObjects");

   for (i;i<number;i++) {
      if (i<selected) {
         pos.set(i*offset,0,0);
         hpr.set(angle,0,0);
      }
      else if (i==selected) {
         pos.set(i*offset+offset,-0.5,0);
         hpr.set(0,0,0);
      }
      else if (i==(selected+1)) {
         pos.set(i*offset+2*offset,0,0);
         hpr.set(-angle,0,0);
      }
      else if (i>selected) {
         pos.set(i*offset+2*offset,0,0);
         hpr.set(-angle,0,0);
      }

      // Create a lerp interval to apply the new pos and hpr.
      PT(CLerpNodePathInterval) ipos = new CLerpNodePathInterval("ipos", duration, CLerpInterval::BT_ease_in_out, true, false, objects[i], NodePath());
      ipos->set_end_pos(pos);
      ipos->set_end_hpr(hpr);

      // Add the interval to the meta interval, in parallel with all
      // of the other intervals.
      ival->add_c_interval(ipos, 0.0, CMetaInterval::RS_previous_begin);
   }
   plnp.set_pos(selected*offset+offset,-7,1);
   camera.set_pos(selected*offset+offset,-7,0);

   // Now play the whole interval.
   ival->start();
}

David

Hey David,

thanks a lot for your answer. Looks really helpful. I’ll try it when i get back to work tomorrow.
I think I made the mistake not using unique names for the intervals.

I just tried it and it works perfectly.
The only thing that would be nice is, that at the moment the animations jump when I press the arrow keys again while an animation is playing. Can I set the function to wait for the current animation to finish before starting the new one?

Sure, but that’s up to your ingenuity. One way is to add a callback interval to the end of the interval chain to call a function that checks whether another key has been pressed. To do this, you’ll have to subclass cInterval and overload priv_instant().

Another approach is to start a task that checks each frame whether the interval is still playing and doesn’t start up a new one until it’s done.

David

I tried another way that, I think, works better for my setup. I just added start values to the intervals:

void posObjects(int selected) {
	LPoint3f pos;
	LVecBase3f hpr;
	PT(CMetaInterval) sequence = new CMetaInterval("posObjects");	// move sequence
	int i = 0;

	for (i;i<number;i++) {
		if (i<selected) {		// covers on the left
			pos.set(i*offset,0,0);
			hpr.set(angle,0,0);
		}
		else if (i==selected) {	// selected cover
			pos.set(i*offset+2*offset,-selectedZoom,0);
			hpr.set(0,0,0);
		}
		else if (i>selected) {	// covers on the right
			pos.set(i*offset+4*offset,0,0);
			hpr.set(-angle,0,0);
		}
//----------------- Position/Rotate Interval ------------------
		PT(CLerpNodePathInterval) coverInt = new CLerpNodePathInterval("coverInt", duration,
			CLerpInterval::BT_ease_out, true, false, objects[i], NodePath());
		coverInt->set_start_pos(objects[i].get_pos());
		coverInt->set_end_pos(pos);
		coverInt->set_start_hpr(objects[i].get_hpr());
		coverInt->set_end_hpr(hpr);
		sequence->add_c_interval(coverInt, 0.0, CMetaInterval::RS_previous_begin);
//-------------------------------------------------------------
	}

//--------------------- Camera Interval -----------------------
	PT(CLerpNodePathInterval) camInt = new CLerpNodePathInterval("camInt", duration,
			CLerpInterval::BT_no_blend, true, false, camera, NodePath());
	camInt->set_start_pos(camera.get_pos());
	camInt->set_end_pos(LPoint3f(selected*offset+2*offset,-cameraZoom,0));

	sequence->add_c_interval(camInt, 0.0, CMetaInterval::RS_previous_begin);
//-------------------------------------------------------------

	sequence->start();	// start sequence to move covers and camera
}

It works really well and I can scroll through the covers faster, when i press the key continuously.
But somehow I sometimes get a short flash where the scene, which would’ve been rendered if I pushed the key only once appears for a millisecond.
The correct scene still renders fine in the background.
Any idea why that could happen?

I think you are seeing the artifacts from finishing the old sequence when the new one starts. By default, when you start a new interval with the same name as a previous interval, the previous one is “finished”, which means it is advanced to its end position, and then the new one is started.

In this case, you’d rather have the previous one be “paused”, which stops it while leaving it in the middle wherever it is. To do this, keep around a pointer to the interval, and pause it explicitly:

PT(CMetaInterval) sequence;
void posObjects(int selected) {
   LPoint3f pos;
   LVecBase3f hpr;
   if (sequence != NULL) {
     sequence->pause();
   }
   sequence = new CMetaInterval("posObjects");   // move sequence
   int i = 0;

David

Thank you so much for your help!
I worked a lot on it and got it almost perfect now.

I hope I can post another question in this thread, too. It’s not about animation but a new thread would be too much I guess.

I already set my objects to be rendered two sided:

objects[i].set_two_sided (true);

But is it also possible to set another texture to the back then? I know that by default the texture is applied by the direction of the face normal but maybe you can somehow flip a texture to the back?

Don’t be afraid to start a new thread when you have a new question.

To answer your question, though: no, it’s not possible to have two different textures on the opposite sides of two-sided rendering. If you want to have one polygon that has a texture on each side, you will need to create two back-to-back single-sided polygons instead.

David

Ok, that’s what I thought, too.
It might get a little slower then but I still get like 130 fps with a pretty old ATI card,
so it should be fine.