public void paint(final Graphics g)
	{
		final Graphics2D graphics = (Graphics2D) g;

		// clear canvas
		graphics.setColor(Color.LIGHT_GRAY);
		graphics.fillRect( 0 , 0, getWidth() , getHeight() );

		final List<Object3D> objects = world.getObjects();

		final long start = -System.currentTimeMillis();

		// ** rendering start **
		
		final Matrix viewProjectionMatrix = world.getProjectionMatrix().multiply(  world.getViewMatrix() );

		// latch used to wait until all objects have been calculated
		final CountDownLatch latch = new CountDownLatch( objects.size() );

		final AtomicLong renderingTime = new AtomicLong(0); // updated by rendering thread
		
		synchronized( batches ) {
			batches.clear();
		}
		
		for( final Object3D obj : objects ) 
		{
			renderObject(graphics, viewProjectionMatrix, latch, renderingTime, obj , ! obj.hasChildren() );
		}

		// wait until primitives for all visible objects have been queued
		while( true ) 
		{
			try {
				if ( latch.await(5,TimeUnit.SECONDS) ) 
				{
					break;
				} else {
					System.err.println("Rendering didn't return after 5 seconds?");
				}
			} catch(InterruptedException e) {
			}
		}
		
		// render objects using painter's algorithm
		
		final long renderStart = System.currentTimeMillis();
		
		synchronized( batches ) 
		{
			// sort objects ascending by distance to viewer...	
			Collections.sort( batches , new Comparator<PrimitiveBatch>() {
	
				@Override
				public int compare(PrimitiveBatch o1, PrimitiveBatch o2) 
				{
					final float d1 = o1.getDistanceToViewer();
					final float d2 = o2.getDistanceToViewer();
					if ( d1 > d2 ) {
						return -1; // farther away , render first
					} else if ( d1 < d2 ) {
						return 1;
					}
					return 0;
				}
			});
			
			for ( PrimitiveBatch batch : batches ) {
				batch.renderBatch( graphics );
			}
		}
		
		if ( RENDER_COORDINATE_SYSTEM ) {
			renderCoordinateSystem(graphics);
		}
		
		renderingTime.addAndGet( System.currentTimeMillis() - renderStart );
		
		// ** rendering end **

		final long totalTime = start + System.currentTimeMillis();

		//  Render statistics
		this.totalTime += totalTime;
		this.totalRenderingTime += renderingTime.get();

		frameCounter++;
		
		final float avgTotalTime = this.totalTime / frameCounter;
		final float fps = 1000.0f / avgTotalTime;
		
		final String fpsString = new DecimalFormat("###0.0#").format( fps );
		final String drawingTimeString = new DecimalFormat("##0.0#").format( 100.0*(this.totalRenderingTime / (float) this.totalTime));

		g.setColor( Color.WHITE );
		g.drawString( objects.size()+" objects in "+totalTime+" millis ( rendering time: "+drawingTimeString+"% , "+fpsString+" fps)" , 10 , 20 );
//		g.drawString( "Use A,D,W,S,Q,E to move , + and - minus to zoom , ESC to exit mouse look, ENTER to reset view position", 10 , 40 );
		g.drawString( "Eye position: "+world.getCamera().getEyePosition() , 10 , 40 );
		g.drawString( "Eye target: "+world.getCamera().getEyeTarget() , 10 , 60 );
		g.drawString( "View vector: "+world.getCamera().getViewOrientation() , 10 , 80 );		
	}