merge of latest upstream work in preparation for pull request #1500

Conflicts:
	libraries/particles/src/ParticleCollisionSystem.cpp
This commit is contained in:
Andrew Meadows 2014-01-10 17:03:45 -08:00
commit c61f071ab7
90 changed files with 3945 additions and 2814 deletions

View file

@ -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)

View file

@ -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})

View file

@ -18,6 +18,7 @@
#include <PacketHeaders.h>
#include <UUID.h>
#include <VoxelConstants.h>
#include <ParticlesScriptingInterface.h>
#include "Agent.h"

View file

@ -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;
}
}
}

View file

@ -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() {
}
}
}
}

View file

@ -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;

View 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);
}
}
}

View 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__) */

View 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()

View file

@ -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)

View file

@ -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();
}
}

View file

@ -62,6 +62,7 @@ private:
const char* _voxelServerConfig;
const char* _particleServerConfig;
const char* _metavoxelServerConfig;
bool _hasCompletedRestartHold;
private slots:

52
examples/clap.js Normal file
View 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
View 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);

View file

@ -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)

View file

@ -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.

View file

@ -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

View file

@ -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__) */

View file

@ -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();

View 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);
}
}
}

View 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__) */

View file

@ -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

View file

@ -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();
};

View file

@ -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;

View file

@ -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);

View file

@ -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() {

View file

@ -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; }

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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();
}

View file

@ -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__) */

View file

@ -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
}

View file

@ -22,6 +22,10 @@ public:
public slots:
void setFilter(bool filter);
private:
uint64_t _lastMovement;
};
#endif /* defined(__interface__SixenseManager__) */

View file

@ -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

View file

@ -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;
}

View file

@ -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__) */

View file

@ -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;

View file

@ -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;

View file

@ -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"

View file

@ -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

View file

@ -15,6 +15,7 @@
#include <QPushButton>
#include <QCheckBox>
#include <QSyntaxHighlighter>
#include <pthread.h>
#include "AbstractLoggerInterface.h"

View file

@ -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();

View file

@ -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);
}
}
}

View file

@ -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";
}

View file

@ -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;

View file

@ -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)

View file

@ -11,7 +11,12 @@
#include "AttributeRegistry.h"
#include "MetavoxelData.h"
AttributeRegistry AttributeRegistry::_instance;
REGISTER_META_OBJECT(QRgbAttribute)
AttributeRegistry* AttributeRegistry::getInstance() {
static AttributeRegistry registry;
return &registry;
}
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);
}

View file

@ -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*)&copy); 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:

View file

@ -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;
}

View file

@ -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__) */

View 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());
}

View 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__) */

View file

@ -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;
}
}

View file

@ -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];
};

View 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__) */

View 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));
}

View 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__) */

View file

@ -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;

View file

@ -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;

View file

@ -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());

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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
}

View file

@ -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;

View file

@ -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) {

View file

@ -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); }

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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

View file

@ -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";
}

View file

@ -29,6 +29,7 @@ public:
AgentType,
VoxelServerType,
ParticleServerType,
MetavoxelServerType,
AllTypes
};

View file

@ -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:

View file

@ -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) {

View file

@ -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);

View file

@ -11,6 +11,8 @@
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QNetworkInterface>
static int hifiSockAddrMetaTypeId = qMetaTypeId<HifiSockAddr>();
HifiSockAddr::HifiSockAddr() :
_address(),
_port(0)

View file

@ -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:

View file

@ -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());

View file

@ -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';

View file

@ -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;

View file

@ -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);

View file

@ -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();

View file

@ -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

View file

@ -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__) */

View file

@ -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
View 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
View 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;
}