mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-14 03:06:35 +02:00
merge of latest upstream work in preparation for pull request #1500
Conflicts: libraries/particles/src/ParticleCollisionSystem.cpp
This commit is contained in:
commit
c61f071ab7
90 changed files with 3945 additions and 2814 deletions
|
@ -6,12 +6,21 @@ set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} $ENV{QT_CMAKE_PREFIX_PATH})
|
|||
|
||||
# set our Base SDK to 10.8
|
||||
set(CMAKE_OSX_SYSROOT /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8)
|
||||
|
||||
# Find includes in corresponding build directories
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
# Instruct CMake to run moc automatically when needed.
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
IF (APPLE)
|
||||
EXEC_PROGRAM(uname ARGS -v OUTPUT_VARIABLE DARWIN_VERSION)
|
||||
STRING(REGEX MATCH "[0-9]+" DARWIN_VERSION ${DARWIN_VERSION})
|
||||
IF (DARWIN_VERSION GREATER 12)
|
||||
SET(CMAKE_CXX_FLAGS "-stdlib=libstdc++")
|
||||
ENDIF (DARWIN_VERSION GREATER 12)
|
||||
ENDIF(APPLE)
|
||||
|
||||
add_subdirectory(animation-server)
|
||||
add_subdirectory(assignment-client)
|
||||
add_subdirectory(domain-server)
|
||||
|
|
|
@ -9,12 +9,13 @@ set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
|
|||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/")
|
||||
|
||||
find_package(Qt5Network REQUIRED)
|
||||
find_package(Qt5Script REQUIRED)
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiProject.cmake)
|
||||
setup_hifi_project(${TARGET_NAME} TRUE)
|
||||
|
||||
qt5_use_modules(${TARGET_NAME} Network Widgets)
|
||||
qt5_use_modules(${TARGET_NAME} Network Script Widgets)
|
||||
|
||||
# include glm
|
||||
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||
|
@ -28,6 +29,7 @@ link_hifi_library(avatars ${TARGET_NAME} ${ROOT_DIR})
|
|||
link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR})
|
||||
link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR})
|
||||
link_hifi_library(particles ${TARGET_NAME} ${ROOT_DIR})
|
||||
link_hifi_library(metavoxels ${TARGET_NAME} ${ROOT_DIR})
|
||||
link_hifi_library(octree-server ${TARGET_NAME} ${ROOT_DIR})
|
||||
link_hifi_library(particle-server ${TARGET_NAME} ${ROOT_DIR})
|
||||
link_hifi_library(voxel-server ${TARGET_NAME} ${ROOT_DIR})
|
||||
|
@ -39,8 +41,3 @@ include_directories(${ROOT_DIR}/externals/civetweb/include)
|
|||
if (UNIX)
|
||||
target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS})
|
||||
endif (UNIX)
|
||||
|
||||
# link curl for synchronous script downloads
|
||||
find_package(CURL REQUIRED)
|
||||
include_directories(${CURL_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${CURL_LIBRARY})
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <PacketHeaders.h>
|
||||
#include <UUID.h>
|
||||
#include <VoxelConstants.h>
|
||||
#include <ParticlesScriptingInterface.h>
|
||||
|
||||
#include "Agent.h"
|
||||
|
||||
|
|
|
@ -8,13 +8,15 @@
|
|||
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "Agent.h"
|
||||
#include "audio/AudioMixer.h"
|
||||
#include "avatars/AvatarMixer.h"
|
||||
#include <VoxelServer.h>
|
||||
#include <ParticleServer.h>
|
||||
|
||||
#include <VoxelServer.h>
|
||||
|
||||
#include "Agent.h"
|
||||
#include "AssignmentFactory.h"
|
||||
#include "audio/AudioMixer.h"
|
||||
#include "avatars/AvatarMixer.h"
|
||||
#include "metavoxels/MetavoxelServer.h"
|
||||
|
||||
ThreadedAssignment* AssignmentFactory::unpackAssignment(const unsigned char* dataBuffer, int numBytes) {
|
||||
int headerBytes = numBytesForPacketHeader(dataBuffer);
|
||||
|
@ -33,7 +35,9 @@ ThreadedAssignment* AssignmentFactory::unpackAssignment(const unsigned char* dat
|
|||
return new VoxelServer(dataBuffer, numBytes);
|
||||
case Assignment::ParticleServerType:
|
||||
return new ParticleServer(dataBuffer, numBytes);
|
||||
case Assignment::MetavoxelServerType:
|
||||
return new MetavoxelServer(dataBuffer, numBytes);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,30 +239,15 @@ void AudioMixer::processDatagram(const QByteArray& dataByteArray, const HifiSock
|
|||
|
||||
void AudioMixer::run() {
|
||||
|
||||
commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NODE_TYPE_AUDIO_MIXER);
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
// change the logging target name while this is running
|
||||
Logging::setTargetName(AUDIO_MIXER_LOGGING_TARGET_NAME);
|
||||
|
||||
nodeList->setOwnerType(NODE_TYPE_AUDIO_MIXER);
|
||||
|
||||
const char AUDIO_MIXER_NODE_TYPES_OF_INTEREST[2] = { NODE_TYPE_AGENT, NODE_TYPE_AUDIO_INJECTOR };
|
||||
nodeList->setNodeTypesOfInterest(AUDIO_MIXER_NODE_TYPES_OF_INTEREST, sizeof(AUDIO_MIXER_NODE_TYPES_OF_INTEREST));
|
||||
|
||||
nodeList->linkedDataCreateCallback = attachNewBufferToNode;
|
||||
|
||||
QTimer* domainServerTimer = new QTimer(this);
|
||||
connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit()));
|
||||
domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000);
|
||||
|
||||
QTimer* silentNodeTimer = new QTimer(this);
|
||||
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
|
||||
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000);
|
||||
|
||||
QTimer* pingNodesTimer = new QTimer(this);
|
||||
connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes()));
|
||||
pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000);
|
||||
|
||||
int nextFrame = 0;
|
||||
timeval startTime;
|
||||
|
||||
|
@ -314,4 +299,4 @@ void AudioMixer::run() {
|
|||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,28 +162,13 @@ void AvatarMixer::processDatagram(const QByteArray& dataByteArray, const HifiSoc
|
|||
}
|
||||
|
||||
void AvatarMixer::run() {
|
||||
// change the logging target name while AvatarMixer is running
|
||||
Logging::setTargetName(AVATAR_MIXER_LOGGING_NAME);
|
||||
commonInit(AVATAR_MIXER_LOGGING_NAME, NODE_TYPE_AVATAR_MIXER);
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
nodeList->setOwnerType(NODE_TYPE_AVATAR_MIXER);
|
||||
|
||||
nodeList->setNodeTypesOfInterest(&NODE_TYPE_AGENT, 1);
|
||||
|
||||
nodeList->linkedDataCreateCallback = attachAvatarDataToNode;
|
||||
|
||||
QTimer* domainServerTimer = new QTimer(this);
|
||||
connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit()));
|
||||
domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000);
|
||||
|
||||
QTimer* pingNodesTimer = new QTimer(this);
|
||||
connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes()));
|
||||
pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000);
|
||||
|
||||
QTimer* silentNodeTimer = new QTimer(this);
|
||||
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
|
||||
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000);
|
||||
|
||||
int nextFrame = 0;
|
||||
timeval startTime;
|
||||
|
||||
|
|
156
assignment-client/src/metavoxels/MetavoxelServer.cpp
Normal file
156
assignment-client/src/metavoxels/MetavoxelServer.cpp
Normal file
|
@ -0,0 +1,156 @@
|
|||
//
|
||||
// MetavoxelServer.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/18/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include <MetavoxelMessages.h>
|
||||
#include <MetavoxelUtil.h>
|
||||
|
||||
#include "MetavoxelServer.h"
|
||||
|
||||
const int SEND_INTERVAL = 50;
|
||||
|
||||
MetavoxelServer::MetavoxelServer(const unsigned char* dataBuffer, int numBytes) :
|
||||
ThreadedAssignment(dataBuffer, numBytes),
|
||||
_data(new MetavoxelData()) {
|
||||
|
||||
_sendTimer.setSingleShot(true);
|
||||
connect(&_sendTimer, SIGNAL(timeout()), SLOT(sendDeltas()));
|
||||
}
|
||||
|
||||
void MetavoxelServer::removeSession(const QUuid& sessionId) {
|
||||
delete _sessions.take(sessionId);
|
||||
}
|
||||
|
||||
void MetavoxelServer::run() {
|
||||
commonInit("metavoxel-server", NODE_TYPE_METAVOXEL_SERVER);
|
||||
|
||||
_lastSend = QDateTime::currentMSecsSinceEpoch();
|
||||
_sendTimer.start(SEND_INTERVAL);
|
||||
}
|
||||
|
||||
void MetavoxelServer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
|
||||
switch (dataByteArray.at(0)) {
|
||||
case PACKET_TYPE_METAVOXEL_DATA:
|
||||
processData(dataByteArray, senderSockAddr);
|
||||
break;
|
||||
|
||||
default:
|
||||
NodeList::getInstance()->processNodeData(senderSockAddr, (unsigned char*)dataByteArray.data(), dataByteArray.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelServer::sendDeltas() {
|
||||
// send deltas for all sessions
|
||||
foreach (MetavoxelSession* session, _sessions) {
|
||||
session->sendDelta();
|
||||
}
|
||||
|
||||
// restart the send timer
|
||||
qint64 now = QDateTime::currentMSecsSinceEpoch();
|
||||
int elapsed = now - _lastSend;
|
||||
_lastSend = now;
|
||||
|
||||
_sendTimer.start(qMax(0, 2 * SEND_INTERVAL - elapsed));
|
||||
}
|
||||
|
||||
void MetavoxelServer::processData(const QByteArray& data, const HifiSockAddr& sender) {
|
||||
// read the session id
|
||||
int headerPlusIDSize;
|
||||
QUuid sessionID = readSessionID(data, sender, headerPlusIDSize);
|
||||
if (sessionID.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// forward to session, creating if necessary
|
||||
MetavoxelSession*& session = _sessions[sessionID];
|
||||
if (!session) {
|
||||
session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize));
|
||||
}
|
||||
session->receivedData(data, sender);
|
||||
}
|
||||
|
||||
MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, const QByteArray& datagramHeader) :
|
||||
QObject(server),
|
||||
_server(server),
|
||||
_sessionId(sessionId),
|
||||
_sequencer(datagramHeader) {
|
||||
|
||||
const int TIMEOUT_INTERVAL = 30 * 1000;
|
||||
_timeoutTimer.setInterval(TIMEOUT_INTERVAL);
|
||||
_timeoutTimer.setSingleShot(true);
|
||||
connect(&_timeoutTimer, SIGNAL(timeout()), SLOT(timedOut()));
|
||||
|
||||
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
|
||||
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
|
||||
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
|
||||
|
||||
// insert the baseline send record
|
||||
SendRecord record = { 0, MetavoxelDataPointer(new MetavoxelData()) };
|
||||
_sendRecords.append(record);
|
||||
}
|
||||
|
||||
void MetavoxelSession::receivedData(const QByteArray& data, const HifiSockAddr& sender) {
|
||||
// reset the timeout timer
|
||||
_timeoutTimer.start();
|
||||
|
||||
// save the most recent sender
|
||||
_sender = sender;
|
||||
|
||||
// process through sequencer
|
||||
_sequencer.receivedDatagram(data);
|
||||
}
|
||||
|
||||
void MetavoxelSession::sendDelta() {
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
out << QVariant::fromValue(MetavoxelDeltaMessage());
|
||||
writeDelta(_server->getData(), _sendRecords.first().data, out);
|
||||
_sequencer.endPacket();
|
||||
|
||||
// record the send
|
||||
SendRecord record = { _sequencer.getOutgoingPacketNumber(), _server->getData() };
|
||||
_sendRecords.append(record);
|
||||
}
|
||||
|
||||
void MetavoxelSession::timedOut() {
|
||||
qDebug() << "Session timed out [sessionId=" << _sessionId << ", sender=" << _sender << "]\n";
|
||||
_server->removeSession(_sessionId);
|
||||
}
|
||||
|
||||
void MetavoxelSession::sendData(const QByteArray& data) {
|
||||
NodeList::getInstance()->getNodeSocket().writeDatagram(data, _sender.getAddress(), _sender.getPort());
|
||||
}
|
||||
|
||||
void MetavoxelSession::readPacket(Bitstream& in) {
|
||||
QVariant message;
|
||||
in >> message;
|
||||
handleMessage(message, in);
|
||||
}
|
||||
|
||||
void MetavoxelSession::clearSendRecordsBefore(int index) {
|
||||
_sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1);
|
||||
}
|
||||
|
||||
void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
int userType = message.userType();
|
||||
if (userType == ClientStateMessage::Type) {
|
||||
ClientStateMessage state = message.value<ClientStateMessage>();
|
||||
_position = state.position;
|
||||
|
||||
} else if (userType == MetavoxelDeltaMessage::Type) {
|
||||
|
||||
|
||||
} else if (userType == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
handleMessage(element, in);
|
||||
}
|
||||
}
|
||||
}
|
102
assignment-client/src/metavoxels/MetavoxelServer.h
Normal file
102
assignment-client/src/metavoxels/MetavoxelServer.h
Normal file
|
@ -0,0 +1,102 @@
|
|||
//
|
||||
// MetavoxelServer.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/18/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__MetavoxelServer__
|
||||
#define __hifi__MetavoxelServer__
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QTimer>
|
||||
#include <QUuid>
|
||||
|
||||
#include <HifiSockAddr.h>
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
#include <DatagramSequencer.h>
|
||||
#include <MetavoxelData.h>
|
||||
|
||||
class MetavoxelSession;
|
||||
|
||||
/// Maintains a shared metavoxel system, accepting change requests and broadcasting updates.
|
||||
class MetavoxelServer : public ThreadedAssignment {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelServer(const unsigned char* dataBuffer, int numBytes);
|
||||
|
||||
const MetavoxelDataPointer& getData() const { return _data; }
|
||||
|
||||
void removeSession(const QUuid& sessionId);
|
||||
|
||||
virtual void run();
|
||||
|
||||
virtual void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
private slots:
|
||||
|
||||
void sendDeltas();
|
||||
|
||||
private:
|
||||
|
||||
void processData(const QByteArray& data, const HifiSockAddr& sender);
|
||||
|
||||
QTimer _sendTimer;
|
||||
qint64 _lastSend;
|
||||
|
||||
QHash<QUuid, MetavoxelSession*> _sessions;
|
||||
|
||||
MetavoxelDataPointer _data;
|
||||
};
|
||||
|
||||
/// Contains the state of a single client session.
|
||||
class MetavoxelSession : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, const QByteArray& datagramHeader);
|
||||
|
||||
void receivedData(const QByteArray& data, const HifiSockAddr& sender);
|
||||
|
||||
void sendDelta();
|
||||
|
||||
private slots:
|
||||
|
||||
void timedOut();
|
||||
|
||||
void sendData(const QByteArray& data);
|
||||
|
||||
void readPacket(Bitstream& in);
|
||||
|
||||
void clearSendRecordsBefore(int index);
|
||||
|
||||
private:
|
||||
|
||||
void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
class SendRecord {
|
||||
public:
|
||||
int packetNumber;
|
||||
MetavoxelDataPointer data;
|
||||
};
|
||||
|
||||
MetavoxelServer* _server;
|
||||
QUuid _sessionId;
|
||||
|
||||
QTimer _timeoutTimer;
|
||||
DatagramSequencer _sequencer;
|
||||
|
||||
HifiSockAddr _sender;
|
||||
|
||||
glm::vec3 _position;
|
||||
|
||||
QList<SendRecord> _sendRecords;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__MetavoxelServer__) */
|
21
cmake/macros/AutoMTC.cmake
Normal file
21
cmake/macros/AutoMTC.cmake
Normal file
|
@ -0,0 +1,21 @@
|
|||
macro(AUTO_MTC TARGET ROOT_DIR)
|
||||
if (NOT TARGET mtc)
|
||||
add_subdirectory(${ROOT_DIR}/tools/mtc ${ROOT_DIR}/tools/mtc)
|
||||
endif (NOT TARGET mtc)
|
||||
|
||||
file(GLOB INCLUDE_FILES src/*.h)
|
||||
|
||||
add_custom_command(OUTPUT ${TARGET}_automtc.cpp COMMAND mtc -o ${TARGET}_automtc.cpp
|
||||
${INCLUDE_FILES} DEPENDS mtc ${INCLUDE_FILES})
|
||||
|
||||
find_package(Qt5Core REQUIRED)
|
||||
|
||||
add_library(${TARGET}_automtc STATIC ${TARGET}_automtc.cpp)
|
||||
|
||||
qt5_use_modules(${TARGET}_automtc Core)
|
||||
|
||||
target_link_libraries(${TARGET} ${TARGET}_automtc)
|
||||
|
||||
endmacro()
|
||||
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
# - Try to find the Leap library
|
||||
#
|
||||
# You must provide a LEAP_ROOT_DIR which contains Lib and Include directories
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# LEAP_FOUND - system found Leap
|
||||
# LEAP_INCLUDE_DIRS - the Leap include directory
|
||||
# LEAP_LIBRARIES - Link this to use Leap
|
||||
#
|
||||
# Created on 6/21/2013 by Eric Johnston,
|
||||
# adapted from FindLibOVR.cmake by Stephen Birarda
|
||||
# Copyright (c) 2013 High Fidelity
|
||||
#
|
||||
|
||||
if (LEAP_LIBRARIES AND LEAP_INCLUDE_DIRS)
|
||||
# in cache already
|
||||
set(LEAP_FOUND TRUE)
|
||||
else (LEAP_LIBRARIES AND LEAP_INCLUDE_DIRS)
|
||||
find_path(LEAP_INCLUDE_DIRS Leap.h ${LEAP_ROOT_DIR}/include)
|
||||
|
||||
if (LEAP_INCLUDE_DIRS)
|
||||
### If we found the real header, get the library as well.
|
||||
if (APPLE)
|
||||
find_library(LEAP_LIBRARIES libLeap.dylib ${LEAP_ROOT_DIR}/lib/libc++/)
|
||||
elseif (WIN32)
|
||||
find_library(LEAP_LIBRARIES libLeap.dylib ${LEAP_ROOT_DIR}/lib/libc++/)
|
||||
endif ()
|
||||
else ()
|
||||
### If we didn't find the real header, just use the stub header, and no library.
|
||||
find_path(LEAP_INCLUDE_DIRS Leap.h ${LEAP_ROOT_DIR}/stubs/include)
|
||||
endif ()
|
||||
|
||||
# If we're using the Leap stubs, there's only a header, no lib.
|
||||
if (LEAP_LIBRARIES AND LEAP_INCLUDE_DIRS)
|
||||
set(LEAP_FOUND TRUE)
|
||||
endif (LEAP_LIBRARIES AND LEAP_INCLUDE_DIRS)
|
||||
|
||||
if (LEAP_FOUND)
|
||||
if (NOT Leap_FIND_QUIETLY)
|
||||
message(STATUS "Found Leap: ${LEAP_LIBRARIES}")
|
||||
endif (NOT Leap_FIND_QUIETLY)
|
||||
else (LEAP_FOUND)
|
||||
if (Leap_FIND_REQUIRED)
|
||||
message(FATAL_ERROR "Could not find Leap")
|
||||
endif (Leap_FIND_REQUIRED)
|
||||
endif (LEAP_FOUND)
|
||||
|
||||
# show the LEAP_INCLUDE_DIRS and LEAP_LIBRARIES variables only in the advanced view
|
||||
mark_as_advanced(LEAP_INCLUDE_DIRS LEAP_LIBRARIES)
|
||||
|
||||
endif (LEAP_LIBRARIES AND LEAP_INCLUDE_DIRS)
|
|
@ -37,6 +37,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
_staticAssignmentFile(QString("%1/config.ds").arg(QCoreApplication::applicationDirPath())),
|
||||
_staticAssignmentFileData(NULL),
|
||||
_voxelServerConfig(NULL),
|
||||
_metavoxelServerConfig(NULL),
|
||||
_hasCompletedRestartHold(false)
|
||||
{
|
||||
DomainServer::setDomainServerInstance(this);
|
||||
|
@ -55,6 +56,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
const char PARTICLE_CONFIG_OPTION[] = "--particleServerConfig";
|
||||
_particleServerConfig = getCmdOption(argc, (const char**) argv, PARTICLE_CONFIG_OPTION);
|
||||
|
||||
const char METAVOXEL_CONFIG_OPTION[] = "--metavoxelServerConfig";
|
||||
_metavoxelServerConfig = getCmdOption(argc, (const char**)argv, METAVOXEL_CONFIG_OPTION);
|
||||
|
||||
// setup the mongoose web server
|
||||
struct mg_callbacks callbacks = {};
|
||||
|
||||
|
@ -152,10 +156,11 @@ void DomainServer::readAvailableDatagrams() {
|
|||
int numBytesPublicSocket = HifiSockAddr::unpackSockAddr(packetData + packetIndex, nodeLocalAddress);
|
||||
packetIndex += numBytesPublicSocket;
|
||||
|
||||
const char STATICALLY_ASSIGNED_NODES[3] = {
|
||||
const char STATICALLY_ASSIGNED_NODES[] = {
|
||||
NODE_TYPE_AUDIO_MIXER,
|
||||
NODE_TYPE_AVATAR_MIXER,
|
||||
NODE_TYPE_VOXEL_SERVER
|
||||
NODE_TYPE_VOXEL_SERVER,
|
||||
NODE_TYPE_METAVOXEL_SERVER
|
||||
};
|
||||
|
||||
Assignment* matchingStaticAssignment = NULL;
|
||||
|
@ -612,6 +617,13 @@ void DomainServer::prepopulateStaticAssignmentFile() {
|
|||
freshStaticAssignments[numFreshStaticAssignments++] = rootParticleServerAssignment;
|
||||
}
|
||||
|
||||
// handle metavoxel configuration command line argument
|
||||
Assignment& metavoxelAssignment = (freshStaticAssignments[numFreshStaticAssignments++] =
|
||||
Assignment(Assignment::CreateCommand, Assignment::MetavoxelServerType));
|
||||
if (_metavoxelServerConfig) {
|
||||
metavoxelAssignment.setPayload((const unsigned char*)_metavoxelServerConfig, strlen(_metavoxelServerConfig));
|
||||
}
|
||||
|
||||
qDebug() << "Adding" << numFreshStaticAssignments << "static assignments to fresh file.\n";
|
||||
|
||||
_staticAssignmentFile.open(QIODevice::WriteOnly);
|
||||
|
@ -787,4 +799,4 @@ void DomainServer::addStaticAssignmentsBackToQueueAfterRestart() {
|
|||
void DomainServer::cleanup() {
|
||||
_staticAssignmentFile.unmap(_staticAssignmentFileData);
|
||||
_staticAssignmentFile.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ private:
|
|||
|
||||
const char* _voxelServerConfig;
|
||||
const char* _particleServerConfig;
|
||||
const char* _metavoxelServerConfig;
|
||||
|
||||
bool _hasCompletedRestartHold;
|
||||
private slots:
|
||||
|
|
52
examples/clap.js
Normal file
52
examples/clap.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// This sample script watches your hydra hands and makes clapping sound
|
||||
//
|
||||
|
||||
function length(v) {
|
||||
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
||||
}
|
||||
|
||||
|
||||
function printVector(v) {
|
||||
print(v.x + ", " + v.y + ", " + v.z + "\n");
|
||||
}
|
||||
|
||||
function vMinus(a, b) {
|
||||
var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
|
||||
return rval;
|
||||
}
|
||||
|
||||
// First, load the clap sound from a URL
|
||||
var clap = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/clap1.raw");
|
||||
var clapping = new Array();
|
||||
clapping[0] = false;
|
||||
clapping[1] = false;
|
||||
|
||||
function maybePlaySound() {
|
||||
// Set the location and other info for the sound to play
|
||||
var palm1Position = Controller.getSpatialControlPosition(0);
|
||||
var palm2Position = Controller.getSpatialControlPosition(2);
|
||||
var distanceBetween = length(vMinus(palm1Position, palm2Position));
|
||||
|
||||
for (var palm = 0; palm < 2; palm++) {
|
||||
var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1);
|
||||
var speed = length(palmVelocity);
|
||||
|
||||
const CLAP_SPEED = 0.2;
|
||||
const CLAP_DISTANCE = 0.3;
|
||||
|
||||
if (!clapping[palm] && (distanceBetween < CLAP_DISTANCE) && (speed > CLAP_SPEED)) {
|
||||
var options = new AudioInjectionOptions();
|
||||
options.position = palm1Position;
|
||||
options.volume = speed / 2.0;
|
||||
if (options.volume > 1.0) options.volume = 1.0;
|
||||
Audio.playSound(clap, options);
|
||||
clapping[palm] = true;
|
||||
} else if (clapping[palm] && (speed < (CLAP_SPEED / 1.5))) {
|
||||
clapping[palm] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(maybePlaySound);
|
20
examples/playSound.js
Normal file
20
examples/playSound.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// This sample script makes an occassional clapping sound at the location of your palm
|
||||
//
|
||||
|
||||
// First, load the clap sound from a URL
|
||||
var clap = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/clap1.raw");
|
||||
|
||||
function maybePlaySound() {
|
||||
if (Math.random() < 0.01) {
|
||||
// Set the location and other info for the sound to play
|
||||
var options = new AudioInjectionOptions();
|
||||
var palmPosition = Controller.getSpatialControlPosition(0);
|
||||
options.position = palmPosition;
|
||||
options.volume = 0.1;
|
||||
Audio.playSound(clap, options);
|
||||
}
|
||||
}
|
||||
|
||||
// Connect a call back that happens every frame
|
||||
Agent.willSendVisualDataCallback.connect(maybePlaySound);
|
|
@ -11,7 +11,6 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake
|
|||
set(FACESHIFT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/faceshift)
|
||||
set(LIBOVR_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/LibOVR)
|
||||
set(LIBVPX_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/LibVPX)
|
||||
set(LEAP_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/Leap)
|
||||
set(MOTIONDRIVER_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/MotionDriver)
|
||||
set(OPENCV_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/OpenCV)
|
||||
set(SIXENSE_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/Sixense)
|
||||
|
@ -104,7 +103,6 @@ find_package(Faceshift)
|
|||
find_package(GLM REQUIRED)
|
||||
find_package(LibOVR)
|
||||
find_package(LibVPX)
|
||||
find_package(Leap)
|
||||
find_package(MotionDriver)
|
||||
find_package(OpenCV)
|
||||
find_package(OpenNI)
|
||||
|
@ -195,11 +193,6 @@ if (APPLE)
|
|||
${QuartzCore}
|
||||
${UVCCAMERACONTROL_LIBRARIES}
|
||||
)
|
||||
|
||||
if (LEAP_FOUND)
|
||||
target_link_libraries(${TARGET_NAME} ${LEAP_LIBRARIES})
|
||||
endif(LEAP_FOUND)
|
||||
|
||||
else (APPLE)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(GLUT REQUIRED)
|
||||
|
|
11
interface/external/Leap/readme.txt
vendored
11
interface/external/Leap/readme.txt
vendored
|
@ -1,11 +0,0 @@
|
|||
|
||||
Instructions for adding the Leap driver to Interface
|
||||
Eric Johnston, July 10, 2013
|
||||
|
||||
NOTE: Without doing step 2, you will crash at program start time.
|
||||
|
||||
1. Copy the Leap sdk folders (lib, include, etc.) into the interface/external/Leap folder. There should be a folder already there called "stub", and this read me.txt should be there as well.
|
||||
|
||||
2. IMPORTANT: Copy the file interface/external/Leap/lib/libc++/libLeap.dylib to /usr/lib
|
||||
|
||||
3. Delete your build directory, run cmake and build, and you should be all set.
|
13
interface/external/Leap/stubs/include/Leap.h
vendored
13
interface/external/Leap/stubs/include/Leap.h
vendored
|
@ -1,13 +0,0 @@
|
|||
|
||||
// This is an empty stub, used as a placeholder for the real Leap.h
|
||||
// The entire containing Leap folder should be replaced by the one
|
||||
// from the Leap SDK.
|
||||
|
||||
#define LEAP_STUBS // We're using the stubbed-out Leap header
|
||||
|
||||
namespace Leap {
|
||||
class Frame {};
|
||||
class Controller {};
|
||||
class Listener {};
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,7 @@
|
|||
#define __interface__Application__
|
||||
|
||||
#include <map>
|
||||
#include <pthread.h>
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <QApplication>
|
||||
|
@ -37,6 +37,7 @@
|
|||
#include "Cloud.h"
|
||||
#include "Environment.h"
|
||||
#include "GLCanvas.h"
|
||||
#include "MetavoxelSystem.h"
|
||||
#include "PacketHeaders.h"
|
||||
#include "PieMenu.h"
|
||||
#include "Stars.h"
|
||||
|
@ -59,7 +60,6 @@
|
|||
#include "renderer/AmbientOcclusionEffect.h"
|
||||
#include "renderer/GeometryCache.h"
|
||||
#include "renderer/GlowEffect.h"
|
||||
#include "renderer/MetavoxelSystem.h"
|
||||
#include "renderer/PointShader.h"
|
||||
#include "renderer/TextureCache.h"
|
||||
#include "renderer/VoxelShader.h"
|
||||
|
@ -108,14 +108,14 @@ public:
|
|||
~Application();
|
||||
|
||||
void restoreSizeAndPosition();
|
||||
void storeSizeAndPosition();
|
||||
void storeSizeAndPosition();
|
||||
void initializeGL();
|
||||
void paintGL();
|
||||
void resizeGL(int width, int height);
|
||||
|
||||
void keyPressEvent(QKeyEvent* event);
|
||||
void keyReleaseEvent(QKeyEvent* event);
|
||||
|
||||
|
||||
void mouseMoveEvent(QMouseEvent* event);
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
void mouseReleaseEvent(QMouseEvent* event);
|
||||
|
@ -123,27 +123,27 @@ public:
|
|||
void touchBeginEvent(QTouchEvent* event);
|
||||
void touchEndEvent(QTouchEvent* event);
|
||||
void touchUpdateEvent(QTouchEvent* event);
|
||||
|
||||
|
||||
void updateWindowTitle();
|
||||
|
||||
void wheelEvent(QWheelEvent* event);
|
||||
|
||||
void shootParticle(); // shoots a particle in the direction you're looking
|
||||
ParticleEditHandle* newParticleEditHandle(uint32_t id = NEW_PARTICLE);
|
||||
ParticleEditHandle* makeParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity,
|
||||
ParticleEditHandle* makeParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity,
|
||||
glm::vec3 gravity, float damping, bool inHand, QString updateScript);
|
||||
|
||||
|
||||
void makeVoxel(glm::vec3 position,
|
||||
float scale,
|
||||
unsigned char red,
|
||||
unsigned char green,
|
||||
unsigned char blue,
|
||||
bool isDestructive);
|
||||
|
||||
|
||||
void removeVoxel(glm::vec3 position, float scale);
|
||||
|
||||
|
||||
const glm::vec3 getMouseVoxelWorldCoordinates(const VoxelDetail _mouseVoxel);
|
||||
|
||||
|
||||
QGLWidget* getGLWidget() { return _glWidget; }
|
||||
MyAvatar* getAvatar() { return &_myAvatar; }
|
||||
Audio* getAudio() { return &_audio; }
|
||||
|
@ -166,24 +166,24 @@ public:
|
|||
NodeToVoxelSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
|
||||
void lockVoxelSceneStats() { _voxelSceneStatsLock.lockForRead(); }
|
||||
void unlockVoxelSceneStats() { _voxelSceneStatsLock.unlock(); }
|
||||
|
||||
|
||||
QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; }
|
||||
GeometryCache* getGeometryCache() { return &_geometryCache; }
|
||||
TextureCache* getTextureCache() { return &_textureCache; }
|
||||
GlowEffect* getGlowEffect() { return &_glowEffect; }
|
||||
|
||||
|
||||
Avatar* getLookatTargetAvatar() const { return _lookatTargetAvatar; }
|
||||
|
||||
|
||||
Profile* getProfile() { return &_profile; }
|
||||
void resetProfile(const QString& username);
|
||||
|
||||
|
||||
static void controlledBroadcastToNodes(unsigned char* broadcastData, size_t dataBytes,
|
||||
const char* nodeTypes, int numNodeTypes);
|
||||
|
||||
|
||||
void setupWorldLight();
|
||||
|
||||
void displaySide(Camera& whichCamera, bool selfAvatarOnly = false);
|
||||
|
||||
|
||||
/// Loads a view matrix that incorporates the specified model translation without the precision issues that can
|
||||
/// result from matrix multiplication at high translation magnitudes.
|
||||
void loadTranslatedViewMatrix(const glm::vec3& translation);
|
||||
|
@ -197,9 +197,9 @@ public:
|
|||
virtual void nodeAdded(Node* node);
|
||||
virtual void nodeKilled(Node* node);
|
||||
virtual void packetSentNotification(ssize_t length);
|
||||
|
||||
|
||||
virtual void domainChanged(QString domain);
|
||||
|
||||
|
||||
VoxelShader& getVoxelShader() { return _voxelShader; }
|
||||
PointShader& getPointShader() { return _pointShader; }
|
||||
FileLogger* getLogger() { return _logger; }
|
||||
|
@ -208,7 +208,7 @@ public:
|
|||
NodeToJurisdictionMap& getVoxelServerJurisdictions() { return _voxelServerJurisdictions; }
|
||||
NodeToJurisdictionMap& getParticleServerJurisdictions() { return _particleServerJurisdictions; }
|
||||
void pasteVoxelsToOctalCode(const unsigned char* octalCodeDestination);
|
||||
|
||||
|
||||
/// set a voxel which is to be rendered with a highlight
|
||||
void setHighlightVoxel(const VoxelDetail& highlightVoxel) { _highlightVoxel = highlightVoxel; }
|
||||
void setIsHighlightVoxel(bool isHighlightVoxel) { _isHighlightVoxel = isHighlightVoxel; }
|
||||
|
@ -222,25 +222,26 @@ public slots:
|
|||
void pasteVoxels();
|
||||
void nudgeVoxels();
|
||||
void deleteVoxels();
|
||||
|
||||
|
||||
void setRenderVoxels(bool renderVoxels);
|
||||
void doKillLocalVoxels();
|
||||
void decreaseVoxelSize();
|
||||
void increaseVoxelSize();
|
||||
void loadScript();
|
||||
void toggleLogDialog();
|
||||
|
||||
void initAvatarAndViewFrustum();
|
||||
|
||||
private slots:
|
||||
|
||||
|
||||
void timer();
|
||||
void idle();
|
||||
void terminate();
|
||||
|
||||
|
||||
void setFullscreen(bool fullscreen);
|
||||
|
||||
|
||||
void renderThrustAtVoxel(const glm::vec3& thrust);
|
||||
void renderLineToTouchedVoxel();
|
||||
|
||||
|
||||
void renderCoverageMap();
|
||||
void renderCoverageMapsRecursively(CoverageMap* map);
|
||||
|
||||
|
@ -250,7 +251,7 @@ private slots:
|
|||
glm::vec2 getScaledScreenPoint(glm::vec2 projectedPoint);
|
||||
|
||||
void toggleFollowMode();
|
||||
|
||||
|
||||
void closeMirrorView();
|
||||
void restoreMirrorView();
|
||||
void shrinkMirrorView();
|
||||
|
@ -265,17 +266,17 @@ private:
|
|||
static void processAvatarURLsMessage(unsigned char* packetData, size_t dataBytes);
|
||||
static void processAvatarFaceVideoMessage(unsigned char* packetData, size_t dataBytes);
|
||||
static void sendPingPackets();
|
||||
|
||||
|
||||
void initDisplay();
|
||||
void init();
|
||||
|
||||
|
||||
void update(float deltaTime);
|
||||
|
||||
// Various helper functions called during update()
|
||||
void updateMouseRay(float deltaTime, glm::vec3& mouseRayOrigin, glm::vec3& mouseRayDirection);
|
||||
void updateFaceshift();
|
||||
void updateMyAvatarLookAtPosition(glm::vec3& lookAtSpot, glm::vec3& lookAtRayOrigin, glm::vec3& lookAtRayDirection);
|
||||
void updateHoverVoxels(float deltaTime, glm::vec3& mouseRayOrigin, glm::vec3& mouseRayDirection,
|
||||
void updateHoverVoxels(float deltaTime, glm::vec3& mouseRayOrigin, glm::vec3& mouseRayDirection,
|
||||
float& distance, BoxFace& face);
|
||||
void updateMouseVoxels(float deltaTime, glm::vec3& mouseRayOrigin, glm::vec3& mouseRayDirection,
|
||||
float& distance, BoxFace& face);
|
||||
|
@ -298,32 +299,32 @@ private:
|
|||
Avatar* findLookatTargetAvatar(const glm::vec3& mouseRayOrigin, const glm::vec3& mouseRayDirection,
|
||||
glm::vec3& eyePosition, QUuid &nodeUUID);
|
||||
bool isLookingAtMyAvatar(Avatar* avatar);
|
||||
|
||||
|
||||
void renderLookatIndicator(glm::vec3 pointOfInterest);
|
||||
void renderFollowIndicator();
|
||||
void renderHighlightVoxel(VoxelDetail voxel);
|
||||
|
||||
|
||||
void updateAvatar(float deltaTime);
|
||||
void updateAvatars(float deltaTime, glm::vec3 mouseRayOrigin, glm::vec3 mouseRayDirection);
|
||||
void queryOctree(NODE_TYPE serverType, PACKET_TYPE packetType, NodeToJurisdictionMap& jurisdictions);
|
||||
void loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum);
|
||||
|
||||
|
||||
glm::vec3 getSunDirection();
|
||||
|
||||
|
||||
void updateShadowMap();
|
||||
void displayOverlay();
|
||||
void displayStats();
|
||||
void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false);
|
||||
void renderViewFrustum(ViewFrustum& viewFrustum);
|
||||
|
||||
|
||||
void checkBandwidthMeterClick();
|
||||
|
||||
|
||||
bool maybeEditVoxelUnderCursor();
|
||||
void deleteVoxelUnderCursor();
|
||||
void eyedropperVoxelUnderCursor();
|
||||
|
||||
|
||||
void setMenuShortcutsEnabled(bool enabled);
|
||||
|
||||
|
||||
static void attachNewHeadToNode(Node *newNode);
|
||||
static void* networkReceive(void* args); // network receive thread
|
||||
|
||||
|
@ -333,18 +334,18 @@ private:
|
|||
|
||||
QMainWindow* _window;
|
||||
QGLWidget* _glWidget;
|
||||
|
||||
|
||||
QAction* _followMode;
|
||||
|
||||
|
||||
BandwidthMeter _bandwidthMeter;
|
||||
|
||||
SerialInterface _serialHeadSensor;
|
||||
QNetworkAccessManager* _networkAccessManager;
|
||||
QSettings* _settings;
|
||||
bool _displayLevels;
|
||||
|
||||
|
||||
glm::vec3 _gravity;
|
||||
|
||||
|
||||
// Frame Rate Measurement
|
||||
int _frameCount;
|
||||
float _fps;
|
||||
|
@ -354,55 +355,55 @@ private:
|
|||
bool _justStarted;
|
||||
|
||||
Stars _stars;
|
||||
|
||||
|
||||
Cloud _cloud;
|
||||
|
||||
|
||||
VoxelSystem _voxels;
|
||||
VoxelTree _clipboard; // if I copy/paste
|
||||
VoxelImporter _voxelImporter;
|
||||
VoxelSystem _sharedVoxelSystem;
|
||||
ViewFrustum _sharedVoxelSystemViewFrustum;
|
||||
|
||||
|
||||
ParticleTreeRenderer _particles;
|
||||
ParticleCollisionSystem _particleCollisionSystem;
|
||||
|
||||
QByteArray _voxelsFilename;
|
||||
bool _wantToKillLocalVoxels;
|
||||
|
||||
|
||||
MetavoxelSystem _metavoxels;
|
||||
|
||||
|
||||
ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc.
|
||||
|
||||
Oscilloscope _audioScope;
|
||||
|
||||
VoxelQuery _voxelQuery; // NodeData derived class for querying voxels from voxel server
|
||||
|
||||
|
||||
MyAvatar _myAvatar; // The rendered avatar of oneself
|
||||
Profile _profile; // The data-server linked profile for this user
|
||||
|
||||
|
||||
Transmitter _myTransmitter; // Gets UDP data from transmitter app used to animate the avatar
|
||||
|
||||
|
||||
Webcam _webcam; // The webcam interface
|
||||
|
||||
|
||||
Faceshift _faceshift;
|
||||
|
||||
|
||||
SixenseManager _sixenseManager;
|
||||
|
||||
|
||||
Camera _myCamera; // My view onto the world
|
||||
Camera _viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode
|
||||
Camera _mirrorCamera; // Cammera for mirror view
|
||||
QRect _mirrorViewRect;
|
||||
RearMirrorTools* _rearMirrorTools;
|
||||
|
||||
|
||||
glm::mat4 _untranslatedViewMatrix;
|
||||
glm::vec3 _viewMatrixTranslation;
|
||||
|
||||
|
||||
glm::mat4 _shadowMatrix;
|
||||
|
||||
|
||||
Environment _environment;
|
||||
|
||||
|
||||
int _headMouseX, _headMouseY;
|
||||
|
||||
|
||||
int _mouseX;
|
||||
int _mouseY;
|
||||
int _mouseDragStartedX;
|
||||
|
@ -420,7 +421,7 @@ private:
|
|||
bool _isTouchPressed; // true if multitouch has been pressed (clear when finished)
|
||||
float _yawFromTouch;
|
||||
float _pitchFromTouch;
|
||||
|
||||
|
||||
VoxelDetail _mouseVoxelDragging;
|
||||
bool _mousePressed; // true if mouse has been pressed (clear when finished)
|
||||
|
||||
|
@ -428,7 +429,7 @@ private:
|
|||
bool _isHoverVoxel;
|
||||
bool _isHoverVoxelSounding;
|
||||
nodeColor _hoverVoxelOriginalColor;
|
||||
|
||||
|
||||
VoxelDetail _mouseVoxel; // details of the voxel to be edited
|
||||
float _mouseVoxelScale; // the scale for adding/removing voxels
|
||||
bool _mouseVoxelScaleInitialized;
|
||||
|
@ -437,7 +438,7 @@ private:
|
|||
|
||||
VoxelDetail _highlightVoxel;
|
||||
bool _isHighlightVoxel;
|
||||
|
||||
|
||||
VoxelDetail _nudgeVoxel; // details of the voxel to be nudged
|
||||
bool _nudgeStarted;
|
||||
bool _lookingAlongX;
|
||||
|
@ -447,46 +448,46 @@ private:
|
|||
Avatar* _lookatTargetAvatar;
|
||||
glm::vec3 _lookatOtherPosition;
|
||||
float _lookatIndicatorScale;
|
||||
|
||||
|
||||
glm::vec3 _transmitterPickStart;
|
||||
glm::vec3 _transmitterPickEnd;
|
||||
|
||||
bool _perfStatsOn; // Do we want to display perfStats?
|
||||
|
||||
ChatEntry _chatEntry; // chat entry field
|
||||
bool _chatEntryOn; // Whether to show the chat entry
|
||||
|
||||
|
||||
bool _perfStatsOn; // Do we want to display perfStats?
|
||||
|
||||
ChatEntry _chatEntry; // chat entry field
|
||||
bool _chatEntryOn; // Whether to show the chat entry
|
||||
|
||||
GeometryCache _geometryCache;
|
||||
TextureCache _textureCache;
|
||||
|
||||
|
||||
GlowEffect _glowEffect;
|
||||
AmbientOcclusionEffect _ambientOcclusionEffect;
|
||||
VoxelShader _voxelShader;
|
||||
PointShader _pointShader;
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
Audio _audio;
|
||||
#endif
|
||||
|
||||
|
||||
bool _enableNetworkThread;
|
||||
pthread_t _networkReceiveThread;
|
||||
bool _stopNetworkReceiveThread;
|
||||
|
||||
|
||||
bool _enableProcessVoxelsThread;
|
||||
VoxelPacketProcessor _voxelProcessor;
|
||||
VoxelHideShowThread _voxelHideShowThread;
|
||||
VoxelEditPacketSender _voxelEditSender;
|
||||
ParticleEditPacketSender _particleEditSender;
|
||||
|
||||
|
||||
unsigned char _incomingPacket[MAX_PACKET_SIZE];
|
||||
int _packetCount;
|
||||
int _packetsPerSecond;
|
||||
int _bytesPerSecond;
|
||||
int _bytesCount;
|
||||
|
||||
|
||||
int _recentMaxPackets; // recent max incoming voxel packets to process
|
||||
bool _resetRecentMaxPacketsSoon;
|
||||
|
||||
|
||||
StDev _idleLoopStdev;
|
||||
float _idleLoopMeasuredJitter;
|
||||
|
||||
|
@ -496,22 +497,27 @@ private:
|
|||
bool _pasteMode;
|
||||
|
||||
PieMenu _pieMenu;
|
||||
|
||||
|
||||
int parseOctreeStats(unsigned char* messageData, ssize_t messageLength, const HifiSockAddr& senderAddress);
|
||||
void trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength,
|
||||
const HifiSockAddr& senderSockAddr, bool wasStatsPacket);
|
||||
|
||||
|
||||
NodeToJurisdictionMap _voxelServerJurisdictions;
|
||||
NodeToJurisdictionMap _particleServerJurisdictions;
|
||||
NodeToVoxelSceneStats _octreeServerSceneStats;
|
||||
QReadWriteLock _voxelSceneStatsLock;
|
||||
|
||||
|
||||
std::vector<VoxelFade> _voxelFades;
|
||||
std::vector<Avatar*> _avatarFades;
|
||||
ControllerScriptingInterface _controllerScriptingInterface;
|
||||
QPointer<LogDialog> _logDialog;
|
||||
|
||||
FileLogger* _logger;
|
||||
|
||||
OctreePersistThread* _persistThread;
|
||||
|
||||
QString getLocalVoxelCacheFileName();
|
||||
void updateLocalOctreeCache(bool firstTime = false);
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__Application__) */
|
||||
|
|
|
@ -824,6 +824,10 @@ void Menu::editPreferences() {
|
|||
leanScale->setValue(applicationInstance->getAvatar()->getLeanScale());
|
||||
form->addRow("Lean Scale:", leanScale);
|
||||
|
||||
QDoubleSpinBox* avatarScale = new QDoubleSpinBox();
|
||||
avatarScale->setValue(applicationInstance->getAvatar()->getScale());
|
||||
form->addRow("Avatar Scale:", avatarScale);
|
||||
|
||||
QSpinBox* audioJitterBufferSamples = new QSpinBox();
|
||||
audioJitterBufferSamples->setMaximum(10000);
|
||||
audioJitterBufferSamples->setMinimum(-10000);
|
||||
|
@ -887,6 +891,7 @@ void Menu::editPreferences() {
|
|||
_maxVoxelPacketsPerSecond = maxVoxelsPPS->value();
|
||||
|
||||
applicationInstance->getAvatar()->setLeanScale(leanScale->value());
|
||||
applicationInstance->getAvatar()->setNewScale(avatarScale->value());
|
||||
|
||||
_audioJitterBufferSamples = audioJitterBufferSamples->value();
|
||||
|
||||
|
|
244
interface/src/MetavoxelSystem.cpp
Normal file
244
interface/src/MetavoxelSystem.cpp
Normal file
|
@ -0,0 +1,244 @@
|
|||
//
|
||||
// MetavoxelSystem.cpp
|
||||
// interface
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/10/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include <MetavoxelMessages.h>
|
||||
#include <MetavoxelUtil.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "MetavoxelSystem.h"
|
||||
|
||||
ProgramObject MetavoxelSystem::_program;
|
||||
int MetavoxelSystem::_pointScaleLocation;
|
||||
|
||||
MetavoxelSystem::MetavoxelSystem() :
|
||||
_pointVisitor(_points),
|
||||
_buffer(QOpenGLBuffer::VertexBuffer) {
|
||||
}
|
||||
|
||||
MetavoxelSystem::~MetavoxelSystem() {
|
||||
NodeList::getInstance()->removeHook(this);
|
||||
}
|
||||
|
||||
void MetavoxelSystem::init() {
|
||||
if (!_program.isLinked()) {
|
||||
switchToResourcesParentIfRequired();
|
||||
_program.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/metavoxel_point.vert");
|
||||
_program.link();
|
||||
|
||||
_pointScaleLocation = _program.uniformLocation("pointScale");
|
||||
}
|
||||
|
||||
NodeList::getInstance()->addHook(this);
|
||||
|
||||
AttributeRegistry::getInstance()->configureScriptEngine(&_scriptEngine);
|
||||
|
||||
QFile scriptFile("resources/scripts/sphere.js");
|
||||
scriptFile.open(QIODevice::ReadOnly);
|
||||
QScriptValue guideFunction = _scriptEngine.evaluate(QTextStream(&scriptFile).readAll());
|
||||
_data.setAttributeValue(MetavoxelPath(), AttributeValue(AttributeRegistry::getInstance()->getGuideAttribute(),
|
||||
encodeInline(PolymorphicDataPointer(new ScriptedMetavoxelGuide(guideFunction)))));
|
||||
|
||||
_buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
_buffer.create();
|
||||
}
|
||||
|
||||
void MetavoxelSystem::processData(const QByteArray& data, const HifiSockAddr& sender) {
|
||||
QMetaObject::invokeMethod(this, "receivedData", Q_ARG(const QByteArray&, data), Q_ARG(const HifiSockAddr&, sender));
|
||||
}
|
||||
|
||||
void MetavoxelSystem::simulate(float deltaTime) {
|
||||
// simulate the clients
|
||||
foreach (MetavoxelClient* client, _clients) {
|
||||
client->simulate(deltaTime);
|
||||
}
|
||||
|
||||
_points.clear();
|
||||
_data.guide(_pointVisitor);
|
||||
|
||||
_buffer.bind();
|
||||
int bytes = _points.size() * sizeof(Point);
|
||||
if (_buffer.size() < bytes) {
|
||||
_buffer.allocate(_points.constData(), bytes);
|
||||
} else {
|
||||
_buffer.write(0, _points.constData(), bytes);
|
||||
}
|
||||
_buffer.release();
|
||||
}
|
||||
|
||||
void MetavoxelSystem::render() {
|
||||
int viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
const int VIEWPORT_WIDTH_INDEX = 2;
|
||||
const int VIEWPORT_HEIGHT_INDEX = 3;
|
||||
float viewportWidth = viewport[VIEWPORT_WIDTH_INDEX];
|
||||
float viewportHeight = viewport[VIEWPORT_HEIGHT_INDEX];
|
||||
float viewportDiagonal = sqrtf(viewportWidth*viewportWidth + viewportHeight*viewportHeight);
|
||||
float worldDiagonal = glm::distance(Application::getInstance()->getViewFrustum()->getNearBottomLeft(),
|
||||
Application::getInstance()->getViewFrustum()->getNearTopRight());
|
||||
|
||||
_program.bind();
|
||||
_program.setUniformValue(_pointScaleLocation, viewportDiagonal *
|
||||
Application::getInstance()->getViewFrustum()->getNearClip() / worldDiagonal);
|
||||
|
||||
_buffer.bind();
|
||||
|
||||
Point* pt = 0;
|
||||
glVertexPointer(4, GL_FLOAT, sizeof(Point), &pt->vertex);
|
||||
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Point), &pt->color);
|
||||
glNormalPointer(GL_BYTE, sizeof(Point), &pt->normal);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
|
||||
|
||||
glDrawArrays(GL_POINTS, 0, _points.size());
|
||||
|
||||
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
_buffer.release();
|
||||
|
||||
_program.release();
|
||||
}
|
||||
|
||||
void MetavoxelSystem::nodeAdded(Node* node) {
|
||||
if (node->getType() == NODE_TYPE_METAVOXEL_SERVER) {
|
||||
QMetaObject::invokeMethod(this, "addClient", Q_ARG(const QUuid&, node->getUUID()),
|
||||
Q_ARG(const HifiSockAddr&, node->getLocalSocket()));
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSystem::nodeKilled(Node* node) {
|
||||
if (node->getType() == NODE_TYPE_METAVOXEL_SERVER) {
|
||||
QMetaObject::invokeMethod(this, "removeClient", Q_ARG(const QUuid&, node->getUUID()));
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelSystem::addClient(const QUuid& uuid, const HifiSockAddr& address) {
|
||||
MetavoxelClient* client = new MetavoxelClient(address);
|
||||
_clients.insert(uuid, client);
|
||||
_clientsBySessionID.insert(client->getSessionID(), client);
|
||||
}
|
||||
|
||||
void MetavoxelSystem::removeClient(const QUuid& uuid) {
|
||||
MetavoxelClient* client = _clients.take(uuid);
|
||||
_clientsBySessionID.remove(client->getSessionID());
|
||||
delete client;
|
||||
}
|
||||
|
||||
void MetavoxelSystem::receivedData(const QByteArray& data, const HifiSockAddr& sender) {
|
||||
int headerPlusIDSize;
|
||||
QUuid sessionID = readSessionID(data, sender, headerPlusIDSize);
|
||||
if (sessionID.isNull()) {
|
||||
return;
|
||||
}
|
||||
MetavoxelClient* client = _clientsBySessionID.value(sessionID);
|
||||
if (client) {
|
||||
client->receivedData(data, sender);
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelSystem::PointVisitor::PointVisitor(QVector<Point>& points) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() <<
|
||||
AttributeRegistry::getInstance()->getColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getNormalAttribute()),
|
||||
_points(points) {
|
||||
}
|
||||
|
||||
bool MetavoxelSystem::PointVisitor::visit(const MetavoxelInfo& info) {
|
||||
if (!info.isLeaf) {
|
||||
return true;
|
||||
}
|
||||
QRgb color = info.attributeValues.at(0).getInlineValue<QRgb>();
|
||||
QRgb normal = info.attributeValues.at(1).getInlineValue<QRgb>();
|
||||
int alpha = qAlpha(color);
|
||||
if (alpha > 0) {
|
||||
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ qRed(color), qGreen(color), qBlue(color), alpha }, { qRed(normal), qGreen(normal), qBlue(normal) } };
|
||||
_points.append(point);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static QByteArray createDatagramHeader(const QUuid& sessionID) {
|
||||
QByteArray header(MAX_PACKET_HEADER_BYTES, 0);
|
||||
populateTypeAndVersion(reinterpret_cast<unsigned char*>(header.data()), PACKET_TYPE_METAVOXEL_DATA);
|
||||
header += sessionID.toRfc4122();
|
||||
return header;
|
||||
}
|
||||
|
||||
MetavoxelClient::MetavoxelClient(const HifiSockAddr& address) :
|
||||
_address(address),
|
||||
_sessionID(QUuid::createUuid()),
|
||||
_sequencer(createDatagramHeader(_sessionID)),
|
||||
_data(new MetavoxelData()) {
|
||||
|
||||
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
|
||||
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
|
||||
connect(&_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int)));
|
||||
|
||||
// insert the baseline receive record
|
||||
ReceiveRecord record = { 0, _data };
|
||||
_receiveRecords.append(record);
|
||||
}
|
||||
|
||||
void MetavoxelClient::simulate(float deltaTime) {
|
||||
Bitstream& out = _sequencer.startPacket();
|
||||
ClientStateMessage state = { Application::getInstance()->getCamera()->getPosition() };
|
||||
out << QVariant::fromValue(state);
|
||||
_sequencer.endPacket();
|
||||
}
|
||||
|
||||
void MetavoxelClient::receivedData(const QByteArray& data, const HifiSockAddr& sender) {
|
||||
// save the most recent sender
|
||||
_address = sender;
|
||||
|
||||
// process through sequencer
|
||||
_sequencer.receivedDatagram(data);
|
||||
}
|
||||
|
||||
void MetavoxelClient::sendData(const QByteArray& data) {
|
||||
NodeList::getInstance()->getNodeSocket().writeDatagram(data, _address.getAddress(), _address.getPort());
|
||||
}
|
||||
|
||||
void MetavoxelClient::readPacket(Bitstream& in) {
|
||||
QVariant message;
|
||||
in >> message;
|
||||
handleMessage(message, in);
|
||||
|
||||
// record the receipt
|
||||
ReceiveRecord record = { _sequencer.getIncomingPacketNumber(), _data };
|
||||
_receiveRecords.append(record);
|
||||
}
|
||||
|
||||
void MetavoxelClient::clearReceiveRecordsBefore(int index) {
|
||||
_receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1);
|
||||
}
|
||||
|
||||
void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
int userType = message.userType();
|
||||
if (userType == MetavoxelDeltaMessage::Type) {
|
||||
readDelta(_data, _receiveRecords.first().data, in);
|
||||
|
||||
} else if (userType == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
handleMessage(element, in);
|
||||
}
|
||||
}
|
||||
}
|
124
interface/src/MetavoxelSystem.h
Normal file
124
interface/src/MetavoxelSystem.h
Normal file
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// MetavoxelSystem.h
|
||||
// interface
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/10/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __interface__MetavoxelSystem__
|
||||
#define __interface__MetavoxelSystem__
|
||||
|
||||
#include <QList>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QScriptEngine>
|
||||
#include <QVector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <NodeList.h>
|
||||
|
||||
#include <DatagramSequencer.h>
|
||||
#include <MetavoxelData.h>
|
||||
|
||||
#include "renderer/ProgramObject.h"
|
||||
|
||||
class MetavoxelClient;
|
||||
|
||||
/// Renders a metavoxel tree.
|
||||
class MetavoxelSystem : public QObject, public NodeListHook {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelSystem();
|
||||
~MetavoxelSystem();
|
||||
|
||||
void init();
|
||||
|
||||
void processData(const QByteArray& data, const HifiSockAddr& sender);
|
||||
|
||||
void simulate(float deltaTime);
|
||||
void render();
|
||||
|
||||
virtual void nodeAdded(Node* node);
|
||||
virtual void nodeKilled(Node* node);
|
||||
|
||||
private:
|
||||
|
||||
Q_INVOKABLE void addClient(const QUuid& uuid, const HifiSockAddr& address);
|
||||
Q_INVOKABLE void removeClient(const QUuid& uuid);
|
||||
Q_INVOKABLE void receivedData(const QByteArray& data, const HifiSockAddr& sender);
|
||||
|
||||
class Point {
|
||||
public:
|
||||
glm::vec4 vertex;
|
||||
quint8 color[4];
|
||||
quint8 normal[3];
|
||||
};
|
||||
|
||||
class PointVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
PointVisitor(QVector<Point>& points);
|
||||
virtual bool visit(const MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
QVector<Point>& _points;
|
||||
};
|
||||
|
||||
static ProgramObject _program;
|
||||
static int _pointScaleLocation;
|
||||
|
||||
QScriptEngine _scriptEngine;
|
||||
MetavoxelData _data;
|
||||
QVector<Point> _points;
|
||||
PointVisitor _pointVisitor;
|
||||
QOpenGLBuffer _buffer;
|
||||
|
||||
QHash<QUuid, MetavoxelClient*> _clients;
|
||||
QHash<QUuid, MetavoxelClient*> _clientsBySessionID;
|
||||
};
|
||||
|
||||
/// A client session associated with a single server.
|
||||
class MetavoxelClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetavoxelClient(const HifiSockAddr& address);
|
||||
|
||||
const QUuid& getSessionID() const { return _sessionID; }
|
||||
|
||||
void simulate(float deltaTime);
|
||||
|
||||
void receivedData(const QByteArray& data, const HifiSockAddr& sender);
|
||||
|
||||
private slots:
|
||||
|
||||
void sendData(const QByteArray& data);
|
||||
|
||||
void readPacket(Bitstream& in);
|
||||
|
||||
void clearReceiveRecordsBefore(int index);
|
||||
|
||||
private:
|
||||
|
||||
void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
class ReceiveRecord {
|
||||
public:
|
||||
int packetNumber;
|
||||
MetavoxelDataPointer data;
|
||||
};
|
||||
|
||||
HifiSockAddr _address;
|
||||
QUuid _sessionID;
|
||||
|
||||
DatagramSequencer _sequencer;
|
||||
|
||||
MetavoxelDataPointer _data;
|
||||
|
||||
QList<ReceiveRecord> _receiveRecords;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__MetavoxelSystem__) */
|
|
@ -35,12 +35,12 @@ bool VoxelHideShowThread::process() {
|
|||
if (showExtraDebugging && elapsed > USECS_PER_FRAME) {
|
||||
qDebug() << "VoxelHideShowThread::process()... checkForCulling took " << elapsed << "\n";
|
||||
}
|
||||
|
||||
|
||||
if (isStillRunning()) {
|
||||
if (elapsed < USECS_PER_FRAME) {
|
||||
uint64_t sleepFor = USECS_PER_FRAME - elapsed;
|
||||
usleep(sleepFor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return isStillRunning(); // keep running till they terminate us
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -18,6 +18,7 @@
|
|||
#include <NodeData.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <VoxelTree.h>
|
||||
#include <OctreePersistThread.h>
|
||||
|
||||
#include "Camera.h"
|
||||
#include "Util.h"
|
||||
|
@ -36,8 +37,8 @@ struct VoxelShaderVBOData
|
|||
};
|
||||
|
||||
|
||||
class VoxelSystem : public NodeData, public OctreeElementDeleteHook, public OctreeElementUpdateHook,
|
||||
public NodeListHook, public DomainChangeListener {
|
||||
class VoxelSystem : public NodeData, public OctreeElementDeleteHook, public OctreeElementUpdateHook,
|
||||
public NodeListHook {
|
||||
Q_OBJECT
|
||||
|
||||
friend class VoxelHideShowThread;
|
||||
|
@ -48,9 +49,9 @@ public:
|
|||
|
||||
void setDataSourceUUID(const QUuid& dataSourceUUID) { _dataSourceUUID = dataSourceUUID; }
|
||||
const QUuid& getDataSourceUUID() const { return _dataSourceUUID; }
|
||||
|
||||
|
||||
int parseData(unsigned char* sourceBuffer, int numBytes);
|
||||
|
||||
|
||||
virtual void init();
|
||||
void simulate(float deltaTime) { }
|
||||
void render(bool texture);
|
||||
|
@ -85,19 +86,19 @@ public:
|
|||
virtual void hideOutOfView(bool forceFullFrustum = false);
|
||||
bool hasViewChanged();
|
||||
bool isViewChanging();
|
||||
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
VoxelDetail& detail, float& distance, BoxFace& face);
|
||||
|
||||
|
||||
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration);
|
||||
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration);
|
||||
|
||||
void deleteVoxelAt(float x, float y, float z, float s);
|
||||
VoxelTreeElement* getVoxelAt(float x, float y, float z, float s) const;
|
||||
void createVoxel(float x, float y, float z, float s,
|
||||
void createVoxel(float x, float y, float z, float s,
|
||||
unsigned char red, unsigned char green, unsigned char blue, bool destructive = false);
|
||||
void createLine(glm::vec3 point1, glm::vec3 point2, float unitSize, rgbColor color, bool destructive = false);
|
||||
void createSphere(float r,float xc, float yc, float zc, float s, bool solid,
|
||||
void createSphere(float r,float xc, float yc, float zc, float s, bool solid,
|
||||
creationMode mode, bool destructive = false, bool debug = false);
|
||||
|
||||
void copySubTreeIntoNewTree(VoxelTreeElement* startNode, VoxelSystem* destinationTree, bool rebaseToRoot);
|
||||
|
@ -113,19 +114,18 @@ public:
|
|||
virtual void elementUpdated(OctreeElement* element);
|
||||
virtual void nodeAdded(Node* node);
|
||||
virtual void nodeKilled(Node* node);
|
||||
virtual void domainChanged(QString domain);
|
||||
|
||||
|
||||
bool treeIsBusy() const { return _treeIsBusy; }
|
||||
|
||||
|
||||
VoxelTreeElement* getVoxelEnclosing(const glm::vec3& point);
|
||||
|
||||
|
||||
signals:
|
||||
void importSize(float x, float y, float z);
|
||||
void importProgress(int progress);
|
||||
|
||||
public slots:
|
||||
void collectStatsForTreesAndVBOs();
|
||||
|
||||
|
||||
// Methods that recurse tree
|
||||
void showAllLocalVoxels();
|
||||
void randomizeVoxelColors();
|
||||
|
@ -141,24 +141,27 @@ public slots:
|
|||
void clearAllNodesBufferIndex();
|
||||
|
||||
void cancelImport();
|
||||
|
||||
|
||||
void setDisableFastVoxelPipeline(bool disableFastVoxelPipeline);
|
||||
void setUseVoxelShader(bool useVoxelShader);
|
||||
void setVoxelsAsPoints(bool voxelsAsPoints);
|
||||
|
||||
|
||||
void localVoxelCacheLoaded();
|
||||
void beginLoadingLocalVoxelCache();
|
||||
|
||||
protected:
|
||||
float _treeScale;
|
||||
int _maxVoxels;
|
||||
float _treeScale;
|
||||
int _maxVoxels;
|
||||
VoxelTree* _tree;
|
||||
|
||||
void setupNewVoxelsForDrawing();
|
||||
static const bool DONT_BAIL_EARLY; // by default we will bail early, if you want to force not bailing, then use this
|
||||
void setupNewVoxelsForDrawingSingleNode(bool allowBailEarly = true);
|
||||
void checkForCulling();
|
||||
|
||||
|
||||
glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const;
|
||||
|
||||
|
||||
|
||||
virtual void updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex,
|
||||
float voxelScale, const nodeColor& color);
|
||||
virtual void copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd);
|
||||
|
@ -170,7 +173,7 @@ private:
|
|||
// disallow copying of VoxelSystem objects
|
||||
VoxelSystem(const VoxelSystem&);
|
||||
VoxelSystem& operator= (const VoxelSystem&);
|
||||
|
||||
|
||||
bool _initialized;
|
||||
int _callsToTreesToArrays;
|
||||
OctreeElementBag _removedVoxels;
|
||||
|
@ -223,10 +226,10 @@ private:
|
|||
unsigned long _voxelsInReadArrays;
|
||||
unsigned long _voxelsInWriteArrays;
|
||||
unsigned long _abandonedVBOSlots;
|
||||
|
||||
|
||||
bool _writeRenderFullVBO;
|
||||
bool _readRenderFullVBO;
|
||||
|
||||
|
||||
int _setupNewVoxelsForDrawingLastElapsed;
|
||||
uint64_t _setupNewVoxelsForDrawingLastFinished;
|
||||
uint64_t _lastViewCulling;
|
||||
|
@ -234,7 +237,7 @@ private:
|
|||
uint64_t _lastAudit;
|
||||
int _lastViewCullingElapsed;
|
||||
bool _hasRecentlyChanged;
|
||||
|
||||
|
||||
void initVoxelMemory();
|
||||
void cleanupVoxelMemory();
|
||||
|
||||
|
@ -246,7 +249,7 @@ private:
|
|||
GLuint _vboVoxelsIndicesID; /// when using voxel shader, we'll use this VBO for our indexes
|
||||
VoxelShaderVBOData* _writeVoxelShaderData;
|
||||
VoxelShaderVBOData* _readVoxelShaderData;
|
||||
|
||||
|
||||
GLuint _vboVerticesID;
|
||||
GLuint _vboColorsID;
|
||||
|
||||
|
@ -269,11 +272,11 @@ private:
|
|||
|
||||
void setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndices[]);
|
||||
|
||||
int newTreeToArrays(VoxelTreeElement *currentNode);
|
||||
int newTreeToArrays(VoxelTreeElement* currentNode);
|
||||
void cleanupRemovedVoxels();
|
||||
|
||||
void copyWrittenDataToReadArrays(bool fullVBOs);
|
||||
|
||||
|
||||
void updateFullVBOs(); // all voxels in the VBO
|
||||
void updatePartialVBOs(); // multiple segments, only dirty voxels
|
||||
|
||||
|
@ -281,7 +284,7 @@ private:
|
|||
|
||||
static ProgramObject _perlinModulateProgram;
|
||||
static ProgramObject _shadowMapProgram;
|
||||
|
||||
|
||||
int _hookID;
|
||||
std::vector<glBufferIndex> _freeIndexes;
|
||||
pthread_mutex_t _freeIndexLock;
|
||||
|
@ -289,22 +292,22 @@ private:
|
|||
void freeBufferIndex(glBufferIndex index);
|
||||
void clearFreeBufferIndexes();
|
||||
glBufferIndex getNextBufferIndex();
|
||||
|
||||
|
||||
bool _falseColorizeBySource;
|
||||
QUuid _dataSourceUUID;
|
||||
|
||||
|
||||
int _voxelServerCount;
|
||||
unsigned long _memoryUsageRAM;
|
||||
unsigned long _memoryUsageVBO;
|
||||
unsigned long _initialMemoryUsageGPU;
|
||||
bool _hasMemoryUsageGPU;
|
||||
|
||||
|
||||
bool _inSetupNewVoxelsForDrawing;
|
||||
bool _useFastVoxelPipeline;
|
||||
|
||||
|
||||
bool _inhideOutOfView;
|
||||
bool _treeIsBusy; // is the tree mutex locked? if so, it's busy, and if you can avoid it, don't access the tree
|
||||
|
||||
|
||||
void lockTree();
|
||||
void unlockTree();
|
||||
};
|
||||
|
|
|
@ -190,15 +190,13 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) {
|
|||
// update avatar skeleton
|
||||
_skeleton.update(deltaTime, getOrientation(), _position);
|
||||
|
||||
|
||||
// if this is not my avatar, then hand position comes from transmitted data
|
||||
_skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position = _handPosition;
|
||||
|
||||
_hand.simulate(deltaTime, false);
|
||||
_skeletonModel.simulate(deltaTime);
|
||||
_head.setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
|
||||
glm::vec3 headPosition;
|
||||
_skeletonModel.getHeadPosition(headPosition);
|
||||
if (!_skeletonModel.getHeadPosition(headPosition)) {
|
||||
headPosition = _position;
|
||||
}
|
||||
_head.setPosition(headPosition);
|
||||
_head.setScale(_scale);
|
||||
_head.setSkinColor(glm::vec3(SKIN_COLOR[0], SKIN_COLOR[1], SKIN_COLOR[2]));
|
||||
|
@ -340,8 +338,24 @@ void Avatar::getSkinColors(glm::vec3& lighter, glm::vec3& darker) {
|
|||
}
|
||||
}
|
||||
|
||||
bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
|
||||
float minDistance = FLT_MAX;
|
||||
float modelDistance;
|
||||
if (_skeletonModel.findRayIntersection(origin, direction, modelDistance)) {
|
||||
minDistance = qMin(minDistance, modelDistance);
|
||||
}
|
||||
if (_head.getFaceModel().findRayIntersection(origin, direction, modelDistance)) {
|
||||
minDistance = qMin(minDistance, modelDistance);
|
||||
}
|
||||
if (minDistance < FLT_MAX) {
|
||||
distance = minDistance;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Avatar::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
glm::vec3& penetration, int skeletonSkipIndex) {
|
||||
glm::vec3& penetration, int skeletonSkipIndex) const {
|
||||
bool didPenetrate = false;
|
||||
glm::vec3 totalPenetration;
|
||||
glm::vec3 skeletonPenetration;
|
||||
|
|
|
@ -24,8 +24,6 @@
|
|||
#include "devices/SerialInterface.h"
|
||||
#include "devices/Transmitter.h"
|
||||
|
||||
static const float MAX_SCALE = 1000.f;
|
||||
static const float MIN_SCALE = .005f;
|
||||
static const float SCALING_RATIO = .05f;
|
||||
static const float SMOOTHING_RATIO = .05f; // 0 < ratio < 1
|
||||
static const float RESCALING_TOLERANCE = .02f;
|
||||
|
@ -160,6 +158,8 @@ public:
|
|||
|
||||
void getSkinColors(glm::vec3& lighter, glm::vec3& darker);
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
/// Checks for penetration between the described sphere and the avatar.
|
||||
/// \param penetratorCenter the center of the penetration test sphere
|
||||
/// \param penetratorRadius the radius of the penetration test sphere
|
||||
|
@ -167,7 +167,7 @@ public:
|
|||
/// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
|
||||
/// \return whether or not the sphere penetrated
|
||||
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
glm::vec3& penetration, int skeletonSkipIndex = -1);
|
||||
glm::vec3& penetration, int skeletonSkipIndex = -1) const;
|
||||
|
||||
virtual int parseData(unsigned char* sourceBuffer, int numBytes);
|
||||
|
||||
|
|
|
@ -222,8 +222,9 @@ void Head::simulate(float deltaTime, bool isMine) {
|
|||
calculateGeometry();
|
||||
|
||||
// the blend face may have custom eye meshes
|
||||
_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition);
|
||||
|
||||
if (!_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition)) {
|
||||
_leftEyePosition = _rightEyePosition = getPosition();
|
||||
}
|
||||
}
|
||||
|
||||
void Head::calculateGeometry() {
|
||||
|
|
|
@ -72,6 +72,7 @@ public:
|
|||
|
||||
VideoFace& getVideoFace() { return _videoFace; }
|
||||
FaceModel& getFaceModel() { return _faceModel; }
|
||||
const FaceModel& getFaceModel() const { return _faceModel; }
|
||||
|
||||
const bool getReturnToCenter() const { return _returnHeadToCenter; } // Do you want head to try to return to center (depends on interface detected)
|
||||
float getAverageLoudness() const { return _averageLoudness; }
|
||||
|
|
|
@ -200,7 +200,6 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) {
|
|||
// Compute instantaneous acceleration
|
||||
float forwardAcceleration = glm::length(glm::dot(getBodyFrontDirection(), getVelocity() - oldVelocity)) / deltaTime;
|
||||
const float ACCELERATION_PITCH_DECAY = 0.4f;
|
||||
const float ACCELERATION_YAW_DECAY = 0.4f;
|
||||
const float ACCELERATION_PULL_THRESHOLD = 0.2f;
|
||||
const float OCULUS_ACCELERATION_PULL_THRESHOLD = 1.0f;
|
||||
const int OCULUS_YAW_OFFSET_THRESHOLD = 10;
|
||||
|
@ -210,8 +209,8 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) {
|
|||
// you start moving, but don't do this with an HMD like the Oculus.
|
||||
if (!OculusManager::isConnected()) {
|
||||
if (forwardAcceleration > ACCELERATION_PULL_THRESHOLD) {
|
||||
_head.setPitch(_head.getPitch() * (1.f - forwardAcceleration * ACCELERATION_PITCH_DECAY * deltaTime));
|
||||
_head.setYaw(_head.getYaw() * (1.f - forwardAcceleration * ACCELERATION_YAW_DECAY * deltaTime));
|
||||
_head.setMousePitch(_head.getMousePitch() * qMax(0.0f,
|
||||
(1.f - forwardAcceleration * ACCELERATION_PITCH_DECAY * deltaTime)));
|
||||
}
|
||||
} else if (fabsf(forwardAcceleration) > OCULUS_ACCELERATION_PULL_THRESHOLD
|
||||
&& fabs(_head.getYaw()) > OCULUS_YAW_OFFSET_THRESHOLD) {
|
||||
|
@ -282,7 +281,9 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) {
|
|||
_skeletonModel.simulate(deltaTime);
|
||||
_head.setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
|
||||
glm::vec3 headPosition;
|
||||
_skeletonModel.getHeadPosition(headPosition);
|
||||
if (!_skeletonModel.getHeadPosition(headPosition)) {
|
||||
headPosition = _position;
|
||||
}
|
||||
_head.setPosition(headPosition);
|
||||
_head.setScale(_scale);
|
||||
_head.setSkinColor(glm::vec3(SKIN_COLOR[0], SKIN_COLOR[1], SKIN_COLOR[2]));
|
||||
|
@ -542,6 +543,16 @@ void MyAvatar::loadData(QSettings* settings) {
|
|||
settings->endGroup();
|
||||
}
|
||||
|
||||
void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
|
||||
glm::vec3 vector = getPosition() - position;
|
||||
glm::quat orientation = getOrientation();
|
||||
glm::vec3 up = orientation * IDENTITY_UP;
|
||||
const float ANGULAR_SCALE = 0.5f;
|
||||
glm::quat rotation = glm::angleAxis(deltaX * -ANGULAR_SCALE, up);
|
||||
const float LINEAR_SCALE = 0.01f;
|
||||
setPosition(position + rotation * vector + up * (deltaY * LINEAR_SCALE * _scale));
|
||||
setOrientation(rotation * orientation);
|
||||
}
|
||||
|
||||
float MyAvatar::getAbsoluteHeadYaw() const {
|
||||
return glm::yaw(_head.getOrientation());
|
||||
|
@ -757,15 +768,11 @@ void MyAvatar::updateHandMovementAndTouching(float deltaTime, bool enableHandMov
|
|||
glm::vec3 farVector = _mouseRayOrigin + pointDirection * (float)TREE_SCALE - shoulderPosition;
|
||||
const float ARM_RETRACTION = 0.75f;
|
||||
float retractedLength = _skeletonModel.getRightArmLength() * ARM_RETRACTION;
|
||||
_skeleton.joint[AVATAR_JOINT_RIGHT_FINGERTIPS].position = shoulderPosition +
|
||||
glm::normalize(farVector) * retractedLength;
|
||||
setHandPosition(shoulderPosition + glm::normalize(farVector) * retractedLength);
|
||||
pointing = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Set right hand position and state to be transmitted, and also tell AvatarTouch about it
|
||||
setHandPosition(_skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position);
|
||||
|
||||
if (_mousePressed) {
|
||||
_handState = HAND_STATE_GRASPING;
|
||||
} else if (pointing) {
|
||||
|
@ -919,8 +926,9 @@ void MyAvatar::updateChatCircle(float deltaTime) {
|
|||
// remove members whose accumulated circles are too far away to influence us
|
||||
const float CIRCUMFERENCE_PER_MEMBER = 0.5f;
|
||||
const float CIRCLE_INFLUENCE_SCALE = 2.0f;
|
||||
const float MIN_RADIUS = 0.3f;
|
||||
for (int i = sortedAvatars.size() - 1; i >= 0; i--) {
|
||||
float radius = (CIRCUMFERENCE_PER_MEMBER * (i + 2)) / PI_TIMES_TWO;
|
||||
float radius = qMax(MIN_RADIUS, (CIRCUMFERENCE_PER_MEMBER * (i + 2)) / PI_TIMES_TWO);
|
||||
if (glm::distance(_position, sortedAvatars[i].accumulatedCenter) > radius * CIRCLE_INFLUENCE_SCALE) {
|
||||
sortedAvatars.remove(i);
|
||||
} else {
|
||||
|
@ -931,7 +939,7 @@ void MyAvatar::updateChatCircle(float deltaTime) {
|
|||
return;
|
||||
}
|
||||
center = sortedAvatars.last().accumulatedCenter;
|
||||
float radius = (CIRCUMFERENCE_PER_MEMBER * (sortedAvatars.size() + 1)) / PI_TIMES_TWO;
|
||||
float radius = qMax(MIN_RADIUS, (CIRCUMFERENCE_PER_MEMBER * (sortedAvatars.size() + 1)) / PI_TIMES_TWO);
|
||||
|
||||
// compute the average up vector
|
||||
glm::vec3 up = getWorldAlignedOrientation() * IDENTITY_UP;
|
||||
|
|
|
@ -69,6 +69,8 @@ public:
|
|||
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
|
||||
glm::vec3 getThrust() { return _thrust; };
|
||||
|
||||
void orbit(const glm::vec3& position, int deltaX, int deltaY);
|
||||
|
||||
private:
|
||||
bool _mousePressed;
|
||||
float _bodyPitchDelta;
|
||||
|
|
|
@ -95,6 +95,7 @@ void Faceshift::reset() {
|
|||
void Faceshift::updateFakeCoefficients(float leftBlink, float rightBlink, float browUp,
|
||||
float jawOpen, std::vector<float>& coefficients) const {
|
||||
coefficients.resize(max((int)coefficients.size(), _jawOpenIndex + 1));
|
||||
qFill(coefficients.begin(), coefficients.end(), 0.0f);
|
||||
coefficients[_leftBlinkIndex] = leftBlink;
|
||||
coefficients[_rightBlinkIndex] = rightBlink;
|
||||
coefficients[_browUpCenterIndex] = browUp;
|
||||
|
|
|
@ -1,352 +0,0 @@
|
|||
//
|
||||
// LeapManager.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Eric Johnston on 6/26/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <dlfcn.h> // needed for RTLD_LAZY
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <Leap.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "LeapManager.h"
|
||||
#include "Menu.h"
|
||||
|
||||
// Uncomment the next line to use Leap-smoothed stabilized (slower) data.
|
||||
//#define USE_STABILIZED_DATA
|
||||
|
||||
bool LeapManager::_libraryExists = false;
|
||||
bool LeapManager::_doFakeFingers = false;
|
||||
Leap::Controller* LeapManager::_controller = NULL;
|
||||
HifiLeapListener* LeapManager::_listener = NULL;
|
||||
glm::vec3 LeapManager::_baseDrivePosition(0.0f, 150.0f, 0.0f); // experimentally derived
|
||||
|
||||
class HifiLeapListener : public Leap::Listener {
|
||||
public:
|
||||
HifiLeapListener() {}
|
||||
virtual ~HifiLeapListener() {}
|
||||
|
||||
Leap::Frame lastFrame;
|
||||
virtual void onFrame(const Leap::Controller& controller) {
|
||||
#ifndef LEAP_STUBS
|
||||
lastFrame = controller.frame();
|
||||
#endif
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void LeapManager::initialize() {
|
||||
#ifndef LEAP_STUBS
|
||||
if (dlopen("/usr/lib/libLeap.dylib", RTLD_LAZY)) {
|
||||
_libraryExists = true;
|
||||
_controller = new Leap::Controller();
|
||||
_listener = new HifiLeapListener();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static PalmData* getRightmostPalm() {
|
||||
PalmData* rightmostPalm = NULL;
|
||||
Hand& hand = Application::getInstance()->getAvatar()->getHand();
|
||||
for (int i = 0; i < hand.getNumPalms(); i++) {
|
||||
PalmData* palm = &hand.getPalms()[i];
|
||||
if (palm->isActive() && (rightmostPalm == NULL || palm->getRawPosition().x > rightmostPalm->getRawPosition().x)) {
|
||||
rightmostPalm = palm;
|
||||
}
|
||||
}
|
||||
return rightmostPalm;
|
||||
}
|
||||
|
||||
void LeapManager::reset() {
|
||||
PalmData* rightmostPalm = getRightmostPalm();
|
||||
if (rightmostPalm != NULL) {
|
||||
_baseDrivePosition = rightmostPalm->getRawPosition();
|
||||
}
|
||||
}
|
||||
|
||||
void LeapManager::terminate() {
|
||||
delete _listener;
|
||||
delete _controller;
|
||||
_listener = NULL;
|
||||
_controller = NULL;
|
||||
}
|
||||
|
||||
void LeapManager::nextFrame() {
|
||||
// Apply the frame data directly to the avatar.
|
||||
MyAvatar* avatar = Application::getInstance()->getAvatar();
|
||||
Hand& hand = avatar->getHand();
|
||||
|
||||
// If we actually get valid Leap data, this will be set to true;
|
||||
bool gotRealData = false;
|
||||
|
||||
if (controllersExist()) {
|
||||
_listener->onFrame(*_controller);
|
||||
}
|
||||
|
||||
#ifndef LEAP_STUBS
|
||||
if (controllersExist()) {
|
||||
gotRealData = true;
|
||||
// First, see which palms and fingers are still valid.
|
||||
Leap::Frame& frame = _listener->lastFrame;
|
||||
|
||||
// Note that this is O(n^2) at worst, but n is very small.
|
||||
|
||||
// After this many frames of no data, assume the digit is lost.
|
||||
const int assumeLostAfterFrameCount = 10;
|
||||
|
||||
// Increment our frame data counters
|
||||
for (size_t i = 0; i < hand.getNumPalms(); ++i) {
|
||||
PalmData& palm = hand.getPalms()[i];
|
||||
palm.incrementFramesWithoutData();
|
||||
if (palm.getFramesWithoutData() > assumeLostAfterFrameCount) {
|
||||
palm.setActive(false);
|
||||
}
|
||||
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
|
||||
FingerData& finger = palm.getFingers()[f];
|
||||
finger.incrementFramesWithoutData();
|
||||
if (finger.getFramesWithoutData() > assumeLostAfterFrameCount) {
|
||||
finger.setActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t numLeapHands = frame.hands().count();
|
||||
std::vector<PalmData*> palmAssignment(numLeapHands);
|
||||
|
||||
// Look for matches
|
||||
for (size_t index = 0; index < numLeapHands; ++index) {
|
||||
PalmData* takeoverCandidate = NULL;
|
||||
palmAssignment[index] = NULL;
|
||||
Leap::Hand leapHand = frame.hands()[index];
|
||||
int id = leapHand.id();
|
||||
if (leapHand.isValid()) {
|
||||
for (size_t i = 0; i < hand.getNumPalms() && palmAssignment[index] == NULL; ++i) {
|
||||
PalmData& palm = hand.getPalms()[i];
|
||||
if (palm.getLeapID() == id) {
|
||||
// Found hand with the same ID. We're set!
|
||||
palmAssignment[index] = &palm;
|
||||
palm.resetFramesWithoutData();
|
||||
}
|
||||
else if (palm.getFramesWithoutData() > assumeLostAfterFrameCount) {
|
||||
takeoverCandidate = &palm;
|
||||
}
|
||||
}
|
||||
if (palmAssignment[index] == NULL) {
|
||||
palmAssignment[index] = takeoverCandidate;
|
||||
}
|
||||
if (palmAssignment[index] == NULL) {
|
||||
palmAssignment[index] = &hand.addNewPalm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the assignments
|
||||
for (size_t index = 0; index < numLeapHands; ++index) {
|
||||
if (palmAssignment[index]) {
|
||||
Leap::Hand leapHand = frame.hands()[index];
|
||||
PalmData& palm = *(palmAssignment[index]);
|
||||
|
||||
palm.resetFramesWithoutData();
|
||||
palm.setLeapID(leapHand.id());
|
||||
palm.setActive(true);
|
||||
const Leap::Vector pos = leapHand.palmPosition();
|
||||
const Leap::Vector normal = leapHand.palmNormal();
|
||||
palm.setRawPosition(glm::vec3(pos.x, pos.y, pos.z));
|
||||
palm.setRawNormal(glm::vec3(normal.x, normal.y, normal.z));
|
||||
}
|
||||
}
|
||||
|
||||
// Look for fingers per palm
|
||||
for (size_t i = 0; i < hand.getNumPalms(); ++i) {
|
||||
PalmData& palm = hand.getPalms()[i];
|
||||
if (palm.isActive()) {
|
||||
Leap::Hand leapHand = frame.hand(palm.getLeapID());
|
||||
if (leapHand.isValid()) {
|
||||
int numLeapFingers = leapHand.fingers().count();
|
||||
std::vector<FingerData*> fingerAssignment(numLeapFingers);
|
||||
|
||||
|
||||
// Look for matches
|
||||
for (size_t index = 0; index < numLeapFingers; ++index) {
|
||||
FingerData* takeoverCandidate = NULL;
|
||||
fingerAssignment[index] = NULL;
|
||||
Leap::Finger leapFinger = leapHand.fingers()[index];
|
||||
int id = leapFinger.id();
|
||||
if (leapFinger.isValid()) {
|
||||
for (size_t f = 0; f < palm.getNumFingers() && fingerAssignment[index] == NULL; ++f) {
|
||||
FingerData& finger = palm.getFingers()[f];
|
||||
if (finger.getLeapID() == id) {
|
||||
// Found hand with the same ID. We're set!
|
||||
fingerAssignment[index] = &finger;
|
||||
}
|
||||
else if (finger.getFramesWithoutData() > assumeLostAfterFrameCount) {
|
||||
takeoverCandidate = &finger;
|
||||
}
|
||||
}
|
||||
// If we didn't find a match, but we found an unused finger, us it.
|
||||
if (fingerAssignment[index] == NULL) {
|
||||
fingerAssignment[index] = takeoverCandidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the assignments
|
||||
for (size_t index = 0; index < numLeapFingers; ++index) {
|
||||
if (fingerAssignment[index]) {
|
||||
Leap::Finger leapFinger = leapHand.fingers()[index];
|
||||
FingerData& finger = *(fingerAssignment[index]);
|
||||
|
||||
finger.resetFramesWithoutData();
|
||||
finger.setLeapID(leapFinger.id());
|
||||
finger.setActive(true);
|
||||
#ifdef USE_STABILIZED_DATA
|
||||
const Leap::Vector tip = leapFinger.stabilizedTipPosition();
|
||||
#else
|
||||
const Leap::Vector tip = leapFinger.tipPosition();
|
||||
#endif
|
||||
const Leap::Vector root = tip - leapFinger.direction() * leapFinger.length();
|
||||
finger.setRawTipPosition(glm::vec3(tip.x, tip.y, tip.z));
|
||||
finger.setRawRootPosition(glm::vec3(root.x, root.y, root.z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!gotRealData) {
|
||||
if (_doFakeFingers) {
|
||||
// There's no real Leap data and we need to fake it.
|
||||
for (size_t i = 0; i < hand.getNumPalms(); ++i) {
|
||||
static const glm::vec3 fakeHandOffsets[] = {
|
||||
glm::vec3( -250.0f, 50.0f, 50.0f),
|
||||
glm::vec3( 250.0f, 50.0f, 50.0f)
|
||||
};
|
||||
static const glm::vec3 fakeHandFingerMirrors[] = {
|
||||
glm::vec3( -1.0f, 1.0f, 1.0f),
|
||||
glm::vec3( 1.0f, 1.0f, 1.0f)
|
||||
};
|
||||
static const glm::vec3 fakeFingerPositions[] = {
|
||||
glm::vec3( -60.0f, 0.0f, -40.0f),
|
||||
glm::vec3( -20.0f, 0.0f, -60.0f),
|
||||
glm::vec3( 20.0f, 0.0f, -60.0f),
|
||||
glm::vec3( 60.0f, 0.0f, -40.0f),
|
||||
glm::vec3( -50.0f, 0.0f, 30.0f)
|
||||
};
|
||||
|
||||
PalmData& palm = hand.getPalms()[i];
|
||||
palm.setActive(true);
|
||||
// Simulated data
|
||||
|
||||
palm.setRawPosition(glm::vec3( 0.0f, 0.0f, 0.0f) + fakeHandOffsets[i]);
|
||||
palm.setRawNormal(glm::vec3(0.0f, -1.0f, 0.0f));
|
||||
|
||||
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
|
||||
FingerData& finger = palm.getFingers()[f];
|
||||
finger.setActive(true);
|
||||
const float tipScale = 1.5f;
|
||||
const float rootScale = 0.75f;
|
||||
glm::vec3 fingerPos = fakeFingerPositions[f] * fakeHandFingerMirrors[i];
|
||||
finger.setRawTipPosition(fingerPos * tipScale + fakeHandOffsets[i]);
|
||||
finger.setRawRootPosition(fingerPos * rootScale + fakeHandOffsets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Just deactivate everything.
|
||||
for (size_t i = 0; i < hand.getNumPalms(); ++i) {
|
||||
PalmData& palm = hand.getPalms()[i];
|
||||
palm.setActive(false);
|
||||
for (size_t f = 0; f < palm.getNumFingers(); ++f) {
|
||||
FingerData& finger = palm.getFingers()[f];
|
||||
finger.setActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hand.updateFingerTrails();
|
||||
|
||||
// if Leap drive is enabled, drive avatar based on Leap input
|
||||
if (!(Menu::getInstance()->isOptionChecked(MenuOption::LeapDrive) && gotRealData)) {
|
||||
return;
|
||||
}
|
||||
glm::vec3 relativePosition;
|
||||
glm::vec3 eulerAngles;
|
||||
PalmData* rightmostPalm = getRightmostPalm();
|
||||
if (rightmostPalm != NULL) {
|
||||
relativePosition = rightmostPalm->getRawPosition() - _baseDrivePosition;
|
||||
|
||||
glm::vec3 directionSum;
|
||||
int activeFingerCount = 0;
|
||||
for (int i = 0; i < rightmostPalm->getNumFingers(); i++) {
|
||||
FingerData& finger = rightmostPalm->getFingers()[i];
|
||||
glm::vec3 fingerVector = finger.getTipRawPosition() - rightmostPalm->getRawPosition();
|
||||
if (finger.isActive() && glm::length(fingerVector) > EPSILON) {
|
||||
directionSum += glm::normalize(fingerVector);
|
||||
activeFingerCount++;
|
||||
}
|
||||
}
|
||||
const int MIN_DIRECTION_FINGER_COUNT = 3;
|
||||
glm::vec3 right;
|
||||
if (activeFingerCount >= MIN_DIRECTION_FINGER_COUNT) {
|
||||
right = glm::normalize(glm::cross(glm::normalize(directionSum), -rightmostPalm->getRawNormal()));
|
||||
|
||||
} else {
|
||||
right = glm::normalize(glm::cross(IDENTITY_FRONT, -rightmostPalm->getRawNormal()));
|
||||
}
|
||||
eulerAngles = safeEulerAngles(glm::quat_cast(glm::mat3(right, -rightmostPalm->getRawNormal(),
|
||||
glm::cross(right, -rightmostPalm->getRawNormal()))));
|
||||
}
|
||||
const float LINEAR_DRIVE_SCALE = 0.01f;
|
||||
const float LINEAR_DEAD_ZONE = 0.25f;
|
||||
avatar->setDriveKeys(FWD, glm::clamp(-relativePosition.z * LINEAR_DRIVE_SCALE - LINEAR_DEAD_ZONE, 0.0f, 1.0f));
|
||||
avatar->setDriveKeys(BACK, glm::clamp(relativePosition.z * LINEAR_DRIVE_SCALE - LINEAR_DEAD_ZONE, 0.0f, 1.0f));
|
||||
avatar->setDriveKeys(LEFT, glm::clamp(-relativePosition.x * LINEAR_DRIVE_SCALE - LINEAR_DEAD_ZONE, 0.0f, 1.0f));
|
||||
avatar->setDriveKeys(RIGHT, glm::clamp(relativePosition.x * LINEAR_DRIVE_SCALE - LINEAR_DEAD_ZONE, 0.0f, 1.0f));
|
||||
avatar->setDriveKeys(UP, glm::clamp(relativePosition.y * LINEAR_DRIVE_SCALE - LINEAR_DEAD_ZONE, 0.0f, 1.0f));
|
||||
avatar->setDriveKeys(DOWN, glm::clamp(-relativePosition.y * LINEAR_DRIVE_SCALE - LINEAR_DEAD_ZONE, 0.0f, 1.0f));
|
||||
|
||||
const float ANGULAR_DRIVE_SCALE = 0.05f;
|
||||
const float ANGULAR_DEAD_ZONE = 0.75f;
|
||||
avatar->setDriveKeys(ROT_LEFT, glm::clamp(glm::max(eulerAngles.y, eulerAngles.z) * ANGULAR_DRIVE_SCALE -
|
||||
ANGULAR_DEAD_ZONE, 0.0f, 1.0f));
|
||||
avatar->setDriveKeys(ROT_RIGHT, glm::clamp(glm::max(-eulerAngles.y, -eulerAngles.z) * ANGULAR_DRIVE_SCALE -
|
||||
ANGULAR_DEAD_ZONE, 0.0f, 1.0f));
|
||||
avatar->setDriveKeys(ROT_UP, glm::clamp(eulerAngles.x * ANGULAR_DRIVE_SCALE - ANGULAR_DEAD_ZONE, 0.0f, 1.0f));
|
||||
avatar->setDriveKeys(ROT_DOWN, glm::clamp(-eulerAngles.x * ANGULAR_DRIVE_SCALE - ANGULAR_DEAD_ZONE, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
void LeapManager::enableFakeFingers(bool enable) {
|
||||
_doFakeFingers = enable;
|
||||
}
|
||||
|
||||
bool LeapManager::controllersExist() {
|
||||
#ifdef LEAP_STUBS
|
||||
return false;
|
||||
#else
|
||||
return _listener && _controller && _controller->devices().count() > 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string LeapManager::statusString() {
|
||||
std::stringstream leapString;
|
||||
#ifndef LEAP_STUBS
|
||||
if (!_libraryExists)
|
||||
leapString << "Leap library at /usr/lib/libLeap.dylib does not exist.";
|
||||
else if (!_controller || !_listener || !_controller->devices().count())
|
||||
leapString << "Leap controller is not attached.";
|
||||
else {
|
||||
leapString << "Leap pointables: " << _listener->lastFrame.pointables().count();
|
||||
if (_listener->lastFrame.pointables().count() > 0) {
|
||||
Leap::Vector pos = _listener->lastFrame.pointables()[0].tipPosition();
|
||||
leapString << " pos: " << pos.x << " " << pos.y << " " << pos.z;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return leapString.str();
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
//
|
||||
// LeapManager.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Eric Johnston on 6/26/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__LeapManager__
|
||||
#define __hifi__LeapManager__
|
||||
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
#include <string>
|
||||
|
||||
class Avatar;
|
||||
class HifiLeapListener;
|
||||
namespace Leap {
|
||||
class Controller;
|
||||
}
|
||||
|
||||
class LeapManager {
|
||||
public:
|
||||
static void nextFrame(); // called once per frame to get new Leap data
|
||||
static bool controllersExist(); // Returns true if there's at least one active Leap plugged in
|
||||
static void enableFakeFingers(bool enable); // put fake data in if there's no Leap plugged in
|
||||
static const std::vector<glm::vec3>& getFingerTips();
|
||||
static const std::vector<glm::vec3>& getFingerRoots();
|
||||
static const std::vector<glm::vec3>& getHandPositions();
|
||||
static const std::vector<glm::vec3>& getHandNormals();
|
||||
static std::string statusString();
|
||||
static void initialize();
|
||||
static void reset();
|
||||
static void terminate();
|
||||
|
||||
private:
|
||||
static bool _libraryExists; // The library is present, so we won't crash if we call it.
|
||||
static bool _doFakeFingers;
|
||||
static Leap::Controller* _controller;
|
||||
static HifiLeapListener* _listener;
|
||||
static glm::vec3 _baseDrivePosition;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__LeapManager__) */
|
|
@ -13,7 +13,9 @@
|
|||
#include "Application.h"
|
||||
#include "SixenseManager.h"
|
||||
|
||||
SixenseManager::SixenseManager() {
|
||||
using namespace std;
|
||||
|
||||
SixenseManager::SixenseManager() : _lastMovement(0) {
|
||||
#ifdef HAVE_SIXENSE
|
||||
sixenseInit();
|
||||
#endif
|
||||
|
@ -98,6 +100,12 @@ void SixenseManager::update(float deltaTime) {
|
|||
palm->setRawVelocity(rawVelocity); // meters/sec
|
||||
palm->setRawPosition(position);
|
||||
|
||||
// use the velocity to determine whether there's any movement (if the hand isn't new)
|
||||
const float MOVEMENT_SPEED_THRESHOLD = 0.05f;
|
||||
if (glm::length(rawVelocity) > MOVEMENT_SPEED_THRESHOLD && foundHand) {
|
||||
_lastMovement = usecTimestampNow();
|
||||
}
|
||||
|
||||
// initialize the "finger" based on the direction
|
||||
FingerData finger(palm, &hand);
|
||||
finger.setActive(true);
|
||||
|
@ -118,6 +126,14 @@ void SixenseManager::update(float deltaTime) {
|
|||
palm->getFingers().push_back(finger);
|
||||
palm->getFingers().push_back(finger);
|
||||
}
|
||||
|
||||
// if the controllers haven't been moved in a while, disable
|
||||
const int MOVEMENT_DISABLE_DURATION = 30 * 1000 * 1000;
|
||||
if (usecTimestampNow() - _lastMovement > MOVEMENT_DISABLE_DURATION) {
|
||||
for (vector<PalmData>::iterator it = hand.getPalms().begin(); it != hand.getPalms().end(); it++) {
|
||||
it->setActive(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,10 @@ public:
|
|||
public slots:
|
||||
|
||||
void setFilter(bool filter);
|
||||
|
||||
private:
|
||||
|
||||
uint64_t _lastMovement;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__SixenseManager__) */
|
||||
|
|
|
@ -548,17 +548,18 @@ public:
|
|||
};
|
||||
|
||||
void appendModelIDs(const QString& parentID, const QMultiHash<QString, QString>& childMap,
|
||||
QHash<QString, FBXModel>& models, QVector<QString>& modelIDs) {
|
||||
if (models.contains(parentID)) {
|
||||
QHash<QString, FBXModel>& models, QSet<QString>& remainingModels, QVector<QString>& modelIDs) {
|
||||
if (remainingModels.contains(parentID)) {
|
||||
modelIDs.append(parentID);
|
||||
remainingModels.remove(parentID);
|
||||
}
|
||||
int parentIndex = modelIDs.size() - 1;
|
||||
foreach (const QString& childID, childMap.values(parentID)) {
|
||||
if (models.contains(childID)) {
|
||||
if (remainingModels.contains(childID)) {
|
||||
FBXModel& model = models[childID];
|
||||
if (model.parentIndex == -1) {
|
||||
model.parentIndex = parentIndex;
|
||||
appendModelIDs(childID, childMap, models, modelIDs);
|
||||
appendModelIDs(childID, childMap, models, remainingModels, modelIDs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1101,8 +1102,12 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
|
||||
// get the list of models in depth-first traversal order
|
||||
QVector<QString> modelIDs;
|
||||
if (!models.isEmpty()) {
|
||||
QString top = models.constBegin().key();
|
||||
QSet<QString> remainingModels;
|
||||
for (QHash<QString, FBXModel>::const_iterator model = models.constBegin(); model != models.constEnd(); model++) {
|
||||
remainingModels.insert(model.key());
|
||||
}
|
||||
while (!remainingModels.isEmpty()) {
|
||||
QString top = *remainingModels.constBegin();
|
||||
forever {
|
||||
foreach (const QString& name, parentMap.values(top)) {
|
||||
if (models.contains(name)) {
|
||||
|
@ -1115,7 +1120,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
|
||||
outerContinue: ;
|
||||
}
|
||||
appendModelIDs(top, childMap, models, modelIDs);
|
||||
appendModelIDs(top, childMap, models, remainingModels, modelIDs);
|
||||
}
|
||||
|
||||
// convert the models to joints
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
//
|
||||
// MetavoxelSystem.cpp
|
||||
// interface
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/10/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "MetavoxelSystem.h"
|
||||
|
||||
ProgramObject MetavoxelSystem::_program;
|
||||
int MetavoxelSystem::_pointScaleLocation;
|
||||
|
||||
MetavoxelSystem::MetavoxelSystem() :
|
||||
_pointVisitor(_points),
|
||||
_buffer(QOpenGLBuffer::VertexBuffer) {
|
||||
}
|
||||
|
||||
void MetavoxelSystem::init() {
|
||||
if (!_program.isLinked()) {
|
||||
switchToResourcesParentIfRequired();
|
||||
_program.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/metavoxel_point.vert");
|
||||
_program.link();
|
||||
|
||||
_pointScaleLocation = _program.uniformLocation("pointScale");
|
||||
}
|
||||
|
||||
AttributeRegistry::getInstance()->configureScriptEngine(&_scriptEngine);
|
||||
|
||||
QFile scriptFile("resources/scripts/sphere.js");
|
||||
scriptFile.open(QIODevice::ReadOnly);
|
||||
QScriptValue guideFunction = _scriptEngine.evaluate(QTextStream(&scriptFile).readAll());
|
||||
_data.setAttributeValue(MetavoxelPath(), AttributeValue(AttributeRegistry::getInstance()->getGuideAttribute(),
|
||||
encodeInline(PolymorphicDataPointer(new ScriptedMetavoxelGuide(guideFunction)))));
|
||||
|
||||
_buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
|
||||
_buffer.create();
|
||||
}
|
||||
|
||||
void MetavoxelSystem::simulate(float deltaTime) {
|
||||
_points.clear();
|
||||
_data.guide(_pointVisitor);
|
||||
|
||||
_buffer.bind();
|
||||
int bytes = _points.size() * sizeof(Point);
|
||||
if (_buffer.size() < bytes) {
|
||||
_buffer.allocate(_points.constData(), bytes);
|
||||
} else {
|
||||
_buffer.write(0, _points.constData(), bytes);
|
||||
}
|
||||
_buffer.release();
|
||||
}
|
||||
|
||||
void MetavoxelSystem::render() {
|
||||
int viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
const int VIEWPORT_WIDTH_INDEX = 2;
|
||||
const int VIEWPORT_HEIGHT_INDEX = 3;
|
||||
float viewportWidth = viewport[VIEWPORT_WIDTH_INDEX];
|
||||
float viewportHeight = viewport[VIEWPORT_HEIGHT_INDEX];
|
||||
float viewportDiagonal = sqrtf(viewportWidth*viewportWidth + viewportHeight*viewportHeight);
|
||||
float worldDiagonal = glm::distance(Application::getInstance()->getViewFrustum()->getNearBottomLeft(),
|
||||
Application::getInstance()->getViewFrustum()->getNearTopRight());
|
||||
|
||||
_program.bind();
|
||||
_program.setUniformValue(_pointScaleLocation, viewportDiagonal *
|
||||
Application::getInstance()->getViewFrustum()->getNearClip() / worldDiagonal);
|
||||
|
||||
_buffer.bind();
|
||||
|
||||
Point* pt = 0;
|
||||
glVertexPointer(4, GL_FLOAT, sizeof(Point), &pt->vertex);
|
||||
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Point), &pt->color);
|
||||
glNormalPointer(GL_BYTE, sizeof(Point), &pt->normal);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
|
||||
|
||||
glDrawArrays(GL_POINTS, 0, _points.size());
|
||||
|
||||
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
_buffer.release();
|
||||
|
||||
_program.release();
|
||||
}
|
||||
|
||||
MetavoxelSystem::PointVisitor::PointVisitor(QVector<Point>& points) :
|
||||
MetavoxelVisitor(QVector<AttributePointer>() <<
|
||||
AttributeRegistry::getInstance()->getColorAttribute() <<
|
||||
AttributeRegistry::getInstance()->getNormalAttribute()),
|
||||
_points(points) {
|
||||
}
|
||||
|
||||
bool MetavoxelSystem::PointVisitor::visit(const MetavoxelInfo& info) {
|
||||
if (!info.isLeaf) {
|
||||
return true;
|
||||
}
|
||||
QRgb color = info.attributeValues.at(0).getInlineValue<QRgb>();
|
||||
QRgb normal = info.attributeValues.at(1).getInlineValue<QRgb>();
|
||||
int alpha = qAlpha(color);
|
||||
if (alpha > 0) {
|
||||
Point point = { glm::vec4(info.minimum + glm::vec3(info.size, info.size, info.size) * 0.5f, info.size),
|
||||
{ qRed(color), qGreen(color), qBlue(color), alpha }, { qRed(normal), qGreen(normal), qBlue(normal) } };
|
||||
_points.append(point);
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
//
|
||||
// MetavoxelSystem.h
|
||||
// interface
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/10/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __interface__MetavoxelSystem__
|
||||
#define __interface__MetavoxelSystem__
|
||||
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QScriptEngine>
|
||||
#include <QVector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <MetavoxelData.h>
|
||||
|
||||
#include "ProgramObject.h"
|
||||
|
||||
/// Renders a metavoxel tree.
|
||||
class MetavoxelSystem {
|
||||
public:
|
||||
|
||||
MetavoxelSystem();
|
||||
|
||||
void init();
|
||||
|
||||
void simulate(float deltaTime);
|
||||
void render();
|
||||
|
||||
private:
|
||||
|
||||
class Point {
|
||||
public:
|
||||
glm::vec4 vertex;
|
||||
quint8 color[4];
|
||||
quint8 normal[3];
|
||||
};
|
||||
|
||||
class PointVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
PointVisitor(QVector<Point>& points);
|
||||
virtual bool visit(const MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
QVector<Point>& _points;
|
||||
};
|
||||
|
||||
static ProgramObject _program;
|
||||
static int _pointScaleLocation;
|
||||
|
||||
QScriptEngine _scriptEngine;
|
||||
MetavoxelData _data;
|
||||
QVector<Point> _points;
|
||||
PointVisitor _pointVisitor;
|
||||
QOpenGLBuffer _buffer;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__MetavoxelSystem__) */
|
|
@ -546,6 +546,35 @@ glm::vec4 Model::computeAverageColor() const {
|
|||
return _geometry ? _geometry->computeAverageColor() : glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
|
||||
const glm::vec3 relativeOrigin = origin - _translation;
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
float minDistance = FLT_MAX;
|
||||
float radiusScale = extractUniformScale(_scale);
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
glm::vec3 end = extractTranslation(_jointStates[i].transform);
|
||||
float endRadius = joint.boneRadius * radiusScale;
|
||||
glm::vec3 start = end;
|
||||
float startRadius = joint.boneRadius * radiusScale;
|
||||
if (joint.parentIndex != -1) {
|
||||
start = extractTranslation(_jointStates[joint.parentIndex].transform);
|
||||
startRadius = geometry.joints[joint.parentIndex].boneRadius * radiusScale;
|
||||
}
|
||||
// for now, use average of start and end radii
|
||||
float capsuleDistance;
|
||||
if (findRayCapsuleIntersection(relativeOrigin, direction, start, end,
|
||||
(startRadius + endRadius) / 2.0f, capsuleDistance)) {
|
||||
minDistance = qMin(minDistance, capsuleDistance);
|
||||
}
|
||||
}
|
||||
if (minDistance < FLT_MAX) {
|
||||
distance = minDistance;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Model::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
glm::vec3& penetration, float boneScale, int skipIndex) const {
|
||||
const glm::vec3 relativeCenter = penetratorCenter - _translation;
|
||||
|
|
|
@ -125,6 +125,8 @@ public:
|
|||
/// Returns the average color of all meshes in the geometry.
|
||||
glm::vec4 computeAverageColor() const;
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
glm::vec3& penetration, float boneScale = 1.0f, int skipIndex = -1) const;
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
|
||||
#include "UrlReader.h"
|
||||
#include "AngleUtil.h"
|
||||
#include "Radix2InplaceSort.h"
|
||||
#include "Radix2IntegerScanner.h"
|
||||
|
|
|
@ -1,183 +0,0 @@
|
|||
//
|
||||
// starfield/Loader.h
|
||||
// interface
|
||||
//
|
||||
// Created by Tobias Schwinger on 3/29/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __interface__starfield__Loader__
|
||||
#define __interface__starfield__Loader__
|
||||
|
||||
#ifndef __interface__Starfield_impl__
|
||||
#error "This is an implementation file - not intended for direct inclusion."
|
||||
#endif
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#include "starfield/data/InputVertex.h"
|
||||
#include "starfield/data/BrightnessLevel.h"
|
||||
|
||||
namespace starfield {
|
||||
|
||||
class Loader : UrlReader {
|
||||
public:
|
||||
|
||||
bool loadVertices(
|
||||
InputVertices& destination, char const* url, char const* cacheFile, unsigned limit)
|
||||
{
|
||||
_vertices = & destination;
|
||||
_limit = limit;
|
||||
#if STARFIELD_SAVE_MEMORY
|
||||
if (_limit == 0 || _limit > 60000u)
|
||||
_limit = 60000u;
|
||||
#endif
|
||||
_urlStr = url; // in case we fail early
|
||||
|
||||
if (! UrlReader::readUrl(url, *this, cacheFile))
|
||||
{
|
||||
qDebug("%s:%d: %s\n",
|
||||
_urlStr, _lineNo, getError());
|
||||
|
||||
return false;
|
||||
}
|
||||
qDebug("Loaded %u stars.\n", _recordsRead);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class UrlReader;
|
||||
|
||||
void begin(char const* url,
|
||||
char const* type,
|
||||
int64_t size,
|
||||
int64_t stardate) {
|
||||
|
||||
_lineNo = 0u;
|
||||
_urlStr = url; // new value in http redirect
|
||||
|
||||
_recordsRead = 0u;
|
||||
|
||||
_vertices->clear();
|
||||
_vertices->reserve(_limit);
|
||||
// qDebug("Stars.cpp: loader begin %s\n", url);
|
||||
}
|
||||
|
||||
size_t transfer(char* input, size_t bytes) {
|
||||
|
||||
size_t consumed = 0u;
|
||||
char const* end = input + bytes;
|
||||
char* line, * next = input;
|
||||
|
||||
for (;;) {
|
||||
|
||||
// advance to next line
|
||||
for (; next != end && isspace(*next); ++next);
|
||||
consumed = next - input;
|
||||
line = next;
|
||||
++_lineNo;
|
||||
for (; next != end && *next != '\n' && *next != '\r'; ++next);
|
||||
if (next == end)
|
||||
return consumed;
|
||||
*next++ = '\0';
|
||||
|
||||
// skip comments
|
||||
if (*line == '\\' || *line == '/' || *line == ';')
|
||||
continue;
|
||||
|
||||
// parse
|
||||
float azi, alt;
|
||||
unsigned c;
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
if (sscanf(line, " %f %f #%x", & azi, & alt, & c) == 3) {
|
||||
|
||||
if (spaceFor( getBrightness(c) )) {
|
||||
|
||||
storeVertex(azi, alt, c);
|
||||
}
|
||||
|
||||
++_recordsRead;
|
||||
|
||||
} else {
|
||||
|
||||
qDebug("Stars.cpp:%d: Bad input from %s\n",
|
||||
_lineNo, _urlStr);
|
||||
}
|
||||
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void end(bool ok)
|
||||
{ }
|
||||
|
||||
private:
|
||||
|
||||
bool atLimit() { return _limit > 0u && _recordsRead >= _limit; }
|
||||
|
||||
bool spaceFor(BrightnessLevel b) {
|
||||
|
||||
if (! atLimit()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// just reached the limit? -> establish a minimum heap and
|
||||
// remember the brightness at its top
|
||||
if (_recordsRead == _limit) {
|
||||
|
||||
// qDebug("Stars.cpp: vertex limit reached -> heap mode\n");
|
||||
|
||||
make_heap(
|
||||
_vertices->begin(), _vertices->end(),
|
||||
GreaterBrightness() );
|
||||
|
||||
_minBrightness = getBrightness(
|
||||
_vertices->begin()->getColor() );
|
||||
}
|
||||
|
||||
// not interested? say so
|
||||
if (_minBrightness >= b)
|
||||
return false;
|
||||
|
||||
// otherwise free up space for the new vertex
|
||||
pop_heap(
|
||||
_vertices->begin(), _vertices->end(),
|
||||
GreaterBrightness() );
|
||||
_vertices->pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
void storeVertex(float azi, float alt, unsigned color) {
|
||||
|
||||
_vertices->push_back(InputVertex(azi, alt, color));
|
||||
|
||||
if (atLimit()) {
|
||||
|
||||
push_heap(
|
||||
_vertices->begin(), _vertices->end(),
|
||||
GreaterBrightness() );
|
||||
|
||||
_minBrightness = getBrightness(
|
||||
_vertices->begin()->getColor() );
|
||||
}
|
||||
}
|
||||
|
||||
// variables
|
||||
|
||||
InputVertices* _vertices;
|
||||
unsigned _limit;
|
||||
|
||||
unsigned _lineNo;
|
||||
char const* _urlStr;
|
||||
|
||||
unsigned _recordsRead;
|
||||
BrightnessLevel _minBrightness;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
#endif
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "AbstractLoggerInterface.h"
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ void AudioInjector::injectAudio() {
|
|||
currentPacketPosition += rfcSessionUUID.size();
|
||||
|
||||
// pick a random UUID to use for this stream
|
||||
QUuid randomStreamUUID;
|
||||
QUuid randomStreamUUID = QUuid::createUuid();
|
||||
QByteArray rfcStreamUUID = randomStreamUUID.toRfc4122();
|
||||
memcpy(currentPacketPosition, rfcStreamUUID, rfcStreamUUID.size());
|
||||
currentPacketPosition += rfcStreamUUID.size();
|
||||
|
|
|
@ -27,5 +27,25 @@ Sound::Sound(const QUrl& sampleURL, QObject* parent) :
|
|||
|
||||
void Sound::replyFinished(QNetworkReply* reply) {
|
||||
// replace our byte array with the downloaded data
|
||||
_byteArray = reply->readAll();
|
||||
QByteArray rawAudioByteArray = reply->readAll();
|
||||
|
||||
// assume that this was a RAW file and is now an array of samples that are
|
||||
// signed, 16-bit, 48Khz, mono
|
||||
|
||||
// we want to convert it to the format that the audio-mixer wants
|
||||
// which is signed, 16-bit, 24Khz, mono
|
||||
|
||||
_byteArray.resize(rawAudioByteArray.size() / 2);
|
||||
|
||||
int numSourceSamples = rawAudioByteArray.size() / sizeof(int16_t);
|
||||
int16_t* sourceSamples = (int16_t*) rawAudioByteArray.data();
|
||||
int16_t* destinationSamples = (int16_t*) _byteArray.data();
|
||||
|
||||
for (int i = 1; i < numSourceSamples; i += 2) {
|
||||
if (i + 1 >= numSourceSamples) {
|
||||
destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] / 2) + (sourceSamples[i] / 2);
|
||||
} else {
|
||||
destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] / 4) + (sourceSamples[i] / 2) + (sourceSamples[i + 1] / 4);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -295,3 +295,13 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) {
|
|||
|
||||
return sourceBuffer - startPosition;
|
||||
}
|
||||
|
||||
void AvatarData::setNewScale(float newScale) {
|
||||
if (newScale > MAX_SCALE) {
|
||||
newScale = MAX_SCALE;
|
||||
} else if (newScale < MIN_SCALE) {
|
||||
newScale = MIN_SCALE;
|
||||
}
|
||||
_newScale = newScale;
|
||||
qDebug() << "Changed scale to " << _newScale << "\n";
|
||||
}
|
||||
|
|
|
@ -32,6 +32,9 @@ const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits
|
|||
const int IS_FACESHIFT_CONNECTED = 4; // 5th bit
|
||||
const int IS_CHAT_CIRCLING_ENABLED = 5;
|
||||
|
||||
static const float MAX_SCALE = 1000.f;
|
||||
static const float MIN_SCALE = .005f;
|
||||
|
||||
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
|
||||
|
||||
enum KeyState
|
||||
|
@ -80,7 +83,7 @@ public:
|
|||
|
||||
// Scale
|
||||
float getNewScale() const { return _newScale; }
|
||||
void setNewScale(float newScale) { _newScale = newScale; }
|
||||
void setNewScale(float);
|
||||
|
||||
// Hand State
|
||||
void setHandState(char s) { _handState = s; }
|
||||
|
@ -115,7 +118,7 @@ public:
|
|||
/// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
|
||||
/// \return whether or not the sphere penetrated
|
||||
virtual bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
glm::vec3& penetration, int skeletonSkipIndex = -1) { return false; }
|
||||
glm::vec3& penetration, int skeletonSkipIndex = -1) const { return false; }
|
||||
|
||||
protected:
|
||||
QUuid _uuid;
|
||||
|
|
|
@ -13,6 +13,9 @@ find_package(Qt5Widgets REQUIRED)
|
|||
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
|
||||
setup_hifi_library(${TARGET_NAME})
|
||||
|
||||
include(${MACRO_DIR}/AutoMTC.cmake)
|
||||
auto_mtc(${TARGET_NAME} ${ROOT_DIR})
|
||||
|
||||
qt5_use_modules(${TARGET_NAME} Widgets Script)
|
||||
|
||||
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||
|
|
|
@ -11,7 +11,12 @@
|
|||
#include "AttributeRegistry.h"
|
||||
#include "MetavoxelData.h"
|
||||
|
||||
AttributeRegistry AttributeRegistry::_instance;
|
||||
REGISTER_META_OBJECT(QRgbAttribute)
|
||||
|
||||
AttributeRegistry* AttributeRegistry::getInstance() {
|
||||
static AttributeRegistry registry;
|
||||
return ®istry;
|
||||
}
|
||||
|
||||
AttributeRegistry::AttributeRegistry() :
|
||||
_guideAttribute(registerAttribute(new PolymorphicAttribute("guide", PolymorphicDataPointer(new DefaultMetavoxelGuide())))),
|
||||
|
@ -36,7 +41,7 @@ AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute
|
|||
}
|
||||
|
||||
QScriptValue AttributeRegistry::getAttribute(QScriptContext* context, QScriptEngine* engine) {
|
||||
return engine->newQObject(_instance.getAttribute(context->argument(0).toString()).data(), QScriptEngine::QtOwnership,
|
||||
return engine->newQObject(getInstance()->getAttribute(context->argument(0).toString()).data(), QScriptEngine::QtOwnership,
|
||||
QScriptEngine::PreferExistingWrapperObject);
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class AttributeRegistry {
|
|||
public:
|
||||
|
||||
/// Returns a pointer to the singleton registry instance.
|
||||
static AttributeRegistry* getInstance() { return &_instance; }
|
||||
static AttributeRegistry* getInstance();
|
||||
|
||||
AttributeRegistry();
|
||||
|
||||
|
@ -65,8 +65,6 @@ private:
|
|||
|
||||
static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
static AttributeRegistry _instance;
|
||||
|
||||
QHash<QString, AttributePointer> _attributes;
|
||||
AttributePointer _guideAttribute;
|
||||
AttributePointer _colorAttribute;
|
||||
|
@ -140,8 +138,11 @@ public:
|
|||
virtual void* create(void* copy) const = 0;
|
||||
virtual void destroy(void* value) const = 0;
|
||||
|
||||
virtual bool read(Bitstream& in, void*& value) const = 0;
|
||||
virtual bool write(Bitstream& out, void* value) const = 0;
|
||||
virtual void read(Bitstream& in, void*& value, bool isLeaf) const = 0;
|
||||
virtual void write(Bitstream& out, void* value, bool isLeaf) const = 0;
|
||||
|
||||
virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { read(in, value, isLeaf); }
|
||||
virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { write(out, value, isLeaf); }
|
||||
|
||||
virtual bool equal(void* first, void* second) const = 0;
|
||||
|
||||
|
@ -163,18 +164,31 @@ public:
|
|||
virtual void* create(void* copy) const { void* value; new (&value) T(*(T*)©); return value; }
|
||||
virtual void destroy(void* value) const { ((T*)&value)->~T(); }
|
||||
|
||||
virtual bool read(Bitstream& in, void*& value) const { value = getDefaultValue(); in.read(&value, bits); return false; }
|
||||
virtual bool write(Bitstream& out, void* value) const { out.write(&value, bits); return false; }
|
||||
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
|
||||
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
|
||||
|
||||
virtual bool equal(void* first, void* second) const { return decodeInline<T>(first) == decodeInline<T>(second); }
|
||||
|
||||
virtual void* getDefaultValue() const { return encodeInline(_defaultValue); }
|
||||
|
||||
private:
|
||||
protected:
|
||||
|
||||
T _defaultValue;
|
||||
};
|
||||
|
||||
template<class T, int bits> inline void InlineAttribute<T, bits>::read(Bitstream& in, void*& value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
value = getDefaultValue();
|
||||
in.read(&value, bits);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T, int bits> inline void InlineAttribute<T, bits>::write(Bitstream& out, void* value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
out.write(&value, bits);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides merging using the =, ==, += and /= operators.
|
||||
template<class T, int bits = 32> class SimpleInlineAttribute : public InlineAttribute<T, bits> {
|
||||
public:
|
||||
|
@ -198,9 +212,12 @@ template<class T, int bits> inline bool SimpleInlineAttribute<T, bits>::merge(vo
|
|||
|
||||
/// Provides appropriate averaging for RGBA values.
|
||||
class QRgbAttribute : public InlineAttribute<QRgb> {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int defaultValue MEMBER _defaultValue)
|
||||
|
||||
public:
|
||||
|
||||
QRgbAttribute(const QString& name, QRgb defaultValue = QRgb());
|
||||
Q_INVOKABLE QRgbAttribute(const QString& name = QString(), QRgb defaultValue = QRgb());
|
||||
|
||||
virtual bool merge(void*& parent, void* children[]) const;
|
||||
|
||||
|
@ -216,8 +233,8 @@ public:
|
|||
virtual void* create(void* copy) const { new T(*static_cast<T*>(copy)); }
|
||||
virtual void destroy(void* value) const { delete static_cast<T*>(value); }
|
||||
|
||||
virtual bool read(Bitstream& in, void*& value) const { in >> *static_cast<T*>(value); return true; }
|
||||
virtual bool write(Bitstream& out, void* value) const { out << *static_cast<T*>(value); return true; }
|
||||
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
|
||||
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
|
||||
|
||||
virtual bool equal(void* first, void* second) const { return *static_cast<T*>(first) == *static_cast<T*>(second); }
|
||||
|
||||
|
@ -228,6 +245,18 @@ private:
|
|||
T _defaultValue;
|
||||
};
|
||||
|
||||
template<class T> inline void PointerAttribute<T>::read(Bitstream& in, void*& value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
in.read(value, sizeof(T) * 8);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline void PointerAttribute<T>::write(Bitstream& out, void* value, bool isLeaf) const {
|
||||
if (isLeaf) {
|
||||
out.write(value, sizeof(T) * 8);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides merging using the =, ==, += and /= operators.
|
||||
template<class T> class SimplePointerAttribute : public PointerAttribute<T> {
|
||||
public:
|
||||
|
|
|
@ -6,12 +6,68 @@
|
|||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QDataStream>
|
||||
#include <cstring>
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QMetaProperty>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "AttributeRegistry.h"
|
||||
#include "Bitstream.h"
|
||||
|
||||
Bitstream::Bitstream(QDataStream& underlying)
|
||||
: _underlying(underlying), _byte(0), _position(0) {
|
||||
REGISTER_SIMPLE_TYPE_STREAMER(QByteArray)
|
||||
REGISTER_SIMPLE_TYPE_STREAMER(QString)
|
||||
REGISTER_SIMPLE_TYPE_STREAMER(QVariantList)
|
||||
REGISTER_SIMPLE_TYPE_STREAMER(bool)
|
||||
REGISTER_SIMPLE_TYPE_STREAMER(int)
|
||||
|
||||
IDStreamer::IDStreamer(Bitstream& stream) :
|
||||
_stream(stream),
|
||||
_bits(1) {
|
||||
}
|
||||
|
||||
void IDStreamer::setBitsFromValue(int value) {
|
||||
_bits = 1;
|
||||
while (value >= (1 << _bits) - 1) {
|
||||
_bits++;
|
||||
}
|
||||
}
|
||||
|
||||
IDStreamer& IDStreamer::operator<<(int value) {
|
||||
_stream.write(&value, _bits);
|
||||
if (value == (1 << _bits) - 1) {
|
||||
_bits++;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
IDStreamer& IDStreamer::operator>>(int& value) {
|
||||
value = 0;
|
||||
_stream.read(&value, _bits);
|
||||
if (value == (1 << _bits) - 1) {
|
||||
_bits++;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
int Bitstream::registerMetaObject(const char* className, const QMetaObject* metaObject) {
|
||||
getMetaObjects().insert(className, metaObject);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Bitstream::registerTypeStreamer(int type, TypeStreamer* streamer) {
|
||||
streamer->setType(type);
|
||||
getTypeStreamers().insert(type, streamer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Bitstream::Bitstream(QDataStream& underlying) :
|
||||
_underlying(underlying),
|
||||
_byte(0),
|
||||
_position(0),
|
||||
_metaObjectStreamer(*this),
|
||||
_typeStreamerStreamer(*this),
|
||||
_attributeStreamer(*this) {
|
||||
}
|
||||
|
||||
const int BITS_IN_BYTE = 8;
|
||||
|
@ -41,7 +97,8 @@ Bitstream& Bitstream::read(void* data, int bits, int offset) {
|
|||
_underlying >> _byte;
|
||||
}
|
||||
int bitsToRead = qMin(BITS_IN_BYTE - _position, qMin(BITS_IN_BYTE - offset, bits));
|
||||
*dest |= ((_byte >> _position) & ((1 << bitsToRead) - 1)) << offset;
|
||||
int mask = ((1 << bitsToRead) - 1) << offset;
|
||||
*dest = (*dest & ~mask) | (((_byte >> _position) << offset) & mask);
|
||||
_position = (_position + bitsToRead) & LAST_BIT_POSITION;
|
||||
if ((offset += bitsToRead) == BITS_IN_BYTE) {
|
||||
dest++;
|
||||
|
@ -55,11 +112,40 @@ Bitstream& Bitstream::read(void* data, int bits, int offset) {
|
|||
void Bitstream::flush() {
|
||||
if (_position != 0) {
|
||||
_underlying << _byte;
|
||||
_byte = 0;
|
||||
_position = 0;
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Bitstream::reset() {
|
||||
_byte = 0;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
Bitstream::WriteMappings Bitstream::getAndResetWriteMappings() {
|
||||
WriteMappings mappings = { _metaObjectStreamer.getAndResetTransientOffsets(),
|
||||
_typeStreamerStreamer.getAndResetTransientOffsets(),
|
||||
_attributeStreamer.getAndResetTransientOffsets() };
|
||||
return mappings;
|
||||
}
|
||||
|
||||
void Bitstream::persistWriteMappings(const WriteMappings& mappings) {
|
||||
_metaObjectStreamer.persistTransientOffsets(mappings.metaObjectOffsets);
|
||||
_typeStreamerStreamer.persistTransientOffsets(mappings.typeStreamerOffsets);
|
||||
_attributeStreamer.persistTransientOffsets(mappings.attributeOffsets);
|
||||
}
|
||||
|
||||
Bitstream::ReadMappings Bitstream::getAndResetReadMappings() {
|
||||
ReadMappings mappings = { _metaObjectStreamer.getAndResetTransientValues(),
|
||||
_typeStreamerStreamer.getAndResetTransientValues(),
|
||||
_attributeStreamer.getAndResetTransientValues() };
|
||||
return mappings;
|
||||
}
|
||||
|
||||
void Bitstream::persistReadMappings(const ReadMappings& mappings) {
|
||||
_metaObjectStreamer.persistTransientValues(mappings.metaObjectValues);
|
||||
_typeStreamerStreamer.persistTransientValues(mappings.typeStreamerValues);
|
||||
_attributeStreamer.persistTransientValues(mappings.attributeValues);
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(bool value) {
|
||||
if (value) {
|
||||
|
@ -79,3 +165,171 @@ Bitstream& Bitstream::operator>>(bool& value) {
|
|||
_position = (_position + 1) & LAST_BIT_POSITION;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(int value) {
|
||||
return write(&value, 32);
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(int& value) {
|
||||
qint32 sizedValue;
|
||||
read(&sizedValue, 32);
|
||||
value = sizedValue;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(float value) {
|
||||
return write(&value, 32);
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(float& value) {
|
||||
return read(&value, 32);
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(const glm::vec3& value) {
|
||||
return *this << value.x << value.y << value.z;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(glm::vec3& value) {
|
||||
return *this >> value.x >> value.y >> value.z;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(const QByteArray& string) {
|
||||
*this << string.size();
|
||||
return write(string.constData(), string.size() * BITS_IN_BYTE);
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(QByteArray& string) {
|
||||
int size;
|
||||
*this >> size;
|
||||
string.resize(size);
|
||||
return read(string.data(), size * BITS_IN_BYTE);
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(const QString& string) {
|
||||
*this << string.size();
|
||||
return write(string.constData(), string.size() * sizeof(QChar) * BITS_IN_BYTE);
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(QString& string) {
|
||||
int size;
|
||||
*this >> size;
|
||||
string.resize(size);
|
||||
return read(string.data(), size * sizeof(QChar) * BITS_IN_BYTE);
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(const QVariant& value) {
|
||||
const TypeStreamer* streamer = getTypeStreamers().value(value.userType());
|
||||
if (streamer) {
|
||||
_typeStreamerStreamer << streamer;
|
||||
streamer->write(*this, value);
|
||||
} else {
|
||||
qWarning() << "Non-streamable type: " << value.typeName() << "\n";
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(QVariant& value) {
|
||||
const TypeStreamer* streamer;
|
||||
_typeStreamerStreamer >> streamer;
|
||||
if (streamer) {
|
||||
value = streamer->read(*this);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(const QObject* object) {
|
||||
if (!object) {
|
||||
_metaObjectStreamer << NULL;
|
||||
return *this;
|
||||
}
|
||||
const QMetaObject* metaObject = object->metaObject();
|
||||
_metaObjectStreamer << metaObject;
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (!property.isStored(object)) {
|
||||
continue;
|
||||
}
|
||||
const TypeStreamer* streamer = getTypeStreamers().value(property.userType());
|
||||
if (streamer) {
|
||||
streamer->write(*this, property.read(object));
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(QObject*& object) {
|
||||
const QMetaObject* metaObject;
|
||||
_metaObjectStreamer >> metaObject;
|
||||
if (!metaObject) {
|
||||
object = NULL;
|
||||
return *this;
|
||||
}
|
||||
object = metaObject->newInstance();
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (!property.isStored(object)) {
|
||||
continue;
|
||||
}
|
||||
const TypeStreamer* streamer = getTypeStreamers().value(property.userType());
|
||||
if (streamer) {
|
||||
property.write(object, streamer->read(*this));
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(const QMetaObject* metaObject) {
|
||||
return *this << (metaObject ? QByteArray::fromRawData(
|
||||
metaObject->className(), strlen(metaObject->className())) : QByteArray());
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(const QMetaObject*& metaObject) {
|
||||
QByteArray className;
|
||||
*this >> className;
|
||||
if (className.isEmpty()) {
|
||||
metaObject = NULL;
|
||||
return *this;
|
||||
}
|
||||
metaObject = getMetaObjects().value(className);
|
||||
if (!metaObject) {
|
||||
qWarning() << "Unknown class name: " << className << "\n";
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(const TypeStreamer* streamer) {
|
||||
const char* typeName = QMetaType::typeName(streamer->getType());
|
||||
return *this << QByteArray::fromRawData(typeName, strlen(typeName));
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(const TypeStreamer*& streamer) {
|
||||
QByteArray typeName;
|
||||
*this >> typeName;
|
||||
streamer = getTypeStreamers().value(QMetaType::type(typeName.constData()));
|
||||
if (!streamer) {
|
||||
qWarning() << "Unknown type name: " << typeName << "\n";
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator<<(const AttributePointer& attribute) {
|
||||
return *this << (QObject*)attribute.data();
|
||||
}
|
||||
|
||||
Bitstream& Bitstream::operator>>(AttributePointer& attribute) {
|
||||
QObject* object;
|
||||
*this >> object;
|
||||
attribute = AttributeRegistry::getInstance()->registerAttribute(static_cast<Attribute*>(object));
|
||||
return *this;
|
||||
}
|
||||
|
||||
QHash<QByteArray, const QMetaObject*>& Bitstream::getMetaObjects() {
|
||||
static QHash<QByteArray, const QMetaObject*> metaObjects;
|
||||
return metaObjects;
|
||||
}
|
||||
|
||||
QHash<int, const TypeStreamer*>& Bitstream::getTypeStreamers() {
|
||||
static QHash<int, const TypeStreamer*> typeStreamers;
|
||||
return typeStreamers;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,14 +9,174 @@
|
|||
#ifndef __interface__Bitstream__
|
||||
#define __interface__Bitstream__
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QHash>
|
||||
#include <QMetaType>
|
||||
#include <QSharedPointer>
|
||||
#include <QVariant>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class QByteArray;
|
||||
class QDataStream;
|
||||
struct QMetaObject;
|
||||
class QObject;
|
||||
|
||||
class Attribute;
|
||||
class Bitstream;
|
||||
class TypeStreamer;
|
||||
|
||||
typedef QSharedPointer<Attribute> AttributePointer;
|
||||
|
||||
/// Streams integer identifiers that conform to the following pattern: each ID encountered in the stream is either one that
|
||||
/// has been sent (received) before, or is one more than the highest previously encountered ID (starting at zero). This allows
|
||||
/// us to use the minimum number of bits to encode the IDs.
|
||||
class IDStreamer {
|
||||
public:
|
||||
|
||||
IDStreamer(Bitstream& stream);
|
||||
|
||||
void setBitsFromValue(int value);
|
||||
|
||||
IDStreamer& operator<<(int value);
|
||||
IDStreamer& operator>>(int& value);
|
||||
|
||||
private:
|
||||
|
||||
Bitstream& _stream;
|
||||
int _bits;
|
||||
};
|
||||
|
||||
/// Provides a means to stream repeated values efficiently. The value is first streamed along with a unique ID. When
|
||||
/// subsequently streamed, only the ID is sent.
|
||||
template<class T> class RepeatedValueStreamer {
|
||||
public:
|
||||
|
||||
RepeatedValueStreamer(Bitstream& stream) : _stream(stream), _idStreamer(stream),
|
||||
_lastPersistentID(0), _lastTransientOffset(0) { }
|
||||
|
||||
QHash<T, int> getAndResetTransientOffsets();
|
||||
|
||||
void persistTransientOffsets(const QHash<T, int>& transientOffsets);
|
||||
|
||||
QHash<int, T> getAndResetTransientValues();
|
||||
|
||||
void persistTransientValues(const QHash<int, T>& transientValues);
|
||||
|
||||
RepeatedValueStreamer& operator<<(T value);
|
||||
RepeatedValueStreamer& operator>>(T& value);
|
||||
|
||||
private:
|
||||
|
||||
Bitstream& _stream;
|
||||
IDStreamer _idStreamer;
|
||||
int _lastPersistentID;
|
||||
int _lastTransientOffset;
|
||||
QHash<T, int> _persistentIDs;
|
||||
QHash<T, int> _transientOffsets;
|
||||
QHash<int, T> _persistentValues;
|
||||
QHash<int, T> _transientValues;
|
||||
};
|
||||
|
||||
template<class T> inline QHash<T, int> RepeatedValueStreamer<T>::getAndResetTransientOffsets() {
|
||||
QHash<T, int> transientOffsets;
|
||||
_transientOffsets.swap(transientOffsets);
|
||||
_lastTransientOffset = 0;
|
||||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
return transientOffsets;
|
||||
}
|
||||
|
||||
template<class T> inline void RepeatedValueStreamer<T>::persistTransientOffsets(const QHash<T, int>& transientOffsets) {
|
||||
int oldLastPersistentID = _lastPersistentID;
|
||||
for (typename QHash<T, int>::const_iterator it = transientOffsets.constBegin(); it != transientOffsets.constEnd(); it++) {
|
||||
int id = oldLastPersistentID + it.value();
|
||||
_lastPersistentID = qMax(_lastPersistentID, id);
|
||||
_persistentIDs.insert(it.key(), id);
|
||||
}
|
||||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
}
|
||||
|
||||
template<class T> inline QHash<int, T> RepeatedValueStreamer<T>::getAndResetTransientValues() {
|
||||
QHash<int, T> transientValues;
|
||||
_transientValues.swap(transientValues);
|
||||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
return transientValues;
|
||||
}
|
||||
|
||||
template<class T> inline void RepeatedValueStreamer<T>::persistTransientValues(const QHash<int, T>& transientValues) {
|
||||
int oldLastPersistentID = _lastPersistentID;
|
||||
for (typename QHash<int, T>::const_iterator it = transientValues.constBegin(); it != transientValues.constEnd(); it++) {
|
||||
int id = oldLastPersistentID + it.key();
|
||||
_lastPersistentID = qMax(_lastPersistentID, id);
|
||||
_persistentValues.insert(id, it.value());
|
||||
}
|
||||
_idStreamer.setBitsFromValue(_lastPersistentID);
|
||||
}
|
||||
|
||||
template<class T> inline RepeatedValueStreamer<T>& RepeatedValueStreamer<T>::operator<<(T value) {
|
||||
int id = _persistentIDs.value(value);
|
||||
if (id == 0) {
|
||||
int& offset = _transientOffsets[value];
|
||||
if (offset == 0) {
|
||||
_idStreamer << (_lastPersistentID + (offset = ++_lastTransientOffset));
|
||||
_stream << value;
|
||||
|
||||
} else {
|
||||
_idStreamer << (_lastPersistentID + offset);
|
||||
}
|
||||
} else {
|
||||
_idStreamer << id;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T> inline RepeatedValueStreamer<T>& RepeatedValueStreamer<T>::operator>>(T& value) {
|
||||
int id;
|
||||
_idStreamer >> id;
|
||||
if (id <= _lastPersistentID) {
|
||||
value = _persistentValues.value(id);
|
||||
|
||||
} else {
|
||||
int offset = id - _lastPersistentID;
|
||||
typename QHash<int, T>::iterator it = _transientValues.find(offset);
|
||||
if (it == _transientValues.end()) {
|
||||
_stream >> value;
|
||||
_transientValues.insert(offset, value);
|
||||
|
||||
} else {
|
||||
value = *it;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// A stream for bit-aligned data.
|
||||
class Bitstream {
|
||||
public:
|
||||
|
||||
class WriteMappings {
|
||||
public:
|
||||
QHash<const QMetaObject*, int> metaObjectOffsets;
|
||||
QHash<const TypeStreamer*, int> typeStreamerOffsets;
|
||||
QHash<AttributePointer, int> attributeOffsets;
|
||||
};
|
||||
|
||||
class ReadMappings {
|
||||
public:
|
||||
QHash<int, const QMetaObject*> metaObjectValues;
|
||||
QHash<int, const TypeStreamer*> typeStreamerValues;
|
||||
QHash<int, AttributePointer> attributeValues;
|
||||
};
|
||||
|
||||
/// Registers a metaobject under its name so that instances of it can be streamed.
|
||||
/// \return zero; the function only returns a value so that it can be used in static initialization
|
||||
static int registerMetaObject(const char* className, const QMetaObject* metaObject);
|
||||
|
||||
/// Registers a streamer for the specified Qt-registered type.
|
||||
/// \return zero; the function only returns a value so that it can be used in static initialization
|
||||
static int registerTypeStreamer(int type, TypeStreamer* streamer);
|
||||
|
||||
/// Creates a new bitstream. Note: the stream may be used for reading or writing, but not both.
|
||||
Bitstream(QDataStream& underlying);
|
||||
|
||||
/// Writes a set of bits to the underlying stream.
|
||||
|
@ -32,14 +192,145 @@ public:
|
|||
/// Flushes any unwritten bits to the underlying stream.
|
||||
void flush();
|
||||
|
||||
/// Resets to the initial state.
|
||||
void reset();
|
||||
|
||||
/// Returns a reference to the attribute streamer.
|
||||
RepeatedValueStreamer<AttributePointer>& getAttributeStreamer() { return _attributeStreamer; }
|
||||
|
||||
/// Returns the set of transient mappings gathered during writing and resets them.
|
||||
WriteMappings getAndResetWriteMappings();
|
||||
|
||||
/// Persists a set of write mappings recorded earlier.
|
||||
void persistWriteMappings(const WriteMappings& mappings);
|
||||
|
||||
/// Returns the set of transient mappings gathered during reading and resets them.
|
||||
ReadMappings getAndResetReadMappings();
|
||||
|
||||
/// Persists a set of read mappings recorded earlier.
|
||||
void persistReadMappings(const ReadMappings& mappings);
|
||||
|
||||
Bitstream& operator<<(bool value);
|
||||
Bitstream& operator>>(bool& value);
|
||||
|
||||
Bitstream& operator<<(int value);
|
||||
Bitstream& operator>>(int& value);
|
||||
|
||||
Bitstream& operator<<(float value);
|
||||
Bitstream& operator>>(float& value);
|
||||
|
||||
Bitstream& operator<<(const glm::vec3& value);
|
||||
Bitstream& operator>>(glm::vec3& value);
|
||||
|
||||
Bitstream& operator<<(const QByteArray& string);
|
||||
Bitstream& operator>>(QByteArray& string);
|
||||
|
||||
Bitstream& operator<<(const QString& string);
|
||||
Bitstream& operator>>(QString& string);
|
||||
|
||||
Bitstream& operator<<(const QVariant& value);
|
||||
Bitstream& operator>>(QVariant& value);
|
||||
|
||||
template<class T> Bitstream& operator<<(const QList<T>& list);
|
||||
template<class T> Bitstream& operator>>(QList<T>& list);
|
||||
|
||||
Bitstream& operator<<(const QObject* object);
|
||||
Bitstream& operator>>(QObject*& object);
|
||||
|
||||
Bitstream& operator<<(const QMetaObject* metaObject);
|
||||
Bitstream& operator>>(const QMetaObject*& metaObject);
|
||||
|
||||
Bitstream& operator<<(const TypeStreamer* streamer);
|
||||
Bitstream& operator>>(const TypeStreamer*& streamer);
|
||||
|
||||
Bitstream& operator<<(const AttributePointer& attribute);
|
||||
Bitstream& operator>>(AttributePointer& attribute);
|
||||
|
||||
private:
|
||||
|
||||
QDataStream& _underlying;
|
||||
quint8 _byte;
|
||||
int _position;
|
||||
|
||||
RepeatedValueStreamer<const QMetaObject*> _metaObjectStreamer;
|
||||
RepeatedValueStreamer<const TypeStreamer*> _typeStreamerStreamer;
|
||||
RepeatedValueStreamer<AttributePointer> _attributeStreamer;
|
||||
|
||||
static QHash<QByteArray, const QMetaObject*>& getMetaObjects();
|
||||
static QHash<int, const TypeStreamer*>& getTypeStreamers();
|
||||
};
|
||||
|
||||
template<class T> inline Bitstream& Bitstream::operator<<(const QList<T>& list) {
|
||||
*this << list.size();
|
||||
foreach (const T& entry, list) {
|
||||
*this << entry;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class T> inline Bitstream& Bitstream::operator>>(QList<T>& list) {
|
||||
int size;
|
||||
*this >> size;
|
||||
list.clear();
|
||||
list.reserve(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
T entry;
|
||||
*this >> entry;
|
||||
list.append(entry);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Macro for registering streamable meta-objects.
|
||||
#define REGISTER_META_OBJECT(x) static int x##Registration = Bitstream::registerMetaObject(#x, &x::staticMetaObject);
|
||||
|
||||
/// Interface for objects that can write values to and read values from bitstreams.
|
||||
class TypeStreamer {
|
||||
public:
|
||||
|
||||
void setType(int type) { _type = type; }
|
||||
int getType() const { return _type; }
|
||||
|
||||
virtual void write(Bitstream& out, const QVariant& value) const = 0;
|
||||
virtual QVariant read(Bitstream& in) const = 0;
|
||||
|
||||
private:
|
||||
|
||||
int _type;
|
||||
};
|
||||
|
||||
/// A streamer that works with Bitstream's operators.
|
||||
template<class T> class SimpleTypeStreamer : public TypeStreamer {
|
||||
public:
|
||||
|
||||
virtual void write(Bitstream& out, const QVariant& value) const { out << value.value<T>(); }
|
||||
virtual QVariant read(Bitstream& in) const { T value; in >> value; return QVariant::fromValue(value); }
|
||||
};
|
||||
|
||||
/// Macro for registering simple type streamers.
|
||||
#define REGISTER_SIMPLE_TYPE_STREAMER(x) static int x##Streamer = \
|
||||
Bitstream::registerTypeStreamer(QMetaType::type(#x), new SimpleTypeStreamer<x>());
|
||||
|
||||
/// Declares the metatype and the streaming operators. The last lines
|
||||
/// ensure that the generated file will be included in the link phase.
|
||||
#define STRINGIFY(x) #x
|
||||
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
|
||||
Bitstream& operator<<(Bitstream& out, const X& obj); \
|
||||
Bitstream& operator>>(Bitstream& in, X& obj); \
|
||||
static const int* _TypePtr##X = &X::Type; \
|
||||
_Pragma(STRINGIFY(unused(_TypePtr##X)))
|
||||
|
||||
/// Registers a streamable type and its streamer.
|
||||
template<class T> int registerStreamableMetaType() {
|
||||
int type = qRegisterMetaType<T>();
|
||||
Bitstream::registerTypeStreamer(type, new SimpleTypeStreamer<T>());
|
||||
return type;
|
||||
}
|
||||
|
||||
/// Flags a class as streamable (use as you would Q_OBJECT).
|
||||
#define STREAMABLE public: static const int Type; private:
|
||||
|
||||
/// Flags a field or base class as streaming.
|
||||
#define STREAM
|
||||
|
||||
#endif /* defined(__interface__Bitstream__) */
|
||||
|
|
179
libraries/metavoxels/src/DatagramSequencer.cpp
Normal file
179
libraries/metavoxels/src/DatagramSequencer.cpp
Normal file
|
@ -0,0 +1,179 @@
|
|||
//
|
||||
// DatagramSequencer.cpp
|
||||
// metavoxels
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/20/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
#include "DatagramSequencer.h"
|
||||
|
||||
const int MAX_DATAGRAM_SIZE = 1500;
|
||||
|
||||
DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader) :
|
||||
_outgoingPacketStream(&_outgoingPacketData, QIODevice::WriteOnly),
|
||||
_outputStream(_outgoingPacketStream),
|
||||
_incomingDatagramStream(&_incomingDatagramBuffer),
|
||||
_datagramHeaderSize(datagramHeader.size()),
|
||||
_outgoingPacketNumber(0),
|
||||
_outgoingDatagram(MAX_DATAGRAM_SIZE, 0),
|
||||
_outgoingDatagramBuffer(&_outgoingDatagram),
|
||||
_outgoingDatagramStream(&_outgoingDatagramBuffer),
|
||||
_incomingPacketNumber(0),
|
||||
_incomingPacketStream(&_incomingPacketData, QIODevice::ReadOnly),
|
||||
_inputStream(_incomingPacketStream) {
|
||||
|
||||
_outgoingPacketStream.setByteOrder(QDataStream::LittleEndian);
|
||||
_incomingDatagramStream.setByteOrder(QDataStream::LittleEndian);
|
||||
_incomingPacketStream.setByteOrder(QDataStream::LittleEndian);
|
||||
_outgoingDatagramStream.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
memcpy(_outgoingDatagram.data(), datagramHeader.constData(), _datagramHeaderSize);
|
||||
}
|
||||
|
||||
/// Simple RAII-style object to keep a device open when in scope.
|
||||
class QIODeviceOpener {
|
||||
public:
|
||||
|
||||
QIODeviceOpener(QIODevice* device, QIODevice::OpenMode mode) : _device(device) { _device->open(mode); }
|
||||
~QIODeviceOpener() { _device->close(); }
|
||||
|
||||
private:
|
||||
|
||||
QIODevice* _device;
|
||||
};
|
||||
|
||||
Bitstream& DatagramSequencer::startPacket() {
|
||||
// start with the list of acknowledgements
|
||||
_outgoingPacketStream << (quint32)_receiveRecords.size();
|
||||
foreach (const ReceiveRecord& record, _receiveRecords) {
|
||||
_outgoingPacketStream << (quint32)record.packetNumber;
|
||||
}
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
void DatagramSequencer::endPacket() {
|
||||
_outputStream.flush();
|
||||
sendPacket(QByteArray::fromRawData(_outgoingPacketData.constData(), _outgoingPacketStream.device()->pos()));
|
||||
_outgoingPacketStream.device()->seek(0);
|
||||
}
|
||||
|
||||
void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
|
||||
_incomingDatagramBuffer.setData(datagram.constData() + _datagramHeaderSize, datagram.size() - _datagramHeaderSize);
|
||||
QIODeviceOpener opener(&_incomingDatagramBuffer, QIODevice::ReadOnly);
|
||||
|
||||
// read the sequence number
|
||||
quint32 sequenceNumber;
|
||||
_incomingDatagramStream >> sequenceNumber;
|
||||
|
||||
// if it's less than the last, ignore
|
||||
if (sequenceNumber < _incomingPacketNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
// read the size and offset
|
||||
quint32 packetSize, offset;
|
||||
_incomingDatagramStream >> packetSize >> offset;
|
||||
|
||||
// if it's greater, reset
|
||||
if (sequenceNumber > _incomingPacketNumber) {
|
||||
_incomingPacketNumber = sequenceNumber;
|
||||
_incomingPacketData.resize(packetSize);
|
||||
_offsetsReceived.clear();
|
||||
_offsetsReceived.insert(offset);
|
||||
_remainingBytes = packetSize;
|
||||
|
||||
} else {
|
||||
// make sure it's not a duplicate
|
||||
if (_offsetsReceived.contains(offset)) {
|
||||
return;
|
||||
}
|
||||
_offsetsReceived.insert(offset);
|
||||
}
|
||||
|
||||
// copy in the data
|
||||
memcpy(_incomingPacketData.data() + offset, _incomingDatagramBuffer.data().constData() + _incomingDatagramBuffer.pos(),
|
||||
_incomingDatagramBuffer.bytesAvailable());
|
||||
|
||||
// see if we're done
|
||||
if ((_remainingBytes -= _incomingDatagramBuffer.bytesAvailable()) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// read the list of acknowledged packets
|
||||
quint32 acknowledgementCount;
|
||||
_incomingPacketStream >> acknowledgementCount;
|
||||
for (int i = 0; i < acknowledgementCount; i++) {
|
||||
quint32 packetNumber;
|
||||
_incomingPacketStream >> packetNumber;
|
||||
if (_sendRecords.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
int index = packetNumber - _sendRecords.first().packetNumber;
|
||||
if (index < 0 || index >= _sendRecords.size()) {
|
||||
continue;
|
||||
}
|
||||
QList<SendRecord>::iterator it = _sendRecords.begin() + index;
|
||||
sendRecordAcknowledged(*it);
|
||||
emit sendAcknowledged(index);
|
||||
_sendRecords.erase(_sendRecords.begin(), it + 1);
|
||||
}
|
||||
|
||||
emit readyToRead(_inputStream);
|
||||
_incomingPacketStream.device()->seek(0);
|
||||
_inputStream.reset();
|
||||
|
||||
// record the receipt
|
||||
ReceiveRecord record = { _incomingPacketNumber, _inputStream.getAndResetReadMappings() };
|
||||
_receiveRecords.append(record);
|
||||
}
|
||||
|
||||
void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
|
||||
// stop acknowledging the recorded packets (TODO: replace with interpolation search?)
|
||||
ReceiveRecord compare = { record.lastReceivedPacketNumber };
|
||||
QList<ReceiveRecord>::iterator it = qBinaryFind(_receiveRecords.begin(), _receiveRecords.end(), compare);
|
||||
if (it != _receiveRecords.end()) {
|
||||
_inputStream.persistReadMappings(it->mappings);
|
||||
emit receiveAcknowledged(it - _receiveRecords.begin());
|
||||
_receiveRecords.erase(_receiveRecords.begin(), it + 1);
|
||||
}
|
||||
_outputStream.persistWriteMappings(record.mappings);
|
||||
}
|
||||
|
||||
void DatagramSequencer::sendPacket(const QByteArray& packet) {
|
||||
QIODeviceOpener opener(&_outgoingDatagramBuffer, QIODevice::WriteOnly);
|
||||
|
||||
// increment the packet number
|
||||
_outgoingPacketNumber++;
|
||||
|
||||
// record the send
|
||||
SendRecord record = { _outgoingPacketNumber, _receiveRecords.isEmpty() ? 0 : _receiveRecords.last().packetNumber,
|
||||
_outputStream.getAndResetWriteMappings() };
|
||||
_sendRecords.append(record);
|
||||
|
||||
// write the sequence number and size, which are the same between all fragments
|
||||
_outgoingDatagramBuffer.seek(_datagramHeaderSize);
|
||||
_outgoingDatagramStream << (quint32)_outgoingPacketNumber;
|
||||
_outgoingDatagramStream << (quint32)packet.size();
|
||||
int initialPosition = _outgoingDatagramBuffer.pos();
|
||||
|
||||
// break the packet into MTU-sized datagrams
|
||||
int offset = 0;
|
||||
do {
|
||||
_outgoingDatagramBuffer.seek(initialPosition);
|
||||
_outgoingDatagramStream << (quint32)offset;
|
||||
|
||||
int payloadSize = qMin((int)(_outgoingDatagram.size() - _outgoingDatagramBuffer.pos()), packet.size() - offset);
|
||||
memcpy(_outgoingDatagram.data() + _outgoingDatagramBuffer.pos(), packet.constData() + offset, payloadSize);
|
||||
|
||||
emit readyToWrite(QByteArray::fromRawData(_outgoingDatagram.constData(), _outgoingDatagramBuffer.pos() + payloadSize));
|
||||
|
||||
offset += payloadSize;
|
||||
|
||||
} while(offset < packet.size());
|
||||
}
|
||||
|
109
libraries/metavoxels/src/DatagramSequencer.h
Normal file
109
libraries/metavoxels/src/DatagramSequencer.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// DatagramSequencer.h
|
||||
// metavoxels
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/20/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __interface__DatagramSequencer__
|
||||
#define __interface__DatagramSequencer__
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDataStream>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
|
||||
#include "Bitstream.h"
|
||||
|
||||
/// Performs simple datagram sequencing, packet fragmentation and reassembly.
|
||||
class DatagramSequencer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
DatagramSequencer(const QByteArray& datagramHeader = QByteArray());
|
||||
|
||||
/// Returns the packet number of the last packet sent.
|
||||
int getOutgoingPacketNumber() const { return _outgoingPacketNumber; }
|
||||
|
||||
/// Returns the packet number of the last packet received (or the packet currently being assembled).
|
||||
int getIncomingPacketNumber() const { return _incomingPacketNumber; }
|
||||
|
||||
/// Starts a new packet for transmission.
|
||||
/// \return a reference to the Bitstream to use for writing to the packet
|
||||
Bitstream& startPacket();
|
||||
|
||||
/// Sends the packet currently being written.
|
||||
void endPacket();
|
||||
|
||||
/// Processes a datagram received from the other party, emitting readyToRead when the entire packet
|
||||
/// has been successfully assembled.
|
||||
void receivedDatagram(const QByteArray& datagram);
|
||||
|
||||
signals:
|
||||
|
||||
/// Emitted when a datagram is ready to be transmitted.
|
||||
void readyToWrite(const QByteArray& datagram);
|
||||
|
||||
/// Emitted when a packet is available to read.
|
||||
void readyToRead(Bitstream& input);
|
||||
|
||||
/// Emitted when a sent packet has been acknowledged by the remote side.
|
||||
/// \param index the index of the packet in our list of send records
|
||||
void sendAcknowledged(int index);
|
||||
|
||||
/// Emitted when our acknowledgement of a received packet has been acknowledged by the remote side.
|
||||
/// \param index the index of the packet in our list of receive records
|
||||
void receiveAcknowledged(int index);
|
||||
|
||||
private:
|
||||
|
||||
class SendRecord {
|
||||
public:
|
||||
int packetNumber;
|
||||
int lastReceivedPacketNumber;
|
||||
Bitstream::WriteMappings mappings;
|
||||
};
|
||||
|
||||
class ReceiveRecord {
|
||||
public:
|
||||
int packetNumber;
|
||||
Bitstream::ReadMappings mappings;
|
||||
|
||||
bool operator<(const ReceiveRecord& other) const { return packetNumber < other.packetNumber; }
|
||||
};
|
||||
|
||||
/// Notes that the described send was acknowledged by the other party.
|
||||
void sendRecordAcknowledged(const SendRecord& record);
|
||||
|
||||
/// Sends a packet to the other party, fragmenting it into multiple datagrams (and emitting
|
||||
/// readyToWrite) as necessary.
|
||||
void sendPacket(const QByteArray& packet);
|
||||
|
||||
QList<SendRecord> _sendRecords;
|
||||
QList<ReceiveRecord> _receiveRecords;
|
||||
|
||||
QByteArray _outgoingPacketData;
|
||||
QDataStream _outgoingPacketStream;
|
||||
Bitstream _outputStream;
|
||||
|
||||
QBuffer _incomingDatagramBuffer;
|
||||
QDataStream _incomingDatagramStream;
|
||||
int _datagramHeaderSize;
|
||||
|
||||
int _outgoingPacketNumber;
|
||||
QByteArray _outgoingDatagram;
|
||||
QBuffer _outgoingDatagramBuffer;
|
||||
QDataStream _outgoingDatagramStream;
|
||||
|
||||
int _incomingPacketNumber;
|
||||
QByteArray _incomingPacketData;
|
||||
QDataStream _incomingPacketStream;
|
||||
Bitstream _inputStream;
|
||||
QSet<int> _offsetsReceived;
|
||||
int _remainingBytes;
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__DatagramSequencer__) */
|
|
@ -11,11 +11,22 @@
|
|||
|
||||
#include "MetavoxelData.h"
|
||||
|
||||
MetavoxelData::MetavoxelData() {
|
||||
}
|
||||
|
||||
MetavoxelData::MetavoxelData(const MetavoxelData& other) : _roots(other._roots) {
|
||||
incrementRootReferenceCounts();
|
||||
}
|
||||
|
||||
MetavoxelData::~MetavoxelData() {
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
|
||||
it.value()->destroy(it.key());
|
||||
delete it.value();
|
||||
}
|
||||
decrementRootReferenceCounts();
|
||||
}
|
||||
|
||||
MetavoxelData& MetavoxelData::operator=(const MetavoxelData& other) {
|
||||
decrementRootReferenceCounts();
|
||||
_roots = other._roots;
|
||||
incrementRootReferenceCounts();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void MetavoxelData::guide(MetavoxelVisitor& visitor) {
|
||||
|
@ -43,8 +54,7 @@ void MetavoxelData::setAttributeValue(const MetavoxelPath& path, const Attribute
|
|||
node = new MetavoxelNode(attributeValue.getAttribute());
|
||||
}
|
||||
if (node->setAttributeValue(path, 0, attributeValue) && attributeValue.isDefault()) {
|
||||
node->destroy(attributeValue.getAttribute());
|
||||
delete node;
|
||||
node->decrementReferenceCount(attributeValue.getAttribute());
|
||||
_roots.remove(attributeValue.getAttribute());
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +74,76 @@ AttributeValue MetavoxelData::getAttributeValue(const MetavoxelPath& path, const
|
|||
return node->getAttributeValue(attribute);
|
||||
}
|
||||
|
||||
MetavoxelNode::MetavoxelNode(const AttributeValue& attributeValue) {
|
||||
void MetavoxelData::read(Bitstream& in) {
|
||||
// save the old roots and clear
|
||||
QHash<AttributePointer, MetavoxelNode*> oldRoots = _roots;
|
||||
_roots.clear();
|
||||
|
||||
// read in the new roots, reusing old ones where appropriate
|
||||
qint32 rootCount;
|
||||
in >> rootCount;
|
||||
for (int i = 0; i < rootCount; i++) {
|
||||
AttributePointer attribute;
|
||||
in.getAttributeStreamer() >> attribute;
|
||||
MetavoxelNode* root = oldRoots.take(attribute);
|
||||
if (!root) {
|
||||
root = new MetavoxelNode(attribute);
|
||||
}
|
||||
_roots.insert(attribute, root);
|
||||
root->read(attribute, in);
|
||||
}
|
||||
|
||||
// clear out the remaining old roots
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = oldRoots.constBegin(); it != oldRoots.constEnd(); it++) {
|
||||
it.value()->decrementReferenceCount(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelData::write(Bitstream& out) const {
|
||||
out << (qint32)_roots.size();
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
|
||||
out.getAttributeStreamer() << it.key();
|
||||
it.value()->write(it.key(), out);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelData::readDelta(const MetavoxelData& reference, Bitstream& in) {
|
||||
}
|
||||
|
||||
void MetavoxelData::writeDelta(const MetavoxelData& reference, Bitstream& out) const {
|
||||
}
|
||||
|
||||
void MetavoxelData::incrementRootReferenceCounts() {
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
|
||||
it.value()->incrementReferenceCount();
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelData::decrementRootReferenceCounts() {
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
|
||||
it.value()->decrementReferenceCount(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
void writeDelta(const MetavoxelDataPointer& data, const MetavoxelDataPointer& reference, Bitstream& out) {
|
||||
if (data == reference) {
|
||||
out << false;
|
||||
return;
|
||||
}
|
||||
out << true;
|
||||
data->writeDelta(*reference, out);
|
||||
}
|
||||
|
||||
void readDelta(MetavoxelDataPointer& data, const MetavoxelDataPointer& reference, Bitstream& in) {
|
||||
bool changed;
|
||||
in >> changed;
|
||||
if (changed) {
|
||||
data.detach();
|
||||
data->readDelta(*reference, in);
|
||||
}
|
||||
}
|
||||
|
||||
MetavoxelNode::MetavoxelNode(const AttributeValue& attributeValue) : _referenceCount(1) {
|
||||
_attributeValue = attributeValue.copy();
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
_children[i] = NULL;
|
||||
|
@ -118,12 +197,96 @@ bool MetavoxelNode::isLeaf() const {
|
|||
return true;
|
||||
}
|
||||
|
||||
void MetavoxelNode::read(const AttributePointer& attribute, Bitstream& in) {
|
||||
bool leaf;
|
||||
in >> leaf;
|
||||
attribute->read(in, _attributeValue, leaf);
|
||||
if (leaf) {
|
||||
clearChildren(attribute);
|
||||
|
||||
} else {
|
||||
void* childValues[CHILD_COUNT];
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
if (!_children[i]) {
|
||||
_children[i] = new MetavoxelNode(attribute);
|
||||
}
|
||||
_children[i]->read(attribute, in);
|
||||
childValues[i] = _children[i]->_attributeValue;
|
||||
}
|
||||
attribute->merge(_attributeValue, childValues);
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelNode::write(const AttributePointer& attribute, Bitstream& out) const {
|
||||
bool leaf = isLeaf();
|
||||
out << leaf;
|
||||
attribute->write(out, _attributeValue, leaf);
|
||||
if (!leaf) {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
_children[i]->write(attribute, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelNode::readDelta(const AttributePointer& attribute, const MetavoxelNode& reference, Bitstream& in) {
|
||||
bool different;
|
||||
in >> different;
|
||||
if (!different) {
|
||||
return;
|
||||
}
|
||||
bool leaf;
|
||||
in >> leaf;
|
||||
attribute->readDelta(in, _attributeValue, reference._attributeValue, leaf);
|
||||
if (leaf) {
|
||||
clearChildren(attribute);
|
||||
|
||||
} else {
|
||||
if (reference.isLeaf()) {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
_children[i]->read(attribute, in);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
_children[i]->readDelta(attribute, *reference._children[i], in);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelNode::writeDelta(const AttributePointer& attribute, const MetavoxelNode& reference, Bitstream& out) const {
|
||||
if (this == &reference) {
|
||||
out << false;
|
||||
return;
|
||||
}
|
||||
out << true;
|
||||
bool leaf = isLeaf();
|
||||
out << leaf;
|
||||
attribute->writeDelta(out, _attributeValue, reference._attributeValue, leaf);
|
||||
if (!leaf) {
|
||||
if (reference.isLeaf()) {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
_children[i]->write(attribute, out);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
_children[i]->writeDelta(attribute, *reference._children[i], out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelNode::decrementReferenceCount(const AttributePointer& attribute) {
|
||||
if (--_referenceCount == 0) {
|
||||
destroy(attribute);
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
void MetavoxelNode::destroy(const AttributePointer& attribute) {
|
||||
attribute->destroy(_attributeValue);
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
if (_children[i]) {
|
||||
_children[i]->destroy(attribute);
|
||||
delete _children[i];
|
||||
_children[i]->decrementReferenceCount(attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -131,8 +294,7 @@ void MetavoxelNode::destroy(const AttributePointer& attribute) {
|
|||
void MetavoxelNode::clearChildren(const AttributePointer& attribute) {
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
if (_children[i]) {
|
||||
_children[i]->destroy(attribute);
|
||||
delete _children[i];
|
||||
_children[i]->decrementReferenceCount(attribute);
|
||||
_children[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@
|
|||
#define __interface__MetavoxelData__
|
||||
|
||||
#include <QBitArray>
|
||||
#include <QExplicitlySharedDataPointer>
|
||||
#include <QHash>
|
||||
#include <QSharedData>
|
||||
#include <QScriptString>
|
||||
#include <QScriptValue>
|
||||
#include <QVector>
|
||||
|
@ -27,11 +29,15 @@ class MetavoxelVisitation;
|
|||
class MetavoxelVisitor;
|
||||
|
||||
/// The base metavoxel representation shared between server and client.
|
||||
class MetavoxelData {
|
||||
class MetavoxelData : public QSharedData {
|
||||
public:
|
||||
|
||||
MetavoxelData();
|
||||
MetavoxelData(const MetavoxelData& other);
|
||||
~MetavoxelData();
|
||||
|
||||
MetavoxelData& operator=(const MetavoxelData& other);
|
||||
|
||||
/// Applies the specified visitor to the contained voxels.
|
||||
void guide(MetavoxelVisitor& visitor);
|
||||
|
||||
|
@ -41,11 +47,26 @@ public:
|
|||
/// Retrieves the attribute value corresponding to the specified path.
|
||||
AttributeValue getAttributeValue(const MetavoxelPath& path, const AttributePointer& attribute) const;
|
||||
|
||||
void read(Bitstream& in);
|
||||
void write(Bitstream& out) const;
|
||||
|
||||
void readDelta(const MetavoxelData& reference, Bitstream& in);
|
||||
void writeDelta(const MetavoxelData& reference, Bitstream& out) const;
|
||||
|
||||
private:
|
||||
|
||||
void incrementRootReferenceCounts();
|
||||
void decrementRootReferenceCounts();
|
||||
|
||||
QHash<AttributePointer, MetavoxelNode*> _roots;
|
||||
};
|
||||
|
||||
typedef QExplicitlySharedDataPointer<MetavoxelData> MetavoxelDataPointer;
|
||||
|
||||
void writeDelta(const MetavoxelDataPointer& data, const MetavoxelDataPointer& reference, Bitstream& out);
|
||||
|
||||
void readDelta(MetavoxelDataPointer& data, const MetavoxelDataPointer& reference, Bitstream& in);
|
||||
|
||||
/// A single node within a metavoxel layer.
|
||||
class MetavoxelNode {
|
||||
public:
|
||||
|
@ -69,6 +90,19 @@ public:
|
|||
|
||||
bool isLeaf() const;
|
||||
|
||||
void read(const AttributePointer& attribute, Bitstream& in);
|
||||
void write(const AttributePointer& attribute, Bitstream& out) const;
|
||||
|
||||
void readDelta(const AttributePointer& attribute, const MetavoxelNode& reference, Bitstream& in);
|
||||
void writeDelta(const AttributePointer& attribute, const MetavoxelNode& reference, Bitstream& out) const;
|
||||
|
||||
/// Increments the node's reference count.
|
||||
void incrementReferenceCount() { _referenceCount++; }
|
||||
|
||||
/// Decrements the node's reference count. If the resulting reference count is zero, destroys the node
|
||||
/// and calls delete this.
|
||||
void decrementReferenceCount(const AttributePointer& attribute);
|
||||
|
||||
void destroy(const AttributePointer& attribute);
|
||||
|
||||
private:
|
||||
|
@ -76,6 +110,7 @@ private:
|
|||
|
||||
void clearChildren(const AttributePointer& attribute);
|
||||
|
||||
int _referenceCount;
|
||||
void* _attributeValue;
|
||||
MetavoxelNode* _children[CHILD_COUNT];
|
||||
};
|
||||
|
|
32
libraries/metavoxels/src/MetavoxelMessages.h
Normal file
32
libraries/metavoxels/src/MetavoxelMessages.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// MetavoxelMessages.h
|
||||
// metavoxels
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/31/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __interface__MetavoxelMessages__
|
||||
#define __interface__MetavoxelMessages__
|
||||
|
||||
#include "Bitstream.h"
|
||||
|
||||
/// A message containing the state of a client.
|
||||
class ClientStateMessage {
|
||||
STREAMABLE
|
||||
|
||||
public:
|
||||
|
||||
STREAM glm::vec3 position;
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(ClientStateMessage)
|
||||
|
||||
/// A message preceding metavoxel delta information. The actual delta will follow it in the stream.
|
||||
class MetavoxelDeltaMessage {
|
||||
STREAMABLE
|
||||
};
|
||||
|
||||
DECLARE_STREAMABLE_METATYPE(MetavoxelDeltaMessage)
|
||||
|
||||
#endif /* defined(__interface__MetavoxelMessages__) */
|
29
libraries/metavoxels/src/MetavoxelUtil.cpp
Normal file
29
libraries/metavoxels/src/MetavoxelUtil.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// MetavoxelUtil.cpp
|
||||
// metavoxels
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/30/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <HifiSockAddr.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "MetavoxelUtil.h"
|
||||
|
||||
QUuid readSessionID(const QByteArray& data, const HifiSockAddr& sender, int& headerPlusIDSize) {
|
||||
// get the header size
|
||||
int headerSize = numBytesForPacketHeader(reinterpret_cast<const unsigned char*>(data.constData()));
|
||||
|
||||
// read the session id
|
||||
const int UUID_BYTES = 16;
|
||||
headerPlusIDSize = headerSize + UUID_BYTES;
|
||||
if (data.size() < headerPlusIDSize) {
|
||||
qWarning() << "Metavoxel data too short [size=" << data.size() << ", sender=" << sender << "]\n";
|
||||
return QUuid();
|
||||
}
|
||||
return QUuid::fromRfc4122(QByteArray::fromRawData(data.constData() + headerSize, UUID_BYTES));
|
||||
}
|
23
libraries/metavoxels/src/MetavoxelUtil.h
Normal file
23
libraries/metavoxels/src/MetavoxelUtil.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// MetavoxelUtil.h
|
||||
// metavoxels
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/30/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __interface__MetavoxelUtil__
|
||||
#define __interface__MetavoxelUtil__
|
||||
|
||||
#include <QUuid>
|
||||
|
||||
class QByteArray;
|
||||
|
||||
class HifiSockAddr;
|
||||
|
||||
/// Reads and returns the session ID from a datagram.
|
||||
/// \param[out] headerPlusIDSize the size of the header (including the session ID) within the data
|
||||
/// \return the session ID, or a null ID if invalid (in which case a warning will be logged)
|
||||
QUuid readSessionID(const QByteArray& data, const HifiSockAddr& sender, int& headerPlusIDSize);
|
||||
|
||||
#endif /* defined(__interface__MetavoxelUtil__) */
|
|
@ -49,20 +49,22 @@ bool OctreeQueryNode::packetIsDuplicate() const {
|
|||
// since our packets now include header information, like sequence number, and createTime, we can't just do a memcmp
|
||||
// of the entire packet, we need to compare only the packet content...
|
||||
if (_lastOctreePacketLength == getPacketLength()) {
|
||||
return memcmp(_lastOctreePacket + OCTREE_PACKET_HEADER_SIZE,
|
||||
_octreePacket+OCTREE_PACKET_HEADER_SIZE , getPacketLength() - OCTREE_PACKET_HEADER_SIZE == 0);
|
||||
if (memcmp(_lastOctreePacket + OCTREE_PACKET_HEADER_SIZE,
|
||||
_octreePacket + OCTREE_PACKET_HEADER_SIZE , getPacketLength() - OCTREE_PACKET_HEADER_SIZE) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OctreeQueryNode::shouldSuppressDuplicatePacket() {
|
||||
bool shouldSuppress = false; // assume we won't suppress
|
||||
|
||||
|
||||
// only consider duplicate packets
|
||||
if (packetIsDuplicate()) {
|
||||
_duplicatePacketCount++;
|
||||
|
||||
// If this is the first suppressed packet, remember our time...
|
||||
// If this is the first suppressed packet, remember our time...
|
||||
if (_duplicatePacketCount == 1) {
|
||||
_firstSuppressedPacket = usecTimestampNow();
|
||||
}
|
||||
|
@ -97,8 +99,8 @@ void OctreeQueryNode::resetOctreePacket(bool lastWasSurpressed) {
|
|||
_lastOctreePacketLength = getPacketLength();
|
||||
memcpy(_lastOctreePacket, _octreePacket, _lastOctreePacketLength);
|
||||
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
_currentPacketIsColor = getWantColor();
|
||||
_currentPacketIsCompressed = getWantCompression();
|
||||
OCTREE_PACKET_FLAGS flags = 0;
|
||||
|
@ -152,7 +154,7 @@ void OctreeQueryNode::writeToPacket(const unsigned char* buffer, int bytes) {
|
|||
_octreePacketAvailableBytes -= bytes;
|
||||
_octreePacketAt += bytes;
|
||||
_octreePacketWaiting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OctreeQueryNode::~OctreeQueryNode() {
|
||||
|
@ -175,20 +177,20 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
|
|||
// Also make sure it's got the correct lens details from the camera
|
||||
float originalFOV = getCameraFov();
|
||||
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
|
||||
|
||||
|
||||
newestViewFrustum.setFieldOfView(wideFOV); // hack
|
||||
newestViewFrustum.setAspectRatio(getCameraAspectRatio());
|
||||
newestViewFrustum.setNearClip(getCameraNearClip());
|
||||
newestViewFrustum.setFarClip(getCameraFarClip());
|
||||
newestViewFrustum.setEyeOffsetPosition(getCameraEyeOffsetPosition());
|
||||
|
||||
|
||||
// if there has been a change, then recalculate
|
||||
if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) {
|
||||
_currentViewFrustum = newestViewFrustum;
|
||||
_currentViewFrustum.calculate();
|
||||
currentViewFrustumChanged = true;
|
||||
}
|
||||
|
||||
|
||||
// Also check for LOD changes from the client
|
||||
if (_lodInitialized) {
|
||||
if (_lastClientBoundaryLevelAdjust != getBoundaryLevelAdjust()) {
|
||||
|
@ -205,7 +207,7 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
|
|||
_lastClientBoundaryLevelAdjust = getBoundaryLevelAdjust();
|
||||
_lodChanged = false;
|
||||
}
|
||||
|
||||
|
||||
// When we first detect that the view stopped changing, we record this.
|
||||
// but we don't change it back to false until we've completely sent this
|
||||
// scene.
|
||||
|
@ -216,8 +218,8 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
|
|||
return currentViewFrustumChanged;
|
||||
}
|
||||
|
||||
void OctreeQueryNode::setViewSent(bool viewSent) {
|
||||
_viewSent = viewSent;
|
||||
void OctreeQueryNode::setViewSent(bool viewSent) {
|
||||
_viewSent = viewSent;
|
||||
if (viewSent) {
|
||||
_viewFrustumJustStoppedChanging = false;
|
||||
_lodChanged = false;
|
||||
|
@ -226,12 +228,12 @@ void OctreeQueryNode::setViewSent(bool viewSent) {
|
|||
|
||||
void OctreeQueryNode::updateLastKnownViewFrustum() {
|
||||
bool frustumChanges = !_lastKnownViewFrustum.isVerySimilar(_currentViewFrustum);
|
||||
|
||||
|
||||
if (frustumChanges) {
|
||||
// save our currentViewFrustum into our lastKnownViewFrustum
|
||||
_lastKnownViewFrustum = _currentViewFrustum;
|
||||
}
|
||||
|
||||
|
||||
// save that we know the view has been sent.
|
||||
uint64_t now = usecTimestampNow();
|
||||
setLastTimeBagEmpty(now); // is this what we want? poor names
|
||||
|
@ -242,7 +244,7 @@ bool OctreeQueryNode::moveShouldDump() const {
|
|||
glm::vec3 oldPosition = _lastKnownViewFrustum.getPosition();
|
||||
glm::vec3 newPosition = _currentViewFrustum.getPosition();
|
||||
|
||||
// theoretically we could make this slightly larger but relative to avatar scale.
|
||||
// theoretically we could make this slightly larger but relative to avatar scale.
|
||||
const float MAXIMUM_MOVE_WITHOUT_DUMP = 0.0f;
|
||||
if (glm::distance(newPosition, oldPosition) > MAXIMUM_MOVE_WITHOUT_DUMP) {
|
||||
return true;
|
||||
|
|
|
@ -27,19 +27,19 @@ OctreeSendThread::OctreeSendThread(const QUuid& nodeUUID, OctreeServer* myServer
|
|||
bool OctreeSendThread::process() {
|
||||
uint64_t start = usecTimestampNow();
|
||||
bool gotLock = false;
|
||||
|
||||
|
||||
// don't do any send processing until the initial load of the octree is complete...
|
||||
if (_myServer->isInitialLoadComplete()) {
|
||||
Node* node = NodeList::getInstance()->nodeWithUUID(_nodeUUID);
|
||||
|
||||
|
||||
if (node) {
|
||||
// make sure the node list doesn't kill our node while we're using it
|
||||
if (node->trylock()) {
|
||||
gotLock = true;
|
||||
OctreeQueryNode* nodeData = NULL;
|
||||
|
||||
|
||||
nodeData = (OctreeQueryNode*) node->getLinkedData();
|
||||
|
||||
|
||||
int packetsSent = 0;
|
||||
|
||||
// Sometimes the node data has not yet been linked, in which case we can't really do anything
|
||||
|
@ -50,7 +50,7 @@ bool OctreeSendThread::process() {
|
|||
}
|
||||
packetsSent = packetDistributor(node, nodeData, viewFrustumChanged);
|
||||
}
|
||||
|
||||
|
||||
node->unlock(); // we're done with this node for now.
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ bool OctreeSendThread::process() {
|
|||
qDebug("OctreeSendThread::process() waiting for isInitialLoadComplete()\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Only sleep if we're still running and we got the lock last time we tried, otherwise try to get the lock asap
|
||||
if (isStillRunning() && gotLock) {
|
||||
// dynamically sleep until we need to fire off the next set of octree elements
|
||||
|
@ -99,14 +99,14 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in
|
|||
nodeData->resetOctreePacket(true); // we still need to reset it though!
|
||||
return packetsSent; // without sending...
|
||||
}
|
||||
|
||||
|
||||
const unsigned char* messageData = nodeData->getPacket();
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(messageData);
|
||||
const unsigned char* dataAt = messageData + numBytesPacketHeader;
|
||||
dataAt += sizeof(OCTREE_PACKET_FLAGS);
|
||||
OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt);
|
||||
dataAt += sizeof(OCTREE_PACKET_SEQUENCE);
|
||||
|
||||
|
||||
|
||||
// If we've got a stats message ready to send, then see if we can piggyback them together
|
||||
if (nodeData->stats.isReadyToSend()) {
|
||||
|
@ -126,13 +126,14 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in
|
|||
int thisWastedBytes = 0;
|
||||
_totalWastedBytes += thisWastedBytes;
|
||||
_totalBytes += nodeData->getPacketLength();
|
||||
_totalPackets++;
|
||||
_totalPackets++;
|
||||
if (debug) {
|
||||
qDebug() << "Adding stats to packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence <<
|
||||
" size: " << nodeData->getPacketLength() << " [" << _totalBytes <<
|
||||
qDebug() << "Adding stats to packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence <<
|
||||
" statsMessageLength: " << statsMessageLength <<
|
||||
" original size: " << nodeData->getPacketLength() << " [" << _totalBytes <<
|
||||
"] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]\n";
|
||||
}
|
||||
|
||||
|
||||
// actually send it
|
||||
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) statsMessage, statsMessageLength,
|
||||
node->getActiveSocket()->getAddress(),
|
||||
|
@ -151,7 +152,7 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in
|
|||
_totalBytes += statsMessageLength;
|
||||
_totalPackets++;
|
||||
if (debug) {
|
||||
qDebug() << "Sending separate stats packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence <<
|
||||
qDebug() << "Sending separate stats packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence <<
|
||||
" size: " << statsMessageLength << " [" << _totalBytes <<
|
||||
"] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]\n";
|
||||
}
|
||||
|
@ -171,7 +172,7 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in
|
|||
_totalBytes += nodeData->getPacketLength();
|
||||
_totalPackets++;
|
||||
if (debug) {
|
||||
qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence <<
|
||||
qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence <<
|
||||
" size: " << nodeData->getPacketLength() << " [" << _totalBytes <<
|
||||
"] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]\n";
|
||||
}
|
||||
|
@ -191,7 +192,7 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in
|
|||
_totalBytes += nodeData->getPacketLength();
|
||||
_totalPackets++;
|
||||
if (debug) {
|
||||
qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence <<
|
||||
qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence <<
|
||||
" size: " << nodeData->getPacketLength() << " [" << _totalBytes <<
|
||||
"] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]\n";
|
||||
}
|
||||
|
@ -205,12 +206,13 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in
|
|||
packetsSent++;
|
||||
nodeData->resetOctreePacket();
|
||||
}
|
||||
|
||||
|
||||
return packetsSent;
|
||||
}
|
||||
|
||||
/// Version of voxel distributor that sends the deepest LOD level at once
|
||||
int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
|
||||
bool forceDebugging = false;
|
||||
|
||||
int truePacketsSent = 0;
|
||||
int trueBytesSent = 0;
|
||||
|
@ -220,29 +222,32 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
|
|||
// FOR NOW... node tells us if it wants to receive only view frustum deltas
|
||||
bool wantDelta = viewFrustumChanged && nodeData->getWantDelta();
|
||||
|
||||
// If our packet already has content in it, then we must use the color choice of the waiting packet.
|
||||
// If we're starting a fresh packet, then...
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// If our packet already has content in it, then we must use the color choice of the waiting packet.
|
||||
// If we're starting a fresh packet, then...
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
bool wantColor = nodeData->getWantColor();
|
||||
bool wantCompression = nodeData->getWantCompression();
|
||||
|
||||
// If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color
|
||||
// then let's just send that waiting packet.
|
||||
// then let's just send that waiting packet.
|
||||
if (!nodeData->getCurrentPacketFormatMatches()) {
|
||||
if (nodeData->isPacketWaiting()) {
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("wantColor=%s wantCompression=%s SENDING PARTIAL PACKET! currentPacketIsColor=%s currentPacketIsCompressed=%s\n",
|
||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
||||
qDebug("about to call handlePacketSend() .... line: %d -- format change "
|
||||
"wantColor=%s wantCompression=%s SENDING PARTIAL PACKET! currentPacketIsColor=%s "
|
||||
"currentPacketIsCompressed=%s\n",
|
||||
__LINE__,
|
||||
debug::valueOf(wantColor), debug::valueOf(wantCompression),
|
||||
debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
||||
debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
||||
debug::valueOf(nodeData->getCurrentPacketIsCompressed()) );
|
||||
}
|
||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
} else {
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("wantColor=%s wantCompression=%s FIXING HEADER! currentPacketIsColor=%s currentPacketIsCompressed=%s\n",
|
||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
||||
qDebug("wantColor=%s wantCompression=%s FIXING HEADER! currentPacketIsColor=%s currentPacketIsCompressed=%s\n",
|
||||
debug::valueOf(wantColor), debug::valueOf(wantCompression),
|
||||
debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
||||
debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
||||
debug::valueOf(nodeData->getCurrentPacketIsCompressed()) );
|
||||
}
|
||||
nodeData->resetOctreePacket();
|
||||
|
@ -252,57 +257,57 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
|
|||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
}
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("line:%d _packetData.changeSettings() wantCompression=%s targetSize=%d\n",__LINE__,
|
||||
qDebug("line:%d _packetData.changeSettings() wantCompression=%s targetSize=%d\n", __LINE__,
|
||||
debug::valueOf(wantCompression), targetSize);
|
||||
}
|
||||
|
||||
|
||||
_packetData.changeSettings(wantCompression, targetSize);
|
||||
}
|
||||
|
||||
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("wantColor/isColor=%s/%s wantCompression/isCompressed=%s/%s viewFrustumChanged=%s, getWantLowResMoving()=%s\n",
|
||||
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
||||
qDebug("wantColor/isColor=%s/%s wantCompression/isCompressed=%s/%s viewFrustumChanged=%s, getWantLowResMoving()=%s\n",
|
||||
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
||||
debug::valueOf(wantCompression), debug::valueOf(nodeData->getCurrentPacketIsCompressed()),
|
||||
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->getWantLowResMoving()));
|
||||
}
|
||||
|
||||
const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL;
|
||||
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("packetDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s\n",
|
||||
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()),
|
||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
||||
qDebug("packetDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s\n",
|
||||
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()),
|
||||
debug::valueOf(nodeData->getViewSent())
|
||||
);
|
||||
}
|
||||
|
||||
// If the current view frustum has changed OR we have nothing to send, then search against
|
||||
|
||||
// If the current view frustum has changed OR we have nothing to send, then search against
|
||||
// the current view frustum for things to send.
|
||||
if (viewFrustumChanged || nodeData->nodeBag.isEmpty()) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("(viewFrustumChanged=%s || nodeData->nodeBag.isEmpty() =%s)...\n",
|
||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
||||
qDebug("(viewFrustumChanged=%s || nodeData->nodeBag.isEmpty() =%s)...\n",
|
||||
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()));
|
||||
if (nodeData->getLastTimeBagEmpty() > 0) {
|
||||
float elapsedSceneSend = (now - nodeData->getLastTimeBagEmpty()) / 1000000.0f;
|
||||
if (viewFrustumChanged) {
|
||||
printf("viewFrustumChanged resetting after elapsed time to send scene = %f seconds", elapsedSceneSend);
|
||||
qDebug("viewFrustumChanged resetting after elapsed time to send scene = %f seconds", elapsedSceneSend);
|
||||
} else {
|
||||
printf("elapsed time to send scene = %f seconds", elapsedSceneSend);
|
||||
qDebug("elapsed time to send scene = %f seconds", elapsedSceneSend);
|
||||
}
|
||||
printf(" [occlusionCulling:%s, wantDelta:%s, wantColor:%s ]\n",
|
||||
debug::valueOf(nodeData->getWantOcclusionCulling()), debug::valueOf(wantDelta),
|
||||
qDebug(" [occlusionCulling:%s, wantDelta:%s, wantColor:%s ]\n",
|
||||
debug::valueOf(nodeData->getWantOcclusionCulling()), debug::valueOf(wantDelta),
|
||||
debug::valueOf(wantColor));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if our view has changed, we need to reset these things...
|
||||
if (viewFrustumChanged) {
|
||||
if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) {
|
||||
nodeData->dumpOutOfView();
|
||||
}
|
||||
nodeData->map.erase();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!viewFrustumChanged && !nodeData->getWantDelta()) {
|
||||
// only set our last sent time if we weren't resetting due to frustum change
|
||||
uint64_t now = usecTimestampNow();
|
||||
|
@ -313,34 +318,42 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
|
|||
nodeData->stats.sceneCompleted();
|
||||
::endSceneSleepTime = _usleepTime;
|
||||
unsigned long sleepTime = ::endSceneSleepTime - ::startSceneSleepTime;
|
||||
|
||||
|
||||
unsigned long encodeTime = nodeData->stats.getTotalEncodeTime();
|
||||
unsigned long elapsedTime = nodeData->stats.getElapsedTime();
|
||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
|
||||
if (_myServer->wantsDebugSending()) {
|
||||
qDebug() << "Scene completed at " << usecTimestampNow()
|
||||
<< "encodeTime:" << encodeTime
|
||||
<< " sleepTime:" << sleepTime
|
||||
<< " elapsed:" << elapsedTime
|
||||
<< " Packets:" << _totalPackets
|
||||
<< " Bytes:" << _totalBytes
|
||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
||||
qDebug("about to call handlePacketSend() .... line: %d -- completed scene \n", __LINE__ );
|
||||
}
|
||||
int packetsJustSent = handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
packetsSentThisInterval += packetsJustSent;
|
||||
if (forceDebugging) {
|
||||
qDebug("packetsJustSent=%d packetsSentThisInterval=%d\n", packetsJustSent, packetsSentThisInterval);
|
||||
}
|
||||
|
||||
if (forceDebugging || _myServer->wantsDebugSending()) {
|
||||
qDebug() << "Scene completed at " << usecTimestampNow()
|
||||
<< "encodeTime:" << encodeTime
|
||||
<< " sleepTime:" << sleepTime
|
||||
<< " elapsed:" << elapsedTime
|
||||
<< " Packets:" << _totalPackets
|
||||
<< " Bytes:" << _totalBytes
|
||||
<< " Wasted:" << _totalWastedBytes << "\n";
|
||||
}
|
||||
|
||||
|
||||
// start tracking our stats
|
||||
bool isFullScene = ((!viewFrustumChanged || !nodeData->getWantDelta())
|
||||
bool isFullScene = ((!viewFrustumChanged || !nodeData->getWantDelta())
|
||||
&& nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged();
|
||||
|
||||
|
||||
// If we're starting a full scene, then definitely we want to empty the nodeBag
|
||||
if (isFullScene) {
|
||||
nodeData->nodeBag.deleteAll();
|
||||
}
|
||||
|
||||
if (_myServer->wantsDebugSending()) {
|
||||
qDebug() << "Scene started at " << usecTimestampNow()
|
||||
<< " Packets:" << _totalPackets
|
||||
<< " Bytes:" << _totalBytes
|
||||
if (forceDebugging || _myServer->wantsDebugSending()) {
|
||||
qDebug() << "Scene started at " << usecTimestampNow()
|
||||
<< " Packets:" << _totalPackets
|
||||
<< " Bytes:" << _totalBytes
|
||||
<< " Wasted:" << _totalWastedBytes << "\n";
|
||||
}
|
||||
|
||||
|
@ -367,18 +380,19 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
|
|||
|
||||
int clientMaxPacketsPerInterval = std::max(1,(nodeData->getMaxOctreePacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
|
||||
|
||||
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n",
|
||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(),
|
||||
qDebug("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n",
|
||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(),
|
||||
nodeData->getMaxOctreePacketsPerSecond(), clientMaxPacketsPerInterval);
|
||||
}
|
||||
|
||||
|
||||
int extraPackingAttempts = 0;
|
||||
bool completedScene = false;
|
||||
while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval) {
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n",
|
||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(),
|
||||
qDebug("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n",
|
||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(),
|
||||
nodeData->getMaxOctreePacketsPerSecond(), clientMaxPacketsPerInterval);
|
||||
}
|
||||
|
||||
|
@ -391,27 +405,32 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
|
|||
float voxelSizeScale = nodeData->getOctreeSizeScale();
|
||||
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
|
||||
|
||||
int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving()
|
||||
int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving()
|
||||
? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
||||
|
||||
|
||||
bool isFullScene = ((!viewFrustumChanged || !nodeData->getWantDelta()) &&
|
||||
bool isFullScene = ((!viewFrustumChanged || !nodeData->getWantDelta()) &&
|
||||
nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged();
|
||||
|
||||
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
|
||||
|
||||
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
|
||||
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
|
||||
wantOcclusionCulling, coverageMap, boundaryLevelAdjust, voxelSizeScale,
|
||||
nodeData->getLastTimeBagEmpty(),
|
||||
isFullScene, &nodeData->stats, _myServer->getJurisdiction());
|
||||
|
||||
|
||||
|
||||
_myServer->getOctree()->lockForRead();
|
||||
nodeData->stats.encodeStarted();
|
||||
bytesWritten = _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->nodeBag, params);
|
||||
|
||||
|
||||
// If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've
|
||||
// sent the entire scene. We want to know this below so we'll actually write this content into
|
||||
// the packet and send it
|
||||
completedScene = nodeData->nodeBag.isEmpty();
|
||||
|
||||
// if we're trying to fill a full size packet, then we use this logic to determine if we have a DIDNT_FIT case.
|
||||
if (_packetData.getTargetSize() == MAX_OCTREE_PACKET_DATA_SIZE) {
|
||||
if (_packetData.hasContent() && bytesWritten == 0 &&
|
||||
if (_packetData.hasContent() && bytesWritten == 0 &&
|
||||
params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
|
||||
lastNodeDidntFit = true;
|
||||
}
|
||||
|
@ -432,51 +451,56 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
|
|||
bytesWritten = 0;
|
||||
somethingToSend = false; // this will cause us to drop out of the loop...
|
||||
}
|
||||
|
||||
|
||||
// If the last node didn't fit, but we're in compressed mode, then we actually want to see if we can fit a
|
||||
// little bit more in this packet. To do this we
|
||||
|
||||
// little bit more in this packet. To do this we write into the packet, but don't send it yet, we'll
|
||||
// keep attempting to write in compressed mode to add more compressed segments
|
||||
|
||||
// We only consider sending anything if there is something in the _packetData to send... But
|
||||
// if bytesWritten == 0 it means either the subTree couldn't fit or we had an empty bag... Both cases
|
||||
// mean we should send the previous packet contents and reset it.
|
||||
if (lastNodeDidntFit) {
|
||||
// mean we should send the previous packet contents and reset it.
|
||||
if (completedScene || lastNodeDidntFit) {
|
||||
if (_packetData.hasContent()) {
|
||||
// if for some reason the finalized size is greater than our available size, then probably the "compressed"
|
||||
// form actually inflated beyond our padding, and in this case we will send the current packet, then
|
||||
// write to out new packet...
|
||||
int writtenSize = _packetData.getFinalizedSize()
|
||||
int writtenSize = _packetData.getFinalizedSize()
|
||||
+ (nodeData->getCurrentPacketIsCompressed() ? sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) : 0);
|
||||
|
||||
|
||||
|
||||
|
||||
if (writtenSize > nodeData->getAvailable()) {
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("writtenSize[%d] > available[%d] too big, sending packet as is.\n",
|
||||
writtenSize, nodeData->getAvailable());
|
||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
||||
qDebug("about to call handlePacketSend() .... line: %d -- "
|
||||
"writtenSize[%d] > available[%d] too big, sending packet as is.\n",
|
||||
__LINE__, writtenSize, nodeData->getAvailable());
|
||||
}
|
||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
}
|
||||
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("calling writeToPacket() available=%d compressedSize=%d uncompressedSize=%d target=%d\n",
|
||||
nodeData->getAvailable(), _packetData.getFinalizedSize(),
|
||||
if (forceDebugging || (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug())) {
|
||||
qDebug(">>>>>> calling writeToPacket() available=%d compressedSize=%d uncompressedSize=%d target=%d\n",
|
||||
nodeData->getAvailable(), _packetData.getFinalizedSize(),
|
||||
_packetData.getUncompressedSize(), _packetData.getTargetSize());
|
||||
}
|
||||
nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize());
|
||||
extraPackingAttempts = 0;
|
||||
}
|
||||
|
||||
// If we're not running compressed, the we know we can just send now. Or if we're running compressed, but
|
||||
|
||||
// If we're not running compressed, then we know we can just send now. Or if we're running compressed, but
|
||||
// the packet doesn't have enough space to bother attempting to pack more...
|
||||
bool sendNow = true;
|
||||
|
||||
if (nodeData->getCurrentPacketIsCompressed() &&
|
||||
|
||||
if (nodeData->getCurrentPacketIsCompressed() &&
|
||||
nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING &&
|
||||
extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS) {
|
||||
sendNow = false; // try to pack more
|
||||
}
|
||||
|
||||
|
||||
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
|
||||
if (sendNow) {
|
||||
if (forceDebugging) {
|
||||
qDebug("about to call handlePacketSend() .... line: %d -- sendNow = TRUE\n", __LINE__);
|
||||
}
|
||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
if (wantCompression) {
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
|
@ -491,14 +515,14 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
|
|||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING;
|
||||
}
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("line:%d _packetData.changeSettings() wantCompression=%s targetSize=%d\n",__LINE__,
|
||||
qDebug("line:%d _packetData.changeSettings() wantCompression=%s targetSize=%d\n",__LINE__,
|
||||
debug::valueOf(nodeData->getWantCompression()), targetSize);
|
||||
}
|
||||
_packetData.changeSettings(nodeData->getWantCompression(), targetSize); // will do reset
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Here's where we can/should allow the server to send other data...
|
||||
// send the environment packet
|
||||
if (_myServer->hasSpecialPacketToSend()) {
|
||||
|
@ -506,14 +530,14 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
|
|||
truePacketsSent++;
|
||||
packetsSentThisInterval++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
uint64_t end = usecTimestampNow();
|
||||
int elapsedmsec = (end - start)/1000;
|
||||
|
||||
uint64_t endCompressCalls = OctreePacketData::getCompressContentCalls();
|
||||
int elapsedCompressCalls = endCompressCalls - startCompressCalls;
|
||||
|
||||
|
||||
uint64_t endCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000;
|
||||
int elapsedCompressTimeMsecs = endCompressTimeMsecs - startCompressTimeMsecs;
|
||||
|
||||
|
@ -521,18 +545,24 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
|
|||
if (elapsedmsec > 100) {
|
||||
if (elapsedmsec > 1000) {
|
||||
int elapsedsec = (end - start)/1000000;
|
||||
printf("WARNING! packetLoop() took %d seconds [%d milliseconds %d calls in compress] to generate %d bytes in %d packets %d nodes still to send\n",
|
||||
elapsedsec, elapsedCompressTimeMsecs, elapsedCompressCalls, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
qDebug("WARNING! packetLoop() took %d seconds [%d milliseconds %d calls in compress] "
|
||||
"to generate %d bytes in %d packets %d nodes still to send\n",
|
||||
elapsedsec, elapsedCompressTimeMsecs, elapsedCompressCalls,
|
||||
trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
} else {
|
||||
printf("WARNING! packetLoop() took %d milliseconds [%d milliseconds %d calls in compress] to generate %d bytes in %d packets, %d nodes still to send\n",
|
||||
elapsedmsec, elapsedCompressTimeMsecs, elapsedCompressCalls, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
qDebug("WARNING! packetLoop() took %d milliseconds [%d milliseconds %d calls in compress] "
|
||||
"to generate %d bytes in %d packets, %d nodes still to send\n",
|
||||
elapsedmsec, elapsedCompressTimeMsecs, elapsedCompressCalls,
|
||||
trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
}
|
||||
} else if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("packetLoop() took %d milliseconds [%d milliseconds %d calls in compress] to generate %d bytes in %d packets, %d nodes still to send\n",
|
||||
elapsedmsec, elapsedCompressTimeMsecs, elapsedCompressCalls, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
qDebug("packetLoop() took %d milliseconds [%d milliseconds %d calls in compress] "
|
||||
"to generate %d bytes in %d packets, %d nodes still to send\n",
|
||||
elapsedmsec, elapsedCompressTimeMsecs, elapsedCompressCalls,
|
||||
trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
}
|
||||
|
||||
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
|
||||
|
||||
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
|
||||
// the voxels from the current view frustum
|
||||
if (nodeData->nodeBag.isEmpty()) {
|
||||
nodeData->updateLastKnownViewFrustum();
|
||||
|
@ -544,11 +574,13 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
|
|||
}
|
||||
|
||||
if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n",
|
||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(),
|
||||
nodeData->getMaxOctreePacketsPerSecond(), clientMaxPacketsPerInterval);
|
||||
qDebug("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d "
|
||||
"server PPI=%d nodePPS=%d nodePPI=%d\n",
|
||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval,
|
||||
_myServer->getPacketsPerClientPerInterval(), nodeData->getMaxOctreePacketsPerSecond(),
|
||||
clientMaxPacketsPerInterval);
|
||||
}
|
||||
|
||||
|
||||
} // end if bag wasn't empty, and so we sent stuff...
|
||||
|
||||
return truePacketsSent;
|
||||
|
|
|
@ -514,7 +514,7 @@ void OctreeServer::processDatagram(const QByteArray& dataByteArray, const HifiSo
|
|||
if (packetType == getMyQueryMessageType()) {
|
||||
bool debug = false;
|
||||
if (debug) {
|
||||
qDebug() << "Got PACKET_TYPE_VOXEL_QUERY at " << usecTimestampNow() << "\n";
|
||||
qDebug() << "Got PACKET_TYPE_VOXEL_QUERY at" << usecTimestampNow() << "\n";
|
||||
}
|
||||
|
||||
int numBytesPacketHeader = numBytesForPacketHeader((unsigned char*) dataByteArray.data());
|
||||
|
|
|
@ -43,7 +43,7 @@ Octree::Octree(bool shouldReaverage) :
|
|||
_shouldReaverage(shouldReaverage),
|
||||
_stopImport(false) {
|
||||
_rootNode = NULL;
|
||||
|
||||
|
||||
pthread_mutex_init(&_encodeSetLock, NULL);
|
||||
pthread_mutex_init(&_deleteSetLock, NULL);
|
||||
pthread_mutex_init(&_deletePendingSetLock, NULL);
|
||||
|
@ -66,13 +66,13 @@ void Octree::recurseTreeWithOperation(RecurseOctreeOperation operation, void* ex
|
|||
}
|
||||
|
||||
// Recurses voxel node with an operation function
|
||||
void Octree::recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperation operation, void* extraData,
|
||||
void Octree::recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperation operation, void* extraData,
|
||||
int recursionCount) {
|
||||
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
||||
qDebug() << "Octree::recurseNodeWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (operation(node, extraData)) {
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
OctreeElement* child = node->getChildAtIndex(i);
|
||||
|
@ -92,7 +92,7 @@ void Octree::recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation opera
|
|||
}
|
||||
|
||||
// Recurses voxel node with an operation function
|
||||
void Octree::recurseNodeWithOperationDistanceSorted(OctreeElement* node, RecurseOctreeOperation operation,
|
||||
void Octree::recurseNodeWithOperationDistanceSorted(OctreeElement* node, RecurseOctreeOperation operation,
|
||||
const glm::vec3& point, void* extraData, int recursionCount) {
|
||||
|
||||
if (recursionCount > DANGEROUSLY_DEEP_RECURSION) {
|
||||
|
@ -138,7 +138,7 @@ OctreeElement* Octree::nodeForOctalCode(OctreeElement* ancestorNode,
|
|||
if (needleCode == NULL) {
|
||||
return _rootNode;
|
||||
}
|
||||
|
||||
|
||||
// find the appropriate branch index based on this ancestorNode
|
||||
if (*needleCode > 0) {
|
||||
int branchForNeedle = branchIndexWithDescendant(ancestorNode->getOctalCode(), needleCode);
|
||||
|
@ -213,13 +213,13 @@ int Octree::readNodeData(OctreeElement* destinationNode, const unsigned char* no
|
|||
nodeWasDirty = childNodeAt->isDirty();
|
||||
bytesRead += childNodeAt->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args);
|
||||
childNodeAt->setSourceUUID(args.sourceUUID);
|
||||
|
||||
|
||||
// if we had a local version of the node already, it's possible that we have it already but
|
||||
// with the same color data, so this won't count as a change. To address this we check the following
|
||||
if (!childNodeAt->isDirty() && childNodeAt->getShouldRender() && !childNodeAt->isRendered()) {
|
||||
childNodeAt->setDirtyBit(); // force dirty!
|
||||
}
|
||||
|
||||
|
||||
nodeIsDirty = childNodeAt->isDirty();
|
||||
}
|
||||
if (nodeIsDirty) {
|
||||
|
@ -299,8 +299,8 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, unsigned long
|
|||
int octalCodeBytes = bytesRequiredForCodeLength(*bitstreamAt);
|
||||
int theseBytesRead = 0;
|
||||
theseBytesRead += octalCodeBytes;
|
||||
|
||||
theseBytesRead += readNodeData(bitstreamRootNode, bitstreamAt + octalCodeBytes,
|
||||
|
||||
theseBytesRead += readNodeData(bitstreamRootNode, bitstreamAt + octalCodeBytes,
|
||||
bufferSizeBytes - (bytesRead + octalCodeBytes), args);
|
||||
|
||||
// skip bitstream to new startPoint
|
||||
|
@ -341,7 +341,7 @@ void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool colla
|
|||
args.pathChanged = false;
|
||||
|
||||
OctreeElement* node = _rootNode;
|
||||
|
||||
|
||||
// We can't encode and delete nodes at the same time, so we guard against deleting any node that is actively
|
||||
// being encoded. And we stick that code on our pendingDelete list.
|
||||
if (isEncoding(codeBuffer)) {
|
||||
|
@ -380,15 +380,15 @@ void Octree::deleteOctalCodeFromTreeRecursion(OctreeElement* node, void* extraDa
|
|||
OctreeElement* ancestorNode = node;
|
||||
while (true) {
|
||||
int index = branchIndexWithDescendant(ancestorNode->getOctalCode(), args->codeBuffer);
|
||||
|
||||
|
||||
// we end up with all the children, even the one we want to delete
|
||||
ancestorNode->splitChildren();
|
||||
|
||||
|
||||
int lengthOfAncestorNode = numberOfThreeBitSectionsInCode(ancestorNode->getOctalCode());
|
||||
|
||||
// If we've reached the parent of the target, then stop breaking up children
|
||||
if (lengthOfAncestorNode == (args->lengthOfCode - 1)) {
|
||||
|
||||
|
||||
// since we created all the children when we split, we need to delete this target one
|
||||
ancestorNode->deleteChildAtIndex(index);
|
||||
break;
|
||||
|
@ -454,21 +454,21 @@ void Octree::processRemoveOctreeElementsBitstream(const unsigned char* bitstream
|
|||
int numBytesPacketHeader = numBytesForPacketHeader(bitstream);
|
||||
unsigned short int sequence = (*((unsigned short int*)(bitstream + numBytesPacketHeader)));
|
||||
uint64_t sentAt = (*((uint64_t*)(bitstream + numBytesPacketHeader + sizeof(sequence))));
|
||||
|
||||
|
||||
int atByte = numBytesPacketHeader + sizeof(sequence) + sizeof(sentAt);
|
||||
|
||||
|
||||
unsigned char* voxelCode = (unsigned char*)&bitstream[atByte];
|
||||
while (atByte < bufferSizeBytes) {
|
||||
int maxSize = bufferSizeBytes - atByte;
|
||||
int codeLength = numberOfThreeBitSectionsInCode(voxelCode, maxSize);
|
||||
|
||||
|
||||
if (codeLength == OVERFLOWED_OCTCODE_BUFFER) {
|
||||
printf("WARNING! Got remove voxel bitstream that would overflow buffer in numberOfThreeBitSectionsInCode(), ");
|
||||
printf("bailing processing of packet!\n");
|
||||
break;
|
||||
}
|
||||
int voxelDataSize = bytesRequiredForCodeLength(codeLength) + SIZE_OF_COLOR_DATA;
|
||||
|
||||
|
||||
if (atByte + voxelDataSize <= bufferSizeBytes) {
|
||||
deleteOctalCodeFromTree(voxelCode, COLLAPSE_EMPTY_TREE);
|
||||
voxelCode += voxelDataSize;
|
||||
|
@ -572,7 +572,7 @@ bool findRayIntersectionOp(OctreeElement* node, void* extraData) {
|
|||
|
||||
bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElement*& node, float& distance, BoxFace& face) {
|
||||
RayArgs args = { origin / static_cast<float>(TREE_SCALE), direction, node, distance, face };
|
||||
RayArgs args = { origin / (float)(TREE_SCALE), direction, node, distance, face };
|
||||
recurseTreeWithOperation(findRayIntersectionOp, &args);
|
||||
return args.found;
|
||||
}
|
||||
|
@ -600,21 +600,21 @@ bool findSpherePenetrationOp(OctreeElement* element, void* extraData) {
|
|||
if (element->hasContent()) {
|
||||
glm::vec3 elementPenetration;
|
||||
if (element->findSpherePenetration(args->center, args->radius, elementPenetration, &args->penetratedObject)) {
|
||||
args->penetration = addPenetrations(args->penetration, elementPenetration * static_cast<float>(TREE_SCALE));
|
||||
args->penetration = addPenetrations(args->penetration, elementPenetration * (float)(TREE_SCALE));
|
||||
args->found = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Octree::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration,
|
||||
bool Octree::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration,
|
||||
void** penetratedObject) {
|
||||
|
||||
SphereArgs args = {
|
||||
center / static_cast<float>(TREE_SCALE),
|
||||
radius / static_cast<float>(TREE_SCALE),
|
||||
penetration,
|
||||
false,
|
||||
|
||||
SphereArgs args = {
|
||||
center / (float)(TREE_SCALE),
|
||||
radius / (float)(TREE_SCALE),
|
||||
penetration,
|
||||
false,
|
||||
NULL };
|
||||
penetration = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
recurseTreeWithOperation(findSpherePenetrationOp, &args);
|
||||
|
@ -647,7 +647,7 @@ bool findCapsulePenetrationOp(OctreeElement* node, void* extraData) {
|
|||
if (node->hasContent()) {
|
||||
glm::vec3 nodePenetration;
|
||||
if (box.findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) {
|
||||
args->penetration = addPenetrations(args->penetration, nodePenetration * static_cast<float>(TREE_SCALE));
|
||||
args->penetration = addPenetrations(args->penetration, nodePenetration * (float)(TREE_SCALE));
|
||||
args->found = true;
|
||||
}
|
||||
}
|
||||
|
@ -655,17 +655,17 @@ bool findCapsulePenetrationOp(OctreeElement* node, void* extraData) {
|
|||
}
|
||||
|
||||
bool Octree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) {
|
||||
CapsuleArgs args = {
|
||||
start / static_cast<float>(TREE_SCALE),
|
||||
end / static_cast<float>(TREE_SCALE),
|
||||
radius / static_cast<float>(TREE_SCALE),
|
||||
CapsuleArgs args = {
|
||||
start / (float)(TREE_SCALE),
|
||||
end / (float)(TREE_SCALE),
|
||||
radius / (float)(TREE_SCALE),
|
||||
penetration };
|
||||
penetration = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
recurseTreeWithOperation(findCapsulePenetrationOp, &args);
|
||||
return args.found;
|
||||
}
|
||||
|
||||
int Octree::encodeTreeBitstream(OctreeElement* node,
|
||||
int Octree::encodeTreeBitstream(OctreeElement* node,
|
||||
OctreePacketData* packetData, OctreeElementBag& bag,
|
||||
EncodeBitstreamParams& params) {
|
||||
|
||||
|
@ -680,7 +680,7 @@ int Octree::encodeTreeBitstream(OctreeElement* node,
|
|||
}
|
||||
|
||||
startEncoding(node);
|
||||
|
||||
|
||||
// If we're at a node that is out of view, then we can return, because no nodes below us will be in view!
|
||||
if (params.viewFrustum && !node->isInView(*params.viewFrustum)) {
|
||||
doneEncoding(node);
|
||||
|
@ -696,7 +696,7 @@ int Octree::encodeTreeBitstream(OctreeElement* node,
|
|||
roomForOctalCode = packetData->startSubTree(newCode);
|
||||
|
||||
if (newCode) {
|
||||
delete newCode;
|
||||
delete newCode;
|
||||
} else {
|
||||
codeLength = 1;
|
||||
}
|
||||
|
@ -712,23 +712,23 @@ int Octree::encodeTreeBitstream(OctreeElement* node,
|
|||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
|
||||
bytesWritten += codeLength; // keep track of byte count
|
||||
|
||||
|
||||
int currentEncodeLevel = 0;
|
||||
|
||||
// record some stats, this is the one node that we won't record below in the recursion function, so we need to
|
||||
|
||||
// record some stats, this is the one node that we won't record below in the recursion function, so we need to
|
||||
// track it here
|
||||
if (params.stats) {
|
||||
params.stats->traversed(node);
|
||||
}
|
||||
|
||||
|
||||
int childBytesWritten = encodeTreeBitstreamRecursion(node, packetData, bag, params, currentEncodeLevel);
|
||||
|
||||
// if childBytesWritten == 1 then something went wrong... that's not possible
|
||||
assert(childBytesWritten != 1);
|
||||
|
||||
// if includeColor and childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some
|
||||
// if includeColor and childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some
|
||||
// reason couldn't be written... so reset them here... This isn't true for the non-color included case
|
||||
if (params.includeColor && childBytesWritten == 2) {
|
||||
childBytesWritten = 0;
|
||||
|
@ -743,19 +743,19 @@ int Octree::encodeTreeBitstream(OctreeElement* node,
|
|||
bytesWritten = 0;
|
||||
//params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
|
||||
|
||||
if (bytesWritten == 0) {
|
||||
packetData->discardSubTree();
|
||||
} else {
|
||||
packetData->endSubTree();
|
||||
}
|
||||
|
||||
|
||||
doneEncoding(node);
|
||||
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
||||
int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
||||
OctreePacketData* packetData, OctreeElementBag& bag,
|
||||
EncodeBitstreamParams& params, int& currentEncodeLevel) const {
|
||||
// How many bytes have we written so far at this level;
|
||||
|
@ -770,7 +770,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
|
||||
// Keep track of how deep we've encoded.
|
||||
currentEncodeLevel++;
|
||||
|
||||
|
||||
params.maxLevelReached = std::max(currentEncodeLevel,params.maxLevelReached);
|
||||
|
||||
// If we've reached our max Search Level, then stop searching.
|
||||
|
@ -788,11 +788,11 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
return bytesAtThisLevel;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// caller can pass NULL as viewFrustum if they want everything
|
||||
if (params.viewFrustum) {
|
||||
float distance = node->distanceToCamera(*params.viewFrustum);
|
||||
float boundaryDistance = boundaryDistanceForRenderLevel(node->getLevel() + params.boundaryLevelAdjust,
|
||||
float boundaryDistance = boundaryDistanceForRenderLevel(node->getLevel() + params.boundaryLevelAdjust,
|
||||
params.octreeElementSizeScale);
|
||||
|
||||
// If we're too far away for our render level, then just return
|
||||
|
@ -814,28 +814,28 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
|
||||
// Ok, we are in view, but if we're in delta mode, then we also want to make sure we weren't already in view
|
||||
// because we don't send nodes from the previously know in view frustum.
|
||||
bool wasInView = false;
|
||||
|
||||
|
||||
if (params.deltaViewFrustum && params.lastViewFrustum) {
|
||||
ViewFrustum::location location = node->inFrustum(*params.lastViewFrustum);
|
||||
|
||||
|
||||
// If we're a leaf, then either intersect or inside is considered "formerly in view"
|
||||
if (node->isLeaf()) {
|
||||
wasInView = location != ViewFrustum::OUTSIDE;
|
||||
} else {
|
||||
wasInView = location == ViewFrustum::INSIDE;
|
||||
}
|
||||
|
||||
|
||||
// If we were in view, double check that we didn't switch LOD visibility... namely, the was in view doesn't
|
||||
// tell us if it was so small we wouldn't have rendered it. Which may be the case. And we may have moved closer
|
||||
// to it, and so therefore it may now be visible from an LOD perspective, in which case we don't consider it
|
||||
// as "was in view"...
|
||||
if (wasInView) {
|
||||
float distance = node->distanceToCamera(*params.lastViewFrustum);
|
||||
float boundaryDistance = boundaryDistanceForRenderLevel(node->getLevel() + params.boundaryLevelAdjust,
|
||||
float boundaryDistance = boundaryDistanceForRenderLevel(node->getLevel() + params.boundaryLevelAdjust,
|
||||
params.octreeElementSizeScale);
|
||||
if (distance >= boundaryDistance) {
|
||||
// This would have been invisible... but now should be visible (we wouldn't be here otherwise)...
|
||||
|
@ -855,9 +855,9 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
// If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed,
|
||||
// If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed,
|
||||
// then we can also bail early and save bits
|
||||
if (!params.forceSendScene && !params.deltaViewFrustum &&
|
||||
if (!params.forceSendScene && !params.deltaViewFrustum &&
|
||||
!node->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE)) {
|
||||
if (params.stats) {
|
||||
params.stats->skippedNoChange(node);
|
||||
|
@ -896,9 +896,9 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more!
|
||||
|
||||
// At any given point in writing the bitstream, the largest minimum we might need to flesh out the current level
|
||||
// is 1 byte for child colors + 3*NUMBER_OF_CHILDREN bytes for the actual colors + 1 byte for child trees.
|
||||
// There could be sub trees below this point, which might take many more bytes, but that's ok, because we can
|
||||
// always mark our subtrees as not existing and stop the packet at this point, then start up with a new packet
|
||||
// is 1 byte for child colors + 3*NUMBER_OF_CHILDREN bytes for the actual colors + 1 byte for child trees.
|
||||
// There could be sub trees below this point, which might take many more bytes, but that's ok, because we can
|
||||
// always mark our subtrees as not existing and stop the packet at this point, then start up with a new packet
|
||||
// for the remaining sub trees.
|
||||
unsigned char childrenExistInTreeBits = 0;
|
||||
unsigned char childrenExistInPacketBits = 0;
|
||||
|
@ -972,7 +972,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
// Before we determine consider this further, let's see if it's in our LOD scope...
|
||||
float distance = distancesToChildren[i]; // params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0;
|
||||
float boundaryDistance = !params.viewFrustum ? 1 :
|
||||
boundaryDistanceForRenderLevel(childNode->getLevel() + params.boundaryLevelAdjust,
|
||||
boundaryDistanceForRenderLevel(childNode->getLevel() + params.boundaryLevelAdjust,
|
||||
params.octreeElementSizeScale);
|
||||
|
||||
if (!(distance < boundaryDistance)) {
|
||||
|
@ -1024,12 +1024,12 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
} // wants occlusion culling & isLeaf()
|
||||
|
||||
|
||||
bool shouldRender = !params.viewFrustum
|
||||
? true
|
||||
: childNode->calculateShouldRender(params.viewFrustum,
|
||||
bool shouldRender = !params.viewFrustum
|
||||
? true
|
||||
: childNode->calculateShouldRender(params.viewFrustum,
|
||||
params.octreeElementSizeScale, params.boundaryLevelAdjust);
|
||||
|
||||
// track some stats
|
||||
|
||||
// track some stats
|
||||
if (params.stats) {
|
||||
// don't need to check childNode here, because we can't get here with no childNode
|
||||
if (!shouldRender && childNode->isLeaf()) {
|
||||
|
@ -1040,29 +1040,29 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
params.stats->skippedOccluded(childNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// track children with actual color, only if the child wasn't previously in view!
|
||||
if (shouldRender && !childIsOccluded) {
|
||||
bool childWasInView = false;
|
||||
|
||||
|
||||
if (childNode && params.deltaViewFrustum && params.lastViewFrustum) {
|
||||
ViewFrustum::location location = childNode->inFrustum(*params.lastViewFrustum);
|
||||
|
||||
|
||||
// If we're a leaf, then either intersect or inside is considered "formerly in view"
|
||||
if (childNode->isLeaf()) {
|
||||
childWasInView = location != ViewFrustum::OUTSIDE;
|
||||
} else {
|
||||
childWasInView = location == ViewFrustum::INSIDE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If our child wasn't in view (or we're ignoring wasInView) then we add it to our sending items.
|
||||
// Or if we were previously in the view, but this node has changed since it was last sent, then we do
|
||||
// need to send it.
|
||||
if (!childWasInView ||
|
||||
(params.deltaViewFrustum &&
|
||||
if (!childWasInView ||
|
||||
(params.deltaViewFrustum &&
|
||||
childNode->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))){
|
||||
|
||||
|
||||
childrenColoredBits += (1 << (7 - originalIndex));
|
||||
inViewWithColorCount++;
|
||||
} else {
|
||||
|
@ -1080,10 +1080,10 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool continueThisLevel = true;
|
||||
|
||||
bool continueThisLevel = true;
|
||||
continueThisLevel = packetData->appendBitMask(childrenColoredBits);
|
||||
|
||||
|
||||
if (continueThisLevel) {
|
||||
bytesAtThisLevel += sizeof(childrenColoredBits); // keep track of byte count
|
||||
if (params.stats) {
|
||||
|
@ -1100,11 +1100,11 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
int bytesBeforeChild = packetData->getUncompressedSize();
|
||||
continueThisLevel = childNode->appendElementData(packetData);
|
||||
int bytesAfterChild = packetData->getUncompressedSize();
|
||||
|
||||
|
||||
if (!continueThisLevel) {
|
||||
break; // no point in continuing
|
||||
}
|
||||
|
||||
|
||||
bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child
|
||||
|
||||
// don't need to check childNode here, because we can't get here with no childNode
|
||||
|
@ -1138,7 +1138,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// We only need to keep digging, if there is at least one child that is inView, and not a leaf.
|
||||
keepDiggingDeeper = (inViewNotLeafCount > 0);
|
||||
|
||||
|
@ -1181,7 +1181,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
// XXXBHG - Note, this seems like the correct logic here, if we included the color in this packet, then
|
||||
// the LOD logic determined that the child nodes would not be visible... and if so, we shouldn't recurse
|
||||
// them further. But... for some time now the code has included and recursed into these child nodes, which
|
||||
// would likely still send the child content, even though the client wouldn't render it. This change is
|
||||
// would likely still send the child content, even though the client wouldn't render it. This change is
|
||||
// a major savings (~30%) and it seems to work correctly. But I want us to discuss as a group when we do
|
||||
// a voxel protocol review.
|
||||
//
|
||||
|
@ -1232,7 +1232,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
|
||||
// repair the child exists mask
|
||||
continueThisLevel = packetData->updatePriorBitMask(childExistsPlaceHolder, childrenExistInPacketBits);
|
||||
|
||||
|
||||
// If this is the last of the child exists bits, then we're actually be rolling out the entire tree
|
||||
if (params.stats && childrenExistInPacketBits == 0) {
|
||||
params.stats->childBitsRemoved(params.includeExistsBits, params.includeColor);
|
||||
|
@ -1241,7 +1241,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
if (!continueThisLevel) {
|
||||
break; // can't continue...
|
||||
}
|
||||
|
||||
|
||||
// Note: no need to move the pointer, cause we already stored this
|
||||
} // end if (childTreeBytesOut == 0)
|
||||
} // end if (oneAtBit(childrenExistInPacketBits, originalIndex))
|
||||
|
@ -1282,14 +1282,14 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node,
|
|||
printf("\n");
|
||||
**/
|
||||
|
||||
// if we were unable to fit this level in our packet, then rewind and add it to the node bag for
|
||||
// if we were unable to fit this level in our packet, then rewind and add it to the node bag for
|
||||
// sending later...
|
||||
if (continueThisLevel) {
|
||||
continueThisLevel = packetData->endLevel(thisLevelKey);
|
||||
} else {
|
||||
packetData->discardLevel(thisLevelKey);
|
||||
}
|
||||
|
||||
|
||||
if (!continueThisLevel) {
|
||||
bag.insert(node);
|
||||
|
||||
|
@ -1325,7 +1325,7 @@ bool Octree::readFromSVOFile(const char* fileName) {
|
|||
|
||||
unsigned char* dataAt = entireFile;
|
||||
unsigned long dataLength = fileLength;
|
||||
|
||||
|
||||
// before reading the file, check to see if this version of the Octree supports file versions
|
||||
if (getWantSVOfileVersions()) {
|
||||
// if so, read the first byte of the file and see if it matches the expected version code
|
||||
|
@ -1368,7 +1368,7 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* node) {
|
|||
|
||||
if(file.is_open()) {
|
||||
qDebug("saving to file %s...\n", fileName);
|
||||
|
||||
|
||||
// before reading the file, check to see if this version of the Octree supports file versions
|
||||
if (getWantSVOfileVersions()) {
|
||||
// if so, read the first byte of the file and see if it matches the expected version code
|
||||
|
@ -1377,7 +1377,7 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* node) {
|
|||
file.write(&expectedType, sizeof(expectedType));
|
||||
file.write(&expectedVersion, sizeof(expectedType));
|
||||
}
|
||||
|
||||
|
||||
OctreeElementBag nodeBag;
|
||||
// If we were given a specific node, start from there, otherwise start from root
|
||||
if (node) {
|
||||
|
@ -1392,7 +1392,7 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* node) {
|
|||
|
||||
while (!nodeBag.isEmpty()) {
|
||||
OctreeElement* subTree = nodeBag.extract();
|
||||
|
||||
|
||||
lockForRead(); // do tree locking down here so that we have shorter slices and less thread contention
|
||||
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS);
|
||||
bytesWritten = encodeTreeBitstream(subTree, &packetData, nodeBag, params);
|
||||
|
@ -1480,7 +1480,7 @@ void Octree::copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinat
|
|||
|
||||
while (!nodeBag.isEmpty()) {
|
||||
OctreeElement* subTree = nodeBag.extract();
|
||||
|
||||
|
||||
packetData.reset(); // reset between usage
|
||||
|
||||
// ask our tree to write a bitsteam
|
||||
|
@ -1513,7 +1513,7 @@ void Octree::doneEncoding(OctreeElement* node) {
|
|||
pthread_mutex_lock(&_encodeSetLock);
|
||||
_codesBeingEncoded.erase(node->getOctalCode());
|
||||
pthread_mutex_unlock(&_encodeSetLock);
|
||||
|
||||
|
||||
// if we have any pending delete codes, then delete them now.
|
||||
emptyDeleteQueue();
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ void OctreeElement::init(unsigned char * octalCode) {
|
|||
memcpy(_octalCode.buffer, octalCode, octalCodeLength);
|
||||
delete[] octalCode;
|
||||
}
|
||||
|
||||
|
||||
// set up the _children union
|
||||
_childBitmask = 0;
|
||||
_childrenExternal = false;
|
||||
|
@ -64,7 +64,7 @@ void OctreeElement::init(unsigned char * octalCode) {
|
|||
_singleChildrenCount++;
|
||||
#endif
|
||||
_childrenCount[0]++;
|
||||
|
||||
|
||||
// default pointers to child nodes to NULL
|
||||
#ifdef HAS_AUDIT_CHILDREN
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
|
@ -81,7 +81,7 @@ void OctreeElement::init(unsigned char * octalCode) {
|
|||
#ifdef SIMPLE_EXTERNAL_CHILDREN
|
||||
_children.single = NULL;
|
||||
#endif
|
||||
|
||||
|
||||
_isDirty = true;
|
||||
_shouldRender = false;
|
||||
_sourceUUIDKey = 0;
|
||||
|
@ -100,13 +100,13 @@ OctreeElement::~OctreeElement() {
|
|||
_octcodeMemoryUsage -= bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(getOctalCode()));
|
||||
delete[] _octalCode.pointer;
|
||||
}
|
||||
|
||||
|
||||
// delete all of this node's children, this also takes care of all population tracking data
|
||||
deleteAllChildren();
|
||||
}
|
||||
|
||||
void OctreeElement::markWithChangedTime() {
|
||||
_lastChanged = usecTimestampNow();
|
||||
void OctreeElement::markWithChangedTime() {
|
||||
_lastChanged = usecTimestampNow();
|
||||
notifyUpdateHooks(); // if the node has changed, notify our hooks
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ void OctreeElement::handleSubtreeChanged(Octree* myTree) {
|
|||
if (myTree->getShouldReaverage()) {
|
||||
calculateAverageFromChildren();
|
||||
}
|
||||
|
||||
|
||||
markWithChangedTime();
|
||||
}
|
||||
|
||||
|
@ -184,10 +184,10 @@ void OctreeElement::setShouldRender(bool shouldRender) {
|
|||
|
||||
void OctreeElement::calculateAABox() {
|
||||
glm::vec3 corner;
|
||||
|
||||
|
||||
// copy corner into box
|
||||
copyFirstVertexForCode(getOctalCode(),(float*)&corner);
|
||||
|
||||
|
||||
// this tells you the "size" of the voxel
|
||||
float voxelScale = 1 / powf(2, numberOfThreeBitSectionsInCode(getOctalCode()));
|
||||
_box.setBox(corner,voxelScale);
|
||||
|
@ -201,7 +201,7 @@ void OctreeElement::deleteChildAtIndex(int childIndex) {
|
|||
setChildAtIndex(childIndex, NULL);
|
||||
_isDirty = true;
|
||||
markWithChangedTime();
|
||||
|
||||
|
||||
// after deleting the child, check to see if we're a leaf
|
||||
if (isLeaf()) {
|
||||
_voxelNodeLeafCount++;
|
||||
|
@ -219,13 +219,13 @@ OctreeElement* OctreeElement::removeChildAtIndex(int childIndex) {
|
|||
setChildAtIndex(childIndex, NULL);
|
||||
_isDirty = true;
|
||||
markWithChangedTime();
|
||||
|
||||
|
||||
// after removing the child, check to see if we're a leaf
|
||||
if (isLeaf()) {
|
||||
_voxelNodeLeafCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef HAS_AUDIT_CHILDREN
|
||||
auditChildren("removeChildAtIndex()");
|
||||
#endif // def HAS_AUDIT_CHILDREN
|
||||
|
@ -238,12 +238,12 @@ void OctreeElement::auditChildren(const char* label) const {
|
|||
for (int childIndex = 0; childIndex < NUMBER_OF_CHILDREN; childIndex++) {
|
||||
OctreeElement* testChildNew = getChildAtIndex(childIndex);
|
||||
OctreeElement* testChildOld = _childrenArray[childIndex];
|
||||
|
||||
|
||||
if (testChildNew != testChildOld) {
|
||||
auditFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const bool alwaysReport = false; // set this to true to get additional debugging
|
||||
if (alwaysReport || auditFailed) {
|
||||
qDebug("%s... auditChildren() %s <<<< \n", label, (auditFailed ? "FAILED" : "PASSED"));
|
||||
|
@ -309,7 +309,7 @@ OctreeElement* OctreeElement::getChildAtIndex(int childIndex) const {
|
|||
return NULL;
|
||||
}
|
||||
} break;
|
||||
|
||||
|
||||
default : {
|
||||
return _children.external[childIndex];
|
||||
} break;
|
||||
|
@ -320,11 +320,11 @@ OctreeElement* OctreeElement::getChildAtIndex(int childIndex) const {
|
|||
PerformanceWarning warn(false,"getChildAtIndex",false,&_getChildAtIndexTime,&_getChildAtIndexCalls);
|
||||
OctreeElement* result = NULL;
|
||||
int childCount = getChildCount();
|
||||
|
||||
|
||||
#ifdef HAS_AUDIT_CHILDREN
|
||||
const char* caseStr = NULL;
|
||||
#endif
|
||||
|
||||
|
||||
switch (childCount) {
|
||||
case 0:
|
||||
#ifdef HAS_AUDIT_CHILDREN
|
||||
|
@ -424,7 +424,7 @@ OctreeElement* OctreeElement::getChildAtIndex(int childIndex) const {
|
|||
caseStr, result,_childrenArray[childIndex]);
|
||||
}
|
||||
#endif // def HAS_AUDIT_CHILDREN
|
||||
return result;
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -435,7 +435,7 @@ void OctreeElement::storeTwoChildren(OctreeElement* childOne, OctreeElement* chi
|
|||
|
||||
const int64_t minOffset = std::numeric_limits<int32_t>::min();
|
||||
const int64_t maxOffset = std::numeric_limits<int32_t>::max();
|
||||
|
||||
|
||||
bool forceExternal = true;
|
||||
if (!forceExternal && isBetween(offsetOne, maxOffset, minOffset) && isBetween(offsetTwo, maxOffset, minOffset)) {
|
||||
// if previously external, then clean it up...
|
||||
|
@ -455,7 +455,7 @@ void OctreeElement::storeTwoChildren(OctreeElement* childOne, OctreeElement* chi
|
|||
_twoChildrenOffsetCount++;
|
||||
} else {
|
||||
// encode in array
|
||||
|
||||
|
||||
// if not previously external, then allocate appropriately
|
||||
if (!_childrenExternal) {
|
||||
_childrenExternal = true;
|
||||
|
@ -516,7 +516,7 @@ void OctreeElement::encodeThreeOffsets(int64_t offsetOne, int64_t offsetTwo, int
|
|||
const uint64_t ENCODE_BITS = 21;
|
||||
const uint64_t ENCODE_MASK = 0xFFFFF;
|
||||
const uint64_t ENCODE_MASK_SIGN = 0x100000;
|
||||
|
||||
|
||||
uint64_t offsetEncodedOne, offsetEncodedTwo, offsetEncodedThree;
|
||||
if (offsetOne < 0) {
|
||||
offsetEncodedOne = ((-offsetOne & ENCODE_MASK) | ENCODE_MASK_SIGN);
|
||||
|
@ -544,13 +544,13 @@ void OctreeElement::storeThreeChildren(OctreeElement* childOne, OctreeElement* c
|
|||
int64_t offsetOne = (uint8_t*)childOne - (uint8_t*)this;
|
||||
int64_t offsetTwo = (uint8_t*)childTwo - (uint8_t*)this;
|
||||
int64_t offsetThree = (uint8_t*)childThree - (uint8_t*)this;
|
||||
|
||||
|
||||
const int64_t minOffset = -1048576; // what can fit in 20 bits // std::numeric_limits<int16_t>::min();
|
||||
const int64_t maxOffset = 1048576; // what can fit in 20 bits // std::numeric_limits<int16_t>::max();
|
||||
|
||||
|
||||
bool forceExternal = true;
|
||||
if (!forceExternal &&
|
||||
isBetween(offsetOne, maxOffset, minOffset) &&
|
||||
isBetween(offsetOne, maxOffset, minOffset) &&
|
||||
isBetween(offsetTwo, maxOffset, minOffset) &&
|
||||
isBetween(offsetThree, maxOffset, minOffset)) {
|
||||
// if previously external, then clean it up...
|
||||
|
@ -566,7 +566,7 @@ void OctreeElement::storeThreeChildren(OctreeElement* childOne, OctreeElement* c
|
|||
_threeChildrenOffsetCount++;
|
||||
} else {
|
||||
// encode in array
|
||||
|
||||
|
||||
// if not previously external, then allocate appropriately
|
||||
if (!_childrenExternal) {
|
||||
_childrenExternal = true;
|
||||
|
@ -609,13 +609,13 @@ void OctreeElement::checkStoreFourChildren(OctreeElement* childOne, OctreeElemen
|
|||
int64_t offsetTwo = (uint8_t*)childTwo - (uint8_t*)this;
|
||||
int64_t offsetThree = (uint8_t*)childThree - (uint8_t*)this;
|
||||
int64_t offsetFour = (uint8_t*)childFour - (uint8_t*)this;
|
||||
|
||||
|
||||
const int64_t minOffset = std::numeric_limits<int16_t>::min();
|
||||
const int64_t maxOffset = std::numeric_limits<int16_t>::max();
|
||||
|
||||
|
||||
bool forceExternal = true;
|
||||
if (!forceExternal &&
|
||||
isBetween(offsetOne, maxOffset, minOffset) &&
|
||||
isBetween(offsetOne, maxOffset, minOffset) &&
|
||||
isBetween(offsetTwo, maxOffset, minOffset) &&
|
||||
isBetween(offsetThree, maxOffset, minOffset) &&
|
||||
isBetween(offsetFour, maxOffset, minOffset)
|
||||
|
@ -671,10 +671,10 @@ void OctreeElement::deleteAllChildren() {
|
|||
_externalChildrenCount--;
|
||||
_childrenCount[childCount]--;
|
||||
} break;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// If we had externally stored children, clean them too.
|
||||
if (_childrenExternal && _children.external) {
|
||||
delete[] _children.external;
|
||||
|
@ -734,7 +734,7 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
_children.external[childIndex] = child;
|
||||
|
||||
_externalChildrenMemoryUsage += NUMBER_OF_CHILDREN * sizeof(OctreeElement*);
|
||||
|
||||
|
||||
} else if (previousChildCount == 2 && newChildCount == 1) {
|
||||
assert(child == NULL); // we are removing a child, so this must be true!
|
||||
OctreeElement* previousFirstChild = _children.external[firstIndex];
|
||||
|
@ -757,7 +757,7 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
|
||||
// Here's how we store things...
|
||||
// If we have 0 or 1 children, then we just store them in the _children.single;
|
||||
// If we have 2 children,
|
||||
// If we have 2 children,
|
||||
// then if we can we store them as 32 bit signed offsets from our own this pointer,
|
||||
// _children.offsetsTwoChildren[0]-[1]
|
||||
// these are 32 bit offsets
|
||||
|
@ -770,7 +770,7 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
clearAtBit(_childBitmask, childIndex);
|
||||
}
|
||||
int newChildCount = getChildCount();
|
||||
|
||||
|
||||
// track our population data
|
||||
if (previousChildCount != newChildCount) {
|
||||
_childrenCount[previousChildCount]--;
|
||||
|
@ -781,7 +781,7 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
if (previousChildCount == 0 && newChildCount == 0) {
|
||||
// nothing to do...
|
||||
} else if ((previousChildCount == 0 || previousChildCount == 1) && newChildCount == 1) {
|
||||
// If we had 0 children, and we're setting our first child or if we had 1 child, or we're resetting the same child,
|
||||
// If we had 0 children, and we're setting our first child or if we had 1 child, or we're resetting the same child,
|
||||
// then we can just store it in _children.single
|
||||
_children.single = child;
|
||||
} else if (previousChildCount == 1 && newChildCount == 0) {
|
||||
|
@ -803,21 +803,21 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
}
|
||||
|
||||
_singleChildrenCount--;
|
||||
storeTwoChildren(childOne, childTwo);
|
||||
storeTwoChildren(childOne, childTwo);
|
||||
} else if (previousChildCount == 2 && newChildCount == 1) {
|
||||
// If we had 2 children, and we're removing one, then we know we can go down to single mode
|
||||
//assert(child == NULL); // this is the only logical case
|
||||
|
||||
|
||||
int indexTwo = getNthBit(previousChildMask, 2);
|
||||
bool keepChildOne = indexTwo == childIndex;
|
||||
|
||||
OctreeElement* childOne;
|
||||
OctreeElement* childTwo;
|
||||
|
||||
retrieveTwoChildren(childOne, childTwo);
|
||||
retrieveTwoChildren(childOne, childTwo);
|
||||
|
||||
_singleChildrenCount++;
|
||||
|
||||
|
||||
if (keepChildOne) {
|
||||
_children.single = childOne;
|
||||
} else {
|
||||
|
@ -825,14 +825,14 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
}
|
||||
} else if (previousChildCount == 2 && newChildCount == 2) {
|
||||
// If we had 2 children, and still have 2, then we know we are resetting one of our existing children
|
||||
|
||||
|
||||
int indexOne = getNthBit(previousChildMask, 1);
|
||||
bool replaceChildOne = indexOne == childIndex;
|
||||
|
||||
// Get the existing two children out of their encoding...
|
||||
// Get the existing two children out of their encoding...
|
||||
OctreeElement* childOne;
|
||||
OctreeElement* childTwo;
|
||||
retrieveTwoChildren(childOne, childTwo);
|
||||
retrieveTwoChildren(childOne, childTwo);
|
||||
|
||||
if (replaceChildOne) {
|
||||
childOne = child;
|
||||
|
@ -841,7 +841,7 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
}
|
||||
|
||||
storeTwoChildren(childOne, childTwo);
|
||||
|
||||
|
||||
} else if (previousChildCount == 2 && newChildCount == 3) {
|
||||
// If we had 2 children, and now have 3, then we know we are going to an external case...
|
||||
|
||||
|
@ -850,8 +850,8 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
OctreeElement* childTwo;
|
||||
OctreeElement* childThree;
|
||||
|
||||
// Get the existing two children out of their encoding...
|
||||
retrieveTwoChildren(childOne, childTwo);
|
||||
// Get the existing two children out of their encoding...
|
||||
retrieveTwoChildren(childOne, childTwo);
|
||||
|
||||
// determine order of the existing children
|
||||
int indexOne = getNthBit(previousChildMask, 1);
|
||||
|
@ -870,7 +870,7 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
storeThreeChildren(childOne, childTwo, childThree);
|
||||
} else if (previousChildCount == 3 && newChildCount == 2) {
|
||||
// If we had 3 children, and now have 2, then we know we are going from an external case to a potential internal case
|
||||
|
||||
|
||||
// We need to determine which children we had, and which one we got rid of...
|
||||
int indexOne = getNthBit(previousChildMask, 1);
|
||||
int indexTwo = getNthBit(previousChildMask, 2);
|
||||
|
@ -882,8 +882,8 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
OctreeElement* childTwo;
|
||||
OctreeElement* childThree;
|
||||
|
||||
// Get the existing two children out of their encoding...
|
||||
retrieveThreeChildren(childOne, childTwo, childThree);
|
||||
// Get the existing two children out of their encoding...
|
||||
retrieveThreeChildren(childOne, childTwo, childThree);
|
||||
|
||||
if (removeChildOne) {
|
||||
childOne = childTwo;
|
||||
|
@ -894,10 +894,10 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
// removing child three, nothing to do.
|
||||
}
|
||||
|
||||
storeTwoChildren(childOne, childTwo);
|
||||
storeTwoChildren(childOne, childTwo);
|
||||
} else if (previousChildCount == 3 && newChildCount == 3) {
|
||||
// If we had 3 children, and now have 3, then we need to determine which item we're replacing...
|
||||
|
||||
|
||||
// We need to determine which children we had, and which one we got rid of...
|
||||
int indexOne = getNthBit(previousChildMask, 1);
|
||||
int indexTwo = getNthBit(previousChildMask, 2);
|
||||
|
@ -909,8 +909,8 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
OctreeElement* childTwo;
|
||||
OctreeElement* childThree;
|
||||
|
||||
// Get the existing two children out of their encoding...
|
||||
retrieveThreeChildren(childOne, childTwo, childThree);
|
||||
// Get the existing two children out of their encoding...
|
||||
retrieveThreeChildren(childOne, childTwo, childThree);
|
||||
|
||||
if (replaceChildOne) {
|
||||
childOne = child;
|
||||
|
@ -930,8 +930,8 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
OctreeElement* childThree;
|
||||
OctreeElement* childFour;
|
||||
|
||||
// Get the existing two children out of their encoding...
|
||||
retrieveThreeChildren(childOne, childTwo, childThree);
|
||||
// Get the existing two children out of their encoding...
|
||||
retrieveThreeChildren(childOne, childTwo, childThree);
|
||||
|
||||
// determine order of the existing children
|
||||
int indexOne = getNthBit(previousChildMask, 1);
|
||||
|
@ -959,9 +959,9 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
const int newChildCount = 4;
|
||||
_children.external = new OctreeElement*[newChildCount];
|
||||
memset(_children.external, 0, sizeof(OctreeElement*) * newChildCount);
|
||||
|
||||
|
||||
_externalChildrenMemoryUsage += newChildCount * sizeof(OctreeElement*);
|
||||
|
||||
|
||||
_children.external[0] = childOne;
|
||||
_children.external[1] = childTwo;
|
||||
_children.external[2] = childThree;
|
||||
|
@ -970,7 +970,7 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
} else if (previousChildCount == 4 && newChildCount == 3) {
|
||||
// If we had 4 children, and now have 3, then we know we are going from an external case to a potential internal case
|
||||
//assert(_children.external && _childrenExternal && previousChildCount == 4);
|
||||
|
||||
|
||||
// We need to determine which children we had, and which one we got rid of...
|
||||
int indexOne = getNthBit(previousChildMask, 1);
|
||||
int indexTwo = getNthBit(previousChildMask, 2);
|
||||
|
@ -1008,7 +1008,7 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
} else if (previousChildCount == newChildCount) {
|
||||
//assert(_children.external && _childrenExternal && previousChildCount >= 4);
|
||||
//assert(previousChildCount == newChildCount);
|
||||
|
||||
|
||||
// 4 or more children, one item being replaced, we know we're stored externally, we just need to find the one
|
||||
// that needs to be replaced and replace it.
|
||||
for (int ordinal = 1; ordinal <= 8; ordinal++) {
|
||||
|
@ -1024,12 +1024,12 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
// Growing case... previous must be 4 or greater
|
||||
//assert(_children.external && _childrenExternal && previousChildCount >= 4);
|
||||
//assert(previousChildCount == newChildCount-1);
|
||||
|
||||
|
||||
// 4 or more children, one item being added, we know we're stored externally, we just figure out where to insert
|
||||
// this child pointer into our external list
|
||||
OctreeElement** newExternalList = new OctreeElement*[newChildCount];
|
||||
memset(newExternalList, 0, sizeof(OctreeElement*) * newChildCount);
|
||||
|
||||
|
||||
int copiedCount = 0;
|
||||
for (int ordinal = 1; ordinal <= newChildCount; ordinal++) {
|
||||
int index = getNthBit(previousChildMask, ordinal);
|
||||
|
@ -1037,10 +1037,10 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
newExternalList[ordinal - 1] = _children.external[ordinal - 1];
|
||||
copiedCount++;
|
||||
} else {
|
||||
|
||||
|
||||
// insert our new child here...
|
||||
newExternalList[ordinal - 1] = child;
|
||||
|
||||
|
||||
// if we didn't copy all of our previous children, then we need to
|
||||
if (copiedCount < previousChildCount) {
|
||||
// our child needs to be inserted before this index, and everything else pushed out...
|
||||
|
@ -1060,10 +1060,10 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
//assert(_children.external && _childrenExternal && previousChildCount >= 4);
|
||||
//assert(previousChildCount == newChildCount+1);
|
||||
|
||||
// 4 or more children, one item being removed, we know we're stored externally, we just figure out which
|
||||
// 4 or more children, one item being removed, we know we're stored externally, we just figure out which
|
||||
// item to remove from our external list
|
||||
OctreeElement** newExternalList = new OctreeElement*[newChildCount];
|
||||
|
||||
|
||||
for (int ordinal = 1; ordinal <= previousChildCount; ordinal++) {
|
||||
int index = getNthBit(previousChildMask, ordinal);
|
||||
//assert(index != -1);
|
||||
|
@ -1090,7 +1090,7 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
if (getChildCount() == 4 && _childrenExternal && _children.external) {
|
||||
checkStoreFourChildren(_children.external[0], _children.external[1], _children.external[2], _children.external[3]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef HAS_AUDIT_CHILDREN
|
||||
_childrenArray[childIndex] = child;
|
||||
|
@ -1104,11 +1104,11 @@ void OctreeElement::setChildAtIndex(int childIndex, OctreeElement* child) {
|
|||
OctreeElement* OctreeElement::addChildAtIndex(int childIndex) {
|
||||
OctreeElement* childAt = getChildAtIndex(childIndex);
|
||||
if (!childAt) {
|
||||
// before adding a child, see if we're currently a leaf
|
||||
// before adding a child, see if we're currently a leaf
|
||||
if (isLeaf()) {
|
||||
_voxelNodeLeafCount--;
|
||||
}
|
||||
|
||||
|
||||
unsigned char* newChildCode = childOctalCode(getOctalCode(), childIndex);
|
||||
childAt = createNewElement(newChildCode);
|
||||
setChildAtIndex(childIndex, childAt);
|
||||
|
@ -1133,11 +1133,15 @@ bool OctreeElement::safeDeepDeleteChildAtIndex(int childIndex, int recursionCoun
|
|||
if (!childToDelete->isLeaf()) {
|
||||
// delete all it's children
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
deleteApproved = childToDelete->safeDeepDeleteChildAtIndex(i,recursionCount+1);
|
||||
if (!deleteApproved) {
|
||||
break; // no point in continuing...
|
||||
if (childToDelete->getChildAtIndex(i)) {
|
||||
deleteApproved = childToDelete->safeDeepDeleteChildAtIndex(i,recursionCount+1);
|
||||
if (!deleteApproved) {
|
||||
break; // no point in continuing...
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deleteApproved = true; // because we got here after checking that delete was approved
|
||||
}
|
||||
if (deleteApproved) {
|
||||
deleteChildAtIndex(childIndex);
|
||||
|
@ -1155,14 +1159,14 @@ void OctreeElement::printDebugDetails(const char* label) const {
|
|||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
OctreeElement* childAt = getChildAtIndex(i);
|
||||
if (childAt) {
|
||||
setAtBit(childBits,i);
|
||||
setAtBit(childBits,i);
|
||||
}
|
||||
}
|
||||
|
||||
qDebug("%s - Voxel at corner=(%f,%f,%f) size=%f\n isLeaf=%s isDirty=%s shouldRender=%s\n children=", label,
|
||||
_box.getCorner().x, _box.getCorner().y, _box.getCorner().z, _box.getScale(),
|
||||
debug::valueOf(isLeaf()), debug::valueOf(isDirty()), debug::valueOf(getShouldRender()));
|
||||
|
||||
|
||||
outputBits(childBits, false);
|
||||
qDebug("\n octalCode=");
|
||||
printOctalCode(getOctalCode());
|
||||
|
@ -1188,10 +1192,10 @@ ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) c
|
|||
// There are two types of nodes for which we want to "render"
|
||||
// 1) Leaves that are in the LOD
|
||||
// 2) Non-leaves are more complicated though... usually you don't want to render them, but if their children
|
||||
// wouldn't be rendered, then you do want to render them. But sometimes they have some children that ARE
|
||||
// wouldn't be rendered, then you do want to render them. But sometimes they have some children that ARE
|
||||
// in the LOD, and others that are not. In this case we want to render the parent, and none of the children.
|
||||
//
|
||||
// Since, if we know the camera position and orientation, we can know which of the corners is the "furthest"
|
||||
// Since, if we know the camera position and orientation, we can know which of the corners is the "furthest"
|
||||
// corner. We can use we can use this corner as our "voxel position" to do our distance calculations off of.
|
||||
// By doing this, we don't need to test each child voxel's position vs the LOD boundary
|
||||
bool OctreeElement::calculateShouldRender(const ViewFrustum* viewFrustum, float voxelScaleSize, int boundaryLevelAdjust) const {
|
||||
|
@ -1285,7 +1289,7 @@ void OctreeElement::notifyUpdateHooks() {
|
|||
}
|
||||
}
|
||||
|
||||
bool OctreeElement::findSpherePenetration(const glm::vec3& center, float radius,
|
||||
bool OctreeElement::findSpherePenetration(const glm::vec3& center, float radius,
|
||||
glm::vec3& penetration, void** penetratedObject) const {
|
||||
return _box.findSpherePenetration(center, radius, penetration);
|
||||
}
|
||||
|
@ -1307,7 +1311,7 @@ OctreeElement* OctreeElement::getOrCreateChildElementAt(float x, float y, float
|
|||
}
|
||||
// otherwise, we need to find which of our children we should recurse
|
||||
glm::vec3 ourCenter = _box.calcCenter();
|
||||
|
||||
|
||||
int childIndex = CHILD_UNKNOWN;
|
||||
// left half
|
||||
if (x > ourCenter.x) {
|
||||
|
@ -1352,13 +1356,13 @@ OctreeElement* OctreeElement::getOrCreateChildElementAt(float x, float y, float
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Now, check if we have a child at that location
|
||||
child = getChildAtIndex(childIndex);
|
||||
if (!child) {
|
||||
child = addChildAtIndex(childIndex);
|
||||
}
|
||||
|
||||
|
||||
// Now that we have the child to recurse down, let it answer the original question...
|
||||
return child->getOrCreateChildElementAt(x, y, z, s);
|
||||
}
|
||||
|
|
|
@ -13,9 +13,8 @@
|
|||
#include <SharedUtil.h>
|
||||
|
||||
#include "OctreePersistThread.h"
|
||||
#include "OctreeServer.h"
|
||||
|
||||
OctreePersistThread::OctreePersistThread(Octree* tree, const char* filename, int persistInterval) :
|
||||
OctreePersistThread::OctreePersistThread(Octree* tree, const QString& filename, int persistInterval) :
|
||||
_tree(tree),
|
||||
_filename(filename),
|
||||
_persistInterval(persistInterval),
|
||||
|
@ -27,65 +26,67 @@ bool OctreePersistThread::process() {
|
|||
|
||||
if (!_initialLoadComplete) {
|
||||
uint64_t loadStarted = usecTimestampNow();
|
||||
qDebug("loading Octrees from file: %s...\n", _filename);
|
||||
qDebug() << "loading Octrees from file: " << _filename << "...\n";
|
||||
|
||||
bool persistantFileRead;
|
||||
|
||||
_tree->lockForWrite();
|
||||
{
|
||||
PerformanceWarning warn(true, "Loading Octree File", true);
|
||||
persistantFileRead = _tree->readFromSVOFile(_filename);
|
||||
persistantFileRead = _tree->readFromSVOFile(_filename.toLocal8Bit().constData());
|
||||
}
|
||||
_tree->unlock();
|
||||
|
||||
_loadCompleted = time(0);
|
||||
uint64_t loadDone = usecTimestampNow();
|
||||
_loadTimeUSecs = loadDone - loadStarted;
|
||||
|
||||
|
||||
_tree->clearDirtyBit(); // the tree is clean since we just loaded it
|
||||
qDebug("DONE loading Octrees from file... fileRead=%s\n", debug::valueOf(persistantFileRead));
|
||||
|
||||
|
||||
unsigned long nodeCount = OctreeElement::getNodeCount();
|
||||
unsigned long internalNodeCount = OctreeElement::getInternalNodeCount();
|
||||
unsigned long leafNodeCount = OctreeElement::getLeafNodeCount();
|
||||
qDebug("Nodes after loading scene %lu nodes %lu internal %lu leaves\n", nodeCount, internalNodeCount, leafNodeCount);
|
||||
|
||||
double usecPerGet = (double)OctreeElement::getGetChildAtIndexTime() / (double)OctreeElement::getGetChildAtIndexCalls();
|
||||
qDebug() << "getChildAtIndexCalls=" << OctreeElement::getGetChildAtIndexCalls()
|
||||
qDebug() << "getChildAtIndexCalls=" << OctreeElement::getGetChildAtIndexCalls()
|
||||
<< " getChildAtIndexTime=" << OctreeElement::getGetChildAtIndexTime() << " perGet=" << usecPerGet << " \n";
|
||||
|
||||
|
||||
double usecPerSet = (double)OctreeElement::getSetChildAtIndexTime() / (double)OctreeElement::getSetChildAtIndexCalls();
|
||||
qDebug() << "setChildAtIndexCalls=" << OctreeElement::getSetChildAtIndexCalls()
|
||||
qDebug() << "setChildAtIndexCalls=" << OctreeElement::getSetChildAtIndexCalls()
|
||||
<< " setChildAtIndexTime=" << OctreeElement::getSetChildAtIndexTime() << " perset=" << usecPerSet << " \n";
|
||||
|
||||
_initialLoadComplete = true;
|
||||
_lastCheck = usecTimestampNow(); // we just loaded, no need to save again
|
||||
|
||||
emit loadCompleted();
|
||||
}
|
||||
|
||||
|
||||
if (isStillRunning()) {
|
||||
uint64_t MSECS_TO_USECS = 1000;
|
||||
uint64_t USECS_TO_SLEEP = 10 * MSECS_TO_USECS; // every 10ms
|
||||
usleep(USECS_TO_SLEEP);
|
||||
|
||||
|
||||
// do our updates then check to save...
|
||||
_tree->lockForWrite();
|
||||
_tree->update();
|
||||
_tree->unlock();
|
||||
|
||||
|
||||
uint64_t now = usecTimestampNow();
|
||||
uint64_t sinceLastSave = now - _lastCheck;
|
||||
uint64_t intervalToCheck = _persistInterval * MSECS_TO_USECS;
|
||||
|
||||
|
||||
if (sinceLastSave > intervalToCheck) {
|
||||
// check the dirty bit and persist here...
|
||||
_lastCheck = usecTimestampNow();
|
||||
if (_tree->isDirty()) {
|
||||
qDebug("saving Octrees to file %s...\n",_filename);
|
||||
_tree->writeToSVOFile(_filename);
|
||||
qDebug() << "saving Octrees to file " << _filename << "...\n";
|
||||
_tree->writeToSVOFile(_filename.toLocal8Bit().constData());
|
||||
_tree->clearDirtyBit(); // tree is clean after saving
|
||||
qDebug("DONE saving Octrees to file...\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return isStillRunning(); // keep running till they terminate us
|
||||
}
|
|
@ -11,27 +11,32 @@
|
|||
#ifndef __Octree_server__OctreePersistThread__
|
||||
#define __Octree_server__OctreePersistThread__
|
||||
|
||||
#include <QString>
|
||||
#include <GenericThread.h>
|
||||
#include <Octree.h>
|
||||
#include "Octree.h"
|
||||
|
||||
/// Generalized threaded processor for handling received inbound packets.
|
||||
class OctreePersistThread : public virtual GenericThread {
|
||||
/// Generalized threaded processor for handling received inbound packets.
|
||||
class OctreePersistThread : public GenericThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static const int DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds
|
||||
|
||||
OctreePersistThread(Octree* tree, const char* filename, int persistInterval = DEFAULT_PERSIST_INTERVAL);
|
||||
|
||||
OctreePersistThread(Octree* tree, const QString& filename, int persistInterval = DEFAULT_PERSIST_INTERVAL);
|
||||
|
||||
bool isInitialLoadComplete() const { return _initialLoadComplete; }
|
||||
|
||||
time_t* getLoadCompleted() { return &_loadCompleted; }
|
||||
uint64_t getLoadElapsedTime() const { return _loadTimeUSecs; }
|
||||
|
||||
signals:
|
||||
void loadCompleted();
|
||||
|
||||
protected:
|
||||
/// Implements generic processing behavior for this thread.
|
||||
virtual bool process();
|
||||
private:
|
||||
Octree* _tree;
|
||||
const char* _filename;
|
||||
QString _filename;
|
||||
int _persistInterval;
|
||||
bool _initialLoadComplete;
|
||||
|
|
@ -7,39 +7,43 @@
|
|||
//
|
||||
//
|
||||
|
||||
#include <QtScript/QScriptEngine>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <Octree.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <SharedUtil.h> // usecTimestampNow()
|
||||
#include <Octree.h>
|
||||
|
||||
#include <VoxelsScriptingInterface.h>
|
||||
#include "ParticlesScriptingInterface.h"
|
||||
|
||||
// This is not ideal, but adding script-engine as a linked library, will cause a circular reference
|
||||
// I'm open to other potential solutions. Could we change cmake to allow libraries to reference each others
|
||||
// headers, but not link to each other, this is essentially what this construct is doing, but would be
|
||||
// better to add includes to the include path, but not link
|
||||
#include "../../script-engine/src/ScriptEngine.h"
|
||||
|
||||
#include "ParticlesScriptingInterface.h"
|
||||
#include "Particle.h"
|
||||
|
||||
uint32_t Particle::_nextID = 0;
|
||||
VoxelsScriptingInterface* Particle::_voxelsScriptingInterface = NULL;
|
||||
ParticlesScriptingInterface* Particle::_particlesScriptingInterface = NULL;
|
||||
VoxelEditPacketSender* Particle::_voxelEditSender = NULL;
|
||||
ParticleEditPacketSender* Particle::_particleEditSender = NULL;
|
||||
|
||||
|
||||
Particle::Particle(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, glm::vec3 gravity,
|
||||
Particle::Particle(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, glm::vec3 gravity,
|
||||
float damping, bool inHand, QString updateScript, uint32_t id) {
|
||||
|
||||
|
||||
init(position, radius, color, velocity, gravity, damping, inHand, updateScript, id);
|
||||
}
|
||||
|
||||
Particle::Particle() {
|
||||
rgbColor noColor = { 0, 0, 0 };
|
||||
init(glm::vec3(0,0,0), 0, noColor, glm::vec3(0,0,0),
|
||||
init(glm::vec3(0,0,0), 0, noColor, glm::vec3(0,0,0),
|
||||
DEFAULT_GRAVITY, DEFAULT_DAMPING, NOT_IN_HAND, DEFAULT_SCRIPT, NEW_PARTICLE);
|
||||
}
|
||||
|
||||
Particle::~Particle() {
|
||||
}
|
||||
|
||||
void Particle::init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, glm::vec3 gravity,
|
||||
void Particle::init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, glm::vec3 gravity,
|
||||
float damping, bool inHand, QString updateScript, uint32_t id) {
|
||||
if (id == NEW_PARTICLE) {
|
||||
_id = _nextID;
|
||||
|
@ -51,7 +55,7 @@ void Particle::init(glm::vec3 position, float radius, rgbColor color, glm::vec3
|
|||
_lastEdited = now;
|
||||
_lastUpdated = now;
|
||||
_created = now; // will get updated as appropriate in setLifetime()
|
||||
|
||||
|
||||
_position = position;
|
||||
_radius = radius;
|
||||
_mass = 1.0f;
|
||||
|
@ -221,7 +225,7 @@ int Particle::readParticleDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
_script = tempString;
|
||||
dataAt += scriptLength;
|
||||
bytesRead += scriptLength;
|
||||
|
||||
|
||||
//printf("Particle::readParticleDataFromBuffer()... "); debugDump();
|
||||
}
|
||||
return bytesRead;
|
||||
|
@ -233,11 +237,11 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
|
|||
unsigned char* dataAt = data;
|
||||
processedBytes = 0;
|
||||
|
||||
// the first part of the data is our octcode...
|
||||
// the first part of the data is our octcode...
|
||||
int octets = numberOfThreeBitSectionsInCode(data);
|
||||
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
|
||||
|
||||
// we don't actually do anything with this octcode...
|
||||
|
||||
// we don't actually do anything with this octcode...
|
||||
dataAt += lengthOfOctcode;
|
||||
processedBytes += lengthOfOctcode;
|
||||
|
||||
|
@ -246,7 +250,7 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
|
|||
memcpy(&editID, dataAt, sizeof(editID));
|
||||
dataAt += sizeof(editID);
|
||||
processedBytes += sizeof(editID);
|
||||
|
||||
|
||||
// special case for handling "new" particles
|
||||
if (editID == NEW_PARTICLE) {
|
||||
// If this is a NEW_PARTICLE, then we assume that there's an additional uint32_t creatorToken, that
|
||||
|
@ -257,19 +261,19 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
|
|||
processedBytes += sizeof(creatorTokenID);
|
||||
newParticle.setCreatorTokenID(creatorTokenID);
|
||||
newParticle._newlyCreated = true;
|
||||
|
||||
|
||||
newParticle.setLifetime(0); // this guy is new!
|
||||
|
||||
} else {
|
||||
newParticle._id = editID;
|
||||
newParticle._newlyCreated = false;
|
||||
}
|
||||
}
|
||||
|
||||
// lastEdited
|
||||
memcpy(&newParticle._lastEdited, dataAt, sizeof(newParticle._lastEdited));
|
||||
dataAt += sizeof(newParticle._lastEdited);
|
||||
processedBytes += sizeof(newParticle._lastEdited);
|
||||
|
||||
|
||||
// radius
|
||||
memcpy(&newParticle._radius, dataAt, sizeof(newParticle._radius));
|
||||
dataAt += sizeof(newParticle._radius);
|
||||
|
@ -289,12 +293,12 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
|
|||
memcpy(&newParticle._velocity, dataAt, sizeof(newParticle._velocity));
|
||||
dataAt += sizeof(newParticle._velocity);
|
||||
processedBytes += sizeof(newParticle._velocity);
|
||||
|
||||
|
||||
// gravity
|
||||
memcpy(&newParticle._gravity, dataAt, sizeof(newParticle._gravity));
|
||||
dataAt += sizeof(newParticle._gravity);
|
||||
processedBytes += sizeof(newParticle._gravity);
|
||||
|
||||
|
||||
// damping
|
||||
memcpy(&newParticle._damping, dataAt, sizeof(newParticle._damping));
|
||||
dataAt += sizeof(newParticle._damping);
|
||||
|
@ -317,11 +321,11 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
|
|||
|
||||
const bool wantDebugging = false;
|
||||
if (wantDebugging) {
|
||||
printf("Particle::fromEditPacket()...\n");
|
||||
printf("Particle::fromEditPacket()...\n");
|
||||
printf(" Particle id in packet:%u\n", editID);
|
||||
newParticle.debugDump();
|
||||
}
|
||||
|
||||
|
||||
return newParticle;
|
||||
}
|
||||
|
||||
|
@ -335,7 +339,7 @@ void Particle::debugDump() const {
|
|||
printf(" color:%d,%d,%d\n", _color[0], _color[1], _color[2]);
|
||||
}
|
||||
|
||||
bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, int count, const ParticleDetail* details,
|
||||
bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, int count, const ParticleDetail* details,
|
||||
unsigned char* bufferOut, int sizeIn, int& sizeOut) {
|
||||
|
||||
bool success = true; // assume the best
|
||||
|
@ -344,13 +348,13 @@ bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, int count,
|
|||
|
||||
for (int i = 0; i < count && success; i++) {
|
||||
// get the octal code for the particle
|
||||
unsigned char* octcode = pointToOctalCode(details[i].position.x, details[i].position.y,
|
||||
unsigned char* octcode = pointToOctalCode(details[i].position.x, details[i].position.y,
|
||||
details[i].position.z, details[i].radius);
|
||||
|
||||
int octets = numberOfThreeBitSectionsInCode(octcode);
|
||||
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
|
||||
int lenfthOfEditData = lengthOfOctcode + expectedEditMessageBytes();
|
||||
|
||||
|
||||
// make sure we have room to copy this particle
|
||||
if (sizeOut + lenfthOfEditData > sizeIn) {
|
||||
success = false;
|
||||
|
@ -359,9 +363,9 @@ bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, int count,
|
|||
memcpy(copyAt, octcode, lengthOfOctcode);
|
||||
copyAt += lengthOfOctcode;
|
||||
sizeOut += lengthOfOctcode;
|
||||
|
||||
|
||||
// Now add our edit content details...
|
||||
|
||||
|
||||
// id
|
||||
memcpy(copyAt, &details[i].id, sizeof(details[i].id));
|
||||
copyAt += sizeof(details[i].id);
|
||||
|
@ -425,7 +429,7 @@ bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, int count,
|
|||
sizeOut += scriptLength;
|
||||
|
||||
bool wantDebugging = false;
|
||||
if (wantDebugging) {
|
||||
if (wantDebugging) {
|
||||
printf("encodeParticleEditMessageDetails()....\n");
|
||||
printf("Particle id :%u\n", details[i].id);
|
||||
printf(" nextID:%u\n", _nextID);
|
||||
|
@ -439,7 +443,7 @@ bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, int count,
|
|||
}
|
||||
|
||||
// adjust any internal timestamps to fix clock skew for this server
|
||||
void Particle::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) {
|
||||
void Particle::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) {
|
||||
unsigned char* dataAt = codeColorBuffer;
|
||||
int octets = numberOfThreeBitSectionsInCode(dataAt);
|
||||
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
|
||||
|
@ -462,7 +466,7 @@ void Particle::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssiz
|
|||
uint64_t lastEditedInServerTime = lastEditedInLocalTime + clockSkew;
|
||||
memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime));
|
||||
const bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
if (wantDebug) {
|
||||
qDebug("Particle::adjustEditPacketForClockSkew()...\n");
|
||||
qDebug() << " lastEditedInLocalTime: " << lastEditedInLocalTime << "\n";
|
||||
qDebug() << " clockSkew: " << clockSkew << "\n";
|
||||
|
@ -476,7 +480,7 @@ void Particle::update() {
|
|||
float elapsed = static_cast<float>(now - _lastUpdated);
|
||||
_lastUpdated = now;
|
||||
float timeElapsed = elapsed / static_cast<float>(USECS_PER_SECOND);
|
||||
|
||||
|
||||
// calculate our default shouldDie state... then allow script to change it if it wants...
|
||||
float velocityScalar = glm::length(getVelocity());
|
||||
const float STILL_MOVING = 0.05f / static_cast<float>(TREE_SCALE);
|
||||
|
@ -486,13 +490,13 @@ void Particle::update() {
|
|||
bool isInHand = getInHand();
|
||||
bool shouldDie = getShouldDie() || (!isInHand && !isStillMoving && isReallyOld);
|
||||
setShouldDie(shouldDie);
|
||||
|
||||
|
||||
runUpdateScript(); // allow the javascript to alter our state
|
||||
|
||||
|
||||
// If the ball is in hand, it doesn't move or have gravity effect it
|
||||
if (!isInHand) {
|
||||
_position += _velocity * timeElapsed;
|
||||
|
||||
|
||||
// handle bounces off the ground...
|
||||
if (_position.y <= 0) {
|
||||
_velocity = _velocity * glm::vec3(1,-1,1);
|
||||
|
@ -511,72 +515,59 @@ void Particle::update() {
|
|||
|
||||
void Particle::runUpdateScript() {
|
||||
if (!_script.isEmpty()) {
|
||||
ScriptEngine engine(_script); // no menu or controller interface...
|
||||
|
||||
QScriptEngine engine;
|
||||
|
||||
// register meta-type for glm::vec3 and rgbColor conversions
|
||||
registerMetaTypes(&engine);
|
||||
|
||||
if (_voxelEditSender) {
|
||||
engine.getVoxelsScriptingInterface()->setPacketSender(_voxelEditSender);
|
||||
}
|
||||
if (_particleEditSender) {
|
||||
engine.getParticlesScriptingInterface()->setPacketSender(_particleEditSender);
|
||||
}
|
||||
|
||||
// Add the Particle object
|
||||
ParticleScriptObject particleScriptable(this);
|
||||
QScriptValue particleValue = engine.newQObject(&particleScriptable);
|
||||
engine.globalObject().setProperty("Particle", particleValue);
|
||||
|
||||
QScriptValue treeScaleValue = engine.newVariant(QVariant(TREE_SCALE));
|
||||
engine.globalObject().setProperty("TREE_SCALE", TREE_SCALE);
|
||||
|
||||
QScriptValue result = engine.evaluate(_script);
|
||||
|
||||
engine.registerGlobalObject("Particle", &particleScriptable);
|
||||
|
||||
// init and evaluate the script, but return so we can emit the collision
|
||||
engine.evaluate();
|
||||
|
||||
particleScriptable.emitUpdate();
|
||||
|
||||
if (engine.hasUncaughtException()) {
|
||||
int line = engine.uncaughtExceptionLineNumber();
|
||||
qDebug() << "Uncaught exception at line" << line << ":" << result.toString() << "\n";
|
||||
|
||||
if (_voxelEditSender) {
|
||||
_voxelEditSender->releaseQueuedMessages();
|
||||
}
|
||||
if (_particleEditSender) {
|
||||
_particleEditSender->releaseQueuedMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Particle::collisionWithParticle(Particle* other) {
|
||||
if (!_script.isEmpty()) {
|
||||
ScriptEngine engine(_script); // no menu or controller interface...
|
||||
|
||||
QScriptEngine engine;
|
||||
|
||||
// register meta-type for glm::vec3 and rgbColor conversions
|
||||
registerMetaTypes(&engine);
|
||||
|
||||
if (_voxelEditSender) {
|
||||
engine.getVoxelsScriptingInterface()->setPacketSender(_voxelEditSender);
|
||||
}
|
||||
if (_particleEditSender) {
|
||||
engine.getParticlesScriptingInterface()->setPacketSender(_particleEditSender);
|
||||
}
|
||||
|
||||
// Add the Particle object
|
||||
ParticleScriptObject particleScriptable(this);
|
||||
QScriptValue particleValue = engine.newQObject(&particleScriptable);
|
||||
engine.globalObject().setProperty("Particle", particleValue);
|
||||
|
||||
QScriptValue treeScaleValue = engine.newVariant(QVariant(TREE_SCALE));
|
||||
engine.globalObject().setProperty("TREE_SCALE", TREE_SCALE);
|
||||
engine.registerGlobalObject("Particle", &particleScriptable);
|
||||
|
||||
// init and evaluate the script, but return so we can emit the collision
|
||||
engine.evaluate();
|
||||
|
||||
if (getVoxelsScriptingInterface()) {
|
||||
QScriptValue voxelScripterValue = engine.newQObject(getVoxelsScriptingInterface());
|
||||
engine.globalObject().setProperty("Voxels", voxelScripterValue);
|
||||
}
|
||||
|
||||
if (getParticlesScriptingInterface()) {
|
||||
QScriptValue particleScripterValue = engine.newQObject(getParticlesScriptingInterface());
|
||||
engine.globalObject().setProperty("Particles", particleScripterValue);
|
||||
}
|
||||
|
||||
QScriptValue result = engine.evaluate(_script);
|
||||
|
||||
ParticleScriptObject otherParticleScriptable(other);
|
||||
particleScriptable.emitCollisionWithParticle(&otherParticleScriptable);
|
||||
|
||||
if (getVoxelsScriptingInterface()) {
|
||||
getVoxelsScriptingInterface()->getPacketSender()->releaseQueuedMessages();
|
||||
if (_voxelEditSender) {
|
||||
_voxelEditSender->releaseQueuedMessages();
|
||||
}
|
||||
|
||||
if (getParticlesScriptingInterface()) {
|
||||
getParticlesScriptingInterface()->getPacketSender()->releaseQueuedMessages();
|
||||
}
|
||||
|
||||
if (engine.hasUncaughtException()) {
|
||||
int line = engine.uncaughtExceptionLineNumber();
|
||||
qDebug() << "Uncaught exception at line" << line << ":" << result.toString() << "\n";
|
||||
if (_particleEditSender) {
|
||||
_particleEditSender->releaseQueuedMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -584,45 +575,32 @@ void Particle::collisionWithParticle(Particle* other) {
|
|||
void Particle::collisionWithVoxel(VoxelDetail* voxelDetails) {
|
||||
if (!_script.isEmpty()) {
|
||||
|
||||
QScriptEngine engine;
|
||||
|
||||
// register meta-type for glm::vec3 and rgbColor conversions
|
||||
registerMetaTypes(&engine);
|
||||
|
||||
ScriptEngine engine(_script); // no menu or controller interface...
|
||||
|
||||
// setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so
|
||||
// we can use the same ones as our context.
|
||||
if (_voxelEditSender) {
|
||||
engine.getVoxelsScriptingInterface()->setPacketSender(_voxelEditSender);
|
||||
}
|
||||
if (_particleEditSender) {
|
||||
engine.getParticlesScriptingInterface()->setPacketSender(_particleEditSender);
|
||||
}
|
||||
|
||||
// Add the Particle object
|
||||
ParticleScriptObject particleScriptable(this);
|
||||
QScriptValue particleValue = engine.newQObject(&particleScriptable);
|
||||
engine.globalObject().setProperty("Particle", particleValue);
|
||||
|
||||
QScriptValue treeScaleValue = engine.newVariant(QVariant(TREE_SCALE));
|
||||
engine.globalObject().setProperty("TREE_SCALE", TREE_SCALE);
|
||||
engine.registerGlobalObject("Particle", &particleScriptable);
|
||||
|
||||
// init and evaluate the script, but return so we can emit the collision
|
||||
engine.evaluate();
|
||||
|
||||
if (getVoxelsScriptingInterface()) {
|
||||
QScriptValue voxelScripterValue = engine.newQObject(getVoxelsScriptingInterface());
|
||||
engine.globalObject().setProperty("Voxels", voxelScripterValue);
|
||||
}
|
||||
|
||||
if (getParticlesScriptingInterface()) {
|
||||
QScriptValue particleScripterValue = engine.newQObject(getParticlesScriptingInterface());
|
||||
engine.globalObject().setProperty("Particles", particleScripterValue);
|
||||
}
|
||||
|
||||
QScriptValue result = engine.evaluate(_script);
|
||||
|
||||
VoxelDetailScriptObject voxelDetailsScriptable(voxelDetails);
|
||||
particleScriptable.emitCollisionWithVoxel(&voxelDetailsScriptable);
|
||||
|
||||
if (getVoxelsScriptingInterface()) {
|
||||
getVoxelsScriptingInterface()->getPacketSender()->releaseQueuedMessages();
|
||||
if (_voxelEditSender) {
|
||||
_voxelEditSender->releaseQueuedMessages();
|
||||
}
|
||||
|
||||
if (getParticlesScriptingInterface()) {
|
||||
getParticlesScriptingInterface()->getPacketSender()->releaseQueuedMessages();
|
||||
}
|
||||
|
||||
if (engine.hasUncaughtException()) {
|
||||
int line = engine.uncaughtExceptionLineNumber();
|
||||
qDebug() << "Uncaught exception at line" << line << ":" << result.toString() << "\n";
|
||||
if (_particleEditSender) {
|
||||
_particleEditSender->releaseQueuedMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -631,7 +609,7 @@ void Particle::collisionWithVoxel(VoxelDetail* voxelDetails) {
|
|||
|
||||
void Particle::setLifetime(float lifetime) {
|
||||
uint64_t lifetimeInUsecs = lifetime * USECS_PER_SECOND;
|
||||
_created = usecTimestampNow() - lifetimeInUsecs;
|
||||
_created = usecTimestampNow() - lifetimeInUsecs;
|
||||
}
|
||||
|
||||
void Particle::copyChangedProperties(const Particle& other) {
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
class VoxelsScriptingInterface;
|
||||
class ParticlesScriptingInterface;
|
||||
class VoxelEditPacketSender;
|
||||
class ParticleEditPacketSender;
|
||||
|
||||
|
||||
const uint32_t NEW_PARTICLE = 0xFFFFFFFF;
|
||||
|
@ -48,19 +50,19 @@ const bool IN_HAND = true; // it's in a hand
|
|||
const bool NOT_IN_HAND = !IN_HAND; // it's not in a hand
|
||||
|
||||
class Particle {
|
||||
|
||||
|
||||
public:
|
||||
Particle();
|
||||
Particle(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity,
|
||||
glm::vec3 gravity = DEFAULT_GRAVITY, float damping = DEFAULT_DAMPING, bool inHand = NOT_IN_HAND,
|
||||
Particle(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity,
|
||||
glm::vec3 gravity = DEFAULT_GRAVITY, float damping = DEFAULT_DAMPING, bool inHand = NOT_IN_HAND,
|
||||
QString updateScript = DEFAULT_SCRIPT, uint32_t id = NEW_PARTICLE);
|
||||
|
||||
|
||||
/// creates an NEW particle from an PACKET_TYPE_PARTICLE_ADD_OR_EDIT edit data buffer
|
||||
static Particle fromEditPacket(unsigned char* data, int length, int& processedBytes);
|
||||
|
||||
static Particle fromEditPacket(unsigned char* data, int length, int& processedBytes);
|
||||
|
||||
virtual ~Particle();
|
||||
virtual void init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity,
|
||||
glm::vec3 gravity = DEFAULT_GRAVITY, float damping = DEFAULT_DAMPING, bool inHand = NOT_IN_HAND,
|
||||
virtual void init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity,
|
||||
glm::vec3 gravity = DEFAULT_GRAVITY, float damping = DEFAULT_DAMPING, bool inHand = NOT_IN_HAND,
|
||||
QString updateScript = DEFAULT_SCRIPT, uint32_t id = NEW_PARTICLE);
|
||||
|
||||
const glm::vec3& getPosition() const { return _position; }
|
||||
|
@ -72,7 +74,7 @@ public:
|
|||
const glm::vec3& getGravity() const { return _gravity; }
|
||||
bool getInHand() const { return _inHand; }
|
||||
float getDamping() const { return _damping; }
|
||||
|
||||
|
||||
/// The last updated/simulated time of this particle from the time perspective of the authoritative server/source
|
||||
uint64_t getLastUpdated() const { return _lastUpdated; }
|
||||
|
||||
|
@ -92,9 +94,9 @@ public:
|
|||
void setVelocity(const glm::vec3& value) { _velocity = value; }
|
||||
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
|
||||
void setColor(const xColor& value) {
|
||||
_color[RED_INDEX] = value.red;
|
||||
_color[GREEN_INDEX] = value.green;
|
||||
_color[BLUE_INDEX] = value.blue;
|
||||
_color[RED_INDEX] = value.red;
|
||||
_color[GREEN_INDEX] = value.green;
|
||||
_color[BLUE_INDEX] = value.blue;
|
||||
}
|
||||
void setRadius(float value) { _radius = value; }
|
||||
void setMass(float value);
|
||||
|
@ -104,15 +106,15 @@ public:
|
|||
void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; }
|
||||
void setScript(QString updateScript) { _script = updateScript; }
|
||||
void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; }
|
||||
|
||||
|
||||
bool appendParticleData(OctreePacketData* packetData) const;
|
||||
int readParticleDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
|
||||
static int expectedBytes();
|
||||
static int expectedEditMessageBytes();
|
||||
|
||||
static bool encodeParticleEditMessageDetails(PACKET_TYPE command, int count, const ParticleDetail* details,
|
||||
static bool encodeParticleEditMessageDetails(PACKET_TYPE command, int count, const ParticleDetail* details,
|
||||
unsigned char* bufferOut, int sizeIn, int& sizeOut);
|
||||
|
||||
|
||||
static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew);
|
||||
|
||||
void update();
|
||||
|
@ -120,22 +122,23 @@ public:
|
|||
void collisionWithVoxel(VoxelDetail* voxel);
|
||||
|
||||
void debugDump() const;
|
||||
|
||||
|
||||
// similar to assignment/copy, but it handles keeping lifetime accurate
|
||||
void copyChangedProperties(const Particle& other);
|
||||
|
||||
static VoxelsScriptingInterface* getVoxelsScriptingInterface() { return _voxelsScriptingInterface; }
|
||||
static ParticlesScriptingInterface* getParticlesScriptingInterface() { return _particlesScriptingInterface; }
|
||||
|
||||
static void setVoxelsScriptingInterface(VoxelsScriptingInterface* interface)
|
||||
{ _voxelsScriptingInterface = interface; }
|
||||
|
||||
static void setParticlesScriptingInterface(ParticlesScriptingInterface* interface)
|
||||
{ _particlesScriptingInterface = interface; }
|
||||
|
||||
static VoxelEditPacketSender* getVoxelEditPacketSender() { return _voxelEditSender; }
|
||||
static ParticleEditPacketSender* getParticleEditPacketSender() { return _particleEditSender; }
|
||||
|
||||
static void setVoxelEditPacketSender(VoxelEditPacketSender* interface)
|
||||
{ _voxelEditSender = interface; }
|
||||
|
||||
static void setParticleEditPacketSender(ParticleEditPacketSender* interface)
|
||||
{ _particleEditSender = interface; }
|
||||
|
||||
|
||||
protected:
|
||||
static VoxelsScriptingInterface* _voxelsScriptingInterface;
|
||||
static ParticlesScriptingInterface* _particlesScriptingInterface;
|
||||
static VoxelEditPacketSender* _voxelEditSender;
|
||||
static ParticleEditPacketSender* _particleEditSender;
|
||||
|
||||
void runUpdateScript();
|
||||
static QScriptValue vec3toScriptValue(QScriptEngine *engine, const glm::vec3 &vec3);
|
||||
|
@ -143,8 +146,8 @@ protected:
|
|||
static QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color);
|
||||
static void xColorFromScriptValue(const QScriptValue &object, xColor& color);
|
||||
|
||||
void setLifetime(float lifetime);
|
||||
|
||||
void setLifetime(float lifetime);
|
||||
|
||||
glm::vec3 _position;
|
||||
rgbColor _color;
|
||||
float _radius;
|
||||
|
@ -187,7 +190,7 @@ public slots:
|
|||
float getRadius() const { return _particle->getRadius(); }
|
||||
bool getShouldDie() { return _particle->getShouldDie(); }
|
||||
float getLifetime() const { return _particle->getLifetime(); }
|
||||
|
||||
|
||||
void setPosition(glm::vec3 value) { _particle->setPosition(value); }
|
||||
void setVelocity(glm::vec3 value) { _particle->setVelocity(value); }
|
||||
void setGravity(glm::vec3 value) { _particle->setGravity(value); }
|
||||
|
|
|
@ -19,12 +19,12 @@
|
|||
#include "ParticleEditPacketSender.h"
|
||||
#include "ParticleTree.h"
|
||||
|
||||
ParticleCollisionSystem::ParticleCollisionSystem(ParticleEditPacketSender* packetSender,
|
||||
ParticleCollisionSystem::ParticleCollisionSystem(ParticleEditPacketSender* packetSender,
|
||||
ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio, AvatarData* selfAvatar) {
|
||||
init(packetSender, particles, voxels, audio, selfAvatar);
|
||||
}
|
||||
|
||||
void ParticleCollisionSystem::init(ParticleEditPacketSender* packetSender,
|
||||
void ParticleCollisionSystem::init(ParticleEditPacketSender* packetSender,
|
||||
ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio, AvatarData* selfAvatar) {
|
||||
_packetSender = packetSender;
|
||||
_particles = particles;
|
||||
|
@ -67,8 +67,8 @@ void ParticleCollisionSystem::checkParticle(Particle* particle) {
|
|||
}
|
||||
|
||||
void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) {
|
||||
glm::vec3 center = particle->getPosition() * static_cast<float>(TREE_SCALE);
|
||||
float radius = particle->getRadius() * static_cast<float>(TREE_SCALE);
|
||||
glm::vec3 center = particle->getPosition() * (float)(TREE_SCALE);
|
||||
float radius = particle->getRadius() * (float)(TREE_SCALE);
|
||||
const float ELASTICITY = 0.4f;
|
||||
const float DAMPING = 0.0f;
|
||||
const float COLLISION_FREQUENCY = 0.5f;
|
||||
|
@ -79,17 +79,17 @@ void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) {
|
|||
// let the particles run their collision scripts if they have them
|
||||
particle->collisionWithVoxel(voxelDetails);
|
||||
|
||||
penetration /= static_cast<float>(TREE_SCALE);
|
||||
penetration /= (float)(TREE_SCALE);
|
||||
updateCollisionSound(particle, penetration, COLLISION_FREQUENCY);
|
||||
applyHardCollision(particle, penetration, ELASTICITY, DAMPING);
|
||||
|
||||
|
||||
delete voxelDetails; // cleanup returned details
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA) {
|
||||
glm::vec3 center = particleA->getPosition() * (float)TREE_SCALE;
|
||||
float radius = particleA->getRadius() * (float)TREE_SCALE;
|
||||
glm::vec3 center = particleA->getPosition() * (float)(TREE_SCALE);
|
||||
float radius = particleA->getRadius() * (float)(TREE_SCALE);
|
||||
//const float ELASTICITY = 0.4f;
|
||||
//const float DAMPING = 0.0f;
|
||||
const float COLLISION_FREQUENCY = 0.5f;
|
||||
|
@ -128,7 +128,7 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA)
|
|||
penetratedparticleEditHandle.updateParticle(particleB->getPosition(), particleB->getRadius(), particleB->getXColor(), particleB->getVelocity(),
|
||||
particleB->getGravity(), particleB->getDamping(), particleB->getInHand(), particleB->getScript());
|
||||
|
||||
penetration /= (float)TREE_SCALE;
|
||||
penetration /= (float)(TREE_SCALE);
|
||||
updateCollisionSound(particleA, penetration, COLLISION_FREQUENCY);
|
||||
}
|
||||
}
|
||||
|
@ -142,14 +142,14 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
|
|||
}
|
||||
|
||||
//printf("updateCollisionWithAvatars()...\n");
|
||||
glm::vec3 center = particle->getPosition() * static_cast<float>(TREE_SCALE);
|
||||
float radius = particle->getRadius() * static_cast<float>(TREE_SCALE);
|
||||
glm::vec3 center = particle->getPosition() * (float)(TREE_SCALE);
|
||||
float radius = particle->getRadius() * (float)(TREE_SCALE);
|
||||
const float ELASTICITY = 0.4f;
|
||||
const float DAMPING = 0.0f;
|
||||
const float COLLISION_FREQUENCY = 0.5f;
|
||||
glm::vec3 penetration;
|
||||
const PalmData* collidingPalm = NULL;
|
||||
|
||||
|
||||
// first check the selfAvatar if set...
|
||||
if (_selfAvatar) {
|
||||
AvatarData* avatar = (AvatarData*)_selfAvatar;
|
||||
|
@ -165,13 +165,13 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
|
|||
if (handData->findSpherePenetration(center, radius, penetration, collidingPalm)) {
|
||||
// TODO: dot collidingPalm and hand velocities and skip collision when they are moving apart.
|
||||
// apply a hard collision when ball collides with hand
|
||||
penetration /= static_cast<float>(TREE_SCALE);
|
||||
penetration /= (float)(TREE_SCALE);
|
||||
updateCollisionSound(particle, penetration, COLLISION_FREQUENCY);
|
||||
|
||||
|
||||
// determine if the palm that collided was moving, if so, then we add that palm velocity as well...
|
||||
glm::vec3 addedVelocity = NO_ADDED_VELOCITY;
|
||||
if (collidingPalm) {
|
||||
glm::vec3 palmVelocity = collidingPalm->getVelocity() / static_cast<float>(TREE_SCALE);
|
||||
glm::vec3 palmVelocity = collidingPalm->getVelocity() / (float)(TREE_SCALE);
|
||||
//printf("collidingPalm Velocity=%f,%f,%f\n", palmVelocity.x, palmVelocity.y, palmVelocity.z);
|
||||
addedVelocity = palmVelocity;
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
|
|||
applyHardCollision(particle, penetration, ELASTICITY, DAMPING, addedVelocity);
|
||||
} else if (avatar->findSpherePenetration(center, radius, penetration)) {
|
||||
// apply hard collision when particle collides with avatar
|
||||
penetration /= static_cast<float>(TREE_SCALE);
|
||||
penetration /= (float)(TREE_SCALE);
|
||||
updateCollisionSound(particle, penetration, COLLISION_FREQUENCY);
|
||||
glm::vec3 addedVelocity = avatar->getVelocity();
|
||||
applyHardCollision(particle, penetration, ELASTICITY, DAMPING, addedVelocity);
|
||||
|
@ -194,19 +194,19 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
|
|||
// TODO: dot collidingPalm and hand velocities and skip collision when they are moving apart.
|
||||
AvatarData* avatar = static_cast<AvatarData*>(node->getLinkedData());
|
||||
//printf("updateCollisionWithAvatars()...avatar=%p\n", avatar);
|
||||
|
||||
|
||||
// check hands...
|
||||
const HandData* handData = avatar->getHandData();
|
||||
|
||||
if (handData->findSpherePenetration(center, radius, penetration, collidingPalm)) {
|
||||
// apply a hard collision when ball collides with hand
|
||||
penetration /= static_cast<float>(TREE_SCALE);
|
||||
penetration /= (float)(TREE_SCALE);
|
||||
updateCollisionSound(particle, penetration, COLLISION_FREQUENCY);
|
||||
|
||||
// determine if the palm that collided was moving, if so, then we add that palm velocity as well...
|
||||
glm::vec3 addedVelocity = NO_ADDED_VELOCITY;
|
||||
if (collidingPalm) {
|
||||
glm::vec3 palmVelocity = collidingPalm->getVelocity() / static_cast<float>(TREE_SCALE);
|
||||
glm::vec3 palmVelocity = collidingPalm->getVelocity() / (float)(TREE_SCALE);
|
||||
//printf("collidingPalm Velocity=%f,%f,%f\n", palmVelocity.x, palmVelocity.y, palmVelocity.z);
|
||||
addedVelocity = palmVelocity;
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
|
|||
applyHardCollision(particle, penetration, ELASTICITY, DAMPING, addedVelocity);
|
||||
|
||||
} else if (avatar->findSpherePenetration(center, radius, penetration)) {
|
||||
penetration /= static_cast<float>(TREE_SCALE);
|
||||
penetration /= (float)(TREE_SCALE);
|
||||
updateCollisionSound(particle, penetration, COLLISION_FREQUENCY);
|
||||
glm::vec3 addedVelocity = avatar->getVelocity();
|
||||
applyHardCollision(particle, penetration, ELASTICITY, DAMPING, addedVelocity);
|
||||
|
@ -224,15 +224,15 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
|
|||
}
|
||||
|
||||
|
||||
void ParticleCollisionSystem::applyHardCollision(Particle* particle, const glm::vec3& penetration,
|
||||
void ParticleCollisionSystem::applyHardCollision(Particle* particle, const glm::vec3& penetration,
|
||||
float elasticity, float damping, const glm::vec3& addedVelocity) {
|
||||
//
|
||||
// Update the particle in response to a hard collision. Position will be reset exactly
|
||||
// to outside the colliding surface. Velocity will be modified according to elasticity.
|
||||
//
|
||||
// if elasticity = 0.0, collision is inelastic (vel normal to collision is lost)
|
||||
// if elasticity = 1.0, collision is 100% elastic.
|
||||
//
|
||||
// if elasticity = 1.0, collision is 100% elastic.
|
||||
//
|
||||
glm::vec3 position = particle->getPosition();
|
||||
glm::vec3 velocity = particle->getVelocity();
|
||||
|
||||
|
@ -240,7 +240,7 @@ void ParticleCollisionSystem::applyHardCollision(Particle* particle, const glm::
|
|||
float velocityDotPenetration = glm::dot(velocity, penetration);
|
||||
if (velocityDotPenetration > EPSILON) {
|
||||
position -= penetration;
|
||||
static float HALTING_VELOCITY = 0.2f / static_cast<float>(TREE_SCALE);
|
||||
static float HALTING_VELOCITY = 0.2f / (float)(TREE_SCALE);
|
||||
// cancel out the velocity component in the direction of penetration
|
||||
|
||||
float penetrationLength = glm::length(penetration);
|
||||
|
@ -258,36 +258,36 @@ void ParticleCollisionSystem::applyHardCollision(Particle* particle, const glm::
|
|||
printf("ParticleCollisionSystem::applyHardCollision() particle id:%d new velocity:%f,%f,%f inHand:%s\n",
|
||||
particle->getID(), velocity.x, velocity.y, velocity.z, debug::valueOf(particle->getInHand()));
|
||||
}
|
||||
|
||||
|
||||
ParticleEditHandle particleEditHandle(_packetSender, _particles, particle->getID());
|
||||
particleEditHandle.updateParticle(position, particle->getRadius(), particle->getXColor(), velocity,
|
||||
particle->getGravity(), particle->getDamping(), particle->getInHand(), particle->getScript());
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ParticleCollisionSystem::updateCollisionSound(Particle* particle, const glm::vec3 &penetration, float frequency) {
|
||||
|
||||
|
||||
// consider whether to have the collision make a sound
|
||||
const float AUDIBLE_COLLISION_THRESHOLD = 0.1f;
|
||||
const float COLLISION_LOUDNESS = 1.f;
|
||||
const float DURATION_SCALING = 0.004f;
|
||||
const float NOISE_SCALING = 0.1f;
|
||||
glm::vec3 velocity = particle->getVelocity() * static_cast<float>(TREE_SCALE);
|
||||
glm::vec3 velocity = particle->getVelocity() * (float)(TREE_SCALE);
|
||||
|
||||
/*
|
||||
// how do we want to handle this??
|
||||
//
|
||||
glm::vec3 gravity = particle->getGravity() * static_cast<float>(TREE_SCALE);
|
||||
|
||||
glm::vec3 gravity = particle->getGravity() * (float)(TREE_SCALE);
|
||||
|
||||
if (glm::length(gravity) > EPSILON) {
|
||||
// If gravity is on, remove the effect of gravity on velocity for this
|
||||
// frame, so that we are not constantly colliding with the surface
|
||||
// frame, so that we are not constantly colliding with the surface
|
||||
velocity -= _scale * glm::length(gravity) * GRAVITY_EARTH * deltaTime * glm::normalize(gravity);
|
||||
}
|
||||
*/
|
||||
float velocityTowardCollision = glm::dot(velocity, glm::normalize(penetration));
|
||||
float velocityTangentToCollision = glm::length(velocity) - velocityTowardCollision;
|
||||
|
||||
|
||||
if (velocityTowardCollision > AUDIBLE_COLLISION_THRESHOLD) {
|
||||
// Volume is proportional to collision velocity
|
||||
// Base frequency is modified upward by the angle of the collision
|
||||
|
|
|
@ -19,11 +19,14 @@
|
|||
#include <PacketHeaders.h>
|
||||
#include <UUID.h>
|
||||
#include <VoxelConstants.h>
|
||||
#include <ParticlesScriptingInterface.h>
|
||||
|
||||
#include <Sound.h>
|
||||
|
||||
#include "ScriptEngine.h"
|
||||
|
||||
const unsigned int VISUAL_DATA_CALLBACK_USECS = (1.0 / 60.0) * 1000 * 1000;
|
||||
|
||||
int ScriptEngine::_scriptNumber = 1;
|
||||
VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface;
|
||||
ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface;
|
||||
|
@ -31,7 +34,7 @@ ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface;
|
|||
static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* engine) {
|
||||
QUrl soundURL = QUrl(context->argument(0).toString());
|
||||
QScriptValue soundScriptValue = engine->newQObject(new Sound(soundURL), QScriptEngine::ScriptOwnership);
|
||||
|
||||
|
||||
return soundScriptValue;
|
||||
}
|
||||
|
||||
|
@ -41,7 +44,8 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems,
|
|||
_scriptContents = scriptContents;
|
||||
_isFinished = false;
|
||||
_isRunning = false;
|
||||
|
||||
_isInitialized = false;
|
||||
|
||||
// some clients will use these menu features
|
||||
_wantMenuItems = wantMenuItems;
|
||||
if (scriptMenuName) {
|
||||
|
@ -54,15 +58,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems,
|
|||
}
|
||||
_menu = menu;
|
||||
_controllerScriptingInterface = controllerScriptingInterface;
|
||||
|
||||
// hook up our interfaces
|
||||
if (!Particle::getVoxelsScriptingInterface()) {
|
||||
Particle::setVoxelsScriptingInterface(getVoxelsScriptingInterface());
|
||||
}
|
||||
|
||||
if (!Particle::getParticlesScriptingInterface()) {
|
||||
Particle::setParticlesScriptingInterface(getParticlesScriptingInterface());
|
||||
}
|
||||
}
|
||||
|
||||
ScriptEngine::~ScriptEngine() {
|
||||
|
@ -92,63 +87,89 @@ bool ScriptEngine::setScriptContents(const QString& scriptContents) {
|
|||
|
||||
Q_SCRIPT_DECLARE_QMETAOBJECT(AudioInjectorOptions, QObject*)
|
||||
|
||||
void ScriptEngine::run() {
|
||||
_isRunning = true;
|
||||
QScriptEngine engine;
|
||||
|
||||
void ScriptEngine::init() {
|
||||
if (_isInitialized) {
|
||||
return; // only initialize once
|
||||
}
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
_voxelsScriptingInterface.init();
|
||||
_particlesScriptingInterface.init();
|
||||
|
||||
// register meta-type for glm::vec3 conversions
|
||||
registerMetaTypes(&engine);
|
||||
|
||||
QScriptValue agentValue = engine.newQObject(this);
|
||||
engine.globalObject().setProperty("Agent", agentValue);
|
||||
|
||||
QScriptValue voxelScripterValue = engine.newQObject(&_voxelsScriptingInterface);
|
||||
engine.globalObject().setProperty("Voxels", voxelScripterValue);
|
||||
|
||||
QScriptValue particleScripterValue = engine.newQObject(&_particlesScriptingInterface);
|
||||
engine.globalObject().setProperty("Particles", particleScripterValue);
|
||||
|
||||
|
||||
QScriptValue soundConstructorValue = engine.newFunction(soundConstructor);
|
||||
QScriptValue soundMetaObject = engine.newQMetaObject(&Sound::staticMetaObject, soundConstructorValue);
|
||||
engine.globalObject().setProperty("Sound", soundMetaObject);
|
||||
|
||||
QScriptValue injectionOptionValue = engine.scriptValueFromQMetaObject<AudioInjectorOptions>();
|
||||
engine.globalObject().setProperty("AudioInjectionOptions", injectionOptionValue);
|
||||
|
||||
QScriptValue audioScriptingInterfaceValue = engine.newQObject(&_audioScriptingInterface);
|
||||
engine.globalObject().setProperty("Audio", audioScriptingInterfaceValue);
|
||||
|
||||
// register meta-type for glm::vec3 conversions
|
||||
registerMetaTypes(&_engine);
|
||||
|
||||
QScriptValue agentValue = _engine.newQObject(this);
|
||||
_engine.globalObject().setProperty("Agent", agentValue);
|
||||
|
||||
QScriptValue voxelScripterValue = _engine.newQObject(&_voxelsScriptingInterface);
|
||||
_engine.globalObject().setProperty("Voxels", voxelScripterValue);
|
||||
|
||||
QScriptValue particleScripterValue = _engine.newQObject(&_particlesScriptingInterface);
|
||||
_engine.globalObject().setProperty("Particles", particleScripterValue);
|
||||
|
||||
QScriptValue soundConstructorValue = _engine.newFunction(soundConstructor);
|
||||
QScriptValue soundMetaObject = _engine.newQMetaObject(&Sound::staticMetaObject, soundConstructorValue);
|
||||
_engine.globalObject().setProperty("Sound", soundMetaObject);
|
||||
|
||||
QScriptValue injectionOptionValue = _engine.scriptValueFromQMetaObject<AudioInjectorOptions>();
|
||||
_engine.globalObject().setProperty("AudioInjectionOptions", injectionOptionValue);
|
||||
|
||||
QScriptValue audioScriptingInterfaceValue = _engine.newQObject(&_audioScriptingInterface);
|
||||
_engine.globalObject().setProperty("Audio", audioScriptingInterfaceValue);
|
||||
|
||||
if (_controllerScriptingInterface) {
|
||||
QScriptValue controllerScripterValue = engine.newQObject(_controllerScriptingInterface);
|
||||
engine.globalObject().setProperty("Controller", controllerScripterValue);
|
||||
QScriptValue controllerScripterValue = _engine.newQObject(_controllerScriptingInterface);
|
||||
_engine.globalObject().setProperty("Controller", controllerScripterValue);
|
||||
}
|
||||
|
||||
QScriptValue treeScaleValue = engine.newVariant(QVariant(TREE_SCALE));
|
||||
engine.globalObject().setProperty("TREE_SCALE", treeScaleValue);
|
||||
|
||||
const unsigned int VISUAL_DATA_CALLBACK_USECS = (1.0 / 60.0) * 1000 * 1000;
|
||||
|
||||
|
||||
QScriptValue treeScaleValue = _engine.newVariant(QVariant(TREE_SCALE));
|
||||
_engine.globalObject().setProperty("TREE_SCALE", treeScaleValue);
|
||||
|
||||
// let the VoxelPacketSender know how frequently we plan to call it
|
||||
_voxelsScriptingInterface.getVoxelPacketSender()->setProcessCallIntervalHint(VISUAL_DATA_CALLBACK_USECS);
|
||||
_particlesScriptingInterface.getParticlePacketSender()->setProcessCallIntervalHint(VISUAL_DATA_CALLBACK_USECS);
|
||||
|
||||
//qDebug() << "Script:\n" << _scriptContents << "\n";
|
||||
|
||||
QScriptValue result = engine.evaluate(_scriptContents);
|
||||
}
|
||||
|
||||
void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) {
|
||||
QScriptValue value = _engine.newQObject(object);
|
||||
_engine.globalObject().setProperty(name, value);
|
||||
}
|
||||
|
||||
void ScriptEngine::evaluate() {
|
||||
if (!_isInitialized) {
|
||||
init();
|
||||
}
|
||||
|
||||
QScriptValue result = _engine.evaluate(_scriptContents);
|
||||
qDebug() << "Evaluated script.\n";
|
||||
|
||||
if (engine.hasUncaughtException()) {
|
||||
int line = engine.uncaughtExceptionLineNumber();
|
||||
|
||||
if (_engine.hasUncaughtException()) {
|
||||
int line = _engine.uncaughtExceptionLineNumber();
|
||||
qDebug() << "Uncaught exception at line" << line << ":" << result.toString() << "\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ScriptEngine::run() {
|
||||
if (!_isInitialized) {
|
||||
init();
|
||||
}
|
||||
_isRunning = true;
|
||||
|
||||
QScriptValue result = _engine.evaluate(_scriptContents);
|
||||
qDebug() << "Evaluated script.\n";
|
||||
|
||||
if (_engine.hasUncaughtException()) {
|
||||
int line = _engine.uncaughtExceptionLineNumber();
|
||||
qDebug() << "Uncaught exception at line" << line << ":" << result.toString() << "\n";
|
||||
}
|
||||
|
||||
timeval startTime;
|
||||
gettimeofday(&startTime, NULL);
|
||||
|
||||
|
||||
int thisFrame = 0;
|
||||
|
||||
while (!_isFinished) {
|
||||
|
@ -166,15 +187,15 @@ void ScriptEngine::run() {
|
|||
if (_isFinished) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
bool willSendVisualDataCallBack = false;
|
||||
if (_voxelsScriptingInterface.getVoxelPacketSender()->serversExist()) {
|
||||
if (_voxelsScriptingInterface.getVoxelPacketSender()->serversExist()) {
|
||||
// allow the scripter's call back to setup visual data
|
||||
willSendVisualDataCallBack = true;
|
||||
|
||||
|
||||
// release the queue of edit voxel messages.
|
||||
_voxelsScriptingInterface.getVoxelPacketSender()->releaseQueuedMessages();
|
||||
|
||||
|
||||
// since we're in non-threaded mode, call process so that the packets are sent
|
||||
if (!_voxelsScriptingInterface.getVoxelPacketSender()->isThreaded()) {
|
||||
_voxelsScriptingInterface.getVoxelPacketSender()->process();
|
||||
|
@ -184,23 +205,23 @@ void ScriptEngine::run() {
|
|||
if (_particlesScriptingInterface.getParticlePacketSender()->serversExist()) {
|
||||
// allow the scripter's call back to setup visual data
|
||||
willSendVisualDataCallBack = true;
|
||||
|
||||
|
||||
// release the queue of edit voxel messages.
|
||||
_particlesScriptingInterface.getParticlePacketSender()->releaseQueuedMessages();
|
||||
|
||||
|
||||
// since we're in non-threaded mode, call process so that the packets are sent
|
||||
if (!_particlesScriptingInterface.getParticlePacketSender()->isThreaded()) {
|
||||
_particlesScriptingInterface.getParticlePacketSender()->process();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (willSendVisualDataCallBack) {
|
||||
emit willSendVisualDataCallback();
|
||||
}
|
||||
|
||||
if (engine.hasUncaughtException()) {
|
||||
int line = engine.uncaughtExceptionLineNumber();
|
||||
qDebug() << "Uncaught exception at line" << line << ":" << engine.uncaughtException().toString() << "\n";
|
||||
if (_engine.hasUncaughtException()) {
|
||||
int line = _engine.uncaughtExceptionLineNumber();
|
||||
qDebug() << "Uncaught exception at line" << line << ":" << _engine.uncaughtException().toString() << "\n";
|
||||
}
|
||||
}
|
||||
cleanMenuItems();
|
||||
|
@ -213,9 +234,8 @@ void ScriptEngine::run() {
|
|||
emit finished();
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
void ScriptEngine::stop() {
|
||||
_isFinished = true;
|
||||
void ScriptEngine::stop() {
|
||||
_isFinished = true;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
|
||||
#include <AbstractMenuInterface.h>
|
||||
#include <AudioScriptingInterface.h>
|
||||
#include <ParticlesScriptingInterface.h>
|
||||
#include <VoxelsScriptingInterface.h>
|
||||
|
||||
class ParticlesScriptingInterface;
|
||||
|
||||
#include "AbstractControllerScriptingInterface.h"
|
||||
|
||||
const QString NO_SCRIPT("");
|
||||
|
@ -27,12 +28,12 @@ const QString NO_SCRIPT("");
|
|||
class ScriptEngine : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false,
|
||||
ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false,
|
||||
const char* scriptMenuName = NULL, AbstractMenuInterface* menu = NULL,
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
|
||||
|
||||
~ScriptEngine();
|
||||
|
||||
|
||||
/// Access the VoxelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener
|
||||
VoxelsScriptingInterface* getVoxelsScriptingInterface() { return &_voxelsScriptingInterface; }
|
||||
|
||||
|
@ -44,11 +45,15 @@ public:
|
|||
|
||||
void setupMenuItems();
|
||||
void cleanMenuItems();
|
||||
|
||||
|
||||
void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name
|
||||
|
||||
public slots:
|
||||
void run();
|
||||
void init();
|
||||
void run(); /// runs continuously until Agent.stop() is called
|
||||
void stop();
|
||||
|
||||
void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller
|
||||
|
||||
signals:
|
||||
void willSendAudioDataCallback();
|
||||
void willSendVisualDataCallback();
|
||||
|
@ -57,7 +62,8 @@ protected:
|
|||
QString _scriptContents;
|
||||
bool _isFinished;
|
||||
bool _isRunning;
|
||||
|
||||
bool _isInitialized;
|
||||
QScriptEngine _engine;
|
||||
|
||||
private:
|
||||
static VoxelsScriptingInterface _voxelsScriptingInterface;
|
||||
|
|
|
@ -24,10 +24,6 @@ set(EXTERNAL_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external)
|
|||
if (WIN32)
|
||||
# include headers for external libraries and InterfaceConfig.
|
||||
include_directories(${EXTERNAL_ROOT_DIR})
|
||||
else (WIN32)
|
||||
find_package(CURL REQUIRED)
|
||||
include_directories(${CURL_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${CURL_LIBRARY})
|
||||
endif (WIN32)
|
||||
|
||||
# link required libraries on UNIX
|
||||
|
|
|
@ -29,6 +29,8 @@ Assignment::Type Assignment::typeForNodeType(NODE_TYPE nodeType) {
|
|||
return Assignment::VoxelServerType;
|
||||
case NODE_TYPE_PARTICLE_SERVER:
|
||||
return Assignment::ParticleServerType;
|
||||
case NODE_TYPE_METAVOXEL_SERVER:
|
||||
return Assignment::MetavoxelServerType;
|
||||
default:
|
||||
return Assignment::AllTypes;
|
||||
}
|
||||
|
@ -180,6 +182,8 @@ const char* Assignment::getTypeName() const {
|
|||
return "voxel-server";
|
||||
case Assignment::ParticleServerType:
|
||||
return "particle-server";
|
||||
case Assignment::MetavoxelServerType:
|
||||
return "metavoxel-server";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ public:
|
|||
AgentType,
|
||||
VoxelServerType,
|
||||
ParticleServerType,
|
||||
MetavoxelServerType,
|
||||
AllTypes
|
||||
};
|
||||
|
||||
|
|
|
@ -11,22 +11,25 @@
|
|||
#ifndef __shared__GenericThread__
|
||||
#define __shared__GenericThread__
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
/// A basic generic "thread" class. Handles a single thread of control within the application. Can operate in non-threaded
|
||||
/// mode but caller must regularly call threadRoutine() method.
|
||||
class GenericThread {
|
||||
class GenericThread : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GenericThread();
|
||||
virtual ~GenericThread();
|
||||
|
||||
/// Call to start the thread.
|
||||
/// Call to start the thread.
|
||||
/// \param bool isThreaded true by default. false for non-threaded mode and caller must call threadRoutine() regularly.
|
||||
void initialize(bool isThreaded = true);
|
||||
|
||||
/// Call to stop the thread
|
||||
void terminate();
|
||||
|
||||
|
||||
/// If you're running in non-threaded mode, you must call this regularly
|
||||
void* threadRoutine();
|
||||
|
||||
|
@ -42,7 +45,7 @@ protected:
|
|||
|
||||
/// Unlocks all the resources of the thread.
|
||||
void unlock() { pthread_mutex_unlock(&_mutex); }
|
||||
|
||||
|
||||
bool isStillRunning() const { return !_stopThread; }
|
||||
|
||||
private:
|
||||
|
|
|
@ -156,6 +156,77 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3&
|
|||
newPenetration - (currentDirection * directionalComponent);
|
||||
}
|
||||
|
||||
bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& center, float radius, float& distance) {
|
||||
glm::vec3 relativeOrigin = origin - center;
|
||||
float c = glm::dot(relativeOrigin, relativeOrigin) - radius * radius;
|
||||
if (c < 0.0f) {
|
||||
distance = 0.0f;
|
||||
return true; // starts inside the sphere
|
||||
}
|
||||
float b = glm::dot(direction, relativeOrigin);
|
||||
float radicand = b * b - c;
|
||||
if (radicand < 0.0f) {
|
||||
return false; // doesn't hit the sphere
|
||||
}
|
||||
float t = -b - sqrtf(radicand);
|
||||
if (t < 0.0f) {
|
||||
return false; // doesn't hit the sphere
|
||||
}
|
||||
distance = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& start, const glm::vec3& end, float radius, float& distance) {
|
||||
if (start == end) {
|
||||
return findRaySphereIntersection(origin, direction, start, radius, distance); // handle degenerate case
|
||||
}
|
||||
glm::vec3 relativeOrigin = origin - start;
|
||||
glm::vec3 relativeEnd = end - start;
|
||||
float capsuleLength = glm::length(relativeEnd);
|
||||
relativeEnd /= capsuleLength;
|
||||
float originProjection = glm::dot(relativeEnd, relativeOrigin);
|
||||
glm::vec3 constant = relativeOrigin - relativeEnd * originProjection;
|
||||
float c = glm::dot(constant, constant) - radius * radius;
|
||||
if (c < 0.0f) { // starts inside cylinder
|
||||
if (originProjection < 0.0f) { // below start
|
||||
return findRaySphereIntersection(origin, direction, start, radius, distance);
|
||||
|
||||
} else if (originProjection > capsuleLength) { // above end
|
||||
return findRaySphereIntersection(origin, direction, end, radius, distance);
|
||||
|
||||
} else { // between start and end
|
||||
distance = 0.0f;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
glm::vec3 coefficient = direction - relativeEnd * glm::dot(relativeEnd, direction);
|
||||
float a = glm::dot(coefficient, coefficient);
|
||||
if (a == 0.0f) {
|
||||
return false; // parallel to enclosing cylinder
|
||||
}
|
||||
float b = 2.0f * glm::dot(constant, coefficient);
|
||||
float radicand = b * b - 4.0f * a * c;
|
||||
if (radicand < 0.0f) {
|
||||
return false; // doesn't hit the enclosing cylinder
|
||||
}
|
||||
float t = (-b - sqrtf(radicand)) / (2.0f * a);
|
||||
if (t < 0.0f) {
|
||||
return false; // doesn't hit the enclosing cylinder
|
||||
}
|
||||
glm::vec3 intersection = relativeOrigin + direction * t;
|
||||
float intersectionProjection = glm::dot(relativeEnd, intersection);
|
||||
if (intersectionProjection < 0.0f) { // below start
|
||||
return findRaySphereIntersection(origin, direction, start, radius, distance);
|
||||
|
||||
} else if (intersectionProjection > capsuleLength) { // above end
|
||||
return findRaySphereIntersection(origin, direction, end, radius, distance);
|
||||
}
|
||||
distance = t; // between start and end
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do line segments (r1p1.x, r1p1.y)--(r1p2.x, r1p2.y) and (r2p1.x, r2p1.y)--(r2p2.x, r2p2.y) intersect?
|
||||
// from: http://ptspts.blogspot.com/2010/06/how-to-determine-if-two-line-segments.html
|
||||
bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2) {
|
||||
|
|
|
@ -55,6 +55,12 @@ bool findCapsulePlanePenetration(const glm::vec3& capsuleStart, const glm::vec3&
|
|||
|
||||
glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration);
|
||||
|
||||
bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& center, float radius, float& distance);
|
||||
|
||||
bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& start, const glm::vec3& end, float radius, float& distance);
|
||||
|
||||
bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2);
|
||||
bool isOnSegment(float xi, float yi, float xj, float yj, float xk, float yk);
|
||||
int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk);
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include <QtNetwork/QHostInfo>
|
||||
#include <QtNetwork/QNetworkInterface>
|
||||
|
||||
static int hifiSockAddrMetaTypeId = qMetaTypeId<HifiSockAddr>();
|
||||
|
||||
HifiSockAddr::HifiSockAddr() :
|
||||
_address(),
|
||||
_port(0)
|
||||
|
|
|
@ -52,6 +52,7 @@ Node::~Node() {
|
|||
const char* NODE_TYPE_NAME_DOMAIN = "Domain";
|
||||
const char* NODE_TYPE_NAME_VOXEL_SERVER = "Voxel Server";
|
||||
const char* NODE_TYPE_NAME_PARTICLE_SERVER = "Particle Server";
|
||||
const char* NODE_TYPE_NAME_METAVOXEL_SERVER = "Metavoxel Server";
|
||||
const char* NODE_TYPE_NAME_AGENT = "Agent";
|
||||
const char* NODE_TYPE_NAME_AUDIO_MIXER = "Audio Mixer";
|
||||
const char* NODE_TYPE_NAME_AVATAR_MIXER = "Avatar Mixer";
|
||||
|
@ -68,6 +69,8 @@ const char* Node::getTypeName() const {
|
|||
return NODE_TYPE_NAME_VOXEL_SERVER;
|
||||
case NODE_TYPE_PARTICLE_SERVER:
|
||||
return NODE_TYPE_NAME_PARTICLE_SERVER;
|
||||
case NODE_TYPE_METAVOXEL_SERVER:
|
||||
return NODE_TYPE_NAME_METAVOXEL_SERVER;
|
||||
case NODE_TYPE_AGENT:
|
||||
return NODE_TYPE_NAME_AGENT;
|
||||
case NODE_TYPE_AUDIO_MIXER:
|
||||
|
|
|
@ -703,7 +703,8 @@ Node* NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType,
|
|||
node->lock();
|
||||
|
||||
if (node->getType() == NODE_TYPE_AUDIO_MIXER ||
|
||||
node->getType() == NODE_TYPE_VOXEL_SERVER) {
|
||||
node->getType() == NODE_TYPE_VOXEL_SERVER ||
|
||||
node->getType() == NODE_TYPE_METAVOXEL_SERVER) {
|
||||
// until the Audio class also uses our nodeList, we need to update
|
||||
// the lastRecvTimeUsecs for the audio mixer so it doesn't get killed and re-added continously
|
||||
node->setLastHeardMicrostamp(usecTimestampNow());
|
||||
|
|
|
@ -20,6 +20,7 @@ typedef char NODE_TYPE;
|
|||
const NODE_TYPE NODE_TYPE_DOMAIN = 'D';
|
||||
const NODE_TYPE NODE_TYPE_VOXEL_SERVER = 'V';
|
||||
const NODE_TYPE NODE_TYPE_PARTICLE_SERVER = 'P';
|
||||
const NODE_TYPE NODE_TYPE_METAVOXEL_SERVER = 'm';
|
||||
const NODE_TYPE NODE_TYPE_ENVIRONMENT_SERVER = 'E';
|
||||
const NODE_TYPE NODE_TYPE_AGENT = 'I';
|
||||
const NODE_TYPE NODE_TYPE_AUDIO_MIXER = 'M';
|
||||
|
|
|
@ -51,6 +51,7 @@ const PACKET_TYPE PACKET_TYPE_PARTICLE_DATA = 'v';
|
|||
const PACKET_TYPE PACKET_TYPE_PARTICLE_ADD_OR_EDIT = 'a';
|
||||
const PACKET_TYPE PACKET_TYPE_PARTICLE_ERASE = 'x';
|
||||
const PACKET_TYPE PACKET_TYPE_PARTICLE_ADD_RESPONSE = 'b';
|
||||
const PACKET_TYPE PACKET_TYPE_METAVOXEL_DATA = 't';
|
||||
|
||||
typedef char PACKET_VERSION;
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "ThreadedAssignment.h"
|
||||
|
||||
ThreadedAssignment::ThreadedAssignment(const unsigned char* dataBuffer, int numBytes) :
|
||||
|
@ -23,6 +26,26 @@ void ThreadedAssignment::setFinished(bool isFinished) {
|
|||
}
|
||||
}
|
||||
|
||||
void ThreadedAssignment::commonInit(const char* targetName, NODE_TYPE nodeType) {
|
||||
// change the logging target name while the assignment is running
|
||||
Logging::setTargetName(targetName);
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
nodeList->setOwnerType(nodeType);
|
||||
|
||||
QTimer* domainServerTimer = new QTimer(this);
|
||||
connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit()));
|
||||
domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000);
|
||||
|
||||
QTimer* pingNodesTimer = new QTimer(this);
|
||||
connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes()));
|
||||
pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000);
|
||||
|
||||
QTimer* silentNodeTimer = new QTimer(this);
|
||||
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
|
||||
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000);
|
||||
}
|
||||
|
||||
void ThreadedAssignment::checkInWithDomainServerOrExit() {
|
||||
if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
|
||||
setFinished(true);
|
||||
|
|
|
@ -23,6 +23,7 @@ public slots:
|
|||
|
||||
virtual void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) = 0;
|
||||
protected:
|
||||
void commonInit(const char* targetName, NODE_TYPE nodeType);
|
||||
bool _isFinished;
|
||||
private slots:
|
||||
void checkInWithDomainServerOrExit();
|
||||
|
|
|
@ -1,172 +0,0 @@
|
|||
//
|
||||
// UrlReader.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Tobias Schwinger on 3/21/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <new>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
// (Windows port is incomplete and the build files do not support CURL, yet)
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "UrlReader.h"
|
||||
|
||||
//
|
||||
// ATTENTION: A certain part of the implementation lives in inlined code
|
||||
// (see the bottom of the header file).
|
||||
//
|
||||
// Why? Because it allows stream parsing without having to call around a
|
||||
// lot (one static and one dynamic call per character if the parser just
|
||||
// reads one character at a time).
|
||||
//
|
||||
// Here is an overview of the code structure:
|
||||
//
|
||||
// readUrl
|
||||
// -> transferBegin (sets up state)
|
||||
// -> perform (starts CURL transfer)
|
||||
// -> (specialized, type-erased) callback_template
|
||||
// -> getInfo (fetches HTTP header, eventually initiates caching)
|
||||
// -> stream.begin (client code - called once)
|
||||
// -> feedBuffered (the buffering logic)
|
||||
// -> stream.transfer (client code - called repeatedly)
|
||||
// -> stream.end (client code - called when the transfer is done)
|
||||
// -> transferEnd (closes cache file, if used)
|
||||
//
|
||||
// "->" means "calls or inlines", here
|
||||
//
|
||||
|
||||
size_t const UrlReader::max_read_ahead = CURL_MAX_WRITE_SIZE;
|
||||
|
||||
char const* const UrlReader::success = "UrlReader: Success!";
|
||||
char const* const UrlReader::success_cached = "UrlReader: Using local file.";
|
||||
char const* const UrlReader::error_init_failed = "UrlReader: Initialization failed.";
|
||||
char const* const UrlReader::error_aborted = "UrlReader: Processing error.";
|
||||
char const* const UrlReader::error_buffer_overflow = "UrlReader: Buffer overflow.";
|
||||
char const* const UrlReader::error_leftover_input = "UrlReader: Incomplete processing.";
|
||||
|
||||
#define _curlPtr static_cast<CURL*>(_curlHandle)
|
||||
|
||||
UrlReader::UrlReader()
|
||||
: _curlHandle(0l), _xtraBuffer(0l), _errorStr(0l), _cacheReadBuffer(0l) {
|
||||
|
||||
_xtraBuffer = new(std::nothrow) char[max_read_ahead];
|
||||
if (! _xtraBuffer) { _errorStr = error_init_failed; return; }
|
||||
_curlHandle = curl_easy_init();
|
||||
if (! _curlHandle) { _errorStr = error_init_failed; return; }
|
||||
curl_easy_setopt(_curlPtr, CURLOPT_NOSIGNAL, 1l);
|
||||
curl_easy_setopt(_curlPtr, CURLOPT_FAILONERROR, 1l);
|
||||
curl_easy_setopt(_curlPtr, CURLOPT_FILETIME, 1l);
|
||||
curl_easy_setopt(_curlPtr, CURLOPT_ENCODING, "");
|
||||
}
|
||||
|
||||
UrlReader::~UrlReader() {
|
||||
|
||||
delete[] _xtraBuffer;
|
||||
delete[] _cacheReadBuffer;
|
||||
if (! _curlHandle) {
|
||||
return;
|
||||
}
|
||||
curl_easy_cleanup(_curlPtr);
|
||||
}
|
||||
|
||||
void UrlReader::perform(char const* url, transfer_callback* cb) {
|
||||
|
||||
curl_easy_setopt(_curlPtr, CURLOPT_URL, url);
|
||||
curl_easy_setopt(_curlPtr, CURLOPT_WRITEFUNCTION, cb);
|
||||
curl_easy_setopt(_curlPtr, CURLOPT_WRITEDATA, this);
|
||||
|
||||
CURLcode rc = curl_easy_perform(_curlPtr);
|
||||
|
||||
if (rc == CURLE_OK)
|
||||
{
|
||||
while (_xtraSize > 0 && _errorStr == success)
|
||||
cb(0l, 0, 0, this);
|
||||
}
|
||||
else if (_errorStr == success)
|
||||
_errorStr = curl_easy_strerror(rc);
|
||||
}
|
||||
|
||||
void UrlReader::transferBegin(void* stream, char const* cacheFile) {
|
||||
|
||||
_errorStr = success;
|
||||
_streamPtr = stream;
|
||||
_cacheFileName = cacheFile;
|
||||
_cacheFile = 0l;
|
||||
_cacheMode = no_cache;
|
||||
_xtraSize = ~size_t(0);
|
||||
}
|
||||
|
||||
void UrlReader::getInfo(char const*& url,
|
||||
char const*& type, int64_t& length, int64_t& stardate) {
|
||||
|
||||
// fetch information from HTTP header
|
||||
double clen;
|
||||
long time;
|
||||
curl_easy_getinfo(_curlPtr, CURLINFO_FILETIME, & time);
|
||||
curl_easy_getinfo(_curlPtr, CURLINFO_EFFECTIVE_URL, & url);
|
||||
curl_easy_getinfo(_curlPtr, CURLINFO_CONTENT_TYPE, & type);
|
||||
curl_easy_getinfo(_curlPtr, CURLINFO_CONTENT_LENGTH_DOWNLOAD, & clen);
|
||||
length = static_cast<int64_t>(clen);
|
||||
curl_easy_getinfo(_curlPtr, CURLINFO_FILETIME, & time);
|
||||
stardate = time;
|
||||
|
||||
// printLog("UrlReader: Ready to transfer from URL '%s'\n", url);
|
||||
|
||||
// check caching file time whether we actually want to download anything
|
||||
if (_cacheFileName != 0l) {
|
||||
struct stat s;
|
||||
stat(_cacheFileName, & s);
|
||||
if (time > s.st_mtime) {
|
||||
// file on server is newer -> update cache file
|
||||
_cacheFile = fopen(_cacheFileName, "wb");
|
||||
// printLog("UrlReader: Also writing content to cache file '%s'\n", _cacheFileName);
|
||||
if (_cacheFile != 0l) {
|
||||
_cacheMode = cache_write;
|
||||
}
|
||||
} else {
|
||||
// file on server is older -> use cache file
|
||||
if (! _cacheReadBuffer) {
|
||||
_cacheReadBuffer = new (std::nothrow) char[max_read_ahead];
|
||||
if (! _cacheReadBuffer) {
|
||||
// out of memory, no caching, have CURL catch it
|
||||
return;
|
||||
}
|
||||
}
|
||||
_cacheFile = fopen(_cacheFileName, "rb");
|
||||
// printLog("UrlReader: Delivering cached content from file '%s'\n", _cacheFileName);
|
||||
if (_cacheFile != 0l) {
|
||||
_cacheMode = cache_read;
|
||||
}
|
||||
// override error code returned by CURL when we abort the download
|
||||
_errorStr = success_cached;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UrlReader::transferEnd() {
|
||||
|
||||
if (_cacheFile != 0l) {
|
||||
fclose(_cacheFile);
|
||||
}
|
||||
}
|
||||
|
||||
#else // no-op version for incomplete Windows build:
|
||||
|
||||
UrlReader::UrlReader() : _curlHandle(0l) { }
|
||||
UrlReader::~UrlReader() { }
|
||||
void UrlReader::perform(char const* url, transfer_callback* cb) { }
|
||||
void UrlReader::transferBegin(void* stream, char const* cacheFile) { }
|
||||
void UrlReader::getInfo(char const*& url, char const*& type,
|
||||
int64_t& length, int64_t& stardate) { }
|
||||
void UrlReader::transferEnd() { }
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -1,305 +0,0 @@
|
|||
//
|
||||
// UrlReader.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Tobias Schwinger on 3/21/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __hifi__UrlReader__
|
||||
#define __hifi__UrlReader__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
//
|
||||
// UrlReader class that encapsulates a context for sequential data retrieval
|
||||
// via URLs. Use one per thread.
|
||||
//
|
||||
class UrlReader {
|
||||
public:
|
||||
|
||||
//
|
||||
// Constructor - performs initialization, never throws.
|
||||
//
|
||||
UrlReader();
|
||||
|
||||
//
|
||||
// Destructor - frees resources, never throws.
|
||||
//
|
||||
~UrlReader();
|
||||
|
||||
//
|
||||
// Reads data from an URL and forwards it to the instance of a class
|
||||
// fulfilling the ContentStream concept.
|
||||
//
|
||||
// The call protocol on the ContentStream is detailed as follows:
|
||||
//
|
||||
// 1. begin(char const* url,
|
||||
// char const* content_type, uint64_t bytes, uint64_t stardate)
|
||||
//
|
||||
// All information except 'url' is optional; 'content_type' can
|
||||
// be a null pointer - 'bytes' and 'stardate' can be equal to
|
||||
// to 'unavailable'.
|
||||
//
|
||||
// 2. transfer(char* buffer, size_t bytes)
|
||||
//
|
||||
// Called until all data has been received. The number of bytes
|
||||
// actually processed should be returned.
|
||||
// Unprocessed data is stored in an extra buffer whose size is
|
||||
// given by the constant UrlReader::max_read_ahead - it can be
|
||||
// assumed to be reasonably large for on-the-fly parsing.
|
||||
//
|
||||
// 3. end(bool ok)
|
||||
//
|
||||
// Called at the end of the transfer.
|
||||
//
|
||||
// Returns the same success code
|
||||
//
|
||||
template< class ContentStream >
|
||||
bool readUrl(char const* url, ContentStream& s, char const* cacheFile = 0l);
|
||||
|
||||
//
|
||||
// Returns a pointer to a static C-string that describes the error
|
||||
// condition.
|
||||
//
|
||||
inline char const* getError() const;
|
||||
|
||||
//
|
||||
// Can be called by the stream to set a user-defined error string.
|
||||
//
|
||||
inline void setError(char const* static_c_string);
|
||||
|
||||
//
|
||||
// Pointer to the C-string returned by a call to 'readUrl' when no
|
||||
// error occurred.
|
||||
//
|
||||
static char const* const success;
|
||||
|
||||
//
|
||||
// Pointer to the C-string returned by a call to 'readUrl' when no
|
||||
// error occurred and a local file has been read instead of the
|
||||
// network stream.
|
||||
//
|
||||
static char const* const success_cached;
|
||||
|
||||
//
|
||||
// Pointer to the C-string returned by a call to 'readUrl' when the
|
||||
// initialization has failed.
|
||||
//
|
||||
static char const* const error_init_failed;
|
||||
|
||||
//
|
||||
// Pointer to the C-string returned by a call to 'readUrl' when the
|
||||
// transfer has been aborted by the client.
|
||||
//
|
||||
static char const* const error_aborted;
|
||||
|
||||
//
|
||||
// Pointer to the C-string returned by a call to 'readUrl' when
|
||||
// leftover input from incomplete processing caused a buffer
|
||||
// overflow.
|
||||
//
|
||||
static char const* const error_buffer_overflow;
|
||||
|
||||
//
|
||||
// Pointer to the C-string return by a call to 'readUrl' when the
|
||||
// input provided was not completely consumed.
|
||||
//
|
||||
static char const* const error_leftover_input;
|
||||
|
||||
//
|
||||
// Constant of the maximum number of bytes that are buffered
|
||||
// between invocations of 'transfer'.
|
||||
//
|
||||
static size_t const max_read_ahead;
|
||||
|
||||
//
|
||||
// Constant representing absent information in the call to the
|
||||
// 'begin' member function of the target stream.
|
||||
//
|
||||
static int const unavailable = -1;
|
||||
|
||||
//
|
||||
// Constant for requesting to abort the current transfer when
|
||||
// returned by the 'transfer' member function of the target stream.
|
||||
//
|
||||
static size_t const abort = ~0u;
|
||||
|
||||
private:
|
||||
// instances of this class shall not be copied
|
||||
UrlReader(UrlReader const&); // = delete;
|
||||
UrlReader& operator=(UrlReader const&); // = delete;
|
||||
|
||||
inline bool isSuccess();
|
||||
|
||||
// entrypoints to compiled code
|
||||
|
||||
typedef size_t transfer_callback(char*, size_t, size_t, void*);
|
||||
|
||||
enum CacheMode { no_cache, cache_write, cache_read };
|
||||
|
||||
void transferBegin(void* stream, char const* cacheFile);
|
||||
void transferEnd();
|
||||
|
||||
void perform(char const* url, transfer_callback* transfer);
|
||||
|
||||
void getInfo(char const*& url,
|
||||
char const*& type, int64_t& length, int64_t& stardate);
|
||||
|
||||
// synthesized callback
|
||||
|
||||
template< class Stream > static size_t callback_template(char *input, size_t size,
|
||||
size_t nmemb, void* thiz);
|
||||
|
||||
template< class Stream > size_t feedBuffered(Stream* stream,
|
||||
char* input, size_t size);
|
||||
|
||||
// state
|
||||
|
||||
void* _curlHandle;
|
||||
char* _xtraBuffer;
|
||||
char const* _errorStr;
|
||||
void* _streamPtr;
|
||||
char const* _cacheFileName;
|
||||
FILE* _cacheFile;
|
||||
char* _cacheReadBuffer;
|
||||
CacheMode _cacheMode;
|
||||
size_t _xtraSize;
|
||||
};
|
||||
|
||||
// inline functions
|
||||
|
||||
inline char const* UrlReader::getError() const {
|
||||
|
||||
return _errorStr;
|
||||
}
|
||||
|
||||
bool UrlReader::isSuccess() {
|
||||
|
||||
return _errorStr == success || _errorStr == success_cached;
|
||||
}
|
||||
|
||||
template< class ContentStream >
|
||||
bool UrlReader::readUrl(char const* url, ContentStream& s, char const* cacheFile) {
|
||||
if (! _curlHandle) return false;
|
||||
|
||||
this->transferBegin(& s, cacheFile);
|
||||
this->perform(url, & callback_template<ContentStream>);
|
||||
this->transferEnd();
|
||||
bool ok = isSuccess();
|
||||
s.end(ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
inline void UrlReader::setError(char const* staticCstring) {
|
||||
|
||||
if (this->isSuccess())
|
||||
this->_errorStr = staticCstring;
|
||||
}
|
||||
|
||||
template< class Stream >
|
||||
size_t UrlReader::feedBuffered(Stream* stream, char* input, size_t size) {
|
||||
|
||||
size_t inputOffset = 0u;
|
||||
|
||||
while (true) {
|
||||
|
||||
char* buffer = input + inputOffset;
|
||||
size_t bytes = size - inputOffset;
|
||||
|
||||
// data in extra buffer?
|
||||
if (_xtraSize > 0) {
|
||||
|
||||
// fill extra buffer with beginning of input
|
||||
size_t fill = max_read_ahead - _xtraSize;
|
||||
if (bytes < fill) fill = bytes;
|
||||
memcpy(_xtraBuffer + _xtraSize, buffer, fill);
|
||||
// use extra buffer for next transfer
|
||||
buffer = _xtraBuffer;
|
||||
bytes = _xtraSize + fill;
|
||||
inputOffset += fill;
|
||||
}
|
||||
|
||||
// call 'transfer'
|
||||
size_t processed = stream->transfer(buffer, bytes);
|
||||
if (processed == abort) {
|
||||
|
||||
setError(error_aborted);
|
||||
return 0u;
|
||||
|
||||
} else if (! processed && ! input) {
|
||||
|
||||
setError(error_leftover_input);
|
||||
return 0u;
|
||||
}
|
||||
size_t unprocessed = bytes - processed;
|
||||
|
||||
// can switch to input buffer, now?
|
||||
if (buffer == _xtraBuffer && unprocessed <= inputOffset) {
|
||||
|
||||
_xtraSize = 0u;
|
||||
inputOffset -= unprocessed;
|
||||
|
||||
} else { // no? unprocessed data -> extra buffer
|
||||
|
||||
if (unprocessed > max_read_ahead) {
|
||||
|
||||
setError(error_buffer_overflow);
|
||||
return 0;
|
||||
}
|
||||
_xtraSize = unprocessed;
|
||||
memmove(_xtraBuffer, buffer + processed, unprocessed);
|
||||
|
||||
if (inputOffset == size || buffer != _xtraBuffer) {
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
} // while
|
||||
}
|
||||
|
||||
template< class Stream >
|
||||
size_t UrlReader::callback_template(char *input, size_t size, size_t nmemb, void* thiz) {
|
||||
|
||||
size_t result = 0u;
|
||||
UrlReader* me = static_cast<UrlReader*>(thiz);
|
||||
Stream* stream = static_cast<Stream*>(me->_streamPtr);
|
||||
size *= nmemb;
|
||||
|
||||
// first call?
|
||||
if (me->_xtraSize == ~size_t(0)) {
|
||||
|
||||
me->_xtraSize = 0u;
|
||||
// extract meta information and call 'begin'
|
||||
char const* url, * type;
|
||||
int64_t length, stardate;
|
||||
me->getInfo(url, type, length, stardate);
|
||||
if (me->_cacheMode != cache_read) {
|
||||
stream->begin(url, type, length, stardate);
|
||||
}
|
||||
}
|
||||
do {
|
||||
// will have to repeat from here when reading a local file
|
||||
|
||||
// read from cache file?
|
||||
if (me->_cacheMode == cache_read) {
|
||||
// change input buffer and start
|
||||
input = me->_cacheReadBuffer;
|
||||
size = fread(input, 1, max_read_ahead, me->_cacheFile);
|
||||
nmemb = 1;
|
||||
} else if (me->_cacheMode == cache_write) {
|
||||
fwrite(input, 1, size, me->_cacheFile);
|
||||
}
|
||||
|
||||
result = me->feedBuffered(stream, input, size);
|
||||
|
||||
} while (me->_cacheMode == cache_read && result != 0 && ! feof(me->_cacheFile));
|
||||
|
||||
return me->_cacheMode != cache_read ? result : 0;
|
||||
}
|
||||
|
||||
#endif /* defined(__hifi__UrlReader__) */
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
#include "VoxelTreeElement.h"
|
||||
#include "VoxelTree.h"
|
||||
|
||||
VoxelTreeElement::VoxelTreeElement(unsigned char* octalCode) : OctreeElement() {
|
||||
VoxelTreeElement::VoxelTreeElement(unsigned char* octalCode) : OctreeElement() {
|
||||
init(octalCode);
|
||||
};
|
||||
|
||||
|
@ -23,7 +23,7 @@ VoxelTreeElement::~VoxelTreeElement() {
|
|||
}
|
||||
|
||||
// This will be called primarily on addChildAt(), which means we're adding a child of our
|
||||
// own type to our own tree. This means we should initialize that child with any tree and type
|
||||
// own type to our own tree. This means we should initialize that child with any tree and type
|
||||
// specific settings that our children must have. One example is out VoxelSystem, which
|
||||
// we know must match ours.
|
||||
OctreeElement* VoxelTreeElement::createNewElement(unsigned char* octalCode) const {
|
||||
|
@ -63,10 +63,10 @@ bool VoxelTreeElement::appendElementData(OctreePacketData* packetData) const {
|
|||
}
|
||||
|
||||
|
||||
int VoxelTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args) {
|
||||
const int BYTES_PER_COLOR = 3;
|
||||
|
||||
int VoxelTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args) {
|
||||
const int BYTES_PER_COLOR = 3;
|
||||
|
||||
// pull the color for this child
|
||||
nodeColor newColor = { 128, 128, 128, 1};
|
||||
if (args.includeColor) {
|
||||
|
@ -82,11 +82,11 @@ uint8_t VoxelTreeElement::_nextIndex = INDEX_FOR_NULL + 1; // start at 1, 0 is r
|
|||
std::map<VoxelSystem*, uint8_t> VoxelTreeElement::_mapVoxelSystemPointersToIndex;
|
||||
std::map<uint8_t, VoxelSystem*> VoxelTreeElement::_mapIndexToVoxelSystemPointers;
|
||||
|
||||
VoxelSystem* VoxelTreeElement::getVoxelSystem() const {
|
||||
VoxelSystem* VoxelTreeElement::getVoxelSystem() const {
|
||||
if (_voxelSystemIndex > INDEX_FOR_NULL) {
|
||||
if (_mapIndexToVoxelSystemPointers.end() != _mapIndexToVoxelSystemPointers.find(_voxelSystemIndex)) {
|
||||
|
||||
VoxelSystem* voxelSystem = _mapIndexToVoxelSystemPointers[_voxelSystemIndex];
|
||||
VoxelSystem* voxelSystem = _mapIndexToVoxelSystemPointers[_voxelSystemIndex];
|
||||
return voxelSystem;
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ void VoxelTreeElement::setFalseColored(bool isFalseColored) {
|
|||
if (_falseColored && !isFalseColored) {
|
||||
memcpy(&_currentColor,&_trueColor,sizeof(nodeColor));
|
||||
}
|
||||
_falseColored = isFalseColored;
|
||||
_falseColored = isFalseColored;
|
||||
_isDirty = true;
|
||||
_density = 1.0f; // If color set, assume leaf, re-averaging will update density if needed.
|
||||
markWithChangedTime();
|
||||
|
@ -167,7 +167,7 @@ void VoxelTreeElement::calculateAverageFromChildren() {
|
|||
density += childAt->getDensity();
|
||||
}
|
||||
}
|
||||
density /= (float) NUMBER_OF_CHILDREN;
|
||||
density /= (float) NUMBER_OF_CHILDREN;
|
||||
//
|
||||
// The VISIBLE_ABOVE_DENSITY sets the density of matter above which an averaged color voxel will
|
||||
// be set. It is an important physical constant in our universe. A number below 0.5 will cause
|
||||
|
@ -175,9 +175,9 @@ void VoxelTreeElement::calculateAverageFromChildren() {
|
|||
// less data, which is (probably) going to be preferable because it gives a sense that there is
|
||||
// something out there to go investigate. A number above 0.5 would cause the world to become
|
||||
// more 'empty' at a distance. Exactly 0.5 would match the physical world, at least for materials
|
||||
// that are not shiny and have equivalent ambient reflectance.
|
||||
// that are not shiny and have equivalent ambient reflectance.
|
||||
//
|
||||
const float VISIBLE_ABOVE_DENSITY = 0.10f;
|
||||
const float VISIBLE_ABOVE_DENSITY = 0.10f;
|
||||
nodeColor newColor = { 0, 0, 0, 0};
|
||||
if (density > VISIBLE_ABOVE_DENSITY) {
|
||||
// The density of material in the space of the voxel sets whether it is actually colored
|
||||
|
@ -188,14 +188,14 @@ void VoxelTreeElement::calculateAverageFromChildren() {
|
|||
// set the alpha to 1 to indicate that this isn't transparent
|
||||
newColor[3] = 1;
|
||||
}
|
||||
// Set the color from the average of the child colors, and update the density
|
||||
// Set the color from the average of the child colors, and update the density
|
||||
setColor(newColor);
|
||||
setDensity(density);
|
||||
}
|
||||
|
||||
// will detect if children are leaves AND the same color
|
||||
// and in that case will delete the children and make this node
|
||||
// a leaf, returns TRUE if all the leaves are collapsed into a
|
||||
// a leaf, returns TRUE if all the leaves are collapsed into a
|
||||
// single node
|
||||
bool VoxelTreeElement::collapseChildren() {
|
||||
// scan children, verify that they are ALL present and accounted for
|
||||
|
@ -213,15 +213,15 @@ bool VoxelTreeElement::collapseChildren() {
|
|||
red = childAt->getColor()[0];
|
||||
green = childAt->getColor()[1];
|
||||
blue = childAt->getColor()[2];
|
||||
} else if (red != childAt->getColor()[0] ||
|
||||
} else if (red != childAt->getColor()[0] ||
|
||||
green != childAt->getColor()[1] || blue != childAt->getColor()[2]) {
|
||||
allChildrenMatch=false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (allChildrenMatch) {
|
||||
//qDebug("allChildrenMatch: pruning tree\n");
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
|
@ -230,9 +230,9 @@ bool VoxelTreeElement::collapseChildren() {
|
|||
setChildAtIndex(i, NULL); // set it to NULL
|
||||
}
|
||||
nodeColor collapsedColor;
|
||||
collapsedColor[0]=red;
|
||||
collapsedColor[1]=green;
|
||||
collapsedColor[2]=blue;
|
||||
collapsedColor[0]=red;
|
||||
collapsedColor[1]=green;
|
||||
collapsedColor[2]=blue;
|
||||
collapsedColor[3]=1; // color is set
|
||||
setColor(collapsedColor);
|
||||
}
|
||||
|
@ -240,7 +240,7 @@ bool VoxelTreeElement::collapseChildren() {
|
|||
}
|
||||
|
||||
|
||||
bool VoxelTreeElement::findSpherePenetration(const glm::vec3& center, float radius,
|
||||
bool VoxelTreeElement::findSpherePenetration(const glm::vec3& center, float radius,
|
||||
glm::vec3& penetration, void** penetratedObject) const {
|
||||
if (_box.findSpherePenetration(center, radius, penetration)) {
|
||||
|
||||
|
@ -254,7 +254,7 @@ bool VoxelTreeElement::findSpherePenetration(const glm::vec3& center, float radi
|
|||
voxelDetails->red = getTrueColor()[RED_INDEX];
|
||||
voxelDetails->green = getTrueColor()[GREEN_INDEX];
|
||||
voxelDetails->blue = getTrueColor()[BLUE_INDEX];
|
||||
|
||||
|
||||
*penetratedObject = (void*)voxelDetails;
|
||||
}
|
||||
return true;
|
||||
|
|
11
tools/mtc/CMakeLists.txt
Normal file
11
tools/mtc/CMakeLists.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
set(TARGET_NAME mtc)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiProject.cmake)
|
||||
setup_hifi_project(${TARGET_NAME} TRUE)
|
||||
|
||||
|
180
tools/mtc/src/main.cpp
Normal file
180
tools/mtc/src/main.cpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
//
|
||||
// main.cpp
|
||||
// mtc
|
||||
//
|
||||
// Created by Andrzej Kapolka on 12/31/13.
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QFile>
|
||||
#include <QList>
|
||||
#include <QRegExp>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QTextStream>
|
||||
#include <QtDebug>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class Class {
|
||||
public:
|
||||
QString name;
|
||||
QStringList bases;
|
||||
};
|
||||
|
||||
class Streamable {
|
||||
public:
|
||||
Class clazz;
|
||||
QStringList fields;
|
||||
};
|
||||
|
||||
void processInput(QTextStream& in, QList<Streamable>* streamables) {
|
||||
Class clazz;
|
||||
Streamable currentStreamable;
|
||||
|
||||
QRegExp exp(
|
||||
"(/\\*.*\\*/)|" // multi-line comments
|
||||
"(//.*\n)|" // single-line comments
|
||||
"(\\s*#.*\n)|" // preprocessor definitions
|
||||
"(\\s*STREAMABLE\\s+)|" // STREAMABLE tag for classes
|
||||
"(\\s*STREAM\\s+.*;)|" // STREAM tag for fields
|
||||
"(\\s*class\\s+[^;]+\\{)" // class definition
|
||||
);
|
||||
exp.setMinimal(true);
|
||||
|
||||
QRegExp classExp("class (\\w+) ?:?([^:]*)\\{");
|
||||
|
||||
// read in the entire input and look for matches with our expression
|
||||
QString all = in.readAll();
|
||||
for (int off = 0; (off = exp.indexIn(all, off)) != -1; off += exp.matchedLength()) {
|
||||
QString match = exp.cap().simplified();
|
||||
if (match.startsWith("/*") || match.startsWith("//") || match.startsWith('#')) {
|
||||
continue; // comment, preprocessor definition
|
||||
}
|
||||
if (match.startsWith("STREAMABLE")) {
|
||||
if (clazz.name.isEmpty()) {
|
||||
cerr << "Found STREAMABLE marker before class definition." << endl;
|
||||
continue;
|
||||
}
|
||||
if (!currentStreamable.clazz.name.isEmpty()) {
|
||||
streamables->append(currentStreamable);
|
||||
}
|
||||
currentStreamable.clazz = clazz;
|
||||
currentStreamable.fields.clear();
|
||||
|
||||
} else if (match.startsWith("STREAM")) {
|
||||
match.chop(1); // get rid of the semicolon
|
||||
match = match.trimmed(); // and any space before it
|
||||
currentStreamable.fields.append(match.mid(match.lastIndexOf(' ') + 1));
|
||||
|
||||
} else { // match.startsWith("class")
|
||||
classExp.exactMatch(match);
|
||||
clazz.name = classExp.cap(1);
|
||||
clazz.bases.clear();
|
||||
foreach (const QString& bstr, classExp.cap(2).split(',')) {
|
||||
QString base = bstr.trimmed();
|
||||
if (!base.isEmpty() && base.startsWith("STREAM")) {
|
||||
clazz.bases.append(base.mid(base.lastIndexOf(' ') + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!currentStreamable.clazz.name.isEmpty()) {
|
||||
streamables->append(currentStreamable);
|
||||
}
|
||||
}
|
||||
|
||||
void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
|
||||
foreach (const Streamable& str, streamables) {
|
||||
const QString& name = str.clazz.name;
|
||||
|
||||
out << "Bitstream& operator<< (Bitstream& out, const " << name << "& obj) {\n";
|
||||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " out << static_cast<const " << base << "&>(obj);\n";
|
||||
}
|
||||
foreach (const QString& field, str.fields) {
|
||||
out << " out << obj." << field << ";\n";
|
||||
}
|
||||
out << " return out;\n";
|
||||
out << "}\n";
|
||||
|
||||
out << "Bitstream& operator>> (Bitstream& in, " << name << "& obj) {\n";
|
||||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " in >> static_cast<" << base << "&>(obj);\n";
|
||||
}
|
||||
foreach (const QString& field, str.fields) {
|
||||
out << " in >> obj." << field << ";\n";
|
||||
}
|
||||
out << " return in;\n";
|
||||
out << "}\n";
|
||||
|
||||
out << "const int " << name << "::Type = registerStreamableMetaType<" << name << ">();\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char** argv) {
|
||||
// process the command line arguments
|
||||
QStringList inputs;
|
||||
QString output;
|
||||
for (int ii = 1; ii < argc; ii++) {
|
||||
QString arg(argv[ii]);
|
||||
if (!arg.startsWith('-')) {
|
||||
inputs.append(arg);
|
||||
continue;
|
||||
}
|
||||
QStringRef name = arg.midRef(1);
|
||||
if (name == "o") {
|
||||
if (++ii == argc) {
|
||||
cerr << "Missing file name argument for -o" << endl;
|
||||
return 1;
|
||||
}
|
||||
output = argv[ii];
|
||||
|
||||
} else {
|
||||
cerr << "Unknown option " << arg.toStdString() << endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (inputs.isEmpty()) {
|
||||
cerr << "Usage: mtc [OPTION]... input files" << endl;
|
||||
cerr << "Where options include:" << endl;
|
||||
cerr << " -o filename: Send output to filename rather than standard output." << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
QList<Streamable> streamables;
|
||||
foreach (const QString& input, inputs) {
|
||||
QFile ifile(input);
|
||||
if (!ifile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
cerr << ("Couldn't open " + input + ": " + ifile.errorString()).toStdString() << endl;
|
||||
continue;
|
||||
}
|
||||
QTextStream istream(&ifile);
|
||||
int oldSize = streamables.size();
|
||||
processInput(istream, &streamables);
|
||||
if (streamables.size() == oldSize) {
|
||||
// no streamables; remove from list
|
||||
inputs.removeOne(input);
|
||||
}
|
||||
}
|
||||
|
||||
QFile ofile(output);
|
||||
if (output.isNull()) {
|
||||
ofile.open(stdout, QIODevice::WriteOnly | QIODevice::Text);
|
||||
|
||||
} else if (!ofile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
cerr << ("Couldn't open " + output + ": " + ofile.errorString()).toStdString() << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
QTextStream ostream(&ofile);
|
||||
ostream << "// generated by mtc\n";
|
||||
foreach (const QString& input, inputs) {
|
||||
ostream << "#include \"" << input << "\"\n";
|
||||
}
|
||||
generateOutput(ostream, streamables);
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue