Merge branch 'master' of https://github.com/worklist/hifi into ossome

Conflicts:
	interface/src/renderer/FBXReader.cpp
This commit is contained in:
Andrzej Kapolka 2013-10-18 14:30:50 -07:00
commit 591cf98821
31 changed files with 952 additions and 1346 deletions

View file

@ -36,4 +36,4 @@ private:
std::vector<AudioInjector*> _audioInjectors;
};
#endif /* defined(__hifi__Operative__) */
#endif /* defined(__hifi__Agent__) */

View file

@ -81,6 +81,9 @@ void AudioMixer::run() {
NodeList *nodeList = NodeList::getInstance();
nodeList->setOwnerType(NODE_TYPE_AUDIO_MIXER);
const char AUDIO_MIXER_NODE_TYPES_OF_INTEREST[2] = { NODE_TYPE_AGENT, NODE_TYPE_AUDIO_INJECTOR };
nodeList->setNodeTypesOfInterest(AUDIO_MIXER_NODE_TYPES_OF_INTEREST, sizeof(AUDIO_MIXER_NODE_TYPES_OF_INTEREST));
ssize_t receivedBytes = 0;
nodeList->linkedDataCreateCallback = attachNewBufferToNode;
@ -144,6 +147,9 @@ void AudioMixer::run() {
}
}
// get the NodeList to ping any inactive nodes, for hole punching
nodeList->possiblyPingInactiveNodes();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
PositionalAudioRingBuffer* positionalRingBuffer = (PositionalAudioRingBuffer*) node->getLinkedData();
if (positionalRingBuffer && positionalRingBuffer->shouldBeAddedToMix(JITTER_BUFFER_SAMPLES)) {
@ -157,7 +163,7 @@ void AudioMixer::run() {
const int PHASE_DELAY_AT_90 = 20;
if (node->getType() == NODE_TYPE_AGENT) {
if (node->getType() == NODE_TYPE_AGENT && node->getActiveSocket() && node->getLinkedData()) {
AvatarAudioRingBuffer* nodeRingBuffer = (AvatarAudioRingBuffer*) node->getLinkedData();
// zero out the client mix for this node
@ -165,7 +171,8 @@ void AudioMixer::run() {
// loop through all other nodes that have sufficient audio to mix
for (NodeList::iterator otherNode = nodeList->begin(); otherNode != nodeList->end(); otherNode++) {
if (((PositionalAudioRingBuffer*) otherNode->getLinkedData())->willBeAddedToMix()
if (otherNode->getLinkedData()
&& ((PositionalAudioRingBuffer*) otherNode->getLinkedData())->willBeAddedToMix()
&& (otherNode != node || (otherNode == node && nodeRingBuffer->shouldLoopbackForNode()))) {
PositionalAudioRingBuffer* otherNodeBuffer = (PositionalAudioRingBuffer*) otherNode->getLinkedData();
// based on our listen mode we will do this mixing...
@ -333,7 +340,7 @@ void AudioMixer::run() {
}
memcpy(clientPacket + numBytesPacketHeader, clientSamples, sizeof(clientSamples));
nodeList->getNodeSocket()->send(node->getPublicSocket(), clientPacket, sizeof(clientPacket));
nodeList->getNodeSocket()->send(node->getActiveSocket(), clientPacket, sizeof(clientPacket));
}
}
@ -353,39 +360,24 @@ void AudioMixer::run() {
// pull any new audio data from nodes off of the network stack
while (nodeList->getNodeSocket()->receive(nodeAddress, packetData, &receivedBytes) &&
packetVersionMatch(packetData)) {
if (packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO ||
packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO) {
if (packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO
|| packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO
|| packetData[0] == PACKET_TYPE_INJECT_AUDIO) {
unsigned char* currentBuffer = packetData + numBytesForPacketHeader(packetData);
QUuid nodeUUID = QUuid::fromRfc4122(QByteArray((char*) currentBuffer, NUM_BYTES_RFC4122_UUID));
Node* avatarNode = nodeList->addOrUpdateNode(nodeUUID,
NODE_TYPE_AGENT,
nodeAddress,
nodeAddress);
// temp activation of public socket before server ping/reply is setup
if (!avatarNode->getActiveSocket()) {
avatarNode->activatePublicSocket();
}
nodeList->updateNodeWithData(nodeAddress, packetData, receivedBytes);
if (std::isnan(((PositionalAudioRingBuffer *)avatarNode->getLinkedData())->getOrientation().x)) {
// kill off this node - temporary solution to mixer crash on mac sleep
avatarNode->setAlive(false);
}
} else if (packetData[0] == PACKET_TYPE_INJECT_AUDIO) {
QUuid nodeUUID = QUuid::fromRfc4122(QByteArray((char*) packetData + numBytesForPacketHeader(packetData),
NUM_BYTES_RFC4122_UUID));
Node* matchingInjector = nodeList->addOrUpdateNode(nodeUUID,
NODE_TYPE_AUDIO_INJECTOR,
NULL,
NULL);
Node* matchingNode = nodeList->nodeWithUUID(nodeUUID);
// give the new audio data to the matching injector node
nodeList->updateNodeWithData(matchingInjector, packetData, receivedBytes);
if (matchingNode) {
nodeList->updateNodeWithData(matchingNode, nodeAddress, packetData, receivedBytes);
if (packetData[0] != PACKET_TYPE_INJECT_AUDIO
&& std::isnan(((PositionalAudioRingBuffer *)matchingNode->getLinkedData())->getOrientation().x)) {
// kill off this node - temporary solution to mixer crash on mac sleep
matchingNode->setAlive(false);
}
}
} else {
// let processNodeData handle it.
nodeList->processNodeData(nodeAddress, packetData, receivedBytes);

View file

@ -97,6 +97,8 @@ void AvatarMixer::run() {
NodeList* nodeList = NodeList::getInstance();
nodeList->setOwnerType(NODE_TYPE_AVATAR_MIXER);
nodeList->setNodeTypesOfInterest(&NODE_TYPE_AGENT, 1);
nodeList->linkedDataCreateCallback = attachAvatarDataToNode;
nodeList->startSilentNodeRemovalThread();
@ -123,6 +125,8 @@ void AvatarMixer::run() {
NodeList::getInstance()->sendDomainServerCheckIn();
}
nodeList->possiblyPingInactiveNodes();
if (nodeList->getNodeSocket()->receive(&nodeAddress, packetData, &receivedBytes) &&
packetVersionMatch(packetData)) {
switch (packetData[0]) {
@ -131,10 +135,14 @@ void AvatarMixer::run() {
NUM_BYTES_RFC4122_UUID));
// add or update the node in our list
avatarNode = nodeList->addOrUpdateNode(nodeUUID, NODE_TYPE_AGENT, &nodeAddress, &nodeAddress);
avatarNode = nodeList->nodeWithUUID(nodeUUID);
// parse positional data from an node
nodeList->updateNodeWithData(avatarNode, packetData, receivedBytes);
if (avatarNode) {
// parse positional data from an node
nodeList->updateNodeWithData(avatarNode, &nodeAddress, packetData, receivedBytes);
} else {
break;
}
case PACKET_TYPE_INJECT_AUDIO:
broadcastAvatarData(nodeList, nodeUUID, &nodeAddress);
break;

View file

@ -46,79 +46,121 @@ int DomainServer::civetwebRequestHandler(struct mg_connection *connection) {
const struct mg_request_info* ri = mg_get_request_info(connection);
const char RESPONSE_200[] = "HTTP/1.0 200 OK\r\n\r\n";
const char RESPONSE_400[] = "HTTP/1.0 400 Bad Request\r\n\r\n";
if (strcmp(ri->uri, "/assignment") == 0 && strcmp(ri->request_method, "POST") == 0) {
// return a 200
mg_printf(connection, "%s", RESPONSE_200);
// upload the file
mg_upload(connection, "/tmp");
return 1;
} else if (strcmp(ri->uri, "/assignments.json") == 0) {
// user is asking for json list of assignments
// start with a 200 response
mg_printf(connection, "%s", RESPONSE_200);
// setup the JSON
QJsonObject assignmentJSON;
QJsonObject assignedNodesJSON;
// enumerate the NodeList to find the assigned nodes
NodeList* nodeList = NodeList::getInstance();
const char ASSIGNMENT_JSON_UUID_KEY[] = "UUID";
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
if (node->getLinkedData()) {
// this is a node with assignment
QJsonObject assignedNodeJSON;
const char URI_ASSIGNMENT[] = "/assignment";
const char URI_NODE[] = "/node";
if (strcmp(ri->request_method, "GET") == 0) {
if (strcmp(ri->uri, "/assignments.json") == 0) {
// user is asking for json list of assignments
// start with a 200 response
mg_printf(connection, "%s", RESPONSE_200);
// setup the JSON
QJsonObject assignmentJSON;
QJsonObject assignedNodesJSON;
// enumerate the NodeList to find the assigned nodes
NodeList* nodeList = NodeList::getInstance();
const char ASSIGNMENT_JSON_UUID_KEY[] = "UUID";
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
if (node->getLinkedData()) {
// this is a node with assignment
QJsonObject assignedNodeJSON;
// add the assignment UUID
QString assignmentUUID = uuidStringWithoutCurlyBraces(((Assignment*) node->getLinkedData())->getUUID());
assignedNodeJSON[ASSIGNMENT_JSON_UUID_KEY] = assignmentUUID;
// add the node socket information
assignedNodeJSON["public"] = jsonForSocket(node->getPublicSocket());
assignedNodeJSON["local"] = jsonForSocket(node->getLocalSocket());
// re-format the type name so it matches the target name
QString nodeTypeName(node->getTypeName());
nodeTypeName = nodeTypeName.toLower();
nodeTypeName.replace(' ', '-');
assignedNodesJSON[nodeTypeName] = assignedNodeJSON;
}
}
assignmentJSON["fulfilled"] = assignedNodesJSON;
QJsonObject queuedAssignmentsJSON;
// add the queued but unfilled assignments to the json
std::deque<Assignment*>::iterator assignment = domainServerInstance->_assignmentQueue.begin();
while (assignment != domainServerInstance->_assignmentQueue.end()) {
QJsonObject queuedAssignmentJSON;
// add the assignment UUID
QString assignmentUUID = uuidStringWithoutCurlyBraces(((Assignment*) node->getLinkedData())->getUUID());
assignedNodeJSON[ASSIGNMENT_JSON_UUID_KEY] = assignmentUUID;
QString uuidString = uuidStringWithoutCurlyBraces((*assignment)->getUUID());
queuedAssignmentJSON[ASSIGNMENT_JSON_UUID_KEY] = uuidString;
// add the node socket information
assignedNodeJSON["public"] = jsonForSocket(node->getPublicSocket());
assignedNodeJSON["local"] = jsonForSocket(node->getLocalSocket());
// add this queued assignment to the JSON
queuedAssignmentsJSON[(*assignment)->getTypeName()] = queuedAssignmentJSON;
// re-format the type name so it matches the target name
QString nodeTypeName(node->getTypeName());
nodeTypeName = nodeTypeName.toLower();
nodeTypeName.replace(' ', '-');
// push forward the iterator to check the next assignment
assignment++;
}
assignmentJSON["queued"] = queuedAssignmentsJSON;
// print out the created JSON
QJsonDocument assignmentDocument(assignmentJSON);
mg_printf(connection, "%s", assignmentDocument.toJson().constData());
// we've processed this request
return 1;
}
// not processed, pass to document root
return 0;
} else if (strcmp(ri->request_method, "POST") == 0) {
if (strcmp(ri->uri, URI_ASSIGNMENT) == 0) {
// return a 200
mg_printf(connection, "%s", RESPONSE_200);
// upload the file
mg_upload(connection, "/tmp");
return 1;
}
return 0;
} else if (strcmp(ri->request_method, "DELETE") == 0) {
// this is a DELETE request
// check if it is for an assignment
if (memcmp(ri->uri, URI_NODE, strlen(URI_NODE)) == 0) {
// pull the UUID from the url
QUuid deleteUUID = QUuid(QString(ri->uri + strlen(URI_NODE) + sizeof('/')));
if (!deleteUUID.isNull()) {
Node *nodeToKill = NodeList::getInstance()->nodeWithUUID(deleteUUID);
assignedNodesJSON[nodeTypeName] = assignedNodeJSON;
if (nodeToKill) {
// start with a 200 response
mg_printf(connection, "%s", RESPONSE_200);
// we have a valid UUID and node - kill the node that has this assignment
NodeList::getInstance()->killNode(nodeToKill);
// successfully processed request
return 1;
}
}
}
assignmentJSON["fulfilled"] = assignedNodesJSON;
// request not processed - bad request
mg_printf(connection, "%s", RESPONSE_400);
QJsonObject queuedAssignmentsJSON;
// add the queued but unfilled assignments to the json
std::deque<Assignment*>::iterator assignment = domainServerInstance->_assignmentQueue.begin();
while (assignment != domainServerInstance->_assignmentQueue.end()) {
QJsonObject queuedAssignmentJSON;
QString uuidString = uuidStringWithoutCurlyBraces((*assignment)->getUUID());
queuedAssignmentJSON[ASSIGNMENT_JSON_UUID_KEY] = uuidString;
// add this queued assignment to the JSON
queuedAssignmentsJSON[(*assignment)->getTypeName()] = queuedAssignmentJSON;
// push forward the iterator to check the next assignment
assignment++;
}
assignmentJSON["queued"] = queuedAssignmentsJSON;
// print out the created JSON
QJsonDocument assignmentDocument(assignmentJSON);
mg_printf(connection, "%s", assignmentDocument.toJson().constData());
// we've processed this request
// this was processed by civetweb
return 1;
} else {
// have mongoose process this request from the document_root
@ -160,6 +202,27 @@ void DomainServer::civetwebUploadHandler(struct mg_connection *connection, const
domainServerInstance->_assignmentQueueMutex.unlock();
}
void DomainServer::addReleasedAssignmentBackToQueue(Assignment* releasedAssignment) {
qDebug() << "Adding assignment" << *releasedAssignment << " back to queue.\n";
// find this assignment in the static file
for (int i = 0; i < MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS; i++) {
if (_staticAssignments[i].getUUID() == releasedAssignment->getUUID()) {
// reset the UUID on the static assignment
_staticAssignments[i].resetUUID();
// put this assignment back in the queue so it goes out
_assignmentQueueMutex.lock();
_assignmentQueue.push_back(&_staticAssignments[i]);
_assignmentQueueMutex.unlock();
} else if (_staticAssignments[i].getUUID().isNull()) {
// we are at the blank part of the static assignments - break out
break;
}
}
}
void DomainServer::nodeAdded(Node* node) {
}
@ -169,26 +232,8 @@ void DomainServer::nodeKilled(Node* node) {
if (node->getLinkedData()) {
Assignment* nodeAssignment = (Assignment*) node->getLinkedData();
qDebug() << "Adding assignment" << *nodeAssignment << " back to queue.\n";
// find this assignment in the static file
for (int i = 0; i < MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS; i++) {
if (_staticAssignments[i].getUUID() == nodeAssignment->getUUID()) {
// reset the UUID on the static assignment
_staticAssignments[i].resetUUID();
// put this assignment back in the queue so it goes out
_assignmentQueueMutex.lock();
_assignmentQueue.push_back(&_staticAssignments[i]);
_assignmentQueueMutex.unlock();
} else if (_staticAssignments[i].getUUID().isNull()) {
// we are at the blank part of the static assignments - break out
break;
}
}
addReleasedAssignmentBackToQueue(nodeAssignment);
}
}
unsigned char* DomainServer::addNodeToBroadcastPacket(unsigned char* currentPosition, Node* nodeToAdd) {

View file

@ -47,6 +47,7 @@ private:
void removeAssignmentFromQueue(Assignment* removableAssignment);
bool checkInWithUUIDMatchesExistingNode(sockaddr* nodePublicSocket, sockaddr* nodeLocalSocket, const QUuid& checkInUUI);
void possiblyAddStaticAssignmentsBackToQueueAfterRestart(timeval* startTime);
void addReleasedAssignmentBackToQueue(Assignment* releasedAssignment);
void cleanup();

View file

@ -38,14 +38,11 @@ configure_file(InterfaceConfig.h.in ${PROJECT_BINARY_DIR}/includes/InterfaceConf
# grab the implementation and header files from src dirs
file(GLOB INTERFACE_SRCS src/*.cpp src/*.h)
foreach(SUBDIR avatar devices renderer ui)
file(GLOB SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h)
foreach(SUBDIR avatar devices renderer ui starfield)
file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h)
set(INTERFACE_SRCS ${INTERFACE_SRCS} ${SUBDIR_SRCS})
endforeach(SUBDIR)
# project subdirectories
add_subdirectory(src/starfield)
find_package(Qt5Core REQUIRED)
find_package(Qt5Gui REQUIRED)
find_package(Qt5Multimedia REQUIRED)

View file

@ -72,8 +72,8 @@
using namespace std;
// Starfield information
static char STAR_FILE[] = "http://s3-us-west-1.amazonaws.com/highfidelity/stars.txt";
static char STAR_CACHE_FILE[] = "cachedStars.txt";
static unsigned STARFIELD_NUM_STARS = 50000;
static unsigned STARFIELD_SEED = 1;
static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored
@ -2491,8 +2491,8 @@ void Application::displaySide(Camera& whichCamera) {
if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... stars...");
if (!_stars.getFileLoaded()) {
_stars.readInput(STAR_FILE, STAR_CACHE_FILE, 0);
if (!_stars.isStarsLoaded()) {
_stars.generate(STARFIELD_NUM_STARS, STARFIELD_SEED);
}
// should be the first rendering pass - w/o depth buffer / lighting

20
interface/src/Stars.cpp Normal file → Executable file
View file

@ -7,34 +7,28 @@
//
#include "InterfaceConfig.h"
#include "Stars.h"
#include "Stars.h"
#define __interface__Starfield_impl__
#include "starfield/Controller.h"
#undef __interface__Starfield_impl__
Stars::Stars() :
_controller(0l), _fileLoaded(false) {
_controller = new starfield::Controller;
_controller(0l), _starsLoaded(false) {
_controller = new starfield::Controller;
}
Stars::~Stars() {
delete _controller;
}
bool Stars::readInput(const char* url, const char* cacheFile, unsigned limit) {
_fileLoaded = _controller->readInput(url, cacheFile, limit);
return _fileLoaded;
bool Stars::generate(unsigned numStars, unsigned seed) {
_starsLoaded = _controller->computeStars(numStars, seed);
return _starsLoaded;
}
bool Stars::setResolution(unsigned k) {
return _controller->setResolution(k);
}
float Stars::changeLOD(float fraction, float overalloc, float realloc) {
return float(_controller->changeLOD(fraction, overalloc, realloc));
}
void Stars::render(float fovY, float aspect, float nearZ, float alpha) {
// determine length of screen diagonal from quadrant height and aspect ratio
@ -47,7 +41,7 @@ void Stars::render(float fovY, float aspect, float nearZ, float alpha) {
// pull the modelview matrix off the GL stack
glm::mat4 view; glGetFloatv(GL_MODELVIEW_MATRIX, glm::value_ptr(view));
_controller->render(fovDiagonal, aspect, glm::affineInverse(view), alpha);
_controller->render(fovDiagonal, aspect, glm::affineInverse(view), alpha);
}

84
interface/src/Stars.h Normal file → Executable file
View file

@ -13,69 +13,39 @@
namespace starfield { class Controller; }
//
// Starfield rendering component.
//
// Starfield rendering component.
class Stars {
public:
Stars();
~Stars();
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, const char* cacheFile = 0l, unsigned limit = 200000);
// Generate stars from random number
// The numStars parameter sets the number of stars to generate.
bool generate(unsigned numStars, unsigned seed);
//
// Renders the starfield from a local viewer's perspective.
// The parameters specifiy the field of view.
//
void render(float fovY, float aspect, float nearZ, float alpha);
// Renders the starfield from a local viewer's perspective.
// The parameters specifiy the field of view.
void render(float fovY, float aspect, float nearZ, float alpha);
//
// 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);
// 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);
// Returns true when stars have been loaded
bool isStarsLoaded() const { return _starsLoaded; };
private:
// don't copy/assign
Stars(Stars const&); // = delete;
Stars& operator=(Stars const&); // delete;
bool getFileLoaded() const { return _fileLoaded; };
private:
// don't copy/assign
Stars(Stars const&); // = delete;
Stars& operator=(Stars const&); // delete;
// variables
starfield::Controller* _controller;
bool _fileLoaded;
starfield::Controller* _controller;
bool _starsLoaded;
};

View file

@ -688,6 +688,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
jointNeckID = object.properties.at(0).toString();
}
glm::vec3 translation;
glm::vec3 rotationOffset;
glm::vec3 preRotation, rotation, postRotation;
glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f);
glm::vec3 scalePivot, rotationPivot;
@ -701,6 +702,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
property.properties.at(4).value<double>(),
property.properties.at(5).value<double>());
} else if (property.properties.at(0) == "RotationOffset") {
rotationOffset = glm::vec3(property.properties.at(3).value<double>(),
property.properties.at(4).value<double>(),
property.properties.at(5).value<double>());
} else if (property.properties.at(0) == "RotationPivot") {
rotationPivot = glm::vec3(property.properties.at(3).value<double>(),
property.properties.at(4).value<double>(),
@ -741,6 +747,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
property.properties.at(5).value<double>(),
property.properties.at(6).value<double>());
} else if (property.properties.at(0) == "RotationOffset") {
rotationOffset = glm::vec3(property.properties.at(4).value<double>(),
property.properties.at(5).value<double>(),
property.properties.at(6).value<double>());
} else if (property.properties.at(0) == "RotationPivot") {
rotationPivot = glm::vec3(property.properties.at(4).value<double>(),
property.properties.at(5).value<double>(),
@ -776,13 +787,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
}
}
// see FBX documentation, http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/index.html
model.preRotation = glm::translate(translation) * glm::translate(rotationPivot) *
glm::mat4_cast(glm::quat(glm::radians(preRotation)));
model.preRotation = glm::translate(translation) * glm::translate(rotationOffset) *
glm::translate(rotationPivot) * glm::mat4_cast(glm::quat(glm::radians(preRotation)));
model.rotation = glm::quat(glm::radians(rotation));
model.postRotation = glm::mat4_cast(glm::quat(glm::radians(postRotation))) * glm::translate(-rotationPivot) *
glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot);
models.insert(object.properties.at(0).toString(), model);
model.postRotation = glm::mat4_cast(glm::quat(glm::radians(postRotation))) *
glm::translate(-rotationPivot) * glm::translate(scalePivot) *
glm::scale(scale) * glm::translate(-scalePivot);
models.insert(object.properties.at(0).value<qint64>(), model);
} else if (object.name == "Texture") {
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "RelativeFilename") {

View file

@ -1,9 +0,0 @@
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})

35
interface/src/starfield/Config.h Normal file → Executable file
View file

@ -9,30 +9,6 @@
#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_CULLING
#define STARFIELD_DEBUG_CULLING 0 // set to 1 to peek behind the scenes
#endif
#ifndef STARFIELD_MULTITHREADING
#define STARFIELD_MULTITHREADING 0
#endif
//
// Dependencies:
//
@ -49,11 +25,6 @@
#include <stdint.h>
#if STARFIELD_MULTITHREADING
#include <mutex>
#include <atomic>
#endif
#include <new>
#include <vector>
#include <memory>
@ -88,14 +59,8 @@ namespace starfield {
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
}

View file

@ -0,0 +1,63 @@
//
// starfield/Controller.cpp
// interface
//
// Created by Chris Barnard on 10/16/13
// Portions of code based on earlier work by Tobias Schwinger.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include "starfield/Controller.h"
using namespace starfield;
bool Controller::computeStars(unsigned numStars, unsigned seed) {
timeval startTime;
gettimeofday(&startTime, NULL);
Generator::computeStarPositions(_inputSequence, numStars, seed);
this->retile(numStars, _tileResolution);
qDebug("Total time to generate stars: %llu msec\n", (usecTimestampNow() - usecTimestamp(&startTime)) / 1000);
return true;
}
bool Controller::setResolution(unsigned tileResolution) {
if (tileResolution <= 3) {
return false;
}
if (tileResolution != _tileResolution) {
this->retile(_numStars, tileResolution);
return true;
} else {
return false;
}
}
void Controller::render(float perspective, float angle, mat4 const& orientation, float alpha) {
Renderer* renderer = _renderer;
if (renderer != 0l) {
renderer->render(perspective, angle, orientation, alpha);
}
}
void Controller::retile(unsigned numStars, unsigned tileResolution) {
Tiling tiling(tileResolution);
VertexOrder scanner(tiling);
radix2InplaceSort(_inputSequence.begin(), _inputSequence.end(), scanner);
recreateRenderer(numStars, tileResolution);
_tileResolution = tileResolution;
}
void Controller::recreateRenderer(unsigned numStars, unsigned tileResolution) {
delete _renderer;
_renderer = new Renderer(_inputSequence, numStars, tileResolution);
}

416
interface/src/starfield/Controller.h Normal file → Executable file
View file

@ -3,425 +3,39 @@
// interface
//
// Created by Tobias Schwinger on 3/29/13.
// Modified by Chris Barnard 10/16/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 <time.h>
#include "starfield/Generator.h"
#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 {
public:
Controller() :
_tileResolution(20),
_lodFraction(1.0),
_lodLowWaterMark(0.8),
_lodHighWaterMark(1.0),
_lodOveralloc(1.2),
_lodNalloc(0),
_lodNRender(0),
_lodBrightness(0),
_lodAllocBrightness(0),
_renderer(0l) {
}
~Controller() {
delete _renderer;
}
#if !STARFIELD_MULTITHREADING
#define lock
#define _(x)
#endif
bool readInput(const char* url, const char* cacheFile, unsigned limit)
{
InputVertices vertices;
if (! Loader().loadVertices(vertices, url, cacheFile, limit))
return false;
BrightnessLevels brightness;
extractBrightnessLevels(brightness, vertices);
// input is read, now run the entire data pipeline on the new input
{ lock _(_inputMutex);
_inputSequence.swap(vertices);
#if STARFIELD_MULTITHREADING
unsigned k = _tileResolution.load(memory_order_relaxed);
#else
unsigned k = _tileResolution;
#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 _(_lodStateMutex);
size_t newLast = _inputSequence.size() - 1;
// reciprocal change N_old/N_new tells us how to scale
// the fractions
rcpChange = min(1.0, double(vertices.size()) / _inputSequence.size());
// initialization? use defaults / previously set values
if (rcpChange == 0.0) {
rcpChange = 1.0;
nRender = toBufSize(_lodFraction * newLast);
n = min(newLast, toBufSize(_lodOveralloc * nRender));
} else {
// cannot allocate or render more than we have
n = min(newLast, _lodNalloc);
nRender = min(newLast, _lodNRender);
}
// 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(_inputSequence);
throw;
}
// finally publish the new LOD state
{ lock _(_lodStateMutex);
_lodBrightnessSequence.swap(brightness);
_lodFraction *= rcpChange;
_lodLowWaterMark *= rcpChange;
_lodHighWaterMark *= rcpChange;
_lodOveralloc *= rcpChange;
_lodNalloc = n;
_lodNRender = nRender;
_lodAllocBrightness = bMin;
#if STARFIELD_MULTITHREADING
_lodBrightness.store(b, memory_order_relaxed);
#else
_lodBrightness = b;
#endif
}
}
return true;
}
bool setResolution(unsigned k) {
if (k <= 3) {
return false;
}
// printLog("Stars.cpp: setResolution(%d)\n", k);
#if STARFIELD_MULTITHREADING
if (k != _tileResolution.load(memory_order_relaxed))
#else
if (k != _tileResolution)
#endif
{ lock _(_inputMutex);
unsigned n;
BrightnessLevel b, bMin;
{ lock _(_lodStateMutex);
n = _lodNalloc;
#if STARFIELD_MULTITHREADING
b = _lodBrightness.load(memory_order_relaxed);
#else
b = _lodBrightness;
#endif
bMin = _lodAllocBrightness;
}
this->retile(n, k, b, bMin);
return true;
} else {
return false;
}
}
double changeLOD(double factor, double overalloc, double realloc) {
assert(overalloc >= realloc && realloc >= 0.0);
assert(overalloc <= 1.0 && realloc <= 1.0);
// printLog(
// "Stars.cpp: changeLOD(%lf, %lf, %lf)\n", factor, overalloc, realloc);
size_t n, nRender;
BrightnessLevel bMin, b;
double fraction, lwm, hwm;
{ lock _(_lodStateMutex);
Controller() : _tileResolution(20), _renderer(0l) { }
// acuire a consistent copy of the current LOD state
fraction = _lodFraction;
lwm = _lodLowWaterMark;
hwm = _lodHighWaterMark;
size_t last = _lodBrightnessSequence.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 = toBufSize(oaFract * last);
bMin = _lodBrightnessSequence[n];
n = std::upper_bound(
_lodBrightnessSequence.begin() + n - 1,
_lodBrightnessSequence.end(),
bMin, GreaterBrightness() ) - _lodBrightnessSequence.begin();
// also determine number of vertices to render and brightness
nRender = toBufSize(fraction * last);
// Note: nRender does not have to be accurate
b = _lodBrightnessSequence[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
_lodBrightness.store(b, memory_order_relaxed);
#else
_lodBrightness = b;
#endif
// printLog("Stars.cpp: "
// "fraction = %lf, oaFract = %lf, n = %d, n' = %d, bMin = %d, b = %d\n",
// fraction, oaFract, toBufSize(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 >= _lodLowWaterMark
&& fraction <= _lodHighWaterMark) {
_lodFraction = fraction;
return fraction;
}
}
// reallocate
{ lock _(_inputMutex);
recreateRenderer(n, _tileResolution, b, bMin);
// printLog("Stars.cpp: LOD reallocation\n");
// publish new lod state
{ lock _(_lodStateMutex);
_lodNalloc = n;
_lodNRender = nRender;
_lodFraction = fraction;
_lodLowWaterMark = fraction * (1.0 - realloc);
_lodHighWaterMark = fraction * (1.0 + realloc);
_lodOveralloc = fraction * (1.0 + overalloc);
_lodAllocBrightness = bMin;
}
}
return fraction;
}
void render(float perspective, float angle, mat4 const& orientation, float alpha) {
#if STARFIELD_MULTITHREADING
// check out renderer
Renderer* renderer = _renderer.exchange(0l);
#else
Renderer* renderer = _renderer;
#endif
// have it render
if (renderer != 0l) {
#if STARFIELD_MULTITHREADING
BrightnessLevel b = _lodBrightness.load(memory_order_relaxed);
#else
BrightnessLevel b = _lodBrightness;
#endif
renderer->render(perspective, angle, orientation, b, alpha);
}
#if STARFIELD_MULTITHREADING
// check in - or dispose if there is a new one
Renderer* newOne = 0l;
if (! _renderer.compare_exchange_strong(newOne, renderer)) {
assert(!! newOne);
delete renderer;
}
#else
# undef lock
# undef _
#endif
}
~Controller() { delete _renderer; }
bool computeStars(unsigned numStars, unsigned seed);
bool setResolution(unsigned tileResolution);
void render(float perspective, float angle, mat4 const& orientation, float alpha);
private:
void retile(unsigned numStars, unsigned tileResolution);
void retile(size_t n, unsigned k,
BrightnessLevel b, BrightnessLevel bMin) {
void recreateRenderer(unsigned numStars, unsigned tileResolution);
Tiling tiling(k);
VertexOrder scanner(tiling);
radix2InplaceSort(_inputSequence.begin(), _inputSequence.end(), scanner);
// printLog(
// "Stars.cpp: recreateRenderer(%d, %d, %d, %d)\n", n, k, b, bMin);
recreateRenderer(n, k, b, bMin);
_tileResolution = k;
}
void recreateRenderer(size_t n, unsigned k,
BrightnessLevel b, BrightnessLevel bMin) {
#if STARFIELD_MULTITHREADING
delete _renderer.exchange(new Renderer(_inputSequence, n, k, b, bMin) );
#else
delete _renderer;
_renderer = new Renderer(_inputSequence, n, k, b, bMin);
#endif
}
static inline size_t toBufSize(double f) {
return size_t(floor(f + 0.5f));
}
struct BrightnessSortScanner : Radix2IntegerScanner<BrightnessLevel> {
typedef Radix2IntegerScanner<BrightnessLevel> 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());
}
InputVertices _inputSequence;
#if STARFIELD_MULTITHREADING
mutex _inputMutex;
atomic<unsigned> _tileResolution;
mutex _lodStateMutex;
#else
unsigned _tileResolution;
#endif
double _lodFraction;
double _lodLowWaterMark;
double _lodHighWaterMark;
double _lodOveralloc;
size_t _lodNalloc;
size_t _lodNRender;
BrightnessLevels _lodBrightnessSequence;
#if STARFIELD_MULTITHREADING
atomic<BrightnessLevel> _lodBrightness;
BrightnessLevel _lodAllocBrightness;
atomic<Renderer*> _renderer;
typedef lock_guard<mutex> lock;
#else
BrightnessLevel _lodBrightness;
BrightnessLevel _lodAllocBrightness;
Renderer* _renderer;
#undef lock
#undef _
#endif
InputVertices _inputSequence;
unsigned _tileResolution;
unsigned _numStars;
Renderer* _renderer;
};
}
#endif

View file

@ -0,0 +1,53 @@
//
// starfield/Generator.cpp
// interface
//
// Created by Chris Barnard on 10/13/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include "starfield/Generator.h"
using namespace starfield;
const float Generator::STAR_COLORIZATION = 0.1;
void Generator::computeStarPositions(InputVertices& destination, unsigned limit, unsigned seed) {
InputVertices* vertices = & destination;
//_limit = limit;
timeval startTime;
gettimeofday(&startTime, NULL);
srand(seed);
vertices->clear();
vertices->reserve(limit);
const unsigned NUM_DEGREES = 360;
for(int star = 0; star < limit; ++star) {
float azimuth, altitude;
azimuth = ((float)rand() / (float) RAND_MAX) * NUM_DEGREES;
altitude = (((float)rand() / (float) RAND_MAX) * NUM_DEGREES / 2) - NUM_DEGREES / 4;
vertices->push_back(InputVertex(azimuth, altitude, computeStarColor(STAR_COLORIZATION)));
}
qDebug("Took %llu msec to generate stars.\n", (usecTimestampNow() - usecTimestamp(&startTime)) / 1000);
}
// computeStarColor
// - Generate a star color.
//
// colorization can be a value between 0 and 1 specifying how colorful the resulting star color is.
//
// 0 = completely black & white
// 1 = very colorful
unsigned Generator::computeStarColor(float colorization) {
unsigned char red = rand() % 256;
unsigned char green = round((red * (1 - colorization)) + ((rand() % 256) * colorization));
unsigned char blue = round((red * (1 - colorization)) + ((rand() % 256) * colorization));
return red | green << 8 | blue << 16;
}

View file

@ -0,0 +1,37 @@
//
// starfield/Generator.h
// interface
//
// Created by Chris Barnard on 10/13/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__starfield__Generator__
#define __interface__starfield__Generator__
#include <locale.h>
#include <time.h>
#include "Config.h"
#include "SharedUtil.h"
#include "starfield/data/InputVertex.h"
namespace starfield {
class Generator {
public:
Generator() {}
~Generator() {}
static void computeStarPositions(InputVertices& destination, unsigned limit, unsigned seed);
static unsigned computeStarColor(float colorization);
private:
static const float STAR_COLORIZATION;
};
}
#endif

View file

@ -1,64 +0,0 @@
//
// 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<BrightnessLevel> 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

View file

@ -0,0 +1,23 @@
//
// starfield/data/GpuVertex.cpp
// interface
//
// Created by Chris Barnard on 10/17/13.
// Based on code by Tobias Schwinger on 3/29/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include "starfield/data/GpuVertex.h"
#include "starfield/data/InputVertex.h"
using namespace starfield;
GpuVertex::GpuVertex(InputVertex const& inputVertex) {
_color = inputVertex.getColor();
float azimuth = inputVertex.getAzimuth();
float altitude = inputVertex.getAltitude();
// compute altitude/azimuth into X/Y/Z point on a sphere
_valX = sin(azimuth) * cos(altitude);
_valY = sin(altitude);
_valZ = -cos(azimuth) * cos(altitude);
}

32
interface/src/starfield/data/GpuVertex.h Normal file → Executable file
View file

@ -3,16 +3,13 @@
// interface
//
// Created by Tobias Schwinger on 3/29/13.
// Modified 10/17/13 Chris Barnard.
// 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 {
@ -21,33 +18,18 @@ namespace starfield {
public:
GpuVertex() { }
GpuVertex(InputVertex const& in) {
_color = 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;
}
GpuVertex(InputVertex const& inputVertex);
unsigned getColor() const { return _color; }
private:
unsigned _color;
float _valX;
float _valY;
float _valZ;
unsigned _color;
float _valX;
float _valY;
float _valZ;
};
} // anonymous namespace
}
#endif

View file

@ -0,0 +1,24 @@
//
// starfield/data/InputVertex.cpp
// interface
//
// Created by Chris Barnard on 10/17.13.
// Based on code by Tobias Schwinger 3/29/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include "starfield/data/InputVertex.h"
using namespace starfield;
InputVertex::InputVertex(float azimuth, float altitude, unsigned color) {
_color = color | 0xff000000u;
azimuth = angleConvert<Degrees,Radians>(azimuth);
altitude = angleConvert<Degrees,Radians>(altitude);
angleHorizontalPolar<Radians>(azimuth, altitude);
_azimuth = azimuth;
_altitude = altitude;
}

27
interface/src/starfield/data/InputVertex.h Normal file → Executable file
View file

@ -3,16 +3,13 @@
// interface
//
// Created by Tobias Schwinger on 3/29/13.
// Modified by Chris Barnard 10/17/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 {
@ -20,33 +17,21 @@ namespace starfield {
class InputVertex {
public:
InputVertex(float azimuth, float altitude, unsigned color) {
_color = ((color >> 16) & 0xffu) | (color & 0xff00u) |
((color << 16) & 0xff0000u) | 0xff000000u;
azimuth = angleConvert<Degrees,Radians>(azimuth);
altitude = angleConvert<Degrees,Radians>(altitude);
angleHorizontalPolar<Radians>(azimuth, altitude);
_azimuth = azimuth;
_altitude = altitude;
}
InputVertex(float azimuth, float altitude, unsigned color);
float getAzimuth() const { return _azimuth; }
float getAltitude() const { return _altitude; }
unsigned getColor() const { return _color; }
private:
unsigned _color;
float _azimuth;
float _altitude;
unsigned _color;
float _azimuth;
float _altitude;
};
typedef std::vector<InputVertex> InputVertices;
} // anonymous namespace
}
#endif

18
interface/src/starfield/data/Tile.h Normal file → Executable file
View file

@ -9,30 +9,22 @@
#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;
nuint offset;
nuint count;
nuint flags;
// flags
static uint16_t const checked = 1;
static uint16_t const visited = 2;
static uint16_t const render = 4;
};
} // anonymous namespace
}
#endif

View file

@ -0,0 +1,311 @@
//
// starfield/renderer/Renderer.cpp
// interface
//
// Created by Chris Barnard on 10/17/13.
// Based on earlier work by Tobias Schwinger 3/22/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include "starfield/renderer/Renderer.h"
using namespace starfield;
Renderer::Renderer(InputVertices const& stars, unsigned numStars, unsigned tileResolution) : _dataArray(0l),
_tileArray(0l), _tiling(tileResolution) {
this->glAlloc();
Tiling tiling(tileResolution);
size_t numTiles = tiling.getTileCount();
// REVISIT: batch arrays are probably oversized, but - hey - they
// are not very large (unless for insane tiling) and we're better
// off safe than sorry
_dataArray = new GpuVertex[numStars];
_tileArray = new Tile[numTiles + 1];
_batchOffs = new GLint[numTiles * 2];
_batchCountArray = new GLsizei[numTiles * 2];
prepareVertexData(stars, numStars, tiling);
this->glUpload(numStars);
}
Renderer::~Renderer() {
delete[] _dataArray;
delete[] _tileArray;
delete[] _batchCountArray;
delete[] _batchOffs;
this->glFree();
}
void Renderer::render(float perspective, float aspect, mat4 const& orientation, float alpha) {
float halfPersp = perspective * 0.5f;
// 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 = vec3(matrix[2]);
float azimuth = atan2(ahead.x,-ahead.z) + Radians::pi();
float altitude = atan2(-ahead.y, hypotf(ahead.x, ahead.z));
angleHorizontalPolar<Radians>(azimuth, altitude);
float const eps = 0.002f;
altitude = glm::clamp(altitude, -Radians::halfPi() + eps, Radians::halfPi() - eps);
matrix = glm::affineInverse(matrix);
this->_outIndexPos = (unsigned*) _batchOffs;
this->_wRowVec = -vec3(row(matrix, 2));
this->_halfPerspectiveAngle = halfPersp;
TileSelection::Cursor cursor;
cursor.current = _tileArray + _tiling.getTileIndex(azimuth, altitude);
cursor.firstInRow = _tileArray + _tiling.getTileIndex(0.0f, altitude);
floodFill(cursor, TileSelection(*this, _tileArray, _tileArray + _tiling.getTileCount(), (TileSelection::Cursor*) _batchCountArray));
this->glBatch(glm::value_ptr(matrix), prepareBatch((unsigned*) _batchOffs, _outIndexPos), alpha);
}
// renderer construction
void Renderer::prepareVertexData(InputVertices const& vertices, unsigned numStars, Tiling const& tiling) {
size_t nTiles = tiling.getTileCount();
size_t vertexIndex = 0u, currTileIndex = 0u, count_active = 0u;
_tileArray[0].offset = 0u;
_tileArray[0].flags = 0u;
for (InputVertices::const_iterator i = vertices.begin(), e = vertices.end(); i != e; ++i) {
size_t tileIndex = tiling.getTileIndex(i->getAzimuth(), i->getAltitude());
assert(tileIndex >= currTileIndex);
// moved on to another tile? -> flush
if (tileIndex != currTileIndex) {
Tile* tile = _tileArray + currTileIndex;
Tile* lastTile = _tileArray + tileIndex;
// set count of active vertices (upcoming lod)
tile->count = count_active;
// generate skipped, empty tiles
for(size_t offset = vertexIndex; ++tile != lastTile ;) {
tile->offset = offset, tile->count = 0u, tile->flags = 0u;
}
// initialize next (as far as possible here)
lastTile->offset = vertexIndex;
lastTile->flags = 0u;
currTileIndex = tileIndex;
count_active = 0u;
}
++count_active;
// write converted vertex
_dataArray[vertexIndex++] = *i;
}
assert(vertexIndex == numStars);
// flush last tile (see above)
Tile* tile = _tileArray + currTileIndex;
tile->count = count_active;
for (Tile* e = _tileArray + nTiles + 1; ++tile != e;) {
tile->offset = vertexIndex, tile->count = 0u, tile->flags = 0;
}
}
bool Renderer::visitTile(Tile* tile) {
unsigned index = tile - _tileArray;
*_outIndexPos++ = index;
return isTileVisible(index);
}
bool Renderer::isTileVisible(unsigned index) {
float slice = _tiling.getSliceAngle();
float halfSlice = 0.5f * slice;
unsigned stride = _tiling.getAzimuthalTiles();
float azimuth = (index % stride) * slice;
float altitude = (index / stride) * slice - Radians::halfPi();
float groundX = sin(azimuth);
float groundZ = -cos(azimuth);
float elevation = cos(altitude);
vec3 tileCenter = vec3(groundX * elevation, sin(altitude), groundZ * elevation);
float w = dot(_wRowVec, tileCenter);
float daz = halfSlice * cos(std::max(0.0f, abs(altitude) - halfSlice));
float dal = halfSlice;
float adjustedNear = cos(_halfPerspectiveAngle + sqrt(daz * daz + dal * dal));
return w >= adjustedNear;
}
unsigned Renderer::prepareBatch(unsigned const* indices, unsigned const* indicesEnd) {
unsigned nRanges = 0u;
GLint* offs = _batchOffs;
GLsizei* count = _batchCountArray;
for (unsigned* i = (unsigned*) _batchOffs; i != indicesEnd; ++i) {
Tile* t = _tileArray + *i;
if ((t->flags & Tile::render) > 0u && t->count > 0u) {
*offs++ = t->offset;
*count++ = t->count;
++nRanges;
}
t->flags = 0;
}
return nRanges;
}
// GL API handling
void Renderer::glAlloc() {
GLchar const* const VERTEX_SHADER =
"#version 120\n"
"uniform float alpha;\n"
"void main(void) {\n"
" vec3 c = gl_Color.rgb * 1.22;\n"
" float s = min(max(tan((c.r + c.g + c.b) / 3), 1.0), 3.0);\n"
" gl_Position = ftransform();\n"
" gl_FrontColor= gl_Color * alpha * 1.5;\n"
" gl_PointSize = s;\n"
"}\n";
_program.addShaderFromSourceCode(QGLShader::Vertex, VERTEX_SHADER);
GLchar const* const FRAGMENT_SHADER =
"#version 120\n"
"void main(void) {\n"
" gl_FragColor = gl_Color;\n"
"}\n";
_program.addShaderFromSourceCode(QGLShader::Fragment, FRAGMENT_SHADER);
_program.link();
_alphaLocationHandle = _program.uniformLocation("alpha");
glGenBuffersARB(1, & _vertexArrayHandle);
}
void Renderer::glFree() {
glDeleteBuffersARB(1, & _vertexArrayHandle);
}
void Renderer::glUpload(GLsizei numStars) {
glBindBufferARB(GL_ARRAY_BUFFER, _vertexArrayHandle);
glBufferData(GL_ARRAY_BUFFER, numStars * sizeof(GpuVertex), _dataArray, GL_STATIC_DRAW);
glBindBufferARB(GL_ARRAY_BUFFER, 0);
}
void Renderer::glBatch(GLfloat const* matrix, GLsizei n_ranges, float alpha) {
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
// setup modelview matrix
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
_program.bind();
_program.setUniformValue(_alphaLocationHandle, alpha);
glBindBufferARB(GL_ARRAY_BUFFER, _vertexArrayHandle);
glInterleavedArrays(GL_C4UB_V3F, sizeof(GpuVertex), 0l);
// render
glMultiDrawArrays(GL_POINTS, _batchOffs, _batchCountArray, n_ranges);
// restore state
glBindBufferARB(GL_ARRAY_BUFFER, 0);
_program.release();
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
glDisable(GL_POINT_SMOOTH);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glPopMatrix();
}
// flood fill strategy
bool Renderer::TileSelection::select(Renderer::TileSelection::Cursor const& cursor) {
Tile* tile = cursor.current;
if (tile < _tileArray || tile >= _tilesEnd || !! (tile->flags & Tile::checked)) {
// out of bounds or been here already
return false;
}
// will check now and never again
tile->flags |= Tile::checked;
if (_rendererRef.visitTile(tile)) {
// good one -> remember (for batching) and propagate
tile->flags |= Tile::render;
return true;
}
return false;
}
bool Renderer::TileSelection::process(Renderer::TileSelection::Cursor const& cursor) {
Tile* tile = cursor.current;
if (! (tile->flags & Tile::visited)) {
tile->flags |= Tile::visited;
return true;
}
return false;
}
void Renderer::TileSelection::right(Renderer::TileSelection::Cursor& cursor) const {
cursor.current += 1;
if (cursor.current == cursor.firstInRow + _rendererRef._tiling.getAzimuthalTiles()) {
cursor.current = cursor.firstInRow;
}
}
void Renderer::TileSelection::left(Renderer::TileSelection::Cursor& cursor) const {
if (cursor.current == cursor.firstInRow) {
cursor.current = cursor.firstInRow + _rendererRef._tiling.getAzimuthalTiles();
}
cursor.current -= 1;
}
void Renderer::TileSelection::up(Renderer::TileSelection::Cursor& cursor) const {
unsigned numTiles = _rendererRef._tiling.getAzimuthalTiles();
cursor.current += numTiles;
cursor.firstInRow += numTiles;
}
void Renderer::TileSelection::down(Renderer::TileSelection::Cursor& cursor) const {
unsigned numTiles = _rendererRef._tiling.getAzimuthalTiles();
cursor.current -= numTiles;
cursor.firstInRow -= numTiles;
}
void Renderer::TileSelection::defer(Renderer::TileSelection::Cursor const& cursor) {
*_stackPos++ = cursor;
}
bool Renderer::TileSelection::deferred(Renderer::TileSelection::Cursor& cursor) {
if (_stackPos != _stackArray) {
cursor = *--_stackPos;
return true;
}
return false;
}

506
interface/src/starfield/renderer/Renderer.h Normal file → Executable file
View file

@ -3,24 +3,18 @@
// interface
//
// Created by Tobias Schwinger on 3/22/13.
// Modified 10/17/13 Chris Barnard.
// 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"
#include "starfield/renderer/Tiling.h"
//
// FOV culling
@ -66,174 +60,14 @@ namespace starfield {
class Renderer {
public:
Renderer(InputVertices const& src,
size_t n,
unsigned k,
BrightnessLevel b,
BrightnessLevel bMin) :
_dataArray(0l),
_tileArray(0l),
_tiling(k) {
this->glAlloc();
Tiling tiling(k);
size_t nTiles = tiling.getTileCount();
// REVISIT: could coalesce allocation for faster rebuild
// REVISIT: batch arrays are probably oversized, but - hey - they
// are not very large (unless for insane tiling) and we're better
// off safe than sorry
_dataArray = new GpuVertex[n];
_tileArray = new Tile[nTiles + 1];
_batchOffs = new GLint[nTiles * 2];
_batchCountArray = new GLsizei[nTiles * 2];
prepareVertexData(src, n, tiling, b, bMin);
this->glUpload(n);
}
~Renderer() {
delete[] _dataArray;
delete[] _tileArray;
delete[] _batchCountArray;
delete[] _batchOffs;
this->glFree();
}
void render(float perspective,
float aspect,
mat4 const& orientation,
BrightnessLevel minBright,
float alpha) {
// printLog("
// Stars.cpp: rendering at minimal brightness %d\n", minBright);
float halfPersp = perspective * 0.5f;
// 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 = vec3(matrix[2]);
float azimuth = atan2(ahead.x,-ahead.z) + Radians::pi();
float altitude = atan2(-ahead.y, hypotf(ahead.x, ahead.z));
angleHorizontalPolar<Radians>(azimuth, altitude);
float const eps = 0.002f;
altitude = glm::clamp(altitude,
-Radians::halfPi() + eps, Radians::halfPi() - eps);
#if STARFIELD_HEMISPHERE_ONLY
altitude = std::max(0.0f, altitude);
#endif
#if STARFIELD_DEBUG_CULLING
mat4 matrix_debug = glm::translate(vec3(0.0f, 0.0f, -4.0f)) *
glm::affineInverse(matrix);
#endif
matrix = glm::affineInverse(matrix);
this->_outIndexPos = (unsigned*) _batchOffs;
this->_wRowVec = -vec3(row(matrix, 2));
this->_halfPerspectiveAngle = halfPersp;
this->_minBright = minBright;
TileSelection::Cursor cursor;
cursor.current = _tileArray + _tiling.getTileIndex(azimuth, altitude);
cursor.firstInRow = _tileArray + _tiling.getTileIndex(0.0f, altitude);
floodFill(cursor, TileSelection(*this, _tileArray, _tileArray + _tiling.getTileCount(),
(TileSelection::Cursor*) _batchCountArray));
#if STARFIELD_DEBUG_CULLING
# define matrix matrix_debug
#endif
this->glBatch(glm::value_ptr(matrix), prepareBatch(
(unsigned*) _batchOffs, _outIndexPos), alpha);
#if STARFIELD_DEBUG_CULLING
# undef matrix
#endif
}
Renderer(InputVertices const& src, unsigned numStars, unsigned tileResolution);
~Renderer();
void render(float perspective, float aspect, mat4 const& orientation, float alpha);
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;
_tileArray[0].offset = 0u;
_tileArray[0].lod = b;
_tileArray[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 = _tileArray + currTileIndex;
Tile* tLast = _tileArray + 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;
// printLog("Stars.cpp: Vertex %d on tile #%d\n", vertexIndex, tileIndex);
// write converted vertex
_dataArray[vertexIndex++] = *i;
}
}
assert(vertexIndex == n);
// flush last tile (see above)
Tile* t = _tileArray + currTileIndex;
t->count = count_active;
for (Tile* e = _tileArray + nTiles + 1; ++t != e;) {
t->offset = vertexIndex, t->count = 0u,
t->lod = b, t->flags = 0;
}
}
void prepareVertexData(InputVertices const& vertices, unsigned numStars, Tiling const& tiling);
// FOV culling / LOD
@ -242,299 +76,65 @@ namespace starfield {
class TileSelection {
public:
struct Cursor { Tile* current, * firstInRow; };
private:
Renderer& _rendererRef;
Cursor* const _stackArray;
Cursor* _stackPos;
Tile const* const _tileArray;
Tile const* const _tilesEnd;
public:
struct Cursor { Tile* current, * firstInRow; };
private:
Renderer& _rendererRef;
Cursor* const _stackArray;
Cursor* _stackPos;
Tile const* const _tileArray;
Tile const* const _tilesEnd;
public:
TileSelection(Renderer& renderer, Tile const* tiles,
Tile const* tiles_end, Cursor* stack) :
_rendererRef(renderer),
_stackArray(stack),
_stackPos(stack),
_tileArray(tiles),
_tilesEnd(tiles_end) {
}
public:
TileSelection(Renderer& renderer, Tile const* tiles, Tile const* tiles_end, Cursor* stack) :
_rendererRef(renderer),
_stackArray(stack),
_stackPos(stack),
_tileArray(tiles),
_tilesEnd(tiles_end) { }
protected:
// flood fill strategy
bool select(Cursor const& c) {
Tile* t = c.current;
if (t < _tileArray || t >= _tilesEnd ||
!! (t->flags & Tile::checked)) {
// out of bounds or been here already
return false;
}
// will check now and never again
t->flags |= Tile::checked;
if (_rendererRef.visitTile(t)) {
// good one -> remember (for batching) and propagate
t->flags |= Tile::render;
return true;
}
return false;
}
bool process(Cursor const& c) {
Tile* t = c.current;
if (! (t->flags & Tile::visited)) {
t->flags |= Tile::visited;
return true;
}
return false;
}
void right(Cursor& c) const {
c.current += 1;
if (c.current == c.firstInRow + _rendererRef._tiling.getAzimuthalTiles()) {
c.current = c.firstInRow;
}
}
void left(Cursor& c) const {
if (c.current == c.firstInRow) {
c.current = c.firstInRow + _rendererRef._tiling.getAzimuthalTiles();
}
c.current -= 1;
}
void up(Cursor& c) const {
unsigned d = _rendererRef._tiling.getAzimuthalTiles();
c.current += d;
c.firstInRow += d;
}
void down(Cursor& c) const {
unsigned d = _rendererRef._tiling.getAzimuthalTiles();
c.current -= d;
c.firstInRow -= d;
}
void defer(Cursor const& t) {
*_stackPos++ = t;
}
bool deferred(Cursor& cursor) {
if (_stackPos != _stackArray) {
cursor = *--_stackPos;
return true;
}
return false;
}
protected:
bool select(Cursor const& cursor);
bool process(Cursor const& cursor);
void right(Cursor& cursor) const;
void left(Cursor& cursor) const;
void up(Cursor& cursor) const;
void down(Cursor& cursor) const;
void defer(Cursor const& cursor);
bool deferred(Cursor& cursor);
};
bool visitTile(Tile* t) {
unsigned index = t - _tileArray;
*_outIndexPos++ = index;
if (! tileVisible(t, index)) {
return false;
}
if (t->lod != _minBright) {
updateVertexCount(t, _minBright);
}
return true;
}
bool tileVisible(Tile* t, unsigned i) {
float slice = _tiling.getSliceAngle();
float halfSlice = 0.5f * slice;
unsigned stride = _tiling.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(_wRowVec, tileCenter);
float daz = halfSlice * cos(std::max(0.0f, abs(altitude) - halfSlice));
float dal = halfSlice;
float adjustedNear = cos(_halfPerspectiveAngle + sqrt(daz * daz + dal * dal));
// printLog("Stars.cpp: checking tile #%d, w = %f, near = %f\n", i, w, nearClip);
return w >= adjustedNear;
}
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 = _dataArray + t[0].offset;
GpuVertex const* end = _dataArray + 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 >= _dataArray + t[0].offset);
t->count = end - _dataArray - t[0].offset;
t->lod = minBright;
}
unsigned prepareBatch(unsigned const* indices,
unsigned const* indicesEnd) {
unsigned nRanges = 0u;
GLint* offs = _batchOffs;
GLsizei* count = _batchCountArray;
for (unsigned* i = (unsigned*) _batchOffs;
i != indicesEnd; ++i) {
Tile* t = _tileArray + *i;
if ((t->flags & Tile::render) > 0u && t->count > 0u) {
*offs++ = t->offset;
*count++ = t->count;
++nRanges;
}
t->flags = 0;
}
return nRanges;
}
bool visitTile(Tile* tile);
bool isTileVisible(unsigned index);
unsigned prepareBatch(unsigned const* indices, unsigned const* indicesEnd);
// GL API handling
void glAlloc() {
GLchar const* const VERTEX_SHADER =
"#version 120\n"
"uniform float alpha;\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 * alpha;\n"
" gl_PointSize = s;\n"
"}\n";
_program.addShaderFromSourceCode(QGLShader::Vertex, VERTEX_SHADER);
GLchar const* const FRAGMENT_SHADER =
"#version 120\n"
"void main(void) {\n"
" gl_FragColor = gl_Color;\n"
"}\n";
_program.addShaderFromSourceCode(QGLShader::Fragment, FRAGMENT_SHADER);
_program.link();
_alphaLocationHandle = _program.uniformLocation("alpha");
glGenBuffersARB(1, & _vertexArrayHandle);
}
void glFree() {
glDeleteBuffersARB(1, & _vertexArrayHandle);
}
void glUpload(GLsizei n) {
glBindBufferARB(GL_ARRAY_BUFFER, _vertexArrayHandle);
glBufferData(GL_ARRAY_BUFFER,
n * sizeof(GpuVertex), _dataArray, GL_STATIC_DRAW);
//glInterleavedArrays(GL_C4UB_V3F, sizeof(GpuVertex), 0l);
glBindBufferARB(GL_ARRAY_BUFFER, 0);
}
void glBatch(GLfloat const* matrix, GLsizei n_ranges, float alpha) {
// printLog("Stars.cpp: rendering %d-multibatch\n", n_ranges);
// for (int i = 0; i < n_ranges; ++i)
// printLog("Stars.cpp: Batch #%d - %d stars @ %d\n", i,
// _batchOffs[i], _batchCountArray[i]);
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
// setup modelview matrix
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
_program.bind();
_program.setUniformValue(_alphaLocationHandle, alpha);
glBindBufferARB(GL_ARRAY_BUFFER, _vertexArrayHandle);
glInterleavedArrays(GL_C4UB_V3F, sizeof(GpuVertex), 0l);
// render
glMultiDrawArrays(GL_POINTS,
_batchOffs, _batchCountArray, n_ranges);
// restore state
glBindBufferARB(GL_ARRAY_BUFFER, 0);
_program.release();
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
glDisable(GL_POINT_SMOOTH);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glPopMatrix();
}
void glAlloc();
void glFree();
void glUpload(GLsizei numStars);
void glBatch(GLfloat const* matrix, GLsizei n_ranges, float alpha);
// variables
GpuVertex* _dataArray;
Tile* _tileArray;
GLint* _batchOffs;
GLsizei* _batchCountArray;
GLuint _vertexArrayHandle;
ProgramObject _program;
int _alphaLocationHandle;
GpuVertex* _dataArray;
Tile* _tileArray;
GLint* _batchOffs;
GLsizei* _batchCountArray;
GLuint _vertexArrayHandle;
ProgramObject _program;
int _alphaLocationHandle;
Tiling _tiling;
Tiling _tiling;
unsigned* _outIndexPos;
vec3 _wRowVec;
float _halfPerspectiveAngle;
BrightnessLevel _minBright;
unsigned* _outIndexPos;
vec3 _wRowVec;
float _halfPerspectiveAngle;
};
} // anonymous namespace
}
#endif

64
interface/src/starfield/renderer/Tiling.h Normal file → Executable file
View file

@ -9,64 +9,38 @@
#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 {
const float LOG2 = 1.4426950408889634;
class Tiling {
public:
Tiling(unsigned k) :
_valK(k),
_rcpSlice(k / Radians::twicePi()) {
_nBits = ceil(log(getTileCount()) * 1.4426950408889634); // log2
}
unsigned getAzimuthalTiles() const { return _valK; }
unsigned getAltitudinalTiles() const { return _valK / 2 + 1; }
Tiling(unsigned tileResolution) : _tileResolution(tileResolution), _rcpSlice(tileResolution / Radians::twicePi()) {
_nBits = ceil(log(getTileCount()) * LOG2); }
unsigned getAzimuthalTiles() const { return _tileResolution; }
unsigned getAltitudinalTiles() const { return _tileResolution / 2 + 1; }
unsigned getTileIndexBits() const { return _nBits; }
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 / _rcpSlice;
}
unsigned getTileCount() const { return getAzimuthalTiles() * getAltitudinalTiles(); }
unsigned getTileIndex(float azimuth, float altitude) const { return discreteAzimuth(azimuth) +
_tileResolution * discreteAltitude(altitude); }
float getSliceAngle() const { return 1.0f / _rcpSlice; }
private:
unsigned discreteAngle(float unsigned_angle) const {
return unsigned(floor(unsigned_angle * _rcpSlice + 0.5f));
}
unsigned discreteAzimuth(float a) const {
return discreteAngle(a) % _valK;
}
unsigned discreteAltitude(float a) const {
return min(getAltitudinalTiles() - 1,
discreteAngle(a + Radians::halfPi()) );
}
unsigned discreteAngle(float unsigned_angle) const { return unsigned(floor(unsigned_angle * _rcpSlice + 0.5f)); }
unsigned discreteAzimuth(float angle) const { return discreteAngle(angle) % _tileResolution; }
unsigned discreteAltitude(float angle) const { return min( getAltitudinalTiles() - 1,
discreteAngle(angle + Radians::halfPi()) ); }
// variables
unsigned _valK;
float _rcpSlice;
unsigned _nBits;
unsigned _tileResolution;
float _rcpSlice;
unsigned _nBits;
};
} // anonymous namespace
}
#endif

View file

@ -0,0 +1,18 @@
//
// starfield/renderer/VertexOrder.cpp
// interface
//
// Created by Chris Barnard on 10/17/13.
// Based on code by Tobias Schwinger on 3/22/13.
//
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include "starfield/renderer/VertexOrder.h"
using namespace starfield;
bool VertexOrder::bit(InputVertex const& vertex, state_type const& state) const {
unsigned key = _tiling.getTileIndex(vertex.getAzimuth(), vertex.getAltitude());
return base::bit(key, state);
}

24
interface/src/starfield/renderer/VertexOrder.h Normal file → Executable file
View file

@ -9,37 +9,23 @@
#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.
*/
// Defines the vertex order for the renderer as a bit extractor for
//binary in-place Radix Sort.
class VertexOrder : public Radix2IntegerScanner<unsigned>
{
public:
explicit VertexOrder(Tiling const& tiling) :
base(tiling.getTileIndexBits() + BrightnessBits),
_tiling(tiling) {
}
base(tiling.getTileIndexBits()), _tiling(tiling) { }
bool bit(InputVertex const& v, state_type const& s) const {
// inspect (tile_index, brightness) tuples
unsigned key = getBrightness(v.getColor()) ^ BrightnessMask;
key |= _tiling.getTileIndex(
v.getAzimuth(), v.getAltitude()) << BrightnessBits;
return base::bit(key, s);
}
bool bit(InputVertex const& vertex, state_type const& state) const;
private:
Tiling _tiling;

View file

@ -151,7 +151,7 @@ void NodeList::processNodeData(sockaddr* senderAddress, unsigned char* packetDat
}
case PACKET_TYPE_PING_REPLY: {
// activate the appropriate socket for this node, if not yet updated
activateSocketFromPingReply(senderAddress);
activateSocketFromNodeCommunication(senderAddress);
// set the ping time for this node for stat collection
timePingReply(senderAddress, packetData);
@ -199,6 +199,7 @@ void NodeList::processBulkNodeData(sockaddr *senderAddress, unsigned char *packe
}
currentPosition += updateNodeWithData(matchingNode,
NULL,
packetHolder,
numTotalBytes - (currentPosition - startPosition));
@ -206,35 +207,32 @@ void NodeList::processBulkNodeData(sockaddr *senderAddress, unsigned char *packe
}
}
int NodeList::updateNodeWithData(sockaddr *senderAddress, unsigned char *packetData, size_t dataBytes) {
// find the node by the sockaddr
Node* matchingNode = nodeWithAddress(senderAddress);
if (matchingNode) {
return updateNodeWithData(matchingNode, packetData, dataBytes);
} else {
return 0;
}
}
int NodeList::updateNodeWithData(Node *node, unsigned char *packetData, int dataBytes) {
int NodeList::updateNodeWithData(Node *node, sockaddr* senderAddress, unsigned char *packetData, int dataBytes) {
node->lock();
node->setLastHeardMicrostamp(usecTimestampNow());
if (node->getActiveSocket()) {
if (senderAddress) {
activateSocketFromNodeCommunication(senderAddress);
}
if (node->getActiveSocket() || !senderAddress) {
node->recordBytesReceived(dataBytes);
if (!node->getLinkedData() && linkedDataCreateCallback) {
linkedDataCreateCallback(node);
}
int numParsedBytes = node->getLinkedData()->parseData(packetData, dataBytes);
node->unlock();
return numParsedBytes;
} else {
// we weren't able to match the sender address to the address we have for this node, unlock and don't parse
node->unlock();
return 0;
}
if (!node->getLinkedData() && linkedDataCreateCallback) {
linkedDataCreateCallback(node);
}
int numParsedBytes = node->getLinkedData()->parseData(packetData, dataBytes);
node->unlock();
return numParsedBytes;
}
Node* NodeList::nodeWithAddress(sockaddr *senderAddress) {
@ -601,7 +599,6 @@ void NodeList::pingPublicAndLocalSocketsForInactiveNode(Node* node) const {
currentTime = usecTimestampNow();
memcpy(pingPacket + numHeaderBytes, &currentTime, sizeof(currentTime));
qDebug() << "Attemping to ping" << *node << "\n";
// send the ping packet to the local and public sockets for this node
_nodeSocket.send(node->getLocalSocket(), pingPacket, sizeof(pingPacket));
_nodeSocket.send(node->getPublicSocket(), pingPacket, sizeof(pingPacket));
@ -672,7 +669,25 @@ unsigned NodeList::broadcastToNodes(unsigned char* broadcastData, size_t dataByt
return n;
}
void NodeList::activateSocketFromPingReply(sockaddr *nodeAddress) {
const uint64_t PING_INACTIVE_NODE_INTERVAL_USECS = 1 * 1000 * 1000;
void NodeList::possiblyPingInactiveNodes() {
static timeval lastPing = {};
// make sure PING_INACTIVE_NODE_INTERVAL_USECS has elapsed since last ping
if (usecTimestampNow() - usecTimestamp(&lastPing) >= PING_INACTIVE_NODE_INTERVAL_USECS) {
gettimeofday(&lastPing, NULL);
for(NodeList::iterator node = begin(); node != end(); node++) {
if (!node->getActiveSocket()) {
// we don't have an active link to this node, ping it to set that up
pingPublicAndLocalSocketsForInactiveNode(&(*node));
}
}
}
}
void NodeList::activateSocketFromNodeCommunication(sockaddr *nodeAddress) {
for(NodeList::iterator node = begin(); node != end(); node++) {
if (!node->getActiveSocket()) {
// check both the public and local addresses for each node to see if we find a match
@ -700,6 +715,22 @@ Node* NodeList::soloNodeOfType(char nodeType) {
return NULL;
}
void NodeList::killNode(Node* node, bool mustLockNode) {
if (mustLockNode) {
node->lock();
}
qDebug() << "Killed " << *node << "\n";
notifyHooksOfKilledNode(&*node);
node->setAlive(false);
if (mustLockNode) {
node->unlock();
}
}
void* removeSilentNodes(void *args) {
NodeList* nodeList = (NodeList*) args;
uint64_t checkTimeUsecs = 0;
@ -713,12 +744,8 @@ void* removeSilentNodes(void *args) {
node->lock();
if ((usecTimestampNow() - node->getLastHeardMicrostamp()) > NODE_SILENCE_THRESHOLD_USECS) {
qDebug() << "Killed " << *node << "\n";
nodeList->notifyHooksOfKilledNode(&*node);
node->setAlive(false);
// kill this node, don't lock - we already did it
nodeList->killNode(&(*node), false);
}
node->unlock();

View file

@ -113,12 +113,12 @@ public:
Node* nodeWithUUID(const QUuid& nodeUUID);
Node* addOrUpdateNode(const QUuid& uuid, char nodeType, sockaddr* publicSocket, sockaddr* localSocket);
void killNode(Node* node, bool mustLockNode = true);
void processNodeData(sockaddr *senderAddress, unsigned char *packetData, size_t dataBytes);
void processBulkNodeData(sockaddr *senderAddress, unsigned char *packetData, int numTotalBytes);
int updateNodeWithData(sockaddr *senderAddress, unsigned char *packetData, size_t dataBytes);
int updateNodeWithData(Node *node, unsigned char *packetData, int dataBytes);
int updateNodeWithData(Node *node, sockaddr* senderAddress, unsigned char *packetData, int dataBytes);
unsigned broadcastToNodes(unsigned char *broadcastData, size_t dataBytes, const char* nodeTypes, int numNodeTypes);
@ -140,6 +140,7 @@ public:
void addDomainListener(DomainChangeListener* listener);
void removeDomainListener(DomainChangeListener* listener);
void possiblyPingInactiveNodes();
private:
static NodeList* _sharedInstance;
@ -172,7 +173,7 @@ private:
uint16_t _publicPort;
bool _shouldUseDomainServerAsSTUN;
void activateSocketFromPingReply(sockaddr *nodeAddress);
void activateSocketFromNodeCommunication(sockaddr *nodeAddress);
void timePingReply(sockaddr *nodeAddress, unsigned char *packetData);
std::vector<NodeListHook*> _hooks;

View file

@ -265,16 +265,22 @@ bool UDPSocket::receive(sockaddr* recvAddress, void* receivedData, ssize_t* rece
}
int UDPSocket::send(sockaddr* destAddress, const void* data, size_t byteLength) const {
// send data via UDP
int sent_bytes = sendto(handle, (const char*)data, byteLength,
0, (sockaddr *) destAddress, sizeof(sockaddr_in));
if (sent_bytes != byteLength) {
qDebug("Failed to send packet: %s\n", strerror(errno));
return false;
if (destAddress) {
// send data via UDP
int sent_bytes = sendto(handle, (const char*)data, byteLength,
0, (sockaddr *) destAddress, sizeof(sockaddr_in));
if (sent_bytes != byteLength) {
qDebug("Failed to send packet: %s\n", strerror(errno));
return false;
}
return sent_bytes;
} else {
qDebug("UDPSocket send called with NULL destination address - Likely a node with no active socket.\n");
return 0;
}
return sent_bytes;
}
int UDPSocket::send(const char* destAddress, int destPort, const void* data, size_t byteLength) const {

View file

@ -327,6 +327,9 @@ void VoxelServer::run() {
NodeList* nodeList = NodeList::getInstance();
nodeList->setOwnerType(NODE_TYPE_VOXEL_SERVER);
// we need to ask the DS about agents so we can ping/reply with them
nodeList->setNodeTypesOfInterest(&NODE_TYPE_AGENT, 1);
setvbuf(stdout, NULL, _IOLBF, 0);
// tell our NodeList about our desire to get notifications
@ -434,6 +437,9 @@ void VoxelServer::run() {
NodeList::getInstance()->sendDomainServerCheckIn();
}
// ping our inactive nodes to punch holes with them
nodeList->possiblyPingInactiveNodes();
if (nodeList->getNodeSocket()->receive(&senderAddress, packetData, &packetLength) &&
packetVersionMatch(packetData)) {
@ -445,23 +451,16 @@ void VoxelServer::run() {
QUuid nodeUUID = QUuid::fromRfc4122(QByteArray((char*)packetData + numBytesPacketHeader,
NUM_BYTES_RFC4122_UUID));
Node* node = NodeList::getInstance()->addOrUpdateNode(nodeUUID,
NODE_TYPE_AGENT,
&senderAddress,
&senderAddress);
Node* node = nodeList->nodeWithUUID(nodeUUID);
// temp activation of public socket before server ping/reply is setup
if (!node->getActiveSocket()) {
node->activatePublicSocket();
if (node) {
nodeList->updateNodeWithData(node, &senderAddress, packetData, packetLength);
VoxelNodeData* nodeData = (VoxelNodeData*) node->getLinkedData();
if (nodeData && !nodeData->isVoxelSendThreadInitalized()) {
nodeData->initializeVoxelSendThread(this);
}
}
NodeList::getInstance()->updateNodeWithData(node, packetData, packetLength);
VoxelNodeData* nodeData = (VoxelNodeData*) node->getLinkedData();
if (nodeData && !nodeData->isVoxelSendThreadInitalized()) {
nodeData->initializeVoxelSendThread(this);
}
} else if (packetData[0] == PACKET_TYPE_PING
|| packetData[0] == PACKET_TYPE_DOMAIN
|| packetData[0] == PACKET_TYPE_STUN_RESPONSE) {