Merge branch 'master' of github.com:highfidelity/hifi into 19864

Conflicts:
	interface/CMakeLists.txt
This commit is contained in:
Ryan Huffman 2014-08-19 11:59:53 -07:00
commit 284c4d8ac0
276 changed files with 13049 additions and 5775 deletions

View file

@ -44,10 +44,17 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Instruct CMake to run moc automatically when needed.
set(CMAKE_AUTOMOC ON)
# targets not supported on windows
if (NOT WIN32)
add_subdirectory(animation-server)
endif ()
set(HIFI_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries")
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/")
set(MACRO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros")
file(GLOB HIFI_CUSTOM_MACROS "cmake/macros/*.cmake")
foreach(CUSTOM_MACRO ${HIFI_CUSTOM_MACROS})
include(${CUSTOM_MACRO})
endforeach()
# targets on all platforms
add_subdirectory(assignment-client)

View file

@ -1,39 +0,0 @@
if (WIN32)
cmake_policy (SET CMP0020 NEW)
endif (WIN32)
set(TARGET_NAME animation-server)
set(ROOT_DIR ..)
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/")
find_package(Qt5 COMPONENTS Script)
include_directories(SYSTEM "${Qt5Script_INCLUDE_DIRS}")
# set up the external glm library
include("${MACRO_DIR}/IncludeGLM.cmake")
include_glm(${TARGET_NAME} "${ROOT_DIR}")
include("${MACRO_DIR}/SetupHifiProject.cmake")
setup_hifi_project(${TARGET_NAME} TRUE)
# link in the shared library
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
# link in the hifi octree library
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
# link in the hifi voxels library
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
# link the hifi networking library
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
# add a definition for ssize_t so that windows doesn't bail
if (WIN32)
add_definitions(-Dssize_t=long)
endif ()

View file

@ -1,852 +0,0 @@
//
// AnimationServer.cpp
// animation-server/src
//
// Created by Stephen Birarda on 12/5/2013.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <QtCore/QTimer>
#include <EnvironmentData.h>
#include <NodeList.h>
#include <OctalCode.h>
#include <PacketHeaders.h>
#include <JurisdictionListener.h>
#include <SharedUtil.h>
#include <VoxelEditPacketSender.h>
#include <VoxelTree.h>
#include "AnimationServer.h"
bool shouldShowPacketsPerSecond = false; // do we want to debug packets per second
bool includeBillboard = true;
bool includeBorderTracer = true;
bool includeMovingBug = true;
bool includeBlinkingVoxel = false;
bool includeDanceFloor = true;
bool buildStreet = false;
bool nonThreadedPacketSender = false;
int packetsPerSecond = PacketSender::DEFAULT_PACKETS_PER_SECOND;
bool waitForVoxelServer = true;
const int ANIMATION_LISTEN_PORT = 40107;
int ANIMATE_FPS = 60;
double ANIMATE_FPS_IN_MILLISECONDS = 1000.0/ANIMATE_FPS; // determines FPS from our desired FPS
quint64 ANIMATE_VOXELS_INTERVAL_USECS = (ANIMATE_FPS_IN_MILLISECONDS * 1000); // converts from milliseconds to usecs
int PROCESSING_FPS = 60;
double PROCESSING_FPS_IN_MILLISECONDS = 1000.0/PROCESSING_FPS; // determines FPS from our desired FPS
int FUDGE_USECS = 650; // a little bit of fudge to actually do some processing
quint64 PROCESSING_INTERVAL_USECS = (PROCESSING_FPS_IN_MILLISECONDS * 1000) - FUDGE_USECS; // converts from milliseconds to usecs
bool wantLocalDomain = false;
unsigned long packetsSent = 0;
unsigned long bytesSent = 0;
JurisdictionListener* jurisdictionListener = NULL;
VoxelEditPacketSender* voxelEditPacketSender = NULL;
pthread_t animateVoxelThread;
glm::vec3 rotatePoint(glm::vec3 point, float angle) {
// First, create the quaternion based on this angle of rotation
glm::quat q(glm::vec3(0, -angle, 0));
// Next, create a rotation matrix from that quaternion
glm::mat4 rotation = glm::mat4_cast(q);
// Transform the original vectors by the rotation matrix to get the new vectors
glm::vec4 quatPoint(point.x, point.y, point.z, 0);
glm::vec4 newPoint = quatPoint * rotation;
return glm::vec3(newPoint.x, newPoint.y, newPoint.z);
}
const float BUG_VOXEL_SIZE = 0.0625f / TREE_SCALE;
glm::vec3 bugPosition = glm::vec3(BUG_VOXEL_SIZE * 20.0, BUG_VOXEL_SIZE * 30.0, BUG_VOXEL_SIZE * 20.0);
glm::vec3 bugDirection = glm::vec3(0, 0, 1);
const int VOXELS_PER_BUG = 18;
glm::vec3 bugPathCenter = glm::vec3(0.25f,0.15f,0.25f); // glm::vec3(BUG_VOXEL_SIZE * 150.0, BUG_VOXEL_SIZE * 30.0, BUG_VOXEL_SIZE * 150.0);
float bugPathRadius = 0.2f; //BUG_VOXEL_SIZE * 140.0;
float bugPathTheta = 0.0f * RADIANS_PER_DEGREE;
float bugRotation = 0.0f * RADIANS_PER_DEGREE;
float bugAngleDelta = 0.2f * RADIANS_PER_DEGREE;
bool moveBugInLine = false;
class BugPart {
public:
glm::vec3 partLocation;
unsigned char partColor[3];
BugPart(const glm::vec3& location, unsigned char red, unsigned char green, unsigned char blue ) {
partLocation = location;
partColor[0] = red;
partColor[1] = green;
partColor[2] = blue;
}
};
const BugPart bugParts[VOXELS_PER_BUG] = {
// tail
BugPart(glm::vec3( 0, 0, -3), 51, 51, 153) ,
BugPart(glm::vec3( 0, 0, -2), 51, 51, 153) ,
BugPart(glm::vec3( 0, 0, -1), 51, 51, 153) ,
// body
BugPart(glm::vec3( 0, 0, 0), 255, 200, 0) ,
BugPart(glm::vec3( 0, 0, 1), 255, 200, 0) ,
// head
BugPart(glm::vec3( 0, 0, 2), 200, 0, 0) ,
// eyes
BugPart(glm::vec3( 1, 0, 3), 64, 64, 64) ,
BugPart(glm::vec3(-1, 0, 3), 64, 64, 64) ,
// wings
BugPart(glm::vec3( 3, 1, 1), 0, 153, 0) ,
BugPart(glm::vec3( 2, 1, 1), 0, 153, 0) ,
BugPart(glm::vec3( 1, 0, 1), 0, 153, 0) ,
BugPart(glm::vec3(-1, 0, 1), 0, 153, 0) ,
BugPart(glm::vec3(-2, 1, 1), 0, 153, 0) ,
BugPart(glm::vec3(-3, 1, 1), 0, 153, 0) ,
BugPart(glm::vec3( 2, -1, 0), 153, 200, 0) ,
BugPart(glm::vec3( 1, -1, 0), 153, 200, 0) ,
BugPart(glm::vec3(-1, -1, 0), 153, 200, 0) ,
BugPart(glm::vec3(-2, -1, 0), 153, 200, 0) ,
};
static void renderMovingBug() {
VoxelDetail details[VOXELS_PER_BUG];
// Generate voxels for where bug used to be
for (int i = 0; i < VOXELS_PER_BUG; i++) {
details[i].s = BUG_VOXEL_SIZE;
glm::vec3 partAt = bugParts[i].partLocation * BUG_VOXEL_SIZE * (bugDirection.x < 0 ? -1.0f : 1.0f);
glm::vec3 rotatedPartAt = rotatePoint(partAt, bugRotation);
glm::vec3 offsetPartAt = rotatedPartAt + bugPosition;
details[i].x = offsetPartAt.x;
details[i].y = offsetPartAt.y;
details[i].z = offsetPartAt.z;
details[i].red = bugParts[i].partColor[0];
details[i].green = bugParts[i].partColor[1];
details[i].blue = bugParts[i].partColor[2];
}
// send the "erase message" first...
PacketType message = PacketTypeVoxelErase;
::voxelEditPacketSender->queueVoxelEditMessages(message, VOXELS_PER_BUG, (VoxelDetail*)&details);
// Move the bug...
if (moveBugInLine) {
bugPosition.x += (bugDirection.x * BUG_VOXEL_SIZE);
bugPosition.y += (bugDirection.y * BUG_VOXEL_SIZE);
bugPosition.z += (bugDirection.z * BUG_VOXEL_SIZE);
// Check boundaries
if (bugPosition.z > 1.0f) {
bugDirection.z = -1.f;
}
if (bugPosition.z < BUG_VOXEL_SIZE) {
bugDirection.z = 1.f;
}
} else {
//qDebug("bugPathCenter=(%f,%f,%f)", bugPathCenter.x, bugPathCenter.y, bugPathCenter.z);
bugPathTheta += bugAngleDelta; // move slightly
bugRotation -= bugAngleDelta; // rotate slightly
// If we loop past end of circle, just reset back into normal range
if (bugPathTheta > TWO_PI) {
bugPathTheta = 0.f;
bugRotation = 0.f;
}
float x = bugPathCenter.x + bugPathRadius * cos(bugPathTheta);
float z = bugPathCenter.z + bugPathRadius * sin(bugPathTheta);
float y = bugPathCenter.y;
bugPosition = glm::vec3(x, y, z);
//qDebug("bugPathTheta=%f", bugPathTheta);
//qDebug("bugRotation=%f", bugRotation);
}
//qDebug("bugPosition=(%f,%f,%f)", bugPosition.x, bugPosition.y, bugPosition.z);
//qDebug("bugDirection=(%f,%f,%f)", bugDirection.x, bugDirection.y, bugDirection.z);
// would be nice to add some randomness here...
// Generate voxels for where bug is going to
for (int i = 0; i < VOXELS_PER_BUG; i++) {
details[i].s = BUG_VOXEL_SIZE;
glm::vec3 partAt = bugParts[i].partLocation * BUG_VOXEL_SIZE * (bugDirection.x < 0 ? -1.0f : 1.0f);
glm::vec3 rotatedPartAt = rotatePoint(partAt, bugRotation);
glm::vec3 offsetPartAt = rotatedPartAt + bugPosition;
details[i].x = offsetPartAt.x;
details[i].y = offsetPartAt.y;
details[i].z = offsetPartAt.z;
details[i].red = bugParts[i].partColor[0];
details[i].green = bugParts[i].partColor[1];
details[i].blue = bugParts[i].partColor[2];
}
// send the "create message" ...
message = PacketTypeVoxelSetDestructive;
::voxelEditPacketSender->queueVoxelEditMessages(message, VOXELS_PER_BUG, (VoxelDetail*)&details);
}
float intensity = 0.5f;
float intensityIncrement = 0.1f;
const float MAX_INTENSITY = 1.0f;
const float MIN_INTENSITY = 0.5f;
const float BEACON_SIZE = 0.25f / TREE_SCALE; // approximately 1/4th meter
static void sendVoxelBlinkMessage() {
VoxelDetail detail;
detail.s = BEACON_SIZE;
glm::vec3 position = glm::vec3(0.f, 0.f, detail.s);
detail.x = detail.s * floor(position.x / detail.s);
detail.y = detail.s * floor(position.y / detail.s);
detail.z = detail.s * floor(position.z / detail.s);
::intensity += ::intensityIncrement;
if (::intensity >= MAX_INTENSITY) {
::intensity = MAX_INTENSITY;
::intensityIncrement = -::intensityIncrement;
}
if (::intensity <= MIN_INTENSITY) {
::intensity = MIN_INTENSITY;
::intensityIncrement = -::intensityIncrement;
}
detail.red = 255 * ::intensity;
detail.green = 0 * ::intensity;
detail.blue = 0 * ::intensity;
PacketType message = PacketTypeVoxelSetDestructive;
::voxelEditPacketSender->sendVoxelEditMessage(message, detail);
}
bool stringOfLightsInitialized = false;
int currentLight = 0;
int lightMovementDirection = 1;
const int SEGMENT_COUNT = 4;
const int LIGHTS_PER_SEGMENT = 80;
const int LIGHT_COUNT = LIGHTS_PER_SEGMENT * SEGMENT_COUNT;
glm::vec3 stringOfLights[LIGHT_COUNT];
unsigned char offColor[3] = { 240, 240, 240 };
unsigned char onColor[3] = { 0, 255, 255 };
const float STRING_OF_LIGHTS_SIZE = 0.125f / TREE_SCALE; // approximately 1/8th meter
static void sendBlinkingStringOfLights() {
PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
float lightScale = STRING_OF_LIGHTS_SIZE;
static VoxelDetail details[LIGHTS_PER_SEGMENT];
// first initialized the string of lights if needed...
if (!stringOfLightsInitialized) {
for (int segment = 0; segment < SEGMENT_COUNT; segment++) {
for (int indexInSegment = 0; indexInSegment < LIGHTS_PER_SEGMENT; indexInSegment++) {
int i = (segment * LIGHTS_PER_SEGMENT) + indexInSegment;
// four different segments on sides of initial platform
switch (segment) {
case 0:
// along x axis
stringOfLights[i] = glm::vec3(indexInSegment * lightScale, 0, 0);
break;
case 1:
// parallel to Z axis at outer X edge
stringOfLights[i] = glm::vec3(LIGHTS_PER_SEGMENT * lightScale, 0, indexInSegment * lightScale);
break;
case 2:
// parallel to X axis at outer Z edge
stringOfLights[i] = glm::vec3((LIGHTS_PER_SEGMENT-indexInSegment) * lightScale, 0,
LIGHTS_PER_SEGMENT * lightScale);
break;
case 3:
// on Z axis
stringOfLights[i] = glm::vec3(0, 0, (LIGHTS_PER_SEGMENT-indexInSegment) * lightScale);
break;
}
details[indexInSegment].s = STRING_OF_LIGHTS_SIZE;
details[indexInSegment].x = stringOfLights[i].x;
details[indexInSegment].y = stringOfLights[i].y;
details[indexInSegment].z = stringOfLights[i].z;
details[indexInSegment].red = offColor[0];
details[indexInSegment].green = offColor[1];
details[indexInSegment].blue = offColor[2];
}
::voxelEditPacketSender->queueVoxelEditMessages(message, LIGHTS_PER_SEGMENT, (VoxelDetail*)&details);
}
stringOfLightsInitialized = true;
} else {
// turn off current light
details[0].x = stringOfLights[currentLight].x;
details[0].y = stringOfLights[currentLight].y;
details[0].z = stringOfLights[currentLight].z;
details[0].red = offColor[0];
details[0].green = offColor[1];
details[0].blue = offColor[2];
// move current light...
// if we're at the end of our string, then change direction
if (currentLight == LIGHT_COUNT-1) {
lightMovementDirection = -1;
}
if (currentLight == 0) {
lightMovementDirection = 1;
}
currentLight += lightMovementDirection;
// turn on new current light
details[1].x = stringOfLights[currentLight].x;
details[1].y = stringOfLights[currentLight].y;
details[1].z = stringOfLights[currentLight].z;
details[1].red = onColor[0];
details[1].green = onColor[1];
details[1].blue = onColor[2];
// send both changes in same message
::voxelEditPacketSender->queueVoxelEditMessages(message, 2, (VoxelDetail*)&details);
}
}
bool danceFloorInitialized = false;
const float DANCE_FLOOR_LIGHT_SIZE = 1.0f / TREE_SCALE; // approximately 1 meter
const int DANCE_FLOOR_LENGTH = 10;
const int DANCE_FLOOR_WIDTH = 10;
glm::vec3 danceFloorPosition(100.0f / TREE_SCALE, 30.0f / TREE_SCALE, 10.0f / TREE_SCALE);
glm::vec3 danceFloorLights[DANCE_FLOOR_LENGTH][DANCE_FLOOR_WIDTH];
unsigned char danceFloorOffColor[3] = { 240, 240, 240 };
const int DANCE_FLOOR_COLORS = 6;
unsigned char danceFloorOnColorA[DANCE_FLOOR_COLORS][3] = {
{ 255, 0, 0 }, { 0, 255, 0 }, { 0, 0, 255 },
{ 0, 191, 255 }, { 0, 250, 154 }, { 255, 69, 0 },
};
unsigned char danceFloorOnColorB[DANCE_FLOOR_COLORS][3] = {
{ 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } ,
{ 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }
};
float danceFloorGradient = 0.5f;
const float BEATS_PER_MINUTE = 118.0f;
const float SECONDS_PER_MINUTE = 60.0f;
const float FRAMES_PER_BEAT = (SECONDS_PER_MINUTE * ANIMATE_FPS) / BEATS_PER_MINUTE;
float danceFloorGradientIncrement = 1.0f / FRAMES_PER_BEAT;
const float DANCE_FLOOR_MAX_GRADIENT = 1.0f;
const float DANCE_FLOOR_MIN_GRADIENT = 0.0f;
const int DANCE_FLOOR_VOXELS_PER_PACKET = 100;
int danceFloorColors[DANCE_FLOOR_WIDTH][DANCE_FLOOR_LENGTH];
void sendDanceFloor() {
PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
float lightScale = DANCE_FLOOR_LIGHT_SIZE;
static VoxelDetail details[DANCE_FLOOR_VOXELS_PER_PACKET];
// first initialized the billboard of lights if needed...
if (!::danceFloorInitialized) {
for (int i = 0; i < DANCE_FLOOR_WIDTH; i++) {
for (int j = 0; j < DANCE_FLOOR_LENGTH; j++) {
int randomColorIndex = randIntInRange(-DANCE_FLOOR_COLORS, DANCE_FLOOR_COLORS);
::danceFloorColors[i][j] = randomColorIndex;
::danceFloorLights[i][j] = ::danceFloorPosition +
glm::vec3(i * DANCE_FLOOR_LIGHT_SIZE, 0, j * DANCE_FLOOR_LIGHT_SIZE);
}
}
::danceFloorInitialized = true;
}
::danceFloorGradient += ::danceFloorGradientIncrement;
if (::danceFloorGradient >= DANCE_FLOOR_MAX_GRADIENT) {
::danceFloorGradient = DANCE_FLOOR_MAX_GRADIENT;
::danceFloorGradientIncrement = -::danceFloorGradientIncrement;
}
if (::danceFloorGradient <= DANCE_FLOOR_MIN_GRADIENT) {
::danceFloorGradient = DANCE_FLOOR_MIN_GRADIENT;
::danceFloorGradientIncrement = -::danceFloorGradientIncrement;
}
for (int i = 0; i < DANCE_FLOOR_LENGTH; i++) {
for (int j = 0; j < DANCE_FLOOR_WIDTH; j++) {
int nthVoxel = ((i * DANCE_FLOOR_WIDTH) + j);
int item = nthVoxel % DANCE_FLOOR_VOXELS_PER_PACKET;
::danceFloorLights[i][j] = ::danceFloorPosition +
glm::vec3(i * DANCE_FLOOR_LIGHT_SIZE, 0, j * DANCE_FLOOR_LIGHT_SIZE);
details[item].s = lightScale;
details[item].x = ::danceFloorLights[i][j].x;
details[item].y = ::danceFloorLights[i][j].y;
details[item].z = ::danceFloorLights[i][j].z;
if (danceFloorColors[i][j] > 0) {
int color = danceFloorColors[i][j] - 1;
details[item].red = (::danceFloorOnColorA[color][0] +
((::danceFloorOnColorB[color][0] - ::danceFloorOnColorA[color][0])
* ::danceFloorGradient));
details[item].green = (::danceFloorOnColorA[color][1] +
((::danceFloorOnColorB[color][1] - ::danceFloorOnColorA[color][1])
* ::danceFloorGradient));
details[item].blue = (::danceFloorOnColorA[color][2] +
((::danceFloorOnColorB[color][2] - ::danceFloorOnColorA[color][2])
* ::danceFloorGradient));
} else if (::danceFloorColors[i][j] < 0) {
int color = -(::danceFloorColors[i][j] + 1);
details[item].red = (::danceFloorOnColorB[color][0] +
((::danceFloorOnColorA[color][0] - ::danceFloorOnColorB[color][0])
* ::danceFloorGradient));
details[item].green = (::danceFloorOnColorB[color][1] +
((::danceFloorOnColorA[color][1] - ::danceFloorOnColorB[color][1])
* ::danceFloorGradient));
details[item].blue = (::danceFloorOnColorB[color][2] +
((::danceFloorOnColorA[color][2] - ::danceFloorOnColorB[color][2])
* ::danceFloorGradient));
} else {
int color = 0;
details[item].red = (::danceFloorOnColorB[color][0] +
((::danceFloorOnColorA[color][0] - ::danceFloorOnColorB[color][0])
* ::danceFloorGradient));
details[item].green = (::danceFloorOnColorB[color][1] +
((::danceFloorOnColorA[color][1] - ::danceFloorOnColorB[color][1])
* ::danceFloorGradient));
details[item].blue = (::danceFloorOnColorB[color][2] +
((::danceFloorOnColorA[color][2] - ::danceFloorOnColorB[color][2])
* ::danceFloorGradient));
}
if (item == DANCE_FLOOR_VOXELS_PER_PACKET - 1) {
::voxelEditPacketSender->queueVoxelEditMessages(message, DANCE_FLOOR_VOXELS_PER_PACKET, (VoxelDetail*)&details);
}
}
}
}
bool billboardInitialized = false;
const int BILLBOARD_HEIGHT = 9;
const int BILLBOARD_WIDTH = 45;
glm::vec3 billboardPosition((0.125f / TREE_SCALE),(0.125f / TREE_SCALE),0);
glm::vec3 billboardLights[BILLBOARD_HEIGHT][BILLBOARD_WIDTH];
unsigned char billboardOffColor[3] = { 240, 240, 240 };
unsigned char billboardOnColorA[3] = { 0, 0, 255 };
unsigned char billboardOnColorB[3] = { 0, 255, 0 };
float billboardGradient = 0.5f;
float billboardGradientIncrement = 0.01f;
const float BILLBOARD_MAX_GRADIENT = 1.0f;
const float BILLBOARD_MIN_GRADIENT = 0.0f;
const float BILLBOARD_LIGHT_SIZE = 0.125f / TREE_SCALE; // approximately 1/8 meter per light
const int VOXELS_PER_PACKET = 81;
// top to bottom...
bool billboardMessage[BILLBOARD_HEIGHT][BILLBOARD_WIDTH] = {
{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
{ 0,0,1,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0,1,1,1,1,0,0,0,0,0,1,0,1,1,1,0,1,0,1,0,0,1,0,0,0,0,0,0 },
{ 0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,0,0,1,1,1,0,0,0,0,0 },
{ 0,0,1,1,1,1,0,1,0,1,1,1,0,1,1,1,0,0,1,1,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,1,0,0,1,0,0,1,0,1,0 },
{ 0,0,1,0,0,1,0,1,0,1,0,1,0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,1,0,1,0,0,0,1,0,1,0,0,1,0,0,1,0,1,0 },
{ 0,0,1,0,0,1,0,1,0,1,1,1,0,1,0,1,0,0,1,0,0,0,0,1,0,1,1,1,0,1,1,1,0,1,0,1,0,0,1,0,0,1,1,1,0 },
{ 0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0 },
{ 0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0 },
{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }
};
static void sendBillboard() {
PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
float lightScale = BILLBOARD_LIGHT_SIZE;
static VoxelDetail details[VOXELS_PER_PACKET];
// first initialized the billboard of lights if needed...
if (!billboardInitialized) {
for (int i = 0; i < BILLBOARD_HEIGHT; i++) {
for (int j = 0; j < BILLBOARD_WIDTH; j++) {
billboardLights[i][j] = billboardPosition + glm::vec3(j * lightScale, (float)((BILLBOARD_HEIGHT - i) * lightScale), 0);
}
}
billboardInitialized = true;
}
::billboardGradient += ::billboardGradientIncrement;
if (::billboardGradient >= BILLBOARD_MAX_GRADIENT) {
::billboardGradient = BILLBOARD_MAX_GRADIENT;
::billboardGradientIncrement = -::billboardGradientIncrement;
}
if (::billboardGradient <= BILLBOARD_MIN_GRADIENT) {
::billboardGradient = BILLBOARD_MIN_GRADIENT;
::billboardGradientIncrement = -::billboardGradientIncrement;
}
for (int i = 0; i < BILLBOARD_HEIGHT; i++) {
for (int j = 0; j < BILLBOARD_WIDTH; j++) {
int nthVoxel = ((i * BILLBOARD_WIDTH) + j);
int item = nthVoxel % VOXELS_PER_PACKET;
billboardLights[i][j] = billboardPosition + glm::vec3(j * lightScale, (float)((BILLBOARD_HEIGHT - i) * lightScale), 0);
details[item].s = lightScale;
details[item].x = billboardLights[i][j].x;
details[item].y = billboardLights[i][j].y;
details[item].z = billboardLights[i][j].z;
if (billboardMessage[i][j]) {
details[item].red = (billboardOnColorA[0] + ((billboardOnColorB[0] - billboardOnColorA[0]) * ::billboardGradient));
details[item].green = (billboardOnColorA[1] + ((billboardOnColorB[1] - billboardOnColorA[1]) * ::billboardGradient));
details[item].blue = (billboardOnColorA[2] + ((billboardOnColorB[2] - billboardOnColorA[2]) * ::billboardGradient));
} else {
details[item].red = billboardOffColor[0];
details[item].green = billboardOffColor[1];
details[item].blue = billboardOffColor[2];
}
if (item == VOXELS_PER_PACKET - 1) {
::voxelEditPacketSender->queueVoxelEditMessages(message, VOXELS_PER_PACKET, (VoxelDetail*)&details);
}
}
}
}
bool roadInitialized = false;
const int BRICKS_ACROSS_ROAD = 32;
const float ROAD_BRICK_SIZE = 0.125f/TREE_SCALE; //(ROAD_WIDTH_METERS / TREE_SCALE) / BRICKS_ACROSS_ROAD; // in voxel units
const int ROAD_LENGTH = 1.0f / ROAD_BRICK_SIZE; // in bricks
const int ROAD_WIDTH = BRICKS_ACROSS_ROAD; // in bricks
glm::vec3 roadPosition(0.5f - (ROAD_BRICK_SIZE * BRICKS_ACROSS_ROAD), 0.0f, 0.0f);
const int BRICKS_PER_PACKET = 32; // guessing
void doBuildStreet() {
if (roadInitialized) {
return;
}
PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
static VoxelDetail details[BRICKS_PER_PACKET];
for (int z = 0; z < ROAD_LENGTH; z++) {
for (int x = 0; x < ROAD_WIDTH; x++) {
int nthVoxel = ((z * ROAD_WIDTH) + x);
int item = nthVoxel % BRICKS_PER_PACKET;
glm::vec3 brick = roadPosition + glm::vec3(x * ROAD_BRICK_SIZE, 0, z * ROAD_BRICK_SIZE);
details[item].s = ROAD_BRICK_SIZE;
details[item].x = brick.x;
details[item].y = brick.y;
details[item].z = brick.z;
unsigned char randomTone = randIntInRange(118,138);
details[item].red = randomTone;
details[item].green = randomTone;
details[item].blue = randomTone;
if (item == BRICKS_PER_PACKET - 1) {
::voxelEditPacketSender->queueVoxelEditMessages(message, BRICKS_PER_PACKET, (VoxelDetail*)&details);
}
}
}
roadInitialized = true;
}
double start = 0;
void* animateVoxels(void* args) {
quint64 lastAnimateTime = 0;
quint64 lastProcessTime = 0;
int processesPerAnimate = 0;
bool firstTime = true;
qDebug() << "Setting PPS to " << ::packetsPerSecond;
::voxelEditPacketSender->setPacketsPerSecond(::packetsPerSecond);
qDebug() << "PPS set to " << ::voxelEditPacketSender->getPacketsPerSecond();
while (true) {
// If we're asked to wait for voxel servers, and there isn't one available yet, then
// let the voxelEditPacketSender process and move on.
if (::waitForVoxelServer && !::voxelEditPacketSender->voxelServersExist()) {
if (::nonThreadedPacketSender) {
::voxelEditPacketSender->process();
}
} else {
if (firstTime) {
lastAnimateTime = usecTimestampNow();
firstTime = false;
}
lastProcessTime = usecTimestampNow();
// The while loop will be running at PROCESSING_FPS, but we only want to call these animation functions at
// ANIMATE_FPS. So we check out last animate time and only call these if we've elapsed that time.
quint64 now = usecTimestampNow();
quint64 animationElapsed = now - lastAnimateTime;
int withinAnimationTarget = ANIMATE_VOXELS_INTERVAL_USECS - animationElapsed;
const int CLOSE_ENOUGH_TO_ANIMATE = 2000; // approximately 2 ms
int animateLoopsPerAnimate = 0;
while (withinAnimationTarget < CLOSE_ENOUGH_TO_ANIMATE) {
processesPerAnimate = 0;
animateLoopsPerAnimate++;
lastAnimateTime = now;
// some animations
//sendVoxelBlinkMessage();
if (::includeBillboard) {
sendBillboard();
}
if (::includeBorderTracer) {
sendBlinkingStringOfLights();
}
if (::includeMovingBug) {
renderMovingBug();
}
if (::includeBlinkingVoxel) {
sendVoxelBlinkMessage();
}
if (::includeDanceFloor) {
sendDanceFloor();
}
if (::buildStreet) {
doBuildStreet();
}
if (animationElapsed > ANIMATE_VOXELS_INTERVAL_USECS) {
animationElapsed -= ANIMATE_VOXELS_INTERVAL_USECS; // credit ourselves one animation frame
} else {
animationElapsed = 0;
}
withinAnimationTarget = ANIMATE_VOXELS_INTERVAL_USECS - animationElapsed;
::voxelEditPacketSender->releaseQueuedMessages();
}
if (::nonThreadedPacketSender) {
::voxelEditPacketSender->process();
}
processesPerAnimate++;
}
// dynamically sleep until we need to fire off the next set of voxels
quint64 usecToSleep = ::PROCESSING_INTERVAL_USECS - (usecTimestampNow() - lastProcessTime);
if (usecToSleep > ::PROCESSING_INTERVAL_USECS) {
usecToSleep = ::PROCESSING_INTERVAL_USECS;
}
if (usecToSleep > 0) {
usleep(usecToSleep);
}
}
pthread_exit(0);
}
AnimationServer::AnimationServer(int &argc, char **argv) :
QCoreApplication(argc, argv)
{
::start = usecTimestampNow();
NodeList* nodeList = NodeList::createInstance(NodeType::AnimationServer, ANIMATION_LISTEN_PORT);
setvbuf(stdout, NULL, _IOLBF, 0);
// Handle Local Domain testing with the --local command line
const char* NON_THREADED_PACKETSENDER = "--NonThreadedPacketSender";
::nonThreadedPacketSender = cmdOptionExists(argc, (const char**) argv, NON_THREADED_PACKETSENDER);
qDebug("nonThreadedPacketSender=%s", debug::valueOf(::nonThreadedPacketSender));
// Handle Local Domain testing with the --local command line
const char* NO_BILLBOARD = "--NoBillboard";
::includeBillboard = !cmdOptionExists(argc, (const char**) argv, NO_BILLBOARD);
qDebug("includeBillboard=%s", debug::valueOf(::includeBillboard));
const char* NO_BORDER_TRACER = "--NoBorderTracer";
::includeBorderTracer = !cmdOptionExists(argc, (const char**) argv, NO_BORDER_TRACER);
qDebug("includeBorderTracer=%s", debug::valueOf(::includeBorderTracer));
const char* NO_MOVING_BUG = "--NoMovingBug";
::includeMovingBug = !cmdOptionExists(argc, (const char**) argv, NO_MOVING_BUG);
qDebug("includeMovingBug=%s", debug::valueOf(::includeMovingBug));
const char* INCLUDE_BLINKING_VOXEL = "--includeBlinkingVoxel";
::includeBlinkingVoxel = cmdOptionExists(argc, (const char**) argv, INCLUDE_BLINKING_VOXEL);
qDebug("includeBlinkingVoxel=%s", debug::valueOf(::includeBlinkingVoxel));
const char* NO_DANCE_FLOOR = "--NoDanceFloor";
::includeDanceFloor = !cmdOptionExists(argc, (const char**) argv, NO_DANCE_FLOOR);
qDebug("includeDanceFloor=%s", debug::valueOf(::includeDanceFloor));
const char* BUILD_STREET = "--BuildStreet";
::buildStreet = cmdOptionExists(argc, (const char**) argv, BUILD_STREET);
qDebug("buildStreet=%s", debug::valueOf(::buildStreet));
// Handle Local Domain testing with the --local command line
const char* showPPS = "--showPPS";
::shouldShowPacketsPerSecond = cmdOptionExists(argc, (const char**) argv, showPPS);
// Handle Local Domain testing with the --local command line
const char* local = "--local";
::wantLocalDomain = cmdOptionExists(argc, (const char**) argv,local);
if (::wantLocalDomain) {
qDebug("Local Domain MODE!");
nodeList->getDomainHandler().setIPToLocalhost();
}
const char* domainHostname = getCmdOption(argc, (const char**) argv, "--domain");
if (domainHostname) {
NodeList::getInstance()->getDomainHandler().setHostname(domainHostname);
}
const char* packetsPerSecondCommand = getCmdOption(argc, (const char**) argv, "--pps");
if (packetsPerSecondCommand) {
::packetsPerSecond = atoi(packetsPerSecondCommand);
}
qDebug("packetsPerSecond=%d",packetsPerSecond);
const char* animateFPSCommand = getCmdOption(argc, (const char**) argv, "--AnimateFPS");
const char* animateIntervalCommand = getCmdOption(argc, (const char**) argv, "--AnimateInterval");
if (animateFPSCommand || animateIntervalCommand) {
if (animateIntervalCommand) {
::ANIMATE_FPS_IN_MILLISECONDS = atoi(animateIntervalCommand);
::ANIMATE_VOXELS_INTERVAL_USECS = (ANIMATE_FPS_IN_MILLISECONDS * 1000.0); // converts from milliseconds to usecs
::ANIMATE_FPS = PacketSender::USECS_PER_SECOND / ::ANIMATE_VOXELS_INTERVAL_USECS;
} else {
::ANIMATE_FPS = atoi(animateFPSCommand);
::ANIMATE_FPS_IN_MILLISECONDS = 1000.0/ANIMATE_FPS; // determines FPS from our desired FPS
::ANIMATE_VOXELS_INTERVAL_USECS = (ANIMATE_FPS_IN_MILLISECONDS * 1000.0); // converts from milliseconds to usecs
}
}
qDebug("ANIMATE_FPS=%d",ANIMATE_FPS);
qDebug("ANIMATE_VOXELS_INTERVAL_USECS=%llu",ANIMATE_VOXELS_INTERVAL_USECS);
const char* processingFPSCommand = getCmdOption(argc, (const char**) argv, "--ProcessingFPS");
const char* processingIntervalCommand = getCmdOption(argc, (const char**) argv, "--ProcessingInterval");
if (processingFPSCommand || processingIntervalCommand) {
if (processingIntervalCommand) {
::PROCESSING_FPS_IN_MILLISECONDS = atoi(processingIntervalCommand);
::PROCESSING_INTERVAL_USECS = ::PROCESSING_FPS_IN_MILLISECONDS * 1000.0;
::PROCESSING_FPS = PacketSender::USECS_PER_SECOND / ::PROCESSING_INTERVAL_USECS;
} else {
::PROCESSING_FPS = atoi(processingFPSCommand);
::PROCESSING_FPS_IN_MILLISECONDS = 1000.0/PROCESSING_FPS; // determines FPS from our desired FPS
::PROCESSING_INTERVAL_USECS = (PROCESSING_FPS_IN_MILLISECONDS * 1000.0) - FUDGE_USECS; // converts from milliseconds to usecs
}
}
qDebug("PROCESSING_FPS=%d",PROCESSING_FPS);
qDebug("PROCESSING_INTERVAL_USECS=%llu",PROCESSING_INTERVAL_USECS);
nodeList->linkedDataCreateCallback = NULL; // do we need a callback?
// Create our JurisdictionListener so we'll know where to send edit packets
::jurisdictionListener = new JurisdictionListener();
if (::jurisdictionListener) {
::jurisdictionListener->initialize(true);
}
// Create out VoxelEditPacketSender
::voxelEditPacketSender = new VoxelEditPacketSender;
::voxelEditPacketSender->initialize(!::nonThreadedPacketSender);
if (::jurisdictionListener) {
::voxelEditPacketSender->setVoxelServerJurisdictions(::jurisdictionListener->getJurisdictions());
}
if (::nonThreadedPacketSender) {
::voxelEditPacketSender->setProcessCallIntervalHint(PROCESSING_INTERVAL_USECS);
}
srand((unsigned)time(0));
pthread_create(&::animateVoxelThread, NULL, animateVoxels, NULL);
NodeList::getInstance()->addNodeTypeToInterestSet(NodeType::VoxelServer);
QTimer* domainServerTimer = new QTimer(this);
connect(domainServerTimer, SIGNAL(timeout()), nodeList, SLOT(sendDomainServerCheckIn()));
domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
QTimer* silentNodeTimer = new QTimer(this);
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS);
connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
}
void AnimationServer::readPendingDatagrams() {
NodeList* nodeList = NodeList::getInstance();
static QByteArray receivedPacket;
static HifiSockAddr nodeSockAddr;
// Nodes sending messages to us...
while (nodeList->getNodeSocket().hasPendingDatagrams()) {
receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
nodeSockAddr.getAddressPointer(), nodeSockAddr.getPortPointer());
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
if (packetTypeForPacket(receivedPacket) == PacketTypeJurisdiction) {
int headerBytes = numBytesForPacketHeader(receivedPacket);
// PacketType_JURISDICTION, first byte is the node type...
if (receivedPacket.data()[headerBytes] == NodeType::VoxelServer && ::jurisdictionListener) {
SharedNodePointer matchedNode = NodeList::getInstance()->sendingNodeForPacket(receivedPacket);
if (matchedNode) {
::jurisdictionListener->queueReceivedPacket(matchedNode, receivedPacket);
}
}
}
NodeList::getInstance()->processNodeData(nodeSockAddr, receivedPacket);
}
}
}
AnimationServer::~AnimationServer() {
pthread_join(animateVoxelThread, NULL);
if (::jurisdictionListener) {
::jurisdictionListener->terminate();
delete ::jurisdictionListener;
}
if (::voxelEditPacketSender) {
::voxelEditPacketSender->terminate();
delete ::voxelEditPacketSender;
}
}

View file

@ -1,27 +0,0 @@
//
// AnimationServer.h
// animation-server/src
//
// Created by Stephen Birarda on 12/5/2013.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AnimationServer_h
#define hifi_AnimationServer_h
#include <QtCore/QCoreApplication>
class AnimationServer : public QCoreApplication {
Q_OBJECT
public:
AnimationServer(int &argc, char **argv);
~AnimationServer();
private slots:
void readPendingDatagrams();
};
#endif // hifi_AnimationServer_h

View file

@ -1,17 +0,0 @@
//
// main.cpp
// animation-server/src
//
// Created by Brad Hefta-Gaub on 05/16/2013.
// Copyright 2012 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AnimationServer.h"
int main(int argc, char * argv[]) {
AnimationServer animationServer(argc, argv);
return animationServer.exec();
}

View file

@ -1,47 +1,17 @@
set(TARGET_NAME assignment-client)
set(ROOT_DIR ..)
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
setup_hifi_project(Core Gui Network Script Widgets)
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/")
find_package(Qt5 COMPONENTS Network Script Widgets)
include("${MACRO_DIR}/SetupHifiProject.cmake")
setup_hifi_project(${TARGET_NAME} TRUE)
# include glm
include("${MACRO_DIR}/IncludeGLM.cmake")
include_glm(${TARGET_NAME} "${ROOT_DIR}")
include_glm()
# link in the shared libraries
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}")
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(fbx ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(embedded-webserver ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_libraries(
audio avatars octree voxels fbx particles models metavoxels
networking animation shared script-engine embedded-webserver
)
if (UNIX)
target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS})
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK ${CMAKE_DL_LIBS})
endif (UNIX)
IF (WIN32)
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
ENDIF(WIN32)
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script)
# add a definition for ssize_t so that windows doesn't bail
if (WIN32)
add_definitions(-Dssize_t=long)
endif ()
link_shared_dependencies()

View file

@ -115,7 +115,7 @@ void Agent::readPendingDatagrams() {
sourceNode->setLastHeardMicrostamp(usecTimestampNow());
QByteArray mutablePacket = receivedPacket;
ssize_t messageLength = mutablePacket.size();
int messageLength = mutablePacket.size();
if (datagramPacketType == PacketTypeOctreeStats) {

View file

@ -37,6 +37,7 @@
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
@ -52,6 +53,7 @@
#include "AudioRingBuffer.h"
#include "AudioMixerClientData.h"
#include "AudioMixerDatagramProcessor.h"
#include "AvatarAudioStream.h"
#include "InjectedAudioStream.h"
@ -71,6 +73,8 @@ bool AudioMixer::_useDynamicJitterBuffers = false;
int AudioMixer::_staticDesiredJitterBufferFrames = 0;
int AudioMixer::_maxFramesOverDesired = 0;
bool AudioMixer::_printStreamStats = false;
AudioMixer::AudioMixer(const QByteArray& packet) :
ThreadedAssignment(packet),
_trailingSleepRatio(1.0f),
@ -295,38 +299,34 @@ void AudioMixer::prepareMixForListeningNode(Node* node) {
}
}
void AudioMixer::readPendingDatagrams() {
QByteArray receivedPacket;
HifiSockAddr senderSockAddr;
void AudioMixer::readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
NodeList* nodeList = NodeList::getInstance();
while (readAvailableDatagram(receivedPacket, senderSockAddr)) {
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
// pull any new audio data from nodes off of the network stack
PacketType mixerPacketType = packetTypeForPacket(receivedPacket);
if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho
|| mixerPacketType == PacketTypeMicrophoneAudioWithEcho
|| mixerPacketType == PacketTypeInjectAudio
|| mixerPacketType == PacketTypeSilentAudioFrame
|| mixerPacketType == PacketTypeAudioStreamStats) {
nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket);
} else if (mixerPacketType == PacketTypeMuteEnvironment) {
QByteArray packet = receivedPacket;
populatePacketHeader(packet, PacketTypeMuteEnvironment);
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && node != nodeList->sendingNodeForPacket(receivedPacket)) {
nodeList->writeDatagram(packet, packet.size(), node);
}
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
// pull any new audio data from nodes off of the network stack
PacketType mixerPacketType = packetTypeForPacket(receivedPacket);
if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho
|| mixerPacketType == PacketTypeMicrophoneAudioWithEcho
|| mixerPacketType == PacketTypeInjectAudio
|| mixerPacketType == PacketTypeSilentAudioFrame
|| mixerPacketType == PacketTypeAudioStreamStats) {
nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket);
} else if (mixerPacketType == PacketTypeMuteEnvironment) {
QByteArray packet = receivedPacket;
populatePacketHeader(packet, PacketTypeMuteEnvironment);
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && node != nodeList->sendingNodeForPacket(receivedPacket)) {
nodeList->writeDatagram(packet, packet.size(), node);
}
} else {
// let processNodeData handle it.
nodeList->processNodeData(senderSockAddr, receivedPacket);
}
} else {
// let processNodeData handle it.
nodeList->processNodeData(senderSockAddr, receivedPacket);
}
}
}
}
void AudioMixer::sendStatsPacket() {
@ -390,46 +390,58 @@ void AudioMixer::run() {
ThreadedAssignment::commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer);
NodeList* nodeList = NodeList::getInstance();
// we do not want this event loop to be the handler for UDP datagrams, so disconnect
disconnect(&nodeList->getNodeSocket(), 0, this, 0);
// setup a QThread with us as parent that will house the AudioMixerDatagramProcessor
_datagramProcessingThread = new QThread(this);
// create an AudioMixerDatagramProcessor and move it to that thread
AudioMixerDatagramProcessor* datagramProcessor = new AudioMixerDatagramProcessor(nodeList->getNodeSocket(), thread());
datagramProcessor->moveToThread(_datagramProcessingThread);
// remove the NodeList as the parent of the node socket
nodeList->getNodeSocket().setParent(NULL);
nodeList->getNodeSocket().moveToThread(_datagramProcessingThread);
// let the datagram processor handle readyRead from node socket
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead,
datagramProcessor, &AudioMixerDatagramProcessor::readPendingDatagrams);
// connect to the datagram processing thread signal that tells us we have to handle a packet
connect(datagramProcessor, &AudioMixerDatagramProcessor::packetRequiresProcessing, this, &AudioMixer::readPendingDatagram);
// delete the datagram processor and the associated thread when the QThread quits
connect(_datagramProcessingThread, &QThread::finished, datagramProcessor, &QObject::deleteLater);
connect(datagramProcessor, &QObject::destroyed, _datagramProcessingThread, &QThread::deleteLater);
// start the datagram processing thread
_datagramProcessingThread->start();
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
nodeList->linkedDataCreateCallback = attachNewNodeDataToNode;
// setup a NetworkAccessManager to ask the domain-server for our settings
NetworkAccessManager& networkManager = NetworkAccessManager::getInstance();
// wait until we have the domain-server settings, otherwise we bail
DomainHandler& domainHandler = nodeList->getDomainHandler();
QUrl settingsJSONURL;
settingsJSONURL.setScheme("http");
settingsJSONURL.setHost(nodeList->getDomainHandler().getHostname());
settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT);
settingsJSONURL.setPath("/settings.json");
settingsJSONURL.setQuery(QString("type=%1").arg(_type));
qDebug() << "Waiting for domain settings from domain-server.";
QNetworkReply *reply = NULL;
// block until we get the settingsRequestComplete signal
QEventLoop loop;
connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit);
connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit);
domainHandler.requestDomainSettings();
loop.exec();
int failedAttempts = 0;
const int MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS = 5;
qDebug() << "Requesting settings for assignment from domain-server at" << settingsJSONURL.toString();
while (!reply || reply->error() != QNetworkReply::NoError) {
reply = networkManager.get(QNetworkRequest(settingsJSONURL));
QEventLoop loop;
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
++failedAttempts;
if (failedAttempts == MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS) {
qDebug() << "Failed to get settings from domain-server. Bailing on assignment.";
setFinished(true);
return;
}
if (domainHandler.getSettingsObject().isEmpty()) {
qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment.";
setFinished(true);
return;
}
QJsonObject settingsObject = QJsonDocument::fromJson(reply->readAll()).object();
const QJsonObject& settingsObject = domainHandler.getSettingsObject();
// check the settings object to see if we have anything we can parse out
const QString AUDIO_GROUP_KEY = "audio";
@ -464,7 +476,11 @@ void AudioMixer::run() {
}
qDebug() << "Max frames over desired:" << _maxFramesOverDesired;
const QString PRINT_STREAM_STATS_JSON_KEY = "H-print-stream-stats";
_printStreamStats = audioGroupObject[PRINT_STREAM_STATS_JSON_KEY].toBool();
if (_printStreamStats) {
qDebug() << "Stream stats will be printed to stdout";
}
const QString UNATTENUATED_ZONE_KEY = "D-unattenuated-zone";
@ -563,6 +579,7 @@ void AudioMixer::run() {
sendAudioStreamStats = true;
}
bool streamStatsPrinted = false;
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getLinkedData()) {
AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData();
@ -597,12 +614,21 @@ void AudioMixer::run() {
// send an audio stream stats packet if it's time
if (sendAudioStreamStats) {
nodeData->sendAudioStreamStatsPackets(node);
if (_printStreamStats) {
printf("\nStats for agent %s:\n", node->getUUID().toString().toLatin1().data());
nodeData->printUpstreamDownstreamStats();
streamStatsPrinted = true;
}
}
++_sumListeners;
}
}
}
if (streamStatsPrinted) {
printf("\n----------------------------------------------------------------\n");
}
++_numStatFrames;

View file

@ -33,7 +33,8 @@ public slots:
/// threaded run of assignment
void run();
void readPendingDatagrams();
void readPendingDatagrams() { }; // this will not be called since our datagram processing thread will handle
void readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
void sendStatsPacket();
@ -66,6 +67,8 @@ private:
static int _staticDesiredJitterBufferFrames;
static int _maxFramesOverDesired;
static bool _printStreamStats;
quint64 _lastSendAudioStreamStatsTime;
};

View file

@ -22,7 +22,8 @@
AudioMixerClientData::AudioMixerClientData() :
_audioStreams(),
_outgoingMixedAudioSequenceNumber(0)
_outgoingMixedAudioSequenceNumber(0),
_downstreamAudioStreamStats()
{
}
@ -104,7 +105,7 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend(AABox* checkSourceZone, A
QHash<QUuid, PositionalAudioStream*>::ConstIterator i;
for (i = _audioStreams.constBegin(); i != _audioStreams.constEnd(); i++) {
PositionalAudioStream* stream = i.value();
if (stream->popFrames(1)) {
if (stream->popFrames(1, true) > 0) {
// this is a ring buffer that is ready to go
// calculate the trailing avg loudness for the next frame
@ -263,3 +264,45 @@ QString AudioMixerClientData::getAudioStreamStatsString() const {
}
return result;
}
void AudioMixerClientData::printUpstreamDownstreamStats() const {
// print the upstream (mic stream) stats if the mic stream exists
if (_audioStreams.contains(QUuid())) {
printf("Upstream:\n");
printAudioStreamStats(_audioStreams.value(QUuid())->getAudioStreamStats());
}
// print the downstream stats if they contain valid info
if (_downstreamAudioStreamStats._packetStreamStats._received > 0) {
printf("Downstream:\n");
printAudioStreamStats(_downstreamAudioStreamStats);
}
}
void AudioMixerClientData::printAudioStreamStats(const AudioStreamStats& streamStats) const {
printf(" Packet loss | overall: %5.2f%% (%d lost), last_30s: %5.2f%% (%d lost)\n",
streamStats._packetStreamStats.getLostRate() * 100.0f,
streamStats._packetStreamStats._lost,
streamStats._packetStreamWindowStats.getLostRate() * 100.0f,
streamStats._packetStreamWindowStats._lost);
printf(" Ringbuffer frames | desired: %u, avg_available(10s): %u, available: %u\n",
streamStats._desiredJitterBufferFrames,
streamStats._framesAvailableAverage,
streamStats._framesAvailable);
printf(" Ringbuffer stats | starves: %u, prev_starve_lasted: %u, frames_dropped: %u, overflows: %u\n",
streamStats._starveCount,
streamStats._consecutiveNotMixedCount,
streamStats._framesDropped,
streamStats._overflowCount);
printf(" Inter-packet timegaps (overall) | min: %9s, max: %9s, avg: %9s\n",
formatUsecTime(streamStats._timeGapMin).toLatin1().data(),
formatUsecTime(streamStats._timeGapMax).toLatin1().data(),
formatUsecTime(streamStats._timeGapAverage).toLatin1().data());
printf(" Inter-packet timegaps (last 30s) | min: %9s, max: %9s, avg: %9s\n",
formatUsecTime(streamStats._timeGapWindowMin).toLatin1().data(),
formatUsecTime(streamStats._timeGapWindowMax).toLatin1().data(),
formatUsecTime(streamStats._timeGapWindowAverage).toLatin1().data());
}

View file

@ -38,6 +38,11 @@ public:
void incrementOutgoingMixedAudioSequenceNumber() { _outgoingMixedAudioSequenceNumber++; }
quint16 getOutgoingSequenceNumber() const { return _outgoingMixedAudioSequenceNumber; }
void printUpstreamDownstreamStats() const;
private:
void printAudioStreamStats(const AudioStreamStats& streamStats) const;
private:
QHash<QUuid, PositionalAudioStream*> _audioStreams; // mic stream stored under key of null UUID

View file

@ -0,0 +1,47 @@
//
// AudioMixerDatagramProcessor.cpp
// assignment-client/src
//
// Created by Stephen Birarda on 2014-08-14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QDebug>
#include <HifiSockAddr.h>
#include <NodeList.h>
#include "AudioMixerDatagramProcessor.h"
AudioMixerDatagramProcessor::AudioMixerDatagramProcessor(QUdpSocket& nodeSocket, QThread* previousNodeSocketThread) :
_nodeSocket(nodeSocket),
_previousNodeSocketThread(previousNodeSocketThread)
{
}
AudioMixerDatagramProcessor::~AudioMixerDatagramProcessor() {
// return the node socket to its previous thread
_nodeSocket.moveToThread(_previousNodeSocketThread);
}
void AudioMixerDatagramProcessor::readPendingDatagrams() {
HifiSockAddr senderSockAddr;
static QByteArray incomingPacket;
// read everything that is available
while (_nodeSocket.hasPendingDatagrams()) {
incomingPacket.resize(_nodeSocket.pendingDatagramSize());
// just get this packet off the stack
_nodeSocket.readDatagram(incomingPacket.data(), incomingPacket.size(),
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
// emit the signal to tell AudioMixer it needs to process a packet
emit packetRequiresProcessing(incomingPacket, senderSockAddr);
}
}

View file

@ -0,0 +1,32 @@
//
// AudioMixerDatagramProcessor.h
// assignment-client/src
//
// Created by Stephen Birarda on 2014-08-14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AudioMixerDatagramProcessor_h
#define hifi_AudioMixerDatagramProcessor_h
#include <qobject.h>
#include <qudpsocket.h>
class AudioMixerDatagramProcessor : public QObject {
Q_OBJECT
public:
AudioMixerDatagramProcessor(QUdpSocket& nodeSocket, QThread* previousNodeSocketThread);
~AudioMixerDatagramProcessor();
public slots:
void readPendingDatagrams();
signals:
void packetRequiresProcessing(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
private:
QUdpSocket& _nodeSocket;
QThread* _previousNodeSocketThread;
};
#endif // hifi_AudioMixerDatagramProcessor_h

View file

@ -11,6 +11,8 @@
#include <QThread>
#include <GLMHelpers.h>
#include "ScriptableAvatar.h"
ScriptableAvatar::ScriptableAvatar(ScriptEngine* scriptEngine) : _scriptEngine(scriptEngine), _animation(NULL) {

View file

@ -9,11 +9,14 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QJsonDocument>
#include <QJsonObject>
#include <QTimer>
#include <QUuid>
#include <time.h>
#include <AccountManager.h>
#include <HTTPConnection.h>
#include <Logging.h>
#include <UUID.h>
@ -244,6 +247,10 @@ OctreeServer::OctreeServer(const QByteArray& packet) :
_averageLoopTime.updateAverage(0);
qDebug() << "Octree server starting... [" << this << "]";
// make sure the AccountManager has an Auth URL for payment redemptions
AccountManager::getInstance().setAuthURL(DEFAULT_NODE_AUTH_URL);
}
OctreeServer::~OctreeServer() {
@ -857,6 +864,8 @@ void OctreeServer::readPendingDatagrams() {
}
} else if (packetType == PacketTypeJurisdictionRequest) {
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
} else if (packetType == PacketTypeSignedTransactionPayment) {
handleSignedTransactionPayment(packetType, receivedPacket);
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
} else {
@ -1204,6 +1213,51 @@ QString OctreeServer::getStatusLink() {
return result;
}
void OctreeServer::handleSignedTransactionPayment(PacketType packetType, const QByteArray& datagram) {
// for now we're not verifying that this is actual payment for any octree edits
// just use the AccountManager to send it up to the data server and have it redeemed
AccountManager& accountManager = AccountManager::getInstance();
const int NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE = 72;
const int NUM_BYTES_SIGNED_TRANSACTION_BINARY_SIGNATURE = 256;
int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(packetType);
// pull out the transaction message in binary
QByteArray messageHex = datagram.mid(numBytesPacketHeader, NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE).toHex();
// pull out the binary signed message digest
QByteArray signatureHex = datagram.mid(numBytesPacketHeader + NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE,
NUM_BYTES_SIGNED_TRANSACTION_BINARY_SIGNATURE).toHex();
// setup the QJSONObject we are posting
QJsonObject postObject;
const QString TRANSACTION_OBJECT_MESSAGE_KEY = "message";
const QString TRANSACTION_OBJECT_SIGNATURE_KEY = "signature";
const QString POST_OBJECT_TRANSACTION_KEY = "transaction";
QJsonObject transactionObject;
transactionObject.insert(TRANSACTION_OBJECT_MESSAGE_KEY, QString(messageHex));
transactionObject.insert(TRANSACTION_OBJECT_SIGNATURE_KEY, QString(signatureHex));
postObject.insert(POST_OBJECT_TRANSACTION_KEY, transactionObject);
// setup our callback params
JSONCallbackParameters callbackParameters;
callbackParameters.jsonCallbackReceiver = this;
callbackParameters.jsonCallbackMethod = "handleSignedTransactionPaymentResponse";
accountManager.unauthenticatedRequest("/api/v1/transactions/redeem", QNetworkAccessManager::PostOperation,
callbackParameters, QJsonDocument(postObject).toJson());
}
void OctreeServer::handleSignedTransactionPaymentResponse(const QJsonObject& jsonObject) {
// pull the ID to debug the transaction
QString transactionIDString = jsonObject["data"].toObject()["transaction"].toObject()["id"].toString();
qDebug() << "Redeemed transaction with ID" << transactionIDString << "successfully.";
}
void OctreeServer::sendStatsPacket() {
// TODO: we have too many stats to fit in a single MTU... so for now, we break it into multiple JSON objects and
// send them separately. What we really should do is change the NodeList::sendStatsToDomainServer() to handle the

View file

@ -127,6 +127,8 @@ public slots:
void nodeAdded(SharedNodePointer node);
void nodeKilled(SharedNodePointer node);
void sendStatsPacket();
void handleSignedTransactionPaymentResponse(const QJsonObject& jsonObject);
protected:
void parsePayload();
@ -137,6 +139,8 @@ protected:
QString getConfiguration();
QString getStatusLink();
void handleSignedTransactionPayment(PacketType packetType, const QByteArray& datagram);
int _argc;
const char** _argv;
char** _parsedArgV;

View file

@ -8,8 +8,8 @@
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(AUTO_MTC TARGET ROOT_DIR)
set(AUTOMTC_SRC ${TARGET}_automtc.cpp)
macro(AUTO_MTC)
set(AUTOMTC_SRC ${TARGET_NAME}_automtc.cpp)
file(GLOB INCLUDE_FILES src/*.h)

View file

@ -7,7 +7,7 @@
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(INCLUDE_GLM TARGET ROOT_DIR)
macro(INCLUDE_GLM)
find_package(GLM REQUIRED)
include_directories("${GLM_INCLUDE_DIRS}")
@ -16,4 +16,4 @@ macro(INCLUDE_GLM TARGET ROOT_DIR)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${GLM_INCLUDE_DIRS}")
endif ()
endmacro(INCLUDE_GLM _target _root_dir)
endmacro(INCLUDE_GLM)

View file

@ -0,0 +1,14 @@
#
# IncludeHifiLibraryHeaders.cmake
# cmake/macros
#
# Copyright 2014 High Fidelity, Inc.
# Created by Stephen Birarda on August 8, 2014
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(include_hifi_library_headers LIBRARY)
include_directories("${HIFI_LIBRARY_DIR}/${LIBRARY}/src")
endmacro(include_hifi_library_headers _library _root_dir)

View file

@ -0,0 +1,34 @@
#
# LinkHifiLibrary.cmake
#
# Copyright 2013 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(LINK_HIFI_LIBRARIES)
file(RELATIVE_PATH RELATIVE_LIBRARY_DIR_PATH ${CMAKE_CURRENT_SOURCE_DIR} "${HIFI_LIBRARY_DIR}")
set(LIBRARIES_TO_LINK ${ARGN})
foreach(HIFI_LIBRARY ${LIBRARIES_TO_LINK})
if (NOT TARGET ${HIFI_LIBRARY})
add_subdirectory("${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}" "${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}")
endif ()
include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src")
add_dependencies(${TARGET_NAME} ${HIFI_LIBRARY})
# link the actual library - it is static so don't bubble it up
target_link_libraries(${TARGET_NAME} ${HIFI_LIBRARY})
# ask the library what its dynamic dependencies are and link them
get_target_property(LINKED_TARGET_DEPENDENCY_LIBRARIES ${HIFI_LIBRARY} DEPENDENCY_LIBRARIES)
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK ${LINKED_TARGET_DEPENDENCY_LIBRARIES})
endforeach()
endmacro(LINK_HIFI_LIBRARIES)

View file

@ -1,27 +0,0 @@
#
# LinkHifiLibrary.cmake
#
# Copyright 2013 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(LINK_HIFI_LIBRARY LIBRARY TARGET ROOT_DIR)
if (NOT TARGET ${LIBRARY})
add_subdirectory("${ROOT_DIR}/libraries/${LIBRARY}" "${ROOT_DIR}/libraries/${LIBRARY}")
endif ()
include_directories("${ROOT_DIR}/libraries/${LIBRARY}/src")
add_dependencies(${TARGET} ${LIBRARY})
target_link_libraries(${TARGET} ${LIBRARY})
if (APPLE)
# currently the "shared" library requires CoreServices
# link in required OS X framework
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreServices")
endif ()
endmacro(LINK_HIFI_LIBRARY _library _target _root_dir)

View file

@ -0,0 +1,25 @@
#
# LinkSharedDependencies.cmake
# cmake/macros
#
# Copyright 2014 High Fidelity, Inc.
# Created by Stephen Birarda on August 8, 2014
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(LINK_SHARED_DEPENDENCIES)
if (${TARGET_NAME}_LIBRARIES_TO_LINK)
list(REMOVE_DUPLICATES ${TARGET_NAME}_LIBRARIES_TO_LINK)
# link these libraries to our target
target_link_libraries(${TARGET_NAME} ${${TARGET_NAME}_LIBRARIES_TO_LINK})
endif ()
# we've already linked our Qt modules, but we need to bubble them up to parents
list(APPEND ${TARGET_NAME}_LIBRARIES_TO_LINK "${${TARGET}_QT_MODULES_TO_LINK}")
# set the property on this target so it can be retreived by targets linking to us
set_target_properties(${TARGET_NAME} PROPERTIES DEPENDENCY_LIBRARIES "${${TARGET}_LIBRARIES_TO_LINK}")
endmacro(LINK_SHARED_DEPENDENCIES)

View file

@ -7,20 +7,27 @@
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(SETUP_HIFI_LIBRARY TARGET)
macro(SETUP_HIFI_LIBRARY)
project(${TARGET})
project(${TARGET_NAME})
# grab the implemenation and header files
file(GLOB LIB_SRCS src/*.h src/*.cpp)
set(LIB_SRCS ${LIB_SRCS} ${WRAPPED_SRCS})
set(LIB_SRCS ${LIB_SRCS})
# create a library and set the property so it can be referenced later
add_library(${TARGET} ${LIB_SRCS} ${ARGN})
add_library(${TARGET_NAME} ${LIB_SRCS} ${AUTOMTC_SRC})
find_package(Qt5Core REQUIRED)
qt5_use_modules(${TARGET} Core)
target_link_libraries(${TARGET} ${QT_LIBRARIES})
endmacro(SETUP_HIFI_LIBRARY _target)
set(QT_MODULES_TO_LINK ${ARGN})
list(APPEND QT_MODULES_TO_LINK Core)
find_package(Qt5 COMPONENTS ${QT_MODULES_TO_LINK} REQUIRED)
foreach(QT_MODULE ${QT_MODULES_TO_LINK})
get_target_property(QT_LIBRARY_LOCATION Qt5::${QT_MODULE} LOCATION)
# add the actual path to the Qt module to our LIBRARIES_TO_LINK variable
target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})
list(APPEND ${TARGET_NAME}_QT_MODULES_TO_LINK ${QT_LIBRARY_LOCATION})
endforeach()
endmacro(SETUP_HIFI_LIBRARY)

View file

@ -7,8 +7,8 @@
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(SETUP_HIFI_PROJECT TARGET INCLUDE_QT)
project(${TARGET})
macro(SETUP_HIFI_PROJECT)
project(${TARGET_NAME})
# grab the implemenation and header files
file(GLOB TARGET_SRCS src/*)
@ -23,12 +23,19 @@ macro(SETUP_HIFI_PROJECT TARGET INCLUDE_QT)
endforeach()
# add the executable, include additional optional sources
add_executable(${TARGET} ${TARGET_SRCS} ${ARGN})
add_executable(${TARGET_NAME} ${TARGET_SRCS} "${AUTOMTC_SRC}")
if (${INCLUDE_QT})
find_package(Qt5Core REQUIRED)
qt5_use_modules(${TARGET} Core)
endif ()
set(QT_MODULES_TO_LINK ${ARGN})
list(APPEND QT_MODULES_TO_LINK Core)
find_package(Qt5 COMPONENTS ${QT_MODULES_TO_LINK} REQUIRED)
foreach(QT_MODULE ${QT_MODULES_TO_LINK})
target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})
# add the actual path to the Qt module to our LIBRARIES_TO_LINK variable
get_target_property(QT_LIBRARY_LOCATION Qt5::${QT_MODULE} LOCATION)
list(APPEND ${TARGET_NAME}_QT_MODULES_TO_LINK ${QT_LIBRARY_LOCATION})
endforeach()
target_link_libraries(${TARGET} ${QT_LIBRARIES})
endmacro()

View file

@ -0,0 +1,29 @@
#
# FindATL.cmake
#
# Try to find the ATL library needed to use the LibOVR
#
# Once done this will define
#
# ATL_FOUND - system found ATL
# ATL_LIBRARIES - Link this to use ATL
#
# Created on 8/13/2013 by Stephen Birarda
# Copyright 2014 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
if (WIN32)
find_library(ATL_LIBRARY_RELEASE atls PATH_SUFFIXES "7600.16385.1/lib/ATL/i386" HINTS "C:\\WinDDK")
find_library(ATL_LIBRARY_DEBUG atlsd PATH_SUFFIXES "7600.16385.1/lib/ATL/i386" HINTS "C:\\WinDDK")
include(SelectLibraryConfigurations)
select_library_configurations(ATL)
endif ()
set(ATL_LIBRARIES "${ATL_LIBRARY}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(ATL DEFAULT_MSG ATL_LIBRARIES)

View file

@ -19,6 +19,6 @@ if (WIN32)
endif (WIN32)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(FACEPLUS DEFAULT_MSG FACEPLUS_INCLUDE_DIRS FACEPLUS_LIBRARIES)
find_package_handle_standard_args(Faceplus DEFAULT_MSG FACEPLUS_INCLUDE_DIRS FACEPLUS_LIBRARIES)
mark_as_advanced(FACEPLUS_INCLUDE_DIRS FACEPLUS_LIBRARIES)

View file

@ -40,6 +40,6 @@ select_library_configurations(FACESHIFT)
set(FACESHIFT_LIBRARIES ${FACESHIFT_LIBRARY})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(FACESHIFT DEFAULT_MSG FACESHIFT_INCLUDE_DIRS FACESHIFT_LIBRARIES)
find_package_handle_standard_args(Faceshift DEFAULT_MSG FACESHIFT_INCLUDE_DIRS FACESHIFT_LIBRARIES)
mark_as_advanced(FACESHIFT_INCLUDE_DIRS FACESHIFT_LIBRARIES FACESHIFT_SEARCH_DIRS)

View file

@ -30,6 +30,6 @@ select_library_configurations(LEAPMOTION)
set(LEAPMOTION_LIBRARIES "${LEAPMOTION_LIBRARY}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LEAPMOTION DEFAULT_MSG LEAPMOTION_INCLUDE_DIRS LEAPMOTION_LIBRARIES)
find_package_handle_standard_args(LeapMotion DEFAULT_MSG LEAPMOTION_INCLUDE_DIRS LEAPMOTION_LIBRARIES)
mark_as_advanced(LEAPMOTION_INCLUDE_DIRS LEAPMOTION_LIBRARIES LEAPMOTION_SEARCH_DIRS)

View file

@ -19,16 +19,18 @@
#
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("oculus")
hifi_library_search_hints("libovr")
find_path(LIBOVR_INCLUDE_DIRS OVR.h PATH_SUFFIXES Include HINTS ${OCULUS_SEARCH_DIRS})
find_path(LIBOVR_SRC_DIR Util_Render_Stereo.h PATH_SUFFIXES Src/Util HINTS ${OCULUS_SEARCH_DIRS})
find_path(LIBOVR_INCLUDE_DIRS OVR.h PATH_SUFFIXES Include HINTS ${LIBOVR_SEARCH_DIRS})
find_path(LIBOVR_SRC_DIR Util_Render_Stereo.h PATH_SUFFIXES Src/Util HINTS ${LIBOVR_SEARCH_DIRS})
include(SelectLibraryConfigurations)
if (APPLE)
find_library(LIBOVR_LIBRARY_DEBUG NAMES ovr PATH_SUFFIXES Lib/MacOS/Debug HINTS ${OCULUS_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_RELEASE NAMES ovr PATH_SUFFIXES Lib/MacOS/Release HINTS ${OCULUS_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_DEBUG NAMES ovr PATH_SUFFIXES Lib/Mac/Debug HINTS ${LIBOVR_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_RELEASE NAMES ovr PATH_SUFFIXES Lib/Mac/Release HINTS ${LIBOVR_SEARCH_DIRS})
find_library(ApplicationServices ApplicationServices)
find_library(IOKit IOKit)
elseif (UNIX)
find_library(UDEV_LIBRARY_RELEASE udev /usr/lib/x86_64-linux-gnu/)
find_library(XINERAMA_LIBRARY_RELEASE Xinerama /usr/lib/x86_64-linux-gnu/)
@ -39,29 +41,36 @@ elseif (UNIX)
set(LINUX_ARCH_DIR "x86_64")
endif()
find_library(LIBOVR_LIBRARY_DEBUG NAMES ovr PATH_SUFFIXES Lib/Linux/Debug/${LINUX_ARCH_DIR} HINTS ${OCULUS_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_RELEASE NAMES ovr PATH_SUFFIXES Lib/Linux/Release/${LINUX_ARCH_DIR} HINTS ${OCULUS_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_DEBUG NAMES ovr PATH_SUFFIXES Lib/Linux/Debug/${LINUX_ARCH_DIR} HINTS ${LIBOVR_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_RELEASE NAMES ovr PATH_SUFFIXES Lib/Linux/Release/${LINUX_ARCH_DIR} HINTS ${LIBOVR_SEARCH_DIRS})
select_library_configurations(UDEV)
select_library_configurations(XINERAMA)
elseif (WIN32)
find_library(LIBOVR_LIBRARY_DEBUG NAMES libovrd PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${OCULUS_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_RELEASE NAMES libovr PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${OCULUS_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_DEBUG NAMES libovrd PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
find_library(LIBOVR_LIBRARY_RELEASE NAMES libovr PATH_SUFFIXES Lib/Win32/VS2010 HINTS ${LIBOVR_SEARCH_DIRS})
find_package(ATL)
endif ()
select_library_configurations(LIBOVR)
set(LIBOVR_LIBRARIES "${LIBOVR_LIBRARY}")
set(LIBOVR_LIBRARIES ${LIBOVR_LIBRARY})
if (UNIX AND NOT APPLE)
set(LIBOVR_LIBRARIES "${LIBOVR_LIBRARIES}" "${UDEV_LIBRARY}" "${XINERAMA_LIBRARY}")
list(APPEND LIBOVR_ARGS_LIST LIBOVR_INCLUDE_DIRS LIBOVR_SRC_DIR LIBOVR_LIBRARY)
if (APPLE)
list(APPEND LIBOVR_LIBRARIES ${IOKit} ${ApplicationServices})
list(APPEND LIBOVR_ARGS_LIST IOKit ApplicationServices)
elseif (UNIX)
list(APPEND LIBOVR_LIBRARIES "${UDEV_LIBRARY}" "${XINERAMA_LIBRARY}")
list(APPEND LIBOVR_ARGS_LIST UDEV_LIBRARY XINERAMA_LIBRARY)
elseif (WIN32)
list(APPEND LIBOVR_LIBRARIES ${ATL_LIBRARIES})
list(APPEND LIBOVR_ARGS_LIST ATL_LIBRARIES)
endif ()
include(FindPackageHandleStandardArgs)
if (UNIX AND NOT APPLE)
find_package_handle_standard_args(LIBOVR DEFAULT_MSG LIBOVR_INCLUDE_DIRS LIBOVR_SRC_DIR LIBOVR_LIBRARIES UDEV_LIBRARY XINERAMA_LIBRARY)
else ()
find_package_handle_standard_args(LIBOVR DEFAULT_MSG LIBOVR_INCLUDE_DIRS LIBOVR_SRC_DIR LIBOVR_LIBRARIES)
endif ()
mark_as_advanced(LIBOVR_INCLUDE_DIRS LIBOVR_LIBRARIES OCULUS_SEARCH_DIRS)
find_package_handle_standard_args(LibOVR DEFAULT_MSG ${LIBOVR_ARGS_LIST})
mark_as_advanced(LIBOVR_INCLUDE_DIRS LIBOVR_LIBRARIES LIBOVR_SEARCH_DIRS)

View file

@ -19,6 +19,6 @@ if (WIN32)
endif (WIN32)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PRIOVR DEFAULT_MSG PRIOVR_INCLUDE_DIRS PRIOVR_LIBRARIES)
find_package_handle_standard_args(PrioVR DEFAULT_MSG PRIOVR_INCLUDE_DIRS PRIOVR_LIBRARIES)
mark_as_advanced(PRIOVR_INCLUDE_DIRS PRIOVR_LIBRARIES)

View file

@ -26,12 +26,14 @@ find_path(QXMPP_INCLUDE_DIRS QXmppClient.h PATH_SUFFIXES include/qxmpp HINTS ${Q
find_library(QXMPP_LIBRARY_RELEASE NAMES qxmpp PATH_SUFFIXES lib HINTS ${QXMPP_SEARCH_DIRS})
find_library(QXMPP_LIBRARY_DEBUG NAMES qxmpp_d PATH_SUFFIXES lib HINTS ${QXMPP_SEARCH_DIRS})
find_package(Qt5 COMPONENTS Xml REQUIRED)
include(SelectLibraryConfigurations)
select_library_configurations(QXMPP)
set(QXMPP_LIBRARIES "${QXMPP_LIBRARY}")
set(QXMPP_LIBRARIES "${QXMPP_LIBRARY}" Qt5::Xml)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(QXMPP DEFAULT_MSG QXMPP_INCLUDE_DIRS QXMPP_LIBRARIES)
find_package_handle_standard_args(QXmpp DEFAULT_MSG QXMPP_INCLUDE_DIRS QXMPP_LIBRARIES QXMPP_LIBRARY)
mark_as_advanced(QXMPP_INCLUDE_DIRS QXMPP_LIBRARIES QXMPP_SEARCH_DIRS)

View file

@ -25,6 +25,6 @@ find_path(RTMIDI_INCLUDE_DIRS RtMidi.h PATH_SUFFIXES include HINTS ${RTMIDI_SEAR
find_library(RTMIDI_LIBRARIES NAMES rtmidi PATH_SUFFIXES lib HINTS ${RTMIDI_SEARCH_DIRS})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(RTMIDI DEFAULT_MSG RTMIDI_INCLUDE_DIRS RTMIDI_LIBRARIES)
find_package_handle_standard_args(RtMidi DEFAULT_MSG RTMIDI_INCLUDE_DIRS RTMIDI_LIBRARIES)
mark_as_advanced(RTMIDI_INCLUDE_DIRS RTMIDI_LIBRARIES RTMIDI_SEARCH_DIRS)

View file

@ -40,6 +40,6 @@ select_library_configurations(SIXENSE)
set(SIXENSE_LIBRARIES "${SIXENSE_LIBRARY}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(SIXENSE DEFAULT_MSG SIXENSE_INCLUDE_DIRS SIXENSE_LIBRARIES)
find_package_handle_standard_args(Sixense DEFAULT_MSG SIXENSE_INCLUDE_DIRS SIXENSE_LIBRARIES)
mark_as_advanced(SIXENSE_LIBRARIES SIXENSE_INCLUDE_DIRS SIXENSE_SEARCH_DIRS)

View file

@ -30,8 +30,11 @@ if (APPLE)
find_library(VISAGE_CORE_LIBRARY NAME vscore PATH_SUFFIXES lib HINTS ${VISAGE_SEARCH_DIRS})
find_library(VISAGE_VISION_LIBRARY NAME vsvision PATH_SUFFIXES lib HINTS ${VISAGE_SEARCH_DIRS})
find_library(VISAGE_OPENCV_LIBRARY NAME OpenCV PATH_SUFFIXES dependencies/OpenCV_MacOSX/lib ${VISAGE_SEARCH_DIRS})
find_library(VISAGE_OPENCV_LIBRARY NAME OpenCV PATH_SUFFIXES dependencies/OpenCV_MacOSX/lib HINTS ${VISAGE_SEARCH_DIRS})
find_library(AppKit AppKit)
find_library(QuartzCore QuartzCore)
find_library(QTKit QTKit)
elseif (WIN32)
find_path(VISAGE_XML_INCLUDE_DIR libxml/xmlreader.h PATH_SUFFIXES dependencies/libxml2/include HINTS ${VISAGE_SEARCH_DIRS})
find_path(VISAGE_OPENCV_INCLUDE_DIR opencv/cv.h PATH_SUFFIXES dependencies/OpenCV/include HINTS ${VISAGE_SEARCH_DIRS})
@ -43,16 +46,22 @@ elseif (WIN32)
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(VISAGE DEFAULT_MSG
VISAGE_BASE_INCLUDE_DIR VISAGE_XML_INCLUDE_DIR VISAGE_OPENCV_INCLUDE_DIR VISAGE_OPENCV2_INCLUDE_DIR
VISAGE_CORE_LIBRARY VISAGE_VISION_LIBRARY VISAGE_OPENCV_LIBRARY
)
list(APPEND VISAGE_ARGS_LIST VISAGE_BASE_INCLUDE_DIR VISAGE_XML_INCLUDE_DIR
VISAGE_OPENCV_INCLUDE_DIR VISAGE_OPENCV2_INCLUDE_DIR
VISAGE_CORE_LIBRARY VISAGE_VISION_LIBRARY VISAGE_OPENCV_LIBRARY)
if (APPLE)
list(APPEND VISAGE_ARGS_LIST QuartzCore AppKit QTKit)
endif ()
find_package_handle_standard_args(Visage DEFAULT_MSG ${VISAGE_ARGS_LIST})
set(VISAGE_INCLUDE_DIRS "${VISAGE_XML_INCLUDE_DIR}" "${VISAGE_OPENCV_INCLUDE_DIR}" "${VISAGE_OPENCV2_INCLUDE_DIR}" "${VISAGE_BASE_INCLUDE_DIR}")
set(VISAGE_LIBRARIES "${VISAGE_CORE_LIBRARY}" "${VISAGE_VISION_LIBRARY}" "${VISAGE_OPENCV_LIBRARY}")
mark_as_advanced(
VISAGE_INCLUDE_DIRS VISAGE_LIBRARIES
VISAGE_BASE_INCLUDE_DIR VISAGE_XML_INCLUDE_DIR VISAGE_OPENCV_INCLUDE_DIR VISAGE_OPENCV2_INCLUDE_DIR
VISAGE_CORE_LIBRARY VISAGE_VISION_LIBRARY VISAGE_OPENCV_LIBRARY VISAGE_SEARCH_DIRS
)
if (APPLE)
list(APPEND VISAGE_LIBRARIES ${QuartzCore} ${AppKit} ${QTKit})
endif ()
mark_as_advanced(VISAGE_INCLUDE_DIRS VISAGE_LIBRARIES)

View file

@ -1,24 +1,7 @@
if (WIN32)
cmake_policy (SET CMP0020 NEW)
endif (WIN32)
set(TARGET_NAME domain-server)
set(ROOT_DIR ..)
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/")
# set up the external glm library
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} "${ROOT_DIR}")
find_package(Qt5Network REQUIRED)
include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE)
# setup the project and link required Qt modules
setup_hifi_project(Network)
# remove and then copy the files for the webserver
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
@ -29,20 +12,7 @@ add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
"${PROJECT_SOURCE_DIR}/resources/web"
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web)
# link the shared hifi library
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(embedded-webserver ${TARGET_NAME} "${ROOT_DIR}")
# link the shared hifi libraries
link_hifi_libraries(embedded-webserver networking shared)
IF (WIN32)
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
ENDIF(WIN32)
# link QtNetwork
target_link_libraries(${TARGET_NAME} Qt5::Network)
# add a definition for ssize_t so that windows doesn't bail
if (WIN32)
add_definitions(-Dssize_t=long)
endif ()
link_shared_dependencies()

View file

@ -1,3 +1,4 @@
</div>
<script src='/js/jquery-2.0.3.min.js'></script>
<script src='/js/bootstrap.min.js'></script>
<script src='/js/bootstrap.min.js'></script>
<script src='/js/domain-server.js'></script>

View file

@ -8,4 +8,33 @@
<link href="/css/style.css" rel="stylesheet" media="screen">
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">domain-server</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/">Nodes</a></li>
<li><a href="/settings/">Settings</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="/assignment">New Assignment</a></li>
</ul>
</li>
</ul>
</div>
</div><!-- /.container-fluid -->
</nav>
<div class="container">

View file

@ -10,6 +10,7 @@
<th>Type</th>
<th>UUID</th>
<th>Pool</th>
<th>Username</th>
<th>Public</th>
<th>Local</th>
<th>Uptime (s)</th>
@ -24,8 +25,9 @@
<td><%- node.type %></td>
<td><a href="stats/?uuid=<%- node.uuid %>"><%- node.uuid %></a></td>
<td><%- node.pool %></td>
<td><%- node.public.ip %><span class='port'><%- node.public.port %></span></td>
<td><%- node.local.ip %><span class='port'><%- node.local.port %></span></td>
<td><%- node.username %></td>
<td><%- node.public.ip %><span class='port'>:<%- node.public.port %></span></td>
<td><%- node.local.ip %><span class='port'>:<%- node.local.port %></span></td>
<td><%- ((Date.now() - node.wake_timestamp) / 1000).toLocaleString() %></td>
<td><%- (typeof node.pending_credits == 'number' ? node.pending_credits.toLocaleString() : 'N/A') %></td>
<td><span class='glyphicon glyphicon-remove' data-uuid="<%- node.uuid %>"></span></td>

View file

@ -0,0 +1,10 @@
$(document).ready(function(){
var url = window.location;
// Will only work if string in href matches with location
$('ul.nav a[href="'+ url +'"]').parent().addClass('active');
// Will also work for relative and absolute hrefs
$('ul.nav a').filter(function() {
return this.href == url;
}).parent().addClass('active');
});

View file

@ -21,6 +21,12 @@
"placeholder": "10",
"default": "10"
},
"H-print-stream-stats": {
"type": "checkbox",
"label": "Print Stream Stats:",
"help": "If enabled, audio upstream and downstream stats of each agent will be printed each second to stdout",
"default": false
},
"D-unattenuated-zone": {
"label": "Unattenuated Zone",
"help": "Boxes for source and listener (corner x, corner y, corner z, size x, size y, size z, corner x, corner y, corner z, size x, size y, size z)",

View file

@ -16,16 +16,22 @@
<% var setting_id = group_key + "." + setting_key %>
<label for="<%- setting_id %>" class="col-sm-2 control-label"><%- setting.label %></label>
<div class="col-sm-10">
<% if(setting.type) %>
<% if (setting.type === "checkbox") { %>
<% var checked_box = (values[group_key] || {})[setting_key] || setting.default %>
<input type="checkbox" id="<%- setting_id %>" <%- checked_box ? "checked" : "" %>>
<% } else { %>
<% if (setting.type === "checkbox") { %>
<% var checked_box = (values[group_key] || {})[setting_key] || setting.default %>
<input type="checkbox" id="<%- setting_id %>" <%- checked_box ? "checked" : "" %>>
<% } else { %>
<% if (setting.input_addon) { %>
<div class="input-group">
<div class="input-group-addon"><%- setting.input_addon %></div>
<% } %>
<input type="text" class="form-control" id="<%- setting_id %>"
placeholder="<%- setting.placeholder %>"
value="<%- (values[group_key] || {})[setting_key] %>">
<% if (setting.input_addon) { %>
</div>
<% } %>
<% } %>
</div>
<p class="help-block col-sm-offset-2 col-sm-10"><%- setting.help %></p>
</div>

View file

@ -42,12 +42,17 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_hostname(),
_networkReplyUUIDMap(),
_sessionAuthenticationHash(),
_webAuthenticationStateSet(),
_cookieSessionHash(),
_settingsManager()
{
setOrganizationName("High Fidelity");
setOrganizationDomain("highfidelity.io");
setApplicationName("domain-server");
QSettings::setDefaultFormat(QSettings::IniFormat);
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
@ -57,6 +62,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qDebug() << "Setting up LimitedNodeList and assignments.";
setupNodeListAndAssignments();
loadExistingSessionsFromSettings();
}
}
@ -426,11 +433,14 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
}
}
QString connectedUsername;
if (!isAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
// this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones
if (_sessionAuthenticationHash.contains(packetUUID)) {
if (!_sessionAuthenticationHash.value(packetUUID)) {
connectedUsername = _sessionAuthenticationHash.take(packetUUID);
if (connectedUsername.isEmpty()) {
// we've decided this is a user that isn't allowed in, return out
// TODO: provide information to the user so they know why they can't connect
return;
@ -473,6 +483,9 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
// now that we've pulled the wallet UUID and added the node to our list, delete the pending assignee data
delete pendingAssigneeData;
}
// if we have a username from an OAuth connect request, set it on the DomainServerNodeData
nodeData->setUsername(connectedUsername);
nodeData->setSendingSockAddr(senderSockAddr);
@ -848,7 +861,7 @@ QJsonObject DomainServer::jsonForSocket(const HifiSockAddr& socket) {
QJsonObject socketJSON;
socketJSON["ip"] = socket.getAddress().toString();
socketJSON["port"] = ntohs(socket.getPort());
socketJSON["port"] = socket.getPort();
return socketJSON;
}
@ -860,6 +873,7 @@ const char JSON_KEY_LOCAL_SOCKET[] = "local";
const char JSON_KEY_POOL[] = "pool";
const char JSON_KEY_PENDING_CREDITS[] = "pending_credits";
const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp";
const char JSON_KEY_USERNAME[] = "username";
QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
QJsonObject nodeJson;
@ -884,6 +898,10 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
// if the node has pool information, add it
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
// add the node username, if it exists
nodeJson[JSON_KEY_USERNAME] = nodeData->getUsername();
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
if (matchingAssignment) {
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
@ -921,7 +939,52 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
const QString URI_NODES = "/nodes";
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
// allow sub-handlers to handle requests that do not require authentication
if (_settingsManager.handlePublicHTTPRequest(connection, url)) {
return true;
}
// check if this is a request for a scripted assignment (with a temp unique UUID)
const QString ASSIGNMENT_REGEX_STRING = QString("\\%1\\/(%2)\\/?$").arg(URI_ASSIGNMENT).arg(UUID_REGEX_STRING);
QRegExp assignmentRegex(ASSIGNMENT_REGEX_STRING);
if (connection->requestOperation() == QNetworkAccessManager::GetOperation
&& assignmentRegex.indexIn(url.path()) != -1) {
QUuid matchingUUID = QUuid(assignmentRegex.cap(1));
SharedAssignmentPointer matchingAssignment = _allAssignments.value(matchingUUID);
if (!matchingAssignment) {
// check if we have a pending assignment that matches this temp UUID, and it is a scripted assignment
PendingAssignedNodeData* pendingData = _pendingAssignedNodes.value(matchingUUID);
if (pendingData) {
matchingAssignment = _allAssignments.value(pendingData->getAssignmentUUID());
if (matchingAssignment && matchingAssignment->getType() == Assignment::AgentType) {
// we have a matching assignment and it is for the right type, have the HTTP manager handle it
// via correct URL for the script so the client can download
QUrl scriptURL = url;
scriptURL.setPath(URI_ASSIGNMENT + "/"
+ uuidStringWithoutCurlyBraces(pendingData->getAssignmentUUID()));
// have the HTTPManager serve the appropriate script file
return _httpManager.handleHTTPRequest(connection, scriptURL);
}
}
}
// request not handled
return false;
}
// all requests below require a cookie to prove authentication so check that first
if (!isAuthenticatedRequest(connection, url)) {
// this is not an authenticated request
// return true from the handler since it was handled with a 401 or re-direct to auth
return true;
}
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
if (url.path() == "/assignments.json") {
// user is asking for json list of assignments
@ -1038,38 +1101,6 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
return false;
}
// check if this is a request for a scripted assignment (with a temp unique UUID)
const QString ASSIGNMENT_REGEX_STRING = QString("\\%1\\/(%2)\\/?$").arg(URI_ASSIGNMENT).arg(UUID_REGEX_STRING);
QRegExp assignmentRegex(ASSIGNMENT_REGEX_STRING);
if (assignmentRegex.indexIn(url.path()) != -1) {
QUuid matchingUUID = QUuid(assignmentRegex.cap(1));
SharedAssignmentPointer matchingAssignment = _allAssignments.value(matchingUUID);
if (!matchingAssignment) {
// check if we have a pending assignment that matches this temp UUID, and it is a scripted assignment
PendingAssignedNodeData* pendingData = _pendingAssignedNodes.value(matchingUUID);
if (pendingData) {
matchingAssignment = _allAssignments.value(pendingData->getAssignmentUUID());
if (matchingAssignment && matchingAssignment->getType() == Assignment::AgentType) {
// we have a matching assignment and it is for the right type, have the HTTP manager handle it
// via correct URL for the script so the client can download
QUrl scriptURL = url;
scriptURL.setPath(URI_ASSIGNMENT + "/"
+ uuidStringWithoutCurlyBraces(pendingData->getAssignmentUUID()));
// have the HTTPManager serve the appropriate script file
return _httpManager.handleHTTPRequest(connection, scriptURL);
}
}
}
// request not handled
return false;
}
}
} else if (connection->requestOperation() == QNetworkAccessManager::PostOperation) {
if (url.path() == URI_ASSIGNMENT) {
@ -1162,9 +1193,11 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
}
// didn't process the request, let our DomainServerSettingsManager or HTTPManager handle
return _settingsManager.handleHTTPRequest(connection, url);
return _settingsManager.handleAuthenticatedHTTPRequest(connection, url);
}
const QString HIFI_SESSION_COOKIE_KEY = "DS_WEB_SESSION_UUID";
bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url) {
const QString URI_OAUTH = "/oauth";
qDebug() << "HTTPS request received at" << url.toString();
@ -1178,7 +1211,6 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
const QString STATE_QUERY_KEY = "state";
QUuid stateUUID = QUuid(codeURLQuery.queryItemValue(STATE_QUERY_KEY));
if (!authorizationCode.isEmpty() && !stateUUID.isNull()) {
// fire off a request with this code and state to get an access token for the user
@ -1193,15 +1225,45 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
QNetworkRequest tokenRequest(tokenRequestUrl);
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply* tokenReply = NetworkAccessManager::getInstance().post(tokenRequest, tokenPostBody.toLocal8Bit());
qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID);
// insert this to our pending token replies so we can associate the returned access token with the right UUID
_networkReplyUUIDMap.insert(tokenReply, stateUUID);
connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::handleTokenRequestFinished);
if (_webAuthenticationStateSet.remove(stateUUID)) {
// this is a web user who wants to auth to access web interface
// we hold the response back to them until we get their profile information
// and can decide if they are let in or not
QEventLoop loop;
connect(tokenReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
// start the loop for the token request
loop.exec();
QNetworkReply* profileReply = profileRequestGivenTokenReply(tokenReply);
// stop the loop once the profileReply is complete
connect(profileReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
// restart the loop for the profile request
loop.exec();
// call helper method to get cookieHeaders
Headers cookieHeaders = setupCookieHeadersFromProfileReply(profileReply);
connection->respond(HTTPConnection::StatusCode302, QByteArray(),
HTTPConnection::DefaultContentType, cookieHeaders);
// we've redirected the user back to our homepage
return true;
} else {
qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID);
// insert this to our pending token replies so we can associate the returned access token with the right UUID
_networkReplyUUIDMap.insert(tokenReply, stateUUID);
connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::handleTokenRequestFinished);
}
}
// respond with a 200 code indicating that login is complete
@ -1213,6 +1275,137 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
}
}
bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url) {
const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
const QString BASIC_AUTH_CONFIG_KEY = "basic-auth";
const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server.";
if (!_oauthProviderURL.isEmpty()
&& (_argumentVariantMap.contains(ADMIN_USERS_CONFIG_KEY) || _argumentVariantMap.contains(ADMIN_ROLES_CONFIG_KEY))) {
QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY);
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING);
QUuid cookieUUID;
if (cookieString.indexOf(cookieUUIDRegex) != -1) {
cookieUUID = cookieUUIDRegex.cap(1);
}
if (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) {
qDebug() << "Config file contains web admin settings for OAuth and basic HTTP authentication."
<< "These cannot be combined - using OAuth for authentication.";
}
if (!cookieUUID.isNull() && _cookieSessionHash.contains(cookieUUID)) {
// pull the QJSONObject for the user with this cookie UUID
DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID);
QString profileUsername = sessionData.getUsername();
if (_argumentVariantMap.value(ADMIN_USERS_CONFIG_KEY).toJsonValue().toArray().contains(profileUsername)) {
// this is an authenticated user
return true;
}
// loop the roles of this user and see if they are in the admin-roles array
QJsonArray adminRolesArray = _argumentVariantMap.value(ADMIN_ROLES_CONFIG_KEY).toJsonValue().toArray();
if (!adminRolesArray.isEmpty()) {
foreach(const QString& userRole, sessionData.getRoles()) {
if (adminRolesArray.contains(userRole)) {
// this user has a role that allows them to administer the domain-server
return true;
}
}
}
connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY);
// the user does not have allowed username or role, return 401
return false;
} else {
// re-direct this user to OAuth page
// generate a random state UUID to use
QUuid stateUUID = QUuid::createUuid();
// add it to the set so we can handle the callback from the OAuth provider
_webAuthenticationStateSet.insert(stateUUID);
QUrl oauthRedirectURL = oauthAuthorizationURL(stateUUID);
Headers redirectHeaders;
redirectHeaders.insert("Location", oauthRedirectURL.toEncoded());
connection->respond(HTTPConnection::StatusCode302,
QByteArray(), HTTPConnection::DefaultContentType, redirectHeaders);
// we don't know about this user yet, so they are not yet authenticated
return false;
}
} else if (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) {
// config file contains username and password combinations for basic auth
const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization";
// check if a username and password have been provided with the request
QString basicAuthString = connection->requestHeaders().value(BASIC_AUTH_HEADER_KEY);
if (!basicAuthString.isEmpty()) {
QStringList splitAuthString = basicAuthString.split(' ');
QString base64String = splitAuthString.size() == 2 ? splitAuthString[1] : "";
QString credentialString = QByteArray::fromBase64(base64String.toLocal8Bit());
if (!credentialString.isEmpty()) {
QStringList credentialList = credentialString.split(':');
if (credentialList.size() == 2) {
QString username = credentialList[0];
QString password = credentialList[1];
// we've pulled a username and password - now check if there is a match in our basic auth hash
QJsonObject basicAuthObject = _argumentVariantMap.value(BASIC_AUTH_CONFIG_KEY).toJsonValue().toObject();
if (basicAuthObject.contains(username)) {
const QString BASIC_AUTH_USER_PASSWORD_KEY = "password";
QJsonObject userObject = basicAuthObject.value(username).toObject();
if (userObject.contains(BASIC_AUTH_USER_PASSWORD_KEY)
&& userObject.value(BASIC_AUTH_USER_PASSWORD_KEY).toString() == password) {
// this is username / password match - let this user in
return true;
}
}
}
}
}
// basic HTTP auth being used but no username and password are present
// or the username and password are not correct
// send back a 401 and ask for basic auth
const QByteArray HTTP_AUTH_REQUEST_HEADER_KEY = "WWW-Authenticate";
static QString HTTP_AUTH_REALM_STRING = QString("Basic realm='%1 %2'")
.arg(_hostname.isEmpty() ? "localhost" : _hostname)
.arg("domain-server");
Headers basicAuthHeader;
basicAuthHeader.insert(HTTP_AUTH_REQUEST_HEADER_KEY, HTTP_AUTH_REALM_STRING.toUtf8());
connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY,
HTTPConnection::DefaultContentType, basicAuthHeader);
// not authenticated, bubble up false
return false;
} else {
// we don't have an OAuth URL + admin roles/usernames, so all users are authenticated
return true;
}
}
const QString OAUTH_JSON_ACCESS_TOKEN_KEY = "access_token";
void DomainServer::handleTokenRequestFinished() {
@ -1220,20 +1413,11 @@ void DomainServer::handleTokenRequestFinished() {
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) {
// pull the access token from the returned JSON and store it with the matching session UUID
QJsonDocument returnedJSON = QJsonDocument::fromJson(networkReply->readAll());
QString accessToken = returnedJSON.object()[OAUTH_JSON_ACCESS_TOKEN_KEY].toString();
qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
// fire off a request to get this user's identity so we can see if we will let them in
QUrl profileURL = _oauthProviderURL;
profileURL.setPath("/api/v1/users/profile");
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
QNetworkReply* profileReply = NetworkAccessManager::getInstance().get(QNetworkRequest(profileURL));
qDebug() << "Requesting access token for user with session UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID)
<< "-" << "requesting profile.";
QNetworkReply* profileReply = profileRequestGivenTokenReply(networkReply);
connect(profileReply, &QNetworkReply::finished, this, &DomainServer::handleProfileRequestFinished);
@ -1241,6 +1425,19 @@ void DomainServer::handleTokenRequestFinished() {
}
}
QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenReply) {
// pull the access token from the returned JSON and store it with the matching session UUID
QJsonDocument returnedJSON = QJsonDocument::fromJson(tokenReply->readAll());
QString accessToken = returnedJSON.object()[OAUTH_JSON_ACCESS_TOKEN_KEY].toString();
// fire off a request to get this user's identity so we can see if we will let them in
QUrl profileURL = _oauthProviderURL;
profileURL.setPath("/api/v1/users/profile");
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
return NetworkAccessManager::getInstance().get(QNetworkRequest(profileURL));
}
void DomainServer::handleProfileRequestFinished() {
QNetworkReply* networkReply = reinterpret_cast<QNetworkReply*>(sender());
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
@ -1254,26 +1451,83 @@ void DomainServer::handleProfileRequestFinished() {
QJsonArray allowedRolesArray = _argumentVariantMap.value(ALLOWED_ROLES_CONFIG_KEY).toJsonValue().toArray();
bool shouldAllowUserToConnect = false;
QString connectableUsername;
QString profileUsername = profileJSON.object()["data"].toObject()["user"].toObject()["username"].toString();
foreach(const QJsonValue& roleValue, userRolesArray) {
if (allowedRolesArray.contains(roleValue)) {
// the user has a role that lets them in
// set the bool to true and break
shouldAllowUserToConnect = true;
connectableUsername = profileUsername;
break;
}
}
qDebug() << "Confirmed authentication state for user" << uuidStringWithoutCurlyBraces(matchingSessionUUID)
<< "-" << shouldAllowUserToConnect;
if (connectableUsername.isEmpty()) {
qDebug() << "User" << profileUsername << "with session UUID"
<< uuidStringWithoutCurlyBraces(matchingSessionUUID)
<< "does not have an allowable role. Refusing connection.";
} else {
qDebug() << "User" << profileUsername << "with session UUID"
<< uuidStringWithoutCurlyBraces(matchingSessionUUID)
<< "has an allowable role. Can connect.";
}
// insert this UUID and a flag that indicates if they are allowed to connect
_sessionAuthenticationHash.insert(matchingSessionUUID, shouldAllowUserToConnect);
_sessionAuthenticationHash.insert(matchingSessionUUID, connectableUsername);
}
}
}
const QString DS_SETTINGS_SESSIONS_GROUP = "web-sessions";
Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) {
Headers cookieHeaders;
// create a UUID for this cookie
QUuid cookieUUID = QUuid::createUuid();
QJsonDocument profileDocument = QJsonDocument::fromJson(profileReply->readAll());
QJsonObject userObject = profileDocument.object()["data"].toObject()["user"].toObject();
// add the profile to our in-memory data structure so we know who the user is when they send us their cookie
DomainServerWebSessionData sessionData(userObject);
_cookieSessionHash.insert(cookieUUID, sessionData);
// persist the cookie to settings file so we can get it back on DS relaunch
QSettings localSettings;
localSettings.beginGroup(DS_SETTINGS_SESSIONS_GROUP);
QVariant sessionVariant = QVariant::fromValue(sessionData);
localSettings.setValue(cookieUUID.toString(), QVariant::fromValue(sessionData));
// setup expiry for cookie to 1 month from today
QDateTime cookieExpiry = QDateTime::currentDateTimeUtc().addMonths(1);
QString cookieString = HIFI_SESSION_COOKIE_KEY + "=" + uuidStringWithoutCurlyBraces(cookieUUID.toString());
cookieString += "; expires=" + cookieExpiry.toString("ddd, dd MMM yyyy HH:mm:ss") + " GMT";
cookieString += "; domain=" + _hostname + "; path=/";
cookieHeaders.insert("Set-Cookie", cookieString.toUtf8());
// redirect the user back to the homepage so they can present their cookie and be authenticated
QString redirectString = "http://" + _hostname + ":" + QString::number(_httpManager.serverPort());
cookieHeaders.insert("Location", redirectString.toUtf8());
return cookieHeaders;
}
void DomainServer::loadExistingSessionsFromSettings() {
// read data for existing web sessions into memory so existing sessions can be leveraged
QSettings domainServerSettings;
domainServerSettings.beginGroup(DS_SETTINGS_SESSIONS_GROUP);
foreach(const QString& uuidKey, domainServerSettings.childKeys()) {
_cookieSessionHash.insert(QUuid(uuidKey), domainServerSettings.value(uuidKey).value<DomainServerWebSessionData>());
qDebug() << "Pulled web session from settings - cookie UUID is" << uuidKey;
}
}
void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment) {
QUuid oldUUID = assignment->getUUID();
assignment->resetUUID();

View file

@ -25,6 +25,7 @@
#include <LimitedNodeList.h>
#include "DomainServerSettingsManager.h"
#include "DomainServerWebSessionData.h"
#include "WalletTransaction.h"
#include "PendingAssignedNodeData.h"
@ -85,8 +86,14 @@ private:
QUrl oauthRedirectURL();
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
bool isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url);
void handleTokenRequestFinished();
QNetworkReply* profileRequestGivenTokenReply(QNetworkReply* tokenReply);
void handleProfileRequestFinished();
Headers setupCookieHeadersFromProfileReply(QNetworkReply* profileReply);
void loadExistingSessionsFromSettings();
QJsonObject jsonForSocket(const HifiSockAddr& socket);
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
@ -108,7 +115,10 @@ private:
QString _oauthClientSecret;
QString _hostname;
QMap<QNetworkReply*, QUuid> _networkReplyUUIDMap;
QHash<QUuid, bool> _sessionAuthenticationHash;
QHash<QUuid, QString> _sessionAuthenticationHash;
QSet<QUuid> _webAuthenticationStateSet;
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
DomainServerSettingsManager _settingsManager;
};

View file

@ -21,6 +21,7 @@ DomainServerNodeData::DomainServerNodeData() :
_sessionSecretHash(),
_assignmentUUID(),
_walletUUID(),
_username(),
_paymentIntervalTimer(),
_statsJSONObject(),
_sendingSockAddr(),

View file

@ -35,6 +35,9 @@ public:
void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
const QUuid& getWalletUUID() const { return _walletUUID; }
void setUsername(const QString& username) { _username = username; }
const QString& getUsername() const { return _username; }
QElapsedTimer& getPaymentIntervalTimer() { return _paymentIntervalTimer; }
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
@ -50,6 +53,7 @@ private:
QHash<QUuid, QUuid> _sessionSecretHash;
QUuid _assignmentUUID;
QUuid _walletUUID;
QString _username;
QElapsedTimer _paymentIntervalTimer;
QJsonObject _statsJSONObject;
HifiSockAddr _sendingSockAddr;

View file

@ -47,24 +47,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
bool DomainServerSettingsManager::handleHTTPRequest(HTTPConnection* connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == "/settings.json") {
// this is a POST operation to change one or more settings
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
QJsonObject postedObject = postedDocument.object();
// we recurse one level deep below each group for the appropriate setting
recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionObject);
// store whatever the current _settingsMap is to file
persistToFile();
// return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
return true;
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == "/settings.json") {
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == "/settings.json") {
// this is a GET operation for our settings
// check if there is a query parameter for settings affecting a particular type of assignment
@ -135,6 +119,30 @@ bool DomainServerSettingsManager::handleHTTPRequest(HTTPConnection* connection,
return false;
}
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == "/settings.json") {
// this is a POST operation to change one or more settings
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
QJsonObject postedObject = postedDocument.object();
// we recurse one level deep below each group for the appropriate setting
recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionObject);
// store whatever the current _settingsMap is to file
persistToFile();
// return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
return true;
}
return false;
}
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
QVariantMap& settingsVariant,
QJsonObject descriptionObject) {
@ -145,7 +153,17 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
// we don't continue if this key is not present in our descriptionObject
if (descriptionObject.contains(key)) {
if (rootValue.isString()) {
settingsVariant[key] = rootValue.toString();
if (rootValue.toString().isEmpty()) {
// this is an empty value, clear it in settings variant so the default is sent
settingsVariant.remove(key);
} else {
if (descriptionObject[key].toObject().contains(SETTING_DESCRIPTION_TYPE_KEY)) {
// for now this means that this is a double, so set it as a double
settingsVariant[key] = rootValue.toString().toDouble();
} else {
settingsVariant[key] = rootValue.toString();
}
}
} else if (rootValue.isBool()) {
settingsVariant[key] = rootValue.toBool();
} else if (rootValue.isObject()) {
@ -158,9 +176,16 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
settingsVariant[key] = QVariantMap();
}
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingsVariant[key].data());
recurseJSONObjectAndOverwriteSettings(rootValue.toObject(),
*reinterpret_cast<QVariantMap*>(settingsVariant[key].data()),
thisMap,
nextDescriptionObject[DESCRIPTION_SETTINGS_KEY].toObject());
if (thisMap.isEmpty()) {
// we've cleared all of the settings below this value, so remove this one too
settingsVariant.remove(key);
}
}
}
}

View file

@ -16,11 +16,12 @@
#include <HTTPManager.h>
class DomainServerSettingsManager : public QObject, HTTPRequestHandler {
class DomainServerSettingsManager : public QObject {
Q_OBJECT
public:
DomainServerSettingsManager();
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
QByteArray getJSONSettingsMap() const;
private:

View file

@ -0,0 +1,63 @@
//
// DomainServerWebSessionData.cpp
// domain-server/src
//
// Created by Stephen Birarda on 2014-07-21.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QDebug>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include "DomainServerWebSessionData.h"
DomainServerWebSessionData::DomainServerWebSessionData() :
_username(),
_roles()
{
}
DomainServerWebSessionData::DomainServerWebSessionData(const QJsonObject& userObject) :
_roles()
{
_username = userObject["username"].toString();
// pull each of the roles and throw them into our set
foreach(const QJsonValue& rolesValue, userObject["roles"].toArray()) {
_roles.insert(rolesValue.toString());
}
}
DomainServerWebSessionData::DomainServerWebSessionData(const DomainServerWebSessionData& otherSessionData) {
_username = otherSessionData._username;
_roles = otherSessionData._roles;
}
DomainServerWebSessionData& DomainServerWebSessionData::operator=(const DomainServerWebSessionData& otherSessionData) {
DomainServerWebSessionData temp(otherSessionData);
swap(temp);
return *this;
}
void DomainServerWebSessionData::swap(DomainServerWebSessionData& otherSessionData) {
using std::swap;
swap(_username, otherSessionData._username);
swap(_roles, otherSessionData._roles);
}
QDataStream& operator<<(QDataStream &out, const DomainServerWebSessionData& session) {
out << session._username << session._roles;
return out;
}
QDataStream& operator>>(QDataStream &in, DomainServerWebSessionData& session) {
in >> session._username >> session._roles;
return in;
}

View file

@ -0,0 +1,41 @@
//
// DomainServerWebSessionData.h
// domain-server/src
//
// Created by Stephen Birarda on 2014-07-21.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_DomainServerWebSessionData_h
#define hifi_DomainServerWebSessionData_h
#include <QtCore/QObject>
#include <QtCore/QSet>
class DomainServerWebSessionData : public QObject {
Q_OBJECT
public:
DomainServerWebSessionData();
DomainServerWebSessionData(const QJsonObject& userObject);
DomainServerWebSessionData(const DomainServerWebSessionData& otherSessionData);
DomainServerWebSessionData& operator=(const DomainServerWebSessionData& otherSessionData);
const QString& getUsername() const { return _username; }
const QSet<QString>& getRoles() const { return _roles; }
friend QDataStream& operator<<(QDataStream &out, const DomainServerWebSessionData& session);
friend QDataStream& operator>>(QDataStream &in, DomainServerWebSessionData& session);
private:
void swap(DomainServerWebSessionData& otherSessionData);
QString _username;
QSet<QString> _roles;
};
Q_DECLARE_METATYPE(DomainServerWebSessionData)
#endif // hifi_DomainServerWebSessionData_h

View file

@ -59,6 +59,13 @@ UnitTest.prototype.assertEquals = function(expected, actual, message) {
}
};
UnitTest.prototype.assertContains = function (expected, actual, message) {
this.numAssertions++;
if (actual.indexOf(expected) == -1) {
throw new AssertionException(expected, actual, message);
}
};
UnitTest.prototype.assertHasProperty = function(property, actual, message) {
this.numAssertions++;
if (actual[property] === undefined) {

View file

@ -24,6 +24,7 @@ function setupMenus() {
Menu.removeMenuItem("Edit", "Paste");
Menu.removeMenuItem("Edit", "Delete");
Menu.removeMenuItem("Edit", "Nudge");
Menu.removeMenuItem("Edit", "Replace from File");
Menu.removeMenuItem("File", "Export Voxels");
Menu.removeMenuItem("File", "Import Voxels");
@ -32,6 +33,7 @@ function setupMenus() {
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Copy", shortcutKey: "CTRL+C", afterItem: "Cut" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste", shortcutKey: "CTRL+V", afterItem: "Copy" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Nudge", shortcutKey: "CTRL+N", afterItem: "Paste" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Replace from File", shortcutKey: "CTRL+R", afterItem: "Nudge" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete", shortcutKeyEvent: { text: "backspace" }, afterItem: "Nudge" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Export Voxels", shortcutKey: "CTRL+E", afterItem: "Voxels" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Import Voxels", shortcutKey: "CTRL+I", afterItem: "Export Voxels" });
@ -60,7 +62,6 @@ function menuItemEvent(menuItem) {
print("deleting...");
Clipboard.deleteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
}
if (menuItem == "Export Voxels") {
print("export");
Clipboard.exportVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
@ -73,6 +74,12 @@ function menuItemEvent(menuItem) {
print("nudge");
Clipboard.nudgeVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s, { x: -1, y: 0, z: 0 });
}
if (menuItem == "Replace from File") {
var filename = Window.browse("Select file to load replacement", "", "Voxel Files (*.png *.svo *.schematic)");
if (filename) {
Clipboard.importVoxel(filename, selectedVoxel);
}
}
}
var selectCube = Overlays.addOverlay("cube", {

File diff suppressed because it is too large Load diff

View file

@ -752,7 +752,7 @@ function calculateVoxelFromIntersection(intersection, operation) {
highlightAt.z = z + zFightingSizeAdjust;
voxelSize -= 2 * zFightingSizeAdjust;
if (wantAddAdjust) {
resultVoxel.y += voxelSize;
resultVoxel.y += resultVoxel.s;
}
resultVoxel.bottomRight = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z };
@ -782,7 +782,7 @@ function calculateVoxelFromIntersection(intersection, operation) {
highlightAt.z = z + voxelSize + zFightingSizeAdjust;
voxelSize -= 2 * zFightingSizeAdjust;
if (wantAddAdjust) {
resultVoxel.z += voxelSize;
resultVoxel.z += resultVoxel.s;
}
resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z };

View file

@ -0,0 +1,272 @@
// rockPaperScissorsCells.js
// examples
//
// Created by Ben Arnold on 7/16/14.
// Copyright 2014 High Fidelity, Inc.
//
// This sample script creates a voxel wall that simulates the Rock Paper Scissors cellular
// automata. http://www.gamedev.net/blog/844/entry-2249737-another-cellular-automaton-video/
// If multiple instances of this script are run, they will combine into a larger wall.
// NOTE: You must run each instance one at a time. If they all start at once there are race conditions.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var NUMBER_OF_CELLS_EACH_DIMENSION = 48;
var NUMBER_OF_CELLS_REGION_EACH_DIMESION = 16;
var REGIONS_EACH_DIMENSION = NUMBER_OF_CELLS_EACH_DIMENSION / NUMBER_OF_CELLS_REGION_EACH_DIMESION;
var isLocal = false;
var currentCells = [];
var nextCells = [];
var cornerPosition = {x: 100, y: 0, z: 0 }
var position = {x: 0, y: 0, z: 0 };
var METER_LENGTH = 1;
var cellScale = (NUMBER_OF_CELLS_EACH_DIMENSION * METER_LENGTH) / NUMBER_OF_CELLS_EACH_DIMENSION;
var viewerPosition = {x: cornerPosition.x + (NUMBER_OF_CELLS_EACH_DIMENSION / 2) * cellScale, y: cornerPosition.y + (NUMBER_OF_CELLS_EACH_DIMENSION / 2) * cellScale, z: cornerPosition.z };
viewerPosition.z += 50;
var yaw = 0;
var orientation = Quat.fromPitchYawRollDegrees(0, yaw, 0);
//Feel free to add new cell types here. It can be more than three.
var cellTypes = [];
cellTypes[0] = { r: 255, g: 0, b: 0 };
cellTypes[1] = { r: 0, g: 255, b: 0 };
cellTypes[2] = { r: 0, g:0, b: 255 };
cellTypes[3] = { r: 0, g: 255, b: 255 };
//Check for free region for AC
var regionMarkerX = -1;
var regionMarkerY = -1;
var regionMarkerI = -1;
var regionMarkerJ = -1;
var regionMarkerColor = {r: 254, g: 0, b: 253};
function setRegionToColor(startX, startY, width, height, color) {
for (var i = startY; i < startY + height; i++) {
for (var j = startX; j < startX + width; j++) {
currentCells[i][j] = { changed: true, type: color };
// put the same value in the nextCells array for first board draw
nextCells[i][j] = { changed: true, type: color };
}
}
}
function init() {
for (var i = 0; i < REGIONS_EACH_DIMENSION; i++) {
for (var j = 0; j < REGIONS_EACH_DIMENSION; j++) {
var x = cornerPosition.x + (j) * cellScale;
var y = cornerPosition.y + (i + NUMBER_OF_CELLS_EACH_DIMENSION) * cellScale;
var z = cornerPosition.z;
var voxel = Voxels.getVoxelAt(x, y, z, cellScale);
if (voxel.x != x || voxel.y != y || voxel.z != z || voxel.s != cellScale ||
voxel.red != regionMarkerColor.r || voxel.green != regionMarkerColor.g || voxel.blue != regionMarkerColor.b) {
regionMarkerX = x;
regionMarkerY = y;
regionMarkerI = i;
regionMarkerJ = j;
i = REGIONS_EACH_DIMENSION; //force quit loop
break;
}
}
}
//Didnt find an open spot, end script
if (regionMarkerX == -1) {
Script.stop();
}
position.x = cornerPosition.x + regionMarkerJ * NUMBER_OF_CELLS_REGION_EACH_DIMESION;
position.y = cornerPosition.y + regionMarkerI * NUMBER_OF_CELLS_REGION_EACH_DIMESION;
position.z = cornerPosition.z;
Voxels.setVoxel(regionMarkerX, regionMarkerY, cornerPosition.z, cellScale, regionMarkerColor.r, regionMarkerColor.g, regionMarkerColor.b);
for (var i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) {
// create the array to hold this row
currentCells[i] = [];
// create the array to hold this row in the nextCells array
nextCells[i] = [];
}
var width = NUMBER_OF_CELLS_REGION_EACH_DIMESION / 2;
setRegionToColor(0, 0, width, width, 0);
setRegionToColor(0, width, width, width, 1);
setRegionToColor(width, width, width, width, 2);
setRegionToColor(width, 0, width, width, 3);
}
function updateCells() {
var i = 0;
var j = 0;
var cell;
var y = 0;
var x = 0;
for (i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) {
for (j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) {
cell = currentCells[i][j];
var r = Math.floor(Math.random() * 8);
switch (r){
case 0:
y = i - 1;
x = j - 1;
break;
case 1:
y = i;
x = j-1;
break;
case 2:
y = i + 1;
x = j - 1;
break;
case 3:
y = i + 1;
x = j;
break;
case 4:
y = i + 1;
x = j + 1;
break;
case 5:
y = i;
x = j + 1;
break;
case 6:
y = i - 1;
x = j + 1;
break;
case 7:
y = i - 1;
x = j;
break;
default:
continue;
}
//check the voxel grid instead of local array when on the edge
if (x == -1 || x == NUMBER_OF_CELLS_REGION_EACH_DIMESION ||
y == -1 || y == NUMBER_OF_CELLS_REGION_EACH_DIMESION) {
var voxel = Voxels.getVoxelAt(position.x + x * cellScale, position.y + y * cellScale, position.z, cellScale);
var predatorCellType = ((cell.type + 1) % cellTypes.length);
var predatorCellColor = cellTypes[predatorCellType];
if (voxel.red == predatorCellColor.r && voxel.green == predatorCellColor.g && voxel.blue == predatorCellColor.b) {
nextCells[i][j].type = predatorCellType;
nextCells[i][j].changed = true;
}
} else {
if (currentCells[y][x].type == ((cell.type + 1) % cellTypes.length)) {
nextCells[i][j].type = currentCells[y][x].type;
nextCells[i][j].changed = true;
} else {
//indicate no update
nextCells[i][j].changed = false;
}
}
}
}
for (i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) {
for (j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) {
if (nextCells[i][j].changed == true) {
// there has been a change to this cell, change the value in the currentCells array
currentCells[i][j].type = nextCells[i][j].type;
currentCells[i][j].changed = true;
}
}
}
}
function sendNextCells() {
for (var i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) {
for (var j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) {
if (nextCells[i][j].changed == true) {
// there has been a change to the state of this cell, send it
// find the x and y position for this voxel, z = 0
var x = j * cellScale;
var y = i * cellScale;
var type = nextCells[i][j].type;
// queue a packet to add a voxel for the new cell
Voxels.setVoxel(position.x + x, position.y + y, position.z, cellScale, cellTypes[type].r, cellTypes[type].g, cellTypes[type].b);
}
}
}
}
var sentFirstBoard = false;
var voxelViewerInit = false;
var UPDATES_PER_SECOND = 6.0;
var frameIndex = 1.0;
var oldFrameIndex = 0;
var framesToWait = UPDATES_PER_SECOND;
function step(deltaTime) {
if (isLocal == false) {
if (voxelViewerInit == false) {
VoxelViewer.setPosition(viewerPosition);
VoxelViewer.setOrientation(orientation);
voxelViewerInit = true;
}
VoxelViewer.queryOctree();
}
frameIndex += deltaTime * UPDATES_PER_SECOND;
if (Math.floor(frameIndex) == oldFrameIndex) {
return;
}
oldFrameIndex++;
if (frameIndex <= framesToWait) {
return;
}
if (sentFirstBoard) {
// we've already sent the first full board, perform a step in time
updateCells();
} else {
// this will be our first board send
sentFirstBoard = true;
init();
}
if (isLocal == false) {
VoxelViewer.queryOctree();
}
sendNextCells();
}
function scriptEnding() {
Voxels.eraseVoxel(regionMarkerX, regionMarkerY, position.z, cellScale);
}
Script.scriptEnding.connect(scriptEnding);
Script.update.connect(step);
Voxels.setPacketsPerSecond(2000);
// test for local...
Menu.isOptionChecked("Voxels");
isLocal = true; // will only get here on local client

View file

@ -89,7 +89,12 @@ var sittingDownAnimation = function(deltaTime) {
var pos = { x: startPosition.x - 0.3 * factor, y: startPosition.y - 0.5 * factor, z: startPosition.z};
MyAvatar.position = pos;
}
} else {
Script.update.disconnect(sittingDownAnimation);
if (seat.model) {
MyAvatar.setModelReferential(seat.model.id);
}
}
}
var standingUpAnimation = function(deltaTime) {
@ -103,7 +108,10 @@ var standingUpAnimation = function(deltaTime) {
var pos = { x: startPosition.x + 0.3 * (passedTime/animationLenght), y: startPosition.y + 0.5 * (passedTime/animationLenght), z: startPosition.z};
MyAvatar.position = pos;
}
} else {
Script.update.disconnect(standingUpAnimation);
}
}
var goToSeatAnimation = function(deltaTime) {
@ -147,7 +155,8 @@ function standUp() {
print("standUp sitting status: " + Settings.getValue(sittingSettingsHandle, false));
passedTime = 0.0;
startPosition = MyAvatar.position;
try{
MyAvatar.clearReferential();
try{
Script.update.disconnect(sittingDownAnimation);
} catch (e){}
Script.update.connect(standingUpAnimation);
@ -197,8 +206,10 @@ Controller.mousePressEvent.connect(function(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if (clickedOverlay == sitDownButton) {
seat.model = null;
sitDown();
} else if (clickedOverlay == standUpButton) {
seat.model = null;
standUp();
} else {
var pickRay = Camera.computePickRay(event.x, event.y);
@ -214,6 +225,7 @@ Controller.mousePressEvent.connect(function(event) {
model.properties.sittingPoints[i].indicator.position,
model.properties.sittingPoints[i].indicator.scale / 2)) {
clickedOnSeat = true;
seat.model = model;
seat.position = model.properties.sittingPoints[i].indicator.position;
seat.rotation = model.properties.sittingPoints[i].indicator.orientation;
}
@ -355,6 +367,7 @@ Script.update.connect(update);
Controller.keyPressEvent.connect(keyPressEvent);
Script.scriptEnding.connect(function() {
MyAvatar.clearReferential();
for (var i = 0; i < pose.length; i++){
MyAvatar.clearJointData(pose[i].joint);
}

View file

@ -145,3 +145,98 @@ test("Test timeout", function() {
this.assertEquals(0, req.status, "status should be `0`");
this.assertEquals(4, req.errorCode, "4 is the timeout error code for QNetworkReply::NetworkError");
});
var localFile = Window.browse("Find defaultScripts.js file ...", "", "defaultScripts.js (defaultScripts.js)");
if (localFile !== null) {
localFile = "file:///" + localFile;
test("Test GET local file synchronously", function () {
var req = new XMLHttpRequest();
var statesVisited = [true, false, false, false, false]
req.onreadystatechange = function () {
statesVisited[req.readyState] = true;
};
req.open("GET", localFile, false);
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(200, req.status, "status should be `200`");
this.assertEquals("OK", req.statusText, "statusText should be `OK`");
this.assertEquals(0, req.errorCode);
this.assertNotEquals("", req.getAllResponseHeaders(), "headers should not be null");
this.assertContains("High Fidelity", req.response.substring(0, 100), "expected text not found in response")
for (var i = 0; i <= req.DONE; i++) {
this.assertEquals(true, statesVisited[i], i + " should be set");
}
});
test("Test GET nonexistent local file", function () {
var nonexistentFile = localFile.replace(".js", "NoExist.js");
var req = new XMLHttpRequest();
req.open("GET", nonexistentFile, false);
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(404, req.status, "status should be `404`");
this.assertEquals("Not Found", req.statusText, "statusText should be `Not Found`");
this.assertNotEquals(0, req.errorCode);
});
test("Test GET local file already open", function () {
// Can't open file exclusively in order to test.
});
test("Test GET local file with data not implemented", function () {
var req = new XMLHttpRequest();
req.open("GET", localFile, true);
req.send("data");
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(501, req.status, "status should be `501`");
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
this.assertNotEquals(0, req.errorCode);
});
test("Test GET local file asynchronously not implemented", function () {
var req = new XMLHttpRequest();
req.open("GET", localFile, true);
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(501, req.status, "status should be `501`");
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
this.assertNotEquals(0, req.errorCode);
});
test("Test POST local file not implemented", function () {
var req = new XMLHttpRequest();
req.open("POST", localFile, false);
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(501, req.status, "status should be `501`");
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
this.assertNotEquals(0, req.errorCode);
});
test("Test local file username and password not implemented", function () {
var req = new XMLHttpRequest();
req.open("GET", localFile, false, "username", "password");
req.send();
this.assertEquals(req.DONE, req.readyState, "readyState should be DONE");
this.assertEquals(501, req.status, "status should be `501`");
this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`");
this.assertNotEquals(0, req.errorCode);
});
} else {
print("Local file operation not tested");
}

View file

@ -186,6 +186,14 @@ ToolBar = function(x, y, direction) {
return this.tools.length;
}
this.selectTool = function (tool, select) {
this.tools[tool].select(select);
}
this.toolSelected = function (tool) {
return this.tools[tool].selected();
}
this.cleanup = function() {
for(var tool in this.tools) {
this.tools[tool].cleanup();

View file

@ -1,18 +1,13 @@
set(ROOT_DIR ..)
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
set(TARGET_NAME interface)
project(${TARGET_NAME})
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/")
# set a default root dir for each of our optional externals if it was not passed
set(OPTIONAL_EXTERNALS "faceplus" "faceshift" "oculus" "priovr" "sixense" "visage" "leapmotion" "rtmidi" "qxmpp")
set(OPTIONAL_EXTERNALS "Faceplus" "Faceshift" "LibOVR" "PrioVR" "Sixense" "Visage" "LeapMotion" "RtMidi" "Qxmpp")
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
string(TOUPPER ${EXTERNAL} UPPER_EXTERNAL)
if (NOT ${UPPER_EXTERNAL}_ROOT_DIR)
set(${UPPER_EXTERNAL}_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/${EXTERNAL}")
string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE)
if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR)
string(TOLOWER ${EXTERNAL} ${EXTERNAL}_LOWERCASE)
set(${${EXTERNAL}_UPPERCASE}_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/${${EXTERNAL}_LOWERCASE}")
endif ()
endforeach()
@ -38,8 +33,7 @@ elseif (WIN32)
endif ()
# set up the external glm library
include("${MACRO_DIR}/IncludeGLM.cmake")
include_glm(${TARGET_NAME} "${ROOT_DIR}")
include_glm()
# create the InterfaceConfig.h file based on GL_HEADERS above
configure_file(InterfaceConfig.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceConfig.h")
@ -61,7 +55,7 @@ else ()
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_H})
endif ()
find_package(Qt5 COMPONENTS Core Gui Multimedia Network OpenGL Script Svg WebKit WebKitWidgets Xml UiTools)
find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Script Svg WebKitWidgets)
# grab the ui files in resources/ui
file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
@ -106,60 +100,36 @@ endif()
# create the executable, make it a bundle on OS X
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM})
# link in the hifi shared library
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
# link required hifi libraries
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_libraries(shared octree voxels fbx metavoxels networking particles models avatars audio animation script-engine)
# find any optional and required libraries
find_package(Faceplus)
find_package(Faceshift)
find_package(LibOVR)
find_package(PrioVR)
find_package(SDL)
find_package(Sixense)
find_package(Visage)
find_package(LeapMotion)
find_package(ZLIB)
find_package(Qxmpp)
find_package(RtMidi)
find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED)
# perform standard include and linking for found externals
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
string(TOUPPER ${EXTERNAL} UPPER_EXTERNAL)
if (${UPPER_EXTERNAL} MATCHES "OCULUS")
# the oculus directory is named OCULUS and not LIBOVR so hack to fix that here
set(UPPER_EXTERNAL "LIBOVR")
if (${${EXTERNAL}_UPPERCASE}_REQUIRED)
find_package(${EXTERNAL} REQUIRED)
else ()
find_package(${EXTERNAL})
endif ()
if (${UPPER_EXTERNAL}_FOUND AND NOT DISABLE_${UPPER_EXTERNAL})
add_definitions(-DHAVE_${UPPER_EXTERNAL})
if (${${EXTERNAL}_UPPERCASE}_FOUND AND NOT DISABLE_${${EXTERNAL}_UPPERCASE})
add_definitions(-DHAVE_${${EXTERNAL}_UPPERCASE})
# include the library directories (ignoring warnings)
include_directories(SYSTEM ${${UPPER_EXTERNAL}_INCLUDE_DIRS})
include_directories(SYSTEM ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS})
# perform the system include hack for OS X to ignore warnings
if (APPLE)
foreach(EXTERNAL_INCLUDE_DIR ${${UPPER_EXTERNAL}_INCLUDE_DIRS})
foreach(EXTERNAL_INCLUDE_DIR ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS})
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${EXTERNAL_INCLUDE_DIR}")
endforeach()
endif ()
target_link_libraries(${TARGET_NAME} ${${UPPER_EXTERNAL}_LIBRARIES})
target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES})
endif ()
endforeach()
@ -188,17 +158,11 @@ endif ()
# include headers for interface and InterfaceConfig.
include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes")
# include external library headers
# use system flag so warnings are supressed
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
include_directories("${OPENSSL_INCLUDE_DIR}")
target_link_libraries(
${TARGET_NAME}
"${ZLIB_LIBRARIES}"
${OPENSSL_LIBRARIES}
Qt5::Core Qt5::Gui Qt5::Multimedia Qt5::Network Qt5::OpenGL
Qt5::Script Qt5::Svg Qt5::WebKit Qt5::WebKitWidgets Qt5::Xml Qt5::UiTools
${TARGET_NAME} ${ZLIB_LIBRARIES} ${OPENSSL_LIBRARIES}
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKitWidgets
)
# assume we are using a Qt build without bearer management
@ -206,30 +170,12 @@ add_definitions(-DQT_NO_BEARERMANAGEMENT)
if (APPLE)
# link in required OS X frameworks and include the right GL headers
find_library(AppKit AppKit)
find_library(CoreAudio CoreAudio)
find_library(CoreServices CoreServices)
find_library(Carbon Carbon)
find_library(Foundation Foundation)
find_library(CoreFoundation CoreFoundation)
find_library(GLUT GLUT)
find_library(OpenGL OpenGL)
find_library(IOKit IOKit)
find_library(QTKit QTKit)
find_library(QuartzCore QuartzCore)
target_link_libraries(
${TARGET_NAME}
${AppKit}
${CoreAudio}
${CoreServices}
${Carbon}
${Foundation}
${GLUT}
${OpenGL}
${IOKit}
${QTKit}
${QuartzCore}
)
target_link_libraries(${TARGET_NAME} ${CoreAudio} ${CoreFoundation} ${GLUT} ${OpenGL})
# install command for OS X bundle
INSTALL(TARGETS ${TARGET_NAME}
@ -263,9 +209,9 @@ else (APPLE)
# we're using static GLEW, so define GLEW_STATIC
add_definitions(-DGLEW_STATIC)
# add a definition for ssize_t so that windows doesn't bail
add_definitions(-Dssize_t=long)
target_link_libraries(${TARGET_NAME} "${GLEW_LIBRARIES}" wsock32.lib opengl32.lib)
endif()
endif (APPLE)
# link any dependencies bubbled up from our linked dependencies
link_shared_dependencies()

View file

@ -245,28 +245,28 @@
<context>
<name>QObject</name>
<message>
<location filename="src/ui/ImportDialog.cpp" line="22"/>
<location filename="src/ui/ImportDialog.cpp" line="23"/>
<location filename="src/ui/VoxelImportDialog.cpp" line="22"/>
<location filename="src/ui/VoxelImportDialog.cpp" line="23"/>
<source>Import Voxels</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/ImportDialog.cpp" line="24"/>
<location filename="src/ui/VoxelImportDialog.cpp" line="24"/>
<source>Loading ...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/ImportDialog.cpp" line="25"/>
<location filename="src/ui/VoxelImportDialog.cpp" line="25"/>
<source>Place voxels</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/ImportDialog.cpp" line="26"/>
<location filename="src/ui/VoxelImportDialog.cpp" line="26"/>
<source>&lt;b&gt;Import&lt;/b&gt; %1 as voxels</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/ImportDialog.cpp" line="27"/>
<location filename="src/ui/VoxelImportDialog.cpp" line="27"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>

View file

@ -0,0 +1,25 @@
#version 120
//
// metavoxel_heightfield.frag
// fragment shader
//
// Created by Andrzej Kapolka on 7/28/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the diffuse texture
uniform sampler2D diffuseMap;
// the interpolated normal
varying vec4 normal;
void main(void) {
// compute the base color based on OpenGL lighting model
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalize(normal), gl_LightSource[0].position)));
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st);
}

View file

@ -0,0 +1,51 @@
#version 120
//
// metavoxel_heighfield.vert
// vertex shader
//
// Created by Andrzej Kapolka on 7/28/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the height texture
uniform sampler2D heightMap;
// the distance between height points in texture space
uniform float heightScale;
// the scale between height and color textures
uniform float colorScale;
// the interpolated position
varying vec4 position;
// the interpolated normal
varying vec4 normal;
void main(void) {
// transform and store the normal for interpolation
vec2 heightCoord = gl_MultiTexCoord0.st;
float deltaX = texture2D(heightMap, heightCoord - vec2(heightScale, 0.0)).r -
texture2D(heightMap, heightCoord + vec2(heightScale, 0.0)).r;
float deltaZ = texture2D(heightMap, heightCoord - vec2(0.0, heightScale)).r -
texture2D(heightMap, heightCoord + vec2(0.0, heightScale)).r;
normal = normalize(gl_ModelViewMatrix * vec4(deltaX, heightScale, deltaZ, 0.0));
// add the height to the position
float height = texture2D(heightMap, heightCoord).r;
position = gl_ModelViewMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0));
gl_Position = gl_ProjectionMatrix * position;
// the zero height should be invisible
gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0));
// pass along the scaled/offset texture coordinates
gl_TexCoord[0] = (gl_MultiTexCoord0 - vec4(heightScale, heightScale, 0.0, 0.0)) * colorScale;
// and the shadow texture coordinates
gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], position), dot(gl_EyePlaneT[0], position), dot(gl_EyePlaneR[0], position), 1.0);
}

View file

@ -0,0 +1,48 @@
#version 120
//
// metavoxel_heightfield.frag
// fragment shader
//
// Created by Andrzej Kapolka on 7/28/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the diffuse texture
uniform sampler2D diffuseMap;
// the shadow texture
uniform sampler2DShadow shadowMap;
// the distances to the cascade sections
uniform vec3 shadowDistances;
// the inverse of the size of the shadow map
const float shadowScale = 1.0 / 2048.0;
// the interpolated position
varying vec4 position;
// the interpolated normal
varying vec4 normal;
void main(void) {
// compute the index of the cascade to use and the corresponding texture coordinates
int shadowIndex = int(dot(step(vec3(position.z), shadowDistances), vec3(1.0, 1.0, 1.0)));
vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[shadowIndex], position), dot(gl_EyePlaneT[shadowIndex], position),
dot(gl_EyePlaneR[shadowIndex], position));
// compute the base color based on OpenGL lighting model
float diffuse = dot(normalize(normal), gl_LightSource[0].position);
float facingLight = step(0.0, diffuse) * 0.25 *
(shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, -shadowScale, 0.0)).r +
shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, shadowScale, 0.0)).r +
shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, -shadowScale, 0.0)).r +
shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, shadowScale, 0.0)).r);
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st);
}

View file

@ -0,0 +1,32 @@
#version 120
//
// metavoxel_heightfield_cursor.frag
// fragment shader
//
// Created by Andrzej Kapolka on 8/7/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the inner radius of the outline, squared
const float SQUARED_OUTLINE_INNER_RADIUS = 0.81;
// the outer radius of the outline, squared
const float SQUARED_OUTLINE_OUTER_RADIUS = 1.0;
// the inner radius of the inset, squared
const float SQUARED_INSET_INNER_RADIUS = 0.855625;
// the outer radius of the inset, squared
const float SQUARED_INSET_OUTER_RADIUS = 0.950625;
void main(void) {
// use the distance to compute the ring color, then multiply it by the varying color
float squaredDistance = dot(gl_TexCoord[0].st, gl_TexCoord[0].st);
float alpha = step(SQUARED_OUTLINE_INNER_RADIUS, squaredDistance) * step(squaredDistance, SQUARED_OUTLINE_OUTER_RADIUS);
float white = step(SQUARED_INSET_INNER_RADIUS, squaredDistance) * step(squaredDistance, SQUARED_INSET_OUTER_RADIUS);
gl_FragColor = gl_Color * vec4(white, white, white, alpha);
}

View file

@ -0,0 +1,28 @@
#version 120
//
// metavoxel_heighfield_cursor.vert
// vertex shader
//
// Created by Andrzej Kapolka on 8/7/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the height texture
uniform sampler2D heightMap;
void main(void) {
// compute the view space coordinates
float height = texture2D(heightMap, gl_MultiTexCoord0.st).r;
vec4 viewPosition = gl_ModelViewMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0));
gl_Position = gl_ProjectionMatrix * viewPosition;
// generate the texture coordinates from the view position
gl_TexCoord[0] = vec4(dot(viewPosition, gl_EyePlaneS[4]), dot(viewPosition, gl_EyePlaneT[4]), 0.0, 1.0);
// the zero height should be invisible
gl_FrontColor = vec4(1.0, 1.0, 1.0, 1.0 - step(height, 0.0));
}

View file

@ -0,0 +1,37 @@
#version 120
//
// metavoxel_heightfield.frag
// fragment shader
//
// Created by Andrzej Kapolka on 7/28/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the diffuse texture
uniform sampler2D diffuseMap;
// the shadow texture
uniform sampler2DShadow shadowMap;
// the inverse of the size of the shadow map
const float shadowScale = 1.0 / 2048.0;
// the interpolated normal
varying vec4 normal;
void main(void) {
// compute the base color based on OpenGL lighting model
float diffuse = dot(normalize(normal), gl_LightSource[0].position);
float facingLight = step(0.0, diffuse) * 0.25 *
(shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r +
shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r +
shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r +
shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r);
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st);
}

View file

@ -69,6 +69,7 @@
#include "InterfaceVersion.h"
#include "Menu.h"
#include "ModelUploader.h"
#include "PaymentManager.h"
#include "Util.h"
#include "devices/MIDIManager.h"
#include "devices/OculusManager.h"
@ -136,9 +137,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_frameCount(0),
_fps(60.0f),
_justStarted(true),
_voxelImporter(NULL),
_voxelImportDialog(NULL),
_voxelImporter(),
_importSucceded(false),
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
_modelClipboardRenderer(),
_modelClipboard(),
_wantToKillLocalVoxels(false),
_viewFrustum(),
_lastQueriedViewFrustum(),
@ -173,6 +177,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_lastNackTime(usecTimestampNow()),
_lastSendDownstreamAudioStats(usecTimestampNow())
{
// read the ApplicationInfo.ini file for Name/Version/Domain information
QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat);
@ -233,9 +238,19 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
connect(audioThread, SIGNAL(started()), &_audio, SLOT(start()));
audioThread->start();
const DomainHandler& domainHandler = nodeList->getDomainHandler();
connect(&nodeList->getDomainHandler(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&)));
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&)));
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
// hookup VoxelEditSender to PaymentManager so we can pay for octree edits
const PaymentManager& paymentManager = PaymentManager::getInstance();
connect(&_voxelEditSender, &VoxelEditPacketSender::octreePaymentRequired,
&paymentManager, &PaymentManager::sendSignedPayment);
// update our location every 5 seconds in the data-server, assuming that we are authenticated with one
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
@ -249,8 +264,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), &_voxels, SLOT(nodeAdded(SharedNodePointer)));
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer)));
connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle);
connect(nodeList, SIGNAL(uuidChanged(const QUuid&)), _myAvatar, SLOT(setSessionUUID(const QUuid&)));
connect(nodeList, &NodeList::uuidChanged, _myAvatar, &MyAvatar::setSessionUUID);
connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset);
// connect to appropriate slots on AccountManager
@ -419,7 +433,7 @@ Application::~Application() {
delete idleTimer;
_sharedVoxelSystem.changeTree(new VoxelTree);
delete _voxelImporter;
delete _voxelImportDialog;
// let the avatar mixer know we're out
MyAvatar::sendKillAvatar();
@ -454,8 +468,8 @@ void Application::saveSettings() {
Menu::getInstance()->saveSettings();
_rearMirrorTools->saveSettings(_settings);
if (_voxelImporter) {
_voxelImporter->saveSettings(_settings);
if (_voxelImportDialog) {
_voxelImportDialog->saveSettings(_settings);
}
_settings->sync();
_numChangedSettings = 0;
@ -588,7 +602,7 @@ void Application::paintGL() {
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
_myCamera.setTightness(0.0f); // In first person, camera follows (untweaked) head exactly without delay
_myCamera.setTargetPosition(_myAvatar->getHead()->getFilteredEyePosition());
_myCamera.setTargetPosition(_myAvatar->getHead()->getEyePosition());
_myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
@ -610,7 +624,7 @@ void Application::paintGL() {
_myCamera.setTargetPosition(_myAvatar->getHead()->getEyePosition() + glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0));
} else {
_myCamera.setTightness(0.0f);
glm::vec3 eyePosition = _myAvatar->getHead()->getFilteredEyePosition();
glm::vec3 eyePosition = _myAvatar->getHead()->getEyePosition();
float headHeight = eyePosition.y - _myAvatar->getPosition().y;
_myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
_myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight + (_raiseMirror * _myAvatar->getScale()), 0));
@ -803,9 +817,8 @@ bool Application::event(QEvent* event) {
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
bool isHifiSchemeURL = !fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME);
if (isHifiSchemeURL) {
Menu::getInstance()->goTo(fileEvent->url().toString());
Menu::getInstance()->goToURL(fileEvent->url().toLocalFile());
}
return false;
}
return QApplication::event(event);
@ -1038,9 +1051,22 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_R:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::FrustumRenderMode);
} else if (isMeta) {
if (_myAvatar->isRecording()) {
_myAvatar->stopRecording();
} else {
_myAvatar->startRecording();
_audio.setRecorder(_myAvatar->getRecorder());
}
} else {
if (_myAvatar->isPlaying()) {
_myAvatar->stopPlaying();
} else {
_myAvatar->startPlaying();
_audio.setPlayer(_myAvatar->getPlayer());
}
}
break;
break;
case Qt::Key_Percent:
Menu::getInstance()->triggerOption(MenuOption::Stats);
break;
@ -1429,6 +1455,15 @@ void Application::setEnable3DTVMode(bool enable3DTVMode) {
}
void Application::setEnableVRMode(bool enableVRMode) {
if (enableVRMode) {
if (!OculusManager::isConnected()) {
// attempt to reconnect the Oculus manager - it's possible this was a workaround
// for the sixense crash
OculusManager::disconnect();
OculusManager::connect();
}
}
resizeGL(_glWidget->width(), _glWidget->height());
}
@ -1490,16 +1525,44 @@ glm::vec3 Application::getMouseVoxelWorldCoordinates(const VoxelDetail& mouseVox
}
FaceTracker* Application::getActiveFaceTracker() {
return _cara.isActive() ? static_cast<FaceTracker*>(&_cara) :
(_faceshift.isActive() ? static_cast<FaceTracker*>(&_faceshift) :
(_faceplus.isActive() ? static_cast<FaceTracker*>(&_faceplus) :
(_visage.isActive() ? static_cast<FaceTracker*>(&_visage) : NULL)));
return (_dde.isActive() ? static_cast<FaceTracker*>(&_dde) :
(_cara.isActive() ? static_cast<FaceTracker*>(&_cara) :
(_faceshift.isActive() ? static_cast<FaceTracker*>(&_faceshift) :
(_faceplus.isActive() ? static_cast<FaceTracker*>(&_faceplus) :
(_visage.isActive() ? static_cast<FaceTracker*>(&_visage) : NULL)))));
}
struct SendVoxelsOperationArgs {
const unsigned char* newBaseOctCode;
};
bool Application::exportModels(const QString& filename, float x, float y, float z, float scale) {
QVector<ModelItem*> models;
_models.getTree()->findModelsInCube(AACube(glm::vec3(x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE), scale / (float)TREE_SCALE), models);
if (models.size() > 0) {
glm::vec3 root(x, y, z);
ModelTree exportTree;
for (int i = 0; i < models.size(); i++) {
ModelItemProperties properties;
ModelItemID id = models.at(i)->getModelItemID();
id.isKnownID = false;
properties.copyFromNewModelItem(*models.at(i));
properties.setPosition(properties.getPosition() - root);
exportTree.addModel(id, properties);
}
exportTree.writeToSVOFile(filename.toLocal8Bit().constData());
} else {
qDebug() << "No models were selected";
return false;
}
// restore the main window's active state
_window->activateWindow();
return true;
}
bool Application::sendVoxelsOperation(OctreeElement* element, void* extraData) {
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
SendVoxelsOperationArgs* args = (SendVoxelsOperationArgs*)extraData;
@ -1559,17 +1622,17 @@ void Application::exportVoxels(const VoxelDetail& sourceVoxel) {
void Application::importVoxels() {
_importSucceded = false;
if (!_voxelImporter) {
_voxelImporter = new VoxelImporter(_window);
_voxelImporter->loadSettings(_settings);
if (!_voxelImportDialog) {
_voxelImportDialog = new VoxelImportDialog(_window);
_voxelImportDialog->loadSettings(_settings);
}
if (!_voxelImporter->exec()) {
if (!_voxelImportDialog->exec()) {
qDebug() << "Import succeeded." << endl;
_importSucceded = true;
} else {
qDebug() << "Import failed." << endl;
if (_sharedVoxelSystem.getTree() == _voxelImporter->getVoxelTree()) {
if (_sharedVoxelSystem.getTree() == _voxelImporter.getVoxelTree()) {
_sharedVoxelSystem.killLocalVoxels();
_sharedVoxelSystem.changeTree(&_clipboard);
}
@ -1581,6 +1644,19 @@ void Application::importVoxels() {
emit importDone();
}
bool Application::importModels(const QString& filename) {
_modelClipboard.eraseAllOctreeElements();
bool success = _modelClipboard.readFromSVOFile(filename.toLocal8Bit().constData());
if (success) {
_modelClipboard.reaverageOctreeElements();
}
return success;
}
void Application::pasteModels(float x, float y, float z) {
_modelClipboard.sendModels(&_modelEditSender, x, y, z);
}
void Application::cutVoxels(const VoxelDetail& sourceVoxel) {
copyVoxels(sourceVoxel);
deleteVoxelAt(sourceVoxel);
@ -1671,7 +1747,7 @@ void Application::init() {
// Cleanup of the original shared tree
_sharedVoxelSystem.init();
_voxelImporter = new VoxelImporter(_window);
_voxelImportDialog = new VoxelImportDialog(_window);
_environment.init();
@ -1744,6 +1820,10 @@ void Application::init() {
_models.init();
_models.setViewFrustum(getViewFrustum());
_modelClipboardRenderer.init();
_modelClipboardRenderer.setViewFrustum(getViewFrustum());
_modelClipboardRenderer.setTree(&_modelClipboard);
_metavoxels.init();
_particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
@ -1881,13 +1961,21 @@ void Application::updateVisage() {
_visage.update();
}
void Application::updateDDE() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateDDE()");
// Update Cara
_dde.update();
}
void Application::updateCara() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateCara()");
// Update Cara
_cara.update();
// Copy angular velocity if measured by cara, to the head
if (_cara.isActive()) {
_myAvatar->getHead()->setAngularVelocity(_cara.getHeadAngularVelocity());
@ -2074,12 +2162,6 @@ void Application::update(float deltaTime) {
_prioVR.update(deltaTime);
}
{
PerformanceTimer perfTimer("myAvatar");
updateMyAvatarLookAtPosition();
updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
}
// Dispatch input events
_controllerScriptingInterface.updateInputControllers();
@ -2111,6 +2193,12 @@ void Application::update(float deltaTime) {
PerformanceTimer perfTimer("overlays");
_overlays.update(deltaTime);
}
{
PerformanceTimer perfTimer("myAvatar");
updateMyAvatarLookAtPosition();
updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
}
{
PerformanceTimer perfTimer("emitSimulating");
@ -3209,6 +3297,7 @@ void Application::resetSensors() {
_faceplus.reset();
_faceshift.reset();
_visage.reset();
_dde.reset();
OculusManager::reset();
@ -3255,9 +3344,10 @@ void Application::updateWindowTitle(){
QString buildVersion = " (build " + applicationVersion() + ")";
NodeList* nodeList = NodeList::getInstance();
QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) ";
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
QString title = QString() + (!username.isEmpty() ? username + " @ " : QString())
+ nodeList->getDomainHandler().getHostname() + buildVersion;
+ nodeList->getDomainHandler().getHostname() + connectionStatus + buildVersion;
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.getAccountInfo().hasBalance()) {
@ -3669,6 +3759,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(scriptFinished(const QString&)));
connect(scriptEngine, SIGNAL(loadScript(const QString&)), this, SLOT(loadScript(const QString&)));
scriptEngine->registerGlobalObject("Overlays", &_overlays);
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance());
@ -3795,6 +3887,38 @@ void Application::uploadAttachment() {
uploadModel(ATTACHMENT_MODEL);
}
void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) {
// from the domain-handler, figure out the satoshi cost per voxel and per meter cubed
const QString VOXEL_SETTINGS_KEY = "voxels";
const QString PER_VOXEL_COST_KEY = "per-voxel-credits";
const QString PER_METER_CUBED_COST_KEY = "per-meter-cubed-credits";
const QString VOXEL_WALLET_UUID = "voxel-wallet";
const QJsonObject& voxelObject = domainSettingsObject[VOXEL_SETTINGS_KEY].toObject();
qint64 satoshisPerVoxel = 0;
qint64 satoshisPerMeterCubed = 0;
QUuid voxelWalletUUID;
if (!domainSettingsObject.isEmpty()) {
float perVoxelCredits = (float) voxelObject[PER_VOXEL_COST_KEY].toDouble();
float perMeterCubedCredits = (float) voxelObject[PER_METER_CUBED_COST_KEY].toDouble();
satoshisPerVoxel = (qint64) floorf(perVoxelCredits * SATOSHIS_PER_CREDIT);
satoshisPerMeterCubed = (qint64) floorf(perMeterCubedCredits * SATOSHIS_PER_CREDIT);
voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString());
}
qDebug() << "Voxel costs are" << satoshisPerVoxel << "per voxel and" << satoshisPerMeterCubed << "per meter cubed";
qDebug() << "Destination wallet UUID for voxel payments is" << voxelWalletUUID;
_voxelEditSender.setSatoshisPerVoxel(satoshisPerVoxel);
_voxelEditSender.setSatoshisPerMeterCubed(satoshisPerMeterCubed);
_voxelEditSender.setDestinationWalletUUID(voxelWalletUUID);
}
QString Application::getPreviousScriptLocation() {
QString suggestedName;
if (_previousScriptLocation.isEmpty()) {

View file

@ -64,6 +64,7 @@
#include "devices/SixenseManager.h"
#include "devices/Visage.h"
#include "devices/CaraFaceTracker.h"
#include "devices/DdeFaceTracker.h"
#include "models/ModelTreeRenderer.h"
#include "particles/ParticleTreeRenderer.h"
#include "renderer/AmbientOcclusionEffect.h"
@ -86,6 +87,7 @@
#include "ui/overlays/Overlays.h"
#include "ui/ApplicationOverlay.h"
#include "ui/RunningScriptsWidget.h"
#include "ui/VoxelImportDialog.h"
#include "voxels/VoxelFade.h"
#include "voxels/VoxelHideShowThread.h"
#include "voxels/VoxelImporter.h"
@ -192,6 +194,7 @@ public:
Camera* getCamera() { return &_myCamera; }
ViewFrustum* getViewFrustum() { return &_viewFrustum; }
ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; }
VoxelImporter* getVoxelImporter() { return &_voxelImporter; }
VoxelSystem* getVoxels() { return &_voxels; }
VoxelTree* getVoxelTree() { return _voxels.getTree(); }
const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; }
@ -201,6 +204,8 @@ public:
bool getImportSucceded() { return _importSucceded; }
VoxelSystem* getSharedVoxelSystem() { return &_sharedVoxelSystem; }
VoxelTree* getClipboard() { return &_clipboard; }
ModelTree* getModelClipboard() { return &_modelClipboard; }
ModelTreeRenderer* getModelClipboardRenderer() { return &_modelClipboardRenderer; }
Environment* getEnvironment() { return &_environment; }
bool isMousePressed() const { return _mousePressed; }
bool isMouseHidden() const { return _mouseHidden; }
@ -212,6 +217,7 @@ public:
Faceplus* getFaceplus() { return &_faceplus; }
Faceshift* getFaceshift() { return &_faceshift; }
Visage* getVisage() { return &_visage; }
DdeFaceTracker* getDDE() { return &_dde; }
CaraFaceTracker* getCara() { return &_cara; }
FaceTracker* getActiveFaceTracker();
SixenseManager* getSixenseManager() { return &_sixenseManager; }
@ -227,6 +233,7 @@ public:
float getPacketsPerSecond() const { return _packetsPerSecond; }
float getBytesPerSecond() const { return _bytesPerSecond; }
const glm::vec3& getViewMatrixTranslation() const { return _viewMatrixTranslation; }
void setViewMatrixTranslation(const glm::vec3& translation) { _viewMatrixTranslation = translation; }
/// if you need to access the application settings, use lockSettings()/unlockSettings()
QSettings* lockSettings() { _settingsMutex.lock(); return _settings; }
@ -314,6 +321,10 @@ public slots:
void nodeKilled(SharedNodePointer node);
void packetSent(quint64 length);
void pasteModels(float x, float y, float z);
bool exportModels(const QString& filename, float x, float y, float z, float scale);
bool importModels(const QString& filename);
void importVoxels(); // doesn't include source voxel because it goes to clipboard
void cutVoxels(const VoxelDetail& sourceVoxel);
void copyVoxels(const VoxelDetail& sourceVoxel);
@ -342,6 +353,8 @@ public slots:
void uploadAttachment();
void bumpSettings() { ++_numChangedSettings; }
void domainSettingsReceived(const QJsonObject& domainSettingsObject);
private slots:
void timer();
@ -384,6 +397,7 @@ private:
void updateFaceplus();
void updateFaceshift();
void updateVisage();
void updateDDE();
void updateCara();
void updateMyAvatarLookAtPosition();
void updateThreads(float deltaTime);
@ -451,7 +465,8 @@ private:
VoxelSystem _voxels;
VoxelTree _clipboard; // if I copy/paste
VoxelImporter* _voxelImporter;
VoxelImportDialog* _voxelImportDialog;
VoxelImporter _voxelImporter;
bool _importSucceded;
VoxelSystem _sharedVoxelSystem;
ViewFrustum _sharedVoxelSystemViewFrustum;
@ -460,6 +475,8 @@ private:
ParticleCollisionSystem _particleCollisionSystem;
ModelTreeRenderer _models;
ModelTreeRenderer _modelClipboardRenderer;
ModelTree _modelClipboard;
QByteArray _voxelsFilename;
bool _wantToKillLocalVoxels;
@ -482,6 +499,7 @@ private:
Faceshift _faceshift;
Visage _visage;
CaraFaceTracker _cara;
DdeFaceTracker _dde;
SixenseManager _sixenseManager;
PrioVR _prioVR;

View file

@ -49,10 +49,12 @@ static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_
static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300;
static const int FRAMES_AVAILABLE_STATS_WINDOW_SECONDS = 10;
static const int APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS = (int)(30.0f * 1000.0f / AUDIO_CALLBACK_MSECS);
// Mute icon configration
static const int MUTE_ICON_SIZE = 24;
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100;
Audio::Audio(QObject* parent) :
AbstractAudioInterface(parent),
@ -63,19 +65,14 @@ Audio::Audio(QObject* parent) :
_audioOutput(NULL),
_desiredOutputFormat(),
_outputFormat(),
_outputDevice(NULL),
_outputFrameSize(0),
_numOutputCallbackBytes(0),
_loopbackAudioOutput(NULL),
_loopbackOutputDevice(NULL),
_proceduralAudioOutput(NULL),
_proceduralOutputDevice(NULL),
// NOTE: Be very careful making changes to the initializers of these ring buffers. There is a known problem with some
// Mac audio devices that slowly introduce additional delay in the audio device because they play out audio slightly
// slower than real time (or at least the desired sample rate). If you increase the size of the ring buffer, then it
// this delay will slowly add up and the longer someone runs, they more delayed their audio will be.
_inputRingBuffer(0),
_receivedAudioStream(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO, 100, true, 0, 0, true),
_receivedAudioStream(0, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, true, 0, 0, true),
_isStereoInput(false),
_averagedLatency(0.0),
_lastInputLoudness(0),
@ -104,6 +101,7 @@ Audio::Audio(QObject* parent) :
_scopeOutputOffset(0),
_framesPerScope(DEFAULT_FRAMES_PER_SCOPE),
_samplesPerScope(NETWORK_SAMPLES_PER_FRAME * _framesPerScope),
_peqEnabled(false),
_scopeInput(0),
_scopeOutputLeft(0),
_scopeOutputRight(0),
@ -112,12 +110,17 @@ Audio::Audio(QObject* parent) :
_outgoingAvatarAudioSequenceNumber(0),
_audioInputMsecsReadStats(MSECS_PER_SECOND / (float)AUDIO_CALLBACK_MSECS * CALLBACK_ACCELERATOR_RATIO, FRAMES_AVAILABLE_STATS_WINDOW_SECONDS),
_inputRingBufferMsecsAvailableStats(1, FRAMES_AVAILABLE_STATS_WINDOW_SECONDS),
_audioOutputMsecsUnplayedStats(1, FRAMES_AVAILABLE_STATS_WINDOW_SECONDS)
_audioOutputMsecsUnplayedStats(1, FRAMES_AVAILABLE_STATS_WINDOW_SECONDS),
_lastSentAudioPacket(0),
_packetSentTimeGaps(1, APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS),
_audioOutputIODevice(*this)
{
// clear the array of locally injected samples
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
// Create the noise sample array
_noiseSampleFrames = new float[NUMBER_OF_NOISE_SAMPLE_FRAMES];
connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, this, &Audio::processReceivedAudioStreamSamples, Qt::DirectConnection);
}
void Audio::init(QGLWidget *parent) {
@ -128,8 +131,8 @@ void Audio::init(QGLWidget *parent) {
void Audio::reset() {
_receivedAudioStream.reset();
resetStats();
_peq.reset();
}
void Audio::resetStats() {
@ -142,6 +145,7 @@ void Audio::resetStats() {
_inputRingBufferMsecsAvailableStats.reset();
_audioOutputMsecsUnplayedStats.reset();
_packetSentTimeGaps.reset();
}
void Audio::audioMixerKilled() {
@ -308,7 +312,7 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice,
}
}
void linearResampling(int16_t* sourceSamples, int16_t* destinationSamples,
void linearResampling(const int16_t* sourceSamples, int16_t* destinationSamples,
unsigned int numSourceSamples, unsigned int numDestinationSamples,
const QAudioFormat& sourceAudioFormat, const QAudioFormat& destinationAudioFormat) {
if (sourceAudioFormat == destinationAudioFormat) {
@ -415,9 +419,15 @@ void Audio::start() {
if (!outputFormatSupported) {
qDebug() << "Unable to set up audio output because of a problem with output format.";
}
_peq.initialize( _inputFormat.sampleRate(), _audioInput->bufferSize() );
}
void Audio::stop() {
_peq.finalize();
// "switch" to invalid devices in order to shut down the state
switchInputToAudioDevice(QAudioDeviceInfo());
switchOutputToAudioDevice(QAudioDeviceInfo());
@ -459,7 +469,15 @@ void Audio::handleAudioInput() {
int inputSamplesRequired = (int)((float)NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio);
QByteArray inputByteArray = _inputDevice->readAll();
if (_peqEnabled && !_muted) {
// we wish to pre-filter our captured input, prior to loopback
int16_t* ioBuffer = (int16_t*)inputByteArray.data();
_peq.render(ioBuffer, ioBuffer, inputByteArray.size() / sizeof(int16_t));
}
if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted && _audioOutput) {
// if this person wants local loopback add that to the locally injected audio
@ -658,6 +676,11 @@ void Audio::handleAudioInput() {
NodeList* nodeList = NodeList::getInstance();
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (_recorder && _recorder.data()->isRecording()) {
_recorder.data()->record(reinterpret_cast<char*>(networkAudioSamples), numNetworkBytes);
}
if (audioMixer && audioMixer->getActiveSocket()) {
MyAvatar* interfaceAvatar = Application::getInstance()->getAvatar();
glm::vec3 headPosition = interfaceAvatar->getHead()->getPosition();
@ -699,6 +722,17 @@ void Audio::handleAudioInput() {
// memcpy our orientation
memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation));
currentPacketPtr += sizeof(headOrientation);
// first time this is 0
if (_lastSentAudioPacket == 0) {
_lastSentAudioPacket = usecTimestampNow();
} else {
quint64 now = usecTimestampNow();
quint64 gap = now - _lastSentAudioPacket;
_packetSentTimeGaps.update(gap);
_lastSentAudioPacket = now;
}
nodeList->writeDatagram(audioDataPacket, numAudioBytes + leadingBytes, audioMixer);
_outgoingAvatarAudioSequenceNumber++;
@ -708,21 +742,92 @@ void Audio::handleAudioInput() {
}
delete[] inputAudioSamples;
}
}
if (_receivedAudioStream.getPacketReceived() > 0) {
pushAudioToOutput();
void Audio::processReceivedAudioStreamSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer) {
const int numNetworkOutputSamples = inputBuffer.size() / sizeof(int16_t);
const int numDeviceOutputSamples = numNetworkOutputSamples * (_outputFormat.sampleRate() * _outputFormat.channelCount())
/ (_desiredOutputFormat.sampleRate() * _desiredOutputFormat.channelCount());
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
const int16_t* receivedSamples;
if (_processSpatialAudio) {
unsigned int sampleTime = _spatialAudioStart;
QByteArray buffer = inputBuffer;
// Accumulate direct transmission of audio from sender to receiver
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) {
emit preProcessOriginalInboundAudio(sampleTime, buffer, _desiredOutputFormat);
addSpatialAudioToBuffer(sampleTime, buffer, numNetworkOutputSamples);
}
// Send audio off for spatial processing
emit processInboundAudio(sampleTime, buffer, _desiredOutputFormat);
// copy the samples we'll resample from the spatial audio ring buffer - this also
// pushes the read pointer of the spatial audio ring buffer forwards
_spatialAudioRingBuffer.readSamples(_outputProcessingBuffer, numNetworkOutputSamples);
// Advance the start point for the next packet of audio to arrive
_spatialAudioStart += numNetworkOutputSamples / _desiredOutputFormat.channelCount();
receivedSamples = _outputProcessingBuffer;
} else {
// copy the samples we'll resample from the ring buffer - this also
// pushes the read pointer of the ring buffer forwards
//receivedAudioStreamPopOutput.readSamples(receivedSamples, numNetworkOutputSamples);
receivedSamples = reinterpret_cast<const int16_t*>(inputBuffer.data());
}
// copy the packet from the RB to the output
linearResampling(receivedSamples,
(int16_t*)outputBuffer.data(),
numNetworkOutputSamples,
numDeviceOutputSamples,
_desiredOutputFormat, _outputFormat);
if (_scopeEnabled && !_scopeEnabledPause) {
unsigned int numAudioChannels = _desiredOutputFormat.channelCount();
const int16_t* samples = receivedSamples;
for (int numSamples = numNetworkOutputSamples / numAudioChannels; numSamples > 0; numSamples -= NETWORK_SAMPLES_PER_FRAME) {
unsigned int audioChannel = 0;
addBufferToScope(
_scopeOutputLeft,
_scopeOutputOffset,
samples, audioChannel, numAudioChannels);
audioChannel = 1;
addBufferToScope(
_scopeOutputRight,
_scopeOutputOffset,
samples, audioChannel, numAudioChannels);
_scopeOutputOffset += NETWORK_SAMPLES_PER_FRAME;
_scopeOutputOffset %= _samplesPerScope;
samples += NETWORK_SAMPLES_PER_FRAME * numAudioChannels;
}
}
}
void Audio::addReceivedAudioToStream(const QByteArray& audioByteArray) {
if (_audioOutput) {
// Audio output must exist and be correctly set up if we're going to process received audio
processReceivedAudio(audioByteArray);
_receivedAudioStream.parseData(audioByteArray);
}
Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size());
}
void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) {
int numBytesPacketHeader = numBytesForPacketHeader(packet);
@ -886,119 +991,6 @@ void Audio::toggleStereoInput() {
}
}
void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
// parse audio data
_receivedAudioStream.parseData(audioByteArray);
// This call has been moved to handleAudioInput. handleAudioInput is called at a much more regular interval
// than processReceivedAudio since handleAudioInput does not experience network-related jitter.
// This way, we reduce the jitter of the frames being pushed to the audio output, allowing us to use a reduced
// buffer size for it, which reduces latency.
//pushAudioToOutput();
}
void Audio::pushAudioToOutput() {
if (_audioOutput->bytesFree() == _audioOutput->bufferSize()) {
// the audio output has no samples to play. set the downstream audio to starved so that it
// refills to its desired size before pushing frames
_receivedAudioStream.setToStarved();
}
float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float)_outputFormat.sampleRate())
* (_desiredOutputFormat.channelCount() / (float)_outputFormat.channelCount());
int numFramesToPush;
if (Menu::getInstance()->isOptionChecked(MenuOption::DisableQAudioOutputOverflowCheck)) {
numFramesToPush = _receivedAudioStream.getFramesAvailable();
} else {
// make sure to push a whole number of frames to the audio output
int numFramesAudioOutputRoomFor = (int)(_audioOutput->bytesFree() / sizeof(int16_t) * networkOutputToOutputRatio) / _receivedAudioStream.getNumFrameSamples();
numFramesToPush = std::min(_receivedAudioStream.getFramesAvailable(), numFramesAudioOutputRoomFor);
}
// if there is data in the received stream and room in the audio output, decide what to do
if (numFramesToPush > 0 && _receivedAudioStream.popFrames(numFramesToPush, false)) {
int numNetworkOutputSamples = numFramesToPush * NETWORK_BUFFER_LENGTH_SAMPLES_STEREO;
int numDeviceOutputSamples = numNetworkOutputSamples / networkOutputToOutputRatio;
QByteArray outputBuffer;
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
AudioRingBuffer::ConstIterator receivedAudioStreamPopOutput = _receivedAudioStream.getLastPopOutput();
int16_t* receivedSamples = new int16_t[numNetworkOutputSamples];
if (_processSpatialAudio) {
unsigned int sampleTime = _spatialAudioStart;
QByteArray buffer;
buffer.resize(numNetworkOutputSamples * sizeof(int16_t));
receivedAudioStreamPopOutput.readSamples((int16_t*)buffer.data(), numNetworkOutputSamples);
// Accumulate direct transmission of audio from sender to receiver
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) {
emit preProcessOriginalInboundAudio(sampleTime, buffer, _desiredOutputFormat);
addSpatialAudioToBuffer(sampleTime, buffer, numNetworkOutputSamples);
}
// Send audio off for spatial processing
emit processInboundAudio(sampleTime, buffer, _desiredOutputFormat);
// copy the samples we'll resample from the spatial audio ring buffer - this also
// pushes the read pointer of the spatial audio ring buffer forwards
_spatialAudioRingBuffer.readSamples(receivedSamples, numNetworkOutputSamples);
// Advance the start point for the next packet of audio to arrive
_spatialAudioStart += numNetworkOutputSamples / _desiredOutputFormat.channelCount();
} else {
// copy the samples we'll resample from the ring buffer - this also
// pushes the read pointer of the ring buffer forwards
receivedAudioStreamPopOutput.readSamples(receivedSamples, numNetworkOutputSamples);
}
// copy the packet from the RB to the output
linearResampling(receivedSamples,
(int16_t*)outputBuffer.data(),
numNetworkOutputSamples,
numDeviceOutputSamples,
_desiredOutputFormat, _outputFormat);
if (_outputDevice) {
_outputDevice->write(outputBuffer);
}
if (_scopeEnabled && !_scopeEnabledPause) {
unsigned int numAudioChannels = _desiredOutputFormat.channelCount();
int16_t* samples = receivedSamples;
for (int numSamples = numNetworkOutputSamples / numAudioChannels; numSamples > 0; numSamples -= NETWORK_SAMPLES_PER_FRAME) {
unsigned int audioChannel = 0;
addBufferToScope(
_scopeOutputLeft,
_scopeOutputOffset,
samples, audioChannel, numAudioChannels);
audioChannel = 1;
addBufferToScope(
_scopeOutputRight,
_scopeOutputOffset,
samples, audioChannel, numAudioChannels);
_scopeOutputOffset += NETWORK_SAMPLES_PER_FRAME;
_scopeOutputOffset %= _samplesPerScope;
samples += NETWORK_SAMPLES_PER_FRAME * numAudioChannels;
}
}
delete[] receivedSamples;
}
}
void Audio::processProceduralAudio(int16_t* monoInput, int numSamples) {
// zero out the locally injected audio in preparation for audio procedural sounds
@ -1200,6 +1192,31 @@ void Audio::renderToolBox(int x, int y, bool boxed) {
glDisable(GL_TEXTURE_2D);
}
void Audio::toggleAudioFilter() {
_peqEnabled = !_peqEnabled;
}
void Audio::selectAudioFilterFlat() {
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioFilterFlat)) {
_peq.loadProfile(0);
}
}
void Audio::selectAudioFilterTrebleCut() {
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioFilterTrebleCut)) {
_peq.loadProfile(1);
}
}
void Audio::selectAudioFilterBassCut() {
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioFilterBassCut)) {
_peq.loadProfile(2);
}
}
void Audio::selectAudioFilterSmiley() {
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioFilterSmiley)) {
_peq.loadProfile(3);
}
}
void Audio::toggleScope() {
_scopeEnabled = !_scopeEnabled;
if (_scopeEnabled) {
@ -1307,10 +1324,10 @@ void Audio::renderStats(const float* color, int width, int height) {
return;
}
const int linesWhenCentered = _statsShowInjectedStreams ? 30 : 23;
const int linesWhenCentered = _statsShowInjectedStreams ? 34 : 27;
const int CENTERED_BACKGROUND_HEIGHT = STATS_HEIGHT_PER_LINE * linesWhenCentered;
int lines = _statsShowInjectedStreams ? _audioMixerInjectedStreamAudioStatsMap.size() * 7 + 23 : 23;
int lines = _statsShowInjectedStreams ? _audioMixerInjectedStreamAudioStatsMap.size() * 7 + 27 : 27;
int statsHeight = STATS_HEIGHT_PER_LINE * lines;
@ -1384,7 +1401,28 @@ void Audio::renderStats(const float* color, int width, int height) {
verticalOffset += STATS_HEIGHT_PER_LINE; // blank line
char upstreamMicLabelString[] = "Upstream mic audio stats:";
char clientUpstreamMicLabelString[] = "Upstream Mic Audio Packets Sent Gaps (by client):";
verticalOffset += STATS_HEIGHT_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, clientUpstreamMicLabelString, color);
char stringBuffer[512];
sprintf(stringBuffer, " Inter-packet timegaps (overall) | min: %9s, max: %9s, avg: %9s",
formatUsecTime(_packetSentTimeGaps.getMin()).toLatin1().data(),
formatUsecTime(_packetSentTimeGaps.getMax()).toLatin1().data(),
formatUsecTime(_packetSentTimeGaps.getAverage()).toLatin1().data());
verticalOffset += STATS_HEIGHT_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
sprintf(stringBuffer, " Inter-packet timegaps (last 30s) | min: %9s, max: %9s, avg: %9s",
formatUsecTime(_packetSentTimeGaps.getWindowMin()).toLatin1().data(),
formatUsecTime(_packetSentTimeGaps.getWindowMax()).toLatin1().data(),
formatUsecTime(_packetSentTimeGaps.getWindowAverage()).toLatin1().data());
verticalOffset += STATS_HEIGHT_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
verticalOffset += STATS_HEIGHT_PER_LINE; // blank line
char upstreamMicLabelString[] = "Upstream mic audio stats (received and reported by audio-mixer):";
verticalOffset += STATS_HEIGHT_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamMicLabelString, color);
@ -1424,9 +1462,9 @@ void Audio::renderAudioStreamStats(const AudioStreamStats& streamStats, int hori
sprintf(stringBuffer, " Packet loss | overall: %5.2f%% (%d lost), last_30s: %5.2f%% (%d lost)",
streamStats._packetStreamStats.getLostRate() * 100.0f,
streamStats._packetStreamStats._numLost,
streamStats._packetStreamStats._lost,
streamStats._packetStreamWindowStats.getLostRate() * 100.0f,
streamStats._packetStreamWindowStats._numLost);
streamStats._packetStreamWindowStats._lost);
verticalOffset += STATS_HEIGHT_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color);
@ -1478,11 +1516,11 @@ void Audio::renderScope(int width, int height) {
if (!_scopeEnabled)
return;
static const float backgroundColor[4] = { 0.2f, 0.2f, 0.2f, 0.6f };
static const float backgroundColor[4] = { 0.4f, 0.4f, 0.4f, 0.6f };
static const float gridColor[4] = { 0.3f, 0.3f, 0.3f, 0.6f };
static const float inputColor[4] = { 0.3f, .7f, 0.3f, 0.6f };
static const float outputLeftColor[4] = { 0.7f, .3f, 0.3f, 0.6f };
static const float outputRightColor[4] = { 0.3f, .3f, 0.7f, 0.6f };
static const float inputColor[4] = { 0.3f, 1.0f, 0.3f, 1.0f };
static const float outputLeftColor[4] = { 1.0f, 0.3f, 0.3f, 1.0f };
static const float outputRightColor[4] = { 0.3f, 0.3f, 1.0f, 1.0f };
static const int gridRows = 2;
int gridCols = _framesPerScope;
@ -1595,6 +1633,12 @@ void Audio::renderLineStrip(const float* color, int x, int y, int n, int offset,
}
void Audio::outputFormatChanged() {
int outputFormatChannelCountTimesSampleRate = _outputFormat.channelCount() * _outputFormat.sampleRate();
_outputFrameSize = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * outputFormatChannelCountTimesSampleRate / _desiredOutputFormat.sampleRate();
_receivedAudioStream.outputFormatChanged(outputFormatChannelCountTimesSampleRate);
}
bool Audio::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) {
bool supportedFormat = false;
@ -1645,7 +1689,6 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo)
// cleanup any previously initialized device
if (_audioOutput) {
_audioOutput->stop();
_outputDevice = NULL;
delete _audioOutput;
_audioOutput = NULL;
@ -1667,13 +1710,17 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo)
if (adjustedFormatForAudioDevice(outputDeviceInfo, _desiredOutputFormat, _outputFormat)) {
qDebug() << "The format to be used for audio output is" << _outputFormat;
const int AUDIO_OUTPUT_BUFFER_SIZE_FRAMES = 10;
outputFormatChanged();
const int AUDIO_OUTPUT_BUFFER_SIZE_FRAMES = 3;
// setup our general output device for audio-mixer audio
_audioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this);
_audioOutput->setBufferSize(AUDIO_OUTPUT_BUFFER_SIZE_FRAMES * _outputFormat.bytesForDuration(BUFFER_SEND_INTERVAL_USECS));
qDebug() << "Ring Buffer capacity in frames: " << AUDIO_OUTPUT_BUFFER_SIZE_FRAMES;
_outputDevice = _audioOutput->start();
_audioOutput->setBufferSize(AUDIO_OUTPUT_BUFFER_SIZE_FRAMES * _outputFrameSize * sizeof(int16_t));
qDebug() << "Ring Buffer capacity in frames: " << _audioOutput->bufferSize() / sizeof(int16_t) / (float)_outputFrameSize;
_audioOutputIODevice.start();
_audioOutput->start(&_audioOutputIODevice);
// setup a loopback audio output device
_loopbackAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this);
@ -1743,3 +1790,21 @@ float Audio::getInputRingBufferMsecsAvailable() const {
float msecsInInputRingBuffer = bytesInInputRingBuffer / (float)(_inputFormat.bytesForDuration(USECS_PER_MSEC));
return msecsInInputRingBuffer;
}
qint64 Audio::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
MixedProcessedAudioStream& receivedAUdioStream = _parent._receivedAudioStream;
int samplesRequested = maxSize / sizeof(int16_t);
int samplesPopped;
int bytesWritten;
if ((samplesPopped = receivedAUdioStream.popSamples(samplesRequested, false)) > 0) {
AudioRingBuffer::ConstIterator lastPopOutput = receivedAUdioStream.getLastPopOutput();
lastPopOutput.readSamples((int16_t*)data, samplesPopped);
bytesWritten = samplesPopped * sizeof(int16_t);
} else {
memset(data, 0, maxSize);
bytesWritten = maxSize;
}
return bytesWritten;
}

View file

@ -17,8 +17,10 @@
#include "InterfaceConfig.h"
#include "AudioStreamStats.h"
#include "Recorder.h"
#include "RingBufferHistory.h"
#include "MovingMinMaxAvg.h"
#include "AudioFilter.h"
#include <QAudio>
#include <QAudioInput>
@ -33,7 +35,7 @@
#include <AbstractAudioInterface.h>
#include <StdDev.h>
#include "MixedAudioStream.h"
#include "MixedProcessedAudioStream.h"
static const int NUM_AUDIO_CHANNELS = 2;
@ -45,6 +47,20 @@ class QIODevice;
class Audio : public AbstractAudioInterface {
Q_OBJECT
public:
class AudioOutputIODevice : public QIODevice {
public:
AudioOutputIODevice(Audio& parent) : _parent(parent) {};
void start() { open(QIODevice::ReadOnly); }
void stop() { close(); }
qint64 readData(char * data, qint64 maxSize);
qint64 writeData(const char * data, qint64 maxSize) { return 0; }
private:
Audio& _parent;
};
// setup for audio I/O
Audio(QObject* parent = 0);
@ -87,6 +103,9 @@ public:
float getAudioOutputMsecsUnplayed() const;
float getAudioOutputAverageMsecsUnplayed() const { return (float)_audioOutputMsecsUnplayedStats.getWindowAverage(); }
void setRecorder(RecorderPointer recorder) { _recorder = recorder; }
void setPlayer(PlayerPointer player) { _player = player; }
public slots:
void start();
@ -94,6 +113,7 @@ public slots:
void addReceivedAudioToStream(const QByteArray& audioByteArray);
void parseAudioStreamStatsPacket(const QByteArray& packet);
void addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples);
void processReceivedAudioStreamSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer);
void handleAudioInput();
void reset();
void resetStats();
@ -110,7 +130,12 @@ public slots:
void selectAudioScopeFiveFrames();
void selectAudioScopeTwentyFrames();
void selectAudioScopeFiftyFrames();
void toggleAudioFilter();
void selectAudioFilterFlat();
void selectAudioFilterTrebleCut();
void selectAudioFilterBassCut();
void selectAudioFilterSmiley();
virtual void handleAudioByteArray(const QByteArray& audioByteArray);
void sendDownstreamAudioStatsPacket();
@ -133,7 +158,10 @@ signals:
void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format);
void processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
void processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
private:
void outputFormatChanged();
private:
QByteArray firstInputFrame;
@ -146,14 +174,15 @@ private:
QAudioOutput* _audioOutput;
QAudioFormat _desiredOutputFormat;
QAudioFormat _outputFormat;
QIODevice* _outputDevice;
int _outputFrameSize;
int16_t _outputProcessingBuffer[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO];
int _numOutputCallbackBytes;
QAudioOutput* _loopbackAudioOutput;
QIODevice* _loopbackOutputDevice;
QAudioOutput* _proceduralAudioOutput;
QIODevice* _proceduralOutputDevice;
AudioRingBuffer _inputRingBuffer;
MixedAudioStream _receivedAudioStream;
MixedProcessedAudioStream _receivedAudioStream;
bool _isStereoInput;
QString _inputAudioDeviceName;
@ -211,12 +240,6 @@ private:
// Add sounds that we want the user to not hear themselves, by adding on top of mic input signal
void addProceduralSounds(int16_t* monoInput, int numSamples);
// Process received audio
void processReceivedAudio(const QByteArray& audioByteArray);
// Pushes frames from the output ringbuffer to the audio output device
void pushAudioToOutput();
bool switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo);
bool switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo);
@ -239,12 +262,12 @@ private:
// Audio scope methods for rendering
void renderBackground(const float* color, int x, int y, int width, int height);
void renderGrid(const float* color, int x, int y, int width, int height, int rows, int cols);
void renderLineStrip(const float* color, int x, int y, int n, int offset, const QByteArray* byteArray);
void renderLineStrip(const float* color, int x, int y, int n, int offset, const QByteArray* byteArray);
// audio stats methods for rendering
void renderAudioStreamStats(const AudioStreamStats& streamStats, int horizontalOffset, int& verticalOffset,
float scale, float rotation, int font, const float* color, bool isDownstreamStats = false);
// Audio scope data
static const unsigned int NETWORK_SAMPLES_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
static const unsigned int DEFAULT_FRAMES_PER_SCOPE = 5;
@ -257,6 +280,11 @@ private:
int _scopeOutputOffset;
int _framesPerScope;
int _samplesPerScope;
// Multi-band parametric EQ
bool _peqEnabled;
AudioFilterPEQ3 _peq;
QMutex _guard;
QByteArray* _scopeInput;
QByteArray* _scopeOutputLeft;
@ -279,6 +307,14 @@ private:
MovingMinMaxAvg<float> _inputRingBufferMsecsAvailableStats;
MovingMinMaxAvg<float> _audioOutputMsecsUnplayedStats;
quint64 _lastSentAudioPacket;
MovingMinMaxAvg<quint64> _packetSentTimeGaps;
AudioOutputIODevice _audioOutputIODevice;
WeakRecorderPointer _recorder;
WeakPlayerPointer _player;
};

View file

@ -495,6 +495,48 @@ Menu::Menu() :
true,
appInstance->getAudio(),
SLOT(toggleAudioNoiseReduction()));
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioFilter,
0,
false,
appInstance->getAudio(),
SLOT(toggleAudioFilter()));
QMenu* audioFilterMenu = audioDebugMenu->addMenu("Audio Filter Options");
addDisabledActionAndSeparator(audioFilterMenu, "Filter Response");
{
QAction *flat = addCheckableActionToQMenuAndActionHash(audioFilterMenu, MenuOption::AudioFilterFlat,
0,
true,
appInstance->getAudio(),
SLOT(selectAudioFilterFlat()));
QAction *trebleCut = addCheckableActionToQMenuAndActionHash(audioFilterMenu, MenuOption::AudioFilterTrebleCut,
0,
false,
appInstance->getAudio(),
SLOT(selectAudioFilterTrebleCut()));
QAction *bassCut = addCheckableActionToQMenuAndActionHash(audioFilterMenu, MenuOption::AudioFilterBassCut,
0,
false,
appInstance->getAudio(),
SLOT(selectAudioFilterBassCut()));
QAction *smiley = addCheckableActionToQMenuAndActionHash(audioFilterMenu, MenuOption::AudioFilterSmiley,
0,
false,
appInstance->getAudio(),
SLOT(selectAudioFilterSmiley()));
QActionGroup* audioFilterGroup = new QActionGroup(audioFilterMenu);
audioFilterGroup->addAction(flat);
audioFilterGroup->addAction(trebleCut);
audioFilterGroup->addAction(bassCut);
audioFilterGroup->addAction(smiley);
}
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio);
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoLocalAudio);
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::StereoAudio, 0, false,
@ -615,8 +657,6 @@ Menu::Menu() :
appInstance->getAudio(),
SLOT(toggleStatsShowInjectedStreams()));
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::DisableQAudioOutputOverflowCheck, 0, false);
addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel,
Qt::CTRL | Qt::SHIFT | Qt::Key_V,
this,
@ -1129,8 +1169,8 @@ void Menu::goTo() {
}
bool Menu::goToURL(QString location) {
if (location.startsWith(CUSTOM_URL_SCHEME + "//")) {
QStringList urlParts = location.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts);
if (location.startsWith(CUSTOM_URL_SCHEME + "/")) {
QStringList urlParts = location.remove(0, CUSTOM_URL_SCHEME.length()).split('/', QString::SkipEmptyParts);
if (urlParts.count() > 1) {
// if url has 2 or more parts, the first one is domain name

View file

@ -322,6 +322,11 @@ namespace MenuOption {
const QString Animations = "Animations...";
const QString Atmosphere = "Atmosphere";
const QString Attachments = "Attachments...";
const QString AudioFilter = "Audio Filter Bank";
const QString AudioFilterFlat = "Flat Response";
const QString AudioFilterTrebleCut= "Treble Cut";
const QString AudioFilterBassCut = "Bass Cut";
const QString AudioFilterSmiley = "Smiley Curve";
const QString AudioNoiseReduction = "Audio Noise Reduction";
const QString AudioScope = "Audio Scope";
const QString AudioScopeFiftyFrames = "Fifty";
@ -365,7 +370,6 @@ namespace MenuOption {
const QString DisableActivityLogger = "Disable Activity Logger";
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
const QString DisableNackPackets = "Disable NACK Packets";
const QString DisableQAudioOutputOverflowCheck = "Disable Audio Output Device Overflow Check";
const QString DisplayFrustum = "Display Frustum";
const QString DisplayHands = "Display Hands";
const QString DisplayHandTargets = "Display Hand Targets";

File diff suppressed because it is too large Load diff

View file

@ -34,12 +34,23 @@ public:
virtual void init();
virtual MetavoxelLOD getLOD();
const Frustum& getFrustum() const { return _frustum; }
const AttributePointer& getPointBufferAttribute() { return _pointBufferAttribute; }
const AttributePointer& getHeightfieldBufferAttribute() { return _heightfieldBufferAttribute; }
void simulate(float deltaTime);
void render();
void renderHeightfieldCursor(const glm::vec3& position, float radius);
bool findFirstRayHeightfieldIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance);
Q_INVOKABLE float getHeightfieldHeight(const glm::vec3& location);
Q_INVOKABLE void deleteTextures(int heightID, int colorID);
protected:
virtual MetavoxelClient* createClient(const SharedNodePointer& node);
@ -49,9 +60,11 @@ private:
void guideToAugmented(MetavoxelVisitor& visitor);
AttributePointer _pointBufferAttribute;
AttributePointer _heightfieldBufferAttribute;
MetavoxelLOD _lod;
QReadWriteLock _lodLock;
Frustum _frustum;
};
/// Describes contents of a point in a point buffer.
@ -92,13 +105,24 @@ private:
QReadWriteLock _augmentedDataLock;
};
/// Base class for cached static buffers.
class BufferData : public QSharedData {
public:
virtual ~BufferData();
virtual void render(bool cursor = false) = 0;
};
typedef QExplicitlySharedDataPointer<BufferData> BufferDataPointer;
/// Contains the information necessary to render a group of points.
class PointBuffer : public QSharedData {
class PointBuffer : public BufferData {
public:
PointBuffer(const BufferPointVector& points);
void render();
virtual void render(bool cursor = false);
private:
@ -107,36 +131,133 @@ private:
int _pointCount;
};
typedef QExplicitlySharedDataPointer<PointBuffer> PointBufferPointer;
/// Contains the information necessary to render a heightfield block.
class HeightfieldBuffer : public BufferData {
public:
static const int HEIGHT_BORDER;
static const int SHARED_EDGE;
static const int HEIGHT_EXTENSION;
HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, const QByteArray& color);
~HeightfieldBuffer();
const glm::vec3& getTranslation() const { return _translation; }
float getScale() const { return _scale; }
const Box& getHeightBounds() const { return _heightBounds; }
const Box& getColorBounds() const { return _colorBounds; }
QByteArray& getHeight() { return _height; }
const QByteArray& getHeight() const { return _height; }
QByteArray& getColor() { return _color; }
const QByteArray& getColor() const { return _color; }
QByteArray getUnextendedHeight() const;
QByteArray getUnextendedColor() const;
int getHeightSize() const { return _heightSize; }
float getHeightIncrement() const { return _heightIncrement; }
int getColorSize() const { return _colorSize; }
float getColorIncrement() const { return _colorIncrement; }
virtual void render(bool cursor = false);
/// A client-side attribute that stores point buffers.
class PointBufferAttribute : public InlineAttribute<PointBufferPointer> {
private:
glm::vec3 _translation;
float _scale;
Box _heightBounds;
Box _colorBounds;
QByteArray _height;
QByteArray _color;
GLuint _heightTextureID;
GLuint _colorTextureID;
int _heightSize;
float _heightIncrement;
int _colorSize;
float _colorIncrement;
typedef QPair<QOpenGLBuffer, QOpenGLBuffer> BufferPair;
static QHash<int, BufferPair> _bufferPairs;
};
/// Convenience class for rendering a preview of a heightfield.
class HeightfieldPreview {
public:
void setBuffers(const QVector<BufferDataPointer>& buffers) { _buffers = buffers; }
const QVector<BufferDataPointer>& getBuffers() const { return _buffers; }
void render(const glm::vec3& translation, float scale) const;
private:
QVector<BufferDataPointer> _buffers;
};
/// A client-side attribute that stores renderable buffers.
class BufferDataAttribute : public InlineAttribute<BufferDataPointer> {
Q_OBJECT
public:
Q_INVOKABLE PointBufferAttribute();
Q_INVOKABLE BufferDataAttribute(const QString& name = QString());
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
virtual AttributeValue inherit(const AttributeValue& parentValue) const;
};
/// Renders metavoxels as points.
class PointMetavoxelRendererImplementation : public MetavoxelRendererImplementation {
class DefaultMetavoxelRendererImplementation : public MetavoxelRendererImplementation {
Q_OBJECT
public:
static void init();
static ProgramObject& getHeightfieldProgram() { return _heightfieldProgram; }
static int getHeightScaleLocation() { return _heightScaleLocation; }
static int getColorScaleLocation() { return _colorScaleLocation; }
Q_INVOKABLE PointMetavoxelRendererImplementation();
static ProgramObject& getShadowMapHeightfieldProgram() { return _shadowMapHeightfieldProgram; }
static int getShadowMapHeightScaleLocation() { return _shadowMapHeightScaleLocation; }
static int getShadowMapColorScaleLocation() { return _shadowMapColorScaleLocation; }
static ProgramObject& getCascadedShadowMapHeightfieldProgram() { return _cascadedShadowMapHeightfieldProgram; }
static int getCascadedShadowMapHeightScaleLocation() { return _cascadedShadowMapHeightScaleLocation; }
static int getCascadedShadowMapColorScaleLocation() { return _cascadedShadowMapColorScaleLocation; }
static ProgramObject& getHeightfieldCursorProgram() { return _heightfieldCursorProgram; }
Q_INVOKABLE DefaultMetavoxelRendererImplementation();
virtual void augment(MetavoxelData& data, const MetavoxelData& previous, MetavoxelInfo& info, const MetavoxelLOD& lod);
virtual void simulate(MetavoxelData& data, float deltaTime, MetavoxelInfo& info, const MetavoxelLOD& lod);
virtual void render(MetavoxelData& data, MetavoxelInfo& info, const MetavoxelLOD& lod);
private:
static ProgramObject _program;
static int _pointScaleLocation;
static ProgramObject _pointProgram;
static int _pointScaleLocation;
static ProgramObject _heightfieldProgram;
static int _heightScaleLocation;
static int _colorScaleLocation;
static ProgramObject _shadowMapHeightfieldProgram;
static int _shadowMapHeightScaleLocation;
static int _shadowMapColorScaleLocation;
static ProgramObject _cascadedShadowMapHeightfieldProgram;
static int _cascadedShadowMapHeightScaleLocation;
static int _cascadedShadowMapColorScaleLocation;
static int _shadowDistancesLocation;
static ProgramObject _heightfieldCursorProgram;
};
/// Base class for spanner renderers; provides clipping.

View file

@ -0,0 +1,64 @@
//
// PaymentManager.cpp
// interface/src
//
// Created by Stephen Birarda on 2014-07-30.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <QtCore/QUuid>
#include <Menu.h>
#include <NodeList.h>
#include <PacketHeaders.h>
#include "SignedWalletTransaction.h"
#include "PaymentManager.h"
PaymentManager& PaymentManager::getInstance() {
static PaymentManager sharedInstance;
return sharedInstance;
}
void PaymentManager::sendSignedPayment(qint64 satoshiAmount, const QUuid& nodeUUID, const QUuid& destinationWalletUUID) {
QByteArray walletPrivateKeyByteArray = Menu::getInstance()->getWalletPrivateKey();
if (!walletPrivateKeyByteArray.isEmpty()) {
// setup a signed wallet transaction
const qint64 DEFAULT_TRANSACTION_EXPIRY_SECONDS = 60;
qint64 currentTimestamp = QDateTime::currentDateTimeUtc().toTime_t();
SignedWalletTransaction newTransaction(destinationWalletUUID, satoshiAmount,
currentTimestamp, DEFAULT_TRANSACTION_EXPIRY_SECONDS);
// send the signed transaction to the redeeming node
QByteArray transactionByteArray = byteArrayWithPopulatedHeader(PacketTypeSignedTransactionPayment);
// append the binary message and the signed message digest
transactionByteArray.append(newTransaction.binaryMessage());
QByteArray signedMessageDigest = newTransaction.signedMessageDigest();
if (!signedMessageDigest.isEmpty()) {
transactionByteArray.append(signedMessageDigest);
qDebug() << "Paying" << satoshiAmount << "satoshis to" << destinationWalletUUID << "via" << nodeUUID;
// use the NodeList to send that to the right node
NodeList* nodeList = NodeList::getInstance();
nodeList->writeDatagram(transactionByteArray, nodeList->nodeWithUUID(nodeUUID));
} else {
qDebug() << "Payment of" << satoshiAmount << "satoshis to" << destinationWalletUUID << "via" << nodeUUID <<
"cannot be sent because there was an error signing the transaction.";
}
} else {
qDebug() << "Payment of" << satoshiAmount << "satoshis to" << destinationWalletUUID << "via" << nodeUUID <<
"cannot be sent because there is no stored wallet private key.";
}
}

View file

@ -0,0 +1,25 @@
//
// PaymentManager.h
// interface/src
//
// Created by Stephen Birarda on 2014-07-30.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_PaymentManager_h
#define hifi_PaymentManager_h
#include <QtCore/QObject>
class PaymentManager : public QObject {
Q_OBJECT
public:
static PaymentManager& getInstance();
public slots:
void sendSignedPayment(qint64 satoshiAmount, const QUuid& nodeUUID, const QUuid& destinationWalletUUID);
};
#endif // hifi_PaymentManager_h

321
interface/src/Recorder.cpp Normal file
View file

@ -0,0 +1,321 @@
//
// Recorder.cpp
//
//
// Created by Clement on 8/7/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <GLMHelpers.h>
#include <QMetaObject>
#include "Recorder.h"
void RecordingFrame::setBlendshapeCoefficients(QVector<float> blendshapeCoefficients) {
_blendshapeCoefficients = blendshapeCoefficients;
}
void RecordingFrame::setJointRotations(QVector<glm::quat> jointRotations) {
_jointRotations = jointRotations;
}
void RecordingFrame::setTranslation(glm::vec3 translation) {
_translation = translation;
}
void RecordingFrame::setRotation(glm::quat rotation) {
_rotation = rotation;
}
void RecordingFrame::setScale(float scale) {
_scale = scale;
}
void RecordingFrame::setHeadRotation(glm::quat headRotation) {
_headRotation = headRotation;
}
void RecordingFrame::setLeanSideways(float leanSideways) {
_leanSideways = leanSideways;
}
void RecordingFrame::setLeanForward(float leanForward) {
_leanForward = leanForward;
}
Recording::Recording() : _audio(NULL) {
}
Recording::~Recording() {
delete _audio;
}
void Recording::addFrame(int timestamp, RecordingFrame &frame) {
_timestamps << timestamp;
_frames << frame;
}
void Recording::addAudioPacket(QByteArray byteArray) {
if (!_audio) {
_audio = new Sound(byteArray);
}
_audio->append(byteArray);
}
void Recording::clear() {
_timestamps.clear();
_frames.clear();
delete _audio;
_audio = NULL;
}
Recorder::Recorder(AvatarData* avatar) :
_recording(new Recording()),
_avatar(avatar)
{
_timer.invalidate();
}
bool Recorder::isRecording() const {
return _timer.isValid();
}
qint64 Recorder::elapsed() const {
if (isRecording()) {
return _timer.elapsed();
} else {
return 0;
}
}
void Recorder::startRecording() {
qDebug() << "Recorder::startRecording()";
_recording->clear();
_timer.start();
RecordingFrame frame;
frame.setBlendshapeCoefficients(_avatar->getHeadData()->getBlendshapeCoefficients());
frame.setJointRotations(_avatar->getJointRotations());
frame.setTranslation(_avatar->getPosition());
frame.setRotation(_avatar->getOrientation());
frame.setScale(_avatar->getTargetScale());
const HeadData* head = _avatar->getHeadData();
glm::quat rotation = glm::quat(glm::radians(glm::vec3(head->getFinalPitch(),
head->getFinalYaw(),
head->getFinalRoll())));
frame.setHeadRotation(rotation);
frame.setLeanForward(_avatar->getHeadData()->getLeanForward());
frame.setLeanSideways(_avatar->getHeadData()->getLeanSideways());
_recording->addFrame(0, frame);
}
void Recorder::stopRecording() {
qDebug() << "Recorder::stopRecording()";
_timer.invalidate();
qDebug().nospace() << "Recorded " << _recording->getFrameNumber() << " during " << _recording->getLength() << " msec (" << _recording->getFrameNumber() / (_recording->getLength() / 1000.0f) << " fps)";
}
void Recorder::saveToFile(QString file) {
if (_recording->isEmpty()) {
qDebug() << "Cannot save recording to file, recording is empty.";
}
writeRecordingToFile(_recording, file);
}
void Recorder::record() {
if (isRecording()) {
const RecordingFrame& referenceFrame = _recording->getFrame(0);
RecordingFrame frame;
frame.setBlendshapeCoefficients(_avatar->getHeadData()->getBlendshapeCoefficients());
frame.setJointRotations(_avatar->getJointRotations());
frame.setTranslation(_avatar->getPosition() - referenceFrame.getTranslation());
frame.setRotation(glm::inverse(referenceFrame.getRotation()) * _avatar->getOrientation());
frame.setScale(_avatar->getTargetScale() / referenceFrame.getScale());
const HeadData* head = _avatar->getHeadData();
glm::quat rotation = glm::quat(glm::radians(glm::vec3(head->getFinalPitch(),
head->getFinalYaw(),
head->getFinalRoll())));
frame.setHeadRotation(rotation);
frame.setLeanForward(_avatar->getHeadData()->getLeanForward());
frame.setLeanSideways(_avatar->getHeadData()->getLeanSideways());
_recording->addFrame(_timer.elapsed(), frame);
}
}
void Recorder::record(char* samples, int size) {
QByteArray byteArray(samples, size);
_recording->addAudioPacket(byteArray);
}
Player::Player(AvatarData* avatar) :
_recording(new Recording()),
_avatar(avatar),
_audioThread(NULL)
{
_timer.invalidate();
_options.setLoop(false);
_options.setVolume(1.0f);
}
bool Player::isPlaying() const {
return _timer.isValid();
}
qint64 Player::elapsed() const {
if (isPlaying()) {
return _timer.elapsed();
} else {
return 0;
}
}
glm::quat Player::getHeadRotation() {
if (!computeCurrentFrame()) {
qWarning() << "Incorrect use of Player::getHeadRotation()";
return glm::quat();
}
if (_currentFrame == 0) {
return _recording->getFrame(_currentFrame).getHeadRotation();
}
return _recording->getFrame(0).getHeadRotation() *
_recording->getFrame(_currentFrame).getHeadRotation();
}
float Player::getLeanSideways() {
if (!computeCurrentFrame()) {
qWarning() << "Incorrect use of Player::getLeanSideways()";
return 0.0f;
}
return _recording->getFrame(_currentFrame).getLeanSideways();
}
float Player::getLeanForward() {
if (!computeCurrentFrame()) {
qWarning() << "Incorrect use of Player::getLeanForward()";
return 0.0f;
}
return _recording->getFrame(_currentFrame).getLeanForward();
}
void Player::startPlaying() {
if (_recording && _recording->getFrameNumber() > 0) {
qDebug() << "Recorder::startPlaying()";
_currentFrame = 0;
// Setup audio thread
_audioThread = new QThread();
_options.setPosition(_avatar->getPosition());
_options.setOrientation(_avatar->getOrientation());
_injector.reset(new AudioInjector(_recording->getAudio(), _options), &QObject::deleteLater);
_injector->moveToThread(_audioThread);
_audioThread->start();
QMetaObject::invokeMethod(_injector.data(), "injectAudio", Qt::QueuedConnection);
_timer.start();
}
}
void Player::stopPlaying() {
if (!isPlaying()) {
return;
}
_timer.invalidate();
_avatar->clearJointsData();
// Cleanup audio thread
_injector->stop();
_injector.clear();
_audioThread->exit();
_audioThread->deleteLater();
qDebug() << "Recorder::stopPlaying()";
}
void Player::loadFromFile(QString file) {
if (_recording) {
_recording->clear();
} else {
_recording = RecordingPointer(new Recording());
}
readRecordingFromFile(_recording, file);
}
void Player::loadRecording(RecordingPointer recording) {
_recording = recording;
}
void Player::play() {
computeCurrentFrame();
if (_currentFrame < 0 || _currentFrame >= _recording->getFrameNumber() - 1) {
// If it's the end of the recording, stop playing
stopPlaying();
return;
}
if (_currentFrame == 0) {
_avatar->setPosition(_recording->getFrame(_currentFrame).getTranslation());
_avatar->setOrientation(_recording->getFrame(_currentFrame).getRotation());
_avatar->setTargetScale(_recording->getFrame(_currentFrame).getScale());
_avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations());
HeadData* head = const_cast<HeadData*>(_avatar->getHeadData());
head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients());
} else {
_avatar->setPosition(_recording->getFrame(0).getTranslation() +
_recording->getFrame(_currentFrame).getTranslation());
_avatar->setOrientation(_recording->getFrame(0).getRotation() *
_recording->getFrame(_currentFrame).getRotation());
_avatar->setTargetScale(_recording->getFrame(0).getScale() *
_recording->getFrame(_currentFrame).getScale());
_avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations());
HeadData* head = const_cast<HeadData*>(_avatar->getHeadData());
head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients());
}
_options.setPosition(_avatar->getPosition());
_options.setOrientation(_avatar->getOrientation());
_injector->setOptions(_options);
}
bool Player::computeCurrentFrame() {
if (!isPlaying()) {
_currentFrame = -1;
return false;
}
if (_currentFrame < 0) {
_currentFrame = 0;
}
while (_currentFrame < _recording->getFrameNumber() - 1 &&
_recording->getFrameTimestamp(_currentFrame) < _timer.elapsed()) {
++_currentFrame;
}
return true;
}
void writeRecordingToFile(RecordingPointer recording, QString file) {
// TODO
qDebug() << "Writing recording to " << file;
}
RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file) {
// TODO
qDebug() << "Reading recording from " << file;
return recording;
}

170
interface/src/Recorder.h Normal file
View file

@ -0,0 +1,170 @@
//
// Recorder.h
//
//
// Created by Clement on 8/7/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_Recorder_h
#define hifi_Recorder_h
#include <QBitArray>
#include <QElapsedTimer>
#include <QHash>
#include <QSharedPointer>
#include <QVector>
#include <QWeakPointer>
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <AudioInjector.h>
#include <AvatarData.h>
#include <SharedUtil.h>
#include <Sound.h>
class Recorder;
class Recording;
class Player;
typedef QSharedPointer<Recording> RecordingPointer;
typedef QSharedPointer<Recorder> RecorderPointer;
typedef QWeakPointer<Recorder> WeakRecorderPointer;
typedef QSharedPointer<Player> PlayerPointer;
typedef QWeakPointer<Player> WeakPlayerPointer;
/// Stores the different values associated to one recording frame
class RecordingFrame {
public:
QVector<float> getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
QVector<glm::quat> getJointRotations() const { return _jointRotations; }
glm::vec3 getTranslation() const { return _translation; }
glm::quat getRotation() const { return _rotation; }
float getScale() const { return _scale; }
glm::quat getHeadRotation() const { return _headRotation; }
float getLeanSideways() const { return _leanSideways; }
float getLeanForward() const { return _leanForward; }
protected:
void setBlendshapeCoefficients(QVector<float> blendshapeCoefficients);
void setJointRotations(QVector<glm::quat> jointRotations);
void setTranslation(glm::vec3 translation);
void setRotation(glm::quat rotation);
void setScale(float scale);
void setHeadRotation(glm::quat headRotation);
void setLeanSideways(float leanSideways);
void setLeanForward(float leanForward);
private:
QVector<float> _blendshapeCoefficients;
QVector<glm::quat> _jointRotations;
glm::vec3 _translation;
glm::quat _rotation;
float _scale;
glm::quat _headRotation;
float _leanSideways;
float _leanForward;
friend class Recorder;
friend void writeRecordingToFile(RecordingPointer recording, QString file);
friend RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file);
};
/// Stores a recording
class Recording {
public:
Recording();
~Recording();
bool isEmpty() const { return _timestamps.isEmpty(); }
int getLength() const { return _timestamps.last(); } // in ms
int getFrameNumber() const { return _frames.size(); }
qint32 getFrameTimestamp(int i) const { return _timestamps[i]; }
const RecordingFrame& getFrame(int i) const { return _frames[i]; }
Sound* getAudio() const { return _audio; }
protected:
void addFrame(int timestamp, RecordingFrame& frame);
void addAudioPacket(QByteArray byteArray);
void clear();
private:
QVector<qint32> _timestamps;
QVector<RecordingFrame> _frames;
Sound* _audio;
friend class Recorder;
friend class Player;
friend void writeRecordingToFile(RecordingPointer recording, QString file);
friend RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file);
};
/// Records a recording
class Recorder {
public:
Recorder(AvatarData* avatar);
bool isRecording() const;
qint64 elapsed() const;
RecordingPointer getRecording() const { return _recording; }
public slots:
void startRecording();
void stopRecording();
void saveToFile(QString file);
void record();
void record(char* samples, int size);
private:
QElapsedTimer _timer;
RecordingPointer _recording;
AvatarData* _avatar;
};
/// Plays back a recording
class Player {
public:
Player(AvatarData* avatar);
bool isPlaying() const;
qint64 elapsed() const;
// Those should only be called if isPlaying() returns true
glm::quat getHeadRotation();
float getLeanSideways();
float getLeanForward();
public slots:
void startPlaying();
void stopPlaying();
void loadFromFile(QString file);
void loadRecording(RecordingPointer recording);
void play();
private:
bool computeCurrentFrame();
QElapsedTimer _timer;
RecordingPointer _recording;
int _currentFrame;
QSharedPointer<AudioInjector> _injector;
AudioInjectorOptions _options;
AvatarData* _avatar;
QThread* _audioThread;
};
void writeRecordingToFile(RecordingPointer recording, QString file);
RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file);
#endif // hifi_Recorder_h

View file

@ -32,7 +32,7 @@ SignedWalletTransaction::SignedWalletTransaction(const QUuid& destinationUUID, q
}
QByteArray SignedWalletTransaction::hexMessage() {
QByteArray SignedWalletTransaction::binaryMessage() {
// build the message using the components of this transaction
// UUID, source UUID, destination UUID, message timestamp, expiry delta, amount
@ -49,7 +49,11 @@ QByteArray SignedWalletTransaction::hexMessage() {
messageBinary.append(reinterpret_cast<const char*>(&_amount), sizeof(_amount));
return messageBinary.toHex();
return messageBinary;
}
QByteArray SignedWalletTransaction::hexMessage() {
return binaryMessage().toHex();
}
QByteArray SignedWalletTransaction::messageDigest() {

View file

@ -19,6 +19,7 @@ class SignedWalletTransaction : public WalletTransaction {
public:
SignedWalletTransaction(const QUuid& destinationUUID, qint64 amount, qint64 messageTimestamp, qint64 expiryDelta);
QByteArray binaryMessage();
QByteArray hexMessage();
QByteArray messageDigest();
QByteArray signedMessageDigest();

View file

@ -27,7 +27,9 @@
#include "Hand.h"
#include "Head.h"
#include "Menu.h"
#include "ModelReferential.h"
#include "Physics.h"
#include "Recorder.h"
#include "world.h"
#include "devices/OculusManager.h"
#include "renderer/TextureCache.h"
@ -102,6 +104,31 @@ float Avatar::getLODDistance() const {
void Avatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("simulate");
// update the avatar's position according to its referential
if (_referential) {
if (_referential->hasExtraData()) {
ModelTree* tree = Application::getInstance()->getModels()->getTree();
switch (_referential->type()) {
case Referential::MODEL:
_referential = new ModelReferential(_referential,
tree,
this);
break;
case Referential::JOINT:
_referential = new JointReferential(_referential,
tree,
this);
break;
default:
qDebug() << "[WARNING] Avatar::simulate(): Unknown referential type.";
break;
}
}
_referential->update();
}
if (_scale != _targetScale) {
setScale(_targetScale);
}
@ -218,6 +245,9 @@ static TextRenderer* textRenderer(TextRendererType type) {
}
void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
if (_referential) {
_referential->update();
}
if (glm::distance(Application::getInstance()->getAvatar()->getPosition(),
_position) < 10.0f) {
@ -268,7 +298,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
float boundingRadius = getBillboardSize();
ViewFrustum* frustum = (renderMode == Avatar::SHADOW_RENDER_MODE) ?
Application::getInstance()->getShadowViewFrustum() : Application::getInstance()->getViewFrustum();
if (frustum->sphereInFrustum(_position, boundingRadius) == ViewFrustum::OUTSIDE) {
if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) {
return;
}
@ -696,6 +726,17 @@ bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList&
return collided;
}
QVector<glm::quat> Avatar::getJointRotations() const {
if (QThread::currentThread() != thread()) {
return AvatarData::getJointRotations();
}
QVector<glm::quat> jointRotations(_skeletonModel.getJointStateCount());
for (int i = 0; i < _skeletonModel.getJointStateCount(); ++i) {
_skeletonModel.getJointState(i, jointRotations[i]);
}
return jointRotations;
}
glm::quat Avatar::getJointRotation(int index) const {
if (QThread::currentThread() != thread()) {
return AvatarData::getJointRotation(index);

View file

@ -23,6 +23,7 @@
#include "Hand.h"
#include "Head.h"
#include "InterfaceConfig.h"
#include "Recorder.h"
#include "SkeletonModel.h"
#include "world.h"
@ -121,6 +122,7 @@ public:
virtual bool isMyAvatar() { return false; }
virtual QVector<glm::quat> getJointRotations() const;
virtual glm::quat getJointRotation(int index) const;
virtual int getJointIndex(const QString& name) const;
virtual QStringList getJointNames() const;
@ -209,7 +211,7 @@ protected:
virtual void renderAttachments(RenderMode renderMode);
virtual void updateJointMappings();
private:
bool _initialized;
@ -220,8 +222,6 @@ private:
void renderBillboard();
float getBillboardSize() const;
};
#endif // hifi_Avatar_h

View file

@ -41,7 +41,7 @@ void AvatarManager::init() {
}
void AvatarManager::updateOtherAvatars(float deltaTime) {
if (_avatarHash.size() < 2) {
if (_avatarHash.size() < 2 && _avatarFades.isEmpty()) {
return;
}
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);

View file

@ -64,10 +64,18 @@ void Head::reset() {
void Head::simulate(float deltaTime, bool isMine, bool billboard) {
// Update audio trailing average for rendering facial animations
if (isMine) {
FaceTracker* faceTracker = Application::getInstance()->getActiveFaceTracker();
if ((_isFaceshiftConnected = faceTracker)) {
_blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
_isFaceshiftConnected = true;
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
// Only use face trackers when not playing back a recording.
if (!myAvatar->isPlaying()) {
FaceTracker* faceTracker = Application::getInstance()->getActiveFaceTracker();
if ((_isFaceshiftConnected = faceTracker)) {
_blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
_isFaceshiftConnected = true;
} else if (Application::getInstance()->getDDE()->isActive()) {
faceTracker = Application::getInstance()->getDDE();
_blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
}
}
}
@ -160,9 +168,6 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
}
_eyePosition = calculateAverageEyePosition();
float velocityFilter = glm::clamp(1.0f - glm::length(_filteredEyePosition - _eyePosition), 0.0f, 1.0f);
_filteredEyePosition = velocityFilter * _filteredEyePosition + (1.0f - velocityFilter) * _eyePosition;
}
void Head::relaxLean(float deltaTime) {

View file

@ -48,8 +48,6 @@ public:
void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; }
void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; }
void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; }
void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; }
void setLeanForward(float leanForward) { _leanForward = leanForward; }
/// \return orientationBase+Delta
glm::quat getFinalOrientationInLocalFrame() const;
@ -57,7 +55,6 @@ public:
/// \return orientationBody * (orientationBase+Delta)
glm::quat getFinalOrientationInWorldFrame() const;
/// \return orientationBody * orientationBasePitch
glm::quat getCameraOrientation () const;
@ -71,8 +68,6 @@ public:
glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
glm::vec3 getUpDirection() const { return getOrientation() * IDENTITY_UP; }
glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
float getLeanSideways() const { return _leanSideways; }
float getLeanForward() const { return _leanForward; }
float getFinalLeanSideways() const { return _leanSideways + _deltaLeanSideways; }
float getFinalLeanForward() const { return _leanForward + _deltaLeanForward; }
@ -88,7 +83,6 @@ public:
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; }
glm::vec3 getFilteredEyePosition() const { return _filteredEyePosition; }
/// \return the point about which scaling occurs.
glm::vec3 getScalePivot() const;
@ -121,7 +115,6 @@ private:
glm::vec3 _leftEyePosition;
glm::vec3 _rightEyePosition;
glm::vec3 _eyePosition;
glm::vec3 _filteredEyePosition; // velocity filtered world space eye position
float _scale;
float _lastLoudness;

View file

@ -0,0 +1,189 @@
//
// ModelReferential.cpp
//
//
// Created by Clement on 7/30/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <AvatarData.h>
#include "ModelTree.h"
#include "../renderer/Model.h"
#include "ModelReferential.h"
ModelReferential::ModelReferential(Referential* referential, ModelTree* tree, AvatarData* avatar) :
Referential(MODEL, avatar),
_tree(tree) {
_translation = referential->getTranslation();
_rotation = referential->getRotation();
_scale = referential->getScale();
unpackExtraData(reinterpret_cast<unsigned char*>(referential->getExtraData().data()),
referential->getExtraData().size());
if (!isValid()) {
qDebug() << "ModelReferential::copyConstructor(): Not Valid";
return;
}
const ModelItem* item = _tree->findModelByID(_modelID);
if (item != NULL) {
_refScale = item->getRadius();
_refRotation = item->getModelRotation();
_refPosition = item->getPosition() * (float)TREE_SCALE;
update();
}
}
ModelReferential::ModelReferential(uint32_t modelID, ModelTree* tree, AvatarData* avatar) :
Referential(MODEL, avatar),
_modelID(modelID),
_tree(tree)
{
const ModelItem* item = _tree->findModelByID(_modelID);
if (!isValid() || item == NULL) {
qDebug() << "ModelReferential::constructor(): Not Valid";
_isValid = false;
return;
}
_refScale = item->getRadius();
_refRotation = item->getModelRotation();
_refPosition = item->getPosition() * (float)TREE_SCALE;
glm::quat refInvRot = glm::inverse(_refRotation);
_scale = _avatar->getTargetScale() / _refScale;
_rotation = refInvRot * _avatar->getOrientation();
_translation = refInvRot * (avatar->getPosition() - _refPosition) / _refScale;
}
void ModelReferential::update() {
const ModelItem* item = _tree->findModelByID(_modelID);
if (!isValid() || item == NULL || _avatar == NULL) {
return;
}
bool somethingChanged = false;
if (item->getRadius() != _refScale) {
_refScale = item->getRadius();
_avatar->setTargetScale(_refScale * _scale, true);
somethingChanged = true;
}
if (item->getModelRotation() != _refRotation) {
_refRotation = item->getModelRotation();
_avatar->setOrientation(_refRotation * _rotation, true);
somethingChanged = true;
}
if (item->getPosition() != _refPosition || somethingChanged) {
_refPosition = item->getPosition();
_avatar->setPosition(_refPosition * (float)TREE_SCALE + _refRotation * (_translation * _refScale), true);
}
}
int ModelReferential::packExtraData(unsigned char* destinationBuffer) const {
memcpy(destinationBuffer, &_modelID, sizeof(_modelID));
return sizeof(_modelID);
}
int ModelReferential::unpackExtraData(const unsigned char *sourceBuffer, int size) {
memcpy(&_modelID, sourceBuffer, sizeof(_modelID));
return sizeof(_modelID);
}
JointReferential::JointReferential(Referential* referential, ModelTree* tree, AvatarData* avatar) :
ModelReferential(referential, tree, avatar)
{
_type = JOINT;
if (!isValid()) {
qDebug() << "JointReferential::copyConstructor(): Not Valid";
return;
}
const ModelItem* item = _tree->findModelByID(_modelID);
const Model* model = getModel(item);
if (!isValid() || model == NULL || _jointIndex >= model->getJointStateCount()) {
_refScale = item->getRadius();
model->getJointRotationInWorldFrame(_jointIndex, _refRotation);
model->getJointPositionInWorldFrame(_jointIndex, _refPosition);
}
update();
}
JointReferential::JointReferential(uint32_t jointIndex, uint32_t modelID, ModelTree* tree, AvatarData* avatar) :
ModelReferential(modelID, tree, avatar),
_jointIndex(jointIndex)
{
_type = JOINT;
const ModelItem* item = _tree->findModelByID(_modelID);
const Model* model = getModel(item);
if (!isValid() || model == NULL || _jointIndex >= model->getJointStateCount()) {
qDebug() << "JointReferential::constructor(): Not Valid";
_isValid = false;
return;
}
_refScale = item->getRadius();
model->getJointRotationInWorldFrame(_jointIndex, _refRotation);
model->getJointPositionInWorldFrame(_jointIndex, _refPosition);
glm::quat refInvRot = glm::inverse(_refRotation);
_scale = _avatar->getTargetScale() / _refScale;
_rotation = refInvRot * _avatar->getOrientation();
_translation = refInvRot * (avatar->getPosition() - _refPosition) / _refScale;
}
void JointReferential::update() {
const ModelItem* item = _tree->findModelByID(_modelID);
const Model* model = getModel(item);
if (!isValid() || model == NULL || _jointIndex >= model->getJointStateCount()) {
return;
}
bool somethingChanged = false;
if (item->getRadius() != _refScale) {
_refScale = item->getRadius();
_avatar->setTargetScale(_refScale * _scale, true);
somethingChanged = true;
}
if (item->getModelRotation() != _refRotation) {
model->getJointRotationInWorldFrame(_jointIndex, _refRotation);
_avatar->setOrientation(_refRotation * _rotation, true);
somethingChanged = true;
}
if (item->getPosition() != _refPosition || somethingChanged) {
model->getJointPositionInWorldFrame(_jointIndex, _refPosition);
_avatar->setPosition(_refPosition + _refRotation * (_translation * _refScale), true);
}
}
const Model* JointReferential::getModel(const ModelItem* item) {
ModelItemFBXService* fbxService = _tree->getFBXService();
if (item != NULL && fbxService != NULL) {
return fbxService->getModelForModelItem(*item);
}
return NULL;
}
int JointReferential::packExtraData(unsigned char* destinationBuffer) const {
unsigned char* startPosition = destinationBuffer;
destinationBuffer += ModelReferential::packExtraData(destinationBuffer);
memcpy(destinationBuffer, &_jointIndex, sizeof(_jointIndex));
destinationBuffer += sizeof(_jointIndex);
return destinationBuffer - startPosition;
}
int JointReferential::unpackExtraData(const unsigned char *sourceBuffer, int size) {
const unsigned char* startPosition = sourceBuffer;
sourceBuffer += ModelReferential::unpackExtraData(sourceBuffer, size);
memcpy(&_jointIndex, sourceBuffer, sizeof(_jointIndex));
sourceBuffer += sizeof(_jointIndex);
return sourceBuffer - startPosition;
}

View file

@ -0,0 +1,48 @@
//
// ModelReferential.h
//
//
// Created by Clement on 7/30/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelReferential_h
#define hifi_ModelReferential_h
#include <Referential.h>
class ModelTree;
class Model;
class ModelReferential : public Referential {
public:
ModelReferential(Referential* ref, ModelTree* tree, AvatarData* avatar);
ModelReferential(uint32_t modelID, ModelTree* tree, AvatarData* avatar);
virtual void update();
protected:
virtual int packExtraData(unsigned char* destinationBuffer) const;
virtual int unpackExtraData(const unsigned char* sourceBuffer, int size);
uint32_t _modelID;
ModelTree* _tree;
};
class JointReferential : public ModelReferential {
public:
JointReferential(Referential* ref, ModelTree* tree, AvatarData* avatar);
JointReferential(uint32_t jointIndex, uint32_t modelID, ModelTree* tree, AvatarData* avatar);
virtual void update();
protected:
const Model* getModel(const ModelItem* item);
virtual int packExtraData(unsigned char* destinationBuffer) const;
virtual int unpackExtraData(const unsigned char* sourceBuffer, int size);
uint32_t _jointIndex;
};
#endif // hifi_ModelReferential_h

View file

@ -16,14 +16,13 @@
const float DEFAULT_MUSCLE_STRENGTH = 0.5f * MAX_MUSCLE_STRENGTH;
MuscleConstraint::MuscleConstraint(VerletPoint* parent, VerletPoint* child)
: _rootPoint(parent), _childPoint(child),
MuscleConstraint::MuscleConstraint(VerletPoint* parent, VerletPoint* child) : _rootPoint(parent), _childPoint(child),
_parentIndex(-1), _childndex(-1), _strength(DEFAULT_MUSCLE_STRENGTH) {
_childOffset = child->_position - parent->_position;
}
float MuscleConstraint::enforce() {
_childPoint->_position = (1.0f - _strength) * _childPoint->_position + _strength * (_rootPoint->_position + _childOffset);
_childPoint->_position += _strength * (_rootPoint->_position + _childOffset - _childPoint->_position);
return 0.0f;
}

View file

@ -17,7 +17,7 @@
// MuscleConstraint is a simple constraint that pushes the child toward an offset relative to the parent.
// It does NOT push the parent.
const float MAX_MUSCLE_STRENGTH = 0.5f;
const float MAX_MUSCLE_STRENGTH = 0.75f;
class MuscleConstraint : public Constraint {
public:

View file

@ -32,8 +32,10 @@
#include "Audio.h"
#include "Environment.h"
#include "Menu.h"
#include "ModelReferential.h"
#include "MyAvatar.h"
#include "Physics.h"
#include "Recorder.h"
#include "devices/Faceshift.h"
#include "devices/OculusManager.h"
#include "ui/TextRenderer.h"
@ -82,15 +84,16 @@ MyAvatar::MyAvatar() :
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
_driveKeys[i] = 0.0f;
}
_physicsSimulation.setEntity(&_skeletonModel);
_skeletonModel.setEnableShapes(true);
// The skeleton is both a PhysicsEntity and Ragdoll, so we add it to the simulation once for each type.
_physicsSimulation.addEntity(&_skeletonModel);
_physicsSimulation.addRagdoll(&_skeletonModel);
Ragdoll* ragdoll = _skeletonModel.buildRagdoll();
_physicsSimulation.setRagdoll(ragdoll);
}
MyAvatar::~MyAvatar() {
_physicsSimulation.removeEntity(&_skeletonModel);
_physicsSimulation.removeRagdoll(&_skeletonModel);
_physicsSimulation.setRagdoll(NULL);
_physicsSimulation.setEntity(NULL);
_lookAtTargetAvatar.clear();
}
@ -108,6 +111,10 @@ void MyAvatar::reset() {
}
void MyAvatar::update(float deltaTime) {
if (_referential) {
_referential->update();
}
Head* head = getHead();
head->relaxLean(deltaTime);
updateFromTrackers(deltaTime);
@ -130,6 +137,12 @@ void MyAvatar::update(float deltaTime) {
void MyAvatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("simulate");
// Play back recording
if (_player && _player->isPlaying()) {
_player->play();
}
if (_scale != _targetScale) {
float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale;
setScale(scale);
@ -142,7 +155,7 @@ void MyAvatar::simulate(float deltaTime) {
updateOrientation(deltaTime);
updatePosition(deltaTime);
}
{
PerformanceTimer perfTimer("hand");
// update avatar skeleton and simulate hand and head
@ -200,11 +213,21 @@ void MyAvatar::simulate(float deltaTime) {
{
PerformanceTimer perfTimer("ragdoll");
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) {
const int minError = 0.01f;
const float maxIterations = 10;
const quint64 maxUsec = 2000;
Ragdoll* ragdoll = _skeletonModel.getRagdoll();
if (ragdoll && Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) {
const float minError = 0.00001f;
const float maxIterations = 3;
const quint64 maxUsec = 4000;
_physicsSimulation.setTranslation(_position);
_physicsSimulation.stepForward(deltaTime, minError, maxIterations, maxUsec);
// harvest any displacement of the Ragdoll that is a result of collisions
glm::vec3 ragdollDisplacement = ragdoll->getAndClearAccumulatedMovement();
const float MAX_RAGDOLL_DISPLACEMENT_2 = 1.0f;
float length2 = glm::length2(ragdollDisplacement);
if (length2 > EPSILON && length2 < MAX_RAGDOLL_DISPLACEMENT_2) {
setPosition(getPosition() + ragdollDisplacement);
}
} else {
_skeletonModel.moveShapesTowardJoints(1.0f);
}
@ -230,14 +253,17 @@ void MyAvatar::simulate(float deltaTime) {
} else {
_trapDuration = 0.0f;
}
/* TODO: Andrew to make this work
if (_collisionGroups & COLLISION_GROUP_AVATARS) {
PerformanceTimer perfTimer("avatars");
updateCollisionWithAvatars(deltaTime);
}
*/
}
// Record avatars movements.
if (_recorder && _recorder->isRecording()) {
_recorder->record();
}
// consider updating our billboard
maybeUpdateBillboard();
}
@ -246,11 +272,19 @@ void MyAvatar::simulate(float deltaTime) {
void MyAvatar::updateFromTrackers(float deltaTime) {
glm::vec3 estimatedPosition, estimatedRotation;
if (Application::getInstance()->getPrioVR()->hasHeadRotation()) {
if (isPlaying()) {
estimatedRotation = glm::degrees(safeEulerAngles(_player->getHeadRotation()));
} else if (Application::getInstance()->getPrioVR()->hasHeadRotation()) {
estimatedRotation = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getHeadRotation()));
estimatedRotation.x *= -1.0f;
estimatedRotation.z *= -1.0f;
} else if (OculusManager::isConnected()) {
estimatedPosition = OculusManager::getRelativePosition();
estimatedPosition.x *= -1.0f;
const float OCULUS_LEAN_SCALE = 0.05f;
estimatedPosition /= OCULUS_LEAN_SCALE;
} else {
FaceTracker* tracker = Application::getInstance()->getActiveFaceTracker();
if (tracker) {
@ -282,7 +316,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
Head* head = getHead();
if (OculusManager::isConnected()) {
if (OculusManager::isConnected() || isPlaying()) {
head->setDeltaPitch(estimatedRotation.x);
head->setDeltaYaw(estimatedRotation.y);
} else {
@ -292,6 +326,11 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
}
head->setDeltaRoll(estimatedRotation.z);
if (isPlaying()) {
head->setLeanSideways(_player->getLeanSideways());
head->setLeanForward(_player->getLeanForward());
return;
}
// the priovr can give us exact lean
if (Application::getInstance()->getPrioVR()->isActive()) {
glm::vec3 eulers = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getTorsoRotation()));
@ -299,7 +338,6 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
head->setLeanForward(eulers.x);
return;
}
// Update torso lean distance based on accelerometer data
const float TORSO_LENGTH = 0.5f;
glm::vec3 relativePosition = estimatedPosition - glm::vec3(0.0f, -TORSO_LENGTH, 0.0f);
@ -444,6 +482,71 @@ glm::vec3 MyAvatar::getRightPalmPosition() {
return rightHandPosition;
}
void MyAvatar::clearReferential() {
changeReferential(NULL);
}
bool MyAvatar::setModelReferential(int id) {
ModelTree* tree = Application::getInstance()->getModels()->getTree();
changeReferential(new ModelReferential(id, tree, this));
if (_referential->isValid()) {
return true;
} else {
changeReferential(NULL);
return false;
}
}
bool MyAvatar::setJointReferential(int id, int jointIndex) {
ModelTree* tree = Application::getInstance()->getModels()->getTree();
changeReferential(new JointReferential(jointIndex, id, tree, this));
if (!_referential->isValid()) {
return true;
} else {
changeReferential(NULL);
return false;
}
}
bool MyAvatar::isRecording() const {
return _recorder && _recorder->isRecording();
}
RecorderPointer MyAvatar::startRecording() {
if (!_recorder) {
_recorder = RecorderPointer(new Recorder(this));
}
_recorder->startRecording();
return _recorder;
}
void MyAvatar::stopRecording() {
if (_recorder) {
_recorder->stopRecording();
}
}
bool MyAvatar::isPlaying() const {
return _player && _player->isPlaying();
}
PlayerPointer MyAvatar::startPlaying() {
if (!_player) {
_player = PlayerPointer(new Player(this));
}
if (_recorder) {
_player->loadRecording(_recorder->getRecording());
_player->startPlaying();
}
return _player;
}
void MyAvatar::stopPlaying() {
if (_player) {
_player->stopPlaying();
}
}
void MyAvatar::setLocalGravity(glm::vec3 gravity) {
_motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
// Environmental and Local gravities are incompatible. Since Local is being set here
@ -826,6 +929,14 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const {
const float JOINT_PRIORITY = 2.0f;
void MyAvatar::setJointRotations(QVector<glm::quat> jointRotations) {
for (int i = 0; i < jointRotations.size(); ++i) {
if (i < _jointData.size()) {
_skeletonModel.setJointState(i, true, jointRotations[i], JOINT_PRIORITY + 1.0f);
}
}
}
void MyAvatar::setJointData(int index, const glm::quat& rotation) {
Avatar::setJointData(index, rotation);
if (QThread::currentThread() == thread()) {
@ -840,6 +951,15 @@ void MyAvatar::clearJointData(int index) {
}
}
void MyAvatar::clearJointsData() {
for (int i = 0; i < _jointData.size(); ++i) {
Avatar::clearJointData(i);
if (QThread::currentThread() == thread()) {
_skeletonModel.clearJointState(i);
}
}
}
void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) {
Avatar::setFaceModelURL(faceModelURL);
_billboardValid = false;
@ -1520,8 +1640,6 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float
return false;
}
const float BODY_COLLISION_RESOLUTION_TIMESCALE = 0.5f; // seconds
void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
// Reset detector for nearest avatar
_distanceToNearestAvatar = std::numeric_limits<float>::max();
@ -1532,41 +1650,51 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
}
float myBoundingRadius = getBoundingRadius();
const float BODY_COLLISION_RESOLUTION_FACTOR = glm::max(1.0f, deltaTime / BODY_COLLISION_RESOLUTION_TIMESCALE);
// find nearest avatar
float nearestDistance2 = std::numeric_limits<float>::max();
Avatar* nearestAvatar = NULL;
foreach (const AvatarSharedPointer& avatarPointer, avatars) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (static_cast<Avatar*>(this) == avatar) {
// don't collide with ourselves
continue;
}
float distance = glm::length(_position - avatar->getPosition());
if (_distanceToNearestAvatar > distance) {
_distanceToNearestAvatar = distance;
float distance2 = glm::distance2(_position, avatar->getPosition());
if (nearestDistance2 > distance2) {
nearestDistance2 = distance2;
nearestAvatar = avatar;
}
float theirBoundingRadius = avatar->getBoundingRadius();
if (distance < myBoundingRadius + theirBoundingRadius) {
// collide our body against theirs
QVector<const Shape*> myShapes;
_skeletonModel.getBodyShapes(myShapes);
QVector<const Shape*> theirShapes;
avatar->getSkeletonModel().getBodyShapes(theirShapes);
}
_distanceToNearestAvatar = glm::sqrt(nearestDistance2);
CollisionInfo collision;
if (ShapeCollider::collideShapesCoarse(myShapes, theirShapes, collision)) {
float penetrationDepth = glm::length(collision._penetration);
if (penetrationDepth > myBoundingRadius) {
qDebug() << "WARNING: ignoring avatar-avatar penetration depth " << penetrationDepth;
}
else if (penetrationDepth > EPSILON) {
setPosition(getPosition() - BODY_COLLISION_RESOLUTION_FACTOR * collision._penetration);
_lastBodyPenetration += collision._penetration;
emit collisionWithAvatar(getSessionUUID(), avatar->getSessionUUID(), collision);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) {
if (nearestAvatar != NULL) {
if (_distanceToNearestAvatar > myBoundingRadius + nearestAvatar->getBoundingRadius()) {
// they aren't close enough to put into the _physicsSimulation
// so we clear the pointer
nearestAvatar = NULL;
}
}
foreach (const AvatarSharedPointer& avatarPointer, avatars) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (static_cast<Avatar*>(this) == avatar) {
// don't collide with ourselves
continue;
}
SkeletonModel* skeleton = &(avatar->getSkeletonModel());
PhysicsSimulation* simulation = skeleton->getSimulation();
if (avatar == nearestAvatar) {
if (simulation != &(_physicsSimulation)) {
skeleton->setEnableShapes(true);
_physicsSimulation.addEntity(skeleton);
_physicsSimulation.addRagdoll(skeleton->getRagdoll());
}
} else if (simulation == &(_physicsSimulation)) {
_physicsSimulation.removeRagdoll(skeleton->getRagdoll());
_physicsSimulation.removeEntity(skeleton);
skeleton->setEnableShapes(false);
}
// collide their hands against us
avatar->getHand()->collideAgainstAvatar(this, false);
}
}
}

View file

@ -18,6 +18,8 @@
#include "Avatar.h"
class ModelItemID;
enum AvatarHandState
{
HAND_STATE_NULL = 0,
@ -110,8 +112,10 @@ public:
void updateLookAtTargetAvatar();
void clearLookAtTargetAvatar();
virtual void setJointRotations(QVector<glm::quat> jointRotations);
virtual void setJointData(int index, const glm::quat& rotation);
virtual void clearJointData(int index);
virtual void clearJointsData();
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
@ -149,6 +153,21 @@ public slots:
glm::vec3 getLeftPalmPosition();
glm::vec3 getRightPalmPosition();
void clearReferential();
bool setModelReferential(int id);
bool setJointReferential(int id, int jointIndex);
const RecorderPointer getRecorder() const { return _recorder; }
bool isRecording() const;
RecorderPointer startRecording();
void stopRecording();
const PlayerPointer getPlayer() const { return _player; }
bool isPlaying() const;
PlayerPointer startPlaying();
void stopPlaying();
signals:
void transformChanged();
@ -186,6 +205,9 @@ private:
QList<AnimationHandlePointer> _animationHandles;
PhysicsSimulation _physicsSimulation;
RecorderPointer _recorder;
PlayerPointer _player;
// private methods
float computeDistanceToFloor(const glm::vec3& startPoint);
void updateOrientation(float deltaTime);

View file

@ -14,22 +14,25 @@
#include <VerletCapsuleShape.h>
#include <VerletSphereShape.h>
#include <DistanceConstraint.h>
#include <FixedConstraint.h>
#include "Application.h"
#include "Avatar.h"
#include "Hand.h"
#include "Menu.h"
#include "MuscleConstraint.h"
#include "SkeletonModel.h"
#include "SkeletonRagdoll.h"
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
Model(parent),
Ragdoll(),
_owningAvatar(owningAvatar),
_boundingShape(),
_boundingShapeLocalOffset(0.0f) {
_boundingShapeLocalOffset(0.0f),
_ragdoll(NULL) {
}
SkeletonModel::~SkeletonModel() {
delete _ragdoll;
_ragdoll = NULL;
}
void SkeletonModel::setJointStates(QVector<JointState> states) {
@ -59,9 +62,15 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
Model::simulate(deltaTime, fullUpdate);
if (!(isActive() && _owningAvatar->isMyAvatar())) {
if (!isActive() || !_owningAvatar->isMyAvatar()) {
return; // only simulate for own avatar
}
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
if (myAvatar->isPlaying()) {
// Don't take inputs if playing back a recording.
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
PrioVR* prioVR = Application::getInstance()->getPrioVR();
@ -155,9 +164,6 @@ void SkeletonModel::getBodyShapes(QVector<const Shape*>& shapes) const {
void SkeletonModel::renderIKConstraints() {
renderJointConstraints(getRightHandJointIndex());
renderJointConstraints(getLeftHandJointIndex());
//if (isActive() && _owningAvatar->isMyAvatar()) {
// renderRagdoll();
//}
}
class IndexValue {
@ -432,6 +438,10 @@ bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const {
}
bool SkeletonModel::getNeckPosition(glm::vec3& neckPosition) const {
if (_owningAvatar->isMyAvatar() &&
Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) {
return isActive() && getVisibleJointPositionInWorldFrame(_geometry->getFBXGeometry().neckJointIndex, neckPosition);
}
return isActive() && getJointPositionInWorldFrame(_geometry->getFBXGeometry().neckJointIndex, neckPosition);
}
@ -445,11 +455,16 @@ bool SkeletonModel::getNeckParentRotationFromDefaultOrientation(glm::quat& neckP
}
int parentIndex = geometry.joints.at(geometry.neckJointIndex).parentIndex;
glm::quat worldFrameRotation;
if (getJointRotationInWorldFrame(parentIndex, worldFrameRotation)) {
neckParentRotation = worldFrameRotation * _jointStates[parentIndex].getFBXJoint().inverseDefaultRotation;
return true;
bool success = false;
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) {
success = getVisibleJointRotationInWorldFrame(parentIndex, worldFrameRotation);
} else {
success = getJointRotationInWorldFrame(parentIndex, worldFrameRotation);
}
return false;
if (success) {
neckParentRotation = worldFrameRotation * _jointStates[parentIndex].getFBXJoint().inverseDefaultRotation;
}
return success;
}
bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
@ -480,21 +495,27 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco
}
void SkeletonModel::renderRagdoll() {
if (!_ragdoll) {
return;
}
const QVector<VerletPoint>& points = _ragdoll->getPoints();
const int BALL_SUBDIVISIONS = 6;
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation);
int numPoints = _ragdollPoints.size();
int numPoints = points.size();
float alpha = 0.3f;
float radius1 = 0.008f;
float radius2 = 0.01f;
glm::vec3 simulationTranslation = _ragdoll->getTranslationInSimulationFrame();
for (int i = 0; i < numPoints; ++i) {
glPushMatrix();
// draw each point as a yellow hexagon with black border
glm::vec3 position = _rotation * _ragdollPoints[i]._position;
// NOTE: ragdollPoints are in simulation-frame but we want them to be model-relative
glm::vec3 position = points[i]._position - simulationTranslation;
glTranslatef(position.x, position.y, position.z);
// draw each point as a yellow hexagon with black border
glColor4f(0.0f, 0.0f, 0.0f, alpha);
glutSolidSphere(radius2, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
glColor4f(1.0f, 1.0f, 0.0f, alpha);
@ -506,108 +527,18 @@ void SkeletonModel::renderRagdoll() {
glEnable(GL_LIGHTING);
}
// virtual
void SkeletonModel::initRagdollPoints() {
clearRagdollConstraintsAndPoints();
_muscleConstraints.clear();
// one point for each joint
int numJoints = _jointStates.size();
_ragdollPoints.fill(VerletPoint(), numJoints);
for (int i = 0; i < numJoints; ++i) {
const JointState& state = _jointStates.at(i);
glm::vec3 position = state.getPosition();
_ragdollPoints[i].initPosition(position);
}
}
void SkeletonModel::buildRagdollConstraints() {
// NOTE: the length of DistanceConstraints is computed and locked in at this time
// so make sure the ragdoll positions are in a normal configuration before here.
const int numPoints = _ragdollPoints.size();
assert(numPoints == _jointStates.size());
float minBone = FLT_MAX;
float maxBone = -FLT_MAX;
QMultiMap<int, int> families;
for (int i = 0; i < numPoints; ++i) {
const JointState& state = _jointStates.at(i);
int parentIndex = state.getParentIndex();
if (parentIndex == -1) {
FixedConstraint* anchor = new FixedConstraint(&(_ragdollPoints[i]), glm::vec3(0.0f));
_ragdollConstraints.push_back(anchor);
} else {
DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[i]), &(_ragdollPoints[parentIndex]));
bone->setDistance(state.getDistanceToParent());
_ragdollConstraints.push_back(bone);
families.insert(parentIndex, i);
}
float boneLength = glm::length(state.getPositionInParentFrame());
if (boneLength > maxBone) {
maxBone = boneLength;
} else if (boneLength < minBone) {
minBone = boneLength;
}
}
// Joints that have multiple children effectively have rigid constraints between the children
// in the parent frame, so we add DistanceConstraints between children in the same family.
QMultiMap<int, int>::iterator itr = families.begin();
while (itr != families.end()) {
QList<int> children = families.values(itr.key());
int numChildren = children.size();
if (numChildren > 1) {
for (int i = 1; i < numChildren; ++i) {
DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[i-1]]), &(_ragdollPoints[children[i]]));
_ragdollConstraints.push_back(bone);
}
if (numChildren > 2) {
DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[children[numChildren-1]]), &(_ragdollPoints[children[0]]));
_ragdollConstraints.push_back(bone);
}
}
++itr;
}
float MAX_STRENGTH = 0.3f;
float MIN_STRENGTH = 0.005f;
// each joint gets a MuscleConstraint to its parent
for (int i = 1; i < numPoints; ++i) {
const JointState& state = _jointStates.at(i);
int p = state.getParentIndex();
if (p == -1) {
continue;
}
MuscleConstraint* constraint = new MuscleConstraint(&(_ragdollPoints[p]), &(_ragdollPoints[i]));
_ragdollConstraints.push_back(constraint);
_muscleConstraints.push_back(constraint);
// Short joints are more susceptible to wiggle so we modulate the strength based on the joint's length:
// long = weak and short = strong.
constraint->setIndices(p, i);
float boneLength = glm::length(state.getPositionInParentFrame());
float strength = MIN_STRENGTH + (MAX_STRENGTH - MIN_STRENGTH) * (maxBone - boneLength) / (maxBone - minBone);
if (!families.contains(i)) {
// Although muscles only pull on the children not parents, nevertheless those joints that have
// parents AND children are more stable than joints at the end such as fingers. For such joints we
// bestow maximum strength which helps reduce wiggle.
strength = MAX_MUSCLE_STRENGTH;
}
constraint->setStrength(strength);
}
}
void SkeletonModel::updateVisibleJointStates() {
if (_showTrueJointTransforms) {
if (_showTrueJointTransforms || !_ragdoll) {
// no need to update visible transforms
return;
}
const QVector<VerletPoint>& ragdollPoints = _ragdoll->getPoints();
QVector<glm::vec3> points;
points.reserve(_jointStates.size());
glm::quat invRotation = glm::inverse(_rotation);
for (int i = 0; i < _jointStates.size(); i++) {
JointState& state = _jointStates[i];
points.push_back(_ragdollPoints[i]._position);
points.push_back(ragdollPoints[i]._position);
// get the parent state (this is the state that we want to rotate)
int parentIndex = state.getParentIndex();
@ -629,8 +560,9 @@ void SkeletonModel::updateVisibleJointStates() {
// we're looking for the rotation that moves visible bone parallel to ragdoll bone
// rotationBetween(jointTip - jointPivot, shapeTip - shapePivot)
// NOTE: points are in simulation-frame so rotate line segment into model-frame
glm::quat delta = rotationBetween(state.getVisiblePosition() - extractTranslation(parentTransform),
points[i] - points[parentIndex]);
invRotation * (points[i] - points[parentIndex]));
// apply
parentState.mixVisibleRotationDelta(delta, 0.01f);
@ -640,10 +572,14 @@ void SkeletonModel::updateVisibleJointStates() {
}
}
// virtual
void SkeletonModel::stepRagdollForward(float deltaTime) {
Ragdoll::stepRagdollForward(deltaTime);
updateMuscles();
SkeletonRagdoll* SkeletonModel::buildRagdoll() {
if (!_ragdoll) {
_ragdoll = new SkeletonRagdoll(this);
if (_enableShapes) {
buildShapes();
}
}
return _ragdoll;
}
float DENSITY_OF_WATER = 1000.0f; // kg/m^3
@ -661,10 +597,17 @@ void SkeletonModel::buildShapes() {
return;
}
initRagdollPoints();
if (!_ragdoll) {
_ragdoll = new SkeletonRagdoll(this);
}
_ragdoll->initPoints();
QVector<VerletPoint>& points = _ragdoll->getPoints();
float massScale = _ragdoll->getMassScale();
float uniformScale = extractUniformScale(_scale);
const int numStates = _jointStates.size();
float totalMass = 0.0f;
for (int i = 0; i < numStates; i++) {
JointState& state = _jointStates[i];
const FBXJoint& joint = state.getFBXJoint();
@ -675,29 +618,39 @@ void SkeletonModel::buildShapes() {
// this shape is forced to be a sphere
type = Shape::SPHERE_SHAPE;
}
if (radius < EPSILON) {
type = Shape::UNKNOWN_SHAPE;
}
Shape* shape = NULL;
int parentIndex = joint.parentIndex;
if (type == Shape::SPHERE_SHAPE) {
shape = new VerletSphereShape(radius, &(_ragdollPoints[i]));
shape = new VerletSphereShape(radius, &(points[i]));
shape->setEntity(this);
_ragdollPoints[i]._mass = glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume());
float mass = massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume());
points[i].setMass(mass);
totalMass += mass;
} else if (type == Shape::CAPSULE_SHAPE) {
assert(parentIndex != -1);
shape = new VerletCapsuleShape(radius, &(_ragdollPoints[parentIndex]), &(_ragdollPoints[i]));
shape = new VerletCapsuleShape(radius, &(points[parentIndex]), &(points[i]));
shape->setEntity(this);
_ragdollPoints[i]._mass = glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume());
float mass = massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume());
points[i].setMass(mass);
totalMass += mass;
}
if (parentIndex != -1) {
// always disable collisions between joint and its parent
disableCollisions(i, parentIndex);
} else {
// give the base joint a very large mass since it doesn't actually move
// in the local-frame simulation (it defines the origin)
_ragdollPoints[i]._mass = VERY_BIG_MASS;
}
if (shape) {
disableCollisions(i, parentIndex);
}
}
_shapes.push_back(shape);
}
// set the mass of the root
if (numStates > 0) {
points[0].setMass(totalMass);
}
// This method moves the shapes to their default positions in Model frame.
computeBoundingShape(geometry);
@ -705,16 +658,11 @@ void SkeletonModel::buildShapes() {
// joints that are currently colliding.
disableCurrentSelfCollisions();
buildRagdollConstraints();
_ragdoll->buildConstraints();
// ... then move shapes back to current joint positions
if (_ragdollPoints.size() == numStates) {
int numJoints = _jointStates.size();
for (int i = 0; i < numJoints; ++i) {
_ragdollPoints[i].initPosition(_jointStates.at(i).getPosition());
}
}
enforceRagdollConstraints();
_ragdoll->slamPointPositions();
_ragdoll->enforceConstraints();
}
void SkeletonModel::moveShapesTowardJoints(float deltaTime) {
@ -722,8 +670,9 @@ void SkeletonModel::moveShapesTowardJoints(float deltaTime) {
// unravel a skelton that has become tangled in its constraints. So let's keep this
// around for a while just in case.
const int numStates = _jointStates.size();
assert(_jointStates.size() == _ragdollPoints.size());
if (_ragdollPoints.size() != numStates) {
QVector<VerletPoint>& ragdollPoints = _ragdoll->getPoints();
assert(_jointStates.size() == ragdollPoints.size());
if (ragdollPoints.size() != numStates) {
return;
}
@ -732,45 +681,31 @@ void SkeletonModel::moveShapesTowardJoints(float deltaTime) {
float fraction = glm::clamp(deltaTime / RAGDOLL_FOLLOWS_JOINTS_TIMESCALE, 0.0f, 1.0f);
float oneMinusFraction = 1.0f - fraction;
glm::vec3 simulationTranslation = _ragdoll->getTranslationInSimulationFrame();
for (int i = 0; i < numStates; ++i) {
_ragdollPoints[i].initPosition(oneMinusFraction * _ragdollPoints[i]._position +
fraction * _jointStates.at(i).getPosition());
}
}
void SkeletonModel::updateMuscles() {
int numConstraints = _muscleConstraints.size();
for (int i = 0; i < numConstraints; ++i) {
MuscleConstraint* constraint = _muscleConstraints[i];
int j = constraint->getParentIndex();
if (j == -1) {
continue;
}
int k = constraint->getChildIndex();
if (k == -1) {
continue;
}
constraint->setChildOffset(_jointStates.at(k).getPosition() - _jointStates.at(j).getPosition());
// ragdollPoints are in simulation-frame but jointStates are in model-frame
ragdollPoints[i].initPosition(oneMinusFraction * ragdollPoints[i]._position +
fraction * (simulationTranslation + _rotation * (_jointStates.at(i).getPosition())));
}
}
void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
// compute default joint transforms
int numJoints = geometry.joints.size();
if (numJoints != _ragdollPoints.size()) {
return;
}
int numStates = _jointStates.size();
QVector<glm::mat4> transforms;
transforms.fill(glm::mat4(), numJoints);
transforms.fill(glm::mat4(), numStates);
QVector<VerletPoint>& ragdollPoints = _ragdoll->getPoints();
// compute the default transforms and slam the ragdoll positions accordingly
// (which puts the shapes where we want them)
for (int i = 0; i < numJoints; i++) {
const FBXJoint& joint = geometry.joints.at(i);
for (int i = 0; i < numStates; i++) {
JointState& state = _jointStates[i];
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
transforms[i] = _jointStates[i].getTransform();
_ragdollPoints[i].initPosition(extractTranslation(transforms[i]));
ragdollPoints[i].initPosition(extractTranslation(transforms[i]));
continue;
}
@ -778,7 +713,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
* joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform;
// setting the ragdollPoints here slams the VerletShapes into their default positions
_ragdollPoints[i].initPosition(extractTranslation(transforms[i]));
ragdollPoints[i].initPosition(extractTranslation(transforms[i]));
}
// compute bounding box that encloses all shapes
@ -838,7 +773,7 @@ void SkeletonModel::resetShapePositionsToDefaultPose() {
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (geometry.joints.isEmpty() || _shapes.size() != geometry.joints.size()) {
if (geometry.joints.isEmpty()) {
return;
}
@ -893,3 +828,57 @@ void SkeletonModel::renderBoundingCollisionShapes(float alpha) {
glPopMatrix();
}
const int BALL_SUBDIVISIONS = 10;
// virtual
void SkeletonModel::renderJointCollisionShapes(float alpha) {
if (!_ragdoll) {
return;
}
glPushMatrix();
Application::getInstance()->loadTranslatedViewMatrix(_translation);
glm::vec3 simulationTranslation = _ragdoll->getTranslationInSimulationFrame();
for (int i = 0; i < _shapes.size(); i++) {
Shape* shape = _shapes[i];
if (!shape) {
continue;
}
glPushMatrix();
// shapes are stored in simulation-frame but we want position to be model-relative
if (shape->getType() == Shape::SPHERE_SHAPE) {
glm::vec3 position = shape->getTranslation() - simulationTranslation;
glTranslatef(position.x, position.y, position.z);
// draw a grey sphere at shape position
glColor4f(0.75f, 0.75f, 0.75f, alpha);
glutSolidSphere(shape->getBoundingRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
} else if (shape->getType() == Shape::CAPSULE_SHAPE) {
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
// draw a blue sphere at the capsule endpoint
glm::vec3 endPoint;
capsule->getEndPoint(endPoint);
endPoint = endPoint - simulationTranslation;
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
glColor4f(0.6f, 0.6f, 0.8f, alpha);
glutSolidSphere(capsule->getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
// draw a yellow sphere at the capsule startpoint
glm::vec3 startPoint;
capsule->getStartPoint(startPoint);
startPoint = startPoint - simulationTranslation;
glm::vec3 axis = endPoint - startPoint;
glTranslatef(-axis.x, -axis.y, -axis.z);
glColor4f(0.8f, 0.8f, 0.6f, alpha);
glutSolidSphere(capsule->getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
// draw a green cylinder between the two points
glm::vec3 origin(0.0f);
glColor4f(0.6f, 0.8f, 0.6f, alpha);
Avatar::renderJointConnectingCone( origin, axis, capsule->getRadius(), capsule->getRadius());
}
glPopMatrix();
}
glPopMatrix();
}

View file

@ -15,18 +15,20 @@
#include "renderer/Model.h"
#include <CapsuleShape.h>
#include <Ragdoll.h>
#include "SkeletonRagdoll.h"
class Avatar;
class MuscleConstraint;
class SkeletonRagdoll;
/// A skeleton loaded from a model.
class SkeletonModel : public Model, public Ragdoll {
class SkeletonModel : public Model {
Q_OBJECT
public:
SkeletonModel(Avatar* owningAvatar, QObject* parent = NULL);
~SkeletonModel();
void setJointStates(QVector<JointState> states);
@ -96,15 +98,15 @@ public:
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
virtual void updateVisibleJointStates();
// virtual overrride from Ragdoll
virtual void stepRagdollForward(float deltaTime);
SkeletonRagdoll* buildRagdoll();
SkeletonRagdoll* getRagdoll() { return _ragdoll; }
void moveShapesTowardJoints(float fraction);
void updateMuscles();
void computeBoundingShape(const FBXGeometry& geometry);
void renderBoundingCollisionShapes(float alpha);
void renderJointCollisionShapes(float alpha);
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
@ -114,10 +116,6 @@ public:
protected:
// virtual overrrides from Ragdoll
void initRagdollPoints();
void buildRagdollConstraints();
void buildShapes();
/// \param jointIndex index of joint in model
@ -146,7 +144,7 @@ private:
CapsuleShape _boundingShape;
glm::vec3 _boundingShapeLocalOffset;
QVector<MuscleConstraint*> _muscleConstraints;
SkeletonRagdoll* _ragdoll;
};
#endif // hifi_SkeletonModel_h

View file

@ -0,0 +1,145 @@
//
// SkeletonRagdoll.cpp
// interface/src/avatar
//
// Created by Andrew Meadows 2014.08.14
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <DistanceConstraint.h>
#include <FixedConstraint.h>
#include "SkeletonRagdoll.h"
#include "MuscleConstraint.h"
#include "../renderer/Model.h"
SkeletonRagdoll::SkeletonRagdoll(Model* model) : Ragdoll(), _model(model) {
assert(_model);
}
SkeletonRagdoll::~SkeletonRagdoll() {
}
// virtual
void SkeletonRagdoll::stepForward(float deltaTime) {
setTransform(_model->getTranslation(), _model->getRotation());
Ragdoll::stepForward(deltaTime);
updateMuscles();
int numConstraints = _muscleConstraints.size();
for (int i = 0; i < numConstraints; ++i) {
_muscleConstraints[i]->enforce();
}
}
void SkeletonRagdoll::slamPointPositions() {
QVector<JointState>& jointStates = _model->getJointStates();
int numStates = jointStates.size();
for (int i = 0; i < numStates; ++i) {
_points[i].initPosition(jointStates.at(i).getPosition());
}
}
// virtual
void SkeletonRagdoll::initPoints() {
clearConstraintsAndPoints();
_muscleConstraints.clear();
initTransform();
// one point for each joint
QVector<JointState>& jointStates = _model->getJointStates();
int numStates = jointStates.size();
_points.fill(VerletPoint(), numStates);
slamPointPositions();
}
// virtual
void SkeletonRagdoll::buildConstraints() {
QVector<JointState>& jointStates = _model->getJointStates();
// NOTE: the length of DistanceConstraints is computed and locked in at this time
// so make sure the ragdoll positions are in a normal configuration before here.
const int numPoints = _points.size();
assert(numPoints == jointStates.size());
float minBone = FLT_MAX;
float maxBone = -FLT_MAX;
QMultiMap<int, int> families;
for (int i = 0; i < numPoints; ++i) {
const JointState& state = jointStates.at(i);
int parentIndex = state.getParentIndex();
if (parentIndex != -1) {
DistanceConstraint* bone = new DistanceConstraint(&(_points[i]), &(_points[parentIndex]));
bone->setDistance(state.getDistanceToParent());
_boneConstraints.push_back(bone);
families.insert(parentIndex, i);
}
float boneLength = glm::length(state.getPositionInParentFrame());
if (boneLength > maxBone) {
maxBone = boneLength;
} else if (boneLength < minBone) {
minBone = boneLength;
}
}
// Joints that have multiple children effectively have rigid constraints between the children
// in the parent frame, so we add DistanceConstraints between children in the same family.
QMultiMap<int, int>::iterator itr = families.begin();
while (itr != families.end()) {
QList<int> children = families.values(itr.key());
int numChildren = children.size();
if (numChildren > 1) {
for (int i = 1; i < numChildren; ++i) {
DistanceConstraint* bone = new DistanceConstraint(&(_points[children[i-1]]), &(_points[children[i]]));
_boneConstraints.push_back(bone);
}
if (numChildren > 2) {
DistanceConstraint* bone = new DistanceConstraint(&(_points[children[numChildren-1]]), &(_points[children[0]]));
_boneConstraints.push_back(bone);
}
}
++itr;
}
float MAX_STRENGTH = 0.6f;
float MIN_STRENGTH = 0.05f;
// each joint gets a MuscleConstraint to its parent
for (int i = 1; i < numPoints; ++i) {
const JointState& state = jointStates.at(i);
int p = state.getParentIndex();
if (p == -1) {
continue;
}
MuscleConstraint* constraint = new MuscleConstraint(&(_points[p]), &(_points[i]));
_muscleConstraints.push_back(constraint);
// Short joints are more susceptible to wiggle so we modulate the strength based on the joint's length:
// long = weak and short = strong.
constraint->setIndices(p, i);
float boneLength = glm::length(state.getPositionInParentFrame());
float strength = MIN_STRENGTH + (MAX_STRENGTH - MIN_STRENGTH) * (maxBone - boneLength) / (maxBone - minBone);
if (!families.contains(i)) {
// Although muscles only pull on the children not parents, nevertheless those joints that have
// parents AND children are more stable than joints at the end such as fingers. For such joints we
// bestow maximum strength which helps reduce wiggle.
strength = MAX_MUSCLE_STRENGTH;
}
constraint->setStrength(strength);
}
}
void SkeletonRagdoll::updateMuscles() {
QVector<JointState>& jointStates = _model->getJointStates();
int numConstraints = _muscleConstraints.size();
glm::quat rotation = _model->getRotation();
for (int i = 0; i < numConstraints; ++i) {
MuscleConstraint* constraint = _muscleConstraints[i];
int j = constraint->getParentIndex();
int k = constraint->getChildIndex();
assert(j != -1 && k != -1);
// ragdollPoints are in simulation-frame but jointStates are in model-frame
constraint->setChildOffset(rotation * (jointStates.at(k).getPosition() - jointStates.at(j).getPosition()));
}
}

View file

@ -0,0 +1,44 @@
//
// SkeletonkRagdoll.h
// interface/src/avatar
//
// Created by Andrew Meadows 2014.08.14
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_SkeletonRagdoll_h
#define hifi_SkeletonRagdoll_h
#include <QVector>
#include <Ragdoll.h>
#include "../renderer/JointState.h"
class MuscleConstraint;
class Model;
class SkeletonRagdoll : public Ragdoll {
public:
SkeletonRagdoll(Model* model);
virtual ~SkeletonRagdoll();
void slamPointPositions();
virtual void stepForward(float deltaTime);
virtual void initPoints();
virtual void buildConstraints();
protected:
void updateMuscles();
private:
Model* _model;
QVector<MuscleConstraint*> _muscleConstraints;
};
#endif // hifi_SkeletonRagdoll_h

View file

@ -10,7 +10,7 @@
//
#include "CaraFaceTracker.h"
#include <SharedUtil.h>
#include <GLMHelpers.h>
//qt
#include <QJsonDocument>
@ -37,6 +37,7 @@ static QString sampleJson = "[{\"id\":1, \
static const glm::vec3 DEFAULT_HEAD_ORIGIN(0.0f, 0.0f, 0.0f);
static const float TRANSLATION_SCALE = 1.0f;
static const int NUM_BLENDSHAPE_COEFF = 30;
static const int NUM_SMOOTHING_SAMPLES = 3;
struct CaraPerson {
struct CaraPose {
@ -217,9 +218,9 @@ private:
CaraFaceTracker::CaraFaceTracker() :
_lastReceiveTimestamp(0),
_previousPitch(0.0f),
_previousYaw(0.0f),
_previousRoll(0.0f),
_pitchAverage(NUM_SMOOTHING_SAMPLES),
_yawAverage(NUM_SMOOTHING_SAMPLES),
_rollAverage(NUM_SMOOTHING_SAMPLES),
_eyeGazeLeftPitch(0.0f),
_eyeGazeLeftYaw(0.0f),
_eyeGazeRightPitch(0.0f),
@ -252,9 +253,9 @@ CaraFaceTracker::CaraFaceTracker() :
CaraFaceTracker::CaraFaceTracker(const QHostAddress& host, quint16 port) :
_lastReceiveTimestamp(0),
_previousPitch(0.0f),
_previousYaw(0.0f),
_previousRoll(0.0f),
_pitchAverage(NUM_SMOOTHING_SAMPLES),
_yawAverage(NUM_SMOOTHING_SAMPLES),
_rollAverage(NUM_SMOOTHING_SAMPLES),
_eyeGazeLeftPitch(0.0f),
_eyeGazeLeftYaw(0.0f),
_eyeGazeRightPitch(0.0f),
@ -371,6 +372,7 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) {
CaraPerson person = CaraPacketDecoder::extractOne(buffer, &jsonError);
if(jsonError.error == QJsonParseError::NoError) {
//do some noise filtering to the head poses
//reduce the noise first by truncating to 1 dp
person.pose.roll = glm::floor(person.pose.roll * 10) / 10;
@ -386,43 +388,39 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) {
float theta = 2 * acos(r.w);
if (theta > EPSILON) {
float rMag = glm::length(glm::vec3(r.x, r.y, r.z));
const float AVERAGE_CARA_FRAME_TIME = 0.033f;
const float AVERAGE_CARA_FRAME_TIME = 0.04f;
const float ANGULAR_VELOCITY_MIN = 1.2f;
const float YAW_STANDARD_DEV_DEG = 2.5f;
_headAngularVelocity = theta / AVERAGE_CARA_FRAME_TIME * glm::vec3(r.x, r.y, r.z) / rMag;
_pitchAverage.updateAverage(person.pose.pitch);
_rollAverage.updateAverage(person.pose.roll);
//use the angular velocity for roll and pitch, if it's below the threshold don't move
if(glm::abs(_headAngularVelocity.x) < ANGULAR_VELOCITY_MIN) {
person.pose.pitch = _previousPitch;
}
//could use the angular velocity to detemine whether to update pitch and roll to further remove the noise.
//use the angular velocity for roll and pitch, update if > THRESHOLD
//if(glm::abs(_headAngularVelocity.x) > ANGULAR_VELOCITY_MIN) {
// _pitchAverage.updateAverage(person.pose.pitch);
//}
if(glm::abs(_headAngularVelocity.z) < ANGULAR_VELOCITY_MIN) {
person.pose.roll = _previousRoll;
}
//if(glm::abs(_headAngularVelocity.z) > ANGULAR_VELOCITY_MIN) {
// _rollAverage.updateAverage(person.pose.roll);;
//}
//for yaw, the jitter is great, you can't use angular velocity because it swings too much
//use the previous and current yaw, calculate the
//abs difference and move it the difference is above the standard deviation which is around 2.5
// (this will introduce some jerks but will not encounter lag)
// < the standard deviation 2.5 deg, no move
if(glm::abs(person.pose.yaw - _previousYaw) < YAW_STANDARD_DEV_DEG) {
// > the standard deviation 2.5 deg, update the yaw smoothing average
if(glm::abs(person.pose.yaw - _yawAverage.getAverage()) > YAW_STANDARD_DEV_DEG) {
//qDebug() << "Yaw Diff: " << glm::abs(person.pose.yaw - _previousYaw);
person.pose.yaw = _previousYaw;
_yawAverage.updateAverage(person.pose.yaw);
}
//update the previous angles
_previousPitch = person.pose.pitch;
_previousYaw = person.pose.yaw;
_previousRoll = person.pose.roll;
//set the new rotation
newRotation = glm::quat(glm::vec3(DEGTORAD(person.pose.pitch), DEGTORAD(person.pose.yaw), DEGTORAD(-person.pose.roll)));
newRotation = glm::quat(glm::vec3(DEGTORAD(_pitchAverage.getAverage()), DEGTORAD(_yawAverage.getAverage()), DEGTORAD(-_rollAverage.getAverage())));
}
else {
//no change in position
newRotation = glm::quat(glm::vec3(DEGTORAD(_previousPitch), DEGTORAD(_previousYaw), DEGTORAD(-_previousRoll)));
//no change in position, use previous averages
newRotation = glm::quat(glm::vec3(DEGTORAD(_pitchAverage.getAverage()), DEGTORAD(_yawAverage.getAverage()), DEGTORAD(-_rollAverage.getAverage())));
_headAngularVelocity = glm::vec3(0,0,0);
}
@ -456,4 +454,3 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) {
float CaraFaceTracker::getBlendshapeCoefficient(int index) const {
return (index >= 0 && index < (int)_blendshapeCoefficients.size()) ? _blendshapeCoefficients[index] : 0.0f;
}

View file

@ -9,11 +9,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hi_fi_CaraFaceTracker_h
#define hi_fi_CaraFaceTracker_h
#ifndef hifi_CaraFaceTracker_h
#define hifi_CaraFaceTracker_h
#include <QUdpSocket>
#include <SimpleMovingAverage.h>
#include "FaceTracker.h"
/*!
@ -90,10 +91,10 @@ private:
//head tracking
glm::vec3 _headAngularVelocity;
//pose history
float _previousPitch;
float _previousYaw;
float _previousRoll;
//pose average
SimpleMovingAverage _pitchAverage;
SimpleMovingAverage _yawAverage;
SimpleMovingAverage _rollAverage;
// eye gaze degrees
float _eyeGazeLeftPitch;
@ -120,4 +121,4 @@ private:
int _jawOpenIndex;
};
#endif //endif hi_fi_CaraFaceTracker_h
#endif //endif hifi_CaraFaceTracker_h

View file

@ -0,0 +1,282 @@
//
// DdeFaceTracker.cpp
//
//
// Created by Clement on 8/2/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <SharedUtil.h>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QElapsedTimer>
#include "DdeFaceTracker.h"
static const QHostAddress DDE_FEATURE_POINT_SERVER_ADDR("127.0.0.1");
static const quint16 DDE_FEATURE_POINT_SERVER_PORT = 5555;
static const int NUM_EXPRESSION = 46;
static const int MIN_PACKET_SIZE = (8 + NUM_EXPRESSION) * sizeof(float) + sizeof(int);
static const int MAX_NAME_SIZE = 31;
struct Packet{
//roughly in mm
float focal_length[1];
float translation[3];
//quaternion
float rotation[4];
//blendshape coefficients ranging between -0.2 and 1.5
float expressions[NUM_EXPRESSION];
//avatar id selected on the UI
int avatar_id;
//client name, arbitrary length
char name[MAX_NAME_SIZE + 1];
};
DdeFaceTracker::DdeFaceTracker() :
_lastReceiveTimestamp(0),
_reset(false),
_leftBlinkIndex(0), // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes
_rightBlinkIndex(1),
_leftEyeOpenIndex(8),
_rightEyeOpenIndex(9),
_browDownLeftIndex(14),
_browDownRightIndex(15),
_browUpCenterIndex(16),
_browUpLeftIndex(17),
_browUpRightIndex(18),
_mouthSmileLeftIndex(28),
_mouthSmileRightIndex(29),
_jawOpenIndex(21)
{
_blendshapeCoefficients.resize(NUM_EXPRESSION);
connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError)));
connect(&_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
bindTo(DDE_FEATURE_POINT_SERVER_PORT);
}
DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 port) :
_lastReceiveTimestamp(0),
_reset(false),
_leftBlinkIndex(0), // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes
_rightBlinkIndex(1),
_leftEyeOpenIndex(8),
_rightEyeOpenIndex(9),
_browDownLeftIndex(14),
_browDownRightIndex(15),
_browUpCenterIndex(16),
_browUpLeftIndex(17),
_browUpRightIndex(18),
_mouthSmileLeftIndex(28),
_mouthSmileRightIndex(29),
_jawOpenIndex(21)
{
_blendshapeCoefficients.resize(NUM_EXPRESSION);
connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError)));
connect(&_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SIGNAL(socketStateChanged(QAbstractSocket::SocketState)));
bindTo(host, port);
}
DdeFaceTracker::~DdeFaceTracker() {
if(_udpSocket.isOpen())
_udpSocket.close();
}
void DdeFaceTracker::init() {
}
void DdeFaceTracker::reset() {
_reset = true;
}
void DdeFaceTracker::update() {
}
void DdeFaceTracker::bindTo(quint16 port) {
bindTo(QHostAddress::Any, port);
}
void DdeFaceTracker::bindTo(const QHostAddress& host, quint16 port) {
if(_udpSocket.isOpen()) {
_udpSocket.close();
}
_udpSocket.bind(host, port);
}
bool DdeFaceTracker::isActive() const {
static const int ACTIVE_TIMEOUT_USECS = 3000000; //3 secs
return (usecTimestampNow() - _lastReceiveTimestamp < ACTIVE_TIMEOUT_USECS);
}
//private slots and methods
void DdeFaceTracker::socketErrorOccurred(QAbstractSocket::SocketError socketError) {
qDebug() << "[Error] DDE Face Tracker Socket Error: " << _udpSocket.errorString();
}
void DdeFaceTracker::socketStateChanged(QAbstractSocket::SocketState socketState) {
QString state;
switch(socketState) {
case QAbstractSocket::BoundState:
state = "Bounded";
break;
case QAbstractSocket::ClosingState:
state = "Closing";
break;
case QAbstractSocket::ConnectedState:
state = "Connected";
break;
case QAbstractSocket::ConnectingState:
state = "Connecting";
break;
case QAbstractSocket::HostLookupState:
state = "Host Lookup";
break;
case QAbstractSocket::ListeningState:
state = "Listening";
break;
case QAbstractSocket::UnconnectedState:
state = "Unconnected";
break;
}
qDebug() << "[Info] DDE Face Tracker Socket: " << socketState;
}
void DdeFaceTracker::readPendingDatagrams() {
QByteArray buffer;
while (_udpSocket.hasPendingDatagrams()) {
buffer.resize(_udpSocket.pendingDatagramSize());
_udpSocket.readDatagram(buffer.data(), buffer.size());
}
decodePacket(buffer);
}
float DdeFaceTracker::getBlendshapeCoefficient(int index) const {
return (index >= 0 && index < (int)_blendshapeCoefficients.size()) ? _blendshapeCoefficients[index] : 0.0f;
}
static const float DDE_MIN_RANGE = -0.2;
static const float DDE_MAX_RANGE = 1.5;
float rescaleCoef(float ddeCoef) {
return (ddeCoef - DDE_MIN_RANGE) / (DDE_MAX_RANGE - DDE_MIN_RANGE);
}
const int MIN = 0;
const int AVG = 1;
const int MAX = 2;
const float LONG_TERM_AVERAGE = 0.999f;
void resetCoefficient(float * coefficient, float currentValue) {
coefficient[MIN] = coefficient[MAX] = coefficient[AVG] = currentValue;
}
float updateAndGetCoefficient(float * coefficient, float currentValue, bool scaleToRange = false) {
coefficient[MIN] = (currentValue < coefficient[MIN]) ? currentValue : coefficient[MIN];
coefficient[MAX] = (currentValue > coefficient[MAX]) ? currentValue : coefficient[MAX];
coefficient[AVG] = LONG_TERM_AVERAGE * coefficient[AVG] + (1.f - LONG_TERM_AVERAGE) * currentValue;
if (coefficient[MAX] > coefficient[MIN]) {
if (scaleToRange) {
return glm::clamp((currentValue - coefficient[AVG]) / (coefficient[MAX] - coefficient[MIN]), 0.f, 1.f);
} else {
return glm::clamp(currentValue - coefficient[AVG], 0.f, 1.f);
}
} else {
return 0.f;
}
}
void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
if(buffer.size() > MIN_PACKET_SIZE) {
Packet packet;
int bytesToCopy = glm::min((int)sizeof(packet), buffer.size());
memset(&packet.name, '\n', MAX_NAME_SIZE + 1);
memcpy(&packet, buffer.data(), bytesToCopy);
glm::vec3 translation;
memcpy(&translation, packet.translation, sizeof(packet.translation));
glm::quat rotation;
memcpy(&rotation, &packet.rotation, sizeof(packet.rotation));
if (_reset || (_lastReceiveTimestamp == 0)) {
memcpy(&_referenceTranslation, &translation, sizeof(glm::vec3));
memcpy(&_referenceRotation, &rotation, sizeof(glm::quat));
resetCoefficient(_rightEye, packet.expressions[0]);
resetCoefficient(_leftEye, packet.expressions[1]);
_reset = false;
}
// Compute relative translation
float LEAN_DAMPING_FACTOR = 200.0f;
translation -= _referenceTranslation;
translation /= LEAN_DAMPING_FACTOR;
translation.x *= -1;
// Compute relative rotation
rotation = glm::inverse(_referenceRotation) * rotation;
// copy values
_headTranslation = translation;
_headRotation = rotation;
if (_lastReceiveTimestamp == 0) {
// On first packet, reset coefficients
}
// Set blendshapes
float EYE_MAGNIFIER = 4.0f;
float rightEye = (updateAndGetCoefficient(_rightEye, packet.expressions[0])) * EYE_MAGNIFIER;
_blendshapeCoefficients[_rightBlinkIndex] = rightEye;
float leftEye = (updateAndGetCoefficient(_leftEye, packet.expressions[1])) * EYE_MAGNIFIER;
_blendshapeCoefficients[_leftBlinkIndex] = leftEye;
// Right eye = packet.expressions[0];
float leftBrow = 1.0f - rescaleCoef(packet.expressions[14]);
if (leftBrow < 0.5f) {
_blendshapeCoefficients[_browDownLeftIndex] = 1.0f - 2.0f * leftBrow;
_blendshapeCoefficients[_browUpLeftIndex] = 0.0f;
} else {
_blendshapeCoefficients[_browDownLeftIndex] = 0.0f;
_blendshapeCoefficients[_browUpLeftIndex] = 2.0f * (leftBrow - 0.5f);
}
float rightBrow = 1.0f - rescaleCoef(packet.expressions[15]);
if (rightBrow < 0.5f) {
_blendshapeCoefficients[_browDownRightIndex] = 1.0f - 2.0f * rightBrow;
_blendshapeCoefficients[_browUpRightIndex] = 0.0f;
} else {
_blendshapeCoefficients[_browDownRightIndex] = 0.0f;
_blendshapeCoefficients[_browUpRightIndex] = 2.0f * (rightBrow - 0.5f);
}
float JAW_OPEN_MAGNIFIER = 1.4f;
_blendshapeCoefficients[_jawOpenIndex] = rescaleCoef(packet.expressions[21]) * JAW_OPEN_MAGNIFIER;
_blendshapeCoefficients[_mouthSmileLeftIndex] = rescaleCoef(packet.expressions[24]);
_blendshapeCoefficients[_mouthSmileRightIndex] = rescaleCoef(packet.expressions[23]);
} else {
qDebug() << "[Error] DDE Face Tracker Decode Error";
}
_lastReceiveTimestamp = usecTimestampNow();
}

View file

@ -0,0 +1,93 @@
//
// DdeFaceTracker.h
//
//
// Created by Clement on 8/2/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_DdeFaceTracker_h
#define hifi_DdeFaceTracker_h
#include <QUdpSocket>
#include "FaceTracker.h"
class DdeFaceTracker : public FaceTracker {
Q_OBJECT
public:
DdeFaceTracker();
DdeFaceTracker(const QHostAddress& host, quint16 port);
~DdeFaceTracker();
//initialization
void init();
void reset();
void update();
//sockets
void bindTo(quint16 port);
void bindTo(const QHostAddress& host, quint16 port);
bool isActive() const;
float getLeftBlink() const { return getBlendshapeCoefficient(_leftBlinkIndex); }
float getRightBlink() const { return getBlendshapeCoefficient(_rightBlinkIndex); }
float getLeftEyeOpen() const { return getBlendshapeCoefficient(_leftEyeOpenIndex); }
float getRightEyeOpen() const { return getBlendshapeCoefficient(_rightEyeOpenIndex); }
float getBrowDownLeft() const { return getBlendshapeCoefficient(_browDownLeftIndex); }
float getBrowDownRight() const { return getBlendshapeCoefficient(_browDownRightIndex); }
float getBrowUpCenter() const { return getBlendshapeCoefficient(_browUpCenterIndex); }
float getBrowUpLeft() const { return getBlendshapeCoefficient(_browUpLeftIndex); }
float getBrowUpRight() const { return getBlendshapeCoefficient(_browUpRightIndex); }
float getMouthSize() const { return getBlendshapeCoefficient(_jawOpenIndex); }
float getMouthSmileLeft() const { return getBlendshapeCoefficient(_mouthSmileLeftIndex); }
float getMouthSmileRight() const { return getBlendshapeCoefficient(_mouthSmileRightIndex); }
private slots:
//sockets
void socketErrorOccurred(QAbstractSocket::SocketError socketError);
void readPendingDatagrams();
void socketStateChanged(QAbstractSocket::SocketState socketState);
private:
float getBlendshapeCoefficient(int index) const;
void decodePacket(const QByteArray& buffer);
// sockets
QUdpSocket _udpSocket;
quint64 _lastReceiveTimestamp;
bool _reset;
glm::vec3 _referenceTranslation;
glm::quat _referenceRotation;
int _leftBlinkIndex;
int _rightBlinkIndex;
int _leftEyeOpenIndex;
int _rightEyeOpenIndex;
// Brows
int _browDownLeftIndex;
int _browDownRightIndex;
int _browUpCenterIndex;
int _browUpLeftIndex;
int _browUpRightIndex;
int _mouthSmileLeftIndex;
int _mouthSmileRightIndex;
int _jawOpenIndex;
float _leftEye[3];
float _rightEye[3];
};
#endif // hifi_DdeFaceTracker_h

Some files were not shown because too many files have changed in this diff Show more