diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 1ee1544dda..6aae7b82ed 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -8,6 +8,7 @@ set(PORTAUDIO_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/portaudio) project(interface) + if (APPLE) set(GL_HEADERS "#include \n#include ") else (APPLE) @@ -27,6 +28,9 @@ configure_file(InterfaceConfig.h.in ${PROJECT_BINARY_DIR}/includes/InterfaceConf # grab the implementation and header files from src dir file(GLOB INTERFACE_SRCS src/*.cpp src/*.h) +# project subdirectories +add_subdirectory(src/starfield) + if (APPLE) # set how the icon shows up in the Info.plist file SET(MACOSX_BUNDLE_ICON_FILE interface.icns) @@ -54,6 +58,7 @@ find_package(LodePNG REQUIRED) # include headers for external libraries and InterfaceConfig. include_directories( + ${PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/includes ${GLM_INCLUDE_DIRS} ${LODEPNG_INCLUDE_DIRS} @@ -88,7 +93,7 @@ if (WIN32) ) else (WIN32) target_link_libraries(interface ${LODEPNG_LIBRARY}) - + # include PortAudio as external project include(ExternalProject) set(PORTAUDIO_PROJ_DIR external/portaudio) @@ -133,4 +138,4 @@ endif (WIN32) INSTALL(TARGETS interface BUNDLE DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/install COMPONENT Runtime RUNTIME DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/install COMPONENT Runtime -) \ No newline at end of file +) diff --git a/interface/resources/gen_stars.py b/interface/resources/gen_stars.py new file mode 100644 index 0000000000..d18ec28b86 --- /dev/null +++ b/interface/resources/gen_stars.py @@ -0,0 +1,72 @@ +# +# gen_stars.py +# interface +# +# Created by Tobias Schwinger on 3/22/13. +# Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +# + +# Input file generator for the starfield. + +from random import random,randint +from math import sqrt, hypot, atan2, pi, fmod, degrees +from sys import argv,stderr + +hemisphere_only, equator, meridians= False, 1000, 1000 + +n_random = 100000 +if len(argv) > 1: + n_random = int(argv[1]) + +bars_total, bars_prev = 77, 0 + +def meridian(azimuth,n,(r0,g0,b0),(r1,g1,b1)): + alts = 180.0/n + for alti in range(n): + # color + altj = n-alti-1 + r = (r0 *altj+alti* r1)/n + g = (g0 *altj+alti* g1)/n + b = (b0 *altj+alti* b1)/n + # position + altitude = alts*alti + print "%f %f #%02x%02x%02x" % (azimuth,altitude,r,g,b) + print "%f %f #%02x%02x%02x" % (azimuth,-altitude,r,g,b) + +if meridians: + meridian( 0,meridians,(255,255,255), (180, 60,255)) # N->S + meridian(90,meridians,( 80,255, 80), (255,240, 40)) # E->W + +if equator: + azis = 360.0/equator + for azii in range(equator): + azimuth = azis*azii + print "%f %f #%02x%02x%02x" % (azimuth,0,255,255,255) + +for i in range(n_random): + # color + w = randint(30,randint(40,255)) + r = max(0,min(255,w + randint(-10,70))) + g = max(0,min(255,w + randint(-20,60))) + b = max(0,min(255,w + randint(-10,100))) + # position + x,y,z = random()*2-1,random(),random()*2-1 + if not hemisphere_only: + y = y*2-1 + l = sqrt(x*x + y*y + z*z) + x /= l; y /= l; z /= l + xz = hypot(x,z) + + azimuth = degrees(fmod(atan2(x,z)+pi,2*pi)) + altitude = degrees(atan2(y,xz)) + + bars = round(bars_total*i/n_random) + if bars != bars_prev: + bars_prev = bars + bars = int(bars) + stderr.write('\r[%s%s]' % ('#' * bars, '-' * (bars_total-bars))) + + print "%f %f #%02x%02x%02x" % (azimuth,altitude,r,g,b) + +stderr.write('\r[%s]\n' % ('#' * bars_total,)) + diff --git a/interface/src/FieldOfView.cpp b/interface/src/FieldOfView.cpp new file mode 100644 index 0000000000..d1f535bb39 --- /dev/null +++ b/interface/src/FieldOfView.cpp @@ -0,0 +1,128 @@ +// +// FieldOfView.cpp +// interface +// +// Created by Tobias Schwinger on 3/21/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include "FieldOfView.h" + +#include +#include +#include +#include + +using namespace glm; + +FieldOfView::FieldOfView() + : mat_orientation(mat4(1.0f)), + vec_bounds_low(vec3(-1.0f,-1.0f,-1.0f)), + vec_bounds_high(vec3(1.0f,1.0f,1.0f)), + val_width(256.0f), + val_height(256.0f), + val_angle(0.61), + val_zoom(1.0f), + enm_aspect_balancing(expose_less) +{ +} + +mat4 FieldOfView::getViewerScreenXform() const +{ + mat4 projection; + vec3 low, high; + getFrustum(low, high); + + // perspective projection? determine correct near distance + if (val_angle != 0.0f) + { + projection = translate( + frustum(low.x, high.x, low.y, high.y, low.z, high.z), + vec3(0.f, 0.f, -low.z) ); + } + else + { + projection = ortho(low.x, high.x, low.y, high.y, low.z, high.z); + } + + return projection; +} + +mat4 FieldOfView::getWorldViewerXform() const +{ + return translate(affineInverse(mat_orientation), + vec3(0.0f, 0.0f, -vec_bounds_high.z) ); +} + +mat4 FieldOfView::getWorldScreenXform() const +{ + return translate( + getViewerScreenXform() * affineInverse(mat_orientation), + vec3(0.0f, 0.0f, -vec_bounds_high.z) ); +} + +mat4 FieldOfView::getViewerWorldXform() const +{ + vec3 n_translate = vec3(0.0f, 0.0f, vec_bounds_high.z); + + return translate( + translate(mat4(1.0f), n_translate) + * mat_orientation, -n_translate ); +} + +float FieldOfView::getPixelSize() const +{ + vec3 low, high; + getFrustum(low, high); + + return std::min( + abs(high.x - low.x) / val_width, + abs(high.y - low.y) / val_height); +} + +void FieldOfView::getFrustum(vec3& low, vec3& high) const +{ + low = vec_bounds_low; + high = vec_bounds_high; + + // start with uniform zoom + float inv_zoom = 1.0f / val_zoom; + float adj_x = inv_zoom, adj_y = inv_zoom; + + // balance aspect + if (enm_aspect_balancing != stretch) + { + float f_aspect = (high.x - low.x) / (high.y - low.y); + float vp_aspect = val_width / val_height; + + if ((enm_aspect_balancing == expose_more) + != (f_aspect > vp_aspect)) + { + // expose_more -> f_aspect <= vp_aspect <=> adj >= 1 + // expose_less -> f_aspect > vp_aspect <=> adj < 1 + adj_x = vp_aspect / f_aspect; + } + else + { + // expose_more -> f_aspect > vp_aspect <=> adj > 1 + // expose_less -> f_aspect <= vp_aspect <=> adj <= 1 + adj_y = f_aspect / vp_aspect; + } + } + + // scale according to zoom / aspect correction + float ax = (low.x + high.x) / 2.0f, ay = (low.y + high.y) / 2.0f; + low.x = (low.x - ax) * adj_x + ax; + high.x = (high.x - ax) * adj_x + ax; + low.y = (low.y - ay) * adj_y + ay; + high.y = (high.y - ay) * adj_y + ay; + low.z = (low.z - high.z) * inv_zoom + high.z; + + // calc and apply near distance based on near diagonal and perspective + float w = high.x - low.x, h = high.y - low.y; + high.z -= low.z; + low.z = val_angle == 0.0f ? 0.0f : + sqrt(w*w+h*h) * 0.5f / tan(val_angle * 0.5f); + high.z += low.z; +} + diff --git a/interface/src/FieldOfView.h b/interface/src/FieldOfView.h new file mode 100644 index 0000000000..1bfa7c7f33 --- /dev/null +++ b/interface/src/FieldOfView.h @@ -0,0 +1,128 @@ +// +// FieldOfView.h +// interface +// +// Created by Tobias Schwinger on 3/21/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__FieldOfView__ +#define __interface__FieldOfView__ + +#include + +/** + * Viewing parameter encapsulation. + */ +class FieldOfView +{ + glm::mat4 mat_orientation; + glm::vec3 vec_bounds_low; + glm::vec3 vec_bounds_high; + float val_width; + float val_height; + float val_angle; + float val_zoom; + int enm_aspect_balancing; + public: + + FieldOfView(); + + // mutators + + FieldOfView& setBounds(glm::vec3 const& low, glm::vec3 const& high) + { vec_bounds_low = low; vec_bounds_high = high; return *this; } + + FieldOfView& setOrientation(glm::mat4 const& matrix) + { mat_orientation = matrix; return *this; } + + FieldOfView& setPerspective(float angle) + { val_angle = angle; return *this; } + + FieldOfView& setResolution(unsigned width, unsigned height) + { val_width = width; val_height = height; return *this; } + + FieldOfView& setZoom(float factor) + { val_zoom = factor; return *this; } + + enum aspect_balancing + { + expose_more, + expose_less, + stretch + }; + + FieldOfView& setAspectBalancing(aspect_balancing v) + { enm_aspect_balancing = v; return *this; } + + // dumb accessors + + glm::mat4 const& getOrientation() const { return mat_orientation; } + float getWidthInPixels() const { return val_width; } + float getHeightInPixels() const { return val_height; } + float getPerspective() const { return val_angle; } + + // matrices + + /** + * Returns a full transformation matrix to project world coordinates + * onto the screen. + */ + glm::mat4 getWorldScreenXform() const; + + /** + * Transforms world coordinates to viewer-relative coordinates. + * + * This matrix can be used as the modelview matrix in legacy GL code + * where the projection matrix is kept separately. + */ + glm::mat4 getWorldViewerXform() const; + + /** + * Returns the transformation to of viewer-relative coordinates back + * to world space. + * + * This matrix can be used to set up a coordinate system for avatar + * rendering. + */ + glm::mat4 getViewerWorldXform() const; + + /** + * Returns the transformation of viewer-relative coordinates to the + * screen. + * + * This matrix can be used as the projection matrix in legacy GL code. + */ + glm::mat4 getViewerScreenXform() const; + + + // other useful information + + /** + * Returns the size of a pixel in world space, that is the minimum + * in respect to x/y screen directions. + */ + float getPixelSize() const; + + /** + * Returns the frustum as used for the projection matrices. + * The result depdends on the bounds, eventually aspect correction + * for the current resolution, the perspective angle (specified in + * respect to diagonal) and zoom. + */ + void getFrustum(glm::vec3& low, glm::vec3& high) const; + + /** + * Returns the z-offset from the origin to where orientation ia + * applied. + */ + float getTransformOffset() const { return vec_bounds_high.z; } + + /** + * Returns the aspect ratio. + */ + float getAspectRatio() const { return val_height / val_width; } +}; + +#endif + diff --git a/interface/src/OGlProgram.h b/interface/src/OGlProgram.h new file mode 100644 index 0000000000..ffd8c33252 --- /dev/null +++ b/interface/src/OGlProgram.h @@ -0,0 +1,130 @@ +#ifndef __interface__OpenGlSupport__ +#define __interface__OpenGlSupport__ + +#include "InterfaceConfig.h" + +/** + * Macro to log OpenGl errors to stderr. + * Example: oglLog( glPushMatrix() ); + */ +#define oGlLog(stmt) \ + stmt; \ + { \ + GLenum e = glGetError(); \ + if (e != GL_NO_ERROR) { \ + fprintf(stderr, __FILE__ ":" oGlLog_stringize(__LINE__) \ + " [OpenGL] %s\n", gluErrorString(e)); \ + } \ + } \ + (void) 0 + +#define oGlLog_stringize(x) oGlLog_stringize_i(x) +#define oGlLog_stringize_i(x) # x + +/** + * Encapsulation of the otherwise lengthy call sequence to compile + * and link shading pipelines. + */ +class OGlProgram { + + GLuint _hndProg; + +public: + + OGlProgram() : _hndProg(0) { } + + ~OGlProgram() { if (_hndProg != 0u) { glDeleteProgram(_hndProg); } } + + // no copy/assign + OGlProgram(OGlProgram const&); // = delete; + OGlProgram& operator=(OGlProgram const&); // = delete; + +#if 0 // let's keep this commented, for now (C++11) + + OGlProgram(OGlProgram&& disposable) : _hndProg(disposable._hndProg) { + + disposable._hndProg = 0; + } + + OGlProgram& operator=(OGlProgram&& disposable) { + + GLuint tmp = _hndProg; + _hndProg = disposable._hndProg; + disposable._hndProg = tmp; + } +#endif + + /** + * Activates the executable for rendering. + * Shaders must be added and linked before this will work. + */ + void activate() const { + + if (_hndProg != 0u) + oGlLog( glUseProgram(_hndProg) ); + } + + /** + * Adds a shader to the program. + */ + bool addShader(GLenum type, GLchar const* cString) { + + addShader(type, 1, & cString); + } + + /** + * Adds a shader to the program and logs to stderr. + */ + bool addShader(GLenum type, GLsizei nStrings, GLchar const** strings) { + + if (! _hndProg) { _hndProg = glCreateProgram(); } + + GLuint s = glCreateShader(type); + glShaderSource(s, nStrings, strings, 0l); + glCompileShader(s); + GLint status; + glGetShaderiv(s, GL_COMPILE_STATUS, & status); + if (!! status) + glAttachShader(_hndProg, s); +#ifdef NDEBUG + else +#endif + fetchLog(s, glGetShaderiv, glGetShaderInfoLog); + glDeleteShader(s); + return !! status; + } + + /** + * Links the program and logs to stderr. + */ + bool link() { + + if (! _hndProg) { return false; } + + glLinkProgram(_hndProg); + GLint status; + glGetProgramiv(_hndProg, GL_LINK_STATUS, & status); +#ifdef NDEBUG + if (status == 0) +#endif + fetchLog(_hndProg, glGetProgramiv, glGetProgramInfoLog); + + return status != 0; + } + +private: + + template< typename ParamFunc, typename GetLogFunc > + void fetchLog(GLint handle, ParamFunc getParam, GetLogFunc getLog) { + GLint logLength = 0; + getParam(handle, GL_INFO_LOG_LENGTH, &logLength); + if (!! logLength) { + GLchar* message = new GLchar[logLength]; + getLog(handle, logLength, 0l, message); + fprintf(stderr, "%s\n", message); + delete[] message; + } + } +}; + +#endif diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp new file mode 100644 index 0000000000..95a0af3654 --- /dev/null +++ b/interface/src/Stars.cpp @@ -0,0 +1,42 @@ +// +// Stars.cpp +// interface +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include "InterfaceConfig.h" +#include "FieldOfView.h" +#include "Stars.h" + +#define __interface__Starfield_impl__ +#include "starfield/Controller.h" +#undef __interface__Starfield_impl__ + +Stars::Stars() : + _ptrController(0l) { + _ptrController = new starfield::Controller; +} + +Stars::~Stars() { + delete _ptrController; +} + +bool Stars::readInput(const char* url, unsigned limit) { + return _ptrController->readInput(url, limit); +} + +bool Stars::setResolution(unsigned k) { + return _ptrController->setResolution(k); +} + +float Stars::changeLOD(float fraction, float overalloc, float realloc) { + return float(_ptrController->changeLOD(fraction, overalloc, realloc)); +} + +void Stars::render(FieldOfView const& fov) { + _ptrController->render(fov.getPerspective(), fov.getAspectRatio(), fov.getOrientation()); +} + + diff --git a/interface/src/Stars.h b/interface/src/Stars.h new file mode 100644 index 0000000000..90c52e64ef --- /dev/null +++ b/interface/src/Stars.h @@ -0,0 +1,80 @@ +// +// Stars.h +// interface +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__Stars__ +#define __interface__Stars__ + +#include "FieldOfView.h" + +namespace starfield { class Controller; } + +/** + * Starfield rendering component. + */ +class Stars { + + starfield::Controller* _ptrController; + + public: + + Stars(); + ~Stars(); + + /** + * Reads input file from URL. Returns true upon success. + * + * The limit parameter allows to reduce the number of stars + * that are loaded, keeping the brightest ones. + */ + bool readInput(const char* url, unsigned limit = 200000); + + /** + * Renders the starfield from a local viewer's perspective. + * The parameter specifies the field of view. + */ + void render(FieldOfView const& fov); + + /** + * Sets the resolution for FOV culling. + * + * The parameter determines the number of tiles in azimuthal + * and altitudinal directions. + * + * GPU resources are updated upon change in which case 'true' + * is returned. + */ + bool setResolution(unsigned k); + + /** + * Allows to alter the number of stars to be rendered given a + * factor. The least brightest ones are omitted first. + * + * The further parameters determine when GPU resources should + * be reallocated. Its value is fractional in respect to the + * last number of stars 'n' that caused 'n * (1+overalloc)' to + * be allocated. When the next call to setLOD causes the total + * number of stars that could be rendered to drop below 'n * + * (1-realloc)' or rises above 'n * (1+realloc)' GPU resources + * are updated. Note that all parameters must be fractions, + * that is within the range [0;1] and that 'overalloc' must be + * greater than or equal to 'realloc'. + * + * The current level of detail is returned as a float in [0;1]. + */ + float changeLOD(float factor, + float overalloc = 0.25, float realloc = 0.15); + + private: + // don't copy/assign + Stars(Stars const&); // = delete; + Stars& operator=(Stars const&); // delete; +}; + + +#endif + diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 6a2c41ce24..08cbf05b8f 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -35,12 +35,19 @@ #include #include +#include +#include + #include "Field.h" #include "world.h" #include "Util.h" #ifndef _WIN32 #include "Audio.h" #endif + +#include "FieldOfView.h" +#include "Stars.h" + #include "Head.h" #include "Hand.h" #include "Particle.h" @@ -58,7 +65,7 @@ using namespace std; -int audio_on = 1; // Whether to turn on the audio support +int audio_on = 0; // Whether to turn on the audio support int simulate_on = 1; AgentList agentList('I'); @@ -89,6 +96,14 @@ Oscilloscope audioScope(256,200,true); #define HAND_RADIUS 0.25 // Radius of in-world 'hand' of you Head myHead; // The rendered head of oneself +char starFile[] = "https://s3-us-west-1.amazonaws.com/highfidelity/stars.txt"; +FieldOfView fov; +Stars stars; +#ifdef STARFIELD_KEYS +int starsTiles = 20; +double starsLod = 1.0; +#endif + glm::vec3 box(WORLD_SIZE,WORLD_SIZE,WORLD_SIZE); ParticleSystem balls(0, box, @@ -312,7 +327,9 @@ void init(void) head_mouse_y = HEIGHT/2; head_lean_x = WIDTH/2; head_lean_y = HEIGHT/2; - + + stars.readInput(starFile, 0); + // Initialize Field values field = Field(); printf( "Field Initialized.\n" ); @@ -503,10 +520,21 @@ void display(void) glMateriali(GL_FRONT, GL_SHININESS, 96); // Rotate, translate to camera location + fov.setOrientation( + glm::rotate(glm::rotate(glm::translate(glm::mat4(1.0f), -myHead.getPos()), + -myHead.getRenderYaw(), glm::vec3(0.0f,1.0f,0.0f)), + -myHead.getRenderPitch(), glm::vec3(1.0f,0.0f,0.0f)) ); + + glLoadMatrixf( glm::value_ptr(fov.getWorldViewerXform()) ); glRotatef(myHead.getRenderPitch(), 1, 0, 0); glRotatef(myHead.getRenderYaw(), 0, 1, 0); - glTranslatef(myHead.getPos().x, myHead.getPos().y, myHead.getPos().z); - + + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + stars.render(fov); + glEnable(GL_LIGHTING); + glEnable(GL_DEPTH_TEST); + glColor3f(1,0,0); glutSolidSphere(0.25, 15, 15); @@ -661,6 +689,7 @@ void addRandomSphere(bool wantColorRandomizer) const float KEYBOARD_YAW_RATE = 0.8; +const float KEYBOARD_PITCH_RATE = 0.6; const float KEYBOARD_STRAFE_RATE = 0.03; const float KEYBOARD_FLY_RATE = 0.08; @@ -756,6 +785,15 @@ void key(unsigned char k, int x, int y) if (k == 'w') myHead.setDriveKeys(FWD, 1); if (k == 's') myHead.setDriveKeys(BACK, 1); if (k == ' ') reset_sensors(); + if (k == 't') renderPitchRate -= KEYBOARD_PITCH_RATE; + if (k == 'g') renderPitchRate += KEYBOARD_PITCH_RATE; +#ifdef STARFIELD_KEYS + if (k == 'u') stars.setResolution(starsTiles += 1); + if (k == 'j') stars.setResolution(starsTiles = max(starsTiles-1,1)); + if (k == 'i') if (starsLod < 1.0) starsLod = stars.changeLOD(1.01); + if (k == 'k') if (starsLod > 0.01) starsLod = stars.changeLOD(0.99); + if (k == 'r') stars.readInput(starFile, 0); +#endif if (k == 'a') myHead.setDriveKeys(ROT_LEFT, 1); if (k == 'd') myHead.setDriveKeys(ROT_RIGHT, 1); if (k == 'o') simulate_on = !simulate_on; @@ -845,19 +883,17 @@ void reshape(int width, int height) { WIDTH = width; HEIGHT = height; - - glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); //hello - glLoadIdentity(); - gluPerspective(45, //view angle - 1.0, //aspect ratio - 0.1, //near clip - 500.0);//far clip + fov.setResolution(width, height) + .setBounds(glm::vec3(-0.5f,-0.5f,-500.0f), glm::vec3(0.5f, 0.5f, 0.1f) ) + .setPerspective(0.7854f); + glLoadMatrixf(glm::value_ptr(fov.getViewerScreenXform())); + glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - + glViewport(0, 0, width, height); } void mouseFunc( int button, int state, int x, int y ) diff --git a/interface/src/starfield/CMakeLists.txt b/interface/src/starfield/CMakeLists.txt new file mode 100644 index 0000000000..1cd1dd34c5 --- /dev/null +++ b/interface/src/starfield/CMakeLists.txt @@ -0,0 +1,9 @@ + +project(starfield) + +# Only headers (that are facaded by the Stars.cpp file) here - +# hence declared as custom target. + +file(GLOB_RECURSE STARFIELD_SRCS *.h) +add_custom_target("starfield" SOURCES ${STARFIELD_SRCS}) + diff --git a/interface/src/starfield/Config.h b/interface/src/starfield/Config.h new file mode 100644 index 0000000000..7e9b816838 --- /dev/null +++ b/interface/src/starfield/Config.h @@ -0,0 +1,106 @@ +// +// starfield/Config.h +// interface +// +// Created by Tobias Schwinger on 3/29/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__Config__ +#define __interface__starfield__Config__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +// +// Compile time configuration: +// + +#ifndef STARFIELD_HEMISPHERE_ONLY +#define STARFIELD_HEMISPHERE_ONLY 0 // set to 1 for hemisphere only +#endif + +#ifndef STARFIELD_LOW_MEMORY +#define STARFIELD_LOW_MEMORY 0 // set to 1 not to use 16-bit types +#endif + +#ifndef STARFIELD_DEBUG_LOD +#define STARFIELD_DEBUG_LOD 0 // set to 1 to peek behind the scenes +#endif + +#ifndef STARFIELD_MULTITHREADING +#define STARFIELD_MULTITHREADING 0 +#endif + +// +// Dependencies: +// + +#include "InterfaceConfig.h" +#include "OGlProgram.h" + +#include +#include +#include +#include +#include +#include + +#include + +#if STARFIELD_MULTITHREADING +#include +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "UrlReader.h" +#include "AngleUtils.h" +#include "Radix2InplaceSort.h" +#include "Radix2IntegerScanner.h" +#include "FloodFill.h" + +// Namespace configuration: + +namespace starfield { + + using glm::vec3; + using glm::vec4; + using glm::dot; + using glm::normalize; + using glm::swizzle; + using glm::X; + using glm::Y; + using glm::Z; + using glm::W; + using glm::mat4; + using glm::column; + using glm::row; + + using namespace std; + + +#if STARFIELD_SAVE_MEMORY + typedef uint16_t nuint; + typedef uint32_t wuint; +#else + typedef uint32_t nuint; + typedef uint64_t wuint; +#endif + +} + +#endif + diff --git a/interface/src/starfield/Controller.h b/interface/src/starfield/Controller.h new file mode 100644 index 0000000000..b004e73037 --- /dev/null +++ b/interface/src/starfield/Controller.h @@ -0,0 +1,422 @@ +// +// starfield/Controller.h +// interface +// +// Created by Tobias Schwinger on 3/29/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__Controller__ +#define __interface__starfield__Confroller__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +// +// Data pipeline +// ============= +// +// ->> readInput -(load)--+---- (get brightness & sort) ---> brightness LUT +// | | +// ->> setResolution --+ | >extractBrightnessLevels< +// V | +// (sort by (tile,brightness)) +// | | +// ->> setLOD ---+ | >retile< ->> setLOD --> (just parameterize +// V V when enough data on-GPU) +// (filter by max-LOD brightness, +// build tile info for rendering) +// | | +// V >recreateRenderer< +// (set new renderer)/ +// +// +// (process), ->> entry point, ---> data flow, >internal routine< +// +// (member functions are ordered by data flow) + +// +// Still open +// ========== +// +// o atomics/mutexes need to be added as annotated in the source to allow +// concurrent threads to pull the strings to e.g. have a low priority +// thread run the data pipeline for update -- rendering is wait-free +// + +#include "starfield/data/InputVertex.h" +#include "starfield/data/BrightnessLevel.h" +#include "starfield/Loader.h" + +#include "starfield/renderer/Renderer.h" +#include "starfield/renderer/VertexOrder.h" + +namespace starfield { + + class Controller { + + InputVertices _seqInput; +#if STARFIELD_MULTITHREADING + mutex _mtxInput; + atomic _valTileResolution; + + mutex _mtxLodState; +#else + unsigned _valTileResolution; +#endif + double _valLodFraction; + double _valLodLowWaterMark; + double _valLodHighWaterMark; + double _valLodOveralloc; + size_t _valLodNalloc; + size_t _valLodNrender; + BrightnessLevels _seqLodBrightness; + BrightnessLevel _valLodAllocBrightness; + +#if STARFIELD_MULTITHREADING + atomic _valLodBrightness; + + atomic _ptrRenderer; + + typedef lock_guard lock; +#else + BrightnessLevel _valLodBrightness; + + Renderer* _ptrRenderer; + + #define lock + #define _(x) +#endif + + public: + + Controller() : + _valTileResolution(20), + _valLodFraction(1.0), + _valLodLowWaterMark(0.8), + _valLodHighWaterMark(1.0), + _valLodOveralloc(1.2), + _valLodNalloc(0), + _valLodNrender(0), + _valLodBrightness(0), + _valLodAllocBrightness(0), + _ptrRenderer(0l) { + } + + bool readInput(const char* url, unsigned limit) + { + InputVertices vertices; + + if (! Loader().loadVertices(vertices, url, limit)) + return false; + + BrightnessLevels brightness; + extractBrightnessLevels(brightness, vertices); + + // input is read, now run the entire data pipeline on the new input + + { lock _(_mtxInput); + + _seqInput.swap(vertices); +#if STARFIELD_MULTITHREADING + unsigned k = _valTileResolution.load(memory_order_relaxed); +#else + unsigned k = _valTileResolution; +#endif + size_t n, nRender; + BrightnessLevel bMin, b; + double rcpChange; + + // we'll have to build a new LOD state for a new total N, + // ideally keeping allocation size and number of vertices + + { lock _(_mtxLodState); + + size_t newLast = _seqInput.size() - 1; + + // reciprocal change N_old/N_new tells us how to scale + // the fractions + rcpChange = min(1.0, double(vertices.size()) / _seqInput.size()); + + // initialization? use defaults / previously set values + if (rcpChange == 0.0) { + + rcpChange = 1.0; + + nRender = lrint(_valLodFraction * newLast); + n = min(newLast, size_t(lrint(_valLodOveralloc * nRender))); + + } else { + + // cannot allocate or render more than we have + n = min(newLast, _valLodNalloc); + nRender = min(newLast, _valLodNrender); + } + + // determine new minimum brightness levels + bMin = brightness[n]; + b = brightness[nRender]; + + // adjust n + n = std::upper_bound( + brightness.begin() + n - 1, + brightness.end(), + bMin, GreaterBrightness() ) - brightness.begin(); + } + + // invoke next stage + try { + + this->retile(n, k, b, bMin); + + } catch (...) { + + // rollback transaction and rethrow + vertices.swap(_seqInput); + throw; + } + + // finally publish the new LOD state + + { lock _(_mtxLodState); + + _seqLodBrightness.swap(brightness); + _valLodFraction *= rcpChange; + _valLodLowWaterMark *= rcpChange; + _valLodHighWaterMark *= rcpChange; + _valLodOveralloc *= rcpChange; + _valLodNalloc = n; + _valLodNrender = nRender; + _valLodAllocBrightness = bMin; +#if STARFIELD_MULTITHREADING + _valLodBrightness.store(b, memory_order_relaxed); +#else + _valLodBrightness = b; +#endif + } + } + + return true; + } + + bool setResolution(unsigned k) { + + if (k <= 3) { + return false; + } + +// fprintf(stderr, "Stars.cpp: setResolution(%d)\n", k); + +#if STARFIELD_MULTITHREADING + if (k != _valTileResolution.load(memory_order_relaxed)) +#else + if (k != _valTileResolution) +#endif + { lock _(_mtxInput); + + unsigned n; + BrightnessLevel b, bMin; + + { lock _(_mtxLodState); + + n = _valLodNalloc; +#if STARFIELD_MULTITHREADING + b = _valLodBrightness.load(memory_order_relaxed); +#else + b = _valLodBrightness; +#endif + bMin = _valLodAllocBrightness; + } + + this->retile(n, k, b, bMin); + + return true; + } else { + return false; + } + } + + private: + + void retile(size_t n, unsigned k, + BrightnessLevel b, BrightnessLevel bMin) { + + Tiling tiling(k); + VertexOrder scanner(tiling); + radix2InplaceSort(_seqInput.begin(), _seqInput.end(), scanner); + +// fprintf(stderr, +// "Stars.cpp: recreateRenderer(%d, %d, %d, %d)\n", n, k, b, bMin); + + recreateRenderer(n, k, b, bMin); + + _valTileResolution = k; + } + + public: + + double changeLOD(double factor, double overalloc, double realloc) { + + assert(overalloc >= realloc && realloc >= 0.0); + assert(overalloc <= 1.0 && realloc <= 1.0); + +// fprintf(stderr, +// "Stars.cpp: changeLOD(%lf, %lf, %lf)\n", factor, overalloc, realloc); + + size_t n, nRender; + BrightnessLevel bMin, b; + double fraction, lwm, hwm; + + { lock _(_mtxLodState); + + // acuire a consistent copy of the current LOD state + fraction = _valLodFraction; + lwm = _valLodLowWaterMark; + hwm = _valLodHighWaterMark; + size_t last = _seqLodBrightness.size() - 1; + + // apply factor + fraction = max(0.0, min(1.0, fraction * factor)); + + // calculate allocation size and corresponding brightness + // threshold + double oaFract = std::min(fraction * (1.0 + overalloc), 1.0); + n = lrint(oaFract * last); + bMin = _seqLodBrightness[n]; + n = std::upper_bound( + _seqLodBrightness.begin() + n - 1, + _seqLodBrightness.end(), + bMin, GreaterBrightness() ) - _seqLodBrightness.begin(); + + // also determine number of vertices to render and brightness + nRender = lrint(fraction * last); + // Note: nRender does not have to be accurate + b = _seqLodBrightness[nRender]; + // this setting controls the renderer, also keep b as the + // brightness becomes volatile as soon as the mutex is + // released, so keep b +#if STARFIELD_MULTITHREADING + _valLodBrightness.store(b, memory_order_relaxed); +#else + _valLodBrightness = b; +#endif + +// fprintf(stderr, "Stars.cpp: " +// "fraction = %lf, oaFract = %lf, n = %d, n' = %d, bMin = %d, b = %d\n", +// fraction, oaFract, lrint(oaFract * last)), n, bMin, b); + + // will not have to reallocate? set new fraction right away + // (it is consistent with the rest of the state in this case) + if (fraction >= _valLodLowWaterMark + && fraction <= _valLodHighWaterMark) { + + _valLodFraction = fraction; + return fraction; + } + } + + // reallocate + + { lock _(_mtxInput); + + recreateRenderer(n, _valTileResolution, b, bMin); + +// fprintf(stderr, "Stars.cpp: LOD reallocation\n"); + + // publish new lod state + + { lock _(_mtxLodState); + + _valLodNalloc = n; + _valLodNrender = nRender; + + _valLodFraction = fraction; + _valLodLowWaterMark = fraction * (1.0 - realloc); + _valLodHighWaterMark = fraction * (1.0 + realloc); + _valLodOveralloc = fraction * (1.0 + overalloc); + _valLodAllocBrightness = bMin; + } + } + return fraction; + } + + private: + + void recreateRenderer(size_t n, unsigned k, + BrightnessLevel b, BrightnessLevel bMin) { + +#if STARFIELD_MULTITHREADING + delete _ptrRenderer.exchange(new Renderer(_seqInput, n, k, b, bMin) ); +#else + delete _ptrRenderer; + _ptrRenderer = new Renderer(_seqInput, n, k, b, bMin); +#endif + } + + public: + + void render(float perspective, float angle, mat4 const& orientation) { + +#if STARFIELD_MULTITHREADING + // check out renderer + Renderer* renderer = _ptrRenderer.exchange(0l); +#else + Renderer* renderer = _ptrRenderer; +#endif + + // have it render + if (renderer != 0l) { +#if STARFIELD_MULTITHREADING + BrightnessLevel b = _valLodBrightness.load(memory_order_relaxed); +#else + BrightnessLevel b = _valLodBrightness; +#endif + renderer->render(perspective, angle, orientation, b); + } + +#if STARFIELD_MULTITHREADING + // check in - or dispose if there is a new one + Renderer* newOne = 0l; + if (! _ptrRenderer.compare_exchange_strong(newOne, renderer)) { + + assert(!! newOne); + delete renderer; + } +#else +# undef lock +# undef _ +#endif + } + + private: + + struct BrightnessSortScanner : Radix2IntegerScanner { + + typedef Radix2IntegerScanner base; + + BrightnessSortScanner() : base(BrightnessBits) { } + + bool bit(BrightnessLevel const& k, state_type& s) { + + // bit is inverted to achieve descending order + return ! base::bit(k,s); + } + }; + + static void extractBrightnessLevels(BrightnessLevels& dst, + InputVertices const& src) { + dst.clear(); + dst.reserve(src.size()); + for (InputVertices::const_iterator i = + src.begin(), e = src.end(); i != e; ++i) + dst.push_back( getBrightness(i->getColor()) ); + + radix2InplaceSort(dst.begin(), dst.end(), BrightnessSortScanner()); + } + + }; +} + + +#endif diff --git a/interface/src/starfield/Loader.h b/interface/src/starfield/Loader.h new file mode 100644 index 0000000000..0db0384734 --- /dev/null +++ b/interface/src/starfield/Loader.h @@ -0,0 +1,180 @@ +// +// starfield/Loader.h +// interface +// +// Created by Tobias Schwinger on 3/29/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__Loader__ +#define __interface__starfield__Loader__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "Config.h" + +#include "starfield/data/InputVertex.h" +#include "starfield/data/BrightnessLevel.h" + +namespace starfield { + + class Loader : UrlReader { + + InputVertices* _ptrVertices; + unsigned _valLimit; + + unsigned _valLineNo; + char const* _strUrl; + + unsigned _valRecordsRead; + BrightnessLevel _valMinBrightness; + public: + + bool loadVertices( + InputVertices& destination, char const* url, unsigned limit) + { + _ptrVertices = & destination; + _valLimit = limit; +#if STARFIELD_SAVE_MEMORY + if (_valLimit == 0 || _valLimit > 60000u) + _valLimit = 60000u; +#endif + _strUrl = url; // in case we fail early + + if (! UrlReader::readUrl(url, *this)) + { + fprintf(stderr, "%s:%d: %s\n", + _strUrl, _valLineNo, getError()); + + return false; + } + fprintf(stderr, "Stars.cpp: read %d vertices, using %d\n", + _valRecordsRead, _ptrVertices->size()); + + return true; + } + + protected: + + friend class UrlReader; + + void begin(char const* url, + char const* type, + int64_t size, + int64_t stardate) { + + _valLineNo = 0u; + _strUrl = url; // new value in http redirect + + _valRecordsRead = 0u; + + _ptrVertices->clear(); + _ptrVertices->reserve(_valLimit); +// fprintf(stderr, "Stars.cpp: loader begin %s\n", url); + } + + size_t transfer(char* input, size_t bytes) { + + size_t consumed = 0u; + char const* end = input + bytes; + char* line, * next = input; + + for (;;) { + + // advance to next line + for (; next != end && isspace(*next); ++next); + consumed = next - input; + line = next; + ++_valLineNo; + for (; next != end && *next != '\n' && *next != '\r'; ++next); + if (next == end) + return consumed; + *next++ = '\0'; + + // skip comments + if (*line == '\\' || *line == '/' || *line == ';') + continue; + + // parse + float azi, alt; + unsigned c; + if (sscanf(line, " %f %f #%x", & azi, & alt, & c) == 3) { + + if (spaceFor( getBrightness(c) )) { + + storeVertex(azi, alt, c); + } + + ++_valRecordsRead; + + } else { + + fprintf(stderr, "Stars.cpp:%d: Bad input from %s\n", + _valLineNo, _strUrl); + } + + } + return consumed; + } + + void end(bool ok) + { } + + private: + + bool atLimit() { return _valLimit > 0u && _valRecordsRead >= _valLimit; } + + bool spaceFor(BrightnessLevel b) { + + if (! atLimit()) { + return true; + } + + // just reached the limit? -> establish a minimum heap and + // remember the brightness at its top + if (_valRecordsRead == _valLimit) { + +// fprintf(stderr, "Stars.cpp: vertex limit reached -> heap mode\n"); + + make_heap( + _ptrVertices->begin(), _ptrVertices->end(), + GreaterBrightness() ); + + _valMinBrightness = getBrightness( + _ptrVertices->begin()->getColor() ); + } + + // not interested? say so + if (_valMinBrightness >= b) + return false; + + // otherwise free up space for the new vertex + pop_heap( + _ptrVertices->begin(), _ptrVertices->end(), + GreaterBrightness() ); + _ptrVertices->pop_back(); + return true; + } + + void storeVertex(float azi, float alt, unsigned color) { + + _ptrVertices->push_back(InputVertex(azi, alt, color)); + + if (atLimit()) { + + push_heap( + _ptrVertices->begin(), _ptrVertices->end(), + GreaterBrightness() ); + + _valMinBrightness = getBrightness( + _ptrVertices->begin()->getColor() ); + } + } + }; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/data/BrightnessLevel.h b/interface/src/starfield/data/BrightnessLevel.h new file mode 100644 index 0000000000..3567b88478 --- /dev/null +++ b/interface/src/starfield/data/BrightnessLevel.h @@ -0,0 +1,64 @@ +// +// starfield/data/BrightnessLevel.h +// interface +// +// Created by Tobias Schwinger on 3/29/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__data__BrightnessLevel__ +#define __interface__starfield__data__BrightnessLevel__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" +#include "starfield/data/InputVertex.h" +#include "starfield/data/GpuVertex.h" + +namespace starfield { + + typedef nuint BrightnessLevel; + + +#if STARFIELD_SAVE_MEMORY + const unsigned BrightnessBits = 16u; +#else + const unsigned BrightnessBits = 18u; +#endif + const BrightnessLevel BrightnessMask = (1u << (BrightnessBits)) - 1u; + + typedef std::vector BrightnessLevels; + + BrightnessLevel getBrightness(unsigned c) { + + unsigned r = (c >> 16) & 0xff; + unsigned g = (c >> 8) & 0xff; + unsigned b = c & 0xff; +#if STARFIELD_SAVE_MEMORY + return BrightnessLevel((r*r+g*g+b*b) >> 2); +#else + return BrightnessLevel(r*r+g*g+b*b); +#endif + } + + + struct GreaterBrightness { + + bool operator()(InputVertex const& lhs, InputVertex const& rhs) const { + return getBrightness(lhs.getColor()) + > getBrightness(rhs.getColor()); + } + bool operator()(BrightnessLevel lhs, GpuVertex const& rhs) const { + return lhs > getBrightness(rhs.getColor());; + } + bool operator()(BrightnessLevel lhs, BrightnessLevel rhs) const { + return lhs > rhs; + } + }; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/data/GpuVertex.h b/interface/src/starfield/data/GpuVertex.h new file mode 100644 index 0000000000..6de3b5a15f --- /dev/null +++ b/interface/src/starfield/data/GpuVertex.h @@ -0,0 +1,53 @@ +// +// starfield/data/GpuVertex.h +// interface +// +// Created by Tobias Schwinger on 3/29/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__data__GpuVertex__ +#define __interface__starfield__data__GpuVertex__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/data/InputVertex.h" + +namespace starfield { + + class GpuVertex { + + unsigned _valColor; + float _valX; + float _valY; + float _valZ; + public: + + GpuVertex() { } + + GpuVertex(InputVertex const& in) { + + _valColor = in.getColor(); + float azi = in.getAzimuth(); + float alt = in.getAltitude(); + + // ground vector in x/z plane... + float gx = sin(azi); + float gz = -cos(azi); + + // ...elevated in y direction by altitude + float exz = cos(alt); + _valX = gx * exz; + _valY = sin(alt); + _valZ = gz * exz; + } + + unsigned getColor() const { return _valColor; } + }; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/data/InputVertex.h b/interface/src/starfield/data/InputVertex.h new file mode 100644 index 0000000000..dac242ec51 --- /dev/null +++ b/interface/src/starfield/data/InputVertex.h @@ -0,0 +1,51 @@ +// +// starfield/data/InputVertex.h +// interface +// +// Created by Tobias Schwinger on 3/29/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__data__InputVertex__ +#define __interface__starfield__data__InputVertex__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" + +namespace starfield { + + class InputVertex { + + unsigned _valColor; + float _valAzimuth; + float _valAltitude; + public: + + InputVertex(float azimuth, float altitude, unsigned color) { + + _valColor = color >> 16 & 0xffu | color & 0xff00u | + color << 16 & 0xff0000u | 0xff000000u; + + azimuth = angleConvert(azimuth); + altitude = angleConvert(altitude); + + angleHorizontalPolar(azimuth, altitude); + + _valAzimuth = azimuth; + _valAltitude = altitude; + } + + float getAzimuth() const { return _valAzimuth; } + float getAltitude() const { return _valAltitude; } + unsigned getColor() const { return _valColor; } + }; + + typedef std::vector InputVertices; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/data/Tile.h b/interface/src/starfield/data/Tile.h new file mode 100644 index 0000000000..6d51921ce9 --- /dev/null +++ b/interface/src/starfield/data/Tile.h @@ -0,0 +1,37 @@ +// +// starfield/data/Tile.h +// interface +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__data__Tile__ +#define __interface__starfield__data__Tile__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" +#include "starfield/data/BrightnessLevel.h" + +namespace starfield { + + struct Tile { + + nuint offset; + nuint count; + BrightnessLevel lod; + nuint flags; + + static uint16_t const checked = 1; + static uint16_t const visited = 2; + static uint16_t const render = 4; + }; + + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/renderer/Renderer.h b/interface/src/starfield/renderer/Renderer.h new file mode 100644 index 0000000000..abb553cd06 --- /dev/null +++ b/interface/src/starfield/renderer/Renderer.h @@ -0,0 +1,524 @@ +// +// starfield/renderer/Renderer.h +// interface +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__renderer__Renderer__ +#define __interface__starfield__renderer__Renderer__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" +#include "starfield/data/InputVertex.h" +#include "starfield/data/BrightnessLevel.h" + +#include "starfield/data/Tile.h" +#include "starfield/data/GpuVertex.h" + +#include "Tiling.h" + +// +// FOV culling +// =========== +// +// As stars can be thought of as at infinity distance, the field of view only +// depends on perspective and rotation: +// +// _----_ <-- visible stars +// from above +-near-+ - - +// \ / | +// near width: \ / | cos(p/2) +// 2sin(p/2) \/ _ +// center +// +// +// Now it is important to note that a change in altitude maps uniformly to a +// distance on a sphere. This is NOT the case for azimuthal angles: In this +// case a factor of 'cos(alt)' (the orbital radius) applies: +// +// +// |<-cos alt ->| | |<-|<----->|->| d_azi cos(alt) +// | +// __--* | --------- - +// __-- * | | | ^ d_alt +// __-- alt) * | | | v +// --------------*- | ------------- - +// | +// side view | tile on sphere +// +// +// This lets us find a worst-case (Eigen) angle from the center to the edge +// of a tile as +// +// hypot( 0.5 d_alt, 0.5 d_azi cos(alt_absmin) ). +// +// This angle must be added to 'p' (the perspective angle) in order to find +// an altered near plane for the culling decision. +// + +namespace starfield { + + class Renderer { + + GpuVertex* _arrData; + Tile* _arrTile; + GLint* _arrBatchOffs; + GLsizei* _arrBatchCount; + GLuint _hndVertexArray; + OGlProgram _objProgram; + + Tiling _objTiling; + + unsigned* _itrOutIndex; + vec3 _vecWxform; + float _valHalfPersp; + BrightnessLevel _valMinBright; + + public: + + Renderer(InputVertices const& src, + size_t n, + unsigned k, + BrightnessLevel b, + BrightnessLevel bMin) : + + _arrData(0l), + _arrTile(0l), + _objTiling(k) { + + this->glAlloc(); + + Tiling tiling(k); + size_t nTiles = tiling.getTileCount(); + + _arrData = new GpuVertex[n]; + _arrTile = new Tile[nTiles + 1]; + _arrBatchOffs = new GLint[nTiles]; + _arrBatchCount = new GLsizei[nTiles]; + + prepareVertexData(src, n, tiling, b, bMin); + + this->glUpload(n); + } + + ~Renderer() + { + delete[] _arrData; + delete[] _arrTile; + delete[] _arrBatchCount; + delete[] _arrBatchOffs; + + this->glFree(); + } + + void render(float perspective, + float aspect, + mat4 const& orientation, + BrightnessLevel minBright) + { + +// fprintf(stderr, " +// Stars.cpp: rendering at minimal brightness %d\n", minBright); + + float halfPersp = perspective * 0.5f; + + // determine dimensions based on a sought screen diagonal + // + // ww + hh = dd + // a = h / w => h = wa + // ww + ww aa = dd + // ww = dd / (1 + aa) + float diag = 2.0f * std::sin(halfPersp); + float near = std::cos(halfPersp); + + float hw = 0.5f * sqrt(diag * diag / (1.0f + aspect * aspect)); + float hh = hw * aspect; + + // cancel all translation + mat4 matrix = orientation; + matrix[3][0] = 0.0f; + matrix[3][1] = 0.0f; + matrix[3][2] = 0.0f; + + // extract local z vector + vec3 ahead = swizzle( column(matrix, 2) ); + + float azimuth = atan2(ahead.x,-ahead.z) + Radians::pi(); + float altitude = atan2(-ahead.y, hypotf(ahead.x, ahead.z)); + angleHorizontalPolar(azimuth, altitude); +#if STARFIELD_HEMISPHERE_ONLY + altitude = std::max(0.0f, altitude); +#endif + unsigned tileIndex = + _objTiling.getTileIndex(azimuth, altitude); + +// fprintf(stderr, "Stars.cpp: starting on tile #%d\n", tileIndex); + + +#if STARFIELD_DEBUG_LOD + mat4 matrix_debug = glm::translate( + glm::frustum(-hw,hw, -hh,hh, near,10.0f), + vec3(0.0f, 0.0f, -4.0f)) * glm::affineInverse(matrix); +#endif + + matrix = glm::frustum(-hw,hw, -hh,hh, near,10.0f) + * glm::affineInverse(matrix); + + this->_itrOutIndex = (unsigned*) _arrBatchOffs; + this->_vecWxform = swizzle(row(matrix, 3)); + this->_valHalfPersp = halfPersp; + this->_valMinBright = minBright; + + floodFill(_arrTile + tileIndex, TileSelection(*this, + _arrTile, _arrTile + _objTiling.getTileCount(), + (Tile**) _arrBatchCount)); + +#if STARFIELD_DEBUG_LOD +# define matrix matrix_debug +#endif + this->glBatch(glm::value_ptr(matrix), prepareBatch( + (unsigned*) _arrBatchOffs, _itrOutIndex) ); + +#if STARFIELD_DEBUG_LOD +# undef matrix +#endif + } + + private: // renderer construction + + void prepareVertexData(InputVertices const& src, + size_t n, // <-- at bMin and brighter + Tiling const& tiling, + BrightnessLevel b, + BrightnessLevel bMin) { + + size_t nTiles = tiling.getTileCount(); + size_t vertexIndex = 0u, currTileIndex = 0u, count_active = 0u; + + _arrTile[0].offset = 0u; + _arrTile[0].lod = b; + _arrTile[0].flags = 0u; + + for (InputVertices::const_iterator i = + src.begin(), e = src.end(); i != e; ++i) { + + BrightnessLevel bv = getBrightness(i->getColor()); + // filter by alloc brightness + if (bv >= bMin) { + + size_t tileIndex = tiling.getTileIndex( + i->getAzimuth(), i->getAltitude()); + + assert(tileIndex >= currTileIndex); + + // moved on to another tile? -> flush + if (tileIndex != currTileIndex) { + + Tile* t = _arrTile + currTileIndex; + Tile* tLast = _arrTile + tileIndex; + + // set count of active vertices (upcoming lod) + t->count = count_active; + // generate skipped, empty tiles + for(size_t offs = vertexIndex; ++t != tLast ;) { + t->offset = offs, t->count = 0u, + t->lod = b, t->flags = 0u; + } + + // initialize next (as far as possible here) + tLast->offset = vertexIndex; + tLast->lod = b; + tLast->flags = 0u; + + currTileIndex = tileIndex; + count_active = 0u; + } + + if (bv >= b) + ++count_active; + +// fprintf(stderr, "Stars.cpp: Vertex %d on tile #%d\n", vertexIndex, tileIndex); + + // write converted vertex + _arrData[vertexIndex++] = *i; + } + } + assert(vertexIndex == n); + // flush last tile (see above) + Tile* t = _arrTile + currTileIndex; + t->count = count_active; + for (Tile* e = _arrTile + nTiles + 1; ++t != e;) { + t->offset = vertexIndex, t->count = 0u, + t->lod = b, t->flags = 0; + } + } + + + private: // FOV culling / LOD + + class TileSelection; + friend class Renderer::TileSelection; + + class TileSelection { + + Renderer& _refRenderer; + Tile** const _arrStack; + Tile** _itrStack; + Tile const* const _arrTile; + Tile const* const _itrTilesEnd; + + public: + + TileSelection(Renderer& renderer, Tile const* tiles, + Tile const* tiles_end, Tile** stack) : + + _refRenderer(renderer), + _arrStack(stack), + _itrStack(stack), + _arrTile(tiles), + _itrTilesEnd(tiles_end) { + } + + protected: + + // flood fill strategy + + bool select(Tile* t) { + + if (t < _arrTile || t >= _itrTilesEnd || + !! (t->flags & Tile::visited)) { + + return false; + } + if (! (t->flags & Tile::checked)) { + + if (_refRenderer.visitTile(t)) + t->flags |= Tile::render; + } + return !! (t->flags & Tile::render); + } + + void process(Tile* t) { + + t->flags |= Tile::visited; + } + + void right(Tile*& cursor) const { cursor += 1; } + void left(Tile*& cursor) const { cursor -= 1; } + void up(Tile*& cursor) const { cursor += yStride(); } + void down(Tile*& cursor) const { cursor -= yStride(); } + + void defer(Tile* t) { *_itrStack++ = t; } + + bool deferred(Tile*& cursor) { + + if (_itrStack != _arrStack) { + cursor = *--_itrStack; + return true; + } + return false; + } + + private: + unsigned yStride() const { + + return _refRenderer._objTiling.getAzimuthalTiles(); + } + }; + + bool visitTile(Tile* t) { + + unsigned index = t - _arrTile; + *_itrOutIndex++ = index; + + if (! tileVisible(t, index)) { + return false; + } + + if (t->lod != _valMinBright) { + updateVertexCount(t, _valMinBright); + } + return true; + } + + bool tileVisible(Tile* t, unsigned i) { + + float slice = _objTiling.getSliceAngle(); + unsigned stride = _objTiling.getAzimuthalTiles(); + float azimuth = (i % stride) * slice; + float altitude = (i / stride) * slice - Radians::halfPi(); + float gx = sin(azimuth); + float gz = -cos(azimuth); + float exz = cos(altitude); + vec3 tileCenter = vec3(gx * exz, sin(altitude), gz * exz); + float w = dot(_vecWxform, tileCenter); + + float halfSlice = 0.5f * slice; + float daz = halfSlice * cos(abs(altitude) - halfSlice); + float dal = halfSlice; + float near = cos(_valHalfPersp + sqrt(daz*daz+dal*dal)); + +// fprintf(stderr, "Stars.cpp: checking tile #%d, w = %f, near = %f\n", i, w, near); + + return w > near; + } + + void updateVertexCount(Tile* t, BrightnessLevel minBright) { + + // a growing number of stars needs to be rendereed when the + // minimum brightness decreases + // perform a binary search in the so found partition for the + // new vertex count of this tile + + GpuVertex const* start = _arrData + t[0].offset; + GpuVertex const* end = _arrData + t[1].offset; + + assert(end >= start); + + if (start == end) + return; + + if (t->lod < minBright) + end = start + t->count; + else + start += (t->count > 0 ? t->count - 1 : 0); + + end = std::upper_bound( + start, end, minBright, GreaterBrightness()); + + assert(end >= _arrData + t[0].offset); + + t->count = end - _arrData - t[0].offset; + t->lod = minBright; + } + + unsigned prepareBatch(unsigned const* indices, + unsigned const* indicesEnd) { + + unsigned nRanges = 0u; + GLint* offs = _arrBatchOffs; + GLsizei* count = _arrBatchCount; + + for (unsigned* i = (unsigned*) _arrBatchOffs; + i != indicesEnd; ++i) { + + Tile* t = _arrTile + *i; + if ((t->flags & Tile::render) > 0u && t->count > 0u) { + + *offs++ = t->offset; + *count++ = t->count; + ++nRanges; + } + t->flags = 0; + } + return nRanges; + } + + private: // gl API handling + +#ifdef __APPLE__ +# define glBindVertexArray glBindVertexArrayAPPLE +# define glGenVertexArrays glGenVertexArraysAPPLE +# define glDeleteVertexArrays glDeleteVertexArraysAPPLE +#endif + void glAlloc() { + + GLchar const* const VERTEX_SHADER = + "#version 120\n" + "void main(void) {\n" + + " vec3 c = gl_Color.rgb * 1.0125;\n" + " float s = max(1.0, dot(c, c) * 0.7);\n" + + " gl_Position = ftransform();\n" + " gl_FrontColor= gl_Color;\n" + " gl_PointSize = s;\n" + "}\n"; + + _objProgram.addShader(GL_VERTEX_SHADER, VERTEX_SHADER); + GLchar const* const FRAGMENT_SHADER = + "#version 120\n" + "void main(void) {\n" + " gl_FragColor = gl_Color;\n" + "}\n"; + _objProgram.addShader(GL_FRAGMENT_SHADER, FRAGMENT_SHADER); + _objProgram.link(); + + glGenVertexArrays(1, & _hndVertexArray); + } + + void glFree() { + + glDeleteVertexArrays(1, & _hndVertexArray); + } + + void glUpload(GLsizei n) { + + GLuint vbo; + glGenBuffers(1, & vbo); + + glBindVertexArray(_hndVertexArray); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, + n * sizeof(GpuVertex), _arrData, GL_STATIC_DRAW); + glInterleavedArrays(GL_C4UB_V3F, sizeof(GpuVertex), 0l); + + glBindVertexArray(0); + } + + void glBatch(GLfloat const* matrix, GLsizei n_ranges) { + +// fprintf(stderr, "Stars.cpp: rendering %d-multibatch\n", n_ranges); + +// for (int i = 0; i < n_ranges; ++i) +// fprintf(stderr, "Stars.cpp: Batch #%d - %d stars @ %d\n", i, +// _arrBatchOffs[i], _arrBatchCount[i]); + + // setup modelview matrix (identity) + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + // set projection matrix + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadMatrixf(matrix); + + // set point size and smoothing + shader control + glPointSize(1.0f); + glEnable(GL_POINT_SMOOTH); + glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); + + // select shader and vertex array + _objProgram.activate(); + glBindVertexArray(_hndVertexArray); + + // render + glMultiDrawArrays(GL_POINTS, + _arrBatchOffs, _arrBatchCount, n_ranges); + + // restore state + glBindVertexArray(0); + glUseProgram(0); + glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } +#ifdef __APPLE__ +# undef glBindVertexArray +# undef glGenVertexArrays +# undef glDeleteVertexArrays +#endif + }; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/renderer/Tiling.h b/interface/src/starfield/renderer/Tiling.h new file mode 100644 index 0000000000..d24b2e8f8c --- /dev/null +++ b/interface/src/starfield/renderer/Tiling.h @@ -0,0 +1,71 @@ +// +// starfield/renderer/Tiling.h +// interface +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__renderer__Tiling__ +#define __interface__starfield__renderer__Tiling__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" + +namespace starfield { + + class Tiling { + + unsigned _valK; + float _valRcpSlice; + unsigned _valBits; + + public: + + Tiling(unsigned k) : + _valK(k), + _valRcpSlice(k / Radians::twicePi()) { + _valBits = ceil(log2(getTileCount())); + } + + unsigned getAzimuthalTiles() const { return _valK; } + unsigned getAltitudinalTiles() const { return _valK / 2 + 1; } + unsigned getTileIndexBits() const { return _valBits; } + + unsigned getTileCount() const { + return getAzimuthalTiles() * getAltitudinalTiles(); + } + + unsigned getTileIndex(float azimuth, float altitude) const { + return discreteAzimuth(azimuth) + + _valK * discreteAltitude(altitude); + } + + float getSliceAngle() const { + return 1.0f / _valRcpSlice; + } + + private: + + unsigned discreteAngle(float unsigned_angle) const { + return unsigned(round(unsigned_angle * _valRcpSlice)); + } + + unsigned discreteAzimuth(float a) const { + return discreteAngle(a) % _valK; + } + + unsigned discreteAltitude(float a) const { + return min(getAltitudinalTiles() - 1, + discreteAngle(a + Radians::halfPi()) ); + } + + }; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/renderer/VertexOrder.h b/interface/src/starfield/renderer/VertexOrder.h new file mode 100644 index 0000000000..13784de53e --- /dev/null +++ b/interface/src/starfield/renderer/VertexOrder.h @@ -0,0 +1,52 @@ +// +// starfield/renderer/VertexOrder.h +// interface +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__renderer__VertexOrder__ +#define __interface__starfield__renderer__VertexOrder__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" +#include "starfield/data/InputVertex.h" +#include "starfield/renderer/Tiling.h" + +namespace starfield { + + /** + * Defines the vertex order for the renderer as a bit extractor for + * binary in-place Radix Sort. + */ + class VertexOrder : public Radix2IntegerScanner + { + Tiling _objTiling; + + typedef Radix2IntegerScanner base; + public: + + explicit VertexOrder(Tiling const& tiling) : + + base(tiling.getTileIndexBits() + BrightnessBits), + _objTiling(tiling) { + } + + bool bit(InputVertex const& v, state_type const& s) const { + + // inspect (tile_index, brightness) tuples + unsigned key = getBrightness(v.getColor()) ^ BrightnessMask; + key |= _objTiling.getTileIndex( + v.getAzimuth(), v.getAltitude()) << BrightnessBits; + return base::bit(key, s); + } + }; + +} // anonymous namespace + +#endif + diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt index e03926f641..32df85acc9 100644 --- a/shared/CMakeLists.txt +++ b/shared/CMakeLists.txt @@ -9,6 +9,11 @@ file(GLOB HIFI_SHARED_SRCS src/*.h src/*.cpp) add_library(HifiShared ${HIFI_SHARED_SRCS}) set(HIFI_SHARED_LIBRARY HifiShared) +find_package(CURL REQUIRED) +include_directories(${CURL_INCLUDE_DIRS}) +# link target to common, external libraries +target_link_libraries(HifiShared ${CURL_LIBRARY}) + # link required libraries on UNIX if (UNIX AND NOT APPLE) find_package(Threads REQUIRED) diff --git a/shared/src/AgentList.cpp b/shared/src/AgentList.cpp index 9b2905e940..5fa28bba9a 100644 --- a/shared/src/AgentList.cpp +++ b/shared/src/AgentList.cpp @@ -8,7 +8,8 @@ #include #include -#include +#include +#include #include "AgentList.h" #include "SharedUtil.h" @@ -355,4 +356,4 @@ void AgentList::startDomainServerCheckInThread() { void AgentList::stopDomainServerCheckInThread() { domainServerCheckinStopFlag = true; pthread_join(checkInWithDomainServerThread, NULL); -} \ No newline at end of file +} diff --git a/shared/src/AngleUtils.h b/shared/src/AngleUtils.h new file mode 100644 index 0000000000..533df57da9 --- /dev/null +++ b/shared/src/AngleUtils.h @@ -0,0 +1,92 @@ +// +// AngleUtils.h +// hifi +// +// Created by Tobias Schwinger on 3/23/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__AngleUtils__ +#define __hifi__AngleUtils__ + +#include + +struct Degrees +{ + static float pi() { return 180.0f; } + static float twicePi() { return 360.0f; } + static float halfPi() { return 90.0f; } +}; + +struct Radians +{ + static float pi() { return 3.141592653589793f; } + static float twicePi() { return 6.283185307179586f; } + static float halfPi() { return 1.5707963267948966; } +}; + +struct Rotations +{ + static float pi() { return 0.5f; } + static float twicePi() { return 1.0f; } + static float halfPi() { return 0.25f; } +}; + +/** + * Converts an angle from one unit to another. + */ +template< class UnitFrom, class UnitTo > +float angleConvert(float a) +{ + return a * (UnitTo::halfPi() / UnitFrom::halfPi()); +} + + +/** + * Clamps an angle to the range of [-180; 180) degrees. + */ +template< class Unit > +float angleSignedNormal(float a) +{ + float result = remainder(a, Unit::twicePi()); + if (result == Unit::pi()) + result = -Unit::pi(); + return result; +} + +/** + * Clamps an angle to the range of [0; 360) degrees. + */ +template< class Unit > +float angleUnsignedNormal(float a) +{ + return angleSignedNormal(a - Unit::pi()) + Unit::pi(); +} + + +/** + * Clamps a polar direction so that azimuth is in the range of [0; 360) + * degrees and altitude is in the range of [-90; 90] degrees. + * + * The so normalized angle still contains ambiguity due to gimbal lock: + * Both poles can be reached from any azimuthal direction. + */ +template< class Unit > +void angleHorizontalPolar(float& azimuth, float& altitude) +{ + altitude = angleSignedNormal(altitude); + if (altitude > Unit::halfPi()) + { + altitude = Unit::pi() - altitude; + azimuth += Unit::pi(); + } + else if (altitude < -Unit::halfPi()) + { + altitude = -Unit::pi() - altitude; + azimuth += Unit::pi(); + } + azimuth = angleUnsignedNormal(azimuth); +} + +#endif + diff --git a/shared/src/FloodFill.h b/shared/src/FloodFill.h new file mode 100644 index 0000000000..ff278b185b --- /dev/null +++ b/shared/src/FloodFill.h @@ -0,0 +1,95 @@ +// +// FloodFill.h +// hifi +// +// Created by Tobias Schwinger 3/26/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__FloodFill__ +#define __hifi__FloodFill__ + +/** + * Line scanning, iterative flood fill algorithm. + */ +template< class Strategy, typename Cursor > +void floodFill(Cursor const& position, + Strategy const& strategy = Strategy()); + + +template< class Strategy, typename Cursor > +struct floodFill_impl : Strategy +{ + floodFill_impl(Strategy const& s) : Strategy(s) { } + + using Strategy::select; + using Strategy::process; + + using Strategy::left; + using Strategy::right; + using Strategy::up; + using Strategy::down; + + using Strategy::defer; + using Strategy::deferred; + + void go(Cursor position) + { + Cursor higher, lower, h,l, i; + bool higherFound, lowerFound, hf, lf; + do + { + if (! select(position)) + continue; + + process(position); + + higher = position; higherFound = false; + up(higher); yTest(higher, higherFound); + lower = position; lowerFound = false; + down(lower); yTest(lower, lowerFound); + + i = position, h = higher, l = lower; + hf = higherFound, lf = lowerFound; + do { right(i), right(h), right(l); yTest(h,hf); yTest(l,lf); } + while (selectAndProcess(i)); + + i = position, h = higher, l = lower; + hf = higherFound, lf = lowerFound; + do { left(i); left(h); left(l); yTest(h,hf); yTest(l,lf); } + while (selectAndProcess(i)); + } + while (deferred(position)); + } + + bool selectAndProcess(Cursor const& i) + { + if (select(i)) + { + process(i); + return true; + } + return false; + } + + void yTest(Cursor const& i, bool& state) + { + if (! select(i)) + state = false; + else if (! state) + { + state = true; + defer(i); + } + } +}; + +template< class Strategy, typename Cursor > +void floodFill(Cursor const& p, Strategy const& s) +{ + floodFill_impl(s).go(p); +} + + +#endif /* defined(__hifi__FloodFill__) */ + diff --git a/shared/src/OctalCode.h b/shared/src/OctalCode.h index a20cc8dfae..d5367fbddf 100644 --- a/shared/src/OctalCode.h +++ b/shared/src/OctalCode.h @@ -9,7 +9,7 @@ #ifndef __hifi__OctalCode__ #define __hifi__OctalCode__ -#include +#include void printOctalCode(unsigned char * octalCode); int bytesRequiredForCodeLength(unsigned char threeBitCodes); diff --git a/shared/src/Radix2InplaceSort.h b/shared/src/Radix2InplaceSort.h new file mode 100644 index 0000000000..cf89f13583 --- /dev/null +++ b/shared/src/Radix2InplaceSort.h @@ -0,0 +1,96 @@ +// +// Radix2InplaceSort.h +// hifi +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__Radix2InplaceSort__ +#define __hifi__Radix2InplaceSort__ + +#include + + +/** + * Sorts the range between two iterators in linear time. + * + * A Radix2Scanner must be provided to decompose the sorting + * criterion into a fixed number of bits. + */ +template< class Radix2Scanner, typename BidiIterator > +void radix2InplaceSort( BidiIterator from, BidiIterator to, + Radix2Scanner const& scanner = Radix2Scanner() ); + + + +template< class Scanner, typename Iterator > +struct radix2InplaceSort_impl : Scanner +{ + radix2InplaceSort_impl(Scanner const& s) : Scanner(s) { } + + using Scanner::advance; + using Scanner::bit; + + void go(Iterator& from, Iterator& to, typename Scanner::state_type s) + { + Iterator l(from), r(to); + unsigned cl, cr; + + using std::swap; + + for (;;) + { + // scan from left for set bit + for (cl = cr = 0u; l != r ; ++l, ++cl) + if (bit(*l, s)) + { + // scan from the right for unset bit + for (++cr; --r != l ;++cr) + if (! bit(*r, s)) + { + // swap, continue scanning from left + swap(*l, *r); + break; + } + if (l == r) + break; + } + + // on to the next digit, if any + if (! advance(s)) + return; + + // recurse into smaller branch and prepare iterative + // processing of the other + if (cl < cr) + { + if (cl > 1u) go(from, l, s); + else if (cr <= 1u) + return; + + l = from = r; + r = to; + } + else + { + if (cr > 1u) go(r, to, s); + else if (cl <= 1u) + return; + + r = to = l; + l = from; + } + } + } +}; + +template< class Radix2Scanner, typename BidiIterator > +void radix2InplaceSort( BidiIterator from, BidiIterator to, + Radix2Scanner const& scanner) +{ + radix2InplaceSort_impl(scanner) + .go(from, to, scanner.initial_state()); +} + +#endif /* defined(__hifi__Radix2InplaceSort__) */ diff --git a/shared/src/Radix2IntegerScanner.h b/shared/src/Radix2IntegerScanner.h new file mode 100644 index 0000000000..c617a1080d --- /dev/null +++ b/shared/src/Radix2IntegerScanner.h @@ -0,0 +1,87 @@ +// +// Radix2IntegerScanner.h +// hifi +// +// Created by Tobias Schwinger on 3/23/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__Radix2IntegerScanner__ +#define __hifi__Radix2IntegerScanner__ + +#include +#include + +namespace type_traits // those are needed for the declaration, see below +{ + // Note: There are better / more generally appicable implementations + // in C++11, make_signed is missing in TR1 too - so I just use C++98 + // hacks that get the job done... + + template< typename T > struct is_signed + { static bool const value = T(-1) < T(0); }; + + template< typename T, size_t S = sizeof(T) > struct make_unsigned; + template< typename T > struct make_unsigned< T, 1 > { typedef uint8_t type; }; + template< typename T > struct make_unsigned< T, 2 > { typedef uint16_t type; }; + template< typename T > struct make_unsigned< T, 4 > { typedef uint32_t type; }; + template< typename T > struct make_unsigned< T, 8 > { typedef uint64_t type; }; +} + + +/** + * Bit decomposition facility for integers. + */ +template< typename T, + bool _Signed = type_traits::is_signed::value > +class Radix2IntegerScanner; + + + +template< typename UInt > +class Radix2IntegerScanner< UInt, false > +{ + UInt valMsb; + public: + + Radix2IntegerScanner() + : valMsb(~UInt(0) &~ (~UInt(0) >> 1)) { } + + explicit Radix2IntegerScanner(int bits) + : valMsb(UInt(1u) << (bits - 1)) + { } + + + typedef UInt state_type; + + state_type initial_state() const { return valMsb; } + bool advance(state_type& s) const { return (s >>= 1) != 0u; } + + bool bit(UInt const& v, state_type const& s) const { return !!(v & s); } +}; + +template< typename Int > +class Radix2IntegerScanner< Int, true > +{ + typename type_traits::make_unsigned::type valMsb; + public: + + Radix2IntegerScanner() + : valMsb(~state_type(0u) &~ (~state_type(0u) >> 1)) + { } + + explicit Radix2IntegerScanner(int bits) + : valMsb(state_type(1u) << (bits - 1)) + { } + + + typedef typename type_traits::make_unsigned::type state_type; + + state_type initial_state() const { return valMsb; } + bool advance(state_type& s) const { return (s >>= 1) != 0u; } + + bool bit(Int const& v, state_type const& s) const { return !!((v-valMsb) & s); } +}; + +#endif /* defined(__hifi__Radix2IntegerScanner__) */ + diff --git a/shared/src/SharedUtil.h b/shared/src/SharedUtil.h index eb09a98436..e81592d5fd 100644 --- a/shared/src/SharedUtil.h +++ b/shared/src/SharedUtil.h @@ -9,8 +9,7 @@ #ifndef __hifi__SharedUtil__ #define __hifi__SharedUtil__ -#include -#include +#include #ifdef _WIN32 #include "Systime.h" diff --git a/shared/src/UrlReader.cpp b/shared/src/UrlReader.cpp new file mode 100644 index 0000000000..ee1f6efc3f --- /dev/null +++ b/shared/src/UrlReader.cpp @@ -0,0 +1,77 @@ +// +// UrlReader.cpp +// hifi +// +// Created by Tobias Schwinger on 3/21/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + + +#include "UrlReader.h" + +#include +#include + +size_t const UrlReader::max_read_ahead = CURL_MAX_WRITE_SIZE; + +char const* const UrlReader::success = "UrlReader: Success!"; +char const* const UrlReader::error_init_failed = "UrlReader: Initialization failed."; +char const* const UrlReader::error_aborted = "UrlReader: Processing error."; +char const* const UrlReader::error_buffer_overflow = "UrlReader: Buffer overflow."; +char const* const UrlReader::error_leftover_input = "UrlReader: Incomplete processing."; + +#define hnd_curl static_cast(_ptrImpl) + +UrlReader::UrlReader() + : _ptrImpl(0l), _arrXtra(0l), _strError(0l) +{ + _arrXtra = new(std::nothrow) char[max_read_ahead]; + if (! _arrXtra) { _strError = error_init_failed; return; } + _ptrImpl = curl_easy_init(); + if (! _ptrImpl) { _strError = error_init_failed; return; } + curl_easy_setopt(hnd_curl, CURLOPT_NOSIGNAL, 1l); + curl_easy_setopt(hnd_curl, CURLOPT_FAILONERROR, 1l); + curl_easy_setopt(hnd_curl, CURLOPT_FILETIME, 1l); +} + +UrlReader::~UrlReader() +{ + delete _arrXtra; + if (! hnd_curl) return; + curl_easy_cleanup(hnd_curl); +} + +bool UrlReader::perform(char const* url, transfer_callback* cb) +{ + curl_easy_setopt(hnd_curl, CURLOPT_URL, url); + curl_easy_setopt(hnd_curl, CURLOPT_WRITEFUNCTION, cb); + curl_easy_setopt(hnd_curl, CURLOPT_WRITEDATA, this); + + CURLcode rc = curl_easy_perform(hnd_curl); + + if (rc == CURLE_OK) + { + while (_valXtraSize > 0 && _strError == success) + cb(0l, 0, 0, this); + } + else if (_strError == success) + _strError = curl_easy_strerror(rc); + + return rc == CURLE_OK; +} + +void UrlReader::getinfo(char const*& url, + char const*& type, int64_t& length, int64_t& stardate) +{ + curl_easy_getinfo(hnd_curl, CURLINFO_EFFECTIVE_URL, & url); + curl_easy_getinfo(hnd_curl, CURLINFO_CONTENT_TYPE, & type); + + double clen; + curl_easy_getinfo(hnd_curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, & clen); + length = static_cast(clen); + + long time; + curl_easy_getinfo(hnd_curl, CURLINFO_FILETIME, & time); + stardate = time; +} + diff --git a/shared/src/UrlReader.h b/shared/src/UrlReader.h new file mode 100644 index 0000000000..3ee4f04241 --- /dev/null +++ b/shared/src/UrlReader.h @@ -0,0 +1,250 @@ +// +// UrlReader.h +// hifi +// +// Created by Tobias Schwinger on 3/21/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__UrlReader__ +#define __hifi__UrlReader__ + +#include +#include +#include + +/** + * UrlReader class that encapsulates a context for sequential data retrieval + * via URLs. Use one per thread. + */ +class UrlReader +{ + void* _ptrImpl; + char* _arrXtra; + char const* _strError; + void* _ptrStream; + size_t _valXtraSize; + + public: + + /** + * Constructor - performs initialization, never throws. + */ + UrlReader(); + + /** + * Destructor - frees resources, never throws. + */ + ~UrlReader(); + + /** + * Reads data from an URL and forwards it to the instance of a class + * fulfilling the ContentStream concept. + * + * The call protocol on the ContentStream is detailed as follows: + * + * 1. begin(char const* url, + * char const* content_type, uint64_t bytes, uint64_t stardate) + * + * All information except 'url' is optional; 'content_type' can + * be a null pointer - 'bytes' and 'stardate' can be equal to + * to 'unavailable'. + * + * 2. transfer(char* buffer, size_t bytes) + * + * Called until all data has been received. The number of bytes + * actually processed should be returned. + * Unprocessed data is stored in an extra buffer whose size is + * given by the constant UrlReader::max_read_ahead - it can be + * assumed to be reasonably large for on-the-fly parsing. + * + * 3. end(bool ok) + * + * Called at the end of the transfer. + * + * Returns the same success code + */ + template< class ContentStream > + bool readUrl(char const* url, ContentStream& s); + + /** + * Returns a pointer to a static C-string that describes the error + * condition. + */ + inline char const* getError() const; + + /** + * Can be called by the stream to set a user-defined error string. + */ + inline void setError(char const* static_c_string); + + /** + * Pointer to the C-string returned by a call to 'readUrl' when no + * error occurred. + */ + static char const* const success; + + /** + * Pointer to the C-string returned by a call to 'readUrl' when the + * initialization has failed. + */ + static char const* const error_init_failed; + + /** + * Pointer to the C-string returned by a call to 'readUrl' when the + * transfer has been aborted by the client. + */ + static char const* const error_aborted; + + /** + * Pointer to the C-string returned by a call to 'readUrl' when + * leftover input from incomplete processing caused a buffer + * overflow. + */ + static char const* const error_buffer_overflow; + + /** + * Pointer to the C-string return by a call to 'readUrl' when the + * input provided was not completely consumed. + */ + static char const* const error_leftover_input; + + /** + * Constant of the maximum number of bytes that are buffered + * between invocations of 'transfer'. + */ + static size_t const max_read_ahead; + + /** + * Constant representing absent information in the call to the + * 'begin' member function of the target stream. + */ + static int const unavailable = -1; + + /** + * Constant for requesting to abort the current transfer when + * returned by the 'transfer' member function of the target stream. + */ + static size_t const abort = ~0u; + + private: + // instances of this class shall not be copied + UrlReader(UrlReader const&); // = delete; + UrlReader& operator=(UrlReader const&); // = delete; + + // entrypoints to compiled code + + typedef size_t transfer_callback(char*, size_t, size_t, void*); + + bool perform(char const* url, transfer_callback* transfer); + + void getinfo(char const*& url, + char const*& type, int64_t& length, int64_t& stardate); + + // synthesized callback + + template< class Stream > + static size_t callback_template( + char *input, size_t size, size_t nmemb, void* thiz); +}; + +template< class ContentStream > +bool UrlReader::readUrl(char const* url, ContentStream& s) +{ + if (! _ptrImpl) return false; + _strError = success; + _ptrStream = & s; + _valXtraSize = ~size_t(0); + this->perform(url, & callback_template); + s.end(_strError == success); + return _strError == success; +} + +inline char const* UrlReader::getError() const { return this->_strError; } + +inline void UrlReader::setError(char const* static_c_string) +{ + if (this->_strError == success) + this->_strError = static_c_string; +} + +template< class Stream > +size_t UrlReader::callback_template( + char *input, size_t size, size_t nmemb, void* thiz) +{ + size *= nmemb; + + UrlReader* me = static_cast(thiz); + Stream* stream = static_cast(me->_ptrStream); + + // first call? + if (me->_valXtraSize == ~size_t(0)) + { + me->_valXtraSize = 0u; + // extract meta information and call 'begin' + char const* url, * type; + int64_t length, stardate; + me->getinfo(url, type, length, stardate); + stream->begin(url, type, length, stardate); + } + + size_t input_offset = 0u; + + for (;;) + { + char* buffer = input + input_offset; + size_t bytes = size - input_offset; + + // data in extra buffer? + if (me->_valXtraSize > 0) + { + // fill extra buffer with beginning of input + size_t fill = max_read_ahead - me->_valXtraSize; + if (bytes < fill) fill = bytes; + memcpy(me->_arrXtra + me->_valXtraSize, buffer, fill); + // use extra buffer for next transfer + buffer = me->_arrXtra; + bytes = me->_valXtraSize + fill; + input_offset += fill; + } + + // call 'transfer' + size_t processed = stream->transfer(buffer, bytes); + if (processed == abort) + { + me->setError(error_aborted); + return 0u; + } + else if (! processed && ! input) + { + me->setError(error_leftover_input); + return 0u; + } + size_t unprocessed = bytes - processed; + + // can switch to input buffer, now? + if (buffer == me->_arrXtra && unprocessed <= input_offset) + { + me->_valXtraSize = 0u; + input_offset -= unprocessed; + } + else // no? unprocessed data -> extra buffer + { + if (unprocessed > max_read_ahead) + { + me->setError(error_buffer_overflow); + return 0; + } + me->_valXtraSize = unprocessed; + memmove(me->_arrXtra, buffer + processed, unprocessed); + + if (input_offset == size || buffer != me->_arrXtra) + { + return size; + } + } + } // for +} + +#endif /* defined(__hifi__UrlReader__) */ + diff --git a/shared/src/VoxelTree.cpp b/shared/src/VoxelTree.cpp index c9136e9d33..e9c8c67c0b 100644 --- a/shared/src/VoxelTree.cpp +++ b/shared/src/VoxelTree.cpp @@ -7,6 +7,7 @@ // #include +#include #include #include "SharedUtil.h" #include "OctalCode.h" @@ -14,6 +15,7 @@ #include // to load voxels from file #include // to load voxels from file + int boundaryDistanceForRenderLevel(unsigned int renderLevel) { switch (renderLevel) { case 1: @@ -612,4 +614,4 @@ void VoxelTree::createSphere(float r,float xc, float yc, float zc, float s, bool } } this->reaverageVoxelColors(this->rootNode); -} \ No newline at end of file +} diff --git a/shared/src/VoxelTree.h b/shared/src/VoxelTree.h index 726aa9ab9b..8a998a73bf 100644 --- a/shared/src/VoxelTree.h +++ b/shared/src/VoxelTree.h @@ -9,7 +9,6 @@ #ifndef __hifi__VoxelTree__ #define __hifi__VoxelTree__ -#include #include "VoxelNode.h" #include "MarkerNode.h"