Index: /trunk/tests/planet_test/nv_planet_test.cc
===================================================================
--- /trunk/tests/planet_test/nv_planet_test.cc	(revision 324)
+++ /trunk/tests/planet_test/nv_planet_test.cc	(revision 324)
@@ -0,0 +1,236 @@
+#include <nv/gfx/keyframed_mesh.hh>
+#include <nv/gl/gl_device.hh>
+#include <nv/gfx/image.hh>
+#include <nv/gfx/debug_draw.hh>
+#include <nv/interface/context.hh>
+#include <nv/interface/window.hh>
+#include <nv/core/profiler.hh>
+#include <nv/core/logging.hh>
+#include <nv/core/logger.hh>
+#include <nv/core/math.hh>
+#include <nv/core/time.hh>
+#include <nv/core/string.hh>
+#include <glm/gtx/rotate_vector.hpp>
+
+class application
+{
+public:
+	application();
+	bool initialize();
+	bool run();
+	~application();
+protected:
+	nv::mesh_data* generate_box();
+	nv::mesh_data* generate_subdiv_sphere( int levels );
+
+	nv::device*      m_device;
+	nv::window*      m_window;
+	nv::context*     m_context;
+	nv::debug_data*  m_debug_data;
+	nv::texture      m_diffuse;
+	nv::program      m_program;
+	nv::mesh_data*   m_data;
+	nv::vertex_array m_va;
+
+	nv::clear_state   m_clear_state;
+	nv::render_state  m_render_state;
+	nv::scene_state   m_scene_state;
+};
+
+struct vertex
+{
+	nv::vec3 position;
+};
+
+nv::mesh_data* generate_subdiv_sphere( int levels )
+{
+
+
+	nv::mesh_raw_channel* vchannel = nv::mesh_raw_channel::create<vertex>( 8 );
+	nv::mesh_raw_channel* ichannel = nv::mesh_raw_channel::create_index( nv::USHORT, 6 * 6 );
+	vertex*     vtx = (vertex*)vchannel->data;
+	nv::uint16* idx = (nv::uint16*)ichannel->data;
+
+	nv::mesh_data* result = new nv::mesh_data;
+	result->add_channel( vchannel );
+	result->add_channel( ichannel );
+	return result;
+
+}
+
+nv::mesh_data* application::generate_box()
+{
+	nv::mesh_raw_channel* vchannel = nv::mesh_raw_channel::create<vertex>( 8 );
+	nv::mesh_raw_channel* ichannel = nv::mesh_raw_channel::create_index( nv::USHORT, 6 * 6 );
+	vertex*     vtx = (vertex*)vchannel->data;
+	nv::uint16* idx = (nv::uint16*)ichannel->data;
+	
+	vtx[0].position = nv::vec3( 1.0f, 1.0f, 1.0f );
+	vtx[1].position = nv::vec3( 1.0f, 1.0f,-1.0f );
+	vtx[2].position = nv::vec3(-1.0f, 1.0f,-1.0f );
+	vtx[3].position = nv::vec3(-1.0f, 1.0f, 1.0f );
+	vtx[4].position = nv::vec3( 1.0f,-1.0f, 1.0f );
+	vtx[5].position = nv::vec3( 1.0f,-1.0f,-1.0f );
+	vtx[6].position = nv::vec3(-1.0f,-1.0f,-1.0f );
+	vtx[7].position = nv::vec3(-1.0f,-1.0f, 1.0f );
+
+	auto square = [&]( nv::uint16 i, nv::uint16 a, nv::uint16 b, nv::uint16 c, nv::uint16 d ) 
+	{
+		idx[i+0] = a; 
+		idx[i+1] = b; 
+		idx[i+2] = c; 
+		
+		idx[i+3] = c; 
+		idx[i+4] = d; 
+		idx[i+5] = a; 
+	};
+
+	square( 0, 0, 1, 2, 3 );
+	square( 6, 1, 0, 4, 5 );
+	square(12, 2, 1, 5, 6 );
+	square(18, 3, 2, 6, 7 );
+	square(24, 0, 3, 7, 4 );
+	square(30, 7, 6, 5, 4 );
+
+	nv::mesh_data* result = new nv::mesh_data;
+	result->add_channel( vchannel );
+	result->add_channel( ichannel );
+	return result;
+}
+
+
+application::application()
+{
+	NV_PROFILE( "app_construct" );
+	m_device  = new nv::gl_device();
+	m_window  = m_device->create_window( 800, 600, false );
+	m_context = m_window->get_context();
+
+// 	nv::sampler sampler( nv::sampler::LINEAR, nv::sampler::REPEAT );
+// 	nv::image_data* data = m_device->create_image_data( "data/manc.png" );
+// 	m_diffuse  = m_device->create_texture( data, sampler );
+// 	delete data;
+
+	m_clear_state.buffers = nv::clear_state::COLOR_AND_DEPTH_BUFFER;
+	m_clear_state.color   = nv::vec4(0.2f,0.2f,0.2f,1.0f);
+	m_render_state.depth_test.enabled = true;
+	m_render_state.culling.enabled    = true;
+	m_render_state.blending.enabled   = false;
+	m_render_state.blending.src_rgb_factor   = nv::blending::SRC_ALPHA;
+	m_render_state.blending.dst_rgb_factor   = nv::blending::ONE_MINUS_SRC_ALPHA;
+	m_render_state.blending.src_alpha_factor = nv::blending::SRC_ALPHA;
+	m_render_state.blending.dst_alpha_factor = nv::blending::ONE_MINUS_SRC_ALPHA;
+
+	m_debug_data = new nv::debug_data( m_context );
+	m_debug_data->push_aabox( nv::vec3( 1.0f, 1.0f, 1.0f ), nv::vec3(-1.0f, -1.0f,-1.0f ), nv::vec3( 1.0f, 0.0f, 0.0f ) );
+	m_debug_data->update();
+}
+
+bool application::initialize()
+{
+	NV_PROFILE( "app_initialize" );
+	m_program = m_device->create_program( nv::slurp( "planet.vert" ), nv::slurp( "planet.frag" ) );
+	m_data = generate_box();
+	m_va   = m_context->create_vertex_array( m_data, nv::STATIC_DRAW );
+	return true;
+}
+
+bool application::run()
+{
+	nv::profiler::pointer()->log_report(); 
+	NV_PROFILE( "app_run" );
+	int keypress = 0;
+
+	nv::uint32 ticks   = m_device->get_ticks();
+	nv::uint32 last_ticks;
+	nv::fps_counter_class< nv::system_us_timer > fps_counter;
+
+	nv::uint16 count = 0;
+	while(!keypress) 
+	{
+		last_ticks = ticks;
+		ticks      = m_device->get_ticks();
+		nv::uint32 elapsed = ticks - last_ticks;
+		m_context->clear( m_clear_state );
+		glm::vec3 eye = glm::rotate( glm::vec3( 2.0f, 0.0f, 0.0f ), (ticks / 20.f), glm::vec3( 0.0,1.0,0.0 ) );
+//		eye = glm::vec3( 3.0f, 0.0f, 0.0f );
+		m_scene_state.set_model( nv::mat4(1.0f) );
+		m_scene_state.get_camera().set_lookat(eye, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0, 1.0, 0.0));
+		float ratio = (float)m_window->get_width() / (float)m_window->get_height();
+		m_scene_state.get_camera().set_perspective(60.0f, ratio, 0.1f, 1000.0f);
+
+//		m_context->bind( m_diffuse, nv::TEX_DIFFUSE );
+		m_device->set_opt_uniform( m_program, "center", glm::vec3(0.0,0.0,0.0) );
+		m_device->set_opt_uniform( m_program, "radius", 1.0f );
+		m_device->set_opt_uniform( m_program, "light_position", glm::vec3(120.0, 120.0, 0) );
+		m_device->set_opt_uniform( m_program, "light_diffuse",  glm::vec4(0.5,0.5,0.5,1.0) );
+		m_device->set_opt_uniform( m_program, "light_specular", glm::vec4(1.0,1.0,1.0,1.0) );
+		m_render_state.culling.order    = nv::culling::CW;
+		m_context->draw( nv::TRIANGLES, m_render_state, m_scene_state, m_program, m_va, 6*6 );
+		m_render_state.culling.order    = nv::culling::CCW;
+
+		m_context->draw( nv::LINES, m_render_state, m_scene_state, m_debug_data->get_program(), m_debug_data->get_vertex_array(), m_debug_data->get_count() );
+
+		m_window->swap_buffers();
+
+		nv::io_event event;
+		while(m_window->poll_event(event)) 
+		{      
+			switch (event.type) 
+			{
+			case nv::EV_QUIT:
+				keypress = 1;
+				break;
+			case nv::EV_KEY:
+				if (event.key.pressed)
+				{
+					switch (event.key.code) 
+					{
+					case nv::KEY_ESCAPE : keypress = 1; break;
+					case nv::KEY_F1 : nv::profiler::pointer()->log_report(); break;
+					default: break;
+					}
+				}
+				break;
+			default: break;
+			}
+		}
+
+		fps_counter.tick();
+		if ( count % 120 == 0 )
+		{
+			m_window->set_title( "Nova Engine - " + nv::to_string( (nv::uint32)fps_counter.fps() ) );
+		}
+		count++;
+	}
+	return true;
+}
+
+application::~application()
+{
+	m_context->release( m_va );
+	m_device->release( m_program );
+	m_device->release( m_diffuse );
+
+	delete m_window;
+	delete m_device;
+}
+
+
+int main(int, char* [])
+{
+	nv::logger log(nv::LOG_TRACE);
+	log.add_sink( new nv::log_file_sink("log.txt"), nv::LOG_TRACE );
+	log.add_sink( new nv::log_console_sink(), nv::LOG_TRACE );
+	
+	NV_LOG( nv::LOG_NOTICE, "Logging started" );
+	application app;
+	if ( app.initialize() )
+	{
+		app.run();
+	}
+	NV_LOG( nv::LOG_NOTICE, "Logging stopped" );
+
+	return 0;
+}
+
Index: /trunk/tests/planet_test/planet.frag
===================================================================
--- /trunk/tests/planet_test/planet.frag	(revision 324)
+++ /trunk/tests/planet_test/planet.frag	(revision 324)
@@ -0,0 +1,515 @@
+#version 120
+
+//uniform sampler2D nv_t_diffuse;
+uniform vec4 light_diffuse;
+uniform vec4 light_specular;
+
+//varying vec2 v_texcoord;
+//varying vec3 v_normal;
+//varying vec3 v_light_vector;
+//varying vec3 v_view_vector;
+varying vec3 v_world_pos;
+varying vec3 v_camera_loc;
+varying vec3 v_light_loc;
+uniform vec3 center;
+uniform float radius;
+uniform mat4 nv_m_mvp;
+
+vec3 mod289(vec3 x) {
+  return x - floor(x * (1.0 / 289.0)) * 289.0;
+}
+
+vec4 mod289(vec4 x) {
+  return x - floor(x * (1.0 / 289.0)) * 289.0;
+}
+
+vec4 permute(vec4 x) {
+     return mod289(((x*34.0)+1.0)*x);
+}
+
+vec4 taylorInvSqrt(vec4 r)
+{
+  return 1.79284291400159 - 0.85373472095314 * r;
+}
+
+#define jitter 1.0 // smaller jitter gives more regular pattern
+
+float snoise(vec3 v)
+  { 
+  const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
+  const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);
+
+// First corner
+  vec3 i  = floor(v + dot(v, C.yyy) );
+  vec3 x0 =   v - i + dot(i, C.xxx) ;
+
+// Other corners
+  vec3 g = step(x0.yzx, x0.xyz);
+  vec3 l = 1.0 - g;
+  vec3 i1 = min( g.xyz, l.zxy );
+  vec3 i2 = max( g.xyz, l.zxy );
+
+  //   x0 = x0 - 0.0 + 0.0 * C.xxx;
+  //   x1 = x0 - i1  + 1.0 * C.xxx;
+  //   x2 = x0 - i2  + 2.0 * C.xxx;
+  //   x3 = x0 - 1.0 + 3.0 * C.xxx;
+  vec3 x1 = x0 - i1 + C.xxx;
+  vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
+  vec3 x3 = x0 - D.yyy;      // -1.0+3.0*C.x = -0.5 = -D.y
+
+// Permutations
+  i = mod289(i); 
+  vec4 p = permute( permute( permute( 
+             i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
+           + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 
+           + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
+
+// Gradients: 7x7 points over a square, mapped onto an octahedron.
+// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
+  float n_ = 0.142857142857; // 1.0/7.0
+  vec3  ns = n_ * D.wyz - D.xzx;
+
+  vec4 j = p - 49.0 * floor(p * ns.z * ns.z);  //  mod(p,7*7)
+
+  vec4 x_ = floor(j * ns.z);
+  vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)
+
+  vec4 x = x_ *ns.x + ns.yyyy;
+  vec4 y = y_ *ns.x + ns.yyyy;
+  vec4 h = 1.0 - abs(x) - abs(y);
+
+  vec4 b0 = vec4( x.xy, y.xy );
+  vec4 b1 = vec4( x.zw, y.zw );
+
+  //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
+  //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
+  vec4 s0 = floor(b0)*2.0 + 1.0;
+  vec4 s1 = floor(b1)*2.0 + 1.0;
+  vec4 sh = -step(h, vec4(0.0));
+
+  vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
+  vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
+
+  vec3 p0 = vec3(a0.xy,h.x);
+  vec3 p1 = vec3(a0.zw,h.y);
+  vec3 p2 = vec3(a1.xy,h.z);
+  vec3 p3 = vec3(a1.zw,h.w);
+
+//Normalise gradients
+  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
+  p0 *= norm.x;
+  p1 *= norm.y;
+  p2 *= norm.z;
+  p3 *= norm.w;
+
+// Mix final noise value
+  vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
+  m = m * m;
+  return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 
+                                dot(p2,x2), dot(p3,x3) ) );
+}
+
+// Permutation polynomial: (34x^2 + x) mod 289
+vec3 permute(vec3 x) {
+  return mod((34.0 * x + 1.0) * x, 289.0);
+}
+
+#define jitter2x2x2 0.8 // smaller jitter gives less errors in F2
+
+vec2 cellular(vec3 P) {
+#define K 0.142857142857 // 1/7
+#define Ko 0.428571428571 // 1/2-K/2
+#define K2 0.020408163265306 // 1/(7*7)
+#define Kz 0.166666666667 // 1/6
+#define Kzo 0.416666666667 // 1/2-1/6*2
+
+	vec3 Pi = mod(floor(P), 289.0);
+ 	vec3 Pf = fract(P) - 0.5;
+
+	vec3 Pfx = Pf.x + vec3(1.0, 0.0, -1.0);
+	vec3 Pfy = Pf.y + vec3(1.0, 0.0, -1.0);
+	vec3 Pfz = Pf.z + vec3(1.0, 0.0, -1.0);
+
+	vec3 p = permute(Pi.x + vec3(-1.0, 0.0, 1.0));
+	vec3 p1 = permute(p + Pi.y - 1.0);
+	vec3 p2 = permute(p + Pi.y);
+	vec3 p3 = permute(p + Pi.y + 1.0);
+
+	vec3 p11 = permute(p1 + Pi.z - 1.0);
+	vec3 p12 = permute(p1 + Pi.z);
+	vec3 p13 = permute(p1 + Pi.z + 1.0);
+
+	vec3 p21 = permute(p2 + Pi.z - 1.0);
+	vec3 p22 = permute(p2 + Pi.z);
+	vec3 p23 = permute(p2 + Pi.z + 1.0);
+
+	vec3 p31 = permute(p3 + Pi.z - 1.0);
+	vec3 p32 = permute(p3 + Pi.z);
+	vec3 p33 = permute(p3 + Pi.z + 1.0);
+
+	vec3 ox11 = fract(p11*K) - Ko;
+	vec3 oy11 = mod(floor(p11*K), 7.0)*K - Ko;
+	vec3 oz11 = floor(p11*K2)*Kz - Kzo; // p11 < 289 guaranteed
+
+	vec3 ox12 = fract(p12*K) - Ko;
+	vec3 oy12 = mod(floor(p12*K), 7.0)*K - Ko;
+	vec3 oz12 = floor(p12*K2)*Kz - Kzo;
+
+	vec3 ox13 = fract(p13*K) - Ko;
+	vec3 oy13 = mod(floor(p13*K), 7.0)*K - Ko;
+	vec3 oz13 = floor(p13*K2)*Kz - Kzo;
+
+	vec3 ox21 = fract(p21*K) - Ko;
+	vec3 oy21 = mod(floor(p21*K), 7.0)*K - Ko;
+	vec3 oz21 = floor(p21*K2)*Kz - Kzo;
+
+	vec3 ox22 = fract(p22*K) - Ko;
+	vec3 oy22 = mod(floor(p22*K), 7.0)*K - Ko;
+	vec3 oz22 = floor(p22*K2)*Kz - Kzo;
+
+	vec3 ox23 = fract(p23*K) - Ko;
+	vec3 oy23 = mod(floor(p23*K), 7.0)*K - Ko;
+	vec3 oz23 = floor(p23*K2)*Kz - Kzo;
+
+	vec3 ox31 = fract(p31*K) - Ko;
+	vec3 oy31 = mod(floor(p31*K), 7.0)*K - Ko;
+	vec3 oz31 = floor(p31*K2)*Kz - Kzo;
+
+	vec3 ox32 = fract(p32*K) - Ko;
+	vec3 oy32 = mod(floor(p32*K), 7.0)*K - Ko;
+	vec3 oz32 = floor(p32*K2)*Kz - Kzo;
+
+	vec3 ox33 = fract(p33*K) - Ko;
+	vec3 oy33 = mod(floor(p33*K), 7.0)*K - Ko;
+	vec3 oz33 = floor(p33*K2)*Kz - Kzo;
+
+	vec3 dx11 = Pfx + jitter2x2x2*ox11;
+	vec3 dy11 = Pfy.x + jitter2x2x2*oy11;
+	vec3 dz11 = Pfz.x + jitter2x2x2*oz11;
+
+	vec3 dx12 = Pfx + jitter2x2x2*ox12;
+	vec3 dy12 = Pfy.x + jitter2x2x2*oy12;
+	vec3 dz12 = Pfz.y + jitter2x2x2*oz12;
+
+	vec3 dx13 = Pfx + jitter2x2x2*ox13;
+	vec3 dy13 = Pfy.x + jitter2x2x2*oy13;
+	vec3 dz13 = Pfz.z + jitter2x2x2*oz13;
+
+	vec3 dx21 = Pfx + jitter2x2x2*ox21;
+	vec3 dy21 = Pfy.y + jitter2x2x2*oy21;
+	vec3 dz21 = Pfz.x + jitter2x2x2*oz21;
+
+	vec3 dx22 = Pfx + jitter2x2x2*ox22;
+	vec3 dy22 = Pfy.y + jitter2x2x2*oy22;
+	vec3 dz22 = Pfz.y + jitter2x2x2*oz22;
+
+	vec3 dx23 = Pfx + jitter2x2x2*ox23;
+	vec3 dy23 = Pfy.y + jitter2x2x2*oy23;
+	vec3 dz23 = Pfz.z + jitter2x2x2*oz23;
+
+	vec3 dx31 = Pfx + jitter2x2x2*ox31;
+	vec3 dy31 = Pfy.z + jitter2x2x2*oy31;
+	vec3 dz31 = Pfz.x + jitter2x2x2*oz31;
+
+	vec3 dx32 = Pfx + jitter2x2x2*ox32;
+	vec3 dy32 = Pfy.z + jitter2x2x2*oy32;
+	vec3 dz32 = Pfz.y + jitter2x2x2*oz32;
+
+	vec3 dx33 = Pfx + jitter2x2x2*ox33;
+	vec3 dy33 = Pfy.z + jitter2x2x2*oy33;
+	vec3 dz33 = Pfz.z + jitter2x2x2*oz33;
+
+	vec3 d11 = dx11 * dx11 + dy11 * dy11 + dz11 * dz11;
+	vec3 d12 = dx12 * dx12 + dy12 * dy12 + dz12 * dz12;
+	vec3 d13 = dx13 * dx13 + dy13 * dy13 + dz13 * dz13;
+	vec3 d21 = dx21 * dx21 + dy21 * dy21 + dz21 * dz21;
+	vec3 d22 = dx22 * dx22 + dy22 * dy22 + dz22 * dz22;
+	vec3 d23 = dx23 * dx23 + dy23 * dy23 + dz23 * dz23;
+	vec3 d31 = dx31 * dx31 + dy31 * dy31 + dz31 * dz31;
+	vec3 d32 = dx32 * dx32 + dy32 * dy32 + dz32 * dz32;
+	vec3 d33 = dx33 * dx33 + dy33 * dy33 + dz33 * dz33;
+
+	// Sort out the two smallest distances (F1, F2)
+#if 0
+	// Cheat and sort out only F1
+	vec3 d1 = min(min(d11,d12), d13);
+	vec3 d2 = min(min(d21,d22), d23);
+	vec3 d3 = min(min(d31,d32), d33);
+	vec3 d = min(min(d1,d2), d3);
+	d.x = min(min(d.x,d.y),d.z);
+	return sqrt(d.xx); // F1 duplicated, no F2 computed
+#else
+	// Do it right and sort out both F1 and F2
+	vec3 d1a = min(d11, d12);
+	d12 = max(d11, d12);
+	d11 = min(d1a, d13); // Smallest now not in d12 or d13
+	d13 = max(d1a, d13);
+	d12 = min(d12, d13); // 2nd smallest now not in d13
+	vec3 d2a = min(d21, d22);
+	d22 = max(d21, d22);
+	d21 = min(d2a, d23); // Smallest now not in d22 or d23
+	d23 = max(d2a, d23);
+	d22 = min(d22, d23); // 2nd smallest now not in d23
+	vec3 d3a = min(d31, d32);
+	d32 = max(d31, d32);
+	d31 = min(d3a, d33); // Smallest now not in d32 or d33
+	d33 = max(d3a, d33);
+	d32 = min(d32, d33); // 2nd smallest now not in d33
+	vec3 da = min(d11, d21);
+	d21 = max(d11, d21);
+	d11 = min(da, d31); // Smallest now in d11
+	d31 = max(da, d31); // 2nd smallest now not in d31
+	d11.xy = (d11.x < d11.y) ? d11.xy : d11.yx;
+	d11.xz = (d11.x < d11.z) ? d11.xz : d11.zx; // d11.x now smallest
+	d12 = min(d12, d21); // 2nd smallest now not in d21
+	d12 = min(d12, d22); // nor in d22
+	d12 = min(d12, d31); // nor in d31
+	d12 = min(d12, d32); // nor in d32
+	d11.yz = min(d11.yz,d12.xy); // nor in d12.yz
+	d11.y = min(d11.y,d12.z); // Only two more to go
+	d11.y = min(d11.y,d11.z); // Done! (Phew!)
+	return sqrt(d11.xy); // F1, F2
+#endif
+}
+
+// Permutation polynomial: (34x^2 + x) mod 289
+vec4 permute4(vec4 x) {
+  return mod((34.0 * x + 1.0) * x, 289.0);
+}
+
+// Cellular noise, returning F1 and F2 in a vec2.
+// Speeded up by using 2x2x2 search window instead of 3x3x3,
+// at the expense of some pattern artifacts.
+// F2 is often wrong and has sharp discontinuities.
+// If you need a good F2, use the slower 3x3x3 version.
+vec2 cellular2x2x2(vec3 P) {
+#define K 0.142857142857 // 1/7
+#define Ko 0.428571428571 // 1/2-K/2
+#define K2 0.020408163265306 // 1/(7*7)
+#define Kz 0.166666666667 // 1/6
+#define Kzo 0.416666666667 // 1/2-1/6*2
+	vec3 Pi = mod(floor(P), 289.0);
+ 	vec3 Pf = fract(P);
+	vec4 Pfx = Pf.x + vec4(0.0, -1.0, 0.0, -1.0);
+	vec4 Pfy = Pf.y + vec4(0.0, 0.0, -1.0, -1.0);
+	vec4 p = permute4(Pi.x + vec4(0.0, 1.0, 0.0, 1.0));
+	p = permute4(p + Pi.y + vec4(0.0, 0.0, 1.0, 1.0));
+	vec4 p1 = permute4(p + Pi.z); // z+0
+	vec4 p2 = permute4(p + Pi.z + vec4(1.0)); // z+1
+	vec4 ox1 = fract(p1*K) - Ko;
+	vec4 oy1 = mod(floor(p1*K), 7.0)*K - Ko;
+	vec4 oz1 = floor(p1*K2)*Kz - Kzo; // p1 < 289 guaranteed
+	vec4 ox2 = fract(p2*K) - Ko;
+	vec4 oy2 = mod(floor(p2*K), 7.0)*K - Ko;
+	vec4 oz2 = floor(p2*K2)*Kz - Kzo;
+	vec4 dx1 = Pfx + jitter*ox1;
+	vec4 dy1 = Pfy + jitter*oy1;
+	vec4 dz1 = Pf.z + jitter*oz1;
+	vec4 dx2 = Pfx + jitter*ox2;
+	vec4 dy2 = Pfy + jitter*oy2;
+	vec4 dz2 = Pf.z - 1.0 + jitter*oz2;
+	vec4 d1 = dx1 * dx1 + dy1 * dy1 + dz1 * dz1; // z+0
+	vec4 d2 = dx2 * dx2 + dy2 * dy2 + dz2 * dz2; // z+1
+
+	// Sort out the two smallest distances (F1, F2)
+#if 0
+	// Cheat and sort out only F1
+	d1 = min(d1, d2);
+	d1.xy = min(d1.xy, d1.wz);
+	d1.x = min(d1.x, d1.y);
+	return sqrt(d1.xx);
+#else
+	// Do it right and sort out both F1 and F2
+	vec4 d = min(d1,d2); // F1 is now in d
+	d2 = max(d1,d2); // Make sure we keep all candidates for F2
+	d.xy = (d.x < d.y) ? d.xy : d.yx; // Swap smallest to d.x
+	d.xz = (d.x < d.z) ? d.xz : d.zx;
+	d.xw = (d.x < d.w) ? d.xw : d.wx; // F1 is now in d.x
+	d.yzw = min(d.yzw, d2.yzw); // F2 now not in d2.yzw
+	d.y = min(d.y, d.z); // nor in d.z
+	d.y = min(d.y, d.w); // nor in d.w
+	d.y = min(d.y, d2.x); // F2 is now in d.y
+	return sqrt(d.xy); // F1 and F2
+#endif
+}
+
+float fbm_map( vec3 position )
+{
+//	float frequency  = 1.0;
+//	float lacunarity = 2.0;
+//	float gain       = 0.5;
+	float fbm=snoise(1.0*position)
+ 		+ 0.5*snoise(2.0*position)
+ 		+ 0.25*snoise(4.0*position)
+ 		+ 0.125*snoise(8.0*position)
+ 		+ 0.0625*snoise(16.0*position)
+ 		;
+	return fbm;
+}
+
+float crater_map( vec3 position, float cutoff, float inset )
+{
+	vec2 value = cellular2x2x2( position ); // 0..1
+	float factor = smoothstep( 0.0, cutoff, value.x );
+	float rmaxabs = 1.0 / max( 1.0 - inset, inset );
+	return 1.0 - smoothstep( -0.05, 1.0, abs( factor - inset ) * rmaxabs ); 
+}
+
+float height_map( vec3 position )
+{
+	float crater1 = crater_map( 3*position, 0.5, 0.5 ); 
+	float crater2 = crater_map( 1.7*position, 0.5, 0.8 ); 
+	float crater  = 0.7 * crater2 + 0.3 * crater1;
+	float fbm     = ( fbm_map( 6*position ) + 1.0 ) / 2.0;
+	//fbm = 1.0;
+	return 1.5*crater*(0.15*fbm+0.85) + fbm * 0.2;
+}
+
+vec3 color_map( vec3 position, float h )
+{
+	float fbm = ( fbm_map( 2*position ) + 1.0 ) / 2.0;
+	//fbm = 1.0;
+	return (fbm*0.5 + 0.5)*vec3(0.9,0.9,1.0);
+}
+
+vec3 self_ilum_map( vec3 position, float h )
+{
+//	float test     = fbm_map( 8*position );
+//	float test = crater_map( 1.7*position, 0.5, 0.8 ); 
+//	if ( test < -1.0 )
+//		return vec3(1.0,0.0,0.0);
+//	if ( test > 1.2 )
+//		return vec3(0.0,1.0,0.0);
+	return vec3(0.0,0.0,0.0);
+}
+
+float specular_map( vec3 position, float h )
+{
+	return 0.2*h;
+}
+
+vec3 normal_map( vec3 position, vec3 normal, vec3 tangent, vec3 bitangent )
+{
+	float step  = 0.001;
+	float ystep = 0.1;
+	vec3 n = ystep * normal;
+	vec3 t = step * tangent;
+	vec3 b = step * bitangent;
+	vec3 prev_x = position - t;
+	vec3 prev_z = position - b;
+	vec3 next_x = position + t;
+	vec3 next_z = position + b;
+	float px = height_map( prev_x );
+	float nx = height_map( next_x );
+	float pz = height_map( prev_z );
+	float nz = height_map( next_z );
+	prev_x += px * n;
+	prev_z += pz * n;
+	next_x += nx * n;
+	next_z += nz * n;
+	vec3 nt = next_x - prev_x;
+	vec3 nb = next_z - prev_z;
+	return normalize( cross( nt, nb ) );
+}
+
+
+float world_depth( vec3 world_pos )
+{
+	vec4 v = nv_m_mvp * vec4( world_pos, 1.0 );
+	v.z /= v.w;
+	v.z = ( v.z + 1.0 ) * 0.5;
+	return v.z;
+}
+
+vec4 sphere_isect( vec3 origin, vec3 ray, vec3 center, float r2 )
+{
+	vec3 sd = center - origin;
+	float b = dot( ray, sd );
+	float disc = b*b + r2 - dot(sd,sd);
+	if ( disc > 0.0 )
+	{
+		float tnow = b - sqrt(disc);
+		return vec4( origin + tnow * ray, tnow );
+	}
+	return vec4(0,0,0,0);
+}
+
+// Minnaert limb darkening diffuse term
+float minnaert( vec3 L, vec3 Nf, float k) {
+	float ndotl = max( 0.0, dot(L, Nf));
+	return pow( ndotl, k);
+}
+
+// Ward isotropic specular term
+float wardiso( vec3 Nf, vec3 Ln, vec3 Hn, float roughness, float ndotv ) {
+	float ndoth = dot( Nf, Hn);
+	float ndotl = dot( Nf, Ln);
+	float tandelta = tan( acos(ndoth));
+	return exp( -( pow( tandelta, 2.0) / pow( roughness, 2.0)))
+		* (1.0 / sqrt( ndotl * ndotv ))
+		* (1.0 / (4.0 * pow( roughness, 2.0)));
+	}
+	
+float schlick( vec3 Nf, vec3 Vf, float ior, float ndotv ) {
+	float kr = (ior-1.0)/(ior+1.0);
+	kr *= kr;
+	return kr + (1.0-kr)*pow( 1.0 - ndotv, 5.0);
+}
+
+void main(void) {
+	vec3 view_vector  = normalize( v_world_pos - v_camera_loc );
+	vec4 inter = sphere_isect( v_camera_loc, view_vector, center, radius*radius );
+	//float height = height_map( inter.xyz );
+	//vec3 position = inter.xyz + height*0.1*inter.xyz;
+	vec3 position = inter.xyz;
+	float height = height_map( position );
+	vec3 light_vector = normalize( position - v_light_loc );
+	view_vector       = normalize( position - v_camera_loc );
+	vec3 normal       = normalize( position - center );
+	vec3 tangent      = normalize( cross( normal, vec3(0.0,1.0,0.0) ) );
+	vec3 bitangent    = cross( normal, tangent );
+
+	normal       = normal_map( position, normal, tangent, bitangent );
+	tangent      = normalize( cross( normal, vec3(0.0,1.0,0.0) ) );
+	bitangent    = cross( normal, tangent );
+
+	//vec3 diff_texel      = vec3( texture2D( nv_t_diffuse, v_texcoord ) );	
+	vec3 diff_texel       = color_map( position, height );
+	float specular_amount = specular_map( position, height );
+	vec3 self_ilum_color  = self_ilum_map( position, height );
+	vec3 ambient_color    = vec3 (0.1, 0.1, 0.1);
+	float diffuse_amount  = 1.0 - specular_amount;
+
+	vec3 reflect_vector     = reflect( light_vector, normal );
+	float dot_prod_specular = dot( reflect_vector, -view_vector );
+	float dot_prod_diffuse  = dot( -light_vector, normal );
+
+	float diffuse_factor  = max( dot_prod_diffuse, 0.0 );
+	float specular_factor = pow( max( dot_prod_specular, 0.0 ), 16.0 ); // 100.0
+
+	float final_diffuse  = diffuse_amount * diffuse_factor;
+	float final_specular = specular_amount * specular_factor;
+
+
+	vec3 N = normalize(normal);
+	vec3 V = normalize(view_vector);
+	vec3 L = normalize(-light_vector);
+	vec3 Vf = -V;
+	float ndotv = dot(N, Vf);
+	vec3 H = normalize(L+Vf);
+
+	final_diffuse  = minnaert( L, N, 1.5) * diffuse_factor;
+	//float fresnel  = schlick( N, V, 0.1, ndotv);
+	//final_specular = wardiso( N, L, H, 0.1, ndotv) * fresnel;
+
+	vec3 diffuse_color   = light_diffuse.xyz  * final_diffuse * diff_texel;
+	vec3 specular_color  = light_specular.xyz * final_specular * diff_texel;
+
+	if ( inter.w < 0.0 || inter.w > 0.0 )
+		gl_FragColor = vec4( max( self_ilum_color, diffuse_color + specular_color + ambient_color ), 1.0 );
+	else
+		discard;
+	gl_FragDepth = world_depth( inter.xyz );
+	//gl_FragColor = vec4( 0.0, 1.0, 0.5, 0.8 );
+
+}
Index: /trunk/tests/planet_test/planet.vert
===================================================================
--- /trunk/tests/planet_test/planet.vert	(revision 324)
+++ /trunk/tests/planet_test/planet.vert	(revision 324)
@@ -0,0 +1,32 @@
+#version 120
+
+attribute vec3 nv_position;
+
+//varying vec3 v_light_vector;
+//varying vec3 v_view_vector;
+//varying vec2 v_texcoord;
+varying vec3 v_world_pos;
+varying vec3 v_camera_loc;
+varying vec3 v_light_loc;
+
+uniform mat4 nv_m_mvp;
+uniform mat4 nv_m_model;
+uniform mat4 nv_m_model_inv;
+uniform vec3 nv_v_camera_position;
+uniform vec3 center;
+uniform vec3 light_position;
+
+void main(void)
+{
+	vec4 position   = vec4( nv_position, 1.0 );
+	//v_normal        = normalize( mix( nv_normal, nv_next_normal, nv_interpolate ) );
+	//v_texcoord      = nv_texcoord;
+	v_world_pos     = vec3( nv_m_model_inv * position );
+	gl_Position     = nv_m_mvp * position;
+
+	v_camera_loc    = vec3( nv_m_model_inv * vec4 (nv_v_camera_position, 1.0) );
+	v_light_loc     = vec3( nv_m_model_inv * vec4 (light_position, 1.0) );
+
+	//v_view_vector  = normalize( nv_position - camera_loc  );
+	//v_light_vector = normalize( nv_position - light_loc );
+}
Index: /trunk/tests/planet_test/planet_test.lua
===================================================================
--- /trunk/tests/planet_test/planet_test.lua	(revision 324)
+++ /trunk/tests/planet_test/planet_test.lua	(revision 324)
@@ -0,0 +1,8 @@
+project "nv_planet_test"
+	kind "ConsoleApp"
+	files { "nv_planet_test.cc" }
+	includedirs { "../../" }
+	targetname "nv_planet_test"
+	links { "nv-core", "nv-gl", "nv-formats" }
+	targetdir "../../bin"	
+  
Index: /trunk/tests/planet_test/premake4.lua
===================================================================
--- /trunk/tests/planet_test/premake4.lua	(revision 324)
+++ /trunk/tests/planet_test/premake4.lua	(revision 324)
@@ -0,0 +1,18 @@
+solution "nv_planet_test"
+	configurations { "debug", "release" }
+
+  	language "C++"
+	flags { "ExtraWarnings", "NoPCH" }
+
+	configuration "debug"
+		defines { "DEBUG" }
+		flags { "Symbols", "StaticRuntime" }
+		objdir ("../../".._ACTION.."/debug")
+
+	configuration "release"
+		defines { "NDEBUG" }
+		flags { "Optimize", "StaticRuntime" }
+		objdir ("../../".._ACTION.."/release")
+
+	dofile("planet_test.lua")
+	dofile("../../nv.lua")
