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

This commit is contained in:
samcake 2015-11-09 09:23:59 -08:00
commit 2078c753ae
38 changed files with 949 additions and 252 deletions

View file

@ -18,12 +18,19 @@ macro(COPY_DLLS_BESIDE_WINDOWS_EXECUTABLE)
@ONLY
)
if (APPLE)
set(PLUGIN_PATH "interface.app/Contents/MacOS/plugins")
else()
set(PLUGIN_PATH "plugins")
endif()
# add a post-build command to copy DLLs beside the executable
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DBUNDLE_EXECUTABLE=$<TARGET_FILE:${TARGET_NAME}>
-DBUNDLE_PLUGIN_DIR=$<TARGET_FILE_DIR:${TARGET_NAME}>/${PLUGIN_PATH}
-P ${CMAKE_CURRENT_BINARY_DIR}/FixupBundlePostBuild.cmake
)

View file

@ -41,4 +41,16 @@ function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item)
endfunction()
message(STATUS "FIXUP_LIBS for fixup_bundle called for bundle ${BUNDLE_EXECUTABLE} are @FIXUP_LIBS@")
fixup_bundle("${BUNDLE_EXECUTABLE}" "" "@FIXUP_LIBS@")
message(STATUS "Scanning for plugins from ${BUNDLE_PLUGIN_DIR}")
if (APPLE)
set(PLUGIN_EXTENSION "dylib")
elseif (WIN32)
set(PLUGIN_EXTENSION "dll")
else()
set(PLUGIN_EXTENSION "so")
endif()
file(GLOB RUNTIME_PLUGINS "${BUNDLE_PLUGIN_DIR}/*.${PLUGIN_EXTENSION}")
fixup_bundle("${BUNDLE_EXECUTABLE}" "${RUNTIME_PLUGINS}" "@FIXUP_LIBS@")

View file

@ -0,0 +1,188 @@
(function() {
this.defaultRange = 5;
this.acceleration = {
x: 0,
y: 0,
z: 0
};
this.onColor = {
red: 77,
green: 11,
blue: 111
};
this.offColor = {
red: 200,
green: 0,
blue: 0
};
var self = this;
//Default forward direction of mover object
this.forward = {
x: 0,
y: 0,
z: -1
};
this.isMoving = false;
this.velocity = {
x: 0,
y: 0,
z: 0
};
this.defaultThrust = 500;
this.maxRotMixVal = 0.01;
this.minRotMixVal = this.maxRotMixVal * 0.5;
this.minThrustPercentage = 0.2;
this.userData = {};
this.getUserData = function() {
if (this.properties.userData) {
this.userData = JSON.parse(this.properties.userData);
}
}
this.updateUserData = function() {
Entities.editEntity(this.entityId, {
userData: JSON.stringify(this.userData)
});
}
this.toggleMover = function() {
if (!this.userData.active) {
this.activate();
} else if (this.userData.active) {
this.deactivate();
}
}
this.clickReleaseOnEntity = function(entityId, mouseEvent) {
this.entityId = entityId
if (mouseEvent.isLeftButton) {
this.toggleMover();
}
}
this.activate = function() {
//activate a light at the movers position
this.properties = Entities.getEntityProperties(this.entityId);
this.getUserData();
this.userData.active = true;
this.initUserData();
var lightPos = this.properties.position;
lightPos.y += .1;
this.light = Entities.addEntity({
type: "Light",
position: lightPos,
isSpotlight: false,
dimensions: {
x: 2,
y: 2,
z: 2
},
color: this.onColor,
intensity: 10
// rotation: {x : 0, y: Math.PI/2, z: 0}
});
this.field = Overlays.addOverlay("sphere", {
position: this.properties.position,
size: this.userData.range,
solid: false,
color: {
red: 250,
green: 10,
blue: 10
},
})
//change color
Entities.editEntity(this.entityId, {
color: this.onColor,
});
}
this.initUserData = function() {
this.userData.range = this.userData.range || this.defaultRange;
this.userData.thrust = this.userData.thrust || this.defaultThrust;
this.updateUserData();
}
this.updateOverlays = function() {
if (this.field) {
Overlays.editOverlay(this.field, {
size: this.userData.range
});
}
}
this.deactivate = function() {
this.userData.active = false;
this.updateUserData();
Entities.editEntity(this.entityId, {
color: this.offColor
});
this.cleanUp();
}
this.scriptEnding = function() {
this.cleanUp();
}
this.update = function(deltaTime) {
self.properties = Entities.getEntityProperties(self.entityId);
self.getUserData();
self.updateOverlays();
if (!self.userData.active) {
return;
}
self.distance = Vec3.distance(MyAvatar.position, self.properties.position);
if (self.distance < self.userData.range) {
self.rotationMixVal = map(self.distance, 0, self.userData.range, self.maxRotMixVal, self.minRotMixVal);
//We want to extract yaw from rotated object so avatars do not pith or roll, as they will be stuck that way.
self.sanitizedRotation = Quat.fromPitchYawRollDegrees(0, Quat.safeEulerAngles(self.properties.rotation).y, 0);
self.newOrientation = Quat.mix(MyAvatar.orientation, self.sanitizedRotation, self.rotationMixVal);
MyAvatar.orientation = self.newOrientation;
self.rotatedDir = {
x: self.forward.x,
y: self.forward.y,
z: self.forward.z
};
self.rotatedDir = Vec3.multiplyQbyV(self.properties.rotation, self.rotatedDir);
self.thrust = map(self.distance, 0, self.userData.range, self.userData.thrust, self.userData.thrust * self.minThrustPercentage);
self.direction = Vec3.normalize(self.rotatedDir);
self.velocity = Vec3.multiply(self.direction, self.thrust);
MyAvatar.addThrust(Vec3.multiply(self.velocity, deltaTime));
}
}
this.preload = function(entityId) {
this.entityId = entityId;
}
this.unload = function() {
Script.update.disconnect(this.update);
this.cleanUp();
}
this.cleanUp = function() {
Entities.deleteEntity(this.light);
Overlays.deleteOverlay(this.field);
}
function map(value, min1, max1, min2, max2) {
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
}
Script.scriptEnding.connect(this.scriptEnding);
Script.update.connect(this.update);
});

View file

@ -0,0 +1,18 @@
var modelURL = "https://s3.amazonaws.com/hifi-public/eric/models/arrow.fbx";
var scriptURL = Script.resolvePath('avatarMover.js');
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation())));
var avatarMover = Entities.addEntity({
type: "Model",
modelURL: modelURL,
position: center,
userData: JSON.stringify({range: 5}),
script: scriptURL
});
function cleanup() {
Entities.deleteEntity(avatarMover);
}
Script.scriptEnding.connect(cleanup);

View file

@ -0,0 +1,10 @@
var MAPPING_NAME = "com.highfidelity.rightClickExample";
var mapping = Controller.newMapping(MAPPING_NAME);
mapping.from(Controller.Hardware.Keyboard.RightMouseClicked).to(function (value) {
print("Keyboard.RightMouseClicked");
});
Controller.enableMapping(MAPPING_NAME);
Script.scriptEnding.connect(function () {
Controller.disableMapping(MAPPING_NAME);
});

View file

@ -44,22 +44,11 @@
#include "Menu.h"
Menu* Menu::_instance = NULL;
static const char* const MENU_PROPERTY_NAME = "com.highfidelity.Menu";
Menu* Menu::getInstance() {
static QMutex menuInstanceMutex;
// lock the menu instance mutex to make sure we don't race and create two menus and crash
menuInstanceMutex.lock();
if (!_instance) {
qCDebug(interfaceapp, "First call to Menu::getInstance() - initing menu.");
_instance = new Menu();
}
menuInstanceMutex.unlock();
return _instance;
static Menu* instance = globalInstance<Menu>(MENU_PROPERTY_NAME);
return instance;
}
Menu::Menu() {

View file

@ -57,6 +57,7 @@ private:
class Menu : public QMenuBar {
Q_OBJECT
public:
Menu();
static Menu* getInstance();
void loadSettings();
@ -103,9 +104,6 @@ public slots:
void setIsOptionChecked(const QString& menuOption, bool isChecked);
private:
static Menu* _instance;
Menu();
typedef void(*settingsAction)(Settings&, QAction&);
static void loadAction(Settings& settings, QAction& action);
static void saveAction(Settings& settings, QAction& action);

View file

@ -473,18 +473,7 @@ void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) {
continue;
}
int controllerButtons = 0;
//Check for if we should toggle or drag the magnification window
if (controllerButtons & BUTTON_3) {
if (isPressed[index] == false) {
//We are now dragging the window
isPressed[index] = true;
//set the pressed time in us
pressedTime[index] = usecTimestampNow();
stateWhenPressed[index] = _magActive[index];
}
} else if (isPressed[index]) {
if (isPressed[index]) {
isPressed[index] = false;
//If the button was only pressed for < 250 ms
//then disable it.

View file

@ -300,6 +300,8 @@ void Rig::setJointAnimatinoPriority(int index, float newPriority) {
}
}
// Deprecated.
// WARNING: this is not symmetric with getJointRotation. It's historical. Use the appropriate specific variation.
void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) {
if (index != -1 && index < _jointStates.size()) {
JointState& state = _jointStates[index];
@ -350,6 +352,8 @@ bool Rig::getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const
return true;
}
// Deprecated.
// WARNING: this is not symmetric with setJointRotation. It's historical. Use the appropriate specific variation.
bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;

View file

@ -240,7 +240,8 @@ public:
Q_INVOKABLE void setHandState(char s) { _handState = s; }
Q_INVOKABLE char getHandState() const { return _handState; }
const QVector<JointData>& getJointData() const { return _jointData; }
const QVector<JointData>& getRawJointData() const { return _jointData; }
void setRawJointData(QVector<JointData> data) { _jointData = data; }
Q_INVOKABLE virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation);
Q_INVOKABLE virtual void setJointRotation(int index, const glm::quat& rotation);

View file

@ -0,0 +1,14 @@
//
// InputPluginsLogging.cpp
// libraries/input-plugins/src/input-plugins
//
// Created by Clement on 11/6/15.
// Copyright 2015 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 "InputPluginsLogging.h"
Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins")

View file

@ -0,0 +1,18 @@
//
// InputPluginsLogging.h
// libraries/input-plugins/src/input-plugins
//
// Created by Clement on 11/6/15.
// Copyright 2015 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_InputPluginsLogging_h
#define hifi_InputPluginsLogging_h
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(inputplugins)
#endif // hifi_InputPluginsLogging_h

View file

@ -9,55 +9,37 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <vector>
#include "SixenseManager.h"
#ifdef HAVE_SIXENSE
#include <sixense.h>
#endif
#include <QCoreApplication>
#include <QtCore/QSysInfo>
#include <QtGlobal>
#include <controllers/UserInputMapper.h>
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <PerfStat.h>
#include <SettingHandle.h>
#include <plugins/PluginContainer.h>
#include <PathUtils.h>
#include <NumericalConstants.h>
#include <PerfStat.h>
#include <plugins/PluginContainer.h>
#include <SettingHandle.h>
#include <UserActivityLogger.h>
#include <controllers/UserInputMapper.h>
#include "SixenseManager.h"
#ifdef HAVE_SIXENSE
#include "sixense.h"
#ifdef __APPLE__
static QLibrary* _sixenseLibrary { nullptr };
#endif
#endif
// TODO: This should not be here
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(inputplugins)
Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins")
#ifdef HAVE_SIXENSE
#include "InputPluginsLogging.h"
static const unsigned int BUTTON_0 = 1U << 0; // the skinny button between 1 and 2
static const unsigned int BUTTON_1 = 1U << 5;
static const unsigned int BUTTON_2 = 1U << 6;
static const unsigned int BUTTON_3 = 1U << 3;
static const unsigned int BUTTON_4 = 1U << 4;
static const unsigned int BUTTON_FWD = 1U << 7;
static const unsigned int BUTTON_TRIGGER = 1U << 8;
const glm::vec3 SixenseManager::DEFAULT_AVATAR_POSITION { -0.25f, -0.35f, -0.3f }; // in hydra frame
const float SixenseManager::CONTROLLER_THRESHOLD { 0.35f };
const float SixenseManager::DEFAULT_REACH_LENGTH { 1.5f };
#endif
#ifdef __APPLE__
typedef int (*SixenseBaseFunction)();
typedef int (*SixenseTakeIntFunction)(int);
#ifdef HAVE_SIXENSE
typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData*);
#endif
#endif
const QString SixenseManager::NAME = "Sixense";
const QString SixenseManager::HYDRA_ID_STRING = "Razer Hydra";
@ -66,7 +48,6 @@ const QString MENU_PARENT = "Avatar";
const QString MENU_NAME = "Sixense";
const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME;
const QString TOGGLE_SMOOTH = "Smooth Sixense Movement";
const float DEFAULT_REACH_LENGTH = 1.5f;
bool SixenseManager::isSupported() const {
#ifdef HAVE_SIXENSE
@ -84,41 +65,16 @@ bool SixenseManager::isSupported() const {
void SixenseManager::activate() {
InputPlugin::activate();
#ifdef HAVE_SIXENSE
_container->addMenu(MENU_PATH);
_container->addMenuItem(MENU_PATH, TOGGLE_SMOOTH,
[this] (bool clicked) { this->setSixenseFilter(clicked); },
[this] (bool clicked) { setSixenseFilter(clicked); },
true, true);
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
userInputMapper->registerDevice(_inputDevice);
#ifdef __APPLE__
if (!_sixenseLibrary) {
#ifdef SIXENSE_LIB_FILENAME
_sixenseLibrary = new QLibrary(SIXENSE_LIB_FILENAME);
#else
const QString SIXENSE_LIBRARY_NAME = "libsixense_x64";
QString frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "/../Frameworks/"
+ SIXENSE_LIBRARY_NAME;
_sixenseLibrary = new QLibrary(frameworkSixenseLibrary);
#endif
}
if (_sixenseLibrary->load()){
qCDebug(inputplugins) << "Loaded sixense library for hydra support -" << _sixenseLibrary->fileName();
} else {
qCDebug(inputplugins) << "Sixense library at" << _sixenseLibrary->fileName() << "failed to load."
<< "Continuing without hydra support.";
return;
}
SixenseBaseFunction sixenseInit = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseInit");
#endif
loadSettings();
sixenseInit();
#endif
@ -139,26 +95,14 @@ void SixenseManager::deactivate() {
userInputMapper->removeDevice(_inputDevice->_deviceID);
}
#ifdef __APPLE__
SixenseBaseFunction sixenseExit = (SixenseBaseFunction)_sixenseLibrary->resolve("sixenseExit");
#endif
sixenseExit();
#ifdef __APPLE__
delete _sixenseLibrary;
#endif
saveSettings();
#endif
}
void SixenseManager::setSixenseFilter(bool filter) {
#ifdef HAVE_SIXENSE
#ifdef __APPLE__
SixenseTakeIntFunction sixenseSetFilterEnabled = (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseSetFilterEnabled");
#endif
int newFilter = filter ? 1 : 0;
sixenseSetFilterEnabled(newFilter);
sixenseSetFilterEnabled(filter ? 1 : 0);
#endif
}
@ -180,13 +124,6 @@ void SixenseManager::InputDevice::update(float deltaTime, bool jointsCaptured) {
#ifdef HAVE_SIXENSE
_buttonPressedMap.clear();
#ifdef __APPLE__
SixenseBaseFunction sixenseGetNumActiveControllers =
(SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetNumActiveControllers");
#endif
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
static const float MAX_DISCONNECTED_TIME = 2.0f;
static bool disconnected { false };
static float disconnectedInterval { 0.0f };
@ -213,24 +150,11 @@ void SixenseManager::InputDevice::update(float deltaTime, bool jointsCaptured) {
// FIXME send this message once when we've positively identified hydra hardware
//UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra");
#ifdef __APPLE__
SixenseBaseFunction sixenseGetMaxControllers =
(SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetMaxControllers");
#endif
int maxControllers = sixenseGetMaxControllers();
// we only support two controllers
sixenseControllerData controllers[2];
#ifdef __APPLE__
SixenseTakeIntFunction sixenseIsControllerEnabled =
(SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseIsControllerEnabled");
SixenseTakeIntAndSixenseControllerData sixenseGetNewestData =
(SixenseTakeIntAndSixenseControllerData) _sixenseLibrary->resolve("sixenseGetNewestData");
#endif
int numActiveControllers = 0;
for (int i = 0; i < maxControllers && numActiveControllers < 2; i++) {
if (!sixenseIsControllerEnabled(i)) {
@ -293,9 +217,9 @@ void SixenseManager::InputDevice::update(float deltaTime, bool jointsCaptured) {
// (4) assume that the orb is on a flat surface (yAxis is UP)
// (5) compute the forward direction (zAxis = xAxis cross yAxis)
const float MINIMUM_ARM_REACH = 0.3f; // meters
const float MAXIMUM_NOISE_LEVEL = 0.05f; // meters
const quint64 LOCK_DURATION = USECS_PER_SECOND / 4; // time for lock to be acquired
static const float MINIMUM_ARM_REACH = 0.3f; // meters
static const float MAXIMUM_NOISE_LEVEL = 0.05f; // meters
static const quint64 LOCK_DURATION = USECS_PER_SECOND / 4; // time for lock to be acquired
void SixenseManager::InputDevice::updateCalibration(void* controllersX) {
auto controllers = reinterpret_cast<sixenseControllerData*>(controllersX);
@ -315,14 +239,12 @@ void SixenseManager::InputDevice::updateCalibration(void* controllersX) {
glm::vec3 xAxis = glm::normalize(_reachRight - _reachLeft);
glm::vec3 zAxis = glm::normalize(glm::cross(xAxis, Vectors::UNIT_Y));
xAxis = glm::normalize(glm::cross(Vectors::UNIT_Y, zAxis));
_reachLength = glm::dot(xAxis, _reachRight - _reachLeft);
_avatarRotation = glm::inverse(glm::quat_cast(glm::mat3(xAxis, Vectors::UNIT_Y, zAxis)));
const float Y_OFFSET_CALIBRATED_HANDS_TO_AVATAR = -0.3f;
_avatarPosition.y += Y_OFFSET_CALIBRATED_HANDS_TO_AVATAR;
qCDebug(inputplugins, "succeess: sixense calibration");
}
break;
default:
_calibrationState = CALIBRATION_STATE_IDLE;
qCDebug(inputplugins, "failed: sixense calibration");
@ -479,8 +401,6 @@ void SixenseManager::InputDevice::handlePoseEvent(float deltaTime, glm::vec3 pos
glm::vec3 velocity(0.0f);
glm::quat angularVelocity;
if (prevPose.isValid() && deltaTime > std::numeric_limits<float>::epsilon()) {
velocity = (position - prevPose.getTranslation()) / deltaTime;
@ -518,8 +438,6 @@ static const auto R2 = controller::A;
static const auto R3 = controller::B;
static const auto R4 = controller::Y;
using namespace controller;
controller::Input::NamedVector SixenseManager::InputDevice::getAvailableInputs() const {
using namespace controller;
static const Input::NamedVector availableInputs {
@ -563,7 +481,6 @@ void SixenseManager::saveSettings() const {
{
settings.setVec3Value(QString("avatarPosition"), _inputDevice->_avatarPosition);
settings.setQuatValue(QString("avatarRotation"), _inputDevice->_avatarRotation);
settings.setValue(QString("reachLength"), QVariant(_inputDevice->_reachLength));
}
settings.endGroup();
}
@ -575,7 +492,6 @@ void SixenseManager::loadSettings() {
{
settings.getVec3ValueIfValid(QString("avatarPosition"), _inputDevice->_avatarPosition);
settings.getQuatValueIfValid(QString("avatarRotation"), _inputDevice->_avatarRotation);
settings.getFloatValueIfValid(QString("reachLength"), _inputDevice->_reachLength);
}
settings.endGroup();
}

View file

@ -12,18 +12,6 @@
#ifndef hifi_SixenseManager_h
#define hifi_SixenseManager_h
#ifdef HAVE_SIXENSE
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include "sixense.h"
#ifdef __APPLE__
#include <QCoreApplication>
#include <qlibrary.h>
#endif
#endif
#include <SimpleMovingAverage.h>
#include <controllers/InputDevice.h>
@ -31,23 +19,10 @@
#include "InputPlugin.h"
class QLibrary;
const unsigned int BUTTON_0 = 1U << 0; // the skinny button between 1 and 2
const unsigned int BUTTON_1 = 1U << 5;
const unsigned int BUTTON_2 = 1U << 6;
const unsigned int BUTTON_3 = 1U << 3;
const unsigned int BUTTON_4 = 1U << 4;
const unsigned int BUTTON_FWD = 1U << 7;
const unsigned int BUTTON_TRIGGER = 1U << 8;
const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = false;
// Handles interaction with the Sixense SDK (e.g., Razer Hydra).
class SixenseManager : public InputPlugin {
Q_OBJECT
public:
// Plugin functions
virtual bool isSupported() const override;
virtual bool isJointController() const override { return true; }
@ -66,18 +41,18 @@ public:
public slots:
void setSixenseFilter(bool filter);
private:
private:
static const int MAX_NUM_AVERAGING_SAMPLES = 50; // At ~100 updates per seconds this means averaging over ~.5s
static const int CALIBRATION_STATE_IDLE = 0;
static const int CALIBRATION_STATE_IN_PROGRESS = 1;
static const int CALIBRATION_STATE_COMPLETE = 2;
static const glm::vec3 DEFAULT_AVATAR_POSITION;
static const float CONTROLLER_THRESHOLD;
static const float DEFAULT_REACH_LENGTH;
using Samples = std::pair< MovingAverage< glm::vec3, MAX_NUM_AVERAGING_SAMPLES>, MovingAverage< glm::vec4, MAX_NUM_AVERAGING_SAMPLES> >;
using MovingAverageMap = std::map< int, Samples >;
template<typename T>
using SampleAverage = MovingAverage<T, MAX_NUM_AVERAGING_SAMPLES>;
using Samples = std::pair<SampleAverage<glm::vec3>, SampleAverage<glm::vec4>>;
using MovingAverageMap = std::map<int, Samples>;
class InputDevice : public controller::InputDevice {
public:
@ -103,7 +78,6 @@ private:
glm::vec3 _avatarPosition { DEFAULT_AVATAR_POSITION }; // in hydra-frame
glm::quat _avatarRotation; // in hydra-frame
float _reachLength { DEFAULT_REACH_LENGTH };
float _lastDistance;
// these are measured values used to compute the calibration results
quint64 _lockExpiry;
@ -113,9 +87,6 @@ private:
glm::vec3 _reachRight;
};
bool _useSixenseFilter = true;
std::shared_ptr<InputDevice> _inputDevice { std::make_shared<InputDevice>() };
static const QString NAME;

View file

@ -0,0 +1,153 @@
//
// SixenseSupportOSX.cpp
// libraries/input-plugins/src/input-plugins
//
// Created by Clement on 10/20/15.
// Copyright 2015 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
//
// Mock implementation of sixense.h to hide dynamic linking on OS X
#if defined(__APPLE__) && defined(HAVE_SIXENSE)
#include <type_traits>
#include <sixense.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QLibrary>
#include "InputPluginsLogging.h"
#ifndef SIXENSE_LIB_FILENAME
#define SIXENSE_LIB_FILENAME QCoreApplication::applicationDirPath() + "/../Frameworks/libsixense_x64"
#endif
using Library = std::unique_ptr<QLibrary>;
static Library SIXENSE;
struct Callable {
template<typename... Args>
int operator() (Args&&... args){
return reinterpret_cast<int(*)(Args...)>(function)(std::forward<Args>(args)...);
}
QFunctionPointer function;
};
Callable resolve(const Library& library, const char* name) {
Q_ASSERT_X(library && library->isLoaded(), __FUNCTION__, "Sixense library not loaded");
auto function = library->resolve(name);
Q_ASSERT_X(function, __FUNCTION__, std::string("Could not resolve ").append(name).c_str());
return Callable { function };
}
#define FORWARD resolve(SIXENSE, __FUNCTION__)
void loadSixense() {
Q_ASSERT_X(!(SIXENSE && SIXENSE->isLoaded()), __FUNCTION__, "Sixense library already loaded");
SIXENSE.reset(new QLibrary(SIXENSE_LIB_FILENAME));
Q_CHECK_PTR(SIXENSE);
if (SIXENSE->load()){
qDebug() << "Loaded sixense library for hydra support -" << SIXENSE->fileName();
} else {
qDebug() << "Sixense library at" << SIXENSE->fileName() << "failed to load:" << SIXENSE->errorString();
qDebug() << "Continuing without hydra support.";
}
}
void unloadSixense() {
SIXENSE->unload();
}
// sixense.h wrapper for OSX dynamic linking
int sixenseInit() {
loadSixense();
return FORWARD();
}
int sixenseExit() {
auto returnCode = FORWARD();
unloadSixense();
return returnCode;
}
int sixenseGetMaxBases() {
return FORWARD();
}
int sixenseSetActiveBase(int i) {
return FORWARD(i);
}
int sixenseIsBaseConnected(int i) {
return FORWARD(i);
}
int sixenseGetMaxControllers() {
return FORWARD();
}
int sixenseIsControllerEnabled(int which) {
return FORWARD(which);
}
int sixenseGetNumActiveControllers() {
return FORWARD();
}
int sixenseGetHistorySize() {
return FORWARD();
}
int sixenseGetData(int which, int index_back, sixenseControllerData* data) {
return FORWARD(which, index_back, data);
}
int sixenseGetAllData(int index_back, sixenseAllControllerData* data) {
return FORWARD(index_back, data);
}
int sixenseGetNewestData(int which, sixenseControllerData* data) {
return FORWARD(which, data);
}
int sixenseGetAllNewestData(sixenseAllControllerData* data) {
return FORWARD(data);
}
int sixenseSetHemisphereTrackingMode(int which_controller, int state) {
return FORWARD(which_controller, state);
}
int sixenseGetHemisphereTrackingMode(int which_controller, int* state) {
return FORWARD(which_controller, state);
}
int sixenseAutoEnableHemisphereTracking(int which_controller) {
return FORWARD(which_controller);
}
int sixenseSetHighPriorityBindingEnabled(int on_or_off) {
return FORWARD(on_or_off);
}
int sixenseGetHighPriorityBindingEnabled(int* on_or_off) {
return FORWARD(on_or_off);
}
int sixenseTriggerVibration(int controller_id, int duration_100ms, int pattern_id) {
return FORWARD(controller_id, duration_100ms, pattern_id);
}
int sixenseSetFilterEnabled(int on_or_off) {
return FORWARD(on_or_off);
}
int sixenseGetFilterEnabled(int* on_or_off) {
return FORWARD(on_or_off);
}
int sixenseSetFilterParams(float near_range, float near_val, float far_range, float far_val) {
return FORWARD(near_range, near_val, far_range, far_val);
}
int sixenseGetFilterParams(float* near_range, float* near_val, float* far_range, float* far_val) {
return FORWARD(near_range, near_val, far_range, far_val);
}
int sixenseSetBaseColor(unsigned char red, unsigned char green, unsigned char blue) {
return FORWARD(red, green, blue);
}
int sixenseGetBaseColor(unsigned char* red, unsigned char* green, unsigned char* blue) {
return FORWARD(red, green, blue);
}
#endif

View file

@ -16,33 +16,34 @@
using namespace recording;
Clip::Pointer Clip::fromFile(const QString& filePath) {
return std::make_shared<FileClip>(filePath);
auto result = std::make_shared<FileClip>(filePath);
if (result->frameCount() == 0) {
return Clip::Pointer();
}
return result;
}
void Clip::toFile(Clip::Pointer clip, const QString& filePath) {
// FIXME
void Clip::toFile(const QString& filePath, Clip::Pointer clip) {
FileClip::write(filePath, clip->duplicate());
}
Clip::Pointer Clip::newClip() {
return std::make_shared<BufferClip>();
}
Clip::Pointer Clip::duplicate() {
Clip::Pointer result = std::make_shared<BufferClip>();
Locker lock(_mutex);
float currentPosition = position();
seek(0);
Frame::Pointer frame = nextFrame();
while (frame) {
result->appendFrame(frame);
result->addFrame(frame);
frame = nextFrame();
}
seek(currentPosition);
return result;
}
#if 0
Clip::Pointer Clip::fromIODevice(QIODevice * device) {
return std::make_shared<IOClip>(device);
}
void Clip::fromIODevice(Clip::Pointer clip, QIODevice * device) {
}
#endif

View file

@ -12,35 +12,44 @@
#include "Forward.h"
#include <mutex>
#include <QtCore/QObject>
class QIODevice;
namespace recording {
class Clip : public QObject {
class Clip {
public:
using Pointer = std::shared_ptr<Clip>;
Clip(QObject* parent = nullptr) : QObject(parent) {}
virtual ~Clip() {}
Pointer duplicate();
virtual float duration() const = 0;
virtual size_t frameCount() const = 0;
virtual void seek(float offset) = 0;
virtual float position() const = 0;
virtual FramePointer peekFrame() const = 0;
virtual FramePointer nextFrame() = 0;
virtual void skipFrame() = 0;
virtual void appendFrame(FramePointer) = 0;
virtual void addFrame(FramePointer) = 0;
static Pointer fromFile(const QString& filePath);
static void toFile(Pointer clip, const QString& filePath);
static void toFile(const QString& filePath, Pointer clip);
static Pointer newClip();
protected:
using Mutex = std::recursive_mutex;
using Locker = std::unique_lock<Mutex>;
virtual void reset() = 0;
mutable Mutex _mutex;
};
}

View file

@ -29,6 +29,10 @@ public:
float timeOffset { 0 };
QByteArray data;
Frame() {}
Frame(FrameType type, float timeOffset, const QByteArray& data)
: type(type), timeOffset(timeOffset), data(data) {}
static FrameType registerFrameType(const QString& frameTypeName);
static QMap<QString, FrameType> getFrameTypes();
static QMap<FrameType, QString> getFrameTypeNames();

View file

@ -0,0 +1,11 @@
//
// Created by Bradley Austin Davis 2015/10/11
// Copyright 2015 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 "Logging.h"
Q_LOGGING_CATEGORY(recordingLog, "hifi.recording")

View file

@ -0,0 +1,16 @@
//
// Created by Bradley Austin Davis 2015/10/11
// Copyright 2015 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_Controllers_Logging_h
#define hifi_Controllers_Logging_h
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(recordingLog)
#endif

View file

@ -51,7 +51,7 @@ void Recorder::recordFrame(FrameType type, QByteArray frameData) {
frame->type = type;
frame->data = frameData;
frame->timeOffset = (float)(_elapsed + _timer.elapsed()) / MSECS_PER_SECOND;
_clip->appendFrame(frame);
_clip->addFrame(frame);
}
ClipPointer Recorder::getClip() {

View file

@ -51,11 +51,15 @@ FramePointer BufferClip::nextFrame() {
return result;
}
void BufferClip::appendFrame(FramePointer newFrame) {
void BufferClip::addFrame(FramePointer newFrame) {
if (newFrame->timeOffset < 0.0f) {
throw std::runtime_error("Frames may not have negative time offsets");
}
auto currentPosition = position();
seek(newFrame->timeOffset);
{
Locker lock(_mutex);
_frames.insert(_frames.begin() + _frameIndex, newFrame);
}
seek(currentPosition);
@ -72,3 +76,15 @@ void BufferClip::reset() {
Locker lock(_mutex);
_frameIndex = 0;
}
float BufferClip::duration() const {
if (_frames.empty()) {
return 0;
}
return (*_frames.rbegin())->timeOffset;
}
size_t BufferClip::frameCount() const {
return _frames.size();
}

View file

@ -20,25 +20,23 @@ class BufferClip : public Clip {
public:
using Pointer = std::shared_ptr<BufferClip>;
BufferClip(QObject* parent = nullptr) : Clip(parent) {}
virtual ~BufferClip() {}
virtual float duration() const override;
virtual size_t frameCount() const override;
virtual void seek(float offset) override;
virtual float position() const override;
virtual FramePointer peekFrame() const override;
virtual FramePointer nextFrame() override;
virtual void skipFrame() override;
virtual void appendFrame(FramePointer) override;
virtual void addFrame(FramePointer) override;
private:
using Mutex = std::mutex;
using Locker = std::unique_lock<Mutex>;
virtual void reset() override;
std::vector<FramePointer> _frames;
mutable Mutex _mutex;
mutable size_t _frameIndex { 0 };
};

View file

@ -8,42 +8,197 @@
#include "FileClip.h"
#include "../Frame.h"
#include <algorithm>
#include <QtCore/QDebug>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <Finally.h>
#include "../Frame.h"
#include "../Logging.h"
using namespace recording;
static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(float) + sizeof(uint16_t) + 1;
static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(float) + sizeof(uint16_t);
FileClip::FileClip(const QString& fileName, QObject* parent) : Clip(parent), _file(fileName) {
auto size = _file.size();
_map = _file.map(0, size, QFile::MapPrivateOption);
static const QString FRAME_TYPE_MAP = QStringLiteral("frameTypes");
auto current = _map;
using FrameHeaderList = std::list<FileClip::FrameHeader>;
using FrameTranslationMap = QMap<FrameType, FrameType>;
FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) {
FrameTranslationMap results;
auto headerObj = doc.object();
if (headerObj.contains(FRAME_TYPE_MAP)) {
auto frameTypeObj = headerObj[FRAME_TYPE_MAP].toObject();
auto currentFrameTypes = Frame::getFrameTypes();
for (auto frameTypeName : frameTypeObj.keys()) {
qDebug() << frameTypeName;
if (!currentFrameTypes.contains(frameTypeName)) {
continue;
}
FrameType currentTypeEnum = currentFrameTypes[frameTypeName];
FrameType storedTypeEnum = static_cast<FrameType>(frameTypeObj[frameTypeName].toInt());
results[storedTypeEnum] = currentTypeEnum;
}
}
return results;
}
FrameHeaderList parseFrameHeaders(uchar* const start, const qint64& size) {
using FrameHeader = FileClip::FrameHeader;
FrameHeaderList results;
auto current = start;
auto end = current + size;
// Read all the frame headers
while (end - current < MINIMUM_FRAME_SIZE) {
// FIXME move to Frame::readHeader?
while (end - current >= MINIMUM_FRAME_SIZE) {
FrameHeader header;
memcpy(&(header.type), current, sizeof(FrameType));
current += sizeof(FrameType);
memcpy(&(header.timeOffset), current, sizeof(FrameType));
memcpy(&(header.timeOffset), current, sizeof(float));
current += sizeof(float);
memcpy(&(header.size), current, sizeof(uint16_t));
current += sizeof(uint16_t);
header.fileOffset = current - _map;
header.fileOffset = current - start;
if (end - current < header.size) {
current = end;
break;
}
_frameHeaders.push_back(header);
current += header.size;
results.push_back(header);
}
return results;
}
FileClip::FileClip(const QString& fileName) : _file(fileName) {
auto size = _file.size();
bool opened = _file.open(QIODevice::ReadOnly);
if (!opened) {
qCWarning(recordingLog) << "Unable to open file " << fileName;
return;
}
_map = _file.map(0, size, QFile::MapPrivateOption);
if (!_map) {
qCWarning(recordingLog) << "Unable to map file " << fileName;
return;
}
FrameHeaderList parsedFrameHeaders = parseFrameHeaders(_map, size);
// Verify that at least one frame exists and that the first frame is a header
if (0 == parsedFrameHeaders.size()) {
qWarning() << "No frames found, invalid file";
return;
}
// Grab the file header
{
auto fileHeaderFrameHeader = *parsedFrameHeaders.begin();
parsedFrameHeaders.pop_front();
if (fileHeaderFrameHeader.type != Frame::TYPE_HEADER) {
qWarning() << "Missing header frame, invalid file";
return;
}
QByteArray fileHeaderData((char*)_map + fileHeaderFrameHeader.fileOffset, fileHeaderFrameHeader.size);
_fileHeader = QJsonDocument::fromBinaryData(fileHeaderData);
}
// Find the type enum translation map and fix up the frame headers
{
FrameTranslationMap translationMap = parseTranslationMap(_fileHeader);
if (translationMap.empty()) {
qWarning() << "Header missing frame type map, invalid file";
return;
}
// Update the loaded headers with the frame data
_frameHeaders.reserve(parsedFrameHeaders.size());
for (auto& frameHeader : parsedFrameHeaders) {
if (!translationMap.contains(frameHeader.type)) {
continue;
}
frameHeader.type = translationMap[frameHeader.type];
_frameHeaders.push_back(frameHeader);
}
}
}
// FIXME move to frame?
bool writeFrame(QIODevice& output, const Frame& frame) {
auto written = output.write((char*)&(frame.type), sizeof(FrameType));
if (written != sizeof(FrameType)) {
return false;
}
written = output.write((char*)&(frame.timeOffset), sizeof(float));
if (written != sizeof(float)) {
return false;
}
uint16_t dataSize = frame.data.size();
written = output.write((char*)&dataSize, sizeof(uint16_t));
if (written != sizeof(uint16_t)) {
return false;
}
if (dataSize != 0) {
written = output.write(frame.data);
if (written != dataSize) {
return false;
}
}
return true;
}
bool FileClip::write(const QString& fileName, Clip::Pointer clip) {
qCDebug(recordingLog) << "Writing clip to file " << fileName;
if (0 == clip->frameCount()) {
return false;
}
QFile outputFile(fileName);
if (!outputFile.open(QFile::Truncate | QFile::WriteOnly)) {
return false;
}
Finally closer([&] { outputFile.close(); });
{
auto frameTypes = Frame::getFrameTypes();
QJsonObject frameTypeObj;
for (const auto& frameTypeName : frameTypes.keys()) {
frameTypeObj[frameTypeName] = frameTypes[frameTypeName];
}
QJsonObject rootObject;
rootObject.insert(FRAME_TYPE_MAP, frameTypeObj);
QByteArray headerFrameData = QJsonDocument(rootObject).toBinaryData();
if (!writeFrame(outputFile, Frame({ Frame::TYPE_HEADER, 0, headerFrameData }))) {
return false;
}
}
clip->seek(0);
for (auto frame = clip->nextFrame(); frame; frame = clip->nextFrame()) {
if (!writeFrame(outputFile, *frame)) {
return false;
}
}
outputFile.close();
return true;
}
FileClip::~FileClip() {
Locker lock(_mutex);
_file.unmap(_map);
_map = nullptr;
if (_file.isOpen()) {
_file.close();
}
}
void FileClip::seek(float offset) {
@ -72,7 +227,9 @@ FramePointer FileClip::readFrame(uint32_t frameIndex) const {
const FrameHeader& header = _frameHeaders[frameIndex];
result->type = header.type;
result->timeOffset = header.timeOffset;
result->data.insert(0, reinterpret_cast<char*>(_map)+header.fileOffset, header.size);
if (header.size) {
result->data.insert(0, reinterpret_cast<char*>(_map)+header.fileOffset, header.size);
}
}
return result;
}
@ -99,7 +256,18 @@ void FileClip::reset() {
_frameIndex = 0;
}
void FileClip::appendFrame(FramePointer) {
void FileClip::addFrame(FramePointer) {
throw std::runtime_error("File clips are read only");
}
float FileClip::duration() const {
if (_frameHeaders.empty()) {
return 0;
}
return _frameHeaders.rbegin()->timeOffset;
}
size_t FileClip::frameCount() const {
return _frameHeaders.size();
}

View file

@ -13,6 +13,7 @@
#include "../Clip.h"
#include <QtCore/QFile>
#include <QtCore/QJsonDocument>
#include <mutex>
@ -22,22 +23,25 @@ class FileClip : public Clip {
public:
using Pointer = std::shared_ptr<FileClip>;
FileClip(const QString& file, QObject* parent = nullptr);
FileClip(const QString& file);
virtual ~FileClip();
virtual float duration() const override;
virtual size_t frameCount() const override;
virtual void seek(float offset) override;
virtual float position() const override;
virtual FramePointer peekFrame() const override;
virtual FramePointer nextFrame() override;
virtual void appendFrame(FramePointer) override;
virtual void skipFrame() override;
virtual void addFrame(FramePointer) override;
private:
using Mutex = std::mutex;
using Locker = std::unique_lock<Mutex>;
const QJsonDocument& getHeader() {
return _fileHeader;
}
virtual void reset() override;
static bool write(const QString& filePath, Clip::Pointer clip);
struct FrameHeader {
FrameType type;
@ -46,15 +50,20 @@ private:
quint64 fileOffset;
};
using FrameHeaders = std::vector<FrameHeader>;
private:
virtual void reset() override;
using FrameHeaderVector = std::vector<FrameHeader>;
FramePointer readFrame(uint32_t frameIndex) const;
mutable Mutex _mutex;
QJsonDocument _fileHeader;
QFile _file;
uint32_t _frameIndex { 0 };
uchar* _map;
FrameHeaders _frameHeaders;
uchar* _map { nullptr };
FrameHeaderVector _frameHeaders;
};
}

View file

@ -11,8 +11,16 @@
#include "DependencyManager.h"
DependencyManager DependencyManager::_manager;
#include "SharedUtil.h"
#include "Finally.h"
static const char* const DEPENDENCY_PROPERTY_NAME = "com.highfidelity.DependencyMananger";
DependencyManager& DependencyManager::manager() {
static DependencyManager* instance = globalInstance<DependencyManager>(DEPENDENCY_PROPERTY_NAME);
return *instance;
}
QSharedPointer<Dependency>& DependencyManager::safeGet(size_t hashCode) {
return _instanceHash[hashCode];
}
}

View file

@ -62,8 +62,8 @@ public:
static void registerInheritance();
private:
static DependencyManager _manager;
static DependencyManager& manager();
template<typename T>
size_t getHashCode();
@ -75,11 +75,11 @@ private:
template <typename T>
QSharedPointer<T> DependencyManager::get() {
static size_t hashCode = _manager.getHashCode<T>();
static size_t hashCode = manager().getHashCode<T>();
static QWeakPointer<T> instance;
if (instance.isNull()) {
instance = qSharedPointerCast<T>(_manager.safeGet(hashCode));
instance = qSharedPointerCast<T>(manager().safeGet(hashCode));
if (instance.isNull()) {
qWarning() << "DependencyManager::get(): No instance available for" << typeid(T).name();
@ -91,9 +91,9 @@ QSharedPointer<T> DependencyManager::get() {
template <typename T, typename ...Args>
QSharedPointer<T> DependencyManager::set(Args&&... args) {
static size_t hashCode = _manager.getHashCode<T>();
static size_t hashCode = manager().getHashCode<T>();
QSharedPointer<Dependency>& instance = _manager.safeGet(hashCode);
QSharedPointer<Dependency>& instance = manager().safeGet(hashCode);
instance.clear(); // Clear instance before creation of new one to avoid edge cases
QSharedPointer<T> newInstance(new T(args...), &T::customDeleter);
QSharedPointer<Dependency> storedInstance = qSharedPointerCast<Dependency>(newInstance);
@ -104,9 +104,9 @@ QSharedPointer<T> DependencyManager::set(Args&&... args) {
template <typename T, typename I, typename ...Args>
QSharedPointer<T> DependencyManager::set(Args&&... args) {
static size_t hashCode = _manager.getHashCode<T>();
static size_t hashCode = manager().getHashCode<T>();
QSharedPointer<Dependency>& instance = _manager.safeGet(hashCode);
QSharedPointer<Dependency>& instance = manager().safeGet(hashCode);
instance.clear(); // Clear instance before creation of new one to avoid edge cases
QSharedPointer<T> newInstance(new I(args...), &I::customDeleter);
QSharedPointer<Dependency> storedInstance = qSharedPointerCast<Dependency>(newInstance);
@ -117,15 +117,15 @@ QSharedPointer<T> DependencyManager::set(Args&&... args) {
template <typename T>
void DependencyManager::destroy() {
static size_t hashCode = _manager.getHashCode<T>();
_manager.safeGet(hashCode).clear();
static size_t hashCode = manager().getHashCode<T>();
manager().safeGet(hashCode).clear();
}
template<typename Base, typename Derived>
void DependencyManager::registerInheritance() {
size_t baseHashCode = typeid(Base).hash_code();
size_t derivedHashCode = typeid(Derived).hash_code();
_manager._inheritanceHash.insert(baseHashCode, derivedHashCode);
manager()._inheritanceHash.insert(baseHashCode, derivedHashCode);
}
template<typename T>

View file

@ -13,6 +13,7 @@
#define hifi_SharedUtil_h
#include <memory>
#include <mutex>
#include <math.h>
#include <stdint.h>
@ -20,7 +21,36 @@
#include <unistd.h> // not on windows, not needed for mac or windows
#endif
#include <QDebug>
#include <QtCore/QDebug>
#include <QtCore/QCoreApplication>
// Provides efficient access to a named global type. By storing the value
// in the QApplication by name we can implement the singleton pattern and
// have the single instance function across DLL boundaries.
template <typename T, typename... Args>
T* globalInstance(const char* propertyName, Args&&... args) {
static std::unique_ptr<T> instancePtr;
static T* resultInstance { nullptr };
static std::mutex mutex;
if (!resultInstance) {
std::unique_lock<std::mutex> lock(mutex);
if (!resultInstance) {
auto variant = qApp->property(propertyName);
if (variant.isNull()) {
// Since we're building the object, store it in a shared_ptr so it's
// destroyed by the destructor of the static instancePtr
instancePtr = std::unique_ptr<T>(new T(std::forward<Args>(args)...));
void* voidInstance = &(*instancePtr);
variant = QVariant::fromValue(voidInstance);
qApp->setProperty(propertyName, variant);
}
void* returnedVoidInstance = variant.value<void*>();
resultInstance = static_cast<T*>(returnedVoidInstance);
}
}
return resultInstance;
}
const int BYTES_PER_COLOR = 3;
const int BYTES_PER_FLAGS = 1;

View file

@ -1,10 +1,16 @@
set(TARGET_NAME recording-test)
# This is not a testcase -- just set it up as a regular hifi project
setup_hifi_project(Test)
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
link_hifi_libraries(shared recording)
copy_dlls_beside_windows_executable()
# FIXME convert to unit tests
# Declare dependencies
macro (setup_testcase_dependencies)
# link in the shared libraries
link_hifi_libraries(shared recording)
copy_dlls_beside_windows_executable()
endmacro ()
setup_hifi_testcase()
#macro (setup_testcase_dependencies)
# # link in the shared libraries
# link_hifi_libraries(shared recording)
#
# copy_dlls_beside_windows_executable()
#endmacro ()
#setup_hifi_testcase()

View file

@ -11,6 +11,8 @@
#ifndef hifi_Constants_h
#define hifi_Constants_h
#include <QtCore/QString>
static const QString HEADER_NAME = "com.highfidelity.recording.Header";
static const QString TEST_NAME = "com.highfidelity.recording.Test";

View file

@ -8,6 +8,9 @@
#include "FrameTests.h"
#include "Constants.h"
#if 0
#include "../QTestExtensions.h"
#include <recording/Frame.h>
@ -27,3 +30,4 @@ void FrameTests::registerFrameTypeTest() {
QCOMPARE(backMap[recording::Frame::TYPE_HEADER], HEADER_NAME);
}
#endif

View file

@ -10,6 +10,7 @@
#ifndef hifi_FrameTests_h
#define hifi_FrameTests_h
#if 0
#include <QtTest/QtTest>
class FrameTests : public QObject {
@ -18,4 +19,6 @@ private slots:
void registerFrameTypeTest();
};
#endif
#endif // hifi_FrameTests_h

View file

@ -8,6 +8,9 @@
#include "RecorderTests.h"
#include "Constants.h"
#if 0
#include "../QTestExtensions.h"
#include <recording/Recorder.h>
@ -23,3 +26,4 @@ void RecorderTests::recorderTest() {
//QCOMPARE(recoreder.isRecording(), false);
}
#endif

View file

@ -10,6 +10,8 @@
#ifndef hifi_RecorderTests_h
#define hifi_RecorderTests_h
#if 0
#include <QtTest/QtTest>
class RecorderTests : public QObject {
@ -19,3 +21,5 @@ private slots:
};
#endif
#endif

View file

@ -0,0 +1,114 @@
#include <QtGlobal>
#include <QtTest/QtTest>
#include <QtCore/QTemporaryFile>
#include <QtCore/QString>
#ifdef Q_OS_WIN32
#include <Windows.h>
#endif
#include <recording/Clip.h>
#include <recording/Frame.h>
#include "Constants.h"
#define QVERIFY Q_ASSERT
using namespace recording;
FrameType TEST_FRAME_TYPE { Frame::TYPE_INVALID };
void testFrameTypeRegistration() {
TEST_FRAME_TYPE = Frame::registerFrameType(TEST_NAME);
QVERIFY(TEST_FRAME_TYPE != Frame::TYPE_INVALID);
QVERIFY(TEST_FRAME_TYPE != Frame::TYPE_HEADER);
auto forwardMap = recording::Frame::getFrameTypes();
QVERIFY(forwardMap.count(TEST_NAME) == 1);
QVERIFY(forwardMap[TEST_NAME] == TEST_FRAME_TYPE);
QVERIFY(forwardMap[HEADER_NAME] == recording::Frame::TYPE_HEADER);
auto backMap = recording::Frame::getFrameTypeNames();
QVERIFY(backMap.count(TEST_FRAME_TYPE) == 1);
QVERIFY(backMap[TEST_FRAME_TYPE] == TEST_NAME);
QVERIFY(backMap[recording::Frame::TYPE_HEADER] == HEADER_NAME);
}
void testFilePersist() {
QTemporaryFile file;
QString fileName;
if (file.open()) {
fileName = file.fileName();
file.close();
}
auto readClip = Clip::fromFile(fileName);
QVERIFY(Clip::Pointer() == readClip);
auto writeClip = Clip::newClip();
writeClip->addFrame(std::make_shared<Frame>(TEST_FRAME_TYPE, 5.0f, QByteArray()));
QVERIFY(writeClip->frameCount() == 1);
QVERIFY(writeClip->duration() == 5.0f);
Clip::toFile(fileName, writeClip);
readClip = Clip::fromFile(fileName);
QVERIFY(readClip != Clip::Pointer());
QVERIFY(readClip->frameCount() == 1);
QVERIFY(readClip->duration() == 5.0f);
readClip->seek(0);
writeClip->seek(0);
size_t count = 0;
for (auto readFrame = readClip->nextFrame(), writeFrame = writeClip->nextFrame(); readFrame && writeFrame;
readFrame = readClip->nextFrame(), writeFrame = writeClip->nextFrame(), ++count) {
QVERIFY(readFrame->type == writeFrame->type);
QVERIFY(readFrame->timeOffset == writeFrame->timeOffset);
QVERIFY(readFrame->data == writeFrame->data);
}
QVERIFY(readClip->frameCount() == count);
writeClip = Clip::newClip();
writeClip->addFrame(std::make_shared<Frame>(TEST_FRAME_TYPE, 5.0f, QByteArray()));
// Simulate an unknown frametype
writeClip->addFrame(std::make_shared<Frame>(Frame::TYPE_INVALID - 1, 10.0f, QByteArray()));
QVERIFY(writeClip->frameCount() == 2);
QVERIFY(writeClip->duration() == 10.0f);
Clip::toFile(fileName, writeClip);
// Verify that the read version of the clip ignores the unknown frame type
readClip = Clip::fromFile(fileName);
QVERIFY(readClip != Clip::Pointer());
QVERIFY(readClip->frameCount() == 1);
QVERIFY(readClip->duration() == 5.0f);
}
void testClipOrdering() {
auto writeClip = Clip::newClip();
// simulate our of order addition of frames
writeClip->addFrame(std::make_shared<Frame>(TEST_FRAME_TYPE, 10.0f, QByteArray()));
writeClip->addFrame(std::make_shared<Frame>(TEST_FRAME_TYPE, 5.0f, QByteArray()));
QVERIFY(writeClip->frameCount() == 2);
QVERIFY(writeClip->duration() == 10.0f);
QVERIFY(std::numeric_limits<float>::max() == writeClip->position());
writeClip->seek(0);
QVERIFY(5.0f == writeClip->position());
float lastFrameTimeOffset { 0 };
for (auto writeFrame = writeClip->nextFrame(); writeFrame; writeFrame = writeClip->nextFrame()) {
QVERIFY(writeClip->position() >= lastFrameTimeOffset);
}
}
#ifdef Q_OS_WIN32
void myMessageHandler(QtMsgType type, const QMessageLogContext & context, const QString & msg) {
OutputDebugStringA(msg.toLocal8Bit().toStdString().c_str());
OutputDebugStringA("\n");
}
#endif
int main(int, const char**) {
#ifdef Q_OS_WIN32
qInstallMessageHandler(myMessageHandler);
#endif
testFrameTypeRegistration();
testFilePersist();
testClipOrdering();
}

View file

@ -82,7 +82,7 @@ var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
z: 0
},
collisionsWillMove: true,
collisionsSoundURL: basketballCollisionSoundURL,
collisionSoundURL: basketballCollisionSoundURL,
ignoreForCollisions: false,
modelURL: basketballURL,
userData: JSON.stringify({

View file

@ -308,6 +308,7 @@
z: 0
},
collisionsWillMove: true,
collisionSoundURL: 'http://hifi-public.s3.amazonaws.com/sounds/basketball/basketball.wav',
ignoreForCollisions: false,
modelURL: basketballURL,
userData: JSON.stringify({
@ -1258,4 +1259,4 @@
};
// entity scripts always need to return a newly constructed object of our type
return new ResetSwitch();
});
});

View file

@ -248,7 +248,7 @@ MasterReset = function() {
},
grabbableKey: {
grabbable: false,
wantsTrigger:true
wantsTrigger: true
}
})
});
@ -289,6 +289,7 @@ MasterReset = function() {
z: 0
},
collisionsWillMove: true,
collisionSoundURL: 'http://hifi-public.s3.amazonaws.com/sounds/basketball/basketball.wav',
ignoreForCollisions: false,
modelURL: basketballURL,
userData: JSON.stringify({
@ -334,7 +335,7 @@ MasterReset = function() {
name: "Basketball Resetter",
script: basketballResetterScriptURL,
dimensions: dimensions,
visible:false,
visible: false,
userData: JSON.stringify({
resetMe: {
resetMe: true
@ -367,7 +368,7 @@ MasterReset = function() {
name: "Target Resetter",
script: targetsResetterScriptURL,
dimensions: dimensions,
visible:false,
visible: false,
userData: JSON.stringify({
resetMe: {
resetMe: true
@ -1238,4 +1239,4 @@ MasterReset = function() {
Script.scriptEnding.connect(cleanup);
}
};
};