mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 00:23:33 +02:00
Merge branch 'master' of https://github.com/worklist/hifi
This commit is contained in:
commit
9b463dd8cf
62 changed files with 2589 additions and 278 deletions
|
@ -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");
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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()) {
|
||||
|
@ -123,12 +123,36 @@ void broadcastIdentityPacket() {
|
|||
}
|
||||
}
|
||||
|
||||
void broadcastBillboardPacket(const SharedNodePointer& sendingNode) {
|
||||
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(sendingNode->getLinkedData());
|
||||
QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard);
|
||||
packet.append(sendingNode->getUUID().toRfc4122());
|
||||
packet.append(nodeData->getBillboard());
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||
if (node->getType() == NodeType::Agent && node != sendingNode) {
|
||||
nodeList->writeDatagram(packet, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void broadcastBillboardPackets() {
|
||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
||||
if (node->getLinkedData() && node->getType() == NodeType::Agent) {
|
||||
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
broadcastBillboardPacket(node);
|
||||
nodeData->setHasSentBillboardBetweenKeyFrames(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
||||
if (killedNode->getType() == NodeType::Agent
|
||||
&& 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 +183,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());
|
||||
|
@ -170,6 +194,23 @@ void AvatarMixer::readPendingDatagrams() {
|
|||
nodeList->broadcastToNodes(identityPacket, NodeSet() << NodeType::Agent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PacketTypeAvatarBillboard: {
|
||||
|
||||
// check if we have a matching node in our list
|
||||
SharedNodePointer avatarNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
|
||||
if (avatarNode && avatarNode->getLinkedData()) {
|
||||
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||
if (nodeData->hasBillboardChangedAfterParsing(receivedPacket)
|
||||
&& !nodeData->hasSentBillboardBetweenKeyFrames()) {
|
||||
// this avatar changed their billboard and we haven't sent a packet in this keyframe
|
||||
broadcastBillboardPacket(avatarNode);
|
||||
nodeData->setHasSentBillboardBetweenKeyFrames(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PacketTypeKillAvatar: {
|
||||
nodeList->processKillNode(receivedPacket);
|
||||
|
@ -185,6 +226,7 @@ void AvatarMixer::readPendingDatagrams() {
|
|||
}
|
||||
|
||||
const qint64 AVATAR_IDENTITY_KEYFRAME_MSECS = 5000;
|
||||
const qint64 AVATAR_BILLBOARD_KEYFRAME_MSECS = 5000;
|
||||
|
||||
void AvatarMixer::run() {
|
||||
commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer);
|
||||
|
@ -202,6 +244,9 @@ void AvatarMixer::run() {
|
|||
QElapsedTimer identityTimer;
|
||||
identityTimer.start();
|
||||
|
||||
QElapsedTimer billboardTimer;
|
||||
billboardTimer.start();
|
||||
|
||||
while (!_isFinished) {
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
|
@ -219,6 +264,11 @@ void AvatarMixer::run() {
|
|||
// restart the timer so we do it again in AVATAR_IDENTITY_KEYFRAME_MSECS
|
||||
identityTimer.restart();
|
||||
}
|
||||
|
||||
if (billboardTimer.elapsed() >= AVATAR_BILLBOARD_KEYFRAME_MSECS) {
|
||||
broadcastBillboardPackets();
|
||||
billboardTimer.restart();
|
||||
}
|
||||
|
||||
int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * AVATAR_DATA_SEND_INTERVAL_USECS) - usecTimestampNow();
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
#include "AvatarMixerClientData.h"
|
||||
|
||||
AvatarMixerClientData::AvatarMixerClientData() :
|
||||
_hasSentIdentityBetweenKeyFrames(false)
|
||||
_hasSentIdentityBetweenKeyFrames(false),
|
||||
_hasSentBillboardBetweenKeyFrames(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -21,9 +21,15 @@ public:
|
|||
bool hasSentIdentityBetweenKeyFrames() const { return _hasSentIdentityBetweenKeyFrames; }
|
||||
void setHasSentIdentityBetweenKeyFrames(bool hasSentIdentityBetweenKeyFrames)
|
||||
{ _hasSentIdentityBetweenKeyFrames = hasSentIdentityBetweenKeyFrames; }
|
||||
|
||||
bool hasSentBillboardBetweenKeyFrames() const { return _hasSentBillboardBetweenKeyFrames; }
|
||||
void setHasSentBillboardBetweenKeyFrames(bool hasSentBillboardBetweenKeyFrames)
|
||||
{ _hasSentBillboardBetweenKeyFrames = hasSentBillboardBetweenKeyFrames; }
|
||||
|
||||
private:
|
||||
|
||||
bool _hasSentIdentityBetweenKeyFrames;
|
||||
bool _hasSentBillboardBetweenKeyFrames;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__AvatarMixerClientData__) */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -221,10 +221,10 @@ void DomainServer::readAvailableDatagrams() {
|
|||
|
||||
HifiSockAddr senderSockAddr, nodePublicAddress, nodeLocalAddress;
|
||||
|
||||
static QByteArray broadcastPacket = byteArrayWithPopluatedHeader(PacketTypeDomainList);
|
||||
static QByteArray broadcastPacket = byteArrayWithPopulatedHeader(PacketTypeDomainList);
|
||||
static int numBroadcastPacketHeaderBytes = broadcastPacket.size();
|
||||
|
||||
static QByteArray assignmentPacket = byteArrayWithPopluatedHeader(PacketTypeCreateAssignment);
|
||||
static QByteArray assignmentPacket = byteArrayWithPopulatedHeader(PacketTypeCreateAssignment);
|
||||
static int numAssignmentPacketHeaderBytes = assignmentPacket.size();
|
||||
|
||||
QByteArray receivedPacket;
|
||||
|
|
34
examples/bot.js
Normal file
34
examples/bot.js
Normal 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;
|
|
@ -869,7 +869,7 @@ function mouseReleaseEvent(event) {
|
|||
var cameraOrientation = Camera.getOrientation();
|
||||
var eulers = Quat.safeEulerAngles(cameraOrientation);
|
||||
MyAvatar.position = Camera.getPosition();
|
||||
MyAvatar.orientation = cameraOrientation;
|
||||
MyAvatar.headOrientation = cameraOrientation;
|
||||
Camera.stopLooking();
|
||||
Camera.setMode(oldMode);
|
||||
Camera.setOrientation(cameraOrientation);
|
||||
|
|
|
@ -156,6 +156,13 @@ var line3d = Overlays.addOverlay("line3d", {
|
|||
lineWidth: 5
|
||||
});
|
||||
|
||||
// this will display the content of your clipboard at the origin of the domain
|
||||
var clipboardPreview = Overlays.addOverlay("clipboard", {
|
||||
position: { x: 0, y: 0, z: 0},
|
||||
size: 1 / 32,
|
||||
visible: true
|
||||
});
|
||||
|
||||
|
||||
// When our script shuts down, we should clean up all of our overlays
|
||||
function scriptEnding() {
|
||||
|
@ -170,6 +177,7 @@ function scriptEnding() {
|
|||
Overlays.deleteOverlay(solidCube);
|
||||
Overlays.deleteOverlay(sphere);
|
||||
Overlays.deleteOverlay(line3d);
|
||||
Overlays.deleteOverlay(clipboardPreview);
|
||||
}
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
|
|
|
@ -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>")
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -98,6 +98,7 @@ const int MIRROR_VIEW_HEIGHT = 215;
|
|||
const float MIRROR_FULLSCREEN_DISTANCE = 0.35f;
|
||||
const float MIRROR_REARVIEW_DISTANCE = 0.65f;
|
||||
const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f;
|
||||
const float MIRROR_FIELD_OF_VIEW = 30.0f;
|
||||
|
||||
const QString CHECK_VERSION_URL = "http://highfidelity.io/latestVersion.xml";
|
||||
const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion";
|
||||
|
@ -253,7 +254,12 @@ 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);
|
||||
|
||||
// send the billboard packet for our avatar every few seconds
|
||||
QTimer* billboardPacketTimer = new QTimer();
|
||||
connect(billboardPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendBillboardPacket);
|
||||
billboardPacketTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);
|
||||
|
||||
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||
|
||||
|
@ -527,73 +533,8 @@ void Application::paintGL() {
|
|||
_glowEffect.render();
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
|
||||
|
||||
bool eyeRelativeCamera = false;
|
||||
if (_rearMirrorTools->getZoomLevel() == BODY) {
|
||||
_mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale());
|
||||
_mirrorCamera.setTargetPosition(_myAvatar->getChestPosition());
|
||||
} else { // HEAD zoom level
|
||||
_mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
|
||||
if (_myAvatar->getSkeletonModel().isActive() && _myAvatar->getHead()->getFaceModel().isActive()) {
|
||||
// as a hack until we have a better way of dealing with coordinate precision issues, reposition the
|
||||
// face/body so that the average eye position lies at the origin
|
||||
eyeRelativeCamera = true;
|
||||
_mirrorCamera.setTargetPosition(glm::vec3());
|
||||
|
||||
} else {
|
||||
_mirrorCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
|
||||
}
|
||||
}
|
||||
|
||||
_mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
|
||||
_mirrorCamera.update(1.0f/_fps);
|
||||
|
||||
// set the bounds of rear mirror view
|
||||
glViewport(_mirrorViewRect.x(), _glWidget->height() - _mirrorViewRect.y() - _mirrorViewRect.height(),
|
||||
_mirrorViewRect.width(), _mirrorViewRect.height());
|
||||
glScissor(_mirrorViewRect.x(), _glWidget->height() - _mirrorViewRect.y() - _mirrorViewRect.height(),
|
||||
_mirrorViewRect.width(), _mirrorViewRect.height());
|
||||
bool updateViewFrustum = false;
|
||||
updateProjectionMatrix(_mirrorCamera, updateViewFrustum);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
// render rear mirror view
|
||||
glPushMatrix();
|
||||
if (eyeRelativeCamera) {
|
||||
// save absolute translations
|
||||
glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation();
|
||||
glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation();
|
||||
|
||||
// get the eye positions relative to the neck and use them to set the face translation
|
||||
glm::vec3 leftEyePosition, rightEyePosition;
|
||||
_myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3());
|
||||
_myAvatar->getHead()->getFaceModel().getEyePositions(leftEyePosition, rightEyePosition);
|
||||
_myAvatar->getHead()->getFaceModel().setTranslation((leftEyePosition + rightEyePosition) * -0.5f);
|
||||
|
||||
// get the neck position relative to the body and use it to set the skeleton translation
|
||||
glm::vec3 neckPosition;
|
||||
_myAvatar->getSkeletonModel().setTranslation(glm::vec3());
|
||||
_myAvatar->getSkeletonModel().getNeckPosition(neckPosition);
|
||||
_myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() -
|
||||
neckPosition);
|
||||
|
||||
displaySide(_mirrorCamera, true);
|
||||
|
||||
// restore absolute translations
|
||||
_myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation);
|
||||
_myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation);
|
||||
} else {
|
||||
displaySide(_mirrorCamera, true);
|
||||
}
|
||||
glPopMatrix();
|
||||
|
||||
_rearMirrorTools->render(false);
|
||||
|
||||
// reset Viewport and projection matrix
|
||||
glViewport(0, 0, _glWidget->width(), _glWidget->height());
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
updateProjectionMatrix(_myCamera, updateViewFrustum);
|
||||
renderRearViewMirror(_mirrorViewRect);
|
||||
|
||||
} else if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
|
||||
_rearMirrorTools->render(true);
|
||||
}
|
||||
|
@ -2412,7 +2353,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);
|
||||
|
@ -2743,6 +2684,24 @@ void Application::setupWorldLight() {
|
|||
glMateriali(GL_FRONT, GL_SHININESS, 96);
|
||||
}
|
||||
|
||||
QImage Application::renderAvatarBillboard() {
|
||||
_textureCache.getPrimaryFramebufferObject()->bind();
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
const int BILLBOARD_SIZE = 64;
|
||||
renderRearViewMirror(QRect(0, _glWidget->height() - BILLBOARD_SIZE, BILLBOARD_SIZE, BILLBOARD_SIZE), true);
|
||||
|
||||
QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32);
|
||||
glReadPixels(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE, GL_BGRA, GL_UNSIGNED_BYTE, image.bits());
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
_textureCache.getPrimaryFramebufferObject()->release();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()");
|
||||
// transform by eye offset
|
||||
|
@ -3660,6 +3619,84 @@ void Application::renderCoverageMapsRecursively(CoverageMap* map) {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::renderRearViewMirror(const QRect& region, bool billboard) {
|
||||
bool eyeRelativeCamera = false;
|
||||
if (billboard) {
|
||||
_mirrorCamera.setFieldOfView(BILLBOARD_FIELD_OF_VIEW);
|
||||
_mirrorCamera.setDistance(BILLBOARD_DISTANCE * _myAvatar->getScale());
|
||||
_mirrorCamera.setTargetPosition(_myAvatar->getPosition());
|
||||
|
||||
} else if (_rearMirrorTools->getZoomLevel() == BODY) {
|
||||
_mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW);
|
||||
_mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale());
|
||||
_mirrorCamera.setTargetPosition(_myAvatar->getChestPosition());
|
||||
|
||||
} else { // HEAD zoom level
|
||||
_mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW);
|
||||
_mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
|
||||
if (_myAvatar->getSkeletonModel().isActive() && _myAvatar->getHead()->getFaceModel().isActive()) {
|
||||
// as a hack until we have a better way of dealing with coordinate precision issues, reposition the
|
||||
// face/body so that the average eye position lies at the origin
|
||||
eyeRelativeCamera = true;
|
||||
_mirrorCamera.setTargetPosition(glm::vec3());
|
||||
|
||||
} else {
|
||||
_mirrorCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
|
||||
}
|
||||
}
|
||||
_mirrorCamera.setAspectRatio((float)region.width() / region.height());
|
||||
|
||||
_mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
|
||||
_mirrorCamera.update(1.0f/_fps);
|
||||
|
||||
// set the bounds of rear mirror view
|
||||
glViewport(region.x(), _glWidget->height() - region.y() - region.height(), region.width(), region.height());
|
||||
glScissor(region.x(), _glWidget->height() - region.y() - region.height(), region.width(), region.height());
|
||||
bool updateViewFrustum = false;
|
||||
updateProjectionMatrix(_mirrorCamera, updateViewFrustum);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
// render rear mirror view
|
||||
glPushMatrix();
|
||||
if (eyeRelativeCamera) {
|
||||
// save absolute translations
|
||||
glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation();
|
||||
glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation();
|
||||
|
||||
// get the eye positions relative to the neck and use them to set the face translation
|
||||
glm::vec3 leftEyePosition, rightEyePosition;
|
||||
_myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3());
|
||||
_myAvatar->getHead()->getFaceModel().getEyePositions(leftEyePosition, rightEyePosition);
|
||||
_myAvatar->getHead()->getFaceModel().setTranslation((leftEyePosition + rightEyePosition) * -0.5f);
|
||||
|
||||
// get the neck position relative to the body and use it to set the skeleton translation
|
||||
glm::vec3 neckPosition;
|
||||
_myAvatar->getSkeletonModel().setTranslation(glm::vec3());
|
||||
_myAvatar->getSkeletonModel().getNeckPosition(neckPosition);
|
||||
_myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() -
|
||||
neckPosition);
|
||||
|
||||
displaySide(_mirrorCamera, true);
|
||||
|
||||
// restore absolute translations
|
||||
_myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation);
|
||||
_myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation);
|
||||
} else {
|
||||
displaySide(_mirrorCamera, true);
|
||||
}
|
||||
glPopMatrix();
|
||||
|
||||
if (!billboard) {
|
||||
_rearMirrorTools->render(false);
|
||||
}
|
||||
|
||||
// reset Viewport and projection matrix
|
||||
glViewport(0, 0, _glWidget->width(), _glWidget->height());
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
updateProjectionMatrix(_myCamera, updateViewFrustum);
|
||||
}
|
||||
|
||||
// renderViewFrustum()
|
||||
//
|
||||
// Description: this will render the view frustum bounds for EITHER the head
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <QApplication>
|
||||
#include <QAction>
|
||||
#include <QImage>
|
||||
#include <QSettings>
|
||||
#include <QTouchEvent>
|
||||
#include <QList>
|
||||
|
@ -96,6 +97,9 @@ static const float NODE_KILLED_BLUE = 0.0f;
|
|||
|
||||
static const QString SNAPSHOT_EXTENSION = ".jpg";
|
||||
|
||||
static const float BILLBOARD_FIELD_OF_VIEW = 30.0f;
|
||||
static const float BILLBOARD_DISTANCE = 5.0f;
|
||||
|
||||
class Application : public QApplication {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -185,6 +189,8 @@ public:
|
|||
|
||||
void setupWorldLight();
|
||||
|
||||
QImage renderAvatarBillboard();
|
||||
|
||||
void displaySide(Camera& whichCamera, bool selfAvatarOnly = false);
|
||||
|
||||
/// Loads a view matrix that incorporates the specified model translation without the precision issues that can
|
||||
|
@ -200,6 +206,8 @@ public:
|
|||
void computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal,
|
||||
float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const;
|
||||
|
||||
|
||||
|
||||
VoxelShader& getVoxelShader() { return _voxelShader; }
|
||||
PointShader& getPointShader() { return _pointShader; }
|
||||
FileLogger* getLogger() { return _logger; }
|
||||
|
@ -328,7 +336,7 @@ private:
|
|||
void displayStats();
|
||||
void checkStatsClick();
|
||||
void toggleStatsExpanded();
|
||||
void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false);
|
||||
void renderRearViewMirror(const QRect& region, bool billboard = false);
|
||||
void renderViewFrustum(ViewFrustum& viewFrustum);
|
||||
|
||||
void checkBandwidthMeterClick();
|
||||
|
|
|
@ -98,7 +98,8 @@ void DatagramProcessor::processDatagrams() {
|
|||
break;
|
||||
case PacketTypeBulkAvatarData:
|
||||
case PacketTypeKillAvatar:
|
||||
case PacketTypeAvatarIdentity: {
|
||||
case PacketTypeAvatarIdentity:
|
||||
case PacketTypeAvatarBillboard: {
|
||||
// update having heard from the avatar-mixer and record the bytes received
|
||||
SharedNodePointer avatarMixer = nodeList->sendingNodeForPacket(incomingPacket);
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -193,6 +193,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";
|
||||
|
@ -275,6 +276,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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
740
interface/src/PrimitiveRenderer.cpp
Normal file
740
interface/src/PrimitiveRenderer.cpp
Normal 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(°enerate, 0, sizeof(degenerate));
|
||||
|
||||
transferVertexElement(idx, °enerate);
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
488
interface/src/PrimitiveRenderer.h
Normal file
488
interface/src/PrimitiveRenderer.h
Normal 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
|
|
@ -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
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "Physics.h"
|
||||
#include "world.h"
|
||||
#include "devices/OculusManager.h"
|
||||
#include "renderer/TextureCache.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
|
||||
using namespace std;
|
||||
|
@ -59,6 +60,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(),
|
||||
|
@ -106,6 +108,10 @@ glm::quat Avatar::getWorldAlignedOrientation () const {
|
|||
return computeRotationFromBodyToWorldUp() * getOrientation();
|
||||
}
|
||||
|
||||
float Avatar::getLODDistance() const {
|
||||
return glm::distance(Application::getInstance()->getCamera()->getPosition(), _position) / _scale;
|
||||
}
|
||||
|
||||
void Avatar::simulate(float deltaTime) {
|
||||
if (_scale != _targetScale) {
|
||||
setScale(_targetScale);
|
||||
|
@ -115,9 +121,9 @@ void Avatar::simulate(float deltaTime) {
|
|||
glm::vec3 oldVelocity = getVelocity();
|
||||
|
||||
getHand()->simulate(deltaTime, false);
|
||||
_skeletonModel.setLODDistance(getLODDistance());
|
||||
_skeletonModel.simulate(deltaTime);
|
||||
Head* head = getHead();
|
||||
head->setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
|
||||
glm::vec3 headPosition;
|
||||
if (!_skeletonModel.getHeadPosition(headPosition)) {
|
||||
headPosition = _position;
|
||||
|
@ -217,7 +223,7 @@ void Avatar::render(bool forceRenderHead) {
|
|||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
const float DISPLAYNAME_DISTANCE = 4.0f;
|
||||
const float DISPLAYNAME_DISTANCE = 10.0f;
|
||||
setShowDisplayName(lengthToTarget < DISPLAYNAME_DISTANCE);
|
||||
renderDisplayName();
|
||||
|
||||
|
@ -281,9 +287,11 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
|
|||
}
|
||||
|
||||
void Avatar::renderBody(bool forceRenderHead) {
|
||||
// Render the body's voxels and head
|
||||
glm::vec3 pos = getPosition();
|
||||
//printf("Render other at %.3f, %.2f, %.2f\n", pos.x, pos.y, pos.z);
|
||||
const float BILLBOARD_DISTANCE = 40.0f;
|
||||
if (!_billboard.isEmpty() && getLODDistance() >= BILLBOARD_DISTANCE) {
|
||||
renderBillboard();
|
||||
return;
|
||||
}
|
||||
_skeletonModel.render(1.0f);
|
||||
if (forceRenderHead) {
|
||||
getHead()->render(1.0f);
|
||||
|
@ -291,6 +299,62 @@ void Avatar::renderBody(bool forceRenderHead) {
|
|||
getHand()->render(false);
|
||||
}
|
||||
|
||||
void Avatar::renderBillboard() {
|
||||
if (!_billboardTexture) {
|
||||
QImage image = QImage::fromData(_billboard).convertToFormat(QImage::Format_ARGB32);
|
||||
|
||||
_billboardTexture.reset(new Texture());
|
||||
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,
|
||||
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
|
||||
}
|
||||
|
||||
glEnable(GL_ALPHA_TEST);
|
||||
glAlphaFunc(GL_GREATER, 0.5f);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glDisable(GL_LIGHTING);
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(_position.x, _position.y, _position.z);
|
||||
|
||||
// rotate about vertical to face the camera
|
||||
glm::quat rotation = getOrientation();
|
||||
glm::vec3 cameraVector = glm::inverse(rotation) * (Application::getInstance()->getCamera()->getPosition() - _position);
|
||||
rotation = rotation * glm::angleAxis(glm::degrees(atan2f(-cameraVector.x, -cameraVector.z)), 0.0f, 1.0f, 0.0f);
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
|
||||
|
||||
// compute the size from the billboard camera parameters and scale
|
||||
float size = _scale * BILLBOARD_DISTANCE * tanf(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f));
|
||||
glScalef(size, size, size);
|
||||
|
||||
glColor3f(1.0f, 1.0f, 1.0f);
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(0.0f, 0.0f);
|
||||
glVertex2f(-1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f);
|
||||
glVertex2f(1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f);
|
||||
glVertex2f(1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f);
|
||||
glVertex2f(-1.0f, 1.0f);
|
||||
glEnd();
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glEnable(GL_LIGHTING);
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void Avatar::renderDisplayName() {
|
||||
|
||||
if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) {
|
||||
|
@ -300,7 +364,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
|
||||
|
@ -339,11 +406,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;
|
||||
|
@ -353,7 +426,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);
|
||||
|
@ -361,14 +434,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);
|
||||
|
||||
|
||||
}
|
||||
|
@ -489,11 +562,14 @@ 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);
|
||||
}
|
||||
|
||||
void Avatar::setBillboard(const QByteArray& billboard) {
|
||||
AvatarData::setBillboard(billboard);
|
||||
|
||||
// clear out any existing billboard texture
|
||||
_billboardTexture.reset();
|
||||
}
|
||||
|
||||
int Avatar::parseData(const QByteArray& packet) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QtCore/QScopedPointer>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <AvatarData.h>
|
||||
|
@ -62,6 +63,8 @@ enum ScreenTintLayer {
|
|||
// Grayson as he's building a street around here for demo dinner 2
|
||||
const glm::vec3 START_LOCATION(0.485f * TREE_SCALE, 0.f, 0.5f * TREE_SCALE);
|
||||
|
||||
class Texture;
|
||||
|
||||
class Avatar : public AvatarData {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -87,6 +90,9 @@ public:
|
|||
Head* getHead() { return static_cast<Head*>(_headData); }
|
||||
Hand* getHand() { return static_cast<Hand*>(_handData); }
|
||||
glm::quat getWorldAlignedOrientation() const;
|
||||
|
||||
/// Returns the distance to use as a LOD parameter.
|
||||
float getLODDistance() const;
|
||||
|
||||
Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); }
|
||||
void setOwningAvatarMixer(const QWeakPointer<Node>& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; }
|
||||
|
@ -114,6 +120,7 @@ public:
|
|||
virtual void setFaceModelURL(const QUrl& faceModelURL);
|
||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||
virtual void setDisplayName(const QString& displayName);
|
||||
virtual void setBillboard(const QByteArray& billboard);
|
||||
|
||||
void setShowDisplayName(bool showDisplayName);
|
||||
|
||||
|
@ -167,8 +174,10 @@ protected:
|
|||
private:
|
||||
|
||||
bool _initialized;
|
||||
QScopedPointer<Texture> _billboardTexture;
|
||||
|
||||
void renderBody(bool forceRenderHead);
|
||||
void renderBillboard();
|
||||
|
||||
void renderDisplayName();
|
||||
};
|
||||
|
|
|
@ -133,6 +133,9 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const
|
|||
case PacketTypeAvatarIdentity:
|
||||
processAvatarIdentityPacket(datagram);
|
||||
break;
|
||||
case PacketTypeAvatarBillboard:
|
||||
processAvatarBillboardPacket(datagram);
|
||||
break;
|
||||
case PacketTypeKillAvatar:
|
||||
processKillAvatar(datagram);
|
||||
break;
|
||||
|
@ -144,7 +147,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;
|
||||
|
||||
|
@ -212,6 +215,20 @@ void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) {
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet) {
|
||||
int headerSize = numBytesForPacketHeader(packet);
|
||||
QUuid nodeUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID);
|
||||
if (matchingAvatar) {
|
||||
Avatar* avatar = static_cast<Avatar*>(matchingAvatar.data());
|
||||
QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID);
|
||||
if (avatar->getBillboard() != billboard) {
|
||||
avatar->setBillboard(billboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarManager::processKillAvatar(const QByteArray& datagram) {
|
||||
// read the node id
|
||||
QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(datagram), NUM_BYTES_RFC4122_UUID));
|
||||
|
|
|
@ -42,6 +42,7 @@ private:
|
|||
|
||||
void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
|
||||
void processAvatarIdentityPacket(const QByteArray& packet);
|
||||
void processAvatarBillboardPacket(const QByteArray& packet);
|
||||
void processKillAvatar(const QByteArray& datagram);
|
||||
|
||||
void simulateAvatarFades(float deltaTime);
|
||||
|
|
|
@ -28,7 +28,6 @@ Head::Head(Avatar* owningAvatar) :
|
|||
_gravity(0.0f, -1.0f, 0.0f),
|
||||
_lastLoudness(0.0f),
|
||||
_audioAttack(0.0f),
|
||||
_bodyRotation(0.0f, 0.0f, 0.0f),
|
||||
_angularVelocity(0,0,0),
|
||||
_renderLookatVectors(false),
|
||||
_saccade(0.0f, 0.0f, 0.0f),
|
||||
|
@ -158,6 +157,9 @@ void Head::simulate(float deltaTime, bool isMine) {
|
|||
glm::clamp(sqrt(_averageLoudness * JAW_OPEN_SCALE) - JAW_OPEN_DEAD_ZONE, 0.0f, 1.0f), _blendshapeCoefficients);
|
||||
}
|
||||
|
||||
if (!isMine) {
|
||||
_faceModel.setLODDistance(static_cast<Avatar*>(_owningAvatar)->getLODDistance());
|
||||
}
|
||||
_faceModel.simulate(deltaTime);
|
||||
|
||||
// the blend face may have custom eye meshes
|
||||
|
@ -180,12 +182,8 @@ void Head::setScale (float scale) {
|
|||
_scale = scale;
|
||||
}
|
||||
|
||||
glm::quat Head::getOrientation() const {
|
||||
return glm::quat(glm::radians(_bodyRotation)) * glm::quat(glm::radians(glm::vec3(_pitch, _yaw, _roll)));
|
||||
}
|
||||
|
||||
glm::quat Head::getTweakedOrientation() const {
|
||||
return glm::quat(glm::radians(_bodyRotation)) * glm::quat(glm::radians(glm::vec3(getTweakedPitch(), getTweakedYaw(), getTweakedRoll() )));
|
||||
return _owningAvatar->getOrientation() * glm::quat(glm::radians(glm::vec3(getTweakedPitch(), getTweakedYaw(), getTweakedRoll() )));
|
||||
}
|
||||
|
||||
glm::quat Head::getCameraOrientation () const {
|
||||
|
|
|
@ -40,13 +40,11 @@ public:
|
|||
void render(float alpha);
|
||||
void setScale(float scale);
|
||||
void setPosition(glm::vec3 position) { _position = position; }
|
||||
void setBodyRotation(glm::vec3 bodyRotation) { _bodyRotation = bodyRotation; }
|
||||
void setGravity(glm::vec3 gravity) { _gravity = gravity; }
|
||||
void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; }
|
||||
void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; }
|
||||
void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; }
|
||||
|
||||
glm::quat getOrientation() const;
|
||||
glm::quat getTweakedOrientation() const;
|
||||
glm::quat getCameraOrientation () const;
|
||||
const glm::vec3& getAngularVelocity() const { return _angularVelocity; }
|
||||
|
@ -97,7 +95,6 @@ private:
|
|||
glm::vec3 _gravity;
|
||||
float _lastLoudness;
|
||||
float _audioAttack;
|
||||
glm::vec3 _bodyRotation;
|
||||
glm::vec3 _angularVelocity;
|
||||
bool _renderLookatVectors;
|
||||
glm::vec3 _saccade;
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
|
||||
#include <NodeList.h>
|
||||
|
@ -57,7 +59,8 @@ MyAvatar::MyAvatar() :
|
|||
_thrustMultiplier(1.0f),
|
||||
_moveTarget(0,0,0),
|
||||
_moveTargetStepCounter(0),
|
||||
_lookAtTargetAvatar()
|
||||
_lookAtTargetAvatar(),
|
||||
_billboardValid(false)
|
||||
{
|
||||
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
|
||||
_driveKeys[i] = 0.0f;
|
||||
|
@ -321,7 +324,6 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
_skeletonModel.simulate(deltaTime);
|
||||
|
||||
Head* head = getHead();
|
||||
head->setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
|
||||
glm::vec3 headPosition;
|
||||
if (!_skeletonModel.getHeadPosition(headPosition)) {
|
||||
headPosition = _position;
|
||||
|
@ -332,7 +334,9 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
|
||||
// Zero thrust out now that we've added it to velocity in this frame
|
||||
_thrust = glm::vec3(0, 0, 0);
|
||||
|
||||
|
||||
// consider updating our billboard
|
||||
maybeUpdateBillboard();
|
||||
}
|
||||
|
||||
const float MAX_PITCH = 90.0f;
|
||||
|
@ -644,17 +648,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();
|
||||
|
@ -712,6 +709,16 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const {
|
|||
return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, getPelvisToHeadLength(), 0.0f);
|
||||
}
|
||||
|
||||
void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) {
|
||||
Avatar::setFaceModelURL(faceModelURL);
|
||||
_billboardValid = false;
|
||||
}
|
||||
|
||||
void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||
Avatar::setSkeletonModelURL(skeletonModelURL);
|
||||
_billboardValid = false;
|
||||
}
|
||||
|
||||
void MyAvatar::renderBody(bool forceRenderHead) {
|
||||
// Render the body's voxels and head
|
||||
_skeletonModel.render(1.0f);
|
||||
|
@ -1131,6 +1138,20 @@ void MyAvatar::updateChatCircle(float deltaTime) {
|
|||
_position = glm::mix(_position, targetPosition, APPROACH_RATE);
|
||||
}
|
||||
|
||||
void MyAvatar::maybeUpdateBillboard() {
|
||||
if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) {
|
||||
return;
|
||||
}
|
||||
QImage image = Application::getInstance()->renderAvatarBillboard();
|
||||
_billboard.clear();
|
||||
QBuffer buffer(&_billboard);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
image.save(&buffer, "PNG");
|
||||
_billboardValid = true;
|
||||
|
||||
sendBillboardPacket();
|
||||
}
|
||||
|
||||
void MyAvatar::setGravity(glm::vec3 gravity) {
|
||||
_gravity = gravity;
|
||||
getHead()->setGravity(_gravity);
|
||||
|
|
|
@ -84,12 +84,14 @@ public:
|
|||
void updateLookAtTargetAvatar(glm::vec3& eyePosition);
|
||||
void clearLookAtTargetAvatar();
|
||||
|
||||
virtual void setFaceModelURL(const QUrl& faceModelURL);
|
||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||
|
||||
public slots:
|
||||
void goHome();
|
||||
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; };
|
||||
|
@ -119,6 +121,8 @@ private:
|
|||
glm::vec3 _transmitterPickStart;
|
||||
glm::vec3 _transmitterPickEnd;
|
||||
|
||||
bool _billboardValid;
|
||||
|
||||
// private methods
|
||||
void renderBody(bool forceRenderHead);
|
||||
void updateThrust(float deltaTime);
|
||||
|
@ -129,6 +133,7 @@ private:
|
|||
void applyHardCollision(const glm::vec3& penetration, float elasticity, float damping);
|
||||
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
|
||||
void updateChatCircle(float deltaTime);
|
||||
void maybeUpdateBillboard();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -17,8 +17,8 @@ using namespace std;
|
|||
|
||||
Model::Model(QObject* parent) :
|
||||
QObject(parent),
|
||||
_pupilDilation(0.0f)
|
||||
{
|
||||
_lodDistance(0.0f),
|
||||
_pupilDilation(0.0f) {
|
||||
// we may have been created in the network thread, but we live in the main thread
|
||||
moveToThread(Application::getInstance()->thread());
|
||||
}
|
||||
|
@ -46,6 +46,21 @@ void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locati
|
|||
program.release();
|
||||
}
|
||||
|
||||
bool Model::isLoadedWithTextures() const {
|
||||
if (!isActive()) {
|
||||
return false;
|
||||
}
|
||||
foreach (const NetworkMesh& mesh, _geometry->getMeshes()) {
|
||||
foreach (const NetworkMeshPart& part, mesh.parts) {
|
||||
if (part.diffuseTexture && !part.diffuseTexture->isLoaded() ||
|
||||
part.normalTexture && !part.normalTexture->isLoaded()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Model::init() {
|
||||
if (!_program.isLinked()) {
|
||||
switchToResourcesParentIfRequired();
|
||||
|
@ -92,8 +107,7 @@ void Model::reset() {
|
|||
void Model::simulate(float deltaTime) {
|
||||
// update our LOD
|
||||
if (_geometry) {
|
||||
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(glm::distance(_translation,
|
||||
Application::getInstance()->getCamera()->getPosition()), _lodHysteresis);
|
||||
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis);
|
||||
if (_geometry != geometry) {
|
||||
deleteGeometry();
|
||||
_dilatedTextures.clear();
|
||||
|
|
|
@ -46,6 +46,8 @@ public:
|
|||
|
||||
bool isActive() const { return _geometry && _geometry->isLoaded(); }
|
||||
|
||||
bool isLoadedWithTextures() const;
|
||||
|
||||
void init();
|
||||
void reset();
|
||||
void simulate(float deltaTime);
|
||||
|
@ -54,6 +56,9 @@ public:
|
|||
Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl());
|
||||
const QUrl& getURL() const { return _url; }
|
||||
|
||||
/// Sets the distance parameter used for LOD computations.
|
||||
void setLODDistance(float distance) { _lodDistance = distance; }
|
||||
|
||||
/// Returns the extents of the model in its bind pose.
|
||||
Extents getBindExtents() const;
|
||||
|
||||
|
@ -228,13 +233,14 @@ private:
|
|||
void renderMeshes(float alpha, bool translucent);
|
||||
|
||||
QSharedPointer<NetworkGeometry> _baseGeometry; ///< reference required to prevent collection of base
|
||||
float _lodDistance;
|
||||
float _lodHysteresis;
|
||||
|
||||
float _pupilDilation;
|
||||
std::vector<float> _blendshapeCoefficients;
|
||||
|
||||
QUrl _url;
|
||||
|
||||
|
||||
QVector<GLuint> _blendedVertexBufferIDs;
|
||||
QVector<QVector<QSharedPointer<Texture> > > _dilatedTextures;
|
||||
bool _resetStates;
|
||||
|
|
|
@ -258,9 +258,11 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) :
|
|||
_reply(NULL),
|
||||
_attempts(0),
|
||||
_averageColor(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
_translucent(false) {
|
||||
_translucent(false),
|
||||
_loaded(false) {
|
||||
|
||||
if (!url.isValid()) {
|
||||
_loaded = true;
|
||||
return;
|
||||
}
|
||||
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||
|
@ -298,6 +300,7 @@ void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTo
|
|||
_reply->disconnect(this);
|
||||
_reply->deleteLater();
|
||||
_reply = NULL;
|
||||
_loaded = true;
|
||||
|
||||
QImage image = QImage::fromData(entirety).convertToFormat(QImage::Format_ARGB32);
|
||||
|
||||
|
@ -345,6 +348,8 @@ void NetworkTexture::handleReplyError() {
|
|||
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest()));
|
||||
debug << " -- retrying...";
|
||||
|
||||
} else {
|
||||
_loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -118,6 +118,8 @@ public:
|
|||
NetworkTexture(const QUrl& url, bool normalMap);
|
||||
~NetworkTexture();
|
||||
|
||||
bool isLoaded() const { return _loaded; }
|
||||
|
||||
/// Returns the average color over the entire texture.
|
||||
const glm::vec4& getAverageColor() const { return _averageColor; }
|
||||
|
||||
|
@ -142,6 +144,7 @@ private:
|
|||
int _attempts;
|
||||
glm::vec4 _averageColor;
|
||||
bool _translucent;
|
||||
bool _loaded;
|
||||
};
|
||||
|
||||
/// Caches derived, dilated textures.
|
||||
|
|
50
interface/src/ui/ClipboardOverlay.cpp
Normal file
50
interface/src/ui/ClipboardOverlay.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// ClipboardOverlay.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Clément Brisset on 2/20/14.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||
#include "InterfaceConfig.h"
|
||||
|
||||
#include <QGLWidget>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "ClipboardOverlay.h"
|
||||
#include "../Application.h"
|
||||
|
||||
static int lastVoxelCount = 0;
|
||||
|
||||
ClipboardOverlay::ClipboardOverlay() {
|
||||
}
|
||||
|
||||
ClipboardOverlay::~ClipboardOverlay() {
|
||||
}
|
||||
|
||||
void ClipboardOverlay::render() {
|
||||
if (!_visible) {
|
||||
return; // do nothing if we're not visible
|
||||
}
|
||||
|
||||
VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem();
|
||||
VoxelTree* clipboard = Application::getInstance()->getClipboard();
|
||||
if (voxelSystem->getTree() != clipboard) {
|
||||
voxelSystem->changeTree(clipboard);
|
||||
}
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(_position.x, _position.y, _position.z);
|
||||
glScalef(_size, _size, _size);
|
||||
|
||||
// We only force the redraw when the clipboard content has changed
|
||||
if (lastVoxelCount != clipboard->getOctreeElementsCount()) {
|
||||
voxelSystem->forceRedrawEntireTree();
|
||||
lastVoxelCount = clipboard->getOctreeElementsCount();
|
||||
}
|
||||
|
||||
voxelSystem->render();
|
||||
|
||||
glPopMatrix();
|
||||
}
|
25
interface/src/ui/ClipboardOverlay.h
Normal file
25
interface/src/ui/ClipboardOverlay.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// ClipboardOverlay.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Clément Brisset on 2/20/14.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __interface__ClipboardOverlay__
|
||||
#define __interface__ClipboardOverlay__
|
||||
|
||||
#include "Volume3DOverlay.h"
|
||||
|
||||
class ClipboardOverlay : public Volume3DOverlay {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ClipboardOverlay();
|
||||
~ClipboardOverlay();
|
||||
|
||||
virtual void render();
|
||||
};
|
||||
|
||||
|
||||
#endif /* defined(__interface__ClipboardOverlay__) */
|
|
@ -12,7 +12,7 @@
|
|||
#include "Overlays.h"
|
||||
#include "Sphere3DOverlay.h"
|
||||
#include "TextOverlay.h"
|
||||
|
||||
#include "ClipboardOverlay.h"
|
||||
|
||||
unsigned int Overlays::_nextOverlayID = 1;
|
||||
|
||||
|
@ -73,6 +73,12 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
|
|||
thisOverlay->setProperties(properties);
|
||||
created = true;
|
||||
is3D = true;
|
||||
} else if (type == "clipboard") {
|
||||
thisOverlay = new ClipboardOverlay();
|
||||
thisOverlay->init(_parent);
|
||||
thisOverlay->setProperties(properties);
|
||||
created = true;
|
||||
is3D = true;
|
||||
}
|
||||
|
||||
if (created) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -36,7 +36,7 @@ AvatarData::AvatarData() :
|
|||
_isChatCirclingEnabled(false),
|
||||
_headData(NULL),
|
||||
_handData(NULL),
|
||||
_displayNameWidth(0),
|
||||
_displayNameBoundingRect(),
|
||||
_displayNameTargetAlpha(0.0f),
|
||||
_displayNameAlpha(0.0f)
|
||||
{
|
||||
|
@ -305,6 +305,15 @@ QByteArray AvatarData::identityByteArray() {
|
|||
return identityData;
|
||||
}
|
||||
|
||||
bool AvatarData::hasBillboardChangedAfterParsing(const QByteArray& packet) {
|
||||
QByteArray newBillboard = packet.mid(numBytesForPacketHeader(packet));
|
||||
if (newBillboard == _billboard) {
|
||||
return false;
|
||||
}
|
||||
_billboard = newBillboard;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AvatarData::setFaceModelURL(const QUrl& faceModelURL) {
|
||||
_faceModelURL = faceModelURL.isEmpty() ? DEFAULT_HEAD_MODEL_URL : faceModelURL;
|
||||
|
||||
|
@ -323,6 +332,11 @@ void AvatarData::setDisplayName(const QString& displayName) {
|
|||
qDebug() << "Changing display name for avatar to" << displayName;
|
||||
}
|
||||
|
||||
void AvatarData::setBillboard(const QByteArray& billboard) {
|
||||
_billboard = billboard;
|
||||
|
||||
qDebug() << "Changing billboard for avatar.";
|
||||
}
|
||||
|
||||
void AvatarData::setClampedTargetScale(float targetScale) {
|
||||
|
||||
|
@ -338,3 +352,17 @@ 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);
|
||||
}
|
||||
|
||||
void AvatarData::sendBillboardPacket() {
|
||||
QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard);
|
||||
billboardPacket.append(_billboard);
|
||||
|
||||
NodeList::getInstance()->broadcastToNodes(billboardPacket, NodeSet() << NodeType::AvatarMixer);
|
||||
}
|
||||
|
|
|
@ -29,10 +29,12 @@ typedef unsigned long long quint64;
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QUuid>
|
||||
#include <QtCore/QVariantMap>
|
||||
#include <QRect>
|
||||
|
||||
#include <CollisionInfo.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
@ -52,6 +54,9 @@ 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 int AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS = 5000;
|
||||
|
||||
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");
|
||||
|
||||
|
@ -75,13 +80,14 @@ class AvatarData : public NodeData {
|
|||
Q_PROPERTY(QString chatMessage READ getQStringChatMessage WRITE setChatMessage)
|
||||
|
||||
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation)
|
||||
Q_PROPERTY(glm::quat headOrientation READ getHeadOrientation WRITE setHeadOrientation)
|
||||
Q_PROPERTY(float headPitch READ getHeadPitch WRITE setHeadPitch)
|
||||
|
||||
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();
|
||||
|
@ -106,6 +112,9 @@ public:
|
|||
glm::quat getOrientation() const { return glm::quat(glm::radians(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll))); }
|
||||
void setOrientation(const glm::quat& orientation);
|
||||
|
||||
glm::quat getHeadOrientation() const { return _headData->getOrientation(); }
|
||||
void setHeadOrientation(const glm::quat& orientation) { _headData->setOrientation(orientation); }
|
||||
|
||||
// access to Head().set/getMousePitch
|
||||
float getHeadPitch() const { return _headData->getPitch(); }
|
||||
void setHeadPitch(float value) { _headData->setPitch(value); };
|
||||
|
@ -148,15 +157,31 @@ public:
|
|||
bool hasIdentityChangedAfterParsing(const QByteArray& packet);
|
||||
QByteArray identityByteArray();
|
||||
|
||||
bool hasBillboardChangedAfterParsing(const QByteArray& packet);
|
||||
|
||||
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);
|
||||
|
||||
virtual void setBillboard(const QByteArray& billboard);
|
||||
const QByteArray& getBillboard() const { return _billboard; }
|
||||
|
||||
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();
|
||||
void sendBillboardPacket();
|
||||
|
||||
protected:
|
||||
glm::vec3 _position;
|
||||
glm::vec3 _handPosition;
|
||||
|
@ -187,10 +212,12 @@ protected:
|
|||
QUrl _skeletonModelURL;
|
||||
QString _displayName;
|
||||
|
||||
int _displayNameWidth;
|
||||
QRect _displayNameBoundingRect;
|
||||
float _displayNameTargetAlpha;
|
||||
float _displayNameAlpha;
|
||||
|
||||
QByteArray _billboard;
|
||||
|
||||
private:
|
||||
// privatize the copy constructor and assignment operator so they cannot be called
|
||||
AvatarData(const AvatarData&);
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <OctreeConstants.h>
|
||||
|
||||
#include "AvatarData.h"
|
||||
#include "HeadData.h"
|
||||
|
||||
HeadData::HeadData(AvatarData* owningAvatar) :
|
||||
|
@ -26,6 +31,24 @@ HeadData::HeadData(AvatarData* owningAvatar) :
|
|||
|
||||
}
|
||||
|
||||
glm::quat HeadData::getOrientation() const {
|
||||
return _owningAvatar->getOrientation() * glm::quat(glm::radians(glm::vec3(_pitch, _yaw, _roll)));
|
||||
}
|
||||
|
||||
void HeadData::setOrientation(const glm::quat& orientation) {
|
||||
// rotate body about vertical axis
|
||||
glm::quat bodyOrientation = _owningAvatar->getOrientation();
|
||||
glm::vec3 newFront = glm::inverse(bodyOrientation) * (orientation * IDENTITY_FRONT);
|
||||
bodyOrientation = bodyOrientation * glm::angleAxis(glm::degrees(atan2f(-newFront.x, -newFront.z)), 0.0f, 1.0f, 0.0f);
|
||||
_owningAvatar->setOrientation(bodyOrientation);
|
||||
|
||||
// the rest goes to the head
|
||||
glm::vec3 eulers = safeEulerAngles(glm::inverse(bodyOrientation) * orientation);
|
||||
_pitch = eulers.x;
|
||||
_yaw = eulers.y;
|
||||
_roll = eulers.z;
|
||||
}
|
||||
|
||||
void HeadData::addYaw(float yaw) {
|
||||
setYaw(_yaw + yaw);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
const float MIN_HEAD_YAW = -110;
|
||||
const float MAX_HEAD_YAW = 110;
|
||||
|
@ -42,6 +43,9 @@ public:
|
|||
float getRoll() const { return _roll; }
|
||||
void setRoll(float roll) { _roll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); }
|
||||
|
||||
glm::quat getOrientation() const;
|
||||
void setOrientation(const glm::quat& orientation);
|
||||
|
||||
float getAudioLoudness() const { return _audioLoudness; }
|
||||
void setAudioLoudness(float audioLoudness) { _audioLoudness = audioLoudness; }
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ const HifiSockAddr& DataServerClient::dataServerSockAddr() {
|
|||
|
||||
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 = byteArrayWithPopluatedHeader(PacketTypeDataServerPut);
|
||||
QByteArray putPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerPut);
|
||||
QDataStream packetStream(&putPacket, QIODevice::Append);
|
||||
|
||||
// pack our data for the put packet
|
||||
|
@ -66,7 +66,7 @@ void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QU
|
|||
void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString,
|
||||
DataServerCallbackObject* callbackObject) {
|
||||
if (!userString.isEmpty() && keys.size() <= UCHAR_MAX) {
|
||||
QByteArray getPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerGet);
|
||||
QByteArray getPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerGet);
|
||||
QDataStream packetStream(&getPacket, QIODevice::Append);
|
||||
|
||||
// pack our data for the getPacket
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -531,7 +531,7 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
// construct the DS check in packet if we can
|
||||
|
||||
// check in packet has header, optional UUID, node type, port, IP, node types of interest, null termination
|
||||
QByteArray domainServerPacket = byteArrayWithPopluatedHeader(PacketTypeDomainListRequest);
|
||||
QByteArray domainServerPacket = byteArrayWithPopulatedHeader(PacketTypeDomainListRequest);
|
||||
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
|
||||
|
||||
// pack our data to send to the domain-server
|
||||
|
@ -619,7 +619,7 @@ void NodeList::sendAssignment(Assignment& assignment) {
|
|||
? PacketTypeCreateAssignment
|
||||
: PacketTypeRequestAssignment;
|
||||
|
||||
QByteArray packet = byteArrayWithPopluatedHeader(assignmentPacketType);
|
||||
QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType);
|
||||
QDataStream packetStream(&packet, QIODevice::Append);
|
||||
|
||||
packetStream << assignment;
|
||||
|
@ -634,7 +634,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);
|
||||
|
||||
|
@ -654,7 +654,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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -53,7 +53,8 @@ enum PacketType {
|
|||
PacketTypeParticleErase,
|
||||
PacketTypeParticleAddResponse,
|
||||
PacketTypeMetavoxelData,
|
||||
PacketTypeAvatarIdentity
|
||||
PacketTypeAvatarIdentity,
|
||||
PacketTypeAvatarBillboard
|
||||
};
|
||||
|
||||
typedef char PacketVersion;
|
||||
|
@ -66,7 +67,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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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__) */
|
||||
|
||||
|
|
Loading…
Reference in a new issue