This commit is contained in:
Philip Rosedale 2014-03-20 15:34:28 -07:00
commit 03381c3df8
25 changed files with 674 additions and 94 deletions

View file

@ -62,7 +62,11 @@ void attachNewBufferToNode(Node *newNode) {
}
AudioMixer::AudioMixer(const QByteArray& packet) :
ThreadedAssignment(packet)
ThreadedAssignment(packet),
_minSourceLoudnessInFrame(1.0f),
_maxSourceLoudnessInFrame(0.0f),
_loudnessCutoffRatio(0.0f),
_minRequiredLoudness(0.0f)
{
}
@ -350,14 +354,64 @@ void AudioMixer::run() {
char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO
+ numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)];
int usecToSleep = 0;
bool isFirstRun = true;
while (!_isFinished) {
_minSourceLoudnessInFrame = 1.0f;
_maxSourceLoudnessInFrame = 0.0f;
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getLinkedData()) {
((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES);
((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES,
_minSourceLoudnessInFrame,
_maxSourceLoudnessInFrame);
}
}
if (!isFirstRun) {
const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10;
const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.30;
const float CUTOFF_EPSILON = 0.0001;
float percentageSleep = (usecToSleep / (float) BUFFER_SEND_INTERVAL_USECS);
float lastCutoffRatio = _loudnessCutoffRatio;
bool hasRatioChanged = false;
if (percentageSleep <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD || usecToSleep < 0) {
// we're struggling - change our min required loudness to reduce some load
_loudnessCutoffRatio += (1 - _loudnessCutoffRatio) / 2;
qDebug() << "Mixer is struggling, sleeping" << percentageSleep * 100 << "% of frame time. Old cutoff was"
<< lastCutoffRatio << "and is now" << _loudnessCutoffRatio;
hasRatioChanged = true;
} else if (percentageSleep >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _loudnessCutoffRatio != 0) {
// we've recovered and can back off the required loudness
_loudnessCutoffRatio -= _loudnessCutoffRatio / 2;
if (_loudnessCutoffRatio < CUTOFF_EPSILON) {
_loudnessCutoffRatio = 0;
}
qDebug() << "Mixer is recovering, sleeping" << percentageSleep * 100 << "% of frame time. Old cutoff was"
<< lastCutoffRatio << "and is now" << _loudnessCutoffRatio;
hasRatioChanged = true;
}
if (hasRatioChanged) {
// set out min required loudness from the new ratio
_minRequiredLoudness = _loudnessCutoffRatio * (_maxSourceLoudnessInFrame - _minSourceLoudnessInFrame);
qDebug() << "Minimum loudness required to be mixed is now" << _minRequiredLoudness;
}
} else {
isFirstRun = false;
}
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData()
@ -384,7 +438,7 @@ void AudioMixer::run() {
break;
}
int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow();
usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow();
if (usecToSleep > 0) {
usleep(usecToSleep);

View file

@ -37,7 +37,13 @@ private:
void prepareMixForListeningNode(Node* node);
// client samples capacity is larger than what will be sent to optimize mixing
int16_t _clientSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + SAMPLE_PHASE_DELAY_AT_90];
// we are MMX adding 4 samples at a time so we need client samples to have an extra 4
int16_t _clientSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)];
float _minSourceLoudnessInFrame;
float _maxSourceLoudnessInFrame;
float _loudnessCutoffRatio;
float _minRequiredLoudness;
};
#endif /* defined(__hifi__AudioMixer__) */

View file

@ -6,6 +6,8 @@
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QDebug>
#include <PacketHeaders.h>
#include <UUID.h>
@ -82,7 +84,9 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
return 0;
}
void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples) {
void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples,
float& currentMinLoudness,
float& currentMaxLoudness) {
for (unsigned int i = 0; i < _ringBuffers.size(); i++) {
if (_ringBuffers[i]->shouldBeAddedToMix(jitterBufferLengthSamples)) {
// this is a ring buffer that is ready to go
@ -92,6 +96,14 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSam
// calculate the average loudness for the next NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL
// that would be mixed in
_nextOutputLoudness = _ringBuffers[i]->averageLoudnessForBoundarySamples(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
if (_nextOutputLoudness != 0 && _nextOutputLoudness < currentMinLoudness) {
currentMinLoudness = _nextOutputLoudness;
}
if (_nextOutputLoudness > currentMaxLoudness) {
currentMaxLoudness = _nextOutputLoudness;
}
}
}
}

View file

@ -27,7 +27,7 @@ public:
float getNextOutputLoudness() const { return _nextOutputLoudness; }
int parseData(const QByteArray& packet);
void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples);
void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples, float& currentMinLoudness, float& currentMaxLoudness);
void pushBuffersAfterFrameSend();
private:
std::vector<PositionalAudioRingBuffer*> _ringBuffers;

View file

@ -36,18 +36,34 @@ OctreeQueryNode::OctreeQueryNode() :
_lodChanged(false),
_lodInitialized(false),
_sequenceNumber(0),
_lastRootTimestamp(0)
_lastRootTimestamp(0),
_myPacketType(PacketTypeUnknown),
_isShuttingDown(false)
{
}
OctreeQueryNode::~OctreeQueryNode() {
_isShuttingDown = true;
const bool extraDebugging = false;
if (extraDebugging) {
qDebug() << "OctreeQueryNode::~OctreeQueryNode()";
}
if (_octreeSendThread) {
if (extraDebugging) {
qDebug() << "OctreeQueryNode::~OctreeQueryNode()... calling _octreeSendThread->terminate()";
}
_octreeSendThread->terminate();
if (extraDebugging) {
qDebug() << "OctreeQueryNode::~OctreeQueryNode()... calling delete _octreeSendThread";
}
delete _octreeSendThread;
}
delete[] _octreePacket;
delete[] _lastOctreePacket;
if (extraDebugging) {
qDebug() << "OctreeQueryNode::~OctreeQueryNode()... DONE...";
}
}
@ -59,9 +75,13 @@ void OctreeQueryNode::initializeOctreeSendThread(OctreeServer* octreeServer, con
}
bool OctreeQueryNode::packetIsDuplicate() const {
// if shutting down, return immediately
if (_isShuttingDown) {
return false;
}
// since our packets now include header information, like sequence number, and createTime, we can't just do a memcmp
// of the entire packet, we need to compare only the packet content...
int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(getMyPacketType());
int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(_myPacketType);
if (_lastOctreePacketLength == getPacketLength()) {
if (memcmp(_lastOctreePacket + (numBytesPacketHeader + OCTREE_PACKET_EXTRA_HEADERS_SIZE),
@ -74,6 +94,11 @@ bool OctreeQueryNode::packetIsDuplicate() const {
}
bool OctreeQueryNode::shouldSuppressDuplicatePacket() {
// if shutting down, return immediately
if (_isShuttingDown) {
return true;
}
bool shouldSuppress = false; // assume we won't suppress
// only consider duplicate packets
@ -107,7 +132,17 @@ bool OctreeQueryNode::shouldSuppressDuplicatePacket() {
return shouldSuppress;
}
void OctreeQueryNode::init() {
_myPacketType = getMyPacketType();
resetOctreePacket(true); // don't bump sequence
}
void OctreeQueryNode::resetOctreePacket(bool lastWasSurpressed) {
// if shutting down, return immediately
if (_isShuttingDown) {
return;
}
// Whenever we call this, we will keep a copy of the last packet, so we can determine if the last packet has
// changed since we last reset it. Since we know that no two packets can ever be identical without being the same
// scene information, (e.g. the root node packet of a static scene), we can use this as a strategy for reducing
@ -128,7 +163,7 @@ void OctreeQueryNode::resetOctreePacket(bool lastWasSurpressed) {
}
_octreePacketAvailableBytes = MAX_PACKET_SIZE;
int numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(_octreePacket), getMyPacketType());
int numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(_octreePacket), _myPacketType);
_octreePacketAt = _octreePacket + numBytesPacketHeader;
_octreePacketAvailableBytes -= numBytesPacketHeader;
@ -158,6 +193,11 @@ void OctreeQueryNode::resetOctreePacket(bool lastWasSurpressed) {
}
void OctreeQueryNode::writeToPacket(const unsigned char* buffer, unsigned int bytes) {
// if shutting down, return immediately
if (_isShuttingDown) {
return;
}
// compressed packets include lead bytes which contain compressed size, this allows packing of
// multiple compressed portions together
if (_currentPacketIsCompressed) {
@ -174,6 +214,11 @@ void OctreeQueryNode::writeToPacket(const unsigned char* buffer, unsigned int by
}
bool OctreeQueryNode::updateCurrentViewFrustum() {
// if shutting down, return immediately
if (_isShuttingDown) {
return false;
}
bool currentViewFrustumChanged = false;
ViewFrustum newestViewFrustum;
// get position and orientation details from the camera
@ -233,6 +278,11 @@ void OctreeQueryNode::setViewSent(bool viewSent) {
}
void OctreeQueryNode::updateLastKnownViewFrustum() {
// if shutting down, return immediately
if (_isShuttingDown) {
return;
}
bool frustumChanges = !_lastKnownViewFrustum.isVerySimilar(_currentViewFrustum);
if (frustumChanges) {
@ -247,6 +297,11 @@ void OctreeQueryNode::updateLastKnownViewFrustum() {
bool OctreeQueryNode::moveShouldDump() const {
// if shutting down, return immediately
if (_isShuttingDown) {
return false;
}
glm::vec3 oldPosition = _lastKnownViewFrustum.getPosition();
glm::vec3 newPosition = _currentViewFrustum.getPosition();
@ -259,6 +314,11 @@ bool OctreeQueryNode::moveShouldDump() const {
}
void OctreeQueryNode::dumpOutOfView() {
// if shutting down, return immediately
if (_isShuttingDown) {
return;
}
int stillInView = 0;
int outOfView = 0;
OctreeElementBag tempBag;

View file

@ -28,6 +28,7 @@ public:
OctreeQueryNode();
virtual ~OctreeQueryNode();
void init(); // called after creation to set up some virtual items
virtual PacketType getMyPacketType() const = 0;
void resetOctreePacket(bool lastWasSurpressed = false); // resets octree packet to after "V" header
@ -91,6 +92,7 @@ public:
unsigned int getlastOctreePacketLength() const { return _lastOctreePacketLength; }
int getDuplicatePacketCount() const { return _duplicatePacketCount; }
bool isShuttingDown() const { return _isShuttingDown; }
private:
OctreeQueryNode(const OctreeQueryNode &);
@ -127,6 +129,9 @@ private:
OCTREE_PACKET_SEQUENCE _sequenceNumber;
quint64 _lastRootTimestamp;
PacketType _myPacketType;
bool _isShuttingDown;
};
#endif /* defined(__hifi__OctreeQueryNode__) */

View file

@ -65,7 +65,7 @@ bool OctreeSendThread::process() {
nodeData = (OctreeQueryNode*) node->getLinkedData();
// Sometimes the node data has not yet been linked, in which case we can't really do anything
if (nodeData) {
if (nodeData && !nodeData->isShuttingDown()) {
bool viewFrustumChanged = nodeData->updateCurrentViewFrustum();
packetDistributor(node, nodeData, viewFrustumChanged);
}
@ -99,7 +99,14 @@ quint64 OctreeSendThread::_totalBytes = 0;
quint64 OctreeSendThread::_totalWastedBytes = 0;
quint64 OctreeSendThread::_totalPackets = 0;
int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) {
int OctreeSendThread::handlePacketSend(const SharedNodePointer& node,
OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) {
// if we're shutting down, then exit early
if (nodeData->isShuttingDown()) {
return 0;
}
bool debug = _myServer->wantsDebugSending();
quint64 now = usecTimestampNow();
@ -136,7 +143,7 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQuer
// If we've got a stats message ready to send, then see if we can piggyback them together
if (nodeData->stats.isReadyToSend()) {
if (nodeData->stats.isReadyToSend() && !nodeData->isShuttingDown()) {
// Send the stats message to the client
unsigned char* statsMessage = nodeData->stats.getStatsMessage();
int statsMessageLength = nodeData->stats.getStatsMessageLength();
@ -203,7 +210,7 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQuer
nodeData->stats.markAsSent();
} else {
// If there's actually a packet waiting, then send it.
if (nodeData->isPacketWaiting()) {
if (nodeData->isPacketWaiting() && !nodeData->isShuttingDown()) {
// just send the voxel packet
NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(),
SharedNodePointer(node));
@ -234,6 +241,12 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQuer
/// Version of voxel distributor that sends the deepest LOD level at once
int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
// if shutting down, exit early
if (nodeData->isShuttingDown()) {
return 0;
}
int truePacketsSent = 0;
int trueBytesSent = 0;
int packetsSentThisInterval = 0;
@ -336,7 +349,7 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue
int extraPackingAttempts = 0;
bool completedScene = false;
while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval) {
while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) {
float lockWaitElapsedUsec = OctreeServer::SKIP_TIME;
float encodeElapsedUsec = OctreeServer::SKIP_TIME;
float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME;
@ -503,7 +516,7 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue
// Here's where we can/should allow the server to send other data...
// send the environment packet
// TODO: should we turn this into a while loop to better handle sending multiple special packets
if (_myServer->hasSpecialPacketToSend(node)) {
if (_myServer->hasSpecialPacketToSend(node) && !nodeData->isShuttingDown()) {
trueBytesSent += _myServer->sendSpecialPacket(node);
truePacketsSent++;
packetsSentThisInterval++;

View file

@ -168,7 +168,7 @@ void OctreeServer::trackPacketSendingTime(float time) {
void OctreeServer::attachQueryNodeToNode(Node* newNode) {
if (!newNode->getLinkedData()) {
OctreeQueryNode* newQueryNodeData = _instance->createOctreeQueryNode();
newQueryNodeData->resetOctreePacket(true); // don't bump sequence
newQueryNodeData->init();
newNode->setLinkedData(newQueryNodeData);
}
}
@ -784,16 +784,26 @@ void OctreeServer::readPendingDatagrams() {
if (packetType == getMyQueryMessageType()) {
bool debug = false;
if (debug) {
qDebug() << "Got PacketTypeVoxelQuery at" << usecTimestampNow();
if (matchingNode) {
qDebug() << "Got PacketTypeVoxelQuery at" << usecTimestampNow() << "node:" << *matchingNode;
} else {
qDebug() << "Got PacketTypeVoxelQuery at" << usecTimestampNow() << "node: ??????";
}
}
// If we got a PacketType_VOXEL_QUERY, then we're talking to an NodeType_t_AVATAR, and we
// need to make sure we have it in our nodeList.
if (matchingNode) {
if (debug) {
qDebug() << "calling updateNodeWithDataFromPacket()... node:" << *matchingNode;
}
nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket);
OctreeQueryNode* nodeData = (OctreeQueryNode*) matchingNode->getLinkedData();
if (nodeData && !nodeData->isOctreeSendThreadInitalized()) {
if (debug) {
qDebug() << "calling initializeOctreeSendThread()... node:" << *matchingNode;
}
nodeData->initializeOctreeSendThread(this, matchingNode->getUUID());
}
}
@ -999,6 +1009,8 @@ void OctreeServer::nodeKilled(SharedNodePointer node) {
node->setLinkedData(NULL); // set this first in case another thread comes through and tryes to acces this
qDebug() << qPrintable(_safeServerName) << "server deleting Linked Data for node:" << *node;
nodeData->deleteLater();
} else {
qDebug() << qPrintable(_safeServerName) << "server node missing linked data node:" << *node;
}
}

View file

@ -4,22 +4,22 @@
<context>
<name>Application</name>
<message>
<location filename="src/Application.cpp" line="1354"/>
<location filename="src/Application.cpp" line="1364"/>
<source>Export Voxels</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="1355"/>
<location filename="src/Application.cpp" line="1365"/>
<source>Sparse Voxel Octree Files (*.svo)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3565"/>
<location filename="src/Application.cpp" line="3577"/>
<source>Open Script</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3566"/>
<location filename="src/Application.cpp" line="3578"/>
<source>JavaScript Files (*.js)</source>
<translation type="unfinished"></translation>
</message>
@ -113,18 +113,18 @@
<context>
<name>Menu</name>
<message>
<location filename="src/Menu.cpp" line="437"/>
<location filename="src/Menu.cpp" line="439"/>
<source>Open .ini config file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="439"/>
<location filename="src/Menu.cpp" line="451"/>
<location filename="src/Menu.cpp" line="441"/>
<location filename="src/Menu.cpp" line="453"/>
<source>Text files (*.ini)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="449"/>
<location filename="src/Menu.cpp" line="451"/>
<source>Save .ini config file</source>
<translation type="unfinished"></translation>
</message>

View file

@ -692,6 +692,8 @@ bool Application::event(QEvent* event) {
void Application::keyPressEvent(QKeyEvent* event) {
_keysPressed.insert(event->key());
_controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
@ -914,6 +916,8 @@ void Application::keyPressEvent(QKeyEvent* event) {
void Application::keyReleaseEvent(QKeyEvent* event) {
_keysPressed.remove(event->key());
_controllerScriptingInterface.emitKeyReleaseEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
@ -921,60 +925,66 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
return;
}
switch (event->key()) {
case Qt::Key_E:
_myAvatar->setDriveKeys(UP, 0.f);
break;
if (activeWindow() == _window) {
switch (event->key()) {
case Qt::Key_E:
_myAvatar->setDriveKeys(UP, 0.f);
break;
case Qt::Key_C:
_myAvatar->setDriveKeys(DOWN, 0.f);
break;
case Qt::Key_C:
_myAvatar->setDriveKeys(DOWN, 0.f);
break;
case Qt::Key_W:
_myAvatar->setDriveKeys(FWD, 0.f);
break;
case Qt::Key_W:
_myAvatar->setDriveKeys(FWD, 0.f);
break;
case Qt::Key_S:
_myAvatar->setDriveKeys(BACK, 0.f);
break;
case Qt::Key_S:
_myAvatar->setDriveKeys(BACK, 0.f);
break;
case Qt::Key_A:
_myAvatar->setDriveKeys(ROT_LEFT, 0.f);
break;
case Qt::Key_A:
_myAvatar->setDriveKeys(ROT_LEFT, 0.f);
break;
case Qt::Key_D:
_myAvatar->setDriveKeys(ROT_RIGHT, 0.f);
break;
case Qt::Key_D:
_myAvatar->setDriveKeys(ROT_RIGHT, 0.f);
break;
case Qt::Key_Up:
_myAvatar->setDriveKeys(FWD, 0.f);
_myAvatar->setDriveKeys(UP, 0.f);
break;
case Qt::Key_Up:
_myAvatar->setDriveKeys(FWD, 0.f);
_myAvatar->setDriveKeys(UP, 0.f);
break;
case Qt::Key_Down:
_myAvatar->setDriveKeys(BACK, 0.f);
_myAvatar->setDriveKeys(DOWN, 0.f);
break;
case Qt::Key_Down:
_myAvatar->setDriveKeys(BACK, 0.f);
_myAvatar->setDriveKeys(DOWN, 0.f);
break;
case Qt::Key_Left:
_myAvatar->setDriveKeys(LEFT, 0.f);
_myAvatar->setDriveKeys(ROT_LEFT, 0.f);
break;
case Qt::Key_Left:
_myAvatar->setDriveKeys(LEFT, 0.f);
_myAvatar->setDriveKeys(ROT_LEFT, 0.f);
break;
case Qt::Key_Right:
_myAvatar->setDriveKeys(RIGHT, 0.f);
_myAvatar->setDriveKeys(ROT_RIGHT, 0.f);
break;
case Qt::Key_Right:
_myAvatar->setDriveKeys(RIGHT, 0.f);
_myAvatar->setDriveKeys(ROT_RIGHT, 0.f);
break;
default:
event->ignore();
break;
}
default:
event->ignore();
break;
}
}
void Application::focusOutEvent(QFocusEvent* event) {
// synthesize events for keys currently pressed, since we may not get their release events
foreach (int key, _keysPressed) {
QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier);
keyReleaseEvent(&event);
}
_keysPressed.clear();
}
void Application::mouseMoveEvent(QMouseEvent* event) {
_controllerScriptingInterface.emitMouseMoveEvent(event); // send events to any registered scripts
@ -2140,7 +2150,8 @@ void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) {
}
glm::vec3 Application::getSunDirection() {
return glm::normalize(_environment.getClosestData(_myCamera.getPosition()).getSunLocation() - _myCamera.getPosition());
return glm::normalize(_environment.getClosestData(_myCamera.getPosition()).getSunLocation(_myCamera.getPosition()) -
_myCamera.getPosition());
}
void Application::updateShadowMap() {
@ -2302,7 +2313,8 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
float alpha = 1.0f;
if (Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) {
const EnvironmentData& closestData = _environment.getClosestData(whichCamera.getPosition());
float height = glm::distance(whichCamera.getPosition(), closestData.getAtmosphereCenter());
float height = glm::distance(whichCamera.getPosition(),
closestData.getAtmosphereCenter(whichCamera.getPosition()));
if (height < closestData.getAtmosphereInnerRadius()) {
alpha = 0.0f;

View file

@ -18,6 +18,7 @@
#include <QSettings>
#include <QTouchEvent>
#include <QList>
#include <QSet>
#include <QStringList>
#include <QPointer>
@ -122,6 +123,8 @@ public:
void keyPressEvent(QKeyEvent* event);
void keyReleaseEvent(QKeyEvent* event);
void focusOutEvent(QFocusEvent* event);
void mouseMoveEvent(QMouseEvent* event);
void mousePressEvent(QMouseEvent* event);
void mouseReleaseEvent(QMouseEvent* event);
@ -432,6 +435,8 @@ private:
bool _mousePressed; // true if mouse has been pressed (clear when finished)
QSet<int> _keysPressed;
GeometryCache _geometryCache;
TextureCache _textureCache;

View file

@ -92,7 +92,7 @@ glm::vec3 Environment::getGravity (const glm::vec3& position) {
foreach (const ServerData& serverData, _data) {
foreach (const EnvironmentData& environmentData, serverData) {
glm::vec3 vector = environmentData.getAtmosphereCenter() - position;
glm::vec3 vector = environmentData.getAtmosphereCenter(position) - position;
float surfaceRadius = environmentData.getAtmosphereInnerRadius();
if (glm::length(vector) <= surfaceRadius) {
// At or inside a planet, gravity is as set for the planet
@ -116,7 +116,7 @@ const EnvironmentData Environment::getClosestData(const glm::vec3& position) {
float closestDistance = FLT_MAX;
foreach (const ServerData& serverData, _data) {
foreach (const EnvironmentData& environmentData, serverData) {
float distance = glm::distance(position, environmentData.getAtmosphereCenter()) -
float distance = glm::distance(position, environmentData.getAtmosphereCenter(position)) -
environmentData.getAtmosphereOuterRadius();
if (distance < closestDistance) {
closest = environmentData;
@ -132,6 +132,8 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3
// collide with the "floor"
bool found = findCapsulePlanePenetration(start, end, radius, glm::vec4(0.0f, 1.0f, 0.0f, 0.0f), penetration);
glm::vec3 middle = (start + end) * 0.5f;
// get the lock for the duration of the call
QMutexLocker locker(&_mutex);
@ -141,7 +143,7 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3
continue; // don't bother colliding with gravity-less environments
}
glm::vec3 environmentPenetration;
if (findCapsuleSpherePenetration(start, end, radius, environmentData.getAtmosphereCenter(),
if (findCapsuleSpherePenetration(start, end, radius, environmentData.getAtmosphereCenter(middle),
environmentData.getAtmosphereInnerRadius(), environmentPenetration)) {
penetration = addPenetrations(penetration, environmentPenetration);
found = true;
@ -203,10 +205,12 @@ ProgramObject* Environment::createSkyProgram(const char* from, int* locations) {
}
void Environment::renderAtmosphere(Camera& camera, const EnvironmentData& data) {
glm::vec3 center = data.getAtmosphereCenter(camera.getPosition());
glPushMatrix();
glTranslatef(data.getAtmosphereCenter().x, data.getAtmosphereCenter().y, data.getAtmosphereCenter().z);
glm::vec3 relativeCameraPos = camera.getPosition() - data.getAtmosphereCenter();
glTranslatef(center.x, center.y, center.z);
glm::vec3 relativeCameraPos = camera.getPosition() - center;
float height = glm::length(relativeCameraPos);
// use the appropriate shader depending on whether we're inside or outside

View file

@ -55,6 +55,10 @@ void GLCanvas::keyReleaseEvent(QKeyEvent* event) {
Application::getInstance()->keyReleaseEvent(event);
}
void GLCanvas::focusOutEvent(QFocusEvent* event) {
Application::getInstance()->focusOutEvent(event);
}
void GLCanvas::mouseMoveEvent(QMouseEvent* event) {
Application::getInstance()->mouseMoveEvent(event);
}

View file

@ -31,6 +31,8 @@ protected:
virtual void keyPressEvent(QKeyEvent* event);
virtual void keyReleaseEvent(QKeyEvent* event);
virtual void focusOutEvent(QFocusEvent* event);
virtual void mouseMoveEvent(QMouseEvent* event);
virtual void mousePressEvent(QMouseEvent* event);
virtual void mouseReleaseEvent(QMouseEvent* event);

View file

@ -24,11 +24,12 @@
#include <QSlider>
#include <QStandardPaths>
#include <QUuid>
#include <QWindow>
#include <QHBoxLayout>
#include <AccountManager.h>
#include <XmppClient.h>
#include <UUID.h>
#include <FileDownloader.h>
#include "Application.h"
#include "Menu.h"
@ -36,6 +37,7 @@
#include "Util.h"
#include "InfoView.h"
#include "ui/MetavoxelEditor.h"
#include "ModelBrowser.h"
Menu* Menu::_instance = NULL;
@ -692,6 +694,10 @@ void Menu::loginForCurrentDomain() {
void Menu::editPreferences() {
Application* applicationInstance = Application::getInstance();
ModelBrowser headBrowser(Head);
ModelBrowser skeletonBrowser(Skeleton);
const QString BROWSE_BUTTON_TEXT = "Browse";
QDialog dialog(applicationInstance->getWindow());
dialog.setWindowTitle("Interface Preferences");
@ -702,17 +708,33 @@ void Menu::editPreferences() {
QFormLayout* form = new QFormLayout();
layout->addLayout(form, 1);
QHBoxLayout headModelLayout;
QString faceURLString = applicationInstance->getAvatar()->getHead()->getFaceModel().getURL().toString();
QLineEdit* faceURLEdit = new QLineEdit(faceURLString);
faceURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
faceURLEdit->setPlaceholderText(DEFAULT_HEAD_MODEL_URL.toString());
form->addRow("Face URL:", faceURLEdit);
QLineEdit headURLEdit(faceURLString);
QPushButton headBrowseButton(BROWSE_BUTTON_TEXT);
connect(&headBrowseButton, SIGNAL(clicked()), &headBrowser, SLOT(browse()));
connect(&headBrowser, SIGNAL(selected(QString)), &headURLEdit, SLOT(setText(QString)));
headURLEdit.setReadOnly(true);
headURLEdit.setMinimumWidth(QLINE_MINIMUM_WIDTH);
headURLEdit.setPlaceholderText(DEFAULT_HEAD_MODEL_URL.toString());
headModelLayout.addWidget(&headURLEdit);
headModelLayout.addWidget(&headBrowseButton);
form->addRow("Head URL:", &headModelLayout);
QHBoxLayout skeletonModelLayout;
QString skeletonURLString = applicationInstance->getAvatar()->getSkeletonModel().getURL().toString();
QLineEdit* skeletonURLEdit = new QLineEdit(skeletonURLString);
skeletonURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
skeletonURLEdit->setPlaceholderText(DEFAULT_BODY_MODEL_URL.toString());
form->addRow("Skeleton URL:", skeletonURLEdit);
QLineEdit skeletonURLEdit(skeletonURLString);
QPushButton SkeletonBrowseButton(BROWSE_BUTTON_TEXT);
connect(&SkeletonBrowseButton, SIGNAL(clicked()), &skeletonBrowser, SLOT(browse()));
connect(&skeletonBrowser, SIGNAL(selected(QString)), &skeletonURLEdit, SLOT(setText(QString)));
skeletonURLEdit.setReadOnly(true);
skeletonURLEdit.setMinimumWidth(QLINE_MINIMUM_WIDTH);
skeletonURLEdit.setPlaceholderText(DEFAULT_BODY_MODEL_URL.toString());
skeletonModelLayout.addWidget(&skeletonURLEdit);
skeletonModelLayout.addWidget(&SkeletonBrowseButton);
form->addRow("Skeleton URL:", &skeletonModelLayout);
QString displayNameString = applicationInstance->getAvatar()->getDisplayName();
QLineEdit* displayNameEdit = new QLineEdit(displayNameString);
@ -774,21 +796,17 @@ void Menu::editPreferences() {
int ret = dialog.exec();
if (ret == QDialog::Accepted) {
QUrl faceModelURL(faceURLEdit->text());
bool shouldDispatchIdentityPacket = false;
if (faceModelURL.toString() != faceURLString) {
if (headURLEdit.text() != faceURLString && !headURLEdit.text().isEmpty()) {
// change the faceModelURL in the profile, it will also update this user's BlendFace
applicationInstance->getAvatar()->setFaceModelURL(faceModelURL);
applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.text()));
shouldDispatchIdentityPacket = true;
}
QUrl skeletonModelURL(skeletonURLEdit->text());
if (skeletonModelURL.toString() != skeletonURLString) {
if (skeletonURLEdit.text() != skeletonURLString && !skeletonURLEdit.text().isEmpty()) {
// change the skeletonModelURL in the profile, it will also update this user's Body
applicationInstance->getAvatar()->setSkeletonModelURL(skeletonModelURL);
applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.text()));
shouldDispatchIdentityPacket = true;
}

View file

@ -0,0 +1,150 @@
//
// ModelBrowser.cpp
// hifi
//
// Created by Clement on 3/17/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include <QUrl>
#include <QXmlStreamReader>
#include <QEventLoop>
#include <QMessageBox>
#include <QGridLayout>
#include <QDialog>
#include <QStringListModel>
#include <QDialogButtonBox>
#include <Application.h>
#include "ModelBrowser.h"
static const QString PREFIX_PARAMETER_NAME = "prefix";
static const QString MARKER_PARAMETER_NAME = "marker";
static const QString IS_TRUNCATED_NAME = "IsTruncated";
static const QString CONTAINER_NAME = "Contents";
static const QString KEY_NAME = "Key";
ModelBrowser::ModelBrowser(ModelType modelType, QWidget* parent) : QWidget(parent), _type(modelType) {
QUrl url(S3_URL);
QUrlQuery query;
if (_type == Head) {
query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION);
} else if (_type == Skeleton) {
query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION);
}
url.setQuery(query);
_downloader = new FileDownloader(url);
connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished()));
}
ModelBrowser::~ModelBrowser() {
delete _downloader;
}
void ModelBrowser::downloadFinished() {
parseXML(_downloader->getData());
}
void ModelBrowser::browse() {
QDialog dialog(this);
dialog.setWindowTitle("Browse models");
QGridLayout* layout = new QGridLayout(&dialog);
dialog.setLayout(layout);
QLineEdit* searchBar = new QLineEdit(&dialog);
layout->addWidget(searchBar, 0, 0);
ListView* listView = new ListView(&dialog);
listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
layout->addWidget(listView, 1, 0);
listView->connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(keyboardSearch(const QString&)));
dialog.connect(listView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept()));
QStringListModel* model = new QStringListModel(_models.keys(), listView);
model->sort(0);
listView->setModel(model);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
layout->addWidget(buttons, 2, 0);
dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
if (dialog.exec() == QDialog::Rejected) {
return;
}
QString selectedKey = model->data(listView->currentIndex(), Qt::DisplayRole).toString();
emit selected(_models[selectedKey]);
}
bool ModelBrowser::parseXML(QByteArray xmlFile) {
QXmlStreamReader xml(xmlFile);
QRegExp rx(".*fst");
bool truncated = false;
QString lastKey;
// Read xml until the end or an error is detected
while(!xml.atEnd() && !xml.hasError()) {
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) {
while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) {
// Let's check if there is more
xml.readNext();
if (xml.text().toString() == "True") {
truncated = true;
}
}
}
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) {
while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) {
// If a file is find, process it
if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) {
xml.readNext();
lastKey = xml.text().toString();
if (rx.exactMatch(xml.text().toString())) {
// Add the found file to the list
_models.insert(QFileInfo(xml.text().toString()).baseName(),
S3_URL + "/" + xml.text().toString());
}
}
xml.readNext();
}
}
xml.readNext();
}
// Error handling
if(xml.hasError()) {
_models.clear();
QMessageBox::critical(this,
"ModelBrowser::ModelBrowser()",
xml.errorString(),
QMessageBox::Ok);
return false;
}
// If we didn't all the files, download the next ones
if (truncated) {
QUrl url(S3_URL);
QUrlQuery query;
if (_type == Head) {
query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION);
} else if (_type == Skeleton) {
query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION);
}
query.addQueryItem(MARKER_PARAMETER_NAME, lastKey);
url.setQuery(query);
delete _downloader;
_downloader = new FileDownloader(url);
connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished()));
}
return true;
}

View file

@ -0,0 +1,62 @@
//
// ModelBrowser.h
// hifi
//
// Created by Clement on 3/17/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__ModelBrowser__
#define __hifi__ModelBrowser__
#include <FileDownloader.h>
#include <QDialog>
#include <QListView>
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
static const QString HEAD_MODELS_LOCATION = "models/heads/";
static const QString SKELETON_MODELS_LOCATION = "models/skeletons/";
enum ModelType {
Head,
Skeleton
};
class ModelBrowser : public QWidget {
Q_OBJECT
public:
ModelBrowser(ModelType modelType, QWidget* parent = NULL);
~ModelBrowser();
signals:
void selected(QString);
public slots:
void browse();
private slots:
void downloadFinished();
private:
ModelType _type;
FileDownloader* _downloader;
QHash<QString, QString> _models;
bool parseXML(QByteArray xmlFile);
};
class ListView : public QListView {
Q_OBJECT
public:
ListView(QWidget* parent) : QListView(parent) {}
public slots:
void keyboardSearch(const QString& text) {
QAbstractItemView::keyboardSearch(text);
}
};
#endif /* defined(__hifi__ModelBrowser__) */

View file

@ -313,7 +313,7 @@ void ImageReader::run() {
int imageArea = image.width() * image.height();
if (opaquePixels == imageArea) {
qDebug() << "Image with alpha channel is completely opaque:" << url;
image.convertToFormat(QImage::Format_RGB888);
image = image.convertToFormat(QImage::Format_RGB888);
}
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image),
Q_ARG(bool, translucentPixels >= imageArea / 2));

View file

@ -50,6 +50,7 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
int16_t numSilentSamples;
memcpy(&numSilentSamples, packet.data() + readBytes, sizeof(int16_t));
readBytes += sizeof(int16_t);
addSilentFrame(numSilentSamples);

View file

@ -88,6 +88,17 @@ QByteArray AvatarData::toByteArray() {
// Body scale
destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _targetScale);
// Head rotation (NOTE: This needs to become a quaternion to save two bytes)
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getTweakedYaw());
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getTweakedPitch());
destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getTweakedRoll());
// Head lean X,Z (head lateral and fwd/back motion relative to torso)
memcpy(destinationBuffer, &_headData->_leanSideways, sizeof(_headData->_leanSideways));
destinationBuffer += sizeof(_headData->_leanSideways);
memcpy(destinationBuffer, &_headData->_leanForward, sizeof(_headData->_leanForward));
destinationBuffer += sizeof(_headData->_leanForward);
// Lookat Position
memcpy(destinationBuffer, &_headData->_lookAtPosition, sizeof(_headData->_lookAtPosition));
destinationBuffer += sizeof(_headData->_lookAtPosition);
@ -191,6 +202,21 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
// Body scale
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale);
// Head rotation (NOTE: This needs to become a quaternion to save two bytes)
float headYaw, headPitch, headRoll;
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll);
_headData->setYaw(headYaw);
_headData->setPitch(headPitch);
_headData->setRoll(headRoll);
// Head position relative to pelvis
memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways));
sourceBuffer += sizeof(float);
memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward));
sourceBuffer += sizeof(_headData->_leanForward);
// Lookat Position
memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition));
sourceBuffer += sizeof(_headData->_lookAtPosition);

View file

@ -0,0 +1,65 @@
//
// FileDownloader.cpp
// hifi
//
// Created by Clement Brisset on 3/14/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
//
#include <QUrl>
#include <QNetworkRequest>
#include <QEventLoop>
#include <QTimer>
#include "FileDownloader.h"
FileDownloader::FileDownloader(const QUrl dataURL, QObject* parent) :
QObject(parent),
_done(false)
{
connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*)));
QNetworkRequest request(dataURL);
_networkAccessManager.get(request);
}
void FileDownloader::processReply(QNetworkReply *reply) {
if (reply->error() == QNetworkReply::NoError) {
_downloadedData = reply->readAll();
}
reply->deleteLater();
_done = true;
emit done(reply->error());
}
void FileDownloader::waitForFile(int timeout) {
QTimer timer;
QEventLoop loop;
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
connect(this, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit()));
if (!_done) {
if (timeout > 0) {
timer.start(timeout);
}
loop.exec();
}
}
QByteArray FileDownloader::download(const QUrl dataURL, int timeout) {
QTimer timer;
QEventLoop loop;
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit));
FileDownloader downloader(dataURL);
connect(&downloader, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit()));
if (timeout > 0) {
timer.start(timeout);
}
loop.exec();
return downloader.getData();
}

View file

@ -0,0 +1,44 @@
//
// FileDownloader.h
// hifi
//
// Created by Clement Brisset on 3/14/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
//
#ifndef __hifi__FileDownloader__
#define __hifi__FileDownloader__
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
class FileDownloader : public QObject {
Q_OBJECT
public:
FileDownloader(const QUrl dataURL, QObject* parent = NULL);
void waitForFile(int timeout = 0);
QByteArray getData() const { return _downloadedData; }
bool done() { return _done; }
static QByteArray download(const QUrl dataURL, int timeout = 0);
signals:
void done(QNetworkReply::NetworkError);
private slots:
void processReply(QNetworkReply* reply);
private:
QNetworkAccessManager _networkAccessManager;
QByteArray _downloadedData;
bool _done;
};
#endif /* defined(__hifi__FileDownloader__) */

View file

@ -45,7 +45,9 @@ int packArithmeticallyCodedValue(int value, char* destination) {
PacketVersion versionForPacketType(PacketType type) {
switch (type) {
case PacketTypeAvatarData:
return 2;
return 3;
case PacketTypeEnvironmentData:
return 1;
case PacketTypeParticleData:
return 1;
case PacketTypeDomainList:

View file

@ -14,6 +14,7 @@
// GameEngine.cpp
EnvironmentData::EnvironmentData(int id) :
_id(id),
_flat(true),
_gravity(0.0f),
_atmosphereCenter(0, -1000, 0),
_atmosphereInnerRadius(1000),
@ -25,12 +26,23 @@ EnvironmentData::EnvironmentData(int id) :
_sunBrightness(20.0f) {
}
glm::vec3 EnvironmentData::getAtmosphereCenter(const glm::vec3& cameraPosition) const {
return _atmosphereCenter + (_flat ? glm::vec3(cameraPosition.x, 0.0f, cameraPosition.z) : glm::vec3());
}
glm::vec3 EnvironmentData::getSunLocation(const glm::vec3& cameraPosition) const {
return _sunLocation + (_flat ? glm::vec3(cameraPosition.x, 0.0f, cameraPosition.z) : glm::vec3());
}
int EnvironmentData::getBroadcastData(unsigned char* destinationBuffer) const {
unsigned char* bufferStart = destinationBuffer;
memcpy(destinationBuffer, &_id, sizeof(_id));
destinationBuffer += sizeof(_id);
memcpy(destinationBuffer, &_flat, sizeof(_flat));
destinationBuffer += sizeof(_flat);
memcpy(destinationBuffer, &_gravity, sizeof(_gravity));
destinationBuffer += sizeof(_gravity);
@ -67,6 +79,9 @@ int EnvironmentData::parseData(const unsigned char* sourceBuffer, int numBytes)
memcpy(&_id, sourceBuffer, sizeof(_id));
sourceBuffer += sizeof(_id);
memcpy(&_flat, sourceBuffer, sizeof(_flat));
sourceBuffer += sizeof(_flat);
memcpy(&_gravity, sourceBuffer, sizeof(_gravity));
sourceBuffer += sizeof(_gravity);

View file

@ -19,6 +19,9 @@ public:
void setID(int id) { _id = id; }
int getID() const { return _id; }
void setFlat(bool flat) { _flat = flat; }
bool isFlat() const { return _flat; }
void setGravity(float gravity) { _gravity = gravity; }
float getGravity() const { return _gravity; }
@ -42,6 +45,9 @@ public:
const glm::vec3& getSunLocation() const { return _sunLocation; }
float getSunBrightness() const { return _sunBrightness; }
glm::vec3 getAtmosphereCenter(const glm::vec3& cameraPosition) const;
glm::vec3 getSunLocation(const glm::vec3& cameraPosition) const;
int getBroadcastData(unsigned char* destinationBuffer) const;
int parseData(const unsigned char* sourceBuffer, int numBytes);
@ -49,6 +55,8 @@ private:
int _id;
bool _flat;
float _gravity;
glm::vec3 _atmosphereCenter;