Creating a Second Camera

I’d like to create a second camera that’s in the same position as the main camera but is able to point in a different direction. What’s the best way to do this?

Chris

You can use base.makeCamera to do so. IIRC, If you pass useCamera, it will reparent the camera to base.camera, this means that it’ll use that position as well. You can simply rotate the camera node itself.

Is that C++?

python. C works too, but python is the intended scripting language.

Oops! Sorry! I didn’t notice the forum this was posted in.

The C++ equivalent is WindowFramework::make_camera, so like:

NodePath newcam = wfx->make_camera();
newcam.set_hpr(h, p, r);

When I added the first line I got the following error:

/Applications/Panda3D/1.6.2/include/windowFramework.h:134: error: ‘NodePath WindowFramework::make_camera()’ is protected

How can I fix this?

Hmm, I’m not honestly sure what that method is protected. I can’t see any harm in calling it directly. I can change it to public; in the meantime, you can simply copy the code from that method and invoke that.

Still, we should clarify your intentions. The make_camera() method will return a new camera that’s not hooked up to any DisplayRegion, so nothing will render that camera’s view. If you want a second camera, you need a DisplayRegion to render its view, so you will be responsible for creating that DisplayRegion wherever you want to draw what that camera sees.

Is that your intention: to have two different views looking in two different directions? Or do you only want to switch your view direction in the main window to an alternate direction? If that’s your plan, you might be better off simply reparenting windowFramework->get_camera(0) to a new node under a different transform (or, more directly, simply changing the transform on the camera itself).

David

Thanks for the reply.

What I’d like to do is render a different view of the scene into an offscreen buffer so I guess I want “two different views looking in two different directions”. I’m experimenting with some custom rendering.

I’m using the following code to create the buffer:

PT(GraphicsWindow) gw = window->get_graphics_window();
PT(GraphicsStateGuardian) gsg = gw->get_gsg();

PT(GraphicsOutput) buffer;
buffer = gsg->get_engine()->make_buffer(gsg, bufferName, 0, 100, 100);

How do I connect this buffer to the camera?

OK. You only need a DisplayRegion to connect it to the camera, as in:

DisplayRegion *region = buffer->make_display_region();
region->set_camera(new_camera);

You may also choose to use PandaFramework::open_window() to construct an offscreen buffer and connect it to the camera and such (despite its name, it can create windows or buffers). This would be something like:

WindowProperties props = WindowProperties::size(100, 100);
int flags = GraphicsPipe::BF_refuse_window | GraphicsPipe::BF_fb_props_optional;
WindowFramework *new_wf = framework.open_buffer(props, flags, gsg->get_pipe(), gsg);

David

Here’s what I’ve got so far:


	// Setup the offscreen buffer.
	const string bufferName("myBuffer");
	PT(GraphicsWindow) gw = window->get_graphics_window(); 
	PT(GraphicsStateGuardian) gsg = gw->get_gsg();
	PT(GraphicsOutput) buffer; 
	buffer = gsg->get_engine()->make_buffer(gsg, bufferName, 0, 128, 128);
	
	// Setup the camera for the offscreen buffer.
	PT(Camera) secondCamera = new Camera("secondCamera");
	NodePath camera_np = window->get_camera_group().attach_new_node(secondCamera);
	camera_np.look_at(sphere);
	
	// Connect the buffer and the camera.
	DisplayRegion *region = buffer->make_display_region(); 
	region->set_camera(camera_np);

What I’d like to be able to do is pop up a window that shows what’s being rendered by the second camera.

What’s the best way to do this?

You need to set the buffer to render to a texture, and then you can apply that texture to a card or anywhere else:

PT(Texture) tbuf = new Texture("tbuf");
buffer->add_render_texture(tbuf, GraphicsOutput::RTM_bind_or_copy);
CardMaker cm("cm");
NodePath card(cm.generate());
card.reparent_to(window->get_aspect2d());
card.set_texture(tbuf);

David

What’s the best way to display the card to the user? Can it be shown in a separate window?

Also, if I want to find out some of the texel values should I use TexturePeeker?

BTW, thanks for all the help.

The best way to display the card to the user is to attach it to render2d (or aspect2d), as I demonstrate above. If you want to put it in a separate window, you can do this too; but it means you need to open a new window with framework->open_window(), and attach your card to the aspect2d of that window.

Or, you can simply open your buffer as a window itself (replacing BF_refuse_window with BF_require_window) so that you can see its contents directly.

If you want to inspect the texel values directly you have to do more work. Normally, the texture is not copied to system RAM; it remains in your graphics memory for efficiency. This means that there is no way for you to access its contents. But you can replace RTM_bind_or_copy with RTM_copy_ram, and this forces the texture to be copied (slowly) to system RAM each frame. Then, you can inspect the texel values with the TexturePeeker if you like, or by direct examination of tbuf->get_ram_image(), or by saving it into a PNMImage via:

PNMImage image;
tbuf->store(image);

and then iterating through the pixels of the PNMImage.

David

I’m a bit unclear as to how the first part of what you suggest is done. Here’s what I’ve tried:

	// Setup the output texture.
	WindowFramework *texWindow = framework.open_window();
	PT(Texture) tbuf = new Texture("tbuf"); 
	buffer->add_render_texture(tbuf, GraphicsOutput::RTM_bind_or_copy); 
	CardMaker cm("cm"); 
	NodePath card(cm.generate()); 
	card.reparent_to(texWindow->get_aspect_2d()); 
	card.set_texture(tbuf);

There are two problems with this. Firstly, I’m getting an error:

:display:tinydisplay(error): Could not load tbuf

Secondly, the window that should be showing the second camera view is showing a white square.

Obviously I’m not doing this right.

What do you suggest?

Note that your buffer size must be a power of 2, e.g. 128x128, rather than 100x100, to satisfy the requirement that the texture size must be a power of 2.

David

The buffer I’m using is 128 x 128. Here’s the code I’m using:


	// Setup the offscreen buffer.
	const string bufferName("myBuffer");
	PT(GraphicsWindow) gw = window->get_graphics_window(); 
	PT(GraphicsStateGuardian) gsg = gw->get_gsg();
	PT(GraphicsOutput) buffer; 
	buffer = gsg->get_engine()->make_buffer(gsg, bufferName, 0, 128, 128);
	
	// Setup the camera for the offscreen buffer.
	PT(Camera) secondCamera = new Camera("secondCamera");
	NodePath camera_np = window->get_camera_group().attach_new_node(secondCamera);
	camera_np.look_at(sphere);
	
	// Connect the buffer and the camera.
	DisplayRegion *region = buffer->make_display_region(); 
	region->set_camera(camera_np);
	region->set_active(true);
	
	// Setup the output texture.
	WindowFramework *texWindow = framework.open_window();
	PT(Texture) tbuf = new Texture("tbuf"); 
	buffer->add_render_texture(tbuf, GraphicsOutput::RTM_bind_or_copy); 
	CardMaker cm("cm"); 
	NodePath card(cm.generate()); 
	card.reparent_to(texWindow->get_aspect_2d()); 
	card.set_texture(tbuf);

The two problems I’m having are described in my previous post.

Hmm, try passing the GSG to the open_window() constructor, so that it uses the same context:

WindowFramework *texWindow = framework.open_window(gsg->get_pipe(), gsg);

Also, though this isn’t related to your problem, you can ensure that your card completely fills the new window by setting its frame to -1 … 1 in both axes, and using render2d instead of aspect2d:

   CardMaker cm("cm");
   cm.set_frame(-1, 1, -1, 1);
   NodePath card(cm.generate());
   card.reparent_to(texWindow->get_render_2d());

Edit: another addendum. It’s probably best to use the flavor of make_buffer() that accepts a GraphicsOutput. The flavor that accepts a GSG is deprecated and unreliable.

buffer = gsg->get_engine()->make_buffer(gw, bufferName, 0, 128, 128); 

David

I did what was suggested and tried:

buffer = gsg->get_engine()->make_buffer(gw, bufferName, 0, 128, 128);

instead of what was there before, however it’s giving me the following error:

GrassAndWalls.cpp: In function ‘int main(int, char**)’:
GrassAndWalls.cpp:63: error: no matching function for call to ‘GraphicsEngine::make_buffer(PointerTo&, const std::string&, int, int, int)’
/Applications/Panda3D/1.6.2/include/graphicsEngine.I:121: note: candidates are: GraphicsOutput* GraphicsEngine::make_buffer(GraphicsStateGuardian*, const std::string&, int, int, int)
make: *** [test.o] Error 1

Oh, the new function only exists in 1.7.0 or higher. If you’re using 1.6.2, you should use the function call you were using.

David

I’ve updated to 1.7.0. I’ve got a second window opening however it’s only displaying a grey screen. What I want is for the second window to show the “sphere” object. Here’s the code so far:


	// Setup the offscreen buffer.
	const string bufferName("myBuffer");
	PT(GraphicsWindow) gw = window->get_graphics_window(); 
	PT(GraphicsStateGuardian) gsg = gw->get_gsg();
	PT(GraphicsOutput) buffer; 
	buffer = gsg->get_engine()->make_buffer(gw, bufferName, 0, 128, 128);

	// Setup the camera for the offscreen buffer.
	PT(Camera) secondCamera = new Camera("secondCamera");
	NodePath camera_np = window->get_camera_group().attach_new_node(secondCamera);
	camera_np.look_at(sphere);
	
	// Connect the buffer and the camera.
	DisplayRegion *region = buffer->make_display_region(); 
	region->set_camera(camera_np);
	region->set_active(true);
	
	// Setup the output texture.
	WindowFramework *texWindow = framework.open_window(gsg->get_pipe(), gsg);
	PT(Texture) tbuf = new Texture("tbuf"); 
	buffer->add_render_texture(tbuf, GraphicsOutput::RTM_bind_or_copy); 
	CardMaker cm("cm"); 
	cm.set_frame(-1, 1, -1, 1); 
	NodePath card(cm.generate()); 
	card.reparent_to(texWindow->get_render_2d());
	card.set_texture(tbuf);