From 4444bcf26efbdda5aff7dbe00bc4a3c7e5fce459 Mon Sep 17 00:00:00 2001 From: tosh Date: Sun, 24 Mar 2013 05:50:07 +0100 Subject: [PATCH] starfield and numerous utility components, initial checkin --- interface/resources/gen_stars.py | 20 + interface/src/FieldOfView.cpp | 130 ++++++ interface/src/FieldOfView.h | 115 +++++ interface/src/Stars.cpp | 679 ++++++++++++++++++++++++++++++ interface/src/Stars.h | 76 ++++ shared/src/AngleUtils.h | 82 ++++ shared/src/Radix2InplaceSort.h | 92 ++++ shared/src/Radix2IntegerScanner.h | 82 ++++ shared/src/UrlReader.cpp | 77 ++++ shared/src/UrlReader.h | 250 +++++++++++ 10 files changed, 1603 insertions(+) create mode 100644 interface/resources/gen_stars.py create mode 100644 interface/src/FieldOfView.cpp create mode 100644 interface/src/FieldOfView.h create mode 100644 interface/src/Stars.cpp create mode 100644 interface/src/Stars.h create mode 100644 shared/src/AngleUtils.h create mode 100644 shared/src/Radix2InplaceSort.h create mode 100644 shared/src/Radix2IntegerScanner.h create mode 100644 shared/src/UrlReader.cpp create mode 100644 shared/src/UrlReader.h diff --git a/interface/resources/gen_stars.py b/interface/resources/gen_stars.py new file mode 100644 index 0000000000..50dc23668e --- /dev/null +++ b/interface/resources/gen_stars.py @@ -0,0 +1,20 @@ + +from random import random,randint +from sys import argv + +n = 1000 + +if len(argv) > 1: + n = int(argv[1]) + +for i in range(n): + # 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 + azi = random() * 360 + alt = random() * 90 + print "%f %f #%02x%02x%02x" % (azi,alt,r,g,b) + diff --git a/interface/src/FieldOfView.cpp b/interface/src/FieldOfView.cpp new file mode 100644 index 0000000000..5b7ae3ee8e --- /dev/null +++ b/interface/src/FieldOfView.cpp @@ -0,0 +1,130 @@ +// +// 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_frustum_low(vec3(-1.0f,-1.0f,-1.0f)), + vec_frustum_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; + calcGlFrustum(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_frustum_high.z) ); +} + +mat4 FieldOfView::getWorldScreenXform() const +{ + return translate( + getViewerScreenXform() * affineInverse(mat_orientation), + vec3(0.0f, 0.0f, -vec_frustum_high.z) ); +} + +mat4 FieldOfView::getViewerWorldXform() const +{ + vec3 n_translate = vec3(0.0f, 0.0f, vec_frustum_high.z); + + return translate( + translate(mat4(1.0f), n_translate) + * mat_orientation, -n_translate ); +} + +float FieldOfView::getPixelSize() const +{ + vec3 low, high; + calcGlFrustum(low, high); + + return std::min( + abs(high.x - low.x) / val_width, + abs(high.y - low.y) / val_height); +} + +void FieldOfView::calcGlFrustum(vec3& low, vec3& high) const +{ + low = vec_frustum_low; + high = vec_frustum_high; + + // apply zoom + float inv_zoom = 1.0f / val_zoom; + float ax = (low.x + high.x) / 2.0f, ay = (low.y + high.y) / 2.0f; + low.x = (low.x - ax) * inv_zoom + ax; + high.x = (high.x - ax) * inv_zoom + ax; + low.y = (low.y - ay) * inv_zoom + ay; + high.y = (high.y - ay) * inv_zoom + ay; + low.z = (low.z - high.z) * inv_zoom + high.z; + + // 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; + + float adj; + 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 = vp_aspect / f_aspect; + low.x *= adj; + high.x *= adj; + } + else + { + // expose_more -> f_aspect > vp_aspect <=> adj > 1 + // expose_less -> f_aspect <= vp_aspect <=> adj <= 1 + adj = f_aspect / vp_aspect; + low.y *= adj; + high.y *= adj; + } + } + + 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..15ff0de4a9 --- /dev/null +++ b/interface/src/FieldOfView.h @@ -0,0 +1,115 @@ +// +// FieldOfView.cpp +// 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_frustum_low; + glm::vec3 vec_frustum_high; + float val_width; + float val_height; + float val_angle; + float val_zoom; + int enm_aspect_balancing; + public: + + FieldOfView(); + + // mutators + + FieldOfView& setFrustum(glm::vec3 const& low, glm::vec3 const& high) + { vec_frustum_low = low; vec_frustum_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::vec3 const& getFrustumLow() const { return vec_frustum_low; } + glm::vec3 const& getFrustumHigh() const { return vec_frustum_high; } + 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; + + private: + + void calcGlFrustum(glm::vec3& low, glm::vec3& high) const; +}; + +#endif + diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp new file mode 100644 index 0000000000..a05bab5a8f --- /dev/null +++ b/interface/src/Stars.cpp @@ -0,0 +1,679 @@ +// +// Stars.cpp +// interface +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include "InterfaceConfig.h" + +#include "Stars.h" +#include "UrlReader.h" +#include "FieldOfView.h" +#include "AngleUtils.h" +#include "Radix2InplaceSort.h" +#include "Radix2IntegerScanner.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#define DEG2RAD 0.017453292519f + +/* Data pipeline + * ------------- + * + * ->> readInput -(load)--+---- (get brightness & sort) ---> brightness LUT + * | | + * ->> setResolution --+ | >extractBrightnessLevels< + * V | + * (sort by (tile,brightness)) + * | | + * ->> setLOD ---+ | >retile< ->> setLOD --> (just parameterize + * V V renderer when on-GPU + * (filter by max-LOD brightness, data suffices) + * build tile info for rendering) + * | | + * V >recreateRenderer< + * (set new renderer)/ + * + * + * (process), ->> entry point, ---> data flow, >internal routine< + * + * + * Open issues + * ----------- + * + * o FOV culling is too eager - gotta revisit + * o LOD adjustment in a living renderer still needs to be coded (planned) + * o input limit (while keeping the brightest) needs to be coded (planned) + * 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 + */ + +namespace +{ + using std::swap; + using std::min; + using std::max; + + using glm::mat4; + using glm::value_ptr; + + class InputVertex + { + unsigned val_color; + float val_azimuth; + float val_altitude; + public: + + InputVertex(float azimuth, float altitude, unsigned color) + { + val_color = color >> 16 & 0xffu | color & 0xff00u | + color << 16 & 0xff0000u | 0xff000000u; + + angleHorizontalPolar(azimuth, altitude); + + val_azimuth = azimuth; + val_altitude = altitude; + } + + float getAzimuth() const { return val_azimuth; } + float getAltitude() const { return val_altitude; } + unsigned getColor() const { return val_color; } + }; + + typedef std::vector InputVertices; + + class Loader : UrlReader + { + InputVertices* ptr_vertices; + unsigned val_limit; + + unsigned val_lineno; + char const* str_actual_url; + public: + + bool loadVertices( + InputVertices& destination, char const* url, unsigned limit) + { + ptr_vertices = & destination; + val_limit = limit; + str_actual_url = url; // in case we fail early + + if (! UrlReader::readUrl(url, *this)) + { + fprintf(stderr, "%s:%d: %s\n", + str_actual_url, val_lineno, getError()); + + return false; + } + return true; + } + + protected: + + friend class UrlReader; + + void begin(char const* url, + char const* type, int64_t size, int64_t stardate) + { + val_lineno = 0u; + str_actual_url = url; // new value in http redirect + + ptr_vertices->clear(); + ptr_vertices->reserve(val_limit); + } + + 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; + ++val_lineno; + 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 (ptr_vertices->size() < val_limit) + ptr_vertices->push_back( InputVertex(azi, alt, c) ); + + // TODO handle limit by switching to a minheap when + // buffer is full + } + else + { + fprintf(stderr, "Stars.cpp:%d: Bad input from %s\n", + val_lineno, str_actual_url); + } + + } + return consumed; + } + + void end(bool ok) + { + } + }; + + typedef uint16_t BrightnessLevel; + typedef std::vector BrightnessLevels; + const unsigned BrightnessBits = 16u; + + template< class Vertex > + BrightnessLevel getBrightness(Vertex const& v) + { + unsigned c = v.getColor(); + unsigned r = (c >> 16) & 0xff; + unsigned g = (c >> 8) & 0xff; + unsigned b = c & 0xff; + return BrightnessLevel((r*r+g*g+b*b) >> 1); + } + + struct BrightnessSortScanner : Radix2IntegerScanner + { + typedef Radix2IntegerScanner Base; + BrightnessSortScanner() : Base(BrightnessBits) { } + bool bit(BrightnessLevel const& k, state_type& s) + { return ! Base::bit(k,s); } + }; + + 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) ); + + radix2InplaceSort(dst.begin(), dst.end(), BrightnessSortScanner()); + } + + + template< class Unit > + class HorizontalTiling + { + unsigned val_k; + float val_rcp_slice; + unsigned val_bits; + public: + + HorizontalTiling(unsigned k) + : val_k(k), val_rcp_slice(k / Unit::twice_pi()) + { + val_bits = ceil(log2(getTileCount() )); + } + + unsigned getAzimuthalTiles() const { return val_k; } + unsigned getAltitudinalTiles() const { return val_k / 2 + 1; } + unsigned getTileIndexBits() const { return val_bits; } + + unsigned getTileCount() const + { + return getAzimuthalTiles() * getAltitudinalTiles(); + } + + unsigned getTileIndex(float azimuth, float altitude) const + { + unsigned result; + return discreteAngle(azimuth) % val_k + + discreteAngle(altitude + Unit::half_pi()) * val_k; + } + + unsigned getTileIndex(InputVertex const& v) const + { + return getTileIndex(v.getAzimuth(), v.getAltitude()); + } + + unsigned discreteAngle(float unsigned_angle) const + { + return unsigned(round(unsigned_angle * val_rcp_slice)); + } + }; + + class TileSortScanner : public Radix2IntegerScanner + { + HorizontalTiling obj_tiling; + + typedef Radix2IntegerScanner Base; + public: + + explicit TileSortScanner(HorizontalTiling const& tiling) + : Base(tiling.getTileIndexBits() + BrightnessBits), + obj_tiling(tiling) + { } + + bool bit(InputVertex const& v, state_type const& s) const + { + // inspect (tile_index, brightness) tuples + unsigned key = getBrightness(v); + key |= obj_tiling.getTileIndex(v) << BrightnessBits; + return Base::bit(key, s); + } + }; + + struct Tile + { + uint16_t offset; + uint16_t count; // according to previous lod setting + }; + + + struct GpuVertex + { + unsigned val_color; + float val_x; + float val_y; + float val_z; + // + + GpuVertex() { } + + GpuVertex(InputVertex const& in) + { + val_color = in.getColor(); + float azimuth = in.getAzimuth() * DEG2RAD; + float altitude = in.getAltitude() * DEG2RAD; + + // ground vector in x/z plane... + float gx = sin(azimuth); + float gz = -cos(azimuth); + + // ...elevated in y direction by altitude + float exz = cos(altitude); + val_x = gx * exz; + val_y = sin(altitude); + val_z = gz * exz; + +//fprintf(stderr, "Stars.cpp: GpuVertex created (%x,%f,%f,%f)\n", val_color, val_x, val_y, val_z); + } + + unsigned getColor() const { return val_color; } + }; + + class Renderer + { + GpuVertex* ptr_data; + Tile* ptr_tiles; + BrightnessLevel val_brightness; + unsigned val_tile_resolution; + GLint* ptr_batch_offs; + GLsizei* ptr_batch_count; + GLuint hnd_vao; + public: + + Renderer(InputVertices const& src, size_t n, + unsigned k, BrightnessLevel b, BrightnessLevel b_max) + : ptr_data(0l), ptr_tiles(0l), + val_brightness(b), val_tile_resolution(k) + { + + HorizontalTiling tiling(k); + size_t n_tiles = tiling.getTileCount(); + + ptr_data = new GpuVertex[n]; + ptr_tiles = new Tile[n_tiles]; + // TODO tighten bounds and save some memory + ptr_batch_offs = new GLint[n_tiles]; + ptr_batch_count = new GLsizei[n_tiles]; + + size_t vertex_index = 0u, curr_tile_index = 0u, count_active = 0u; + + for (InputVertices::const_iterator i = + src.begin(), e = src.end(); i != e; ++i) + { + BrightnessLevel bv = getBrightness(*i); + // filter by alloc brightness + if (bv >= b_max) + { + size_t tile_index = tiling.getTileIndex(*i); + + assert(tile_index >= curr_tile_index); + + // moved to another tile? + if (tile_index != curr_tile_index) + { + Tile* t = ptr_tiles + curr_tile_index; + Tile* t_last = ptr_tiles + tile_index; + + // set count of active vertices (upcoming lod) + t->count = count_active; + + // generate skipped entries + for(size_t offs = t_last->offset; ++t != t_last ;) + t->offset = offs, t->count = 0u; + + // set offset of the beginning tile` + t_last->offset = vertex_index; + + curr_tile_index = tile_index; + count_active = 0u; + } + + if (bv >= b) + ++count_active; + +//fprintf(stderr, "Stars.cpp: Vertex %d on tile #%d\n", vertex_index, tile_index); + + // write converted vertex + ptr_data[vertex_index++] = *i; + } + } + assert(vertex_index == n); + // finish last tile (see above) + Tile* t = ptr_tiles + curr_tile_index; + Tile* t_last = ptr_tiles + n_tiles; + t->count = count_active; + for(; ++t != t_last ;) + t->offset = vertex_index, t->count = 0u; + + // OpenGL upload + + GLuint vbo; + glGenBuffers(1, & vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, + n * sizeof(GpuVertex), ptr_data, GL_STATIC_DRAW); + + glGenVertexArrays(1, & hnd_vao); + glBindVertexArray(hnd_vao); + glInterleavedArrays(GL_C4UB_V3F, sizeof(GpuVertex), 0l); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + + glBindVertexArray(0); + + } + + ~Renderer() + { + delete[] ptr_data; + delete[] ptr_tiles; + delete[] ptr_batch_count; + delete[] ptr_batch_offs; + glDeleteVertexArrays(1, & hnd_vao); + } + + void render(FieldOfView fov, BrightnessLevel lod) + { + mat4 local_space = fov.getOrientation(); + HorizontalTiling tiling(val_tile_resolution); + + // get z direction + float x = local_space[2][0]; + float y = local_space[2][1]; + float z = local_space[2][2]; + + // to polar + float azimuth = atan2(x,-z) + Radians::pi(); + float altitude = atan2(y, sqrt(x*x+z*z)); + +fprintf(stderr, "Stars.cpp: viewer azimuth = %f, altitude = %f\n", azimuth, altitude); + + // half diagonal perspective angle + float hd_pers = fov.getPerspective() * 0.5f; + + unsigned azi_dim = tiling.getAzimuthalTiles(); + unsigned alt_dim = tiling.getAltitudinalTiles(); + + // determine tile range in azimuthal direction (modulated) + unsigned azi_from = tiling.discreteAngle( + angleUnsignedNormal(azimuth - hd_pers) ) % azi_dim; + unsigned azi_to = (1 + tiling.discreteAngle( + angleUnsignedNormal(azimuth + hd_pers) )) % azi_dim; + + // determine tile range in altitudinal direction (clamped) + unsigned alt_from = tiling.discreteAngle( + max(-Radians::half_pi(),min(Radians::half_pi(), + altitude - hd_pers)) + Radians::half_pi() ); + unsigned alt_to = tiling.discreteAngle( + max(-Radians::half_pi(),min(Radians::half_pi(), + altitude + hd_pers)) + Radians::half_pi() ); + + // iterate the grid... + unsigned n_batches = 0u; + +fprintf(stderr, "Stars.cpp: grid dimensions: %d x %d\n", azi_dim, alt_dim); +fprintf(stderr, "Stars.cpp: grid range: [%d;%d) [%d;%d]\n", azi_from, azi_to, alt_from, alt_to); + + GLint* offs = ptr_batch_offs, * count = ptr_batch_count; + for (unsigned alt = alt_from; alt <= alt_to; ++alt) + { + for (unsigned azi = azi_from; + azi != azi_to; azi = (azi + 1) % azi_dim) + { + unsigned tile_index = azi + alt * azi_dim; + Tile& t = ptr_tiles[tile_index]; + + // TODO handle LOD changes by performing a binary + // search for the new brightness, if any + + if (! t.count) + continue; + +fprintf(stderr, "Stars.cpp: tile %d selected (%d vertices at offset %d)\n", tile_index, t.count, t.offset); + + *offs++ = t.offset; + *count++ = t.count; + + ++n_batches; + } + } + +fprintf(stderr, "Stars.cpp: rendering %d-multibatch\n", n_batches); + + // cancel translation + local_space[3][0] = 0.0f; + local_space[3][1] = 0.0f; + local_space[3][2] = 0.0f; + + // and setup modelview matrix + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadMatrixf(glm::value_ptr( + fov.setOrientation(local_space).getWorldViewerXform())); + + // render + glBindVertexArray(hnd_vao); + glPointSize(1.0); + glMultiDrawArrays(GL_POINTS, + ptr_batch_offs, ptr_batch_count, n_batches); + + // restore state state + glBindVertexArray(0); + glPopMatrix(); + } + + }; +} + +struct Stars::body +{ + InputVertices vec_input; + unsigned val_tile_resolution; + + BrightnessLevels vec_lod_brightness; + BrightnessLevel val_lod_brightness; + BrightnessLevel val_lod_max_brightness; + float val_lod_current_alloc; + float val_lod_low_water_mark; + float val_lod_high_water_mark; + + Renderer* ptr_renderer; + + + body() + : val_tile_resolution(12), val_lod_brightness(0), + val_lod_max_brightness(0), val_lod_current_alloc(1.0f), + val_lod_low_water_mark(0.99f), val_lod_high_water_mark(1.0f), + ptr_renderer(0l) + { } + + bool readInput(const char* url, unsigned limit) + { + InputVertices new_vertices; + + if (! Loader().loadVertices(new_vertices, url, limit)) + return false; + + BrightnessLevels new_brightness; + extractBrightnessLevels(new_brightness, new_vertices); + + { + // TODO input mutex + + vec_input.swap(new_vertices); + + try + { + retile(val_tile_resolution); + } + catch (...) + { + // rollback transaction + new_vertices.swap(vec_input); + throw; + } + + { + // TODO lod mutex + vec_lod_brightness.swap(new_brightness); + } + } + new_vertices.clear(); + new_brightness.clear(); + + return true; + } + + void setResolution(unsigned k) + { + if (k != val_tile_resolution) + { + // TODO input mutex + retile(k); + } + } + + void retile(unsigned k) + { + HorizontalTiling tiling(k); + TileSortScanner scanner(tiling); + radix2InplaceSort(vec_input.begin(), vec_input.end(), scanner); + + recreateRenderer(vec_input.size(), k, + val_lod_brightness, val_lod_max_brightness); + + val_tile_resolution = k; + } + + void setLOD(float fraction, float overalloc, float realloc) + { + assert(fraction >= 0.0f && fraction <= 0.0f); + assert(overalloc >= realloc && realloc >= 0.0f); + assert(overalloc <= 1.0f && realloc <= 1.0f); + + float lwm, hwm; + float oa_fraction = min(fraction * (1.0f + oa_fraction), 1.0f); + size_t oa_new_size; + BrightnessLevel b, b_max; + { + // TODO lod mutex + // Or... There is just one write access, here - so LOD state + // could be CMPed as well... + lwm = val_lod_low_water_mark; + hwm = val_lod_high_water_mark; + size_t last = vec_lod_brightness.size() - 1; + val_lod_brightness = b = + vec_lod_brightness[ size_t(fraction * last) ]; + oa_new_size = size_t(oa_fraction * last); + b_max = vec_lod_brightness[oa_new_size++]; + } + + // have to reallocate? + if (fraction < lwm || fraction > hwm) + { + // TODO input mutex + recreateRenderer(oa_new_size, val_tile_resolution, b, b_max); + + { + // TODO lod mutex + val_lod_current_alloc = fraction; + val_lod_low_water_mark = fraction * (1.0f - realloc); + val_lod_high_water_mark = fraction * (1.0f + realloc); + val_lod_max_brightness = b_max; + } + } + } + + void recreateRenderer( + size_t n, unsigned k, BrightnessLevel b, BrightnessLevel b_max) + { + Renderer* renderer = new Renderer(vec_input, n, k, b, b_max); + swap(ptr_renderer, renderer); // TODO make atomic + delete renderer; // will be NULL when was in use + } + + void render(FieldOfView const& fov) + { + // check out renderer + Renderer* renderer = 0l; + swap(ptr_renderer, renderer); // TODO make atomic + float new_brightness = val_lod_brightness; // make atomic + + // have it render + renderer->render(fov, new_brightness); + + // check in - or dispose if there is a new one + // TODO make atomic (CAS) + if (! ptr_renderer) + ptr_renderer = renderer; + else delete renderer; + } +}; + +Stars::Stars() : ptr_body(0l) { ptr_body = new body; } +Stars::~Stars() { delete ptr_body; } + +bool Stars::readInput(const char* url, unsigned limit) +{ return ptr_body->readInput(url, limit); } + +void Stars::setResolution(unsigned k) +{ ptr_body->setResolution(k); } + +void Stars::setLOD(float fraction, float overalloc, float realloc) +{ ptr_body->setLOD(fraction, 0.0f, 0.0f); } // TODO enable once implemented + +void Stars::render(FieldOfView const& fov) +{ ptr_body->render(fov); } + + diff --git a/interface/src/Stars.h b/interface/src/Stars.h new file mode 100644 index 0000000000..291deaf7b9 --- /dev/null +++ b/interface/src/Stars.h @@ -0,0 +1,76 @@ +// +// 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" + +/** + * Starfield rendering component. + */ +class Stars +{ + struct body; + body* ptr_body; + + 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 = 50000); + + /** + * 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. + */ + void setResolution(unsigned k); + + /** + * Allows to reduce the number of stars to be rendered given a + * fractional LOD value. 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'. + */ + void setLOD(float fraction, + 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/shared/src/AngleUtils.h b/shared/src/AngleUtils.h new file mode 100644 index 0000000000..5e5a0aa2ed --- /dev/null +++ b/shared/src/AngleUtils.h @@ -0,0 +1,82 @@ +// +// 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 twice_pi() { return 360.0f; } + static float half_pi() { return 90.0f; } +}; + +struct Radians +{ + static float pi() { return 3.141592653589793f; } + static float twice_pi() { return 6.283185307179586f; } + static float half_pi() { return 1.5707963267948966; } +}; + +struct Rotations +{ + static float pi() { return 0.5f; } + static float twice_pi() { return 1.0f; } + static float half_pi() { return 0.25f; } +}; + +/** + * Clamps an angle to the range of [-180; 180) degrees. + */ +template< class Unit > +float angleSignedNormal(float a) +{ + float result = remainder(a, Unit::twice_pi()); + 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::half_pi()) + { + altitude = Unit::pi() - altitude; + azimuth = -azimuth; + } + else if (altitude < -Unit::half_pi()) + { + altitude = -Unit::pi() - altitude; + azimuth = -azimuth; + } + azimuth = angleUnsignedNormal(azimuth); +} + +#endif + diff --git a/shared/src/Radix2InplaceSort.h b/shared/src/Radix2InplaceSort.h new file mode 100644 index 0000000000..08b4bc6905 --- /dev/null +++ b/shared/src/Radix2InplaceSort.h @@ -0,0 +1,92 @@ +// +// 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 S, typename I > struct radix2InplaceSort_impl : private S +{ + radix2InplaceSort_impl(S const& scanner) : S(scanner) { } + + void go(I& from, I& to, typename S::state_type s) + { + I 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 (S::bit(*l, s)) + { + // scan from the right for unset bit + for (++cr; --r != l ;++cr) + if (! S::bit(*r, s)) + { + // swap, continue scanning from left + swap(*l, *r); + break; + } + if (l == r) + break; + } + + // on to the next digit, if any + if (! S::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 = Radix2Scanner() ) +{ + 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..32cc03d1e7 --- /dev/null +++ b/shared/src/Radix2IntegerScanner.h @@ -0,0 +1,82 @@ +// +// 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 msb; + public: + + Radix2IntegerScanner() + : msb(~UInt(0) &~ (~UInt(0) >> 1)) { } + + explicit Radix2IntegerScanner(int bits) : msb(1u << (bits - 1)) { } + + + typedef UInt state_type; + + state_type initial_state() const { return msb; } + 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 msb; + public: + + Radix2IntegerScanner() + : msb(~state_type(0) &~ (~state_type(0) >> 1)) { } + + explicit Radix2IntegerScanner(int bits) : msb(1u << (bits - 1)) { } + + + typedef typename type_traits::make_unsigned::type state_type; + + state_type initial_state() const { return msb; } + bool advance(state_type& s) const { return (s >>= 1) != 0u; } + + bool bit(Int const& v, state_type const& s) const { return !!((v-msb) & s); } +}; + +#endif /* defined(__hifi__Radix2IntegerScanner__) */ + diff --git a/shared/src/UrlReader.cpp b/shared/src/UrlReader.cpp new file mode 100644 index 0000000000..5bd82fe4d1 --- /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(ptr_impl) + +UrlReader::UrlReader() + : ptr_impl(0l), ptr_ra(0l), str_error(0l) +{ + ptr_ra = new(std::nothrow) char[max_read_ahead]; + if (! ptr_ra) { str_error = error_init_failed; return; } + ptr_impl = curl_easy_init(); + if (! ptr_impl) { str_error = 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 ptr_ra; + 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 (val_ra_size > 0 && str_error == success) + cb(0l, 0, 0, this); + } + else if (str_error != success) + str_error = 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..dcd5ff3278 --- /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* ptr_impl; + char* ptr_ra; + char const* str_error; + void* ptr_stream; + size_t val_ra_size; + + 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 (! ptr_impl) return false; + str_error = success; + ptr_stream = & s; + val_ra_size = ~size_t(0); + this->perform(url, & callback_template); + s.end(str_error == success); + return str_error == success; +} + +inline char const* UrlReader::getError() const { return this->str_error; } + +inline void UrlReader::setError(char const* static_c_string) +{ + if (this->str_error != success) + this->str_error = 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->ptr_stream); + + // first call? + if (me->val_ra_size == ~size_t(0)) + { + me->val_ra_size = 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->val_ra_size > 0) + { + // fill extra buffer with beginning of input + size_t fill = max_read_ahead - me->val_ra_size; + if (bytes < fill) fill = bytes; + memcpy(me->ptr_ra + me->val_ra_size, buffer, fill); + // use extra buffer for next transfer + buffer = me->ptr_ra; + bytes = me->val_ra_size + 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->ptr_ra && unprocessed <= input_offset) + { + me->val_ra_size = 0u; + input_offset -= unprocessed; + } + else // no? unprocessed data -> extra buffer + { + if (unprocessed > max_read_ahead) + { + me->setError(error_buffer_overflow); + return 0; + } + me->val_ra_size = unprocessed; + memmove(me->ptr_ra, buffer + processed, unprocessed); + + if (input_offset == size || buffer != me->ptr_ra) + { + return size; + } + } + } // for +} + +#endif /* defined(__hifi__UrlReader__) */ +