New stereo mode

Antonio Garcia <[email protected]> Tue, 11 Sep 2012 23:16:49 +0200
Newsgroups gmane.comp.python.visualpython.user
Message-ID <CAKtwE=QrN6mHqBjoe9uGM=Z2geVAr+XLb234C9r2USbw_-B_gg@mail.gmail.com>
I just modify a bit the vpython code to admit a new stereo mode called
"real".

This mode, try to simulate the real vision, what an observer view in the
real world. I have used the stereodepth as a half distance between
eyes, although I should have create a new parameter. I've used a real
vector rather normalized vector because,I think is more realistic. This
mode doesn't use the range (gcf) value, that it's not needed. You can make
the zoom using the user scale. This mode fix the point of view of the two
cameras separated by a given distance and look at the center.

It's the first version, and probably there will be things to improve.I need
this mode for my project, I'm working in a stereoscopic robot, and I using
vpython as a  way to represent (for now) a simple reality.

I attach the new code display_kernel.cpp display_kernel.hpp and ui.py

Best regards,

------------------------------------------------------------------------------
Live Security Virtual Conference
Exclusive live event will cover all the ways today's security and 
threat landscape has changed and how IT managers can respond. Discussions 
will include endpoint security, mobile security and the latest in malware 
threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/

_______________________________________________
Visualpython-users mailing list
Visualpython-users-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
https://lists.sourceforge.net/lists/listinfo/visualpython-users
display_kernel.cpp (text/x-c++src, 51.7 KB)
// Copyright (c) 2000, 2001, 2002, 2003 by David Scherer and others.
// Copyright (c) 2003, 2004 by Jonathan Brandmeyer and others.
// See the file license.txt for complete license terms.
// See the file authors.txt for a complete list of contributors.

#include "display_kernel.hpp"
#include "util/errors.hpp"
#include "util/tmatrix.hpp"
#include "util/gl_enable.hpp"
#include "material.hpp"
#include "frame.hpp"
#include "text.hpp"
#include "wrap_gl.hpp"

#include <cassert>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <iostream>
#include <boost/scoped_array.hpp>

#include <boost/lexical_cast.hpp>

namespace cvisual {

shared_ptr<display_kernel> display_kernel::selected;

bool display_kernel::enable_shaders = true;

////////////////////////////////////////////////////////////////
// Implementation of display_kernel::waitWhileAnyDisplayVisible()

static mutex displays_visible_lock;
static boost::condition displays_visible_condition;
static int displays_visible = 0;
void set_display_visible( display_kernel*, bool visible ) {
	lock L( displays_visible_lock );
	if (visible) displays_visible++;
	else displays_visible--;
	displays_visible_condition.notify_all();
}
void
display_kernel::waitWhileAnyDisplayVisible()
{
	python::gil_release gil;

	lock L( displays_visible_lock );
	while ( displays_visible )
		displays_visible_condition.wait( L );
}
////////////////////////////////////////////////////////////////

static const display_kernel::EXTENSION_FUNCTION notImplemented = (display_kernel::EXTENSION_FUNCTION)-1;

void
display_kernel::enable_lights(view& scene)
{
	scene.light_count[0] = 0;
	scene.light_pos.clear();
	scene.light_color.clear();
	std::list<shared_ptr<renderable> >::iterator i = layer_world.begin();
	std::list<shared_ptr<renderable> >::iterator i_end = layer_world.end();
	for(; i != i_end; ++i)
		(*i)->render_lights( scene );
	std::vector<shared_ptr<renderable> >::iterator j = layer_world_transparent.begin();
	std::vector<shared_ptr<renderable> >::iterator j_end = layer_world_transparent.end();
	for(; j != j_end; ++j)
		(*j)->render_lights( scene );

	tmatrix world_camera; world_camera.gl_modelview_get();
	vertex p;

	// Clear modelview matrix since we are multiplying the light positions ourselves
	gl_matrix_stackguard guard;
	glLoadIdentity();

	for(int i=0; i<scene.light_count[0] && i<8; i++) {
		int li = i*4;

		// Transform the light into eye space
		for(int d=0; d<4; d++) p[d] = scene.light_pos[li+d];
		p = world_camera * p;
		for(int d=0; d<4; d++) scene.light_pos[li+d] = p[d];

		// Enable the light for fixed function lighting.  This is unnecessary if everything in the scene
		// uses materials and the card supports our shaders, but for now...
		int id = GL_LIGHT0 + i;
		glLightfv( id, GL_DIFFUSE, &scene.light_color[li]);
		glLightfv( id, GL_SPECULAR, &scene.light_color[li]);
		glLightfv( id, GL_POSITION, &scene.light_pos[li]);
		glEnable(id);
	}
	for(int i=scene.light_count[0]; i<8; i++)
		glDisable( GL_LIGHT0 + i );

	glEnable( GL_LIGHTING);
	glLightModelfv( GL_LIGHT_MODEL_AMBIENT, &ambient.red);

	check_gl_error();
}

void
display_kernel::disable_lights()
{
	glDisable( GL_LIGHTING);
}

// Compute the horizontal and vertial tangents of half the field-of-view.
void
display_kernel::tan_hfov( double* x, double* y)
{
	// tangent of half the field of view.
	double tan_hfov = std::tan( fov*0.5);
	double aspect_ratio = (double)view_height / view_width;
	  //if (stereo_mode == PASSIVE_STEREO || stereo_mode == CROSSEYED_STEREO )
	if (stereo_mode == PASSIVE_STEREO || stereo_mode == CROSSEYED_STEREO || stereo_mode == REAL_STEREO )
		aspect_ratio *= 2.0;
	if (aspect_ratio > 1.0) {
		// Tall window
		*x = tan_hfov / aspect_ratio;
		*y = tan_hfov;
	}
	else {
		// Wide window
		*x = tan_hfov;
		*y = tan_hfov * aspect_ratio;
	}
}

vector
display_kernel::calc_camera()
{
	return camera;
	/* old scheme not necessary?
	double tan_hfov_x = 0.0;
	double tan_hfov_y = 0.0;
	tan_hfov( &tan_hfov_x, &tan_hfov_y);
	double cot_hfov = 1 / std::min(tan_hfov_x, tan_hfov_y);
	return (-forward.norm() * cot_hfov*user_scale).scale(range) + center;
	*/
}

display_kernel::display_kernel()
	:
	exit(true),
	visible(false),
	explicitly_invisible(false),
	fullscreen(false),
	title( "VPython" ),
	window_x(0), window_y(0), window_width(430), window_height(450),
	view_width(-1), view_height(-1),
	center(0, 0, 0),
	stereo_mode( NO_STEREO),
	forward(0, 0, -1),
	internal_forward(0, 0, -1),
	up(0, 1, 0),
	forward_changed(true),
	fov( 60 * M_PI / 180.0),
	autoscale(true),
	autocenter(false),
	uniform(true),
	camera(0,0,0),
	user_scale(1.0),
	gcf(1.0),
	gcfvec(vector(1.0,1.0,1.0)),
	gcf_changed(false),
	ambient( 0.2f, 0.2f, 0.2f),
	show_toolbar( false),
	show_rendertime( false),
	last_time(0),
	background(0, 0, 0), //< Transparent black.
	spin_allowed(true),
	zoom_allowed(true),
	mouse_mode( ZOOM_ROTATE),
	stereodepth( 0.0f),
	lod_adjust(0),
	realized(false),
	mouse( *this ),
	range_auto(0.0),
	range(0,0,0),
	world_extent(0.0)
{
}

display_kernel::~display_kernel()
{
	if (visible)
		set_display_visible( this, false );
}

void
display_kernel::report_closed() {
	if (visible)
		set_display_visible( this, false );

	VPYTHON_NOTE("report_closed: try to lock realize_lock.");
	lock L( realize_lock );
	VPYTHON_NOTE("report_closed: locked realize_lock.");
	realized = false;
	visible = false;
	explicitly_invisible = true;
	realize_condition.notify_all();
	VPYTHON_NOTE("report_closed: executed realize_condition.notify_all().");
}

void
display_kernel::report_camera_motion( int dx, int dy, mouse_button button )
{
	// This stuff handles automatic movement of the camera in response to user
	// input. See also view_to_world_transform for how the affected variables
	// are used to actually position the camera.

	// Scaling conventions:
	// the full width of the widget rotates the scene horizontally by 120 degrees.
	// the full height of the widget rotates the scene vertically by 120 degrees.
	// the full height of the widget zooms the scene by a factor of 10

	// Panning conventions:
	// The full height or width of the widget pans the scene by the eye distance.

	// Locking:
	// center and forward are already synchronized. The only variable that
	// remains to be synchronized is user_scale.

	// The vertical and horizontal fractions of the window's height that the
	// mouse has traveled for this event.
	// TODO: Implement ZOOM_ROLL modes.
	float vfrac = (float)dy / view_height;
	float hfrac = dx
		/ ((stereo_mode == PASSIVE_STEREO || stereo_mode == CROSSEYED_STEREO  || stereo_mode == REAL_STEREO) ?
		     (view_width*0.5f) : view_width);

	// float hfrac = dx
	// 	/ ((stereo_mode == PASSIVE_STEREO || stereo_mode == CROSSEYED_STEREO  ) ?
	// 	     (view_width*0.5f) : view_width);

	// The amount by which the scene should be shifted in response to panning
	// motion.
	// TODO: Keep this synchronized with the eye_dist calc in
	// world_view_transform
	double tan_hfov_x = 0.0;
	double tan_hfov_y = 0.0;
	tan_hfov( &tan_hfov_x, &tan_hfov_y);
	double pan_rate = (center - calc_camera()).mag()
		* std::min( tan_hfov_x, tan_hfov_y);

	switch (button) {
		case NONE: case LEFT:
			break;
		case MIDDLE:
			switch (mouse_mode) {
				case FIXED:
					// Locked.
					break;
				case PAN:
					// Pan front/back.
					if (spin_allowed)
						center += pan_rate * vfrac * internal_forward.norm();
					break;
				case ZOOM_ROLL: case ZOOM_ROTATE:
					// Zoom in/out.
					if (zoom_allowed)
						user_scale *= std::pow( 10.0f, vfrac);
					break;
			}
			break;
		case RIGHT:
			switch (mouse_mode) {
				case FIXED: case ZOOM_ROLL:
					break;
				case PAN: {
					// Pan up/down and left/right.
					// A vector pointing along the camera's horizontal axis.
					vector horiz_dir = internal_forward.cross(up).norm();
					// A vector pointing along the camera's vertical axis.
					vector vert_dir = horiz_dir.cross(internal_forward).norm();
					if (spin_allowed) {
						center += -horiz_dir * pan_rate * hfrac;
						center += vert_dir * pan_rate * vfrac;
					}
					break;
				}
				case ZOOM_ROTATE: {
					if (spin_allowed) {
						// Rotate
						// First perform the rotation about the up vector.
						tmatrix R = rotation( -hfrac * 2.0, up.norm());
						internal_forward = R * internal_forward;

						// Then perform rotation about an axis orthogonal to up and forward.
						double vertical_angle = vfrac * 2.0;
						double max_vertical_angle = up.diff_angle(-internal_forward.norm());

						// Over the top (or under the bottom) rotation
						if (!(vertical_angle >= max_vertical_angle ||
							vertical_angle <= max_vertical_angle - M_PI)) {
							// Over the top (or under the bottom) rotation
							R = rotation( -vertical_angle, internal_forward.cross(up).norm());
							forward = internal_forward = R*internal_forward;
							forward_changed = true;
						}
					}
					break;
				}
			}
			break;
	}
}

void
display_kernel::report_window_resize( int win_x, int win_y, int win_w, int win_h )
{
	window_x = win_x; window_y = win_y; window_width = win_w; window_height = win_h;
}

void
display_kernel::report_view_resize(	int v_w, int v_h )
{
	view_width = std::max(v_w,1); view_height = std::max(v_h,1);
}

void
display_kernel::realize()
{
	clear_gl_error();
	if (!extensions) {
		using namespace std;
		VPYTHON_NOTE( "Querying the list of OpenGL extensions.");
		extensions.reset( new set<string>());
		istringstream strm( string( (const char*)(glGetString( GL_EXTENSIONS))));
		copy( istream_iterator<string>(strm), istream_iterator<string>(),
			inserter( *extensions, extensions->begin()));

		vendor = std::string((const char*)glGetString(GL_VENDOR));
		version = std::string((const char*)glGetString(GL_VERSION));
		renderer = std::string((const char*)glGetString(GL_RENDERER));

		// The test is a hack so that subclasses not bothering to implement getProcAddress just
		//   don't get any extensions.
		if (getProcAddress("display_kernel::getProcAddress") != notImplemented)
			glext.init( *this );
	}

	// Those features of OpenGL that are always used are set up here.
	// Depth buffer properties
	glClearDepth( 1.0);
	glEnable( GL_DEPTH_TEST);
	glDepthFunc( GL_LEQUAL);

	// Lighting model properties
	glShadeModel( GL_SMOOTH);
	// TODO: Figure out what the concrete costs/benefits of these commands are.
	// glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST);
	glHint( GL_LINE_SMOOTH_HINT, GL_NICEST);
	glHint( GL_POINT_SMOOTH_HINT, GL_NICEST);
	glEnable( GL_NORMALIZE);
	glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	glEnable( GL_COLOR_MATERIAL);
	glEnable( GL_BLEND );
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	// Ensures that fully transparent pixels don't write into the depth buffer,
	// ever.
	glEnable( GL_ALPHA_TEST);
	glAlphaFunc( GL_GREATER, 0.0);

	// FSAA.  Doesn't seem to have much of an effect on my TNT2 card.  Grrr.
	if ( hasExtension( "GL_ARB_multisample" ) ) {
		glEnable( GL_MULTISAMPLE_ARB);
		GLint n_samples, n_buffers;
		glGetIntegerv( GL_SAMPLES_ARB, &n_samples);
		glGetIntegerv( GL_SAMPLE_BUFFERS_ARB, &n_buffers);
		VPYTHON_NOTE( "Using GL_ARB_multisample extension: samples:"
			+ boost::lexical_cast<std::string>(n_samples)
			+ " buffers: " + boost::lexical_cast<std::string>(n_buffers));
	}

	check_gl_error();
}

// Set up matricies for transforms from world coordinates to view coordinates
// Precondition: the OpenGL Modelview and Projection matrix stacks should be
// at the bottom.
// Postcondition: active matrix stack is GL_MODELVIEW, matrix stacks are at
// the bottom.  Viewing transformations have been applied.  geometry.camera
// is initialized.
// whicheye: -1 for left, 0 for center, 1 for right.
void
display_kernel::world_to_view_transform(
	view& geometry, int whicheye, bool forpick)
{
	// See http://www.stereographics.com/support/developers/pcsdk.htm for a
	// discussion regarding the design basis for the frustum offset code.

	// gcf scales the region encompassed by scene.range_* into a ROUGHLY 2x2x2 cube.
	// Note that this is NOT necessarily the entire world, since scene.range
	//   can be changed.
	// This coordinate system is used for most of the calculations below.

        vector scene_center;
        vector scene_forward;
	vector Oo=vector(stereodepth*whicheye,0,0);

        if (stereo_mode == REAL_STEREO){
	  scene_center = center;
	  scene_forward = internal_forward+Oo;
        }else{
    	    scene_center = center.scale(gcfvec);
	    scene_forward = internal_forward.norm();
        }
	vector scene_up = up.norm();

	// the horizontal and vertical tangents of half the field of view.
	double tan_hfov_x;
	double tan_hfov_y;
	tan_hfov( &tan_hfov_x, &tan_hfov_y);

	// The cotangent of half of the wider field of view.
	double cot_hfov;
	if (!uniform) // We force width to be 2.0 (range.x 1.0)
		cot_hfov = 1.0 / tan_hfov_x;
	else
		cot_hfov = 1.0 / std::max(tan_hfov_x, tan_hfov_y);

	// The camera position is chosen by the tightest of the enabled range_* modes.
	double cam_to_center_without_zoom = 1e150;

	/*if (range_sphere_radius)
		cam_to_center_without_zoom = std::min(cam_to_center_without_zoom,
			range_sphere_radius / sin( fov * 0.5 ) );
	if (range_box_size.nonzero()) {
		if (range_unrotated) {
			cam_to_center_without_zoom = std::min(cam_to_center_without_zoom,
				std::max(range_box_size.x, range_box_size.y) * 0.5 * cot_hfov + range_box_size.z * 0.5);
		} else
			cam_to_center_without_zoom = std::min(cam_to_center_without_zoom,
				range_box_size.mag() * 0.5 / sin( fov * 0.5 ) );
	}*/

	if (range_auto)
		cam_to_center_without_zoom = std::min(cam_to_center_without_zoom,
			range_auto);
	if (range.nonzero())
		cam_to_center_without_zoom = std::min(cam_to_center_without_zoom,
			range.x * cot_hfov / 1.02);
	if (cam_to_center_without_zoom >= 1e150)
		cam_to_center_without_zoom = 10.0 / sin( fov * 0.5 );
	cam_to_center_without_zoom *= gcf * 1.02;

	// Position camera so that a sphere containing the box range will fit on the screen
	//   OR a 2*user_scale cube will fit.  The former is tighter for "non cubical" ranges
	//   and the latter is tighter for cubical ones.
	/*double radius = range.mag() * gcf * user_scale;
	double cam_to_center_without_zoom = 1.02 * std::min( radius / sin( fov * 0.5 ),
		                                                 cot_hfov + 1.0 );*/

	vector scene_camera;
        if (stereo_mode == REAL_STEREO){
	  scene_camera = scene_center - scene_forward*user_scale;
	}else{
	  scene_camera = scene_center - cam_to_center_without_zoom*user_scale*scene_forward;
	}

	double nearest, farthest;
	world_extent.get_near_and_far(internal_forward, nearest, farthest); // nearest and farthest points relative to scene.center when projected onto forward

	nearest = nearest*gcf;
	farthest = farthest*gcf;

	double cam_to_center = (scene_center - scene_camera).mag();

	// Z buffer resolution is highly sensitive to nearclip - a "small" camera will have terrible z buffer
	//   precision for distant objects.  PLEASE don't fiddle with this unless you know what kind of
	//   test cases you need to see the results, including at nonstandard fields of view and 24 bit
	//   z buffers!
	// The equation for nearclip below is designed to give similar z buffer resolution at all fields of
	//   view.  It's a little weird, but seems to give acceptable results in all the cases I've been able
	//   to test.
	// The other big design question here is the effect of "zoom" (user_scale) on the near clipping plane.
	//   Most users will have the mental model that this moves the camera closer to the scene, rather than
	//   scaling the scene up.  There is actually a difference since the camera has a finite "size".
	//   Unfortunately, following this model leads to a problem with zooming in a lot!  The problem is
	//   especially pronounced at tiny fields of view, which typically have an enormous camera very far away;
	//   when you try to zoom in the big camera "crashes" into the tiny scene!  So instead we use the
	//   slightly odd model of scaling the scene, or equivalently making the camera smaller as you zoom in.
	

	double fwz;
	double nearclip;
	double farclip;

        if (stereo_mode == REAL_STEREO){
	  nearclip = cam_to_center *0.01 *user_scale;
	  farclip = cam_to_center*100;
	  nearclip= 0.01;
	  //farclip= 1e150;
	}
	else{
	  double fwz = cam_to_center_without_zoom + 1.0;
	  nearclip = fwz * fwz / (100 + fwz) * user_scale;
	  farclip = (farthest + cam_to_center) * 1.05;  //< actual maximum z in scene plus a little
	  farclip = std::max( farclip, nearclip * 1.001 ); //< just in case everything is behind the camera!
	}


	// TODO: nearclip = std::max( nearclip, (cam_to_center + nearest) * 0.95 );  //< ?? boost z buffer resolution if there's nothing close to camera?
	//double farclip = (farthest + cam_to_center) * 1.05;  //< actual maximum z in scene plus a little
	//farclip = std::max( farclip, nearclip * 1.001 ); //< just in case everything is behind the camera!

	// Here is the stereodepth and eye offset machinery from Visual 3, where the docs claimed that
	// stereodepth=0 was the default (zero-parallax plane at screen surface;
	// stereodepth=1 moves the center of the scene to the screen surface;
	// stereodepth=2 moves the back of the scene to the screen surface:
	/*
	double farclip = cotfov + ext;
	double nearclip = 0.0;
	if ((cam - display->c_center).mag() < display->c_extent.mag()) {
		// Then the camera is within the scene.  Pick a value that looks OK.
		nearclip = 0.015;
	}
	else {
		nearclip = cotfov - ext*1.5;
		if (nearclip < 0.01*farclip)
			nearclip = 0.01*farclip;
	}
	double R = nearclip*hfov;
	double T = nearclip*vfov;

	double fl = 0.5*ext + ext*stereodepth + nearclip;  //focal length
	double eyeOffset = eyesign*fl/60.0;  // eye separation 1/30 of focallength
	double eyeOffset1 = eyeOffset * (nearclip/fl);
	frustum(proj, iproj, -R-eyeOffset1, R-eyeOffset1, -T, T, nearclip, farclip);
	*/

	// A multiple of the number of cam_to_center's away from the camera to place
	// the zero-parallax plane.
	// The distance from the camera to the zero-parallax plane.


	double camera_stereo_offset;
	double frustum_stereo_offset;

        if (stereo_mode == REAL_STEREO){
	  //Take the stereodepht as focal distance between OOd and OOi
	  //TODO: introducing new parameter for focal distance
	  //scene_camera += Oo*whicheye;
	  //frustum_stereo_offset = stereodepth *  whicheye;
	  frustum_stereo_offset=0;

	}else{

	  double focallength = cam_to_center+0.5*stereodepth;
	  // Translate camera left/right 2% of the viewable width of the scene at
	  // the distance of its center.
	  //double camera_stereo_offset = tan_hfov_x * cam_to_center * 0.02;
	  camera_stereo_offset = tan_hfov_x * focallength * 0.02;
	  vector camera_stereo_delta = camera_stereo_offset * up.cross( scene_camera).norm() * whicheye;
	  
	  scene_camera += camera_stereo_delta;
	  scene_center += camera_stereo_delta;
	
	  // The amount to translate the frustum to the left and right.
	  frustum_stereo_offset = camera_stereo_offset * nearclip / focallength * whicheye;
	}

	// Finally, the OpenGL transforms based on the geometry just calculated.
	clear_gl_error();
	// Position the camera.
	glMatrixMode( GL_MODELVIEW);
	glLoadIdentity();

	#if 0	// Enable this to peek at the actual scene geometry.
	int max_proj_stack_depth = -1;
	int max_mv_stack_depth = -1;
	int proj_stack_depth = -1;
	int mv_stack_depth = -1;
	glGetIntegerv( GL_MAX_PROJECTION_STACK_DEPTH, &max_proj_stack_depth);
	glGetIntegerv( GL_MAX_MODELVIEW_STACK_DEPTH, &max_mv_stack_depth);
	glGetIntegerv( GL_PROJECTION_STACK_DEPTH, &proj_stack_depth);
	glGetIntegerv( GL_MODELVIEW_STACK_DEPTH, &mv_stack_depth);
	std::cerr << "scene_geometry: camera:" << scene_camera
        << " true camera:" << camera << std::endl
	<< " center:" << scene_center << " true center:" << center << std::endl
	<< " forward:" << scene_forward << " true forward:" << forward << std::endl
	<< " up:" << scene_up << " range:" << range << " gcf:" << gcf  << std::endl
	<< " nearclip:" << nearclip << " nearest:" << nearest << std::endl
	<< " farclip:" << farclip << " farthest:" << farthest << std::endl
		  << " user_scale:" << user_scale << std::endl;
	  //<< " cot_hfov:" << cot_hfov << " tan_hfov_x:" << tan_hfov_x << std::endl
	  //<< " tan_hfov_y: " << tan_hfov_y << std::endl
	  //<< " window_width:" << window_width << " window_height:" << window_height << std::endl
	  //<< " max_proj_depth:" << max_proj_stack_depth << " current_proj_depth:" << proj_stack_depth << std::endl
	  //<< " max_mv_depth:" << max_mv_stack_depth << " current_mv_depth:" << mv_stack_depth << std::endl;
	//world_extent.dump_extent();
	std::cerr << std::endl;
	#endif

	gluLookAt(
	 	scene_camera.x, scene_camera.y, scene_camera.z,
	 	scene_center.x, scene_center.y, scene_center.z,
	 	scene_up.x, scene_up.y, scene_up.z);

	tmatrix world_camera; world_camera.gl_modelview_get();
	inverse( geometry.camera_world, world_camera );

	//vector scene_range = range * gcf;
	//glScaled( 1.0/scene_range.x, 1.0/scene_range.y, 1.0/scene_range.z);

	// Establish a parallel-axis asymmetric stereo projection frustum.
	glMatrixMode( GL_PROJECTION);
	if (!forpick)
		glLoadIdentity();
	if (whicheye == 1) {
		frustum_stereo_offset = -frustum_stereo_offset;
	}
	else if (whicheye == 0) {
		frustum_stereo_offset = 0;
	}

	if (nearclip<=0 || farclip<=nearclip || tan_hfov_x<=0 || tan_hfov_y<=0) {
		std::ostringstream msg;
		msg << "VPython degenerate projection: " << nearclip << " " << farclip << " " << tan_hfov_x << " " << tan_hfov_y;
		VPYTHON_CRITICAL_ERROR( msg.str());
		std::exit(1);
	}

	glFrustum(
		-nearclip * tan_hfov_x + frustum_stereo_offset,
		nearclip * tan_hfov_x + frustum_stereo_offset,
		-nearclip * tan_hfov_y,
		nearclip * tan_hfov_y,
		nearclip,
		farclip );

	glMatrixMode( GL_MODELVIEW);
	check_gl_error();

	// The true camera position, in world space.
        if (stereo_mode == REAL_STEREO){
	  camera=scene_camera;
	}else{
	  camera = scene_camera/gcf;
	}


	// Finish initializing the view object.
	geometry.camera = camera;
	geometry.tan_hfov_x = tan_hfov_x;
	geometry.tan_hfov_y = tan_hfov_y;
	// The true viewing vertical direction is not the same as what is needed for
	// gluLookAt().
	geometry.up = internal_forward.cross_b_cross_c(up, internal_forward).norm();
}

// Calculate a new extent for the universe, adjust gcf, center, and world_scale
// as required.
void
display_kernel::recalc_extent(void)
{
	double tan_hfov_x;
	double tan_hfov_y;
	tan_hfov( &tan_hfov_x, &tan_hfov_y );
	double tan_hfov = std::max(tan_hfov_x, tan_hfov_y);

	while (1) {  //< Might have to do this twice for autocenter
		world_extent = extent_data( tan_hfov );

		tmatrix l_cw;
		l_cw.translate( -center );
		extent ext( world_extent, l_cw );

		world_iterator i( layer_world.begin());
		world_iterator end( layer_world.end());
		while (i != end) {
			i->grow_extent( ext);
			++i;
		}
		world_trans_iterator j( layer_world_transparent.begin());
		world_trans_iterator j_end( layer_world_transparent.end());
		while (j != j_end) {
			j->grow_extent( ext);
			++j;
		}
		if (autocenter) {
			vector c = world_extent.get_center() + center;
			if ( (center-c).mag2() > (center.mag2() + c.mag2()) * 1e-6 ) {
				// Change center and recalculate extent (since camera_z depends on center)
				center = c;
				continue;
			}
		}
		break;
	}
	if (autoscale && uniform) {
		double r = world_extent.get_camera_z();
		if (r > range_auto) range_auto = r;
		else if ( 3.0*r < range_auto ) range_auto = 3.0*r;
	}

	// Rough scale calculation for gcf.  Doesn't need to be exact.
	// TODO: If extent and range are very different in scale, we are using extent to drive
	//   gcf.  Both options have pros and cons.
	double mr = world_extent.get_range(vector(0,0,0)).mag();
	double scale = mr ? 1.0 / mr : 1.0;

	if (!uniform && range.nonzero()) {
		gcf_changed = true;
		gcf = 1.0/range.x;
		//gcf=1.0;

		double width = (stereo_mode == PASSIVE_STEREO || stereo_mode == CROSSEYED_STEREO  || stereo_mode == REAL_STEREO)
			? view_width*0.5 : view_width;

		// double width = (stereo_mode == PASSIVE_STEREO || stereo_mode == CROSSEYED_STEREO )
		// 	? view_width*0.5 : view_width;
		
		gcfvec = vector(1.0/range.x, (view_height/width)/range.y, 0.1/range.z);
		if (stereo_mode == REAL_STEREO){
		   gcf=1.0;
		   gcfvec=vector(1,1,1);
		 }

	} else {
		// TODO: Instead of changing gcf so much, we could change it only when it is 2x
		// off, to aid primitives whose caching may depend on gcf (but are there any?)
		if (gcf != scale) {
		  gcf = scale;
		  gcf_changed = true;
		  if (stereo_mode == REAL_STEREO){
		     gcf=1.0;
		   }
		}
		gcfvec = vector(gcf,gcf,gcf);
	}
}

void display_kernel::implicit_activate() {
	if (!visible && !explicitly_invisible)
		set_visible( true );
}

void
display_kernel::add_renderable( shared_ptr<renderable> obj)
{
	// Driven from visual/primitives.py set_visible
	if (!obj->translucent())
		layer_world.push_back( obj);
	else
		layer_world_transparent.push_back( obj);
	if (!obj->is_light())
		implicit_activate();
}

void
display_kernel::remove_renderable( shared_ptr<renderable> obj)
{
	// Driven from visual/primitives.py set_visible
	if (!obj->translucent()) {
		std::remove( layer_world.begin(), layer_world.end(), obj);
		layer_world.pop_back();
	}
	else {
		std::remove( layer_world_transparent.begin(), layer_world_transparent.end(), obj);
		layer_world_transparent.pop_back();
	}
}

bool
display_kernel::draw(
	view& scene_geometry, int whicheye)
{
	// Set up the base modelview and projection matrices
	world_to_view_transform( scene_geometry, whicheye);

	// Render all opaque objects in the world space layer
	enable_lights(scene_geometry);
	world_iterator i( layer_world.begin());
	world_iterator i_end( layer_world.end());
	while (i != i_end) {
		if (i->translucent()) {
			// The color of the object has become transparent when it was not
			// initially.  Move it to the transparent layer.  The penalty for
			// being rendered in the transparent layer when it is opaque is only
			// a small speed hit when it has to be sorted.  Therefore, that case
			// is not tested at all.  (TODO Untrue-- rendering opaque objects in transparent
			// layer makes it possible to have opacity artifacts with a single convex
			// opaque objects, provided other objects in the scene were ONCE transparent)
			layer_world_transparent.push_back( *i.base());
			i = layer_world.erase(i.base());
			continue;
		}

		i->outer_render( scene_geometry);
		++i;
	}

	// Perform a depth sort of the transparent world from back to front.
	if (layer_world_transparent.size() > 1)
		std::stable_sort(
			layer_world_transparent.begin(), layer_world_transparent.end(),
			z_comparator( internal_forward.norm()));

	// Render translucent objects in world space.
	world_trans_iterator j( layer_world_transparent.begin());
	world_trans_iterator j_end( layer_world_transparent.end());
	while (j != j_end) {
		j->outer_render( scene_geometry );
		++j;
	}

	// Render all objects in screen space.
	disable_lights();
	gl_disable depth_test( GL_DEPTH_TEST);
	typedef std::multimap<vector, displaylist, z_comparator>::iterator
		screen_iterator;
	screen_iterator k( scene_geometry.screen_objects.begin());
	screen_iterator k_end( scene_geometry.screen_objects.end());
	while ( k != k_end) {
		k->second.gl_render();
		++k;
	}
	scene_geometry.screen_objects.clear();

	return true;
}


// Renders the entire scene.
bool
display_kernel::render_scene(void)
{
	// TODO: Exception handling?
	if (!realized) {
		realize();

		lock L(realize_lock);
		realized = true;
		realize_condition.notify_all();
	}
	double start_time, cycle;
	if (show_rendertime) {
		start_time = render_timer.elapsed();
		cycle = start_time - last_time;
		last_time = start_time;
	}
	try {
		recalc_extent();
		view scene_geometry( internal_forward.norm(), center, view_width,
			view_height, forward_changed, gcf, gcfvec, gcf_changed, glext);
		scene_geometry.lod_adjust = lod_adjust;
		scene_geometry.enable_shaders = enable_shaders;
		clear_gl_error();

		on_gl_free.frame();

		glClearColor( background.red, background.green, background.blue, 0);
		// Control which type of stereo to perform.
		switch (stereo_mode) {
			case NO_STEREO:
				scene_geometry.anaglyph = false;
				scene_geometry.coloranaglyph = false;
				glViewport( 0, 0, view_width, view_height);
				glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
				draw(scene_geometry, 0);
				break;
			case ACTIVE_STEREO:
				scene_geometry.anaglyph = false;
				scene_geometry.coloranaglyph = false;
				glViewport( 0, 0, view_width, view_height);

				glDrawBuffer( GL_BACK_LEFT);
				glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

				draw( scene_geometry, -1);
				glDrawBuffer( GL_BACK_RIGHT);
				glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
				draw( scene_geometry, 1);

				break;
			case REDBLUE_STEREO:
				// Red channel
				scene_geometry.anaglyph = true;
				scene_geometry.coloranaglyph = false;
				glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
				glViewport( 0, 0, view_width, view_height);
				glColorMask( GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
				draw( scene_geometry, -1);
				// Blue channel
				glColorMask( GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE);
				glClear( GL_DEPTH_BUFFER_BIT);
				draw( scene_geometry, 1);
				// Put everything back
				glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
				break;
			case REDCYAN_STEREO:
				// Red channel
				scene_geometry.anaglyph = true;
				scene_geometry.coloranaglyph = true;
				glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
				glViewport( 0, 0, view_width, view_height);
				glColorMask( GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
				draw( scene_geometry, -1);
				// Green and Blue channels
				glColorMask( GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE);
				glClear( GL_DEPTH_BUFFER_BIT);
				draw( scene_geometry, 1);
				// Put everything back
				glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
				break;
			case YELLOWBLUE_STEREO:
				// Red and green channels
				scene_geometry.anaglyph = true;
				scene_geometry.coloranaglyph = true;
				glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
				glViewport( 0, 0, view_width, view_height);
				glColorMask( GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
				draw( scene_geometry, -1);
				// Blue channel
				glColorMask( GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE);
				glClear( GL_DEPTH_BUFFER_BIT);
				draw( scene_geometry, 1);
				// Put everything back
				glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
				break;
			case GREENMAGENTA_STEREO:
				// Green channel
			 	scene_geometry.anaglyph = true;
				scene_geometry.coloranaglyph = true;
				glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
				glViewport( 0, 0, view_width, view_height);
				glColorMask( GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
				draw( scene_geometry, -1);
				// Red and blue channels
				glColorMask( GL_TRUE, GL_FALSE, GL_TRUE, GL_TRUE);
				glClear( GL_DEPTH_BUFFER_BIT);
				draw( scene_geometry, 1);
				// Put everything back
				glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
				break;
			case PASSIVE_STEREO: {
				// Also handle viewport modifications.
				scene_geometry.view_width =  view_width/2;
				scene_geometry.anaglyph = false;
				scene_geometry.coloranaglyph = false;
				int stereo_width = int(scene_geometry.view_width);
				// Left eye
				glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
				glViewport( 0, 0, stereo_width, view_height );
				draw( scene_geometry, -1);
				// Right eye
				glViewport( stereo_width+1, 0, stereo_width, view_height);
				draw( scene_geometry, 1);
				break;
			}
			case REAL_STEREO: {  			
			  scene_geometry.view_width =  view_width/2;
			  scene_geometry.anaglyph = false;
			  scene_geometry.coloranaglyph = false;
			  int stereo_width = int(scene_geometry.view_width);
			  // Left eye
			  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
			  glViewport( 0, 0, stereo_width, view_height );
			  draw( scene_geometry, -1);
			  // Right eye
			  glViewport( stereo_width+1, 0, stereo_width, view_height);
			  draw( scene_geometry, 1);
			  break;
			}

			case CROSSEYED_STEREO: {
				// Also handle viewport modifications.
				scene_geometry.view_width =  view_width/2;
				scene_geometry.anaglyph = false;
				scene_geometry.coloranaglyph = false;
				int stereo_width = int(scene_geometry.view_width);
				// Left eye
				glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
				glViewport( 0, 0, stereo_width, view_height);
				draw( scene_geometry, 1);
				// Right eye
				glViewport( stereo_width+1, 0, stereo_width, view_height );
				draw( scene_geometry, -1);
				break;
			}
		}
		if (show_rendertime) {
			double render_time = render_timer.elapsed()-start_time, flush_time = -1;

			#if 0 //< Only for performance measurement; disable in shipping code
			glFinish();

			flush_time = render_timer.elapsed() - start_time - render_time;
			#endif

			std::wostringstream render_msg;
			render_msg.precision(3);

			// render time does not include pick time, which may be negligible
			//render_msg << "cycle: " << int(1000*cycle) <<
			//   " render: " << int(1000*(render_time));

			// render_time is only a portion of the actual paint time in render_manager.cpp,
			// so it is misleading to display it. In render_manager.cpp is measured actual paint time,
			// and buffer swap time, and it generates the interval to the start of the next paint.
			// The cycle time assumes only one scene, but at least it is accurate in this important special case.
			render_msg << "cycle: " << int(1000*cycle);

			if (flush_time>=0) render_msg << " flush: " << int(1000*flush_time);
			glColor3f(
				1.0f - background.red, 1.0f-background.green, 1.0f-background.blue);

			glMatrixMode( GL_PROJECTION);
			glPushMatrix();
			glLoadIdentity();
			gluOrtho2D( 0, view_width, 0, view_height);
			glMatrixMode( GL_MODELVIEW);
			glPushMatrix();
			glLoadIdentity();

			{
				gl_disable depth_test(GL_DEPTH_TEST);
				boost::shared_ptr<font> default_font = font::find_font();
				boost::shared_ptr<layout> lay_out = default_font->lay_out( render_msg.str());
				lay_out->gl_render( scene_geometry, vector(5, lay_out->extent( scene_geometry ).y + 3));
			}

			glPopMatrix();
			glMatrixMode( GL_PROJECTION);
			glPopMatrix();
			glMatrixMode( GL_MODELVIEW);
		}


		// Cleanup
		check_gl_error();
		gcf_changed = false;
		forward_changed = false;
	}
	catch (gl_error e) {
		std::ostringstream msg;
		msg << "OpenGL error: " << e.what() << ", aborting.\n";
		VPYTHON_CRITICAL_ERROR( msg.str());
		std::exit(1);
	}
	if (show_rendertime) {
		render_time = render_timer.elapsed()-start_time;
	}

	// TODO: Can we delay picking until the Python program actually wants one of these attributes?
	mouse.get_mouse().cam = camera;
	boost::tie( mouse.get_mouse().pick, mouse.get_mouse().pickpos, mouse.get_mouse().position) =
		pick( mouse.get_x(), mouse.get_y() );

	on_gl_free.frame();

	return true;
}

boost::tuple< shared_ptr<renderable>, vector, vector>
display_kernel::pick( int x, int y, float d_pixels)
{
	using boost::scoped_array;

	shared_ptr<renderable> best_pick;
    vector pickpos;
    vector mousepos;
	try {
		clear_gl_error();
		// Notes:
		// culled polygons don't count.  glRasterPos() does count.

		// Allocate a selection buffer of uints.  Format for returned hits is:
		// {uint32: n_names}{uint32: minimunm depth}{uint32: maximum depth}
		// {unit32[n_names]: name_stack}
		// n_names is the depth of the name stack at the time of the hit.
		// minimum and maximum depth are the minimum and maximum values in the
		// depth buffer scaled between 0 and 2^32-1. (source is [0,1])
		// name_stack is the full contents of the name stack at the time of the
		// hit.

		size_t hit_buffer_size = std::max(
				(layer_world.size()+layer_world_transparent.size())*4,
				world_extent.get_select_buffer_depth());
		// Allocate an exception-safe buffer for the GL to talk back to us.
		scoped_array<unsigned int> hit_buffer(
			new unsigned int[hit_buffer_size]);
		// unsigned int hit_buffer[hit_buffer_size];

		// Allocate a std::vector<shared_ptr<renderable> > to lookup names
		// as they are rendered.
		std::vector<shared_ptr<renderable> > name_table;
		// Pass the name stack to OpenGL with glSelectBuffer.
		glSelectBuffer( hit_buffer_size, (GLuint*)hit_buffer.get());
		// Enter selection mode with glRenderMode
		glRenderMode( GL_SELECT);
		glClear( GL_DEPTH_BUFFER_BIT);
		// Clear the name stack with glInitNames(), raise the height of the name
		// stack with glPushName() exactly once.
		glInitNames();
		glPushName(0);

		// Initialize the picking matrix.
		GLint viewport_bounds[4] = {
			0, 0, view_width, view_height
		};
		glMatrixMode( GL_PROJECTION);
		glLoadIdentity();
		gluPickMatrix( (float)x, (float)(view_height - y), d_pixels, d_pixels, viewport_bounds);
		view scene_geometry( internal_forward.norm(), center, view_width, view_height,
			forward_changed, gcf, gcfvec, gcf_changed, glext);
		scene_geometry.lod_adjust = lod_adjust;

		world_to_view_transform( scene_geometry, 0, true);

		// Iterate across the world, rendering each body for picking.
		std::list<shared_ptr<renderable> >::iterator i = layer_world.begin();
		std::list<shared_ptr<renderable> >::iterator i_end = layer_world.end();
		while (i != i_end) {
			glLoadName( name_table.size());
			name_table.push_back( *i);
			{
				(*i)->gl_pick_render( scene_geometry);
			}
			++i;
		}
		std::vector<shared_ptr<renderable> >::iterator j
			= layer_world_transparent.begin();
		std::vector<shared_ptr<renderable> >::iterator j_end
			= layer_world_transparent.end();
		while (j != j_end) {
			glLoadName( name_table.size());
			name_table.push_back( *j);
			{
				(*j)->gl_pick_render( scene_geometry);
			}
			++j;
		}
		// Return the name stack to the bottom with glPopName() exactly once.
		glPopName();

		// Exit selection mode, return to normal rendering rendering. (collects
		// the number of hits at this time).
		size_t n_hits = glRenderMode( GL_RENDER);
		check_gl_error();

		// Lookup the name to get the shared_ptr<renderable> associated with it.
		// The farthest point away in the depth buffer.
		double best_pick_depth = 1.0;
		unsigned int* hit_record = hit_buffer.get();
		unsigned int* const hit_buffer_end = hit_buffer.get() + hit_buffer_size;
		while (n_hits > 0 && hit_record < hit_buffer_end) {
			unsigned int n_names = hit_record[0];
			if (hit_record + 3 + n_names > hit_buffer_end)
				break;
			double min_hit_depth = static_cast<double>(hit_record[1])
				/ 0xffffffffu;
			if (min_hit_depth < best_pick_depth) {
				best_pick_depth = min_hit_depth;
				best_pick = name_table[*(hit_record+3)];
				if (n_names > 1) {
					// Then the picked object is the child of a frame.
					frame* ref_frame = dynamic_cast<frame*>(best_pick.get());
					assert(ref_frame != NULL);
					best_pick = ref_frame->lookup_name(
						hit_record + 4, hit_record + 3 + n_names);
				}
			}
			hit_record += 3 + n_names;
			n_hits--;
		}
		if (hit_record > hit_buffer_end)
			VPYTHON_CRITICAL_ERROR(
				"More objects were picked than could be reported by the GL."
				"  The hit buffer size was too small.");

        tmatrix modelview;
        modelview.gl_modelview_get();
        tmatrix projection;
        projection.gl_projection_get();
        gluUnProject(
            x, view_height - y, best_pick_depth,
            modelview.matrix_addr(),
            projection.matrix_addr(),
            viewport_bounds,
            &pickpos.x, &pickpos.y, &pickpos.z);
        // TODO: Replace the calls to gluUnProject() with own tmatrix inverse
        // and such for optimization
        vector tcenter;
        gluProject( center.x*gcf, center.y*gcf, center.z*gcf,
           	modelview.matrix_addr(),
            projection.matrix_addr(),
            viewport_bounds,
            &tcenter.x, &tcenter.y, &tcenter.z);

        gluUnProject(
        	x, view_height - y, tcenter.z,
        	modelview.matrix_addr(),
        	projection.matrix_addr(),
        	viewport_bounds,
        	&mousepos.x, &mousepos.y, &mousepos.z);
	}
	catch (gl_error e) {
		std::ostringstream msg;
		msg << "OpenGL error: " << e.what() << ", aborting.\n";
		VPYTHON_CRITICAL_ERROR( msg.str());
		std::exit(1);
	}
	pickpos.x /= gcfvec.x;
	pickpos.y /= gcfvec.y;
	pickpos.z /= gcfvec.z;
	mousepos.x /= gcfvec.x;
	mousepos.y /= gcfvec.y;
	mousepos.z /= gcfvec.z;
	return boost::make_tuple( best_pick, pickpos, mousepos);
}

void
display_kernel::gl_free()
{
	VPYTHON_NOTE( "Releasing GL resources");
	try {
		clear_gl_error();
		on_gl_free.shutdown();
		check_gl_error();
	}
	catch (gl_error& error) {
		VPYTHON_CRITICAL_ERROR( "Caught OpenGL error during shutdown: "
			+ std::string(error.what())
			+ "; Continuing with the shutdown.");
	}
	VPYTHON_NOTE( "GL resource release complete");
}

void
display_kernel::allow_spin(bool b)
{
	spin_allowed = b;
}

bool
display_kernel::spin_is_allowed(void) const
{
	return spin_allowed;
}

void
display_kernel::allow_zoom(bool b)
{
	zoom_allowed = b;
}

bool
display_kernel::zoom_is_allowed(void) const
{
	return zoom_allowed;
}

void
display_kernel::set_up( const vector& n_up)
{
  
	if (n_up == vector())
		throw std::invalid_argument( "Up cannot be zero.");
	vector v = n_up.norm();
	
	if (v.cross(internal_forward) == vector()) { // if internal_forward parallel to new up, move it away from new up
	  if (v.cross(forward) == vector()) {
	    // old internal_forward was not parallel to old up
	    if (stereo_mode == REAL_STEREO){
	      internal_forward = forward - 0.0001*up;
	    }else{
	      internal_forward = (forward - 0.0001*up).norm();
	    }
	  } else {
	    internal_forward = forward;
	  }
	}
	

	up = v;
}

shared_vector&
display_kernel::get_up()
{
	return up;
}

void
display_kernel::set_forward( const vector& n_forward)
{

  
	if (n_forward == vector())
		throw std::invalid_argument( "Forward cannot be zero.");

	vector v;
        if (stereo_mode == REAL_STEREO){
	  v = n_forward;
	}else{
	  v = n_forward.norm();
	}

	if (v.cross(up) == vector()) { // if new forward parallel to up, move internal_forward away from up
		// old internal_forward was not parallel to up
	  if (stereo_mode == REAL_STEREO){
	    internal_forward = ( v.dot(up)*up + 0.0001*up.cross(internal_forward.cross(up)) );
	  }else{
		internal_forward = ( v.dot(up)*up + 0.0001*up.cross(internal_forward.cross(up)) ).norm();
	  }
	} else { // since new forward not parallel to up, new forward is okay
		internal_forward = v;
	}
	forward = v;

	forward_changed = true;
}

shared_vector&
display_kernel::get_forward()
{
	return forward;
}

void
display_kernel::set_scale( const vector& n_scale)
{
	if (n_scale.x == 0.0 || n_scale.y == 0.0 || n_scale.z == 0.0)
		throw std::invalid_argument(
			"The scale of each axis must be non-zero.");

	vector n_range = vector( 1.0/n_scale.x, 1.0/n_scale.y, 1.0/n_scale.z);
	set_range( n_range );
}

vector
display_kernel::get_scale()
{
	if (autoscale || !range.nonzero())
		throw std::logic_error("Reading .scale and .range is not supported when autoscale is enabled.");
	return vector( 1.0/range.x, 1.0/range.y, 1.0/range.z );
}

void
display_kernel::set_center( const vector& n_center)
{
	center = n_center;
}

shared_vector&
display_kernel::get_center()
{
	return center;
}

void
display_kernel::set_fov( double n_fov)
{
	if (n_fov == 0.0)
		throw std::invalid_argument( "Orthogonal projection is not supported.");
	else if (n_fov < 0.0 || n_fov >= M_PI)
		throw std::invalid_argument(
			"attribute visual.display.fov must be between 0.0 and math.pi "
			"(exclusive)");
	fov = n_fov;
}

double
display_kernel::get_fov()
{
	return fov;
}

void
display_kernel::set_lod(int n_lod)
{
  if (n_lod > 0 || n_lod < -6 )
		throw std::invalid_argument(
		       "attribute visual.display.lod must be between -6 and 0");
  lod_adjust = n_lod;
}

int
display_kernel::get_lod()
{
	return lod_adjust;
}

void
display_kernel::set_uniform( bool n_uniform)
{
	uniform = n_uniform;
}

bool
display_kernel::is_uniform()
{
	return uniform;
}


void
display_kernel::set_background( const rgb& n_background)
{
	background = n_background;
}

rgb
display_kernel::get_background()
{
	return background;
}

void
display_kernel::set_foreground( const rgb& n_foreground)
{
	foreground = n_foreground;
}

rgb
display_kernel::get_foreground()
{
	return foreground;
}

void
display_kernel::set_autoscale( bool n_autoscale)
{
	if (!n_autoscale && autoscale) {
		// Autoscale is disabled, but range_auto remains
		//   set to the current autoscaled scene, until and unless
		//   range is set explicitly.
		recalc_extent();
		range = vector(0,0,0);
	}
	autoscale = n_autoscale;
}

bool
display_kernel::get_autoscale()
{
	return autoscale;
}

bool
display_kernel::get_autocenter()
{
	return autocenter;
}

void
display_kernel::set_autocenter( bool n_autocenter)
{
	autocenter = n_autocenter;
}

void
display_kernel::set_show_rendertime( bool show)
{
	show_rendertime = show;
}

bool
display_kernel::is_showing_rendertime()
{
	return show_rendertime;
}

void
display_kernel::set_ambient_f( float a)
{
	ambient = rgb( a, a, a);
}

void
display_kernel::set_ambient( const rgb& a)
{
	ambient = a;
}

rgb
display_kernel::get_ambient()
{
	return ambient;
}

void
display_kernel::set_range_d( double r)
{
	set_range( vector(r,r,r) );
}

void
display_kernel::set_range( const vector& n_range)
{
	if (n_range.x == 0.0 || n_range.y == 0.0 || n_range.z == 0.0)
		throw std::invalid_argument(
			"attribute visual.display.range may not be zero.");
	autoscale = false;
	range = n_range;
	range_auto = 0.0;
}

vector
display_kernel::get_range()
{
	if (autoscale || !range.nonzero())
		throw std::logic_error("Reading .scale and .range is not supported when autoscale is enabled.");
	return range;
}

float
display_kernel::get_stereodepth()
{
	return stereodepth;
}

void
display_kernel::set_stereodepth( float n_stereodepth)
{
	if (visible)
		throw std::runtime_error( "Cannot change parameters of an active window");
	else
		stereodepth = n_stereodepth;
}

void
display_kernel::set_stereomode( std::string mode)
{
	if (mode == "nostereo")
		stereo_mode = NO_STEREO;
	else if (mode == "active")
		stereo_mode = ACTIVE_STEREO;
	else if (mode == "passive")
		stereo_mode = PASSIVE_STEREO;
	else if (mode == "crosseyed")
		stereo_mode = CROSSEYED_STEREO;
	else if (mode == "redblue")
		stereo_mode = REDBLUE_STEREO;
	else if (mode == "redcyan")
		stereo_mode = REDCYAN_STEREO;
	else if (mode == "yellowblue")
		stereo_mode = YELLOWBLUE_STEREO;
	else if (mode == "greenmagenta")
		stereo_mode = GREENMAGENTA_STEREO;
	else if (mode == "real")
		stereo_mode = REAL_STEREO;
	else
		throw std::invalid_argument( "Unimplemented or invalid stereo mode");
}

std::string
display_kernel::get_stereomode()
{
	switch (stereo_mode) {
		case NO_STEREO:
			return "nostereo";
		case ACTIVE_STEREO:
			return "active";
		case PASSIVE_STEREO:
			return "passive";
		case CROSSEYED_STEREO:
			return "crosseyed";
		case REDBLUE_STEREO:
			return "redblue";
		case REDCYAN_STEREO:
			return "redcyan";
		case YELLOWBLUE_STEREO:
			return "yellowblue";
		case GREENMAGENTA_STEREO:
			return "greenmagenta";
		case REAL_STEREO:
			return "real";

		default:
			// Not strictly required, this just silences a warning about control
			// reaching the end of a non-void funciton.
			return "nostereo";
	}
}

std::vector<shared_ptr<renderable> >
display_kernel::get_objects() const
{
	std::vector<shared_ptr<renderable> > ret;
	ret.insert( ret.end(), layer_world.begin(), layer_world.end() );
	ret.insert( ret.end(), layer_world_transparent.begin(), layer_world_transparent.end() );

	// ret[i]->get_children appends the immediate children of ret[i] to ret.  Since
	//   ret.size() keeps increasing, we keep going until we have all the objects in the tree.
	for(size_t i=0; i<ret.size(); i++)
		ret[i]->get_children(ret);

	return ret;
}

std::string
display_kernel::info()
{
	if (!extensions)
		return std::string( "Renderer inactive.\n");
	else {
		std::string s;
		s += "OpenGL renderer active.\n  Vendor: "
		  + vendor
		  + "\n  Version: " + version
		  + "\n  Renderer: " + renderer
		  + "\n  Extensions: ";

		// this->extensions is a list of extensions
		std::ostringstream buffer;
		std::copy( extensions->begin(), extensions->end(),
			std::ostream_iterator<std::string>( buffer, "\n"));
		s += buffer.str();
		return s;
	}
}

void
display_kernel::set_x( float n_x)
{
	if (visible)
		throw std::runtime_error( "Cannot change parameters of an active window");
	else
		window_x = (int)n_x;
}
float
display_kernel::get_x()
{
	return (float)window_x;
}

void
display_kernel::set_y( float n_y)
{
	if (visible)
		throw std::runtime_error( "Cannot change parameters of an active window");
	else
		window_y = (int)n_y;
}
float
display_kernel::get_y()
{
	return (float)window_y;
}

void
display_kernel::set_width( float w)
{
	if (visible)
		throw std::runtime_error( "Cannot change parameters of an active window");
	else
		window_width = (int)w;
}
float
display_kernel::get_width()
{
	return (float)window_width;
}

void
display_kernel::set_height( float h)
{
	if (visible)
		throw std::runtime_error( "Cannot change parameters of an active window");
	else
		window_height = (int)h;
}
float
display_kernel::get_height()
{
	return (float)window_height;
}

void
display_kernel::set_visible( bool vis)
{
	if (!vis) explicitly_invisible = true;
	if (vis != visible) {
		visible = vis;
		set_display_visible( this, visible );
		activate( vis );

		// Wait for (in)activation to complete
		python::gil_release gil;
		VPYTHON_NOTE( "display_kernel::set_visible -- did gil_release gil");
		lock L( realize_lock );
		VPYTHON_NOTE( "display_kernel::set_visible -- executed lock");
		VPYTHON_NOTE( "display_kernel::set_visible -- next, enter while loop");
		while ( realized != vis ) {
			VPYTHON_NOTE( "display_kernel::set_visible - waiting1");
			realize_condition.wait( L );
			VPYTHON_NOTE( "display_kernel::set_visible - waiting2");
		}
		VPYTHON_NOTE( "display_kernel::set_visible - finished");
	}
}

bool
display_kernel::get_visible()
{
	return visible;
}

void
display_kernel::set_title( std::string n_title)
{
	if (visible)
		throw std::runtime_error( "Cannot change parameters of an active window");
	else
		title = n_title;
}
std::string
display_kernel::get_title()
{
	return title;
}

bool
display_kernel::is_fullscreen()
{
	return fullscreen;
}
void
display_kernel::set_fullscreen( bool fs)
{
	if (visible)
		throw std::runtime_error( "Cannot change parameters of an active window");
	else
		fullscreen = fs;
}

bool display_kernel::get_exit() { return exit; }
void display_kernel::set_exit(bool b) { exit = b; }

bool
display_kernel::is_showing_toolbar()
{
	return show_toolbar;
}

void
display_kernel::set_show_toolbar( bool fs)
{
	if (visible)
		throw std::runtime_error( "Cannot change parameters of an active window");
	show_toolbar = fs;
}

cursor_object*
display_kernel::get_cursor()
{
	implicit_activate();
	return &cursor;
}

mouse_t*
display_kernel::get_mouse()
{
	implicit_activate();
	return &mouse.get_mouse();
}

atomic_queue<std::string>*
display_kernel::get_kb()
{
	implicit_activate();
	return &keys;
}

void
display_kernel::set_selected( shared_ptr<display_kernel> d )
{
	selected = d;
}

shared_ptr<display_kernel>
display_kernel::get_selected()
{
	return selected;
}

bool
display_kernel::hasExtension( const std::string& ext ) {
	return extensions->find( ext ) != extensions->end();
}

display_kernel::EXTENSION_FUNCTION
display_kernel::getProcAddress( const char* x ) {
	if ( !strcmp(x, "display_kernel::getProcAddress" ) ) return notImplemented;
	return NULL;
}

int 
display_kernel::get_screenShotRaw(char *pixel_data,int w,int h){

  glReadBuffer(GL_FRONT);
  glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixel_data);
  return true;

}

void 
display_kernel::screendump(char *destFile, short W, short H) {
 FILE   *out = fopen(destFile, "wb");
 char   pixel_data[3*W*H];
 int nSize=W*H*3;
 int i=0;

  for (i=0;i<nSize;i++){
    pixel_data[i]=0;
  }
 
  glReadBuffer(GL_FRONT);
 glReadPixels(50, 50, W, H, GL_BGR, GL_UNSIGNED_BYTE, pixel_data);

  short  TGAhead[] = {0, 2, 0, 0, 0, 0, W, H, 24};
  //unsigned char TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0};
  //unsigned char header[6] = {W%256,W/256, H%256,H/256,24,0};
 
  //unsigned char temp;

 /*
    while (i < nSize)
    {
        temp = pixel_data[i];       //grab blue
        pixel_data[i] = pixel_data[i+2];//assign red to blue
        pixel_data[i+2] = temp;     //assign blue to red
        i += 3;     //skip to next blue byte
    }
 */

  //fwrite(TGAheader, sizeof(unsigned char), 12, out);
  //fwrite(header, sizeof(unsigned char), 6, out);
  //fwrite(pixel_data, sizeof(char), nSize, out);

    fwrite(&TGAhead, sizeof(TGAhead), 1, out);
    fwrite(pixel_data, 3*W*H, 1, out);
    fclose(out);
}

} // !namespace cvisual
display_kernel.hpp (text/x-c++hdr, 12.3 KB)
#ifndef VPYTHON_DISPLAY_KERNEL_HPP
#define VPYTHON_DISPLAY_KERNEL_HPP

// Copyright (c) 2000, 2001, 2002, 2003 by David Scherer and others.
// Copyright (c) 2003, 2004 by Jonathan Brandmeyer and others.
// See the file license.txt for complete license terms.
// See the file authors.txt for a complete list of contributors.

#include "renderable.hpp"
#include "util/vector.hpp"
#include "util/rgba.hpp"
#include "util/extent.hpp"
#include "util/timer.hpp"
#include "util/thread.hpp"
#include "util/gl_extensions.hpp"
#include "util/atomic_queue.hpp"
#include "mouse_manager.hpp"
#include "mouseobject.hpp"
#include <list>
#include <vector>
#include <set>
#include <string>

#include <boost/signals.hpp>
#include <boost/iterator/indirect_iterator.hpp>
#include <boost/tuple/tuple.hpp>

namespace cvisual {

using boost::indirect_iterator;

class cursor_object;

/** A class that manages all OpenGL aspects of a given scene.  This class
	requires platform-specific support from render_surface to manage an OpenGL
	rendering context and mouse and keyboard interaction.
*/
class display_kernel
{
 private: // Private data
 	shared_ptr<std::set<std::string> > extensions;
 	std::string renderer;
 	std::string version;
 	std::string vendor;
 	double last_time;
 	double render_time;
 	bool realized;

 	static shared_ptr<display_kernel> selected;

 	mutex realize_lock;
 	boost::condition realize_condition;

 private:
	timer render_timer;	// for timing the render pulse

	shared_vector center; ///< The observed center of the display, in world space.
	shared_vector forward; ///< The direction of the camera, in world space.
	shared_vector up; ///< The vertical orientation of the scene, in world space.
	vector internal_forward; ///< Do not permit internal_forward to be +up or -up
	vector range; ///< Explicitly specified scene.range, or (0,0,0)
	vector camera; //< World coordinates of camera location
	double range_auto;	//< Automatically determined camera z from autoscale

	/** True initally and whenever the camera direction changes.  Set to false
	 * after every render cycle.
	 */
	bool forward_changed;

	extent_data world_extent; ///< The extent of the current world.

	double fov; ///< The field of view, in radians
	float stereodepth; //< How far in or out of the screen the scene seems to be
	bool autoscale; ///< True if Visual should scale the camera's position automatically.
	/** True if Visual should automatically reposition the center of the scene. */
	bool autocenter;
	/** True if the autoscaler should compute uniform axes. */
	bool uniform;
	/** A scaling factor determined by middle mouse button scrolling. */
	double user_scale;

	/** The global scaling factor. It is used to ensure that objects with
	 large dimensions are rendered properly. See the .cpp file for details.
	*/
	double gcf;
	/** Vector version of the global scaling factor used when scene.uniform=0.
	 Affects just curve, points, faces, label, frame, and conversion of mouse coordinates.
	*/
	vector gcfvec;

	/** True if the gcf has changed since the last render cycle.  Set to false
	 * after every rendering cycle.
	 */
	bool gcf_changed;

	rgb ambient; ///< The ambient light color.
	/** Called at the beginning of a render cycle to establish lighting. */
	void enable_lights(view& scene);
	/** Called at the end of a render cycle to complete lighting. */
	void disable_lights();

	/** Whether or not we should display the speed of the renderer.
	 * Default: false.
	 */
	bool show_rendertime;
	rgb background; ///< The background color of the scene.
	rgb foreground; ///< The default color for objects to be rendered into the scene.

	// Whether or not the user is allowed to spin or zoom the display
	bool spin_allowed;
	bool zoom_allowed;

	/** Set up the OpenGL transforms from world space to view space. */
	void world_to_view_transform( view&, int whicheye = 0, bool forpick = false);
	/** Renders the scene for one eye.
		@param scene The dimensions of the scene, to be propogated to this
			display_kernel's children.
		@param eye Which eye is being rendered.  -1 for the left, 0 for the
			center, and 1 for the right.
		@param scene_geometry.anaglyph  True if using anaglyph stereo requiring color
			desaturation or grayscaling.
		@param scene_geometry.coloranaglyph  True if colors must be grayscaled, false if colors
			must be desaturated.
	*/
	bool draw( view&, int eye=0);

	/** Opaque objects to be rendered into world space. */
	std::list<shared_ptr<renderable> > layer_world;
	typedef indirect_iterator<std::list<shared_ptr<renderable> >::iterator> world_iterator;

	/** objects with a nonzero level of transparency that need to be depth sorted
		prior to rendering.
	*/
	std::vector<shared_ptr<renderable> > layer_world_transparent;
	typedef indirect_iterator<std::vector<shared_ptr<renderable> >::iterator> world_trans_iterator;

	// Computes the extent of the scene and takes action for autozoom and
	// autoscaling.
	void recalc_extent();

	// Compute the tangents of half the vertical and half the horizontal
	// true fields-of-view.
	void tan_hfov( double* x, double* y);

	void realize();
	void implicit_activate();

protected:
	// Mouse and keyboard objects
	cursor_object cursor;
	mouse_manager mouse;
	atomic_queue<std::string> keys;

	// The bounding rectangle of the window on the screen (or equivalent super-window
	// coordinate system), including all decorations.
	// If the window is invisible, window_x and/or window_y may be -1, meaning
	// that the window will be positioned automatically by the window system.
	int window_x, window_y, window_width, window_height;

	// The rectangle on the screen into which we can actually draw.
	// At present, these are undefined until the display is realized, and
	// they are not used in constructing the display (they are outputs of
	// that process)
	// This includes both viewports in a side-by-side stereo mode, whereas
	//   view::view_width does not.
	int view_width, view_height;

	bool exit; ///< True when Visual should shutdown on window close.
	bool visible; ///< scene.visible
	bool explicitly_invisible;  ///< true iff scene.visible has ever been set to 0 by the program, or by the user closing a window
	bool fullscreen; ///< True when the display is in fullscreen mode.
	bool show_toolbar; ///< True when toolbar is displayed (pan, etc).
	std::string title;

public: // Public Data.
	gl_extensions glext;

	enum mouse_mode_t { ZOOM_ROTATE, ZOOM_ROLL, PAN, FIXED } mouse_mode;
	enum mouse_button { NONE, LEFT, RIGHT, MIDDLE };
	enum stereo_mode_t { NO_STEREO, PASSIVE_STEREO, ACTIVE_STEREO, CROSSEYED_STEREO,
			     REDBLUE_STEREO, REDCYAN_STEREO, YELLOWBLUE_STEREO, GREENMAGENTA_STEREO,REAL_STEREO
	} stereo_mode;

	/** Older machines should set this to some number between -6 and 0.  All of
		the tesselated models choose a lower level of detail based on this value
		when it is less than 0.
	*/
	int lod_adjust;

	/** Add a normal renderable object to the list of objects to be rendered into
	 *  world space.
	 */
	void add_renderable( shared_ptr<renderable>);

	/**  Remove a renderable object from this display, regardless of which layer
	 *   it resides in.  */
	void remove_renderable( shared_ptr<renderable>);

 public: // Public functions
	// Compute the location of the camera based on the current geometry.
	vector calc_camera();

	display_kernel();
	virtual ~display_kernel();

	/** Renders the scene once.  The enveloping widget is resposible for calling
		 this function appropriately.
 		@return If false, something catastrophic has happened and the
 		application should probably exit.
	*/
	bool render_scene();

	/** Inform this object that the window has been closed (is no longer physically
	    visible)
	*/
	void report_closed();

	/** Called by mouse_manager to report mouse movement that should affect the camera.
		Report that the mouse moved with one mouse button down.
 		@param dx horizontal change in mouse position in pixels.
 		@param dy vertical change in mouse position in pixels.
	*/
	void report_camera_motion( int dx, int dy, mouse_button button);

	/** Report that the position and/or size of the window or drawing area widget has changed.
		Some platforms might not know about position changes; they can pass (x,y,new_width,new_height)

 		win_* give the window rectangle (see this->window_*)
 		v_* give the view rectangle (see this->view_*)
 		*/
	void report_window_resize( int win_x, int win_y, int win_w, int win_h );
	void report_view_resize( int v_w, int v_h );

	/** Determine which object (if any) was picked by the cursor.
 	    @param x the x-position of the mouse cursor, in pixels.
		@param y the y-position of the mouse cursor, in pixels.
		@param d_pixels the allowable variation in pixels to successfully score
			a hit.
		@return  the nearest selected object, the position that it was hit, and
			the position of the mouse cursor on the near clipping plane.
           retval.get<0>() may be NULL if nothing was hit, in which case the
           positions are undefined.
	*/
	boost::tuple<shared_ptr<renderable>, vector, vector>
	pick( int x, int y, float d_pixels = 2.0);

	/** Recenters the scene.  Call this function exactly once to move the visual
	 * center of the scene to the true center of the scene.  This will work
	 * regardless of the value of this->autocenter.
	 */
	void recenter();

	/** Rescales the scene.  Call this function exactly once to scale the scene
	 * such that it fits within the entire window.  This will work
	 * regardless of the value of this->autoscale.
	 */
	void rescale();

	/** Release GL resources.  Call this as many times as you like during the
	 * shutdown.  However, neither pick() nor render_scene() may be called on
	 * any display_kernel after gl_free() has been invoked.
	 */
	void gl_free();

	void allow_spin(bool);
	bool spin_is_allowed(void) const;

	void allow_zoom(bool);
	bool zoom_is_allowed(void) const;


	// Python properties
	void set_up( const vector& n_up);
	shared_vector& get_up();

	void set_forward( const vector& n_forward);
	shared_vector& get_forward();

	void set_scale( const vector& n_scale);
	vector get_scale();

	void set_center( const vector& n_center);
	shared_vector& get_center();

	void set_fov( double);
	double get_fov();
	void set_lod(int);
	int get_lod();

	void set_uniform( bool);
	bool is_uniform();

	void set_background( const rgb&);
	rgb get_background();

	void set_foreground( const rgb&);
	rgb get_foreground();

	void set_autoscale( bool);
	bool get_autoscale();

	void set_autocenter( bool);
	bool get_autocenter();

	void set_show_rendertime( bool);
	bool is_showing_rendertime();

	void set_range_d( double);
	void set_range( const vector&);
	vector get_range();

	void set_ambient_f( float);
	void set_ambient( const rgb&);
	rgb get_ambient();

	void set_stereodepth( float);
	float get_stereodepth();

	// The only mode that cannot be changed after initialization is active,
	// which will result in a gl_error exception when rendered.  The completing
	// display class will have to perform some filtering on this parameter.  This
	// properties setter will not change the mode if the new one is invalid.
	void set_stereomode( std::string mode);
	std::string get_stereomode();

	// A list of all objects rendered into this display_kernel.  Modifying it
	// does not propogate to the owning display_kernel.
	std::vector<shared_ptr<renderable> > get_objects() const;

	std::string info( void);

	void set_x( float x);
	float get_x();

	void set_y( float y);
	float get_y();

	void set_width( float w);
	float get_width();

	void set_height( float h);
	float get_height();

	void set_visible( bool v);
	bool get_visible();

	void set_title( std::string n_title);
	std::string get_title();

	bool is_fullscreen();
	void set_fullscreen( bool);

	bool get_exit();
	void set_exit(bool);

	bool is_showing_toolbar();
	void set_show_toolbar( bool);

	static bool enable_shaders;

	cursor_object* get_cursor();
	mouse_t* get_mouse();
	atomic_queue<std::string>* get_kb();

	static void set_selected( shared_ptr<display_kernel> );
	static shared_ptr<display_kernel> get_selected();

	static void waitWhileAnyDisplayVisible();

	bool hasExtension( const std::string& ext );

	typedef void (APIENTRYP EXTENSION_FUNCTION)();
	virtual EXTENSION_FUNCTION getProcAddress( const char* );

	virtual void activate( bool active ) = 0;
        int get_screenShotRaw(char *pixel_data,int w,int h);
        void screendump(char *destFile, short W, short h);


};

} // !namespace cvisual

#endif // !defined VPYTHON_DISPLAY_KERNEL_HPP
ui.py (application/octet-stream, 2.7 KB)
from . import cvisual
from . primitives import distant_light, local_light
from . import materials

# Code to provide special initialization for a display object, and overloaded
# properties.
class display( cvisual.display):
    def __init__( self, **keywords):
        cvisual.display.__init__(self)
        self.material = materials.diffuse
        print 'creando display',keywords
        # If visible is set before width (say), can get error "can't change window".
        # So deal with visible attribute separately.
        visible = None
        if 'stereo' in keywords:
            setattr(self,'stereo',keywords['stereo'])
        if 'visible' in keywords:
            visible = keywords['visible']
            del keywords['visible']
        keys = list(keywords.keys())
        keys.sort()
        for kw in keys:
            print 'set',kw,keywords[kw]
            setattr(self, kw, keywords[kw])
        if visible is not None: setattr(self, 'visible', visible)
        if 'ambient' not in keywords:
            self.ambient = (0.2,0.2,0.2)
        if 'lights' not in keywords:
            distant_light( direction=(0.22, 0.44, 0.88), color=(0.8,0.8,0.8), display=self )
            distant_light( direction=(-0.88, -0.22, -.44), color=(0.3,0.3,0.3), display=self )
        self.select()

    def screendump(self, imagefile, w, h ) :
        cvisual.display.screendump(self,imagefile,w,h)

    def select(self):
        cvisual.display.set_selected(self)
    ambient = property( cvisual.display._get_ambient, cvisual.display._set_ambient)
    range = property( cvisual.display._get_range, cvisual.display._set_range)

    def _return_objects(self):
        return tuple([ o for o in self._get_objects() if not isinstance(o, cvisual.light) ])
    objects = property( _return_objects, None, None)

    def _get_lights(self):
        # TODO: List comprehension used for Python 2.3 compatibility; replace with
        #   generator comprehension
        return tuple([ o for o in self._get_objects() if isinstance(o, cvisual.light) ])
    def _set_lights(self, n_lights):
        old_lights = self._get_lights()
        for lt in old_lights:
            lt.visible = False

        if (type(n_lights) is not list) and (type(n_lights) is not tuple):
            n_lights = [n_lights] # handles case of scene.lights = single light
        for lt in n_lights:
            if isinstance( lt, cvisual.light ):  #< TODO: should this be allowed?
                lt.display = self
                lt.visible = True
            else:
                lum = cvisual.vector(lt).mag
                distant_light( direction=cvisual.vector(lt).norm(),
                               color=(lum,lum,lum),
                               display=self )

    lights = property( _get_lights, _set_lights, None)