resolve conflicts on merge with upstream master

This commit is contained in:
Stephen Birarda 2014-02-21 15:16:15 -08:00
commit 340248ac43
43 changed files with 2230 additions and 181 deletions

View file

@ -113,6 +113,10 @@ void Agent::run() {
// setup an Avatar for the script to use
AvatarData scriptedAvatar;
// call model URL setters with empty URLs so our avatar, if user, will have the default models
scriptedAvatar.setFaceModelURL(QUrl());
scriptedAvatar.setSkeletonModelURL(QUrl());
// give this AvatarData object to the script engine
_scriptEngine.setAvatarData(&scriptedAvatar, "Avatar");

View file

@ -29,7 +29,7 @@ class Agent : public ThreadedAssignment {
public:
Agent(const QByteArray& packet);
void setIsAvatar(bool isAvatar) { _scriptEngine.setIsAvatar(isAvatar); }
void setIsAvatar(bool isAvatar) { QMetaObject::invokeMethod(&_scriptEngine, "setIsAvatar", Q_ARG(bool, isAvatar)); }
bool isAvatar() const { return _scriptEngine.isAvatar(); }
public slots:

View file

@ -19,7 +19,7 @@
#include "AssignmentClient.h"
const char ASSIGNMENT_CLIENT_TARGET_NAME[] = "assignment-client";
const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client";
const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000;
int hifiSockAddrMeta = qRegisterMetaType<HifiSockAddr>("HifiSockAddr");

View file

@ -12,7 +12,7 @@
const char* NUM_FORKS_PARAMETER = "-n";
const char ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME[] = "assignment-client-monitor";
const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor";
AssignmentClientMonitor::AssignmentClientMonitor(int &argc, char **argv, int numAssignmentClientForks) :
QCoreApplication(argc, argv)

View file

@ -52,7 +52,7 @@
const short JITTER_BUFFER_MSECS = 12;
const short JITTER_BUFFER_SAMPLES = JITTER_BUFFER_MSECS * (SAMPLE_RATE / 1000.0);
const char AUDIO_MIXER_LOGGING_TARGET_NAME[] = "audio-mixer";
const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer";
void attachNewBufferToNode(Node *newNode) {
if (!newNode->getLinkedData()) {

View file

@ -24,7 +24,7 @@
#include "AvatarMixer.h"
const char AVATAR_MIXER_LOGGING_NAME[] = "avatar-mixer";
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
const unsigned int AVATAR_DATA_SEND_INTERVAL_USECS = (1 / 60.0) * 1000 * 1000;
@ -93,7 +93,7 @@ void broadcastIdentityPacket() {
NodeList* nodeList = NodeList::getInstance();
QByteArray avatarIdentityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity);
QByteArray avatarIdentityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
int numPacketHeaderBytes = avatarIdentityPacket.size();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
@ -128,7 +128,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
&& killedNode->getLinkedData()) {
// this was an avatar we were sending to other people
// send a kill packet for it to our other nodes
QByteArray killPacket = byteArrayWithPopluatedHeader(PacketTypeKillAvatar);
QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
killPacket += killedNode->getUUID().toRfc4122();
NodeList::getInstance()->broadcastToNodes(killPacket,
@ -159,7 +159,7 @@ void AvatarMixer::readPendingDatagrams() {
if (nodeData->hasIdentityChangedAfterParsing(receivedPacket)
&& !nodeData->hasSentIdentityBetweenKeyFrames()) {
// this avatar changed their identity in some way and we haven't sent a packet in this keyframe
QByteArray identityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity);
QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
QByteArray individualByteArray = nodeData->identityByteArray();
individualByteArray.replace(0, NUM_BYTES_RFC4122_UUID, avatarNode->getUUID().toRfc4122());

View file

@ -32,7 +32,7 @@ void MetavoxelServer::removeSession(const QUuid& sessionId) {
_sessions.take(sessionId)->deleteLater();
}
const char METAVOXEL_SERVER_LOGGING_NAME[] = "metavoxel-server";
const QString METAVOXEL_SERVER_LOGGING_NAME = "metavoxel-server";
void MetavoxelServer::run() {
commonInit(METAVOXEL_SERVER_LOGGING_NAME, NodeType::MetavoxelServer);

View file

@ -121,7 +121,7 @@ void DataServer::readPendingDatagrams() {
if (reply->type == REDIS_REPLY_STATUS && strcmp("OK", reply->str) == 0) {
// if redis stored the value successfully reply back with a confirm
// which is a reply packet with the sequence number
QByteArray replyPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerConfirm, _uuid);
QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerConfirm, _uuid);
replyPacket.append(sequenceNumber);
@ -132,7 +132,7 @@ void DataServer::readPendingDatagrams() {
} else {
// setup a send packet with the returned data
// leverage the packetData sent by overwriting and appending
QByteArray sendPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerSend, _uuid);
QByteArray sendPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerSend, _uuid);
QDataStream sendPacketStream(&sendPacket, QIODevice::Append);
sendPacketStream << sequenceNumber;

View file

@ -464,7 +464,7 @@ void DomainServer::readAvailableDatagrams() {
HifiSockAddr senderSockAddr;
static QByteArray assignmentPacket = byteArrayWithPopluatedHeader(PacketTypeCreateAssignment);
static QByteArray assignmentPacket = byteArrayWithPopulatedHeader(PacketTypeCreateAssignment);
static int numAssignmentPacketHeaderBytes = assignmentPacket.size();
QByteArray receivedPacket;

34
examples/bot.js Normal file
View file

@ -0,0 +1,34 @@
//
// bot.js
// hifi
//
// Created by Brad Hefta-Gaub on 2/20/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
// This is an example script that demonstrates an NPC avatar.
//
//
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// choose a random x and y in the range of 0 to 50
positionX = getRandomFloat(0, 50);
positionZ = getRandomFloat(0, 50);
// change the avatar's position to the random one
Avatar.position = {x: positionX, y: 0, z: positionZ};
// pick an integer between 1 and 20 for the face model for this bot
botNumber = getRandomInt(1, 20);
// set the face model fst using the bot number
// there is no need to change the body model - we're using the default
Avatar.faceModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/bot" + botNumber + ".fst";
Agent.isAvatar = true;

View file

@ -41,7 +41,7 @@ if (WIN32)
# which are not accessible on windows without glew or some other dynamic linking mechanism
set(GLEW_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/external/glew)
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${GLEW_ROOT_PATH})
include_directories(SYSTEM ${GLEW_ROOT_PATH}/include)
include_directories(SYSTEM ${GLEW_ROOT_PATH}/include ${GLUT_ROOT_PATH}/include)
#set(GL_HEADERS "#define GLEW_STATIC\n#define FREEGLUT_STATIC\n#define FREEGLUT_LIB_PRAGMAS 0\n#include <GL/glew.h>\n#include <GL/wglew.h>\n#include <GL/freeglut_std.h>\n#include <GL/freeglut_ext.h>")
set(GL_HEADERS "#define GLEW_STATIC\n#include <windowshacks.h>\n#include <GL/glew.h>\n#include <GL/glut.h>")

View file

@ -36,7 +36,7 @@ QPushButton {
border-radius: 9px;
font-size: 18px;
padding: 17px 0px 15px;
width: 120px;
width: 150px;
margin-top: 20px;
margin-bottom: 18px;
}

View file

@ -259,7 +259,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
// send the identity packet for our avatar each second to our avatar mixer
QTimer* identityPacketTimer = new QTimer();
connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket);
identityPacketTimer->start(1000);
identityPacketTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
@ -2404,7 +2404,7 @@ void Application::updateMyAvatar(float deltaTime) {
_myAvatar->update(deltaTime);
// send head/hand data to the avatar mixer and voxel server
QByteArray packet = byteArrayWithPopluatedHeader(PacketTypeAvatarData);
QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
packet.append(_myAvatar->toByteArray());
controlledBroadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer);

View file

@ -15,7 +15,7 @@
#include <QJsonObject>
const QString WINDOW_NAME = QObject::tr("Import Voxels");
const QString IMPORT_BUTTON_NAME = QObject::tr("Import");
const QString IMPORT_BUTTON_NAME = QObject::tr("Import Voxels");
const QString LOADING_BUTTON_NAME = QObject::tr("Loading ...");
const QString PLACE_BUTTON_NAME = QObject::tr("Place voxels");
const QString IMPORT_INFO = QObject::tr("<b>Import</b> %1 as voxels");
@ -186,16 +186,26 @@ void ImportDialog::setLayout() {
_progressBar.setFixedHeight(progressBarHeight);
_progressBar.setTextVisible(false);
QGridLayout* subLayout = new QGridLayout();
subLayout->addWidget(findChild<QWidget*>("lookInLabel"), 0, 0, 1, 5);
QSize BUTTON_SIZE = QSize(43, 33);
QPushButton* button = (QPushButton*) findChild<QWidget*>("backButton");
button->setIcon(QIcon());
button->setFixedSize(BUTTON_SIZE);
subLayout->addWidget(button, 1, 0, 1, 1);
button = (QPushButton*) findChild<QWidget*>("forwardButton");
button->setIcon(QIcon());
button->setFixedSize(BUTTON_SIZE);
subLayout->addWidget(button, 1, 1, 1, 1);
button = (QPushButton*) findChild<QWidget*>("toParentButton");
button->setIcon(QIcon());
button->setFixedSize(BUTTON_SIZE);
subLayout->addWidget(button, 1, 2, 1, 1);
gridLayout->addLayout(subLayout, 0, 0, 1, 1);
// hide unused embedded widgets in QFileDialog
QWidget* widget = findChild<QWidget*>("lookInCombo");

View file

@ -437,6 +437,20 @@ Menu::Menu() :
appInstance->getVoxels(),
SLOT(trueColorize()));
addCheckableActionToQMenuAndActionHash(renderDebugMenu,
MenuOption::CullSharedFaces,
Qt::CTRL | Qt::SHIFT | Qt::Key_C,
false,
appInstance->getVoxels(),
SLOT(cullSharedFaces()));
addCheckableActionToQMenuAndActionHash(renderDebugMenu,
MenuOption::ShowCulledSharedFaces,
Qt::CTRL | Qt::SHIFT | Qt::Key_X,
false,
appInstance->getVoxels(),
SLOT(showCulledSharedFaces()));
addDisabledActionAndSeparator(renderDebugMenu, "Coverage Maps");
addActionToQMenuAndActionHash(renderDebugMenu,
MenuOption::FalseColorOccluded,

View file

@ -192,6 +192,7 @@ namespace MenuOption {
const QString CopyVoxels = "Copy";
const QString CoverageMap = "Render Coverage Map";
const QString CoverageMapV2 = "Render Coverage Map V2";
const QString CullSharedFaces = "Cull Shared Voxel Faces";
const QString CutVoxels = "Cut";
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DecreaseVoxelSize = "Decrease Voxel Size";
@ -274,6 +275,7 @@ namespace MenuOption {
const QString Shadows = "Shadows";
const QString SettingsExport = "Export Settings";
const QString ShowAllLocalVoxels = "Show All Local Voxels";
const QString ShowCulledSharedFaces = "Show Culled Shared Voxel Faces";
const QString ShowTrueColors = "Show TRUE Colors";
const QString SuppressShortTimings = "Suppress Timings Less than 10ms";
const QString Stars = "Stars";

View file

@ -180,7 +180,7 @@ bool MetavoxelSystem::PointVisitor::visit(MetavoxelInfo& info) {
}
static QByteArray createDatagramHeader(const QUuid& sessionID) {
QByteArray header = byteArrayWithPopluatedHeader(PacketTypeMetavoxelData);
QByteArray header = byteArrayWithPopulatedHeader(PacketTypeMetavoxelData);
header += sessionID.toRfc4122();
return header;
}

View file

@ -0,0 +1,740 @@
///
/// @file PrimitiveRenderer.cpp
/// A geometric primitive renderer.
///
/// @author: Norman Crafts
/// @copyright 2014, High Fidelity, Inc. All rights reserved.
///
#include <QMutexLocker>
#include "InterfaceConfig.h"
#include "OctreeElement.h"
#include "PrimitiveRenderer.h"
Primitive::Primitive() {
}
Primitive::~Primitive() {
}
// Simple dispatch between API and SPI
const VertexElementList& Primitive::vertexElements() const {
return vVertexElements();
}
VertexElementIndexList& Primitive::vertexElementIndices() {
return vVertexElementIndices();
}
TriElementList& Primitive::triElements() {
return vTriElements();
}
void Primitive::releaseVertexElements() {
vReleaseVertexElements();
}
unsigned long Primitive::getMemoryUsage() {
return vGetMemoryUsage();
}
Cube::Cube(
float x,
float y,
float z,
float s,
unsigned char r,
unsigned char g,
unsigned char b,
unsigned char faceExclusions
) :
_cpuMemoryUsage(0) {
init(x, y, z, s, r, g, b, faceExclusions);
}
Cube::~Cube() {
terminate();
}
void Cube::init(
float x,
float y,
float z,
float s,
unsigned char r,
unsigned char g,
unsigned char b,
unsigned char faceExclusions
) {
initializeVertices(x, y, z, s, r, g, b, faceExclusions);
initializeTris(faceExclusions);
}
void Cube::terminate() {
terminateTris();
terminateVertices();
}
void Cube::initializeVertices(
float x,
float y,
float z,
float s,
unsigned char r,
unsigned char g,
unsigned char b,
unsigned char faceExclusions
) {
for (int i = 0; i < _sNumVerticesPerCube; i++) {
// Check whether the vertex is necessary for the faces indicated by faceExclusions bit mask.
// uncomment this line to load all faces: if (~0x00 & _sFaceIndexToHalfSpaceMask[i >> 2]) {
// uncomment this line to include shared faces: if (faceExclusions & _sFaceIndexToHalfSpaceMask[i >> 2]) {
// uncomment this line to exclude shared faces:
if (~faceExclusions & _sFaceIndexToHalfSpaceMask[i >> 2]) {
VertexElement* v = new VertexElement();
if (v) {
// Construct vertex position
v->position.x = x + s * _sVertexIndexToConstructionVector[i][0];
v->position.y = y + s * _sVertexIndexToConstructionVector[i][1];
v->position.z = z + s * _sVertexIndexToConstructionVector[i][2];
// Construct vertex normal
v->normal.x = _sVertexIndexToNormalVector[i >> 2][0];
v->normal.y = _sVertexIndexToNormalVector[i >> 2][1];
v->normal.z = _sVertexIndexToNormalVector[i >> 2][2];
// Construct vertex color
//#define FALSE_COLOR
#ifndef FALSE_COLOR
v->color.r = r;
v->color.g = g;
v->color.b = b;
v->color.a = 255;
#else
static unsigned char falseColor[6][3] = {
192, 0, 0, // Bot
0, 192, 0, // Top
0, 0, 192, // Right
192, 0, 192, // Left
192, 192, 0, // Near
192, 192, 192 // Far
};
v->color.r = falseColor[i >> 2][0];
v->color.g = falseColor[i >> 2][1];
v->color.b = falseColor[i >> 2][2];
v->color.a = 255;
#endif
// Add vertex element to list
_vertices.push_back(v);
_cpuMemoryUsage += sizeof(VertexElement);
_cpuMemoryUsage += sizeof(VertexElement*);
}
}
}
}
void Cube::terminateVertices() {
for (VertexElementList::iterator it = _vertices.begin(); it != _vertices.end(); ++it) {
delete *it;
}
_cpuMemoryUsage -= _vertices.size() * (sizeof(VertexElement) + sizeof(VertexElement*));
_vertices.clear();
}
void Cube::initializeTris(
unsigned char faceExclusions
) {
int index = 0;
for (int i = 0; i < _sNumFacesPerCube; i++) {
// Check whether the vertex is necessary for the faces indicated by faceExclusions bit mask.
// uncomment this line to load all faces: if (~0x00 & _sFaceIndexToHalfSpaceMask[i]) {
// uncomment this line to include shared faces: if (faceExclusions & _sFaceIndexToHalfSpaceMask[i]) {
// uncomment this line to exclude shared faces:
if (~faceExclusions & _sFaceIndexToHalfSpaceMask[i]) {
int start = index;
// Create the triangulated face, two tris, six indices referencing four vertices, both
// with cw winding order, such that:
// A-B
// |\|
// D-C
// Store triangle ABC
TriElement* tri = new TriElement();
if (tri) {
tri->indices[0] = index++;
tri->indices[1] = index++;
tri->indices[2] = index;
// Add tri element to list
_tris.push_back(tri);
_cpuMemoryUsage += sizeof(TriElement);
_cpuMemoryUsage += sizeof(TriElement*);
}
// Now store triangle ACD
tri = new TriElement();
if (tri) {
tri->indices[0] = start;
tri->indices[1] = index++;
tri->indices[2] = index++;
// Add tri element to list
_tris.push_back(tri);
_cpuMemoryUsage += sizeof(TriElement);
_cpuMemoryUsage += sizeof(TriElement*);
}
}
}
}
void Cube::terminateTris() {
for (TriElementList::iterator it = _tris.begin(); it != _tris.end(); ++it) {
delete *it;
}
_cpuMemoryUsage -= _tris.size() * (sizeof(TriElement) + sizeof(TriElement*));
_tris.clear();
}
const VertexElementList& Cube::vVertexElements() const {
return _vertices;
}
VertexElementIndexList& Cube::vVertexElementIndices() {
return _vertexIndices;
}
TriElementList& Cube::vTriElements() {
return _tris;
}
void Cube::vReleaseVertexElements() {
terminateVertices();
}
unsigned long Cube::vGetMemoryUsage() {
return _cpuMemoryUsage;
}
unsigned char Cube::_sFaceIndexToHalfSpaceMask[6] = {
OctreeElement::HalfSpace::Bottom,
OctreeElement::HalfSpace::Top,
OctreeElement::HalfSpace::Right,
OctreeElement::HalfSpace::Left,
OctreeElement::HalfSpace::Near,
OctreeElement::HalfSpace::Far,
};
// Construction vectors ordered such that the vertices of each face are
// clockwise in a right-handed coordinate system with B-L-N at 0,0,0.
float Cube::_sVertexIndexToConstructionVector[24][3] = {
// Bottom
{ 0,0,0 },
{ 1,0,0 },
{ 1,0,1 },
{ 0,0,1 },
// Top
{ 0,1,0 },
{ 0,1,1 },
{ 1,1,1 },
{ 1,1,0 },
// Right
{ 1,0,0 },
{ 1,1,0 },
{ 1,1,1 },
{ 1,0,1 },
// Left
{ 0,0,0 },
{ 0,0,1 },
{ 0,1,1 },
{ 0,1,0 },
// Near
{ 0,0,0 },
{ 0,1,0 },
{ 1,1,0 },
{ 1,0,0 },
// Far
{ 0,0,1 },
{ 1,0,1 },
{ 1,1,1 },
{ 0,1,1 },
};
// Normals for a right-handed coordinate system
float Cube::_sVertexIndexToNormalVector[6][3] = {
{ 0,-1, 0 }, // Bottom
{ 0, 1, 0 }, // Top
{ 1, 0, 0 }, // Right
{ -1, 0, 0 }, // Left
{ 0, 0,-1 }, // Near
{ 0, 0, 1 }, // Far
};
Renderer::Renderer() {
}
Renderer::~Renderer() {
}
// Simple dispatch between API and SPI
int Renderer::add(
Primitive* primitive
) {
return vAdd(primitive);
}
void Renderer::remove(
int id
) {
vRemove(id);
}
void Renderer::release() {
vRelease();
}
void Renderer::render() {
vRender();
}
unsigned long Renderer::getMemoryUsage() {
return vGetMemoryUsage();
}
unsigned long Renderer::getMemoryUsageGPU() {
return vGetMemoryUsageGPU();
}
PrimitiveRenderer::PrimitiveRenderer(
int maxCount
) :
_maxCount(maxCount),
_triBufferId(0),
_vertexBufferId(0),
_vertexElementCount(0),
_maxVertexElementCount(0),
_triElementCount(0),
_maxTriElementCount(0),
_primitives(),
_primitiveCount(0),
_gpuMemoryUsage(0),
_cpuMemoryUsage(0)
{
init();
}
PrimitiveRenderer::~PrimitiveRenderer() {
terminate();
}
void PrimitiveRenderer::init() {
initializeGL();
initializeBookkeeping();
}
void PrimitiveRenderer::initializeGL() {
glGenBuffers(1, &_triBufferId);
glGenBuffers(1, &_vertexBufferId);
// Set up the element array buffer containing the index ids
_maxTriElementCount = _maxCount * 2;
int size = _maxTriElementCount * _sIndicesPerTri * sizeof(GLint);
_gpuMemoryUsage += size;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _triBufferId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, 0, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// Set up the array buffer in the form of array of structures
// I chose AOS because it maximizes the amount of data tranferred
// by a single glBufferSubData call.
_maxVertexElementCount = _maxCount * 8;
size = _maxVertexElementCount * sizeof(VertexElement);
_gpuMemoryUsage += size;
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferId);
glBufferData(GL_ARRAY_BUFFER, size, 0, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// Initialize the first tri element in the buffer to all zeros, the
// degenerate case
deconstructTriElement(0);
// Initialize the first vertex element in the buffer to all zeros, the
// degenerate case
deconstructVertexElement(0);
}
void PrimitiveRenderer::initializeBookkeeping() {
// Start primitive count at one, because zero is reserved for the degenerate triangle
_primitives.resize(_maxCount + 1);
// Set the counters
_primitiveCount = 1;
_vertexElementCount = 1;
_triElementCount = 1;
// Guesstimate the memory consumption
_cpuMemoryUsage = sizeof(PrimitiveRenderer);
_cpuMemoryUsage += _availablePrimitiveIndex.capacity() * sizeof(int);
_cpuMemoryUsage += _availableVertexElementIndex.capacity() * sizeof(int);
_cpuMemoryUsage += _availableTriElementIndex.capacity() * sizeof(int);
_cpuMemoryUsage += _deconstructTriElementIndex.capacity() * sizeof(int);
_cpuMemoryUsage += _constructPrimitiveIndex.capacity() * sizeof(int);
}
void PrimitiveRenderer::terminate() {
terminateBookkeeping();
terminateGL();
}
void PrimitiveRenderer::terminateGL() {
if (_vertexBufferId) {
glDeleteBuffers(1, &_vertexBufferId);
_vertexBufferId = 0;
}
if (_triBufferId) {
glDeleteBuffers(1, &_triBufferId);
_triBufferId = 0;
}
}
void PrimitiveRenderer::terminateBookkeeping() {
// Delete all of the primitives
for (int i = _primitiveCount + 1; --i > 0; ) {
Primitive* primitive = _primitives[i];
if (primitive) {
_cpuMemoryUsage -= primitive->getMemoryUsage();
_primitives[i] = 0;
delete primitive;
}
}
// Drain the queues
_availablePrimitiveIndex.clear();
_availableVertexElementIndex.clear();
_availableTriElementIndex.clear();
_deconstructTriElementIndex.clear();
_constructPrimitiveIndex.clear();
_cpuMemoryUsage = sizeof(PrimitiveRenderer) + _primitives.size() * sizeof(Primitive *);
}
void PrimitiveRenderer::constructElements(
Primitive* primitive
) {
// Load vertex elements
VertexElementIndexList& vertexElementIndexList = primitive->vertexElementIndices();
const VertexElementList& vertices = primitive->vertexElements();
{
for (VertexElementList::const_iterator it = vertices.begin(); it != vertices.end(); ++it ) {
int index = getAvailableVertexElementIndex();
if (index != 0) {
// Store the vertex element index in the primitive's
// vertex element index list
vertexElementIndexList.push_back(index);
VertexElement* vertex = *it;
transferVertexElement(index, vertex);
} else {
break;
}
}
}
// Load tri elements
if (vertexElementIndexList.size() == vertices.size()) {
TriElementList& tris = primitive->triElements();
for (TriElementList::iterator it = tris.begin(); it != tris.end(); ++it) {
TriElement* tri = *it;
int index = getAvailableTriElementIndex();
if (index != 0) {
int k;
k = tri->indices[0];
tri->indices[0] = vertexElementIndexList[k];
k = tri->indices[1];
tri->indices[1] = vertexElementIndexList[k];
k = tri->indices[2];
tri->indices[2] = vertexElementIndexList[k];
tri->id = index;
transferTriElement(index, tri->indices);
} else {
break;
}
}
} else {
// TODO: failure mode
}
}
void PrimitiveRenderer::deconstructElements(
Primitive* primitive
) {
// Schedule the tri elements of the face for deconstruction
{
TriElementList& tris = primitive->triElements();
for (TriElementList::const_iterator it = tris.begin(); it != tris.end(); ++it) {
const TriElement* tri = *it;
if (tri->id) {
// Put the tri element index into decon queue
_deconstructTriElementIndex.push(tri->id);
}
}
}
// Return the vertex element index to the available queue, it is not necessary
// to zero the data
{
VertexElementIndexList& vertexIndexList = primitive->vertexElementIndices();
for (VertexElementIndexList::const_iterator it = vertexIndexList.begin(); it != vertexIndexList.end(); ++it) {
int index = *it;
if (index) {
// Put the vertex element index into the available queue
_availableVertexElementIndex.push(index);
}
}
}
delete primitive;
}
int PrimitiveRenderer::getAvailablePrimitiveIndex() {
int index;
// Check the available primitive index queue first for an available index.
if (!_availablePrimitiveIndex.isEmpty()) {
index = _availablePrimitiveIndex.pop();
} else if (_primitiveCount < _maxCount) {
// There are no primitive indices available from the queue,
// make one up
index = _primitiveCount++;
} else {
index = 0;
}
return index;
}
int PrimitiveRenderer::getAvailableVertexElementIndex() {
int index;
// Check the available vertex element queue first for an available index.
if (!_availableVertexElementIndex.isEmpty()) {
index = _availableVertexElementIndex.pop();
} else if (_vertexElementCount < _maxVertexElementCount) {
// There are no vertex elements available from the queue,
// grab one from the end of the list
index = _vertexElementCount++;
} else {
index = 0;
}
return index;
}
int PrimitiveRenderer::getAvailableTriElementIndex() {
int index;
// Check the tri elements scheduled for deconstruction queue first to
// intercept and reuse an index without it having to be destroyed
if (!_deconstructTriElementIndex.isEmpty()) {
index = _deconstructTriElementIndex.pop();
} else if (!_availableTriElementIndex.isEmpty()) {
// Nothing available in the deconstruction queue, now
// check the available tri element queue for an available index.
index = _availableTriElementIndex.pop();
} else if (_triElementCount < _maxTriElementCount) {
// There are no reusable tri elements available from the queue,
// grab one from the end of the list
index = _triElementCount++;
} else {
index = 0;
}
return index;
}
void PrimitiveRenderer::deconstructTriElement(
int idx
) {
// Set the tri element to the degenerate case.
static int degenerate[3] = { 0, 0, 0 };
transferTriElement(idx, degenerate);
}
void PrimitiveRenderer::deconstructVertexElement(
int idx
) {
// Set the vertex element to the degenerate case.
VertexElement degenerate;
memset(&degenerate, 0, sizeof(degenerate));
transferVertexElement(idx, &degenerate);
}
void PrimitiveRenderer::transferVertexElement(
int idx,
VertexElement* vertex
) {
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferId);
glBufferSubData(GL_ARRAY_BUFFER, idx * sizeof(VertexElement), sizeof(VertexElement), vertex);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void PrimitiveRenderer::transferTriElement(
int idx,
int tri[3]
) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _triBufferId);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, idx * _sBytesPerTriElement, _sBytesPerTriElement, tri);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
int PrimitiveRenderer::vAdd(
Primitive* primitive
) {
QMutexLocker lock(&_guard);
int id = getAvailablePrimitiveIndex();
if (id != 0) {
// Take ownership of primitive, including responsibility
// for destruction
_primitives[id] = primitive;
_constructPrimitiveIndex.push(id);
_cpuMemoryUsage += primitive->getMemoryUsage();
}
return id;
}
void PrimitiveRenderer::vRemove(
int id
) {
if (id != 0) {
QMutexLocker lock(&_guard);
// Locate and remove the primitive by id in the vector map
Primitive* primitive = _primitives[id];
if (primitive) {
_primitives[id] = 0;
_cpuMemoryUsage -= primitive->getMemoryUsage();
deconstructElements(primitive);
// Queue the index onto the available primitive stack.
_availablePrimitiveIndex.push(id);
}
}
}
void PrimitiveRenderer::vRelease() {
QMutexLocker lock(&_guard);
terminateBookkeeping();
initializeBookkeeping();
}
void PrimitiveRenderer::vRender() {
int id;
QMutexLocker lock(&_guard);
// Iterate over the set of triangle element array buffer ids scheduled for
// destruction. Set the triangle element to the degenerate case. Queue the id
// onto the available tri element stack.
while (!_deconstructTriElementIndex.isEmpty()) {
id = _deconstructTriElementIndex.pop();
deconstructTriElement(id);
_availableTriElementIndex.push(id);
}
// Iterate over the set of primitive ids scheduled for construction. Transfer
// primitive data to the GPU.
while (!_constructPrimitiveIndex.isEmpty()) {
id = _constructPrimitiveIndex.pop();
Primitive* primitive = _primitives[id];
if (primitive) {
constructElements(primitive);
// No need to keep an extra copy of the vertices
_cpuMemoryUsage -= primitive->getMemoryUsage();
primitive->releaseVertexElements();
_cpuMemoryUsage += primitive->getMemoryUsage();
}
}
// The application uses clockwise winding for the definition of front face, this renderer
// aalso uses clockwise (that is the gl default) to construct the triangulation
// so...
//glFrontFace(GL_CW);
glEnable(GL_CULL_FACE);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferId);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(VertexElement), 0);
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, sizeof(VertexElement), (const GLvoid*)12);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(VertexElement), (const GLvoid*)24);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _triBufferId);
glDrawElements(GL_TRIANGLES, 3 * _triElementCount, GL_UNSIGNED_INT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisable(GL_CULL_FACE);
}
unsigned long PrimitiveRenderer::vGetMemoryUsage() {
return _cpuMemoryUsage;
}
unsigned long PrimitiveRenderer::vGetMemoryUsageGPU() {
return _gpuMemoryUsage;
}

View file

@ -0,0 +1,488 @@
///
/// @file PrimitiveRenderer.h
/// A geometric primitive renderer.
///
/// @author: Norman Crafts
/// @copyright 2014, High Fidelity, Inc. All rights reserved.
///
#ifndef __interface__PrimitiveRenderer__
#define __interface__PrimitiveRenderer__
#include <QStack>
#include <QVector>
#include <QMutex>
/// Vertex element structure.
/// Using the array of structures approach to specifying
/// vertex data to GL cuts down on the calls to glBufferSubData
///
typedef
struct __VertexElement {
struct __position {
float x;
float y;
float z;
} position;
struct __normal {
float x;
float y;
float z;
} normal;
struct __color {
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
} color;
} VertexElement;
/// Triangle element index structure.
/// Specify the vertex indices of the triangle and its element index.
///
typedef
struct __TriElement {
int indices[3];
int id;
} TriElement;
/// Vertex element list container.
///
typedef QVector<VertexElement *> VertexElementList;
/// Vertex element index list container.
///
typedef QVector<int> VertexElementIndexList;
/// Triangle element list container
///
typedef QVector<TriElement *> TriElementList;
///
/// @class Primitive
/// Primitive Interface class.
/// Abstract class for accessing vertex and tri elements of geometric primitives
///
///
class Primitive {
public:
virtual ~Primitive();
// API methods go here
/// Vertex element accessor.
/// @return A list of vertex elements of the primitive
///
const VertexElementList& vertexElements() const;
/// Vertex element index accessor.
/// @return A list of vertex element indices of the primitive
///
VertexElementIndexList& vertexElementIndices();
/// Tri element accessor.
/// @return A list of tri elements of the primitive
///
TriElementList& triElements();
/// Release vertex elements.
///
void releaseVertexElements();
/// Get memory usage.
///
unsigned long getMemoryUsage();
protected:
/// Default constructor prohibited to API user, restricted to service implementer.
///
Primitive();
private:
/// Copy constructor prohibited.
///
Primitive(
const Primitive& prim
);
// SPI methods are defined here
/// Vertex element accessor.
/// Service implementer to provide private override for this method
/// in derived class
///
virtual const VertexElementList& vVertexElements() const = 0;
/// Vertex element index accessor.
/// Service implementer to provide private override for this method
/// in derived class
///
virtual VertexElementIndexList& vVertexElementIndices() = 0;
/// Tri element accessor.
/// Service implementer to provide private override for this method
/// in derived class
///
virtual TriElementList& vTriElements() = 0;
/// Release vertex elements.
/// Service implementer to provide private override for this method
/// in derived class
///
virtual void vReleaseVertexElements() = 0;
/// Get memory usage.
/// Service implementer to provide private override for this method
/// in derived class
///
virtual unsigned long vGetMemoryUsage() = 0;
};
///
/// @class Cube
/// Class for accessing the vertex and triangle elements of a cube
///
class Cube: public Primitive {
public:
/// Configuration dependency injection constructor.
///
Cube(
float x,
float y,
float z,
float s,
unsigned char r,
unsigned char g,
unsigned char b,
unsigned char faces
);
~Cube();
private:
/// Copy constructor prohibited.
///
Cube (
const Cube& cube
);
void init(
float x,
float y,
float z,
float s,
unsigned char r,
unsigned char g,
unsigned char b,
unsigned char faceExclusions
);
void terminate();
void initializeVertices(
float x,
float y,
float z,
float s,
unsigned char r,
unsigned char g,
unsigned char b,
unsigned char faceExclusions
);
void terminateVertices();
void initializeTris(
unsigned char faceExclusions
);
void terminateTris();
// SPI virtual override methods go here
const VertexElementList& vVertexElements() const;
VertexElementIndexList& vVertexElementIndices();
TriElementList& vTriElements();
void vReleaseVertexElements();
unsigned long vGetMemoryUsage();
private:
VertexElementList _vertices; ///< Vertex element list
VertexElementIndexList _vertexIndices; ///< Vertex element index list
TriElementList _tris; ///< Tri element list
unsigned long _cpuMemoryUsage; ///< Memory allocation of object
static const int _sNumFacesPerCube = 6;
static const int _sNumVerticesPerCube = 24;
static unsigned char _sFaceIndexToHalfSpaceMask[6];
static float _sVertexIndexToConstructionVector[24][3];
static float _sVertexIndexToNormalVector[6][3];
};
///
/// @class Renderer
/// GL renderer interface class.
/// Abstract class for rendering geometric primitives in GL
///
class Renderer {
public:
virtual ~Renderer();
// API methods go here
/// Add primitive to renderer database.
///
int add(
Primitive* primitive ///< Pointer to primitive
);
/// Remove primitive from renderer database.
///
void remove(
int id ///< Primitive id
);
/// Clear all primitives from renderer database
///
void release();
/// Render primitive database.
/// The render method assumes appropriate GL context and state has
/// already been provided for
///
void render();
/// Get memory usage.
///
unsigned long getMemoryUsage();
/// Get GPU memory usage.
///
unsigned long getMemoryUsageGPU();
protected:
/// Default constructor prohibited to API user, restricted to service implementer.
///
Renderer();
private:
/// Copy constructor prohibited.
///
Renderer(
const Renderer& primitive
);
// SPI methods are defined here
/// Add primitive to renderer database.
/// Service implementer to provide private override for this method
/// in derived class
/// @return primitive id
///
virtual int vAdd(
Primitive* primitive ///< Pointer to primitive
) = 0;
/// Remove primitive from renderer database.
/// Service implementer to provide private override for this method
/// in derived class
///
virtual void vRemove(
int id ///< Primitive id
) = 0;
/// Clear all primitives from renderer database
/// Service implementer to provide private override for this method
/// in derived class
///
virtual void vRelease() = 0;
/// Render primitive database.
/// Service implementer to provide private virtual override for this method
/// in derived class
///
virtual void vRender() = 0;
/// Get memory usage.
///
virtual unsigned long vGetMemoryUsage() = 0;
/// Get GPU memory usage.
///
virtual unsigned long vGetMemoryUsageGPU() = 0;
};
///
/// @class PrimitiveRenderer
/// Renderer implementation class for the rendering of geometric primitives
/// using GL element array and GL array buffers
///
class PrimitiveRenderer : public Renderer {
public:
/// Configuration dependency injection constructor.
///
PrimitiveRenderer(
int maxCount
);
~PrimitiveRenderer();
private:
/// Default constructor prohibited.
///
PrimitiveRenderer();
/// Copy constructor prohibited.
///
PrimitiveRenderer(
const PrimitiveRenderer& renderer
);
void init();
void terminate();
/// Allocate and initialize GL buffers.
///
void initializeGL();
/// Terminate and deallocate GL buffers.
///
void terminateGL();
void initializeBookkeeping();
void terminateBookkeeping();
/// Construct the elements of the faces of the primitive.
///
void constructElements(
Primitive* primitive
);
/// Deconstruct the elements of the faces of the primitive.
///
void deconstructElements(
Primitive* primitive
);
/// Deconstruct the triangle element from the GL buffer.
///
void deconstructTriElement(
int idx
);
/// Deconstruct the vertex element from the GL buffer.
///
void deconstructVertexElement(
int idx
);
/// Transfer the vertex element to the GL buffer.
///
void transferVertexElement(
int idx,
VertexElement *vertex
);
/// Transfer the triangle element to the GL buffer.
///
void transferTriElement(
int idx,
int tri[3]
);
/// Get available primitive index.
/// Get an available primitive index from either the recycling
/// queue or incrementing the counter
///
int getAvailablePrimitiveIndex();
/// Get available vertex element index.
/// Get an available vertex element index from either the recycling
/// queue or incrementing the counter
///
int getAvailableVertexElementIndex();
/// Get available triangle element index.
/// Get an available triangle element index from either the elements
/// scheduled for deconstruction queue, the recycling
/// queue or incrementing the counter
///
int getAvailableTriElementIndex();
// SPI virtual override methods go here
/// Add primitive to renderer database.
///
int vAdd(
Primitive* primitive
);
/// Remove primitive from renderer database.
///
void vRemove(
int id
);
/// Clear all primitives from renderer database
///
void vRelease();
/// Render triangle database.
///
void vRender();
/// Get memory usage.
///
unsigned long vGetMemoryUsage();
/// Get gpu memory usage.
///
unsigned long vGetMemoryUsageGPU();
private:
int _maxCount;
// GL related parameters
GLuint _triBufferId; ///< GL element array buffer id
GLuint _vertexBufferId; ///< GL vertex array buffer id
// Book keeping parameters
int _vertexElementCount; ///< Count of vertices
int _maxVertexElementCount; ///< Max count of vertices
int _triElementCount; ///< Count of triangles
int _maxTriElementCount; ///< Max count of triangles
QVector<Primitive *> _primitives; ///< Vector of primitive
int _primitiveCount; ///< Count of primitives
QStack<int> _availablePrimitiveIndex; ///< Queue of primitive indices available
QStack<int> _availableVertexElementIndex; ///< Queue of vertex element indices available
QStack<int> _availableTriElementIndex; ///< Queue of triangle element indices available
QStack<int> _deconstructTriElementIndex; ///< Queue of triangle element indices requiring deletion from GL
QStack<int> _constructPrimitiveIndex; ///< Queue of primitives requiring addition to GL
QMutex _guard;
// Statistics parameters, not necessary for proper operation
unsigned long _gpuMemoryUsage;
unsigned long _cpuMemoryUsage;
static const int _sIndicesPerTri = 3;
static const int _sBytesPerTriElement = sizeof(GLint) * _sIndicesPerTri;
};
#endif

View file

@ -61,8 +61,12 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
_maxVoxels(maxVoxels),
_initialized(false),
_writeArraysLock(QReadWriteLock::Recursive),
_readArraysLock(QReadWriteLock::Recursive)
{
_readArraysLock(QReadWriteLock::Recursive),
_inOcclusions(false),
_showCulledSharedFaces(false),
_usePrimitiveRenderer(false),
_renderer(0)
{
_voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0;
_writeRenderFullVBO = true;
@ -110,7 +114,7 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
void VoxelSystem::elementDeleted(OctreeElement* element) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
if (voxel->getVoxelSystem() == this) {
if (_voxelsInWriteArrays != 0) {
if ((_voxelsInWriteArrays != 0) || _usePrimitiveRenderer) {
forceRemoveNodeFromArrays(voxel);
} else {
if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
@ -290,6 +294,7 @@ void VoxelSystem::setUseVoxelShader(bool useVoxelShader) {
cleanupVoxelMemory();
}
_useVoxelShader = useVoxelShader;
_usePrimitiveRenderer = false;
if (wasInitialized) {
initVoxelMemory();
}
@ -375,8 +380,11 @@ void VoxelSystem::cleanupVoxelMemory() {
_writeVerticesArray = NULL;
_readColorsArray = NULL;
_writeColorsArray = NULL;
}
delete _renderer;
_renderer = 0;
delete[] _writeVoxelDirtyArray;
delete[] _readVoxelDirtyArray;
_writeVoxelDirtyArray = _readVoxelDirtyArray = NULL;
@ -506,7 +514,6 @@ void VoxelSystem::initVoxelMemory() {
_readColorsArray = new GLubyte[vertexPointsPerVoxel * _maxVoxels];
_memoryUsageRAM += (sizeof(GLubyte) * vertexPointsPerVoxel * _maxVoxels);
// create our simple fragment shader if we're the first system to init
if (!_perlinModulateProgram.isLinked()) {
switchToResourcesParentIfRequired();
@ -526,6 +533,7 @@ void VoxelSystem::initVoxelMemory() {
_shadowMapProgram.release();
}
}
_renderer = new PrimitiveRenderer(_maxVoxels);
_initialized = true;
@ -669,7 +677,12 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
};
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);
_callsToTreesToArrays++;
if (_writeRenderFullVBO) {
if (_usePrimitiveRenderer) {
_renderer->release();
clearAllNodesBufferIndex();
}
clearFreeBufferIndexes();
}
_voxelsUpdated = newTreeToArrays(_tree->getRoot());
@ -686,18 +699,24 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
_voxelsUpdated = 0;
}
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
_readArraysLock.lockForWrite();
if (_usePrimitiveRenderer) {
if (_voxelsUpdated) {
_voxelsDirty=true;
}
} else {
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
_readArraysLock.lockForWrite();
if (_voxelsUpdated) {
_voxelsDirty=true;
}
// copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
copyWrittenDataToReadArrays(didWriteFullVBO);
_readArraysLock.unlock();
if (_voxelsUpdated) {
_voxelsDirty=true;
}
// copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
copyWrittenDataToReadArrays(didWriteFullVBO);
_readArraysLock.unlock();
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
_setupNewVoxelsForDrawingLastFinished = end;
@ -724,23 +743,26 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
return; // bail early, it hasn't been long enough since the last time we ran
}
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
{
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"setupNewVoxelsForDrawingSingleNode()... _readArraysLock.lockForWrite();" );
_readArraysLock.lockForWrite();
if (_usePrimitiveRenderer) {
_voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty
_voxelsUpdated = 0;
} else {
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
{
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"setupNewVoxelsForDrawingSingleNode()... _bufferWriteLock.lock();" );
_readArraysLock.lockForWrite();
}
_voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty
// copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
copyWrittenDataToReadArrays(_writeRenderFullVBO);
// after...
_voxelsUpdated = 0;
_readArraysLock.unlock();
}
_voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty
// copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
copyWrittenDataToReadArrays(_writeRenderFullVBO);
// after...
_voxelsUpdated = 0;
_readArraysLock.unlock();
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / 1000;
_setupNewVoxelsForDrawingLastFinished = end;
@ -892,7 +914,8 @@ void VoxelSystem::cleanupRemovedVoxels() {
// we also might have VBO slots that have been abandoned, if too many of our VBO slots
// are abandonded we want to rerender our full VBOs
const float TOO_MANY_ABANDONED_RATIO = 0.5f;
if (!_writeRenderFullVBO && (_abandonedVBOSlots > (_voxelsInWriteArrays * TOO_MANY_ABANDONED_RATIO))) {
if (!_usePrimitiveRenderer && !_writeRenderFullVBO &&
(_abandonedVBOSlots > (_voxelsInWriteArrays * TOO_MANY_ABANDONED_RATIO))) {
if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
qDebug() << "cleanupRemovedVoxels().. _abandonedVBOSlots ["
<< _abandonedVBOSlots << "] > TOO_MANY_ABANDONED_RATIO";
@ -1052,13 +1075,22 @@ int VoxelSystem::forceRemoveNodeFromArrays(VoxelTreeElement* node) {
return 0;
}
// if the node is not in the VBOs then we have nothing to do!
if (node->isKnownBufferIndex()) {
// If this node has not yet been written to the array, then add it to the end of the array.
glBufferIndex nodeIndex = node->getBufferIndex();
node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
freeBufferIndex(nodeIndex); // NOTE: This will make the node invisible!
return 1; // updated!
if (_usePrimitiveRenderer) {
if (node->isKnownBufferIndex()) {
int primitiveIndex = node->getBufferIndex();
_renderer->remove(primitiveIndex);
node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
return 1;
}
} else {
// if the node is not in the VBOs then we have nothing to do!
if (node->isKnownBufferIndex()) {
// If this node has not yet been written to the array, then add it to the end of the array.
glBufferIndex nodeIndex = node->getBufferIndex();
node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
freeBufferIndex(nodeIndex); // NOTE: This will make the node invisible!
return 1; // updated!
}
}
return 0; // not-updated
}
@ -1088,17 +1120,44 @@ int VoxelSystem::updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, boo
if (node->getShouldRender()) {
glm::vec3 startVertex = node->getCorner();
float voxelScale = node->getScale();
nodeColor const & color = node->getColor();
glBufferIndex nodeIndex = GLBUFFER_INDEX_UNKNOWN;
if (reuseIndex && node->isKnownBufferIndex()) {
nodeIndex = node->getBufferIndex();
if (_usePrimitiveRenderer) {
if (node->isKnownBufferIndex()) {
int primitiveIndex = node->getBufferIndex();
_renderer->remove(primitiveIndex);
node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
} else {
node->setVoxelSystem(this);
}
unsigned char occlusions;
if (_showCulledSharedFaces) {
occlusions = ~node->getInteriorOcclusions();
} else {
occlusions = node->getInteriorOcclusions();
}
if (occlusions != OctreeElement::HalfSpace::All) {
Cube* cube = new Cube(
startVertex.x, startVertex.y, startVertex.z, voxelScale,
color[RED_INDEX], color[GREEN_INDEX], color[BLUE_INDEX],
occlusions);
if (cube) {
int primitiveIndex = _renderer->add(cube);
node->setBufferIndex(primitiveIndex);
}
}
} else {
nodeIndex = getNextBufferIndex();
node->setBufferIndex(nodeIndex);
node->setVoxelSystem(this);
glBufferIndex nodeIndex = GLBUFFER_INDEX_UNKNOWN;
if (reuseIndex && node->isKnownBufferIndex()) {
nodeIndex = node->getBufferIndex();
} else {
nodeIndex = getNextBufferIndex();
node->setBufferIndex(nodeIndex);
node->setVoxelSystem(this);
}
// populate the array with points for the 8 vertices and RGB color for each added vertex
updateArraysDetails(nodeIndex, startVertex, voxelScale, node->getColor());
}
// populate the array with points for the 8 vertices and RGB color for each added vertex
updateArraysDetails(nodeIndex, startVertex, voxelScale, node->getColor());
return 1; // updated!
} else {
// If we shouldn't render, and we're in reuseIndex mode, then free our index, this only operates
@ -1246,22 +1305,24 @@ void VoxelSystem::updateVBOs() {
};
// would like to include _callsToTreesToArrays
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);
if (_voxelsDirty) {
if (! _usePrimitiveRenderer) {
if (_voxelsDirty) {
// attempt to lock the read arrays, to for copying from them to the actual GPU VBOs.
// if we fail to get the lock, that's ok, our VBOs will update on the next frame...
const int WAIT_FOR_LOCK_IN_MS = 5;
if (_readArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
if (_readRenderFullVBO) {
updateFullVBOs();
// attempt to lock the read arrays, to for copying from them to the actual GPU VBOs.
// if we fail to get the lock, that's ok, our VBOs will update on the next frame...
const int WAIT_FOR_LOCK_IN_MS = 5;
if (_readArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
if (_readRenderFullVBO) {
updateFullVBOs();
} else {
updatePartialVBOs();
}
_voxelsDirty = false;
_readRenderFullVBO = false;
_readArraysLock.unlock();
} else {
updatePartialVBOs();
qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()";
}
_voxelsDirty = false;
_readRenderFullVBO = false;
_readArraysLock.unlock();
} else {
qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()";
}
}
_callsToTreesToArrays = 0; // clear it
@ -1391,7 +1452,8 @@ void VoxelSystem::render() {
glDisableVertexAttribArray(attributeLocation);
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
}
} else {
} else
if (!_usePrimitiveRenderer) {
PerformanceWarning warn(showWarnings, "render().. TRIANGLES...");
{
@ -1465,6 +1527,12 @@ void VoxelSystem::render() {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
}
else {
applyScaleAndBindProgram(texture);
_renderer->render();
removeScaleAndReleaseProgram(texture);
}
}
void VoxelSystem::applyScaleAndBindProgram(bool texture) {
@ -1517,6 +1585,12 @@ void VoxelSystem::killLocalVoxels() {
_tree->eraseAllOctreeElements();
_tree->unlock();
clearFreeBufferIndexes();
if (_usePrimitiveRenderer) {
if (_renderer) {
_renderer->release();
}
clearAllNodesBufferIndex();
}
_voxelsInReadArrays = 0; // do we need to do this?
setupNewVoxelsForDrawing();
}
@ -1544,6 +1618,209 @@ void VoxelSystem::clearAllNodesBufferIndex() {
}
}
bool VoxelSystem::inspectForInteriorOcclusionsOperation(OctreeElement* element, void* extraData) {
_nodeCount++;
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
// Nothing to do at the leaf level
if (voxel->isLeaf()) {
return false;
}
// Bit mask of occluded shared faces indexed by child
unsigned char occludedSharedFace[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 };
// Traverse all pair combinations of children
for (int i = NUMBER_OF_CHILDREN; --i >= 0; ) {
VoxelTreeElement* childA = voxel->getChildAtIndex(i);
if (childA) {
// Get the child A's occluding faces, for a leaf that will be
// all six voxel faces, and for a non leaf, that will be
// all faces which are completely covered by four child octants.
unsigned char exteriorOcclusionsA = childA->getExteriorOcclusions();
for (int j = i; --j >= 0; ) {
VoxelTreeElement* childB = voxel->getChildAtIndex(j);
if (childB) {
// Get child B's occluding faces
unsigned char exteriorOcclusionsB = childB->getExteriorOcclusions();
// Determine the shared halfspace partition between siblings A and B,
// i.e., near/far, left/right, or top/bottom
unsigned char partitionA = _sOctantIndexToSharedBitMask[i][j] &
exteriorOcclusionsA;
unsigned char partitionB = _sOctantIndexToSharedBitMask[i][j] &
exteriorOcclusionsB;
// Determine which face of each sibling is occluded.
// The _sOctantIndexToBitMask is a partition occupancy mask. For
// example, if the near-left-top (NLT) and near-left-bottom (NLB) child voxels
// exist, the shared partition is top-bottom (TB), and thus the occluded
// shared face of the NLT voxel is its bottom face.
occludedSharedFace[i] |= (partitionB & _sOctantIndexToBitMask[i]);
occludedSharedFace[j] |= (partitionA & _sOctantIndexToBitMask[j]);
}
}
// Exchange bit pairs, left to right, vice versa, etc.
occludedSharedFace[i] = _sSwizzledOcclusionBits[occludedSharedFace[i]];
// Combine this voxel's interior excluded shared face only to those children which are coincident
// with the excluded face.
occludedSharedFace[i] |= (voxel->getInteriorOcclusions() & _sOctantIndexToBitMask[i]);
// Inform the child
childA->setInteriorOcclusions(occludedSharedFace[i]);
if (occludedSharedFace[i] != OctreeElement::HalfSpace::None) {
//const glm::vec3& v = voxel->getCorner();
//float s = voxel->getScale();
//qDebug("Child %d of voxel at %f %f %f size: %f has %02x occlusions", i, v.x, v.y, v.z, s, occludedSharedFace[i]);
}
}
}
return true;
}
bool VoxelSystem::inspectForExteriorOcclusionsOperation(OctreeElement* element, void* extraData) {
_nodeCount++;
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
// Nothing to do at the leaf level
if (voxel->isLeaf()) {
// By definition the the exterior faces of a leaf voxel are
// always occluders.
voxel->setExteriorOcclusions(OctreeElement::HalfSpace::All);
// And the sibling occluders
voxel->setInteriorOcclusions(OctreeElement::HalfSpace::None);
return false;
} else {
voxel->setExteriorOcclusions(OctreeElement::HalfSpace::None);
voxel->setInteriorOcclusions(OctreeElement::HalfSpace::None);
}
// Count of exterior occluding faces of this voxel element indexed
// by half space partition
unsigned int exteriorOcclusionsCt[6] = { 0, 0, 0, 0, 0, 0 };
// Traverse all children
for (int i = NUMBER_OF_CHILDREN; --i >= 0; ) {
VoxelTreeElement* child = voxel->getChildAtIndex(i);
if (child) {
// Get the child's occluding faces, for a leaf, that will be
// all six voxel faces, and for a non leaf, that will be
// all faces which are completely covered by four child octants.
unsigned char exteriorOcclusionsOfChild = child->getExteriorOcclusions();
exteriorOcclusionsOfChild &= _sOctantIndexToBitMask[i];
for (int j = 6; --j >= 0; ) {
// Determine if the halfspace partition indexed by 1 << j is
// present in the exterior occlusions of the child.
unsigned char partition = exteriorOcclusionsOfChild & (1 << j);
if (partition) {
exteriorOcclusionsCt[j]++;
}
}
}
}
{
// Derive the exterior occlusions of the voxel elements from the exclusions
// of its children
unsigned char exteriorOcclusions = OctreeElement::HalfSpace::None;
for (int i = 6; --i >= 0; ) {
if (exteriorOcclusionsCt[i] == _sNumOctantsPerHemiVoxel) {
// Exactly four octants qualify for full exterior occlusion
exteriorOcclusions |= (1 << i);
}
}
// Inform the voxel element
voxel->setExteriorOcclusions(exteriorOcclusions);
if (exteriorOcclusions == OctreeElement::HalfSpace::All) {
//const glm::vec3& v = voxel->getCorner();
//float s = voxel->getScale();
//qDebug("Completely occupied voxel at %f %f %f size: %f", v.x, v.y, v.z, s);
// TODO: All of the exterior faces of this voxel element are
// occluders, which means that this element is completely
// occupied. Hence, the subtree from this node could be
// pruned and replaced by a leaf voxel, if the visible
// properties of the children are the same
} else if (exteriorOcclusions != OctreeElement::HalfSpace::None) {
//const glm::vec3& v = voxel->getCorner();
//float s = voxel->getScale();
//qDebug("Partially occupied voxel at %f %f %f size: %f with %02x", v.x, v.y, v.z, s, exteriorOcclusions);
}
}
return true;
}
void VoxelSystem::cullSharedFaces() {
if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) {
_useVoxelShader = false;
_usePrimitiveRenderer = true;
inspectForOcclusions();
} else {
_usePrimitiveRenderer = false;
clearAllNodesBufferIndex();
}
_writeRenderFullVBO = true;
_tree->setDirtyBit();
setupNewVoxelsForDrawing();
}
void VoxelSystem::showCulledSharedFaces() {
_tree->lockForRead();
if (Menu::getInstance()->isOptionChecked(MenuOption::ShowCulledSharedFaces)) {
_showCulledSharedFaces = true;
} else {
_showCulledSharedFaces = false;
}
_tree->unlock();
if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) {
_writeRenderFullVBO = true;
_tree->setDirtyBit();
setupNewVoxelsForDrawing();
}
}
void VoxelSystem::inspectForOcclusions() {
if (_inOcclusions) {
return;
}
_inOcclusions = true;
_nodeCount = 0;
bool showDebugDetails = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showDebugDetails, "inspectForOcclusions()");
_tree->lockForRead();
_tree->recurseTreeWithPostOperation(inspectForExteriorOcclusionsOperation);
_nodeCount = 0;
_tree->recurseTreeWithOperation(inspectForInteriorOcclusionsOperation);
_tree->unlock();
if (showDebugDetails) {
qDebug("inspecting all occlusions of %d nodes", _nodeCount);
}
_inOcclusions = false;
}
bool VoxelSystem::forceRedrawEntireTreeOperation(OctreeElement* element, void* extraData) {
_nodeCount++;
element->setDirtyBit();
@ -2172,6 +2449,13 @@ bool VoxelSystem::showAllSubTreeOperation(OctreeElement* element, void* extraDat
// These are both needed to force redraw...
voxel->setDirtyBit();
voxel->markWithChangedTime();
// and this?
// no, not needed, because markWithChangedTime notifies hooks, which calls elementUpdated, which calls updateNodeInArrays
// {
// VoxelSystem* thisVoxelSystem = args->thisVoxelSystem;
// thisVoxelSystem->_voxelsUpdated += thisVoxelSystem->updateNodeInArrays(voxel, true, true);
// thisVoxelSystem->setupNewVoxelsForDrawingSingleNode();
// }
args->nodesShown++;
}
@ -2363,7 +2647,9 @@ public:
nodesInVBONotShouldRender(0),
nodesInVBOOverExpectedMax(0),
duplicateVBOIndex(0),
leafNodes(0)
leafNodes(0),
culledLeafNodes(0),
nodesInPrimitiveRenderer(0)
{
hasIndexFound = new bool[maxVoxels];
memset(hasIndexFound, false, maxVoxels * sizeof(bool));
@ -2382,6 +2668,8 @@ public:
unsigned long nodesInVBOOverExpectedMax;
unsigned long duplicateVBOIndex;
unsigned long leafNodes;
unsigned long culledLeafNodes; ///< Number of completely culled nodes because of face sharing
unsigned long nodesInPrimitiveRenderer;
unsigned long expectedMax;
@ -2396,6 +2684,9 @@ bool VoxelSystem::collectStatsForTreesAndVBOsOperation(OctreeElement* element, v
if (voxel->isLeaf()) {
args->leafNodes++;
if (voxel->getInteriorOcclusions() == OctreeElement::HalfSpace::All) {
args->culledLeafNodes++;
}
}
if (voxel->isColored()) {
@ -2410,13 +2701,27 @@ bool VoxelSystem::collectStatsForTreesAndVBOsOperation(OctreeElement* element, v
args->dirtyNodes++;
}
if (voxel->isKnownBufferIndex()) {
args->nodesInVBO++;
unsigned long nodeIndex = voxel->getBufferIndex();
unsigned long nodeIndex = 0;
if (voxel->getBufferIndex()) {
args->nodesInPrimitiveRenderer++;
nodeIndex = voxel->getBufferIndex();
const bool extraDebugging = false; // enable for extra debugging
if (extraDebugging) {
qDebug("node In VBO... [%f,%f,%f] %f ... index=%ld, isDirty=%s, shouldRender=%s",
qDebug("node In Renderer... [%f,%f,%f] %f ... index=%ld, isDirty=%s, shouldRender=%s, culledFaces=0x%02x \n",
voxel->getCorner().x, voxel->getCorner().y, voxel->getCorner().z, voxel->getScale(),
nodeIndex, debug::valueOf(voxel->isDirty()), debug::valueOf(voxel->getShouldRender()),
voxel->getInteriorOcclusions());
}
}
if (voxel->isKnownBufferIndex()) {
args->nodesInVBO++;
nodeIndex = voxel->getBufferIndex();
const bool extraDebugging = false; // enable for extra debugging
if (extraDebugging) {
qDebug("node In VBO... [%f,%f,%f] %f ... index=%ld, isDirty=%s, shouldRender=%s \n",
voxel->getCorner().x, voxel->getCorner().y, voxel->getCorner().z, voxel->getScale(),
nodeIndex, debug::valueOf(voxel->isDirty()), debug::valueOf(voxel->getShouldRender()));
}
@ -2447,10 +2752,12 @@ void VoxelSystem::collectStatsForTreesAndVBOs() {
glBufferIndex minDirty = GLBUFFER_INDEX_UNKNOWN;
glBufferIndex maxDirty = 0;
for (glBufferIndex i = 0; i < _voxelsInWriteArrays; i++) {
if (_writeVoxelDirtyArray[i]) {
minDirty = std::min(minDirty,i);
maxDirty = std::max(maxDirty,i);
if (!_usePrimitiveRenderer) {
for (glBufferIndex i = 0; i < _voxelsInWriteArrays; i++) {
if (_writeVoxelDirtyArray[i]) {
minDirty = std::min(minDirty,i);
maxDirty = std::max(maxDirty,i);
}
}
}
@ -2461,31 +2768,40 @@ void VoxelSystem::collectStatsForTreesAndVBOs() {
_tree->recurseTreeWithOperation(collectStatsForTreesAndVBOsOperation,&args);
qDebug("Local Voxel Tree Statistics:\n total nodes %ld \n leaves %ld \n dirty %ld \n colored %ld \n shouldRender %ld",
qDebug("Local Voxel Tree Statistics:\n total nodes %ld \n leaves %ld \n dirty %ld \n colored %ld \n shouldRender %ld \n",
args.totalNodes, args.leafNodes, args.dirtyNodes, args.coloredNodes, args.shouldRenderNodes);
qDebug(" _voxelsDirty=%s \n _voxelsInWriteArrays=%ld \n minDirty=%ld \n maxDirty=%ld", debug::valueOf(_voxelsDirty),
_voxelsInWriteArrays, minDirty, maxDirty);
if (!_usePrimitiveRenderer) {
qDebug(" _voxelsDirty=%s \n _voxelsInWriteArrays=%ld \n minDirty=%ld \n maxDirty=%ld", debug::valueOf(_voxelsDirty),
_voxelsInWriteArrays, minDirty, maxDirty);
qDebug(" inVBO %ld \n nodesInVBOOverExpectedMax %ld \n duplicateVBOIndex %ld \n nodesInVBONotShouldRender %ld",
args.nodesInVBO, args.nodesInVBOOverExpectedMax, args.duplicateVBOIndex, args.nodesInVBONotShouldRender);
qDebug(" inVBO %ld \n nodesInVBOOverExpectedMax %ld \n duplicateVBOIndex %ld \n nodesInVBONotShouldRender %ld",
args.nodesInVBO, args.nodesInVBOOverExpectedMax, args.duplicateVBOIndex, args.nodesInVBONotShouldRender);
glBufferIndex minInVBO = GLBUFFER_INDEX_UNKNOWN;
glBufferIndex maxInVBO = 0;
qDebug(" memory usage %ld \n gpu memory usage %ld \n", _memoryUsageRAM, _memoryUsageVBO);
for (glBufferIndex i = 0; i < _maxVoxels; i++) {
if (args.hasIndexFound[i]) {
minInVBO = std::min(minInVBO,i);
maxInVBO = std::max(maxInVBO,i);
glBufferIndex minInVBO = GLBUFFER_INDEX_UNKNOWN;
glBufferIndex maxInVBO = 0;
for (glBufferIndex i = 0; i < _maxVoxels; i++) {
if (args.hasIndexFound[i]) {
minInVBO = std::min(minInVBO,i);
maxInVBO = std::max(maxInVBO,i);
}
}
qDebug(" minInVBO=%ld \n maxInVBO=%ld \n _voxelsInWriteArrays=%ld \n _voxelsInReadArrays=%ld",
minInVBO, maxInVBO, _voxelsInWriteArrays, _voxelsInReadArrays);
qDebug(" _freeIndexes.size()=%ld",
_freeIndexes.size());
} else {
qDebug(" PrimitiveRenderer nodes %ld \n completely culled nodes %ld \n",
args.nodesInPrimitiveRenderer, args.culledLeafNodes);
qDebug(" memory usage %ld \n gpu memory usage %ld \n",
_renderer->getMemoryUsage(), _renderer->getMemoryUsageGPU());
}
qDebug(" minInVBO=%ld \n maxInVBO=%ld \n _voxelsInWriteArrays=%ld \n _voxelsInReadArrays=%ld",
minInVBO, maxInVBO, _voxelsInWriteArrays, _voxelsInReadArrays);
qDebug(" _freeIndexes.size()=%ld",
_freeIndexes.size());
qDebug("DONE WITH Local Voxel Tree Statistics >>>>>>>>>>>>");
}
@ -2884,3 +3200,170 @@ unsigned long VoxelSystem::getVoxelMemoryUsageGPU() {
return (_initialMemoryUsageGPU - currentFreeMemory);
}
// Swizzle value of bit pairs of the value of index
unsigned short VoxelSystem::_sSwizzledOcclusionBits[64] = {
0x0000, // 00000000
0x0002, // 00000001
0x0001, // 00000010
0x0003, // 00000011
0x0008, // 00000100
0x000a, // 00000101
0x0009, // 00000110
0x000b, // 00000111
0x0004, // 00001000
0x0006, // 00001001
0x0005, // 00001010
0x0007, // 00001011
0x000c, // 00001100
0x000e, // 00001101
0x000d, // 00001110
0x000f, // 00001111
0x0020, // 00010000
0x0022, // 00010001
0x0021, // 00010010
0x0023, // 00010011
0x0028, // 00010100
0x002a, // 00010101
0x0029, // 00010110
0x002b, // 00010111
0x0024, // 00011000
0x0026, // 00011001
0x0025, // 00011010
0x0027, // 00011011
0x002c, // 00011100
0x002e, // 00011101
0x002d, // 00011110
0x002f, // 00011111
0x0010, // 00100000
0x0012, // 00100001
0x0011, // 00100010
0x0013, // 00100011
0x0018, // 00100100
0x001a, // 00100101
0x0019, // 00100110
0x001b, // 00100111
0x0014, // 00101000
0x0016, // 00101001
0x0015, // 00101010
0x0017, // 00101011
0x001c, // 00101100
0x001e, // 00101101
0x001d, // 00101110
0x001f, // 00101111
0x0030, // 00110000
0x0032, // 00110001
0x0031, // 00110010
0x0033, // 00110011
0x0038, // 00110100
0x003a, // 00110101
0x0039, // 00110110
0x003b, // 00110111
0x0034, // 00111000
0x0036, // 00111001
0x0035, // 00111010
0x0037, // 00111011
0x003c, // 00111100
0x003e, // 00111101
0x003d, // 00111110
0x003f, // 00111111
};
// Octant bitmask array indexed by octant. The mask value indicates the octant's halfspace partitioning. The index
// value corresponds to the voxel's octal code derived in "pointToVoxel" in SharedUtil.cpp, which, BTW, does *not*
// correspond to the "ChildIndex" enum value in OctreeElement.h
unsigned char VoxelSystem::_sOctantIndexToBitMask[8] = {
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Near,
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Far,
OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Near,
OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Left | OctreeElement::HalfSpace::Far,
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Near,
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Far,
OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Near,
OctreeElement::HalfSpace::Top | OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Far,
};
// Two dimensional array map indexed by octant row and column. The mask value
// indicates the two faces shared by the octants
unsigned char VoxelSystem::_sOctantIndexToSharedBitMask[8][8] = {
{ // Index 0: Bottom-Left-Near
0, // Bottom-Left-Near
OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Left-Far
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Left-Near
0, // Top-Left-Far
OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Right-Near
0, // Bottom-Right-Far
0, // Top-Right-Near
0, // Top-Right-Far
},
{ // Index 1: Bottom-Left-Far
OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Left-Near
0, // Bottom-Left-Far
0, // Top-Left-Near
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Left-Far
0, // Bottom-Right-Near
OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Right-Far
0, // Top-Right-Near
0, // Top-Right-Far
},
{ // Index 2: Top-Left-Near
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Left-Near
0, // Bottom-Left-Far
0, // Top-Left-Near
OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Left-Far
0, // Bottom-Right-Near
0, // Bottom-Right-Far
OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Right-Near
0, // Top-Right-Far
},
{ // Index 3: Top-Left-Far
0, // Bottom-Left-Near
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Left-Far
OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Left-Near
0, // Top-Left-Far
0, // Bottom-Right-Near
0, // Bottom-Right-Far
0, // Top-Right-Near
OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Right-Far
},
{ // Index 4: Bottom-Right-Near
OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Left-Near
0, // Bottom-Left-Far
0, // Top-Left-Near
0, // Top-Left-Far
0, // Bottom-Right-Near
OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Right-Far
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Right-Near
0, // Top-Right-Far
},
{ // Index 5: Bottom-Right-Far
0, // Bottom-Left-Near
OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Bottom-Left-Far
0, // Top-Left-Near
0, // Top-Left-Far
OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Bottom-Right-Near
0, // Bottom-Right-Far
0, // Top-Right-Near
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Top-Right-Far
},
{ // Index 6: Top-Right-Near
0, // Bottom-Left-Near
0, // Bottom-Left-Far
OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Left-Near
0, // Top-Left-Far
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Right-Near
0, // Bottom-Right-Far
0, // Top-Right-Near
OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Right-Far
},
{ // Index 7: Top-Right-Far
0, // Bottom-Left-Near
0, // Bottom-Left-Far
0, // Top-Left-Near
OctreeElement::HalfSpace::Right | OctreeElement::HalfSpace::Left, // Top-Left-Far
0, // Bottom-Right-Near
OctreeElement::HalfSpace::Bottom | OctreeElement::HalfSpace::Top, // Bottom-Right-Far
OctreeElement::HalfSpace::Near | OctreeElement::HalfSpace::Far, // Top-Right-Near
0, // Top-Right-Far
},
};

View file

@ -24,11 +24,13 @@
#include "Util.h"
#include "world.h"
#include "renderer/VoxelShader.h"
#include "PrimitiveRenderer.h"
class ProgramObject;
const int NUM_CHILDREN = 8;
struct VoxelShaderVBOData
{
float x, y, z; // position
@ -82,6 +84,7 @@ public:
virtual void removeOutOfView();
virtual void hideOutOfView(bool forceFullFrustum = false);
void inspectForOcclusions();
bool hasViewChanged();
bool isViewChanging();
@ -134,6 +137,8 @@ public slots:
void falseColorizeBySource();
void forceRedrawEntireTree();
void clearAllNodesBufferIndex();
void cullSharedFaces();
void showCulledSharedFaces();
void cancelImport();
@ -189,6 +194,8 @@ private:
static bool killSourceVoxelsOperation(OctreeElement* element, void* extraData);
static bool forceRedrawEntireTreeOperation(OctreeElement* element, void* extraData);
static bool clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData);
static bool inspectForExteriorOcclusionsOperation(OctreeElement* element, void* extraData);
static bool inspectForInteriorOcclusionsOperation(OctreeElement* element, void* extraData);
static bool hideOutOfViewOperation(OctreeElement* element, void* extraData);
static bool hideAllSubTreeOperation(OctreeElement* element, void* extraData);
static bool showAllSubTreeOperation(OctreeElement* element, void* extraData);
@ -305,6 +312,18 @@ private:
float _lastKnownVoxelSizeScale;
int _lastKnownBoundaryLevelAdjust;
bool _inOcclusions;
bool _showCulledSharedFaces; ///< Flag visibility of culled faces
bool _usePrimitiveRenderer; ///< Flag primitive renderer for use
PrimitiveRenderer* _renderer; ///< Voxel renderer
static const int _sNumOctantsPerHemiVoxel = 4;
static int _sCorrectedChildIndex[8];
static unsigned short _sSwizzledOcclusionBits[64]; ///< Swizzle value of bit pairs of the value of index
static unsigned char _sOctantIndexToBitMask[8]; ///< Map octant index to partition mask
static unsigned char _sOctantIndexToSharedBitMask[8][8]; ///< Map octant indices to shared partition mask
};
#endif

View file

@ -58,6 +58,7 @@ const float CHAT_MESSAGE_HEIGHT = 0.1f;
const float DISPLAYNAME_FADE_TIME = 0.5f;
const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME);
const float DISPLAYNAME_ALPHA = 0.95f;
const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f;
Avatar::Avatar() :
AvatarData(),
@ -216,7 +217,7 @@ void Avatar::render(bool forceRenderHead) {
glPopMatrix();
}
}
const float DISPLAYNAME_DISTANCE = 4.0f;
const float DISPLAYNAME_DISTANCE = 10.0f;
setShowDisplayName(lengthToTarget < DISPLAYNAME_DISTANCE);
renderDisplayName();
@ -299,7 +300,10 @@ void Avatar::renderDisplayName() {
glDisable(GL_LIGHTING);
glPushMatrix();
glm::vec3 textPosition = getPosition() + getBodyUpDirection() * ((getSkeletonHeight() + getHeadHeight()) / 1.5f);
glm::vec3 textPosition;
getSkeletonModel().getNeckPosition(textPosition);
textPosition += getBodyUpDirection() * getHeadHeight() * 1.1f;
glTranslatef(textPosition.x, textPosition.y, textPosition.z);
// we need "always facing camera": we must remove the camera rotation from the stack
@ -338,11 +342,17 @@ void Avatar::renderDisplayName() {
float scaleFactor = (textWindowHeight > EPSILON) ? 1.0f / textWindowHeight : 1.0f;
glScalef(scaleFactor, scaleFactor, 1.0);
glScalef(1.0f, -1.0f, 1.0f); // TextRenderer::draw paints the text upside down in y axis
int text_x = -_displayNameBoundingRect.width() / 2;
int text_y = -_displayNameBoundingRect.height() / 2;
// draw a gray background
QFontMetrics metrics = textRenderer(DISPLAYNAME)->metrics();
int bottom = -metrics.descent(), top = bottom + metrics.height();
int left = -_displayNameWidth/2, right = _displayNameWidth/2;
const int border = 5;
int left = text_x + _displayNameBoundingRect.x();
int right = left + _displayNameBoundingRect.width();
int bottom = text_y + _displayNameBoundingRect.y();
int top = bottom + _displayNameBoundingRect.height();
const int border = 8;
bottom -= border;
left -= border;
top += border;
@ -352,7 +362,7 @@ void Avatar::renderDisplayName() {
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.0f, 1.0f);
glColor4f(0.2f, 0.2f, 0.2f, _displayNameAlpha);
glColor4f(0.2f, 0.2f, 0.2f, _displayNameAlpha * DISPLAYNAME_BACKGROUND_ALPHA / DISPLAYNAME_ALPHA);
glBegin(GL_QUADS);
glVertex2f(left, bottom);
glVertex2f(right, bottom);
@ -360,14 +370,14 @@ void Avatar::renderDisplayName() {
glVertex2f(left, top);
glEnd();
glScalef(1.0f, -1.0f, 1.0f); // TextRenderer::draw paints the text upside down in y axis
glColor4f(0.93f, 0.93f, 0.93f, _displayNameAlpha);
QByteArray ba = _displayName.toLocal8Bit();
const char* text = ba.data();
glDisable(GL_POLYGON_OFFSET_FILL);
textRenderer(DISPLAYNAME)->draw(-_displayNameWidth / 2, 0, text);
textRenderer(DISPLAYNAME)->draw(text_x, text_y, text);
}
@ -488,11 +498,7 @@ void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
void Avatar::setDisplayName(const QString& displayName) {
AvatarData::setDisplayName(displayName);
int width = 0;
for (int i = 0; i < displayName.size(); i++) {
width += (textRenderer(DISPLAYNAME)->computeWidth(displayName[i].toLatin1()));
}
_displayNameWidth = width;
_displayNameBoundingRect = textRenderer(DISPLAYNAME)->metrics().tightBoundingRect(displayName);
}
int Avatar::parseData(const QByteArray& packet) {

View file

@ -144,7 +144,7 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const
void AvatarManager::processAvatarDataPacket(const QByteArray &datagram, const QWeakPointer<Node> &mixerWeakPointer) {
int bytesRead = numBytesForPacketHeader(datagram);
QByteArray dummyAvatarByteArray = byteArrayWithPopluatedHeader(PacketTypeAvatarData);
QByteArray dummyAvatarByteArray = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
int numDummyHeaderBytes = dummyAvatarByteArray.size();
int numDummyHeaderBytesWithoutUUID = numDummyHeaderBytes - NUM_BYTES_RFC4122_UUID;

View file

@ -643,17 +643,10 @@ void MyAvatar::loadData(QSettings* settings) {
}
void MyAvatar::sendKillAvatar() {
QByteArray killPacket = byteArrayWithPopluatedHeader(PacketTypeKillAvatar);
QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer);
}
void MyAvatar::sendIdentityPacket() {
QByteArray identityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity);
identityPacket.append(AvatarData::identityByteArray());
NodeList::getInstance()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer);
}
void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
// first orbit horizontally
glm::quat orientation = getOrientation();

View file

@ -89,7 +89,6 @@ public slots:
void increaseSize();
void decreaseSize();
void resetSize();
void sendIdentityPacket();
// Set/Get update the thrust that will move the avatar around
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };

View file

@ -47,7 +47,7 @@ void AudioInjector::injectAudio() {
NodeList* nodeList = NodeList::getInstance();
// setup the packet for injected audio
QByteArray injectAudioPacket = byteArrayWithPopluatedHeader(PacketTypeInjectAudio);
QByteArray injectAudioPacket = byteArrayWithPopulatedHeader(PacketTypeInjectAudio);
QDataStream packetStream(&injectAudioPacket, QIODevice::Append);
packetStream << QUuid::createUuid();

View file

@ -36,7 +36,7 @@ AvatarData::AvatarData() :
_isChatCirclingEnabled(false),
_headData(NULL),
_handData(NULL),
_displayNameWidth(0),
_displayNameBoundingRect(),
_displayNameTargetAlpha(0.0f),
_displayNameAlpha(0.0f)
{
@ -323,7 +323,6 @@ void AvatarData::setDisplayName(const QString& displayName) {
qDebug() << "Changing display name for avatar to" << displayName;
}
void AvatarData::setClampedTargetScale(float targetScale) {
targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
@ -338,3 +337,10 @@ void AvatarData::setOrientation(const glm::quat& orientation) {
_bodyYaw = eulerAngles.y;
_bodyRoll = eulerAngles.z;
}
void AvatarData::sendIdentityPacket() {
QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
identityPacket.append(identityByteArray());
NodeList::getInstance()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer);
}

View file

@ -33,6 +33,7 @@ typedef unsigned long long quint64;
#include <QtCore/QUrl>
#include <QtCore/QUuid>
#include <QtCore/QVariantMap>
#include <QRect>
#include <CollisionInfo.h>
#include <RegisteredMetaTypes.h>
@ -52,6 +53,8 @@ static const float MIN_AVATAR_SCALE = .005f;
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fst");
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fst");
@ -80,8 +83,8 @@ class AvatarData : public NodeData {
Q_PROPERTY(float audioLoudness READ getAudioLoudness WRITE setAudioLoudness)
Q_PROPERTY(float audioAverageLoudness READ getAudioAverageLoudness WRITE setAudioAverageLoudness)
Q_PROPERTY(QUrl faceModelURL READ getFaceModelURL WRITE setFaceModelURL)
Q_PROPERTY(QUrl skeletonModelURL READ getSkeletonModelURL WRITE setSkeletonModelURL)
Q_PROPERTY(QString faceModelURL READ getFaceModelURLFromScript WRITE setFaceModelURLFromScript)
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
public:
AvatarData();
~AvatarData();
@ -149,14 +152,24 @@ public:
QByteArray identityByteArray();
const QUrl& getFaceModelURL() const { return _faceModelURL; }
QString getFaceModelURLString() const { return _faceModelURL.toString(); }
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
const QString& getDisplayName() const { return _displayName; }
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual void setDisplayName(const QString& displayName);
QString getFaceModelURLFromScript() const { return _faceModelURL.toString(); }
void setFaceModelURLFromScript(const QString& faceModelString) { setFaceModelURL(faceModelString); }
QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); }
void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); }
virtual float getBoundingRadius() const { return 1.f; }
public slots:
void sendIdentityPacket();
protected:
glm::vec3 _position;
glm::vec3 _handPosition;
@ -187,7 +200,7 @@ protected:
QUrl _skeletonModelURL;
QString _displayName;
int _displayNameWidth;
QRect _displayNameBoundingRect;
float _displayNameTargetAlpha;
float _displayNameAlpha;

View file

@ -58,6 +58,11 @@ void Octree::recurseTreeWithOperation(RecurseOctreeOperation operation, void* ex
recurseNodeWithOperation(_rootNode, operation, extraData);
}
// Recurses voxel tree calling the RecurseOctreePostFixOperation function for each node in post-fix order.
void Octree::recurseTreeWithPostOperation(RecurseOctreeOperation operation, void* extraData) {
recurseNodeWithPostOperation(_rootNode, operation, extraData);
}
// Recurses voxel node with an operation function
void Octree::recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperation operation, void* extraData,
int recursionCount) {
@ -76,6 +81,23 @@ void Octree::recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperatio
}
}
// Recurses voxel node with an operation function
void Octree::recurseNodeWithPostOperation(OctreeElement* node, RecurseOctreeOperation operation, void* extraData,
int recursionCount) {
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
qDebug() << "Octree::recurseNodeWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n";
return;
}
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
OctreeElement* child = node->getChildAtIndex(i);
if (child) {
recurseNodeWithPostOperation(child, operation, extraData, recursionCount+1);
}
}
operation(node, extraData);
}
// Recurses voxel tree calling the RecurseOctreeOperation function for each node.
// stops recursion if operation function returns false.
void Octree::recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation operation,

View file

@ -208,10 +208,12 @@ public:
OctreeElement* getOctreeElementAt(float x, float y, float z, float s) const;
OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s);
void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData=NULL);
void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData = NULL);
void recurseTreeWithPostOperation(RecurseOctreeOperation operation, void* extraData = NULL);
void recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation operation,
const glm::vec3& point, void* extraData=NULL);
const glm::vec3& point, void* extraData = NULL);
int encodeTreeBitstream(OctreeElement* node, OctreePacketData* packetData, OctreeElementBag& bag,
EncodeBitstreamParams& params) ;
@ -255,6 +257,11 @@ public:
void recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperation operation,
void* extraData, int recursionCount = 0);
/// Traverse child nodes of node applying operation in post-fix order
///
void recurseNodeWithPostOperation(OctreeElement* node, RecurseOctreeOperation operation,
void* extraData, int recursionCount = 0);
void recurseNodeWithOperationDistanceSorted(OctreeElement* node, RecurseOctreeOperation operation,
const glm::vec3& point, void* extraData, int recursionCount = 0);

View file

@ -199,6 +199,20 @@ public:
CHILD_UNKNOWN = -1
};
struct HalfSpace {
enum {
None = 0x00,
Bottom = 0x01,
Top = 0x02,
Right = 0x04,
Left = 0x08,
Near = 0x10,
Far = 0x20,
All = 0x3f,
};
};
OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s);
protected:

View file

@ -44,6 +44,7 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co
AbstractMenuInterface* menu,
AbstractControllerScriptingInterface* controllerScriptingInterface) :
_isAvatar(false),
_avatarIdentityTimer(NULL),
_avatarData(NULL)
{
_scriptContents = scriptContents;
@ -74,6 +75,21 @@ ScriptEngine::~ScriptEngine() {
//printf("ScriptEngine::~ScriptEngine()...\n");
}
void ScriptEngine::setIsAvatar(bool isAvatar) {
_isAvatar = isAvatar;
if (_isAvatar && !_avatarIdentityTimer) {
// set up the avatar identity timer
_avatarIdentityTimer = new QTimer(this);
// connect our slot
connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket);
// start the timer
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
}
}
void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) {
_avatarData = avatarData;
@ -84,6 +100,7 @@ void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectNa
registerGlobalObject(objectName, _avatarData);
}
void ScriptEngine::setupMenuItems() {
if (_menu && _wantMenuItems) {
_menu->addActionToQMenuAndActionHash(_menu->getActiveScriptsMenu(), _scriptMenuName, 0, this, SLOT(stop()));
@ -173,6 +190,12 @@ void ScriptEngine::evaluate() {
}
}
void ScriptEngine::sendAvatarIdentityPacket() {
if (_isAvatar && _avatarData) {
_avatarData->sendIdentityPacket();
}
}
void ScriptEngine::run() {
if (!_isInitialized) {
init();
@ -229,16 +252,7 @@ void ScriptEngine::run() {
}
if (_isAvatar && _avatarData) {
static QByteArray avatarPacket;
int numAvatarHeaderBytes = 0;
if (avatarPacket.size() == 0) {
// pack the avatar header bytes the first time
// unlike the _avatar.getBroadcastData these won't change
numAvatarHeaderBytes = populatePacketHeader(avatarPacket, PacketTypeAvatarData);
}
avatarPacket.resize(numAvatarHeaderBytes);
QByteArray avatarPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
avatarPacket.append(_avatarData->toByteArray());
nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer);
@ -253,6 +267,9 @@ void ScriptEngine::run() {
}
emit scriptEnding();
// kill the avatar identity timer
delete _avatarIdentityTimer;
if (_voxelsScriptingInterface.getVoxelPacketSender()->serversExist()) {
// release the queue of edit voxel messages.
_voxelsScriptingInterface.getVoxelPacketSender()->releaseQueuedMessages();

View file

@ -11,9 +11,9 @@
#include <vector>
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
#include <QtCore/QUrl>
#include <QtScript/QScriptEngine>
#include <AbstractMenuInterface.h>
#include <AudioScriptingInterface.h>
@ -52,7 +52,7 @@ public:
void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name
void setIsAvatar(bool isAvatar) { _isAvatar = isAvatar; }
Q_INVOKABLE void setIsAvatar(bool isAvatar);
bool isAvatar() const { return _isAvatar; }
void setAvatarData(AvatarData* avatarData, const QString& objectName);
@ -84,9 +84,12 @@ protected:
bool _isInitialized;
QScriptEngine _engine;
bool _isAvatar;
QTimer* _avatarIdentityTimer;
QHash<QTimer*, QScriptValue> _timerFunctionMap;
private:
void sendAvatarIdentityPacket();
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
void stopTimer(QTimer* timer);

View file

@ -0,0 +1,148 @@
//
// DataServerClient.cpp
// hifi
//
// Created by Stephen Birarda on 10/7/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QUrl>
#include <QtNetwork/QUdpSocket>
#include "NodeList.h"
#include "PacketHeaders.h"
#include "UUID.h"
#include "DataServerClient.h"
QMap<quint8, QByteArray> DataServerClient::_unmatchedPackets;
QMap<quint8, DataServerCallbackObject*> DataServerClient::_callbackObjects;
quint8 DataServerClient::_sequenceNumber = 0;
const char MULTI_KEY_VALUE_SEPARATOR = '|';
const char DATA_SERVER_HOSTNAME[] = "data.highfidelity.io";
const unsigned short DATA_SERVER_PORT = 3282;
const HifiSockAddr& DataServerClient::dataServerSockAddr() {
static HifiSockAddr dsSockAddr = HifiSockAddr(DATA_SERVER_HOSTNAME, DATA_SERVER_PORT);
return dsSockAddr;
}
void DataServerClient::putValueForKeyAndUserString(const QString& key, const QString& value, const QString& userString) {
// setup the header for this packet and push packetStream to desired spot
QByteArray putPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerPut);
QDataStream packetStream(&putPacket, QIODevice::Append);
// pack our data for the put packet
packetStream << _sequenceNumber << userString << key << value;
// add the putPacket to our vector of unconfirmed packets, will be deleted once put is confirmed
_unmatchedPackets.insert(_sequenceNumber, putPacket);
// send this put request to the data server
NodeList::getInstance()->getNodeSocket().writeDatagram(putPacket, dataServerSockAddr().getAddress(),
dataServerSockAddr().getPort());
// push the sequence number forwards
_sequenceNumber++;
}
void DataServerClient::putValueForKeyAndUUID(const QString& key, const QString& value, const QUuid& uuid) {
putValueForKeyAndUserString(key, value, uuidStringWithoutCurlyBraces(uuid));
}
void DataServerClient::getValueForKeyAndUUID(const QString& key, const QUuid &uuid, DataServerCallbackObject* callbackObject) {
getValuesForKeysAndUUID(QStringList(key), uuid, callbackObject);
}
void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid,
DataServerCallbackObject* callbackObject) {
if (!uuid.isNull()) {
getValuesForKeysAndUserString(keys, uuidStringWithoutCurlyBraces(uuid), callbackObject);
}
}
void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString,
DataServerCallbackObject* callbackObject) {
if (!userString.isEmpty() && keys.size() <= UCHAR_MAX) {
QByteArray getPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerGet);
QDataStream packetStream(&getPacket, QIODevice::Append);
// pack our data for the getPacket
packetStream << _sequenceNumber << userString << keys.join(MULTI_KEY_VALUE_SEPARATOR);
// add the getPacket to our map of unconfirmed packets, will be deleted once we get a response from the nameserver
_unmatchedPackets.insert(_sequenceNumber, getPacket);
_callbackObjects.insert(_sequenceNumber, callbackObject);
// send the get to the data server
NodeList::getInstance()->getNodeSocket().writeDatagram(getPacket, dataServerSockAddr().getAddress(),
dataServerSockAddr().getPort());
_sequenceNumber++;
}
}
void DataServerClient::getValueForKeyAndUserString(const QString& key, const QString& userString,
DataServerCallbackObject* callbackObject) {
getValuesForKeysAndUserString(QStringList(key), userString, callbackObject);
}
void DataServerClient::processConfirmFromDataServer(const QByteArray& packet) {
removeMatchedPacketFromMap(packet);
}
void DataServerClient::processSendFromDataServer(const QByteArray& packet) {
// pull the user string from the packet so we know who to associate this with
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
quint8 sequenceNumber = 0;
packetStream >> sequenceNumber;
if (_callbackObjects.find(sequenceNumber) != _callbackObjects.end()) {
// remove the packet from our two maps, it's matched
DataServerCallbackObject* callbackObject = _callbackObjects.take(sequenceNumber);
_unmatchedPackets.remove(sequenceNumber);
QString userString, keyListString, valueListString;
packetStream >> userString >> keyListString >> valueListString;
callbackObject->processDataServerResponse(userString, keyListString.split(MULTI_KEY_VALUE_SEPARATOR),
valueListString.split(MULTI_KEY_VALUE_SEPARATOR));
}
}
void DataServerClient::processMessageFromDataServer(const QByteArray& packet) {
switch (packetTypeForPacket(packet)) {
case PacketTypeDataServerSend:
processSendFromDataServer(packet);
break;
case PacketTypeDataServerConfirm:
processConfirmFromDataServer(packet);
break;
default:
break;
}
}
void DataServerClient::removeMatchedPacketFromMap(const QByteArray& packet) {
quint8 sequenceNumber = packet[numBytesForPacketHeader(packet)];
// attempt to remove a packet with this sequence number from the QMap of unmatched packets
_unmatchedPackets.remove(sequenceNumber);
}
void DataServerClient::resendUnmatchedPackets() {
if (_unmatchedPackets.size() > 0) {
qDebug() << "Resending" << _unmatchedPackets.size() << "packets to the data server.";
foreach (const QByteArray& packet, _unmatchedPackets) {
// send the unmatched packet to the data server
NodeList::getInstance()->getNodeSocket().writeDatagram(packet.data(), packet.size(),
dataServerSockAddr().getAddress(),
dataServerSockAddr().getPort());
}
}
}

View file

@ -27,27 +27,27 @@
#include "Logging.h"
HifiSockAddr Logging::logstashSocket = HifiSockAddr();
char* Logging::targetName = NULL;
HifiSockAddr Logging::_logstashSocket = HifiSockAddr();
QString Logging::_targetName = QString();
const HifiSockAddr& Logging::socket() {
if (logstashSocket.getAddress().isNull()) {
if (_logstashSocket.getAddress().isNull()) {
// we need to construct the socket object
// use the constant port
logstashSocket.setPort(htons(LOGSTASH_UDP_PORT));
_logstashSocket.setPort(htons(LOGSTASH_UDP_PORT));
// lookup the IP address for the constant hostname
QHostInfo hostInfo = QHostInfo::fromName(LOGSTASH_HOSTNAME);
if (!hostInfo.addresses().isEmpty()) {
// use the first IP address
logstashSocket.setAddress(hostInfo.addresses().first());
_logstashSocket.setAddress(hostInfo.addresses().first());
} else {
printf("Failed to lookup logstash IP - will try again on next log attempt.\n");
}
}
return logstashSocket;
return _logstashSocket;
}
bool Logging::shouldSendStats() {
@ -66,19 +66,10 @@ void Logging::stashValue(char statType, const char* key, float value) {
if (nodeList) {
nodeList->getNodeSocket().writeDatagram(logstashPacket, numPacketBytes,
logstashSocket.getAddress(), logstashSocket.getPort());
_logstashSocket.getAddress(), _logstashSocket.getPort());
}
}
void Logging::setTargetName(const char* targetName) {
// remove the old target name, if it exists
delete Logging::targetName;
// copy over the new target name
Logging::targetName = new char[strlen(targetName)];
strcpy(Logging::targetName, targetName);
}
const char* stringForLogType(QtMsgType msgType) {
switch (msgType) {
case QtDebugMsg:
@ -124,8 +115,8 @@ void Logging::verboseMessageHandler(QtMsgType type, const QMessageLogContext& co
prefixString.append("]");
}
if (Logging::targetName) {
prefixString.append(QString(" [%1]").arg(Logging::targetName));
if (!_targetName.isEmpty()) {
prefixString.append(QString(" [%1]").arg(_targetName));
}
fprintf(stdout, "%s %s\n", prefixString.toLocal8Bit().constData(), message.toLocal8Bit().constData());

View file

@ -44,14 +44,14 @@ public:
/// sets the target name to output via the verboseMessageHandler, called once before logging begins
/// \param targetName the desired target name to output in logs
static void setTargetName(const char* targetName);
static void setTargetName(const QString& targetName) { _targetName = targetName; }
/// a qtMessageHandler that can be hooked up to a target that links to Qt
/// prints various process, message type, and time information
static void verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message);
private:
static HifiSockAddr logstashSocket;
static char* targetName;
static HifiSockAddr _logstashSocket;
static QString _targetName;
};
#endif /* defined(__hifi__Logstash__) */

View file

@ -511,7 +511,7 @@ void NodeList::sendDomainServerCheckIn() {
QUuid packetUUID = (domainPacketType == PacketTypeDomainListRequest)
? _sessionUUID : _domainInfo.getAssignmentUUID();
QByteArray domainServerPacket = byteArrayWithPopluatedHeader(domainPacketType, packetUUID);
QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID);
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
if (domainPacketType == PacketTypeDomainConnectRequest) {
@ -650,7 +650,7 @@ void NodeList::sendAssignment(Assignment& assignment) {
? PacketTypeCreateAssignment
: PacketTypeRequestAssignment;
QByteArray packet = byteArrayWithPopluatedHeader(assignmentPacketType);
QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType);
QDataStream packetStream(&packet, QIODevice::Append);
packetStream << assignment;
@ -665,7 +665,7 @@ void NodeList::sendAssignment(Assignment& assignment) {
}
QByteArray NodeList::constructPingPacket(PingType_t pingType) {
QByteArray pingPacket = byteArrayWithPopluatedHeader(PacketTypePing);
QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing);
QDataStream packetStream(&pingPacket, QIODevice::Append);
@ -685,7 +685,7 @@ QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) {
quint64 timeFromOriginalPing;
pingPacketStream >> timeFromOriginalPing;
QByteArray replyPacket = byteArrayWithPopluatedHeader(PacketTypePingReply);
QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply);
QDataStream packetStream(&replyPacket, QIODevice::Append);
packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow();

View file

@ -65,7 +65,7 @@ PacketVersion versionForPacketType(PacketType type) {
}
}
QByteArray byteArrayWithPopluatedHeader(PacketType type, const QUuid& connectionUUID) {
QByteArray byteArrayWithPopulatedHeader(PacketType type, const QUuid& connectionUUID) {
QByteArray freshByteArray(MAX_PACKET_HEADER_BYTES, 0);
freshByteArray.resize(populatePacketHeader(freshByteArray, type, connectionUUID));
return freshByteArray;

View file

@ -68,7 +68,7 @@ PacketVersion versionForPacketType(PacketType type);
const QUuid nullUUID = QUuid();
QByteArray byteArrayWithPopluatedHeader(PacketType type, const QUuid& connectionUUID = nullUUID);
QByteArray byteArrayWithPopulatedHeader(PacketType type, const QUuid& connectionUUID = nullUUID);
int populatePacketHeader(QByteArray& packet, PacketType type, const QUuid& connectionUUID = nullUUID);
int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionUUID = nullUUID);

View file

@ -33,7 +33,7 @@ void ThreadedAssignment::setFinished(bool isFinished) {
}
}
void ThreadedAssignment::commonInit(const char* targetName, NodeType_t nodeType) {
void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeType) {
// change the logging target name while the assignment is running
Logging::setTargetName(targetName);

View file

@ -27,7 +27,7 @@ public slots:
protected:
bool readAvailableDatagram(QByteArray& destinationByteArray, HifiSockAddr& senderSockAddr);
void commonInit(const char* targetName, NodeType_t nodeType);
void commonInit(const QString& targetName, NodeType_t nodeType);
bool _isFinished;
private slots:
void checkInWithDomainServerOrExit();

View file

@ -13,7 +13,11 @@
#include "VoxelTreeElement.h"
#include "VoxelTree.h"
VoxelTreeElement::VoxelTreeElement(unsigned char* octalCode) : OctreeElement() {
VoxelTreeElement::VoxelTreeElement(unsigned char* octalCode) :
OctreeElement(),
_exteriorOcclusions(OctreeElement::HalfSpace::All),
_interiorOcclusions(OctreeElement::HalfSpace::None)
{
init(octalCode);
};

View file

@ -68,7 +68,12 @@ public:
void setDensity(float density) { _density = density; }
float getDensity() const { return _density; }
void setInteriorOcclusions(unsigned char interiorExclusions);
void setExteriorOcclusions(unsigned char exteriorOcclusions);
unsigned char getExteriorOcclusions() const;
unsigned char getInteriorOcclusions() const;
// type safe versions of OctreeElement methods
VoxelTreeElement* getChildAtIndex(int childIndex) { return (VoxelTreeElement*)OctreeElement::getChildAtIndex(childIndex); }
VoxelTreeElement* addChildAtIndex(int childIndex) { return (VoxelTreeElement*)OctreeElement::addChildAtIndex(childIndex); }
@ -89,6 +94,33 @@ protected:
nodeColor _trueColor; /// Client and server, true color of this voxel, 4 bytes
nodeColor _currentColor; /// Client only, false color of this voxel, 4 bytes
private:
unsigned char _exteriorOcclusions; ///< Exterior shared partition boundaries that are completely occupied
unsigned char _interiorOcclusions; ///< Interior shared partition boundaries with siblings
};
#endif /* defined(__hifi__VoxelTreeElement__) */
inline void VoxelTreeElement::setExteriorOcclusions(unsigned char exteriorOcclusions) {
if (_exteriorOcclusions != exteriorOcclusions) {
_exteriorOcclusions = exteriorOcclusions;
setDirtyBit();
}
}
inline void VoxelTreeElement::setInteriorOcclusions(unsigned char interiorOcclusions) {
if (_interiorOcclusions != interiorOcclusions) {
_interiorOcclusions = interiorOcclusions;
setDirtyBit();
}
}
inline unsigned char VoxelTreeElement::getInteriorOcclusions() const {
return _interiorOcclusions;
}
inline unsigned char VoxelTreeElement::getExteriorOcclusions() const {
return _exteriorOcclusions;
}
#endif /* defined(__hifi__VoxelTreeElement__) */