text:
#include <chrono>
#include <sstream>
#include <random>
#include <stor/document/document.h>
#include <stor/exceptions/document_exception.h>
#include <rapidjson/istreamwrapper.h>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>

namespace esft{

    namespace stor{

        document::document(const std::string &json, document::identifier _id):
                node{},_rjdoc{},_id{_id}
        {
            rapidjson::ParseResult p = _rjdoc.Parse(json.c_str());
            if (!p) throw document_exception{"failed parsing."};
            set_document(&_rjdoc);
            set_underlying(&_rjdoc);

        }

        document::document(const char *json):
                node{},_rjdoc{},_id{make_id()}
        {
            rapidjson::ParseResult p = _rjdoc.Parse(json);
            if (!p) throw document_exception{"failed parsing."};
            set_document(&_rjdoc);
            set_underlying(&_rjdoc);

        }

        document::document():_id{make_id()} {}

        document::document(document &&o):
        _id{std::move(o._id)}
        {
            _rjdoc = std::move(o._rjdoc);
            set_document(&_rjdoc);
            set_underlying(&_rjdoc);
        }

        document &document::operator=(document &&o)
        {
            _id = std::move(o._id);
            _rjdoc = std::move(o._rjdoc);
            set_document(&_rjdoc);
            set_underlying(&_rjdoc);

            o.set_document(nullptr);
            o.set_underlying(nullptr);
            return *this;
        }

        document document::as_object(){
            document d;
            d._rjdoc = rapidjson::Document(rapidjson::kObjectType);
            d.set_document(&d._rjdoc);
            d.set_underlying(&d._rjdoc);
            return d;
        }

        document document::as_array(){
            document d;
            d._rjdoc = rapidjson::Document(rapidjson::kArrayType);
            d.set_document(&d._rjdoc);
            d.set_underlying(&d._rjdoc);
            return d;
        }

        const document::identifier &document::id() const {
            return _id;
        }

        document::identifier document::make_id()
        {
            using namespace std::chrono;
            //node_key key = get_key(&_rjdoc);

            std::ostringstream oss;
//
//            auto duration = system_clock::now().time_since_epoch();
//            auto micro = duration_cast<milliseconds>(duration).count();
//
//            oss << micro;

            boost::uuids::random_generator gen;
            boost::uuids::uuid u = gen();
            oss << u;

            return oss.str();
        }

        std::istream &operator>>(std::istream &is, document &d)
        {
            rapidjson::IStreamWrapper isw{is};
            rapidjson::ParseResult p = d._rjdoc.ParseStream(isw);
            if (!p) {
                is.setstate(std::ios::badbit);
                throw document_exception{"failed parsing."};
            }
            d.set_document(&d._rjdoc);
            d.set_underlying(&d._rjdoc);

            return is;
        }

    }

}

max_stars_repo_name: ethronsoft/stor

max_issues_repo_name: ethronsoft/stor

max_forks_repo_name: ethronsoft/stor


text:
/*
Copyright (c) 2012-2015, Kai Hugo Hustoft Endresen <kai.endresen@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "ximeacapture.h"
#include <boost/thread/thread.hpp>
#include <boost/thread/barrier.hpp>
#include <functional>
#include <chrono>
#include <sepia/comm/dispatcher.h>
#include <sepia/writer.h>
#include <string>
#include <usbreset.h>

using namespace cuttlefish_msgs;

namespace
{
    std::string errorString( int errorcode )
    {
        static std::map< int, std::string > sm_map;

        if( sm_map.empty() )
        {
            sm_map[ XI_OK ] = "OK"; // 0
            sm_map[ XI_WRITEREG ] = "Register Write Error"; // 3
            sm_map[ XI_TIMEOUT  ] = "Timeout"; // 10
            sm_map[ XI_ISOCH_ATTACH_BUFFERS ] = "Attach buffers error"; // 13
            sm_map[ XI_ACQUISITION_STOPED ] = "Acquisition Stopped"; // 45
            sm_map[ XI_NO_DEVICES_FOUND   ] = "No Devices Found"; // 56
        }
        std::map< int, std::string>::iterator it = sm_map.find( errorcode );
        if( it != sm_map.end() )
        {
            return it->second;
        }
        else
        {
            return std::string( "unknown" );
        }
    }

    void printError( int errorcode )
    {
        std::cout << errorString( errorcode ) << " (" << errorcode << ")" << std::endl;
    }
}

XimeaCapture::XimeaCapture( const int devices )
{
    // Reset
    UsbReset r;

    // add XIMEA Vendor:Product IDs
    r.addId( 0x04B4, 0x00F0 );
    r.addId( 0x04B4, 0x00F1 );
    r.addId( 0x04B4, 0x00F2 );
    r.addId( 0x04B4, 0x00F3 );
    r.addId( 0x04B4, 0x8613 );

    r.addId( 0x20F7, 0x3000 );
    r.addId( 0x20F7, 0x3001 );
    r.addId( 0x20F7, 0xA003 );

    r.addId( 0xDEDA, 0xA003 );
    r.reset();

    m_terminate = false;

    m_writer = new sepia::Writer( "/XI_IMG", devices, 1280, 1024, 32 ); // reserve for 4 channels.
    for( int i = 0; i < devices; i++ )
    {
        m_writer->setSize( i, 1280, 1024, 8 );
    }

    m_handles.assign( devices, NULL );
    m_acquisition.assign( devices, false );
    m_trigger.assign( devices, false );
    m_barrier = new boost::barrier( devices );
    m_triggerBarrier = new boost::barrier( devices );
    xiSetParamInt( 0, XI_PRM_DEBUG_LEVEL, XI_DL_WARNING );
    xiSetParamInt( 0, XI_PRM_AUTO_BANDWIDTH_CALCULATION, XI_OFF );
    sepia::comm::Observer< cuttlefish_msgs::XimeaControl >::initReceiver();
    sepia::comm::Observer< cuttlefish_msgs::XimeaSet >::initReceiver();
    sepia::comm::Observer< cuttlefish_msgs::XimeaGet >::initReceiver();
}

XimeaCapture::~XimeaCapture()
{
    sepia::comm::Observer< cuttlefish_msgs::XimeaControl >::destroyReceiver();
    sepia::comm::Observer< cuttlefish_msgs::XimeaSet >::destroyReceiver();
    sepia::comm::Observer< cuttlefish_msgs::XimeaGet >::destroyReceiver();
    stop();
    join();
}

void XimeaCapture::start()
{
   for( unsigned int i = 0; i < m_handles.size(); i++ )
   {
      m_threads.push_back( new boost::thread( boost::bind( &XimeaCapture::acquisition_thread, this, i ) ) );
   }
}

void XimeaCapture::stop()
{
    m_terminate = true;
}

void XimeaCapture::join()
{
    for( unsigned int i = 0; i < m_threads.size(); i++ )
    {
        m_threads[i]->join();
    }
}

void XimeaCapture::receive( const XimeaControl* msg )
{
   const int camera_no = msg->camera_no();
   int retval = 0;
   int temp = 1;
   switch( msg->command() )
   {
      case XimeaControl_CommandType_OPEN:
         if( camera_no == 1 )
         {
             temp = 0;
         }
         retval = xiOpenDevice( temp, &m_handles.at( camera_no ) );
         if( m_handles.at( camera_no ) == NULL )
         {
             std::cerr << "OPEN FAILED.";
             stop();
         }
         break;
      case XimeaControl_CommandType_CLOSE:
         retval = xiCloseDevice( m_handles.at( camera_no ) );
         break;
      case XimeaControl_CommandType_START:
         retval = xiStartAcquisition( m_handles.at( camera_no ) );
         if( retval == 0 )
         {
            m_acquisition.at( camera_no ) = true;
         }
         else
         {
             xiStopAcquisition( m_handles.at( camera_no ) );
         }
         break;
      case XimeaControl_CommandType_STOP:
         m_acquisition.at( camera_no ) = false;
         retval = xiStopAcquisition( m_handles.at( camera_no ) );
         break;
      case XimeaControl_CommandType_TERMINATE:
         std::cout << "GOT TERMINATION COMMAND." << std::endl;
         stop();
         break;
      case XimeaControl_CommandType_TRIGGER_START:
         m_trigger.at( camera_no ) = true;
         break;
      case XimeaControl_CommandType_TRIGGER_STOP:
         m_trigger.at( camera_no ) = false;
         break;
   }
   printError( retval );
}

void XimeaCapture::receive( const cuttlefish_msgs::XimeaSet* msg )
{
    const int camera_no = msg->camera_no();

    int retval = 0;

    if( msg->has_int_value() )
    {
        retval = xiSetParamInt( m_handles.at( camera_no ), msg->parameter().c_str(), msg->int_value() );
    }
    else if( msg->has_float_value() )
    {
        retval = xiSetParamFloat( m_handles.at( camera_no ), msg->parameter().c_str(), msg->float_value() );
    }
    printError( retval );
}

void XimeaCapture::receive( const cuttlefish_msgs::XimeaGet* msg )
{
    const int camera_no = msg->camera_no();
    cuttlefish_msgs::XimeaReply sMsg;
    sMsg.mutable_request()->set_camera_no( msg->camera_no() );
    sMsg.mutable_request()->set_parameter( msg->parameter() );
    sMsg.mutable_request()->set_type( msg->type() );

    int32_t int_value = 0;
    float float_value = 0;

    switch( msg->type() )
    {
        case XimeaGet_Type_INT:
           xiGetParamInt( m_handles.at( camera_no ), msg->parameter().c_str(), &int_value );
           sMsg.set_int_value( int_value );
           sepia::comm::Dispatcher<  cuttlefish_msgs::XimeaReply >::send( &sMsg );
           break;
        case XimeaGet_Type_FLOAT:
           xiGetParamFloat( m_handles.at( camera_no ), msg->parameter().c_str(), &float_value );
           sMsg.set_float_value( float_value );
           sepia::comm::Dispatcher<  cuttlefish_msgs::XimeaReply >::send( &sMsg );
           break;
    }
}

void XimeaCapture::acquisition_thread( const int camera_no )
{
    XI_IMG xi_image;

    xi_image.size = sizeof(XI_IMG);
    xi_image.bp = NULL;
    xi_image.bp_size = 0;

    unsigned int frame_no = 0;

    m_barrier->wait();
    auto start = std::chrono::steady_clock::now();

    while( !m_terminate )
    {
        if( m_acquisition.at( camera_no ) )
        {
           sepia::Stream::image_header_t* hdr = m_writer->getHeader( camera_no );
           frame_no++;
           m_barrier->wait();
           xi_image.bp = m_writer->getAddress( camera_no );
           xi_image.bp_size = hdr->size;

           int retval = xiGetImage( m_handles.at( camera_no ), 1000, &xi_image );

           if( retval != 0 )
           {
               printError( retval );
               m_barrier->wait();
               continue;
           }

           hdr->width = xi_image.width;
           hdr->height = xi_image.height;
           hdr->frame_number = xi_image.nframe;
           hdr->tv_sec = xi_image.tsSec;
           hdr->tv_usec = xi_image.tsUSec;
           hdr->fourcc = 0x00000000; // RAW
           hdr->bpp = 8;
           hdr->channels = 1;
           hdr->size = xi_image.bp_size;
           hdr->gpi_level = xi_image.GPI_level;
           hdr->black_level = xi_image.black_level;

           m_barrier->wait();

           if( camera_no == 0 )
           {
               std::cerr << "." << std::flush;
               m_writer->update();
               if( frame_no % 50 == 0 )
               {
                  auto end = std::chrono::steady_clock::now();
                  auto elapsed = end - start;
                  std::cerr << " Avg FPS: " << 50.0 * std::chrono::steady_clock::period().den / (double) elapsed.count() << std::endl;
                  start = end;
               }
           }
        }
        else
        {
           frame_no = 0;
           m_barrier->wait();
           usleep( 500000 ); // wait 500 ms before retrying.
        }
    }
}

bool XimeaCapture::isTerminated()
{
    return m_terminate;
}

ext: cpp

max_stars_repo_name: SorcererX/SepiaCore

max_issues_repo_name: SorcererX/SepiaCore

max_forks_repo_name: SorcererX/SepiaCore


text:
#include <fstream>
#include <sys/stat.h>
#ifdef _WIN32
	#include <direct.h>
#endif

#include "SDL.h"
#ifdef EMSCRIPTEN
#   include <GLES2/gl2.h>
#   include "emscripten.h"
#else
#   if defined(_WIN32) || defined(__linux__)
#      include <glad/glad.h>
#      include "gl2/src/glad.c"
#   else
#      define GL_GLEXT_PROTOTYPES
#   endif
#endif

#include <math.h>
#include <Eigen/Dense>
using namespace Eigen;
#include "rocktree_util.h"

#include <SDL_opengl.h>

SDL_Window* sdl_window;

static rocktree_t *_planetoid = NULL;

void loadPlanet() {
	auto planetoid = new rocktree_t();
	planetoid->downloaded = false;
	_planetoid = planetoid;

	getPlanetoid([=](std::unique_ptr<PlanetoidMetadata> _metadata) {
		if (!_metadata) fprintf(stderr, "%s", "no planetoid\n"), abort();
		populatePlanetoid(planetoid, std::move(_metadata));

		auto bulk = planetoid->root_bulk;
		assert(bulk->dl_state == dl_state_stub);
		bulk->setStartedDownloading();
		getBulk(bulk->request, bulk, [=](auto _) { /* todo rm cb */ });
	});
}

void initGL(gl_ctx_t &ctx) {
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
	ctx.program = makeShader(
		"uniform mat4 transform;"
		"uniform vec2 uv_offset;"
		"uniform vec2 uv_scale;"
		"uniform bool octant_mask[8];"
		"attribute vec3 position;"
		"attribute float octant;"
		"attribute vec2 texcoords;"
		"varying vec2 v_texcoords;"
		"void main() {"
		"	float mask = octant_mask[int(octant)] ? 0.0 : 1.0;"
		"	v_texcoords = (texcoords + uv_offset) * uv_scale * mask;"
		"	gl_Position = transform * vec4(position, 1.0) * mask;"
		"}",

		"#ifdef GL_ES\n"
		"precision mediump float;\n"
		"#endif\n"
		"uniform sampler2D texture;"
		"varying vec2 v_texcoords;"
		"void main() {"
		"	gl_FragColor = vec4(texture2D(texture, v_texcoords).rgb, 1.0);"
		"}"
	);
	glUseProgram(ctx.program);
	ctx.transform_loc = glGetUniformLocation(ctx.program, "transform");
	ctx.uv_offset_loc = glGetUniformLocation(ctx.program, "uv_offset");
	ctx.uv_scale_loc = glGetUniformLocation(ctx.program, "uv_scale");
	ctx.octant_mask_loc = glGetUniformLocation(ctx.program, "octant_mask");
	ctx.texture_loc = glGetUniformLocation(ctx.program, "texture");
	ctx.position_loc = glGetAttribLocation(ctx.program, "position");
	ctx.octant_loc = glGetAttribLocation(ctx.program, "octant");
	ctx.texcoords_loc = glGetAttribLocation(ctx.program, "texcoords");

	glEnableVertexAttribArray(ctx.position_loc);
	glEnableVertexAttribArray(ctx.octant_loc);
	glEnableVertexAttribArray(ctx.texcoords_loc);
}

Uint64 NOW = SDL_GetPerformanceCounter();
Uint64 LAST = 0;
double deltaTime = 0;

void drawPlanet(gl_ctx_t &ctx) {
	auto planetoid = _planetoid;
	if (!planetoid) return;
	if (!planetoid->downloaded) return;
	if (planetoid->root_bulk->dl_state != dl_state_downloaded) return;
	auto current_bulk = planetoid->root_bulk;
	auto planet_radius = planetoid->radius;

	Matrix4d projection, viewprojection;

	int width, height;
	SDL_GL_GetDrawableSize(sdl_window, &width, &height);
	glViewport(0, 0, width, height);
	auto sky = 0x83b5fc;
	glClearColor((sky>>16 & 0xff) / 255.0f, (sky>>8 & 0xff) / 255.0f, (sky & 0xff) / 255.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	auto mouse_state = SDL_GetMouseState(NULL, NULL);
	auto state = SDL_GetKeyboardState(NULL);
	auto key_up_pressed = state[SDL_SCANCODE_W];
	auto key_left_pressed = state[SDL_SCANCODE_A];
	auto key_down_pressed = state[SDL_SCANCODE_S];
	auto key_right_pressed = state[SDL_SCANCODE_D];
	auto key_boost_pressed = state[SDL_SCANCODE_LSHIFT] || state[SDL_SCANCODE_RSHIFT];
	auto mouse_pressed = mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT);

	// from lat/lon
	//static Vector3d ecef = { ...https://www.oc.nps.edu/oc2902w/coord/llhxyz.htm };
	//static auto ecef_norm = ecef.normalized();
	//static Vector3d eye = (ecef_norm * (planet_radius + 10000));

	// nyc
	static Vector3d eye = { 1329866.230289, -4643494.267515, 4154677.131562 };
	static Vector3d direction = { 0.219862, 0.419329, 0.312226 };

	// print position every 2 seconds
	{
		static double ms = 0;
		ms += deltaTime;
		if (ms > 2000) {
			ms = 0;
			printf("pos: %f %f %f, dir: %f %f %f\n", eye.x(), eye.y(), eye.z(), direction.x(), direction.y(), direction.z());
		}
	}

	// up is the vec from the planetoid's center towards the sky
	auto up = eye.normalized();

	// projection
	float aspect_ratio = (float)width / (float)height;
	float fov = 0.25f * (float)M_PI;
	auto altitude = eye.norm() - planet_radius;
	auto horizon = sqrt( altitude * (2*planet_radius + altitude) );
	auto near = horizon > 370000 ? altitude / 2 : 50;
	auto far = horizon;
	if (near >= far) near = far - 1;
	if (isnan(far) || far < near) far = near + 1;
	projection = perspective(fov, aspect_ratio, near, far);

	// rotation
	int mouse_x, mouse_y;
	SDL_GetRelativeMouseState(&mouse_x, &mouse_y);
	double yaw = mouse_x * 0.001;
	double pitch = -mouse_y * 0.001;
	auto overhead = direction.dot(-up);
	if ((overhead > 0.99 && pitch < 0) || (overhead < -0.99 && pitch > 0))
		pitch = 0;
	auto pitch_axis = direction.cross(up);
	auto yaw_axis = direction.cross(pitch_axis);
	pitch_axis.normalize();
	AngleAxisd roll_angle(0, Vector3d::UnitZ());
	AngleAxisd yaw_angle(yaw, yaw_axis);
	AngleAxisd pitch_angle(pitch, pitch_axis);
	auto quat = roll_angle * yaw_angle * pitch_angle;
	auto rotation = quat.matrix();
	direction = (rotation * direction).normalized();

	// movement
	auto speed_amp = fmin(2600, powf(fmax(0, (altitude - 500)/10000)+1, 1.337)) / 6;
	auto mag = 100*(deltaTime/17.0)*(1+key_boost_pressed*4) * speed_amp;
	auto sideways = direction.cross(up).normalized();
	auto forwards = direction * mag;
	auto backwards = -direction * mag;
	auto left = -sideways * mag;
	auto right = sideways * mag;
	auto new_eye =  eye + key_up_pressed * forwards
	                    + key_down_pressed * backwards
	                    + key_left_pressed * left
	                    + key_right_pressed * right;
	auto pot_altitude = new_eye.norm() - planet_radius;
	if (pot_altitude < 1000 * 1000 * 10) {
		eye = new_eye;
	}

	auto view = lookAt(eye, eye + direction, up);
	viewprojection = projection * view;

	auto frustum_planes = getFrustumPlanes(viewprojection); // for obb culling

	const std::string octs[] = { "0", "1", "2", "3", "4", "5", "6", "7" };
	std::vector<std::pair<std::string, rocktree_t::bulk_t *>> valid = { std::make_pair("", current_bulk) };
	decltype(valid) next_valid;
	std::map<std::string, rocktree_t::node_t *> potential_nodes;
	//std::multimap<double, rocktree_t::node_t *> dist_nodes;

	// todo: improve download order
	// todo: abort emscripten_fetch_close() https://emscripten.org/docs/api_reference/fetch.html
	//       and/or emscripten coroutine fetch semaphore
	// todo: purge branches less aggressively
	// todo: workers instead of shared mem https://emscripten.org/docs/api_reference/emscripten.h.html#worker-api

	std::map<std::string, rocktree_t::bulk_t *> potential_bulks;

	// node culling and level of detail using breadth-first search
	for (;;) {
		for(auto cur2 : valid) {
			auto cur = cur2.first;
			auto bulk = cur2.second;

			if (cur.size() > 0 && cur.size() % 4 == 0) {
				auto rel = cur.substr (floor((cur.size() - 1) / 4) * 4, 4);
				auto bulk_kv = bulk->bulks.find(rel);
				auto has_bulk = bulk_kv != bulk->bulks.end();
				if (!has_bulk) continue;
				auto b = bulk_kv->second.get();
				potential_bulks[cur] = b;
				if (b->dl_state == dl_state_stub) {
					b->setStartedDownloading();
					getBulk(b->request, b, [=](auto) {});
				}
				if (b->dl_state != dl_state_downloaded) continue;
				bulk = b;
			}
			potential_bulks[cur] = bulk;

			for(auto o : octs) {
				auto nxt = cur + o;
				auto nxt_rel = nxt.substr (floor((nxt.size() - 1) / 4) * 4, 4);
				auto node_kv = bulk->nodes.find(nxt_rel);
				if (node_kv == bulk->nodes.end()) // node at "nxt" doesn't exist
					continue;
				auto node = node_kv->second.get();

				// cull outside frustum using obb
				// todo: check if it could cull more
				if (obb_frustum_outside == classifyObbFrustum(&node->obb, frustum_planes)) {
					continue;
				}

				// level of detail
				/*{
					auto obb_center = node->obb.center;
					auto obb_max_diameter = fmax(fmax(node->obb.extents[0], node->obb.extents[1]), node->obb.extents[2]);

					auto t = Affine3d().Identity();
					t.translate(Vector3d(obb_center.x(), obb_center.y(), obb_center.z()));
					t.scale(obb_max_diameter);
					Matrix4d viewprojection_d;
					for(auto i = 0; i < 16; i++) viewprojection_d.data()[i] = viewprojection.data()[i];
					auto m = viewprojection_d * t;
					auto s = m(3, 3);
					if (s < 0) s = -s; // ?
					auto diameter_in_clipspace = 2 * (obb_max_diameter / s);  // *2 because clip space is -1 to +1
					auto amplify = 4; // todo: meters per texel
					if (diameter_in_clipspace < 0.5 / amplify) {
						continue;
					}
				}*/

				{
					auto t = Affine3d().Identity();
					t.translate(eye + (eye-node->obb.center).norm() * direction);
					auto m = viewprojection * t;
					auto s = m(3, 3);
					auto texels_per_meter = 1.0f / node->meters_per_texel;
					auto wh = 768; // width < height ? width : height;
					auto r = (2.0*(1.0/s)) * wh;
					if (texels_per_meter > r) continue;
				}

				next_valid.push_back(std::make_pair(nxt, bulk));

				if (node->can_have_data) {
					potential_nodes[nxt] = node;
					//auto d = (node->obb.center - eye).squaredNorm();
					//dist_nodes[d] = node;
					//dist_nodes.insert(std::make_pair (d, node));
				}
			}
		}
		if (next_valid.size() == 0) break;
		valid = next_valid;
		next_valid.clear();
	}

	for (auto kv = potential_nodes.begin(); kv != potential_nodes.end(); ++kv) { // normal order
	//for (auto kv = potential_nodes.rbegin(); kv != potential_nodes.rend(); ++kv) { // reverse order
	//for (auto kv = dist_nodes.rbegin(); kv != dist_nodes.rend(); ++kv) { // reverse order
	//for (auto kv = dist_nodes.begin(); kv != dist_nodes.end(); ++kv) { // normal order
		auto node = kv->second;
		if (node->dl_state == dl_state_stub) {
			node->setStartedDownloading();
			getNode(node->request, node, [node](auto) {});
		}
	}

	// unbuffer and obsolete nodes
	std::vector<rocktree_t::bulk_t*> x = {current_bulk};
	auto buf_cnt = 0, obs_n_cnt = 0, total_n = 0;
	while(!x.empty()) {
		auto cur_bulk = x[0]; x.erase(x.begin());
		// prepare next iteration
		for (auto &kv : cur_bulk->bulks) {
			auto b = kv.second.get();
			if (b->dl_state != dl_state_downloaded) continue;
			x.emplace(x.begin(), b);
		}
		// current iteration
		for (auto &kv : cur_bulk->nodes) {
			auto n = kv.second.get();
			if (n->dl_state != dl_state_downloaded) continue;

			// just count buffers
			for (auto &m : n->meshes) { if (m.buffered) { buf_cnt++; break;}}

			total_n++;
			auto p = n->request.node_key().path();
			auto has = potential_nodes.find(p) != potential_nodes.end();
			if (!has) {
				// node is obsolete
				obs_n_cnt++;

				// unbuffer
				for (auto &mesh : n->meshes) {
					if (mesh.buffered) unbufferMesh(mesh);
				}
				// clean up
				n->_data = nullptr;
				n->matrix_globe_from_mesh = Matrix4d::Zero();
				n->meshes.clear();
				n->setDeleted();
			}
		}
	}

	// post order dfs purge obsolete bulks
	auto total_b = 0, obs_b_cnt = 0;
	std::function<void(rocktree_t::bulk_t *)> po;
	po = [&po, &potential_bulks, &obs_b_cnt, &total_b](rocktree_t::bulk_t * b){
		for (auto &kv : b->bulks){
			auto b = kv.second.get();
			if (b->dl_state == dl_state_downloaded)
				po(b);
		}
		total_b++;
		auto p = b->request.node_key().path();
		auto has = potential_bulks.find(p) != potential_bulks.end();
		if (!has) {
			if (b->busy_ctr == 0) {
				b->nodes.clear();
				b->bulks.clear();
				b->setDeleted();
			}
		}
	};

	po(current_bulk);

	// log stuff about buffers
	{
		static double ms = 0;
		ms += deltaTime;
		if (ms > 2000) {
			ms = 0;
			printf("buffered: %d, tot_n: %d, tot_b: %d, pot_n: %lu, pot_b: %lu, obs n: %d, obs b: %d\n",
				buf_cnt, total_n, total_b, potential_nodes.size(), potential_bulks.size(), obs_n_cnt, obs_b_cnt
			);
		}
	}

	// 8-bit octant mask flags of nodes
	std::map<std::string, uint8_t> mask_map;

	for (auto kv = potential_nodes.rbegin(); kv != potential_nodes.rend(); ++kv) { // reverse order
		auto full_path = kv->first;
		auto node = kv->second;
		auto level = strlen(full_path.c_str());
		assert(level > 0);
		assert(node->can_have_data);
		if (node->dl_state != dl_state_downloaded) continue;

		// set octant mask of previous node
		auto octant = (int)(full_path[level - 1] - '0');
		auto prev = full_path.substr (0, level - 1);
		mask_map[prev] |= 1 << octant;

		// skip if node is masked completely
		if (mask_map[full_path] == 0xff) continue;

		// float transform matrix
		Matrix4d transform = viewprojection * node->matrix_globe_from_mesh;
		Matrix4f transform_float;
		for(auto i = 0; i < 16; ++i) transform_float.data()[i] = (float)(transform.data()[i]);

		// buffer, bind, draw
		glUniformMatrix4fv(ctx.transform_loc, 1, GL_FALSE, transform_float.data());
		for (auto &mesh : node->meshes) {
			if (!mesh.buffered) bufferMesh(mesh);
			bindAndDrawMesh(mesh, mask_map[full_path], ctx);
		}
		//bufs[full_path] = node;
	}
}

bool quit = false;
void mainloop(gl_ctx_t &ctx) {
	SDL_Event sdl_event;
	while (SDL_PollEvent(&sdl_event)) {
		switch (sdl_event.type) {
			case SDL_QUIT:
				quit = true;
				break;
			case SDL_KEYDOWN:
				if (sdl_event.key.keysym.sym == SDLK_ESCAPE) quit = true;
				break;
		}
	}

	LAST = NOW;
	NOW = SDL_GetPerformanceCounter();
	deltaTime = (double)((NOW - LAST)*1000 / (double)SDL_GetPerformanceFrequency());

	drawPlanet(ctx);
	SDL_GL_SwapWindow(sdl_window);
}

int main(int argc, char* argv[]) {

	int video_width = 1024;
	int video_height = 768;

	if (SDL_Init(SDL_INIT_VIDEO) != 0) {
		fprintf(stderr, "Couldn't init SDL2: %s\n", SDL_GetError());
		exit(1);
	}
#ifdef EMSCRIPTEN
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
		SDL_GL_CONTEXT_PROFILE_ES);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#else
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
		SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
#endif
	sdl_window = SDL_CreateWindow("Earth Client",
		SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
		video_width, video_height,
		SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
	if (!sdl_window) {
		fprintf(stderr, "Couldn't create window: %s\n", SDL_GetError());
		exit(1);
	}
	SDL_GLContext gl_context = SDL_GL_CreateContext(sdl_window);
	if (!gl_context) {
		fprintf(stderr, "Couldn't create OpenGL context: %s\n", SDL_GetError());
		exit(1);
	}
	SDL_GL_SetSwapInterval(1);

#if defined(_WIN32) || defined(__linux__)
	// init glad
	if (!gladLoadGL()) {
		fprintf(stderr, "Failed to init glad\n");
		exit(1);
	}
#endif

	auto ctx = new gl_ctx_t();

	initGL(*ctx);
	loadPlanet();

	SDL_SetRelativeMouseMode(SDL_TRUE);

#ifdef EMSCRIPTEN
	emscripten_set_main_loop_arg([](void* _ctx){
		auto ctx = (gl_ctx_t *)_ctx;
		mainloop(*ctx);
	}, (void *)ctx, 0, 1);
#else
	while (!quit) mainloop(*ctx);
#endif

	SDL_GL_DeleteContext(gl_context);
	SDL_DestroyWindow(sdl_window);
	SDL_Quit();
	delete ctx;

	return 0;
}