From 4ca8a10296340ca8045380cbc7f428de277b48aa Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 26 Aug 2013 14:36:19 -0700 Subject: [PATCH 01/31] Starting on LED tracking. --- interface/src/Menu.cpp | 7 +++++++ interface/src/Menu.h | 1 + interface/src/Webcam.cpp | 5 +++-- interface/src/Webcam.h | 2 ++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index edb7b3cb67..2858794f8a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -193,6 +193,13 @@ Menu::Menu() : appInstance->getWebcam(), SLOT(setSkeletonTrackingOn(bool))); + addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::LEDTracking, + 0, + false, + appInstance->getWebcam()->getGrabber(), + SLOT(setLEDTrackingOn(bool))); + addDisabledActionAndSeparator(viewMenu, "Stats"); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Slash); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Log, Qt::CTRL | Qt::Key_L); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index e484fe5c04..5a80d5c8fe 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -176,6 +176,7 @@ namespace MenuOption { const QString ShowTrueColors = "Show TRUE Colors"; const QString SimulateLeapHand = "Simulate Leap Hand"; const QString SkeletonTracking = "Skeleton Tracking"; + const QString LEDTracking = "LED Tracking"; const QString Stars = "Stars"; const QString Stats = "Stats"; const QString TestPing = "Test Ping"; diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index 349816a4ed..4132c6f70e 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -285,8 +285,9 @@ void Webcam::setFrame(const Mat& color, int format, const Mat& depth, float midF QTimer::singleShot(qMax((int)remaining / 1000, 0), _grabber, SLOT(grabFrame())); } -FrameGrabber::FrameGrabber() : _initialized(false), _videoSendMode(FULL_FRAME_VIDEO), _depthOnly(false), _capture(0), - _searchWindow(0, 0, 0, 0), _smoothedMidFaceDepth(UNINITIALIZED_FACE_DEPTH), _colorCodec(), _depthCodec(), _frameCount(0) { +FrameGrabber::FrameGrabber() : _initialized(false), _videoSendMode(FULL_FRAME_VIDEO), + _depthOnly(false), _ledTrackingOn(false), _capture(0), _searchWindow(0, 0, 0, 0), + _smoothedMidFaceDepth(UNINITIALIZED_FACE_DEPTH), _colorCodec(), _depthCodec(), _frameCount(0) { } FrameGrabber::~FrameGrabber() { diff --git a/interface/src/Webcam.h b/interface/src/Webcam.h index 16b9339eb0..a1c6b0fcc2 100644 --- a/interface/src/Webcam.h +++ b/interface/src/Webcam.h @@ -113,6 +113,7 @@ public slots: void cycleVideoSendMode(); void setDepthOnly(bool depthOnly); + void setLEDTrackingOn(bool ledTrackingOn) { _ledTrackingOn = ledTrackingOn; } void reset(); void shutdown(); void grabFrame(); @@ -128,6 +129,7 @@ private: bool _initialized; VideoSendMode _videoSendMode; bool _depthOnly; + bool _ledTrackingOn; CvCapture* _capture; cv::CascadeClassifier _faceCascade; cv::Mat _hsvFrame; From 8d8ba532e176afe3eb0962c833bb67c631412947 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 26 Aug 2013 14:52:21 -0700 Subject: [PATCH 02/31] These initializers are in the wrong order. --- interface/src/avatar/Avatar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 1b9cc71b7f..55bd10124e 100755 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -105,8 +105,8 @@ Avatar::Avatar(Node* owningNode) : _initialized(false), _handHoldingPosition(0.0f, 0.0f, 0.0f), _maxArmLength(0.0f), - _pelvisStandingHeight(0.0f), - _moving(false) + _moving(false), + _pelvisStandingHeight(0.0f) { // give the pointer to our head to inherited _headData variable from AvatarData _headData = &_head; From 455565a9b905329167102346848aa611d8bca9db Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 26 Aug 2013 16:49:20 -0700 Subject: [PATCH 03/31] Basic blob tracking for LEDs. --- interface/src/Webcam.cpp | 64 ++++++++++++++++++++++++++++++++-------- interface/src/Webcam.h | 12 ++++++-- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index 4132c6f70e..a095aeddf4 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -30,6 +30,7 @@ using namespace xn; // register types with Qt metatype system int jointVectorMetaType = qRegisterMetaType("JointVector"); +int keyPointVectorMetaType = qRegisterMetaType("KeyPointVector"); int matMetaType = qRegisterMetaType("cv::Mat"); int rotatedRectMetaType = qRegisterMetaType("cv::RotatedRect"); @@ -140,6 +141,12 @@ void Webcam::renderPreview(int screenWidth, int screenHeight) { glVertex2f(left + facePoints[3].x * xScale, top + facePoints[3].y * yScale); glEnd(); + glColor3f(1.0f, 0.0f, 0.0f); + for (KeyPointVector::iterator it = _keyPoints.begin(); it != _keyPoints.end(); it++) { + renderCircle(glm::vec3(left + it->pt.x * xScale, top + it->pt.y * yScale, 0.0f), + it->size * 0.5f, glm::vec3(0.0f, 0.0f, 1.0f), 8); + } + const int MAX_FPS_CHARACTERS = 30; char fps[MAX_FPS_CHARACTERS]; sprintf(fps, "FPS: %d", (int)(roundf(_frameCount * 1000000.0f / (usecTimestampNow() - _startTimestamp)))); @@ -157,8 +164,8 @@ Webcam::~Webcam() { const float METERS_PER_MM = 1.0f / 1000.0f; -void Webcam::setFrame(const Mat& color, int format, const Mat& depth, float midFaceDepth, - float aspectRatio, const RotatedRect& faceRect, bool sending, const JointVector& joints) { +void Webcam::setFrame(const Mat& color, int format, const Mat& depth, float midFaceDepth, float aspectRatio, + const RotatedRect& faceRect, bool sending, const JointVector& joints, const KeyPointVector& keyPoints) { if (!_enabled) { return; // was queued before we shut down; ignore } @@ -210,6 +217,7 @@ void Webcam::setFrame(const Mat& color, int format, const Mat& depth, float midF _faceRect = faceRect; _sending = sending; _joints = _skeletonTrackingOn ? joints : JointVector(); + _keyPoints = keyPoints; _frameCount++; const int MAX_FPS = 60; @@ -285,9 +293,21 @@ void Webcam::setFrame(const Mat& color, int format, const Mat& depth, float midF QTimer::singleShot(qMax((int)remaining / 1000, 0), _grabber, SLOT(grabFrame())); } -FrameGrabber::FrameGrabber() : _initialized(false), _videoSendMode(FULL_FRAME_VIDEO), - _depthOnly(false), _ledTrackingOn(false), _capture(0), _searchWindow(0, 0, 0, 0), - _smoothedMidFaceDepth(UNINITIALIZED_FACE_DEPTH), _colorCodec(), _depthCodec(), _frameCount(0) { +static SimpleBlobDetector::Params createBlobDetectorParams() { + SimpleBlobDetector::Params params; + params.blobColor = 255; + params.filterByArea = true; + params.minArea = 5; + params.maxArea = 5000; + params.filterByCircularity = true; + params.filterByInertia = false; + params.filterByConvexity = true; + return params; +} + +FrameGrabber::FrameGrabber() : _initialized(false), _videoSendMode(FULL_FRAME_VIDEO), _depthOnly(false), _ledTrackingOn(false), + _capture(0), _searchWindow(0, 0, 0, 0), _smoothedMidFaceDepth(UNINITIALIZED_FACE_DEPTH), _colorCodec(), _depthCodec(), + _frameCount(0), _blobDetector(createBlobDetectorParams()) { } FrameGrabber::~FrameGrabber() { @@ -392,6 +412,11 @@ void FrameGrabber::setDepthOnly(bool depthOnly) { destroyCodecs(); } +void FrameGrabber::setLEDTrackingOn(bool ledTrackingOn) { + _ledTrackingOn = ledTrackingOn; + configureCapture(); +} + void FrameGrabber::reset() { _searchWindow = cv::Rect(0, 0, 0, 0); @@ -495,7 +520,7 @@ void FrameGrabber::grabFrame() { float depthBitrateMultiplier = 1.0f; Mat faceTransform; float aspectRatio; - if (_videoSendMode == FULL_FRAME_VIDEO) { + if (_ledTrackingOn || _videoSendMode == FULL_FRAME_VIDEO) { // no need to find the face if we're sending full frame video _smoothedFaceRect = RotatedRect(Point2f(color.cols / 2.0f, color.rows / 2.0f), Size2f(color.cols, color.rows), 0.0f); encodedWidth = color.cols; @@ -569,6 +594,11 @@ void FrameGrabber::grabFrame() { aspectRatio = _smoothedFaceRect.size.width / _smoothedFaceRect.size.height; } + KeyPointVector keyPoints; + if (_ledTrackingOn) { + _blobDetector.detect(color, keyPoints); + } + const ushort ELEVEN_BIT_MINIMUM = 0; const uchar EIGHT_BIT_MIDPOINT = 128; double depthOffset; @@ -617,7 +647,7 @@ void FrameGrabber::grabFrame() { _frameCount++; QByteArray payload; - if (_videoSendMode != NO_VIDEO) { + if (!_ledTrackingOn && _videoSendMode != NO_VIDEO) { // start the payload off with the aspect ratio (zero for full frame) payload.append((const char*)&aspectRatio, sizeof(float)); @@ -791,7 +821,7 @@ void FrameGrabber::grabFrame() { QMetaObject::invokeMethod(Application::getInstance()->getWebcam(), "setFrame", Q_ARG(cv::Mat, color), Q_ARG(int, format), Q_ARG(cv::Mat, _grayDepthFrame), Q_ARG(float, _smoothedMidFaceDepth), Q_ARG(float, aspectRatio), Q_ARG(cv::RotatedRect, _smoothedFaceRect), Q_ARG(bool, !payload.isEmpty()), - Q_ARG(JointVector, joints)); + Q_ARG(JointVector, joints), Q_ARG(KeyPointVector, keyPoints)); } bool FrameGrabber::init() { @@ -841,18 +871,28 @@ bool FrameGrabber::init() { cvSetCaptureProperty(_capture, CV_CAP_PROP_FRAME_WIDTH, IDEAL_FRAME_WIDTH); cvSetCaptureProperty(_capture, CV_CAP_PROP_FRAME_HEIGHT, IDEAL_FRAME_HEIGHT); + configureCapture(); + + return true; +} + +void FrameGrabber::configureCapture() { +#ifdef HAVE_OPENNI + if (_depthGenerator.IsValid()) { + return; // don't bother handling LED tracking with depth camera + } +#endif + #ifdef __APPLE__ configureCamera(0x5ac, 0x8510, false, 0.975, 0.5, 1.0, 0.5, true, 0.5); #else cvSetCaptureProperty(_capture, CV_CAP_PROP_EXPOSURE, 0.5); - cvSetCaptureProperty(_capture, CV_CAP_PROP_CONTRAST, 0.5); + cvSetCaptureProperty(_capture, CV_CAP_PROP_CONTRAST, _ledTrackingOn ? 0.99 : 0.5); cvSetCaptureProperty(_capture, CV_CAP_PROP_SATURATION, 0.5); - cvSetCaptureProperty(_capture, CV_CAP_PROP_BRIGHTNESS, 0.5); + cvSetCaptureProperty(_capture, CV_CAP_PROP_BRIGHTNESS, _ledTrackingOn ? 0.99 : 0.5); cvSetCaptureProperty(_capture, CV_CAP_PROP_HUE, 0.5); cvSetCaptureProperty(_capture, CV_CAP_PROP_GAIN, 0.5); #endif - - return true; } void FrameGrabber::updateHSVFrame(const Mat& frame, int format) { diff --git a/interface/src/Webcam.h b/interface/src/Webcam.h index a1c6b0fcc2..7c92a5c757 100644 --- a/interface/src/Webcam.h +++ b/interface/src/Webcam.h @@ -35,6 +35,7 @@ class FrameGrabber; class Joint; typedef QVector JointVector; +typedef std::vector KeyPointVector; class Webcam : public QObject { Q_OBJECT @@ -68,8 +69,8 @@ public: public slots: void setEnabled(bool enabled); - void setFrame(const cv::Mat& color, int format, const cv::Mat& depth, float midFaceDepth, - float aspectRatio, const cv::RotatedRect& faceRect, bool sending, const JointVector& joints); + void setFrame(const cv::Mat& color, int format, const cv::Mat& depth, float midFaceDepth, float aspectRatio, + const cv::RotatedRect& faceRect, bool sending, const JointVector& joints, const KeyPointVector& keyPoints); void setSkeletonTrackingOn(bool toggle) { _skeletonTrackingOn = toggle; }; private: @@ -88,6 +89,7 @@ private: cv::RotatedRect _initialFaceRect; float _initialFaceDepth; JointVector _joints; + KeyPointVector _keyPoints; uint64_t _startTimestamp; int _frameCount; @@ -113,7 +115,7 @@ public slots: void cycleVideoSendMode(); void setDepthOnly(bool depthOnly); - void setLEDTrackingOn(bool ledTrackingOn) { _ledTrackingOn = ledTrackingOn; } + void setLEDTrackingOn(bool ledTrackingOn); void reset(); void shutdown(); void grabFrame(); @@ -125,6 +127,7 @@ private: bool init(); void updateHSVFrame(const cv::Mat& frame, int format); void destroyCodecs(); + void configureCapture(); bool _initialized; VideoSendMode _videoSendMode; @@ -149,6 +152,8 @@ private: QByteArray _encodedFace; cv::RotatedRect _smoothedFaceRect; + cv::SimpleBlobDetector _blobDetector; + #ifdef HAVE_OPENNI xn::Context _xnContext; xn::DepthGenerator _depthGenerator; @@ -173,6 +178,7 @@ public: }; Q_DECLARE_METATYPE(JointVector) +Q_DECLARE_METATYPE(KeyPointVector) Q_DECLARE_METATYPE(cv::Mat) Q_DECLARE_METATYPE(cv::RotatedRect) From 799d2b487fcca4f31fbba2339ba6cc5bafd536ff Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 26 Aug 2013 16:53:12 -0700 Subject: [PATCH 04/31] Initialization order fix. --- interface/src/avatar/Avatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 55bd10124e..8b6eea4ee4 100755 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -102,10 +102,10 @@ Avatar::Avatar(Node* owningNode) : _isCollisionsOn(true), _leadingAvatar(NULL), _voxels(this), + _moving(false), _initialized(false), _handHoldingPosition(0.0f, 0.0f, 0.0f), _maxArmLength(0.0f), - _moving(false), _pelvisStandingHeight(0.0f) { // give the pointer to our head to inherited _headData variable from AvatarData From 4720eac9224e8c6f123f4d0feb42077baea30eab Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 26 Aug 2013 17:01:04 -0700 Subject: [PATCH 05/31] Use green rather than red for better blob visibility. --- interface/src/Webcam.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index a095aeddf4..cbd1ec22a9 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -141,7 +141,7 @@ void Webcam::renderPreview(int screenWidth, int screenHeight) { glVertex2f(left + facePoints[3].x * xScale, top + facePoints[3].y * yScale); glEnd(); - glColor3f(1.0f, 0.0f, 0.0f); + glColor3f(0.0f, 1.0f, 0.0f); for (KeyPointVector::iterator it = _keyPoints.begin(); it != _keyPoints.end(); it++) { renderCircle(glm::vec3(left + it->pt.x * xScale, top + it->pt.y * yScale, 0.0f), it->size * 0.5f, glm::vec3(0.0f, 0.0f, 1.0f), 8); From 18e91de7c6640279802d20566beb33ba60f2ee3b Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 27 Aug 2013 13:08:58 -0700 Subject: [PATCH 06/31] Working on the code to get the transform from the blob locations. --- interface/src/Webcam.cpp | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index cbd1ec22a9..15612c6b31 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -162,6 +162,61 @@ Webcam::~Webcam() { delete _grabber; } +static glm::vec3 createVec3(const Point2f& pt) { + return glm::vec3(pt.x, pt.y, 0.0f); +} + +/// Computes the 3D transform of the LED assembly from the image space location of the key points representing the LEDs. +/// See T.D. Alter's "3D Pose from 3 Corresponding Points under Weak-Perspective Projection" +/// (http://dspace.mit.edu/bitstream/handle/1721.1/6611/AIM-1378.pdf) and the source code to Freetrack +/// (https://camil.dyndns.org/svn/freetrack/tags/V2.2/Freetrack/Pose.pas), which uses the same algorithm. +static void computeTransformFromKeyPoints( + const KeyPointVector& keyPoints, glm::vec3& estimatedRotation, glm::vec3& estimatedPosition) { + // make sure we have at least three points + if (keyPoints.size() < 3) { + return; + } + + // bubblesort the first three points from top to bottom + glm::vec3 i0 = createVec3(keyPoints[0].pt), i1 = createVec3(keyPoints[1].pt), i2 = createVec3(keyPoints[2].pt); + if (i2.y < i1.y) { + swap(i1, i2); + } + if (i1.y < i0.y) { + swap(i0, i1); + } + + // model space LED locations and the distances between them + const glm::vec3 M0(0.0f, 0.0f, 0.0f), M1(0.0f, 0.0f, 0.0f), M2(0.0f, 0.0f, 0.0f); + const float R01 = glm::distance(M0, M1), R02 = glm::distance(M0, M2), R12 = glm::distance(M1, M2); + + // compute the distances between the image points + float d01 = glm::distance(i0, i1), d02 = glm::distance(i0, i2), d12 = glm::distance(i1, i2); + + // compute the terms of the quadratic + float a = (R01 + R02 + R12) * (-R01 + R02 + R12) * (R01 - R02 + R12) * (R01 + R02 - R12); + float b = d01 * d01 * (-R01 * R01 + R02 * R02 + R12 * R12) + d02 * d02 * (R01 * R01 - R02 * R02 + R12 * R12) + + d12 * d12 * (R01 * R01 + R02 * R02 - R12 * R12); + float c = (d01 + d02 + d12) * (-d01 + d02 + d12) * (d01 - d02 + d12) * (d01 + d02 - d12); + + // compute the scale + float s = sqrtf((b + sqrtf(b * b - a * c)) / a); + + float sigma = (d01 * d01 + d02 * d02 - d12 * d12 <= s * s * (R01 * R01 + R02 * R02 - R12 * R12)) ? 1.0f : -1.0f; + + float h1 = sqrtf(s * s * R01 * R01 - d01 * d01); + float h2 = sigma * sqrtf(s * s * R02 * R02 - d02 * d02); + + // now we can compute the 3D locations of the model points in camera-centered coordinates + glm::vec3 m0 = glm::vec3(i0.x, i0.y, 0.0f) / s; + glm::vec3 m1 = glm::vec3(i1.x, i1.y, h1) / s; + glm::vec3 m2 = glm::vec3(i2.x, i2.y, h2) / s; + + // from those and the model space locations, we can compute the transform + + +} + const float METERS_PER_MM = 1.0f / 1000.0f; void Webcam::setFrame(const Mat& color, int format, const Mat& depth, float midFaceDepth, float aspectRatio, @@ -256,6 +311,9 @@ void Webcam::setFrame(const Mat& color, int format, const Mat& depth, float midF _estimatedRotation = safeEulerAngles(_estimatedJoints[AVATAR_JOINT_HEAD_BASE].rotation); _estimatedPosition = _estimatedJoints[AVATAR_JOINT_HEAD_BASE].position; + } else if (!keyPoints.empty()) { + computeTransformFromKeyPoints(keyPoints, _estimatedRotation, _estimatedPosition); + } else { // roll is just the angle of the face rect const float ROTATION_SMOOTHING = 0.95f; @@ -596,6 +654,7 @@ void FrameGrabber::grabFrame() { KeyPointVector keyPoints; if (_ledTrackingOn) { + // find the locations of the LEDs, which should show up as blobs _blobDetector.detect(color, keyPoints); } From 4d9954b2f4c02caa1dfb2dc8ccbaf5017615725f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 27 Aug 2013 13:56:23 -0700 Subject: [PATCH 07/31] More work on getting the transform. --- interface/src/Webcam.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index 15612c6b31..bc6bc8e369 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -163,15 +163,21 @@ Webcam::~Webcam() { } static glm::vec3 createVec3(const Point2f& pt) { - return glm::vec3(pt.x, pt.y, 0.0f); + return glm::vec3(pt.x, -pt.y, 0.0f); } +static glm::mat3 createMat3(const glm::vec3& p0, const glm::vec3& p1, const glm::vec3& p2) { + glm::vec3 u = glm::normalize(p1 - p0); + glm::vec3 p02 = p2 - p0; + glm::vec3 v = glm::normalize(p02 - u * glm::dot(p02, u)); + return glm::mat3(u, v, glm::cross(u, v)); +} + /// Computes the 3D transform of the LED assembly from the image space location of the key points representing the LEDs. /// See T.D. Alter's "3D Pose from 3 Corresponding Points under Weak-Perspective Projection" /// (http://dspace.mit.edu/bitstream/handle/1721.1/6611/AIM-1378.pdf) and the source code to Freetrack /// (https://camil.dyndns.org/svn/freetrack/tags/V2.2/Freetrack/Pose.pas), which uses the same algorithm. -static void computeTransformFromKeyPoints( - const KeyPointVector& keyPoints, glm::vec3& estimatedRotation, glm::vec3& estimatedPosition) { +static void computeTransformFromKeyPoints(const KeyPointVector& keyPoints, glm::vec3& rotation, glm::vec3& position) { // make sure we have at least three points if (keyPoints.size() < 3) { return; @@ -187,7 +193,7 @@ static void computeTransformFromKeyPoints( } // model space LED locations and the distances between them - const glm::vec3 M0(0.0f, 0.0f, 0.0f), M1(0.0f, 0.0f, 0.0f), M2(0.0f, 0.0f, 0.0f); + const glm::vec3 M0(2.0f, 0.0f, 0.0f), M1(0.0f, 0.0f, 0.0f), M2(0.0f, -4.0f, 0.0f); const float R01 = glm::distance(M0, M1), R02 = glm::distance(M0, M2), R12 = glm::distance(M1, M2); // compute the distances between the image points @@ -213,8 +219,12 @@ static void computeTransformFromKeyPoints( glm::vec3 m2 = glm::vec3(i2.x, i2.y, h2) / s; // from those and the model space locations, we can compute the transform + glm::mat3 r1 = createMat3(M0, M1, M2); + glm::mat3 r2 = createMat3(m0, m1, m2); + glm::mat3 r = r2 * glm::transpose(r1); - + position = m0 - r * M0; + rotation = safeEulerAngles(glm::quat_cast(r)); } const float METERS_PER_MM = 1.0f / 1000.0f; From e5e2f7ca00cd1bfbd71ce6d50be9715db4d20b41 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 27 Aug 2013 17:00:55 -0700 Subject: [PATCH 08/31] Bump up the line width to make it easier to see the blobs. --- interface/src/Webcam.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index bc6bc8e369..e0d7de28f4 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -142,10 +142,12 @@ void Webcam::renderPreview(int screenWidth, int screenHeight) { glEnd(); glColor3f(0.0f, 1.0f, 0.0f); + glLineWidth(3.0f); for (KeyPointVector::iterator it = _keyPoints.begin(); it != _keyPoints.end(); it++) { renderCircle(glm::vec3(left + it->pt.x * xScale, top + it->pt.y * yScale, 0.0f), it->size * 0.5f, glm::vec3(0.0f, 0.0f, 1.0f), 8); } + glLineWidth(1.0f); const int MAX_FPS_CHARACTERS = 30; char fps[MAX_FPS_CHARACTERS]; From b01f6df408e1c9ba7de82acfb3f7b94eb3c330ec Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 27 Aug 2013 17:53:36 -0700 Subject: [PATCH 09/31] Tweaks for better LED tracking. --- interface/src/Webcam.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index e0d7de28f4..98d5db5a5c 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -369,7 +369,7 @@ static SimpleBlobDetector::Params createBlobDetectorParams() { params.filterByArea = true; params.minArea = 5; params.maxArea = 5000; - params.filterByCircularity = true; + params.filterByCircularity = false; params.filterByInertia = false; params.filterByConvexity = true; return params; @@ -955,12 +955,12 @@ void FrameGrabber::configureCapture() { #endif #ifdef __APPLE__ - configureCamera(0x5ac, 0x8510, false, 0.975, 0.5, 1.0, 0.5, true, 0.5); + configureCamera(0x5ac, 0x8510, false, _ledTrackingOn ? 1.0 : 0.975, 0.5, 1.0, 0.5, true, 0.5); #else cvSetCaptureProperty(_capture, CV_CAP_PROP_EXPOSURE, 0.5); - cvSetCaptureProperty(_capture, CV_CAP_PROP_CONTRAST, _ledTrackingOn ? 0.99 : 0.5); + cvSetCaptureProperty(_capture, CV_CAP_PROP_CONTRAST, _ledTrackingOn ? 1.0 : 0.5); cvSetCaptureProperty(_capture, CV_CAP_PROP_SATURATION, 0.5); - cvSetCaptureProperty(_capture, CV_CAP_PROP_BRIGHTNESS, _ledTrackingOn ? 0.99 : 0.5); + cvSetCaptureProperty(_capture, CV_CAP_PROP_BRIGHTNESS, _ledTrackingOn ? 0.0 : 0.5); cvSetCaptureProperty(_capture, CV_CAP_PROP_HUE, 0.5); cvSetCaptureProperty(_capture, CV_CAP_PROP_GAIN, 0.5); #endif From 28431b4c57c12bf83521cb7f60cd413649506efc Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 28 Aug 2013 15:02:33 -0700 Subject: [PATCH 10/31] Testing transformation with video from FreeTrack project. --- interface/CMakeLists.txt | 3 +- interface/src/Webcam.cpp | 70 +++++++++++++++++++++++++++++++++++----- interface/src/Webcam.h | 16 +++++++-- 3 files changed, 78 insertions(+), 11 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 2adc7e6643..b1333f5a6b 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -47,6 +47,7 @@ add_subdirectory(src/starfield) find_package(Qt5Core REQUIRED) find_package(Qt5Gui REQUIRED) +find_package(Qt5Multimedia REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5OpenGL REQUIRED) find_package(Qt5Svg REQUIRED) @@ -107,7 +108,7 @@ if (OPENNI_FOUND AND NOT DISABLE_OPENNI) target_link_libraries(${TARGET_NAME} ${OPENNI_LIBRARIES}) endif (OPENNI_FOUND AND NOT DISABLE_OPENNI) -qt5_use_modules(${TARGET_NAME} Core Gui Network OpenGL Svg) +qt5_use_modules(${TARGET_NAME} Core Gui Multimedia Network OpenGL Svg) # include headers for interface and InterfaceConfig. include_directories( diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index 98d5db5a5c..7837ae8ee8 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -5,6 +5,7 @@ // Created by Andrzej Kapolka on 6/17/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +#include #include #include @@ -64,6 +65,7 @@ const float UNINITIALIZED_FACE_DEPTH = 0.0f; void Webcam::reset() { _initialFaceRect = RotatedRect(); _initialFaceDepth = UNINITIALIZED_FACE_DEPTH; + _initialLEDPosition = glm::vec3(); if (_enabled) { // send a message to the grabber @@ -179,18 +181,21 @@ static glm::mat3 createMat3(const glm::vec3& p0, const glm::vec3& p1, const glm: /// See T.D. Alter's "3D Pose from 3 Corresponding Points under Weak-Perspective Projection" /// (http://dspace.mit.edu/bitstream/handle/1721.1/6611/AIM-1378.pdf) and the source code to Freetrack /// (https://camil.dyndns.org/svn/freetrack/tags/V2.2/Freetrack/Pose.pas), which uses the same algorithm. -static void computeTransformFromKeyPoints(const KeyPointVector& keyPoints, glm::vec3& rotation, glm::vec3& position) { +static float computeTransformFromKeyPoints(const KeyPointVector& keyPoints, glm::quat& rotation, glm::vec3& position) { // make sure we have at least three points if (keyPoints.size() < 3) { - return; + return 0.0f; } - // bubblesort the first three points from top to bottom + // bubblesort the first three points from top (greatest) to bottom (least) glm::vec3 i0 = createVec3(keyPoints[0].pt), i1 = createVec3(keyPoints[1].pt), i2 = createVec3(keyPoints[2].pt); - if (i2.y < i1.y) { + if (i1.y > i0.y) { + swap(i0, i1); + } + if (i2.y > i1.y) { swap(i1, i2); } - if (i1.y < i0.y) { + if (i1.y > i0.y) { swap(i0, i1); } @@ -226,7 +231,9 @@ static void computeTransformFromKeyPoints(const KeyPointVector& keyPoints, glm:: glm::mat3 r = r2 * glm::transpose(r1); position = m0 - r * M0; - rotation = safeEulerAngles(glm::quat_cast(r)); + rotation = glm::quat_cast(r); + + return s; } const float METERS_PER_MM = 1.0f / 1000.0f; @@ -324,8 +331,27 @@ void Webcam::setFrame(const Mat& color, int format, const Mat& depth, float midF _estimatedPosition = _estimatedJoints[AVATAR_JOINT_HEAD_BASE].position; } else if (!keyPoints.empty()) { - computeTransformFromKeyPoints(keyPoints, _estimatedRotation, _estimatedPosition); - + glm::quat rotation; + glm::vec3 position; + float scale = computeTransformFromKeyPoints(keyPoints, rotation, position); + if (scale > 0.0f) { + if (_initialLEDPosition == glm::vec3()) { + _initialLEDPosition = position; + _estimatedPosition = glm::vec3(); + _initialLEDRotation = rotation; + _estimatedRotation = glm::vec3(); + _initialLEDScale = scale; + + } else { + position.z += (_initialLEDScale / scale - 1.0f) * 5.0f; + + const float POSITION_SMOOTHING = 0.5f; + _estimatedPosition = glm::mix(position - _initialLEDPosition, _estimatedPosition, POSITION_SMOOTHING); + const float ROTATION_SMOOTHING = 0.5f; + _estimatedRotation = glm::mix(safeEulerAngles(rotation * glm::inverse(_initialLEDRotation)), + _estimatedRotation, ROTATION_SMOOTHING); + } + } } else { // roll is just the angle of the face rect const float ROTATION_SMOOTHING = 0.95f; @@ -386,6 +412,17 @@ FrameGrabber::~FrameGrabber() { } } +QList FrameGrabber::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const { + QList formats; + formats.append(QVideoFrame::Format_RGB24); + return formats; +} + +bool FrameGrabber::present(const QVideoFrame& frame) { + _videoFrame = frame; + return true; +} + #ifdef HAVE_OPENNI static AvatarJointID xnToAvatarJoint(XnSkeletonJoint joint) { switch (joint) { @@ -567,6 +604,7 @@ void FrameGrabber::grabFrame() { #endif if (color.empty()) { + /* IplImage* image = cvQueryFrame(_capture); if (image == 0) { // try again later @@ -580,6 +618,17 @@ void FrameGrabber::grabFrame() { return; } color = image; + */ + if (!_videoFrame.isValid()) { + // try again later + QMetaObject::invokeMethod(this, "grabFrame", Qt::QueuedConnection); + return; + } + _videoColor.create(_videoFrame.height(), _videoFrame.width(), CV_8UC3); + _videoFrame.map(QAbstractVideoBuffer::ReadOnly); + memcpy(_videoColor.ptr(), _videoFrame.bits(), _videoFrame.mappedBytes()); + _videoFrame.unmap(); + color = _videoColor; } const int ENCODED_FACE_WIDTH = 128; @@ -944,6 +993,11 @@ bool FrameGrabber::init() { configureCapture(); + QMediaPlayer* player = new QMediaPlayer(this); + player->setMedia(QUrl::fromLocalFile("/export/hifi/interface/demo.avi")); + player->setVideoOutput(this); + player->play(); + return true; } diff --git a/interface/src/Webcam.h b/interface/src/Webcam.h index 7c92a5c757..85a4aa2688 100644 --- a/interface/src/Webcam.h +++ b/interface/src/Webcam.h @@ -9,8 +9,8 @@ #ifndef __interface__Webcam__ #define __interface__Webcam__ +#include #include -#include #include #include @@ -37,6 +37,7 @@ class Joint; typedef QVector JointVector; typedef std::vector KeyPointVector; +/// Handles interaction with the webcam (including depth cameras such as the Kinect). class Webcam : public QObject { Q_OBJECT @@ -90,6 +91,10 @@ private: float _initialFaceDepth; JointVector _joints; KeyPointVector _keyPoints; + + glm::quat _initialLEDRotation; + glm::vec3 _initialLEDPosition; + float _initialLEDScale; uint64_t _startTimestamp; int _frameCount; @@ -103,7 +108,8 @@ private: bool _skeletonTrackingOn; }; -class FrameGrabber : public QObject { +/// Acquires and processes video frames in a dedicated thread. +class FrameGrabber : public QAbstractVideoSurface { Q_OBJECT public: @@ -111,6 +117,9 @@ public: FrameGrabber(); virtual ~FrameGrabber(); + virtual QList supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const; + virtual bool present(const QVideoFrame& frame); + public slots: void cycleVideoSendMode(); @@ -153,6 +162,8 @@ private: cv::RotatedRect _smoothedFaceRect; cv::SimpleBlobDetector _blobDetector; + QVideoFrame _videoFrame; + cv::Mat _videoColor; #ifdef HAVE_OPENNI xn::Context _xnContext; @@ -165,6 +176,7 @@ private: #endif }; +/// Contains the 3D transform and 2D projected position of a tracked joint. class Joint { public: From c23eb92da9121fa83d41044a51ed4339509cabd8 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 29 Aug 2013 10:45:55 -0700 Subject: [PATCH 11/31] Tweaking the transforms. --- interface/src/Webcam.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index 7837ae8ee8..05d82455bd 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -604,7 +604,7 @@ void FrameGrabber::grabFrame() { #endif if (color.empty()) { - /* + IplImage* image = cvQueryFrame(_capture); if (image == 0) { // try again later @@ -618,7 +618,8 @@ void FrameGrabber::grabFrame() { return; } color = image; - */ + + /* if (!_videoFrame.isValid()) { // try again later QMetaObject::invokeMethod(this, "grabFrame", Qt::QueuedConnection); @@ -629,6 +630,7 @@ void FrameGrabber::grabFrame() { memcpy(_videoColor.ptr(), _videoFrame.bits(), _videoFrame.mappedBytes()); _videoFrame.unmap(); color = _videoColor; + */ } const int ENCODED_FACE_WIDTH = 128; @@ -997,7 +999,7 @@ bool FrameGrabber::init() { player->setMedia(QUrl::fromLocalFile("/export/hifi/interface/demo.avi")); player->setVideoOutput(this); player->play(); - + return true; } From fe47c6c387fd7223305a608c69017583a367d9eb Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 29 Aug 2013 16:44:56 -0700 Subject: [PATCH 12/31] Thresholding, more transform tweaks. --- interface/src/Webcam.cpp | 21 ++++++++++++++++----- interface/src/Webcam.h | 1 + 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index 05d82455bd..1edfdee123 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -348,8 +348,10 @@ void Webcam::setFrame(const Mat& color, int format, const Mat& depth, float midF const float POSITION_SMOOTHING = 0.5f; _estimatedPosition = glm::mix(position - _initialLEDPosition, _estimatedPosition, POSITION_SMOOTHING); const float ROTATION_SMOOTHING = 0.5f; - _estimatedRotation = glm::mix(safeEulerAngles(rotation * glm::inverse(_initialLEDRotation)), - _estimatedRotation, ROTATION_SMOOTHING); + glm::vec3 eulers = safeEulerAngles(rotation * glm::inverse(_initialLEDRotation)); + eulers.y = -eulers.y; + eulers.z = -eulers.z; + _estimatedRotation = glm::mix(eulers, _estimatedRotation, ROTATION_SMOOTHING); } } } else { @@ -393,11 +395,11 @@ static SimpleBlobDetector::Params createBlobDetectorParams() { SimpleBlobDetector::Params params; params.blobColor = 255; params.filterByArea = true; - params.minArea = 5; + params.minArea = 4; params.maxArea = 5000; params.filterByCircularity = false; params.filterByInertia = false; - params.filterByConvexity = true; + params.filterByConvexity = false; return params; } @@ -717,8 +719,17 @@ void FrameGrabber::grabFrame() { KeyPointVector keyPoints; if (_ledTrackingOn) { + // convert to grayscale + cvtColor(color, _grayFrame, format == GL_RGB ? CV_RGB2GRAY : CV_BGR2GRAY); + + // apply threshold + threshold(_grayFrame, _grayFrame, 28.0, 255.0, THRESH_BINARY); + + // convert back so that we can see + cvtColor(_grayFrame, color, format == GL_RGB ? CV_GRAY2RGB : CV_GRAY2BGR); + // find the locations of the LEDs, which should show up as blobs - _blobDetector.detect(color, keyPoints); + _blobDetector.detect(_grayFrame, keyPoints); } const ushort ELEVEN_BIT_MINIMUM = 0; diff --git a/interface/src/Webcam.h b/interface/src/Webcam.h index 85a4aa2688..eec6987409 100644 --- a/interface/src/Webcam.h +++ b/interface/src/Webcam.h @@ -162,6 +162,7 @@ private: cv::RotatedRect _smoothedFaceRect; cv::SimpleBlobDetector _blobDetector; + cv::Mat _grayFrame; QVideoFrame _videoFrame; cv::Mat _videoColor; From 54f0a3029038f51d9edc68d62ac5b68675f9a9b5 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 30 Aug 2013 15:56:16 -0700 Subject: [PATCH 13/31] Remove video stuff used in testing. --- interface/src/Webcam.cpp | 31 ------------------------------- interface/src/Webcam.h | 8 +------- 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index 1edfdee123..62d14f4fbb 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -5,7 +5,6 @@ // Created by Andrzej Kapolka on 6/17/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. -#include #include #include @@ -414,17 +413,6 @@ FrameGrabber::~FrameGrabber() { } } -QList FrameGrabber::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const { - QList formats; - formats.append(QVideoFrame::Format_RGB24); - return formats; -} - -bool FrameGrabber::present(const QVideoFrame& frame) { - _videoFrame = frame; - return true; -} - #ifdef HAVE_OPENNI static AvatarJointID xnToAvatarJoint(XnSkeletonJoint joint) { switch (joint) { @@ -606,7 +594,6 @@ void FrameGrabber::grabFrame() { #endif if (color.empty()) { - IplImage* image = cvQueryFrame(_capture); if (image == 0) { // try again later @@ -620,19 +607,6 @@ void FrameGrabber::grabFrame() { return; } color = image; - - /* - if (!_videoFrame.isValid()) { - // try again later - QMetaObject::invokeMethod(this, "grabFrame", Qt::QueuedConnection); - return; - } - _videoColor.create(_videoFrame.height(), _videoFrame.width(), CV_8UC3); - _videoFrame.map(QAbstractVideoBuffer::ReadOnly); - memcpy(_videoColor.ptr(), _videoFrame.bits(), _videoFrame.mappedBytes()); - _videoFrame.unmap(); - color = _videoColor; - */ } const int ENCODED_FACE_WIDTH = 128; @@ -1006,11 +980,6 @@ bool FrameGrabber::init() { configureCapture(); - QMediaPlayer* player = new QMediaPlayer(this); - player->setMedia(QUrl::fromLocalFile("/export/hifi/interface/demo.avi")); - player->setVideoOutput(this); - player->play(); - return true; } diff --git a/interface/src/Webcam.h b/interface/src/Webcam.h index eec6987409..10b1644eb0 100644 --- a/interface/src/Webcam.h +++ b/interface/src/Webcam.h @@ -9,7 +9,6 @@ #ifndef __interface__Webcam__ #define __interface__Webcam__ -#include #include #include #include @@ -109,7 +108,7 @@ private: }; /// Acquires and processes video frames in a dedicated thread. -class FrameGrabber : public QAbstractVideoSurface { +class FrameGrabber : public QObject { Q_OBJECT public: @@ -117,9 +116,6 @@ public: FrameGrabber(); virtual ~FrameGrabber(); - virtual QList supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const; - virtual bool present(const QVideoFrame& frame); - public slots: void cycleVideoSendMode(); @@ -163,8 +159,6 @@ private: cv::SimpleBlobDetector _blobDetector; cv::Mat _grayFrame; - QVideoFrame _videoFrame; - cv::Mat _videoColor; #ifdef HAVE_OPENNI xn::Context _xnContext; From c57118a64de70efb4f1731cae008e5555d06ccd8 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 30 Aug 2013 15:57:32 -0700 Subject: [PATCH 14/31] De-magicked a number. --- interface/src/Webcam.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index 62d14f4fbb..679ad65c54 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -342,7 +342,8 @@ void Webcam::setFrame(const Mat& color, int format, const Mat& depth, float midF _initialLEDScale = scale; } else { - position.z += (_initialLEDScale / scale - 1.0f) * 5.0f; + const float Z_SCALE = 5.0f; + position.z += (_initialLEDScale / scale - 1.0f) * Z_SCALE; const float POSITION_SMOOTHING = 0.5f; _estimatedPosition = glm::mix(position - _initialLEDPosition, _estimatedPosition, POSITION_SMOOTHING); From 1f468a77f2a767963265959a5f35c5cb80fcceed Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 30 Aug 2013 17:19:41 -0700 Subject: [PATCH 15/31] Added Faceshift-network library. --- interface/external/faceshift/CMakeLists.txt | 11 + .../faceshift/include/fsbinarystream.h | 410 ++++++++++++++ .../faceshift/lib/UNIX/libfaceshift.a | Bin 0 -> 523750 bytes .../external/faceshift/src/fsbinarystream.cpp | 502 ++++++++++++++++++ 4 files changed, 923 insertions(+) create mode 100644 interface/external/faceshift/CMakeLists.txt create mode 100644 interface/external/faceshift/include/fsbinarystream.h create mode 100644 interface/external/faceshift/lib/UNIX/libfaceshift.a create mode 100644 interface/external/faceshift/src/fsbinarystream.cpp diff --git a/interface/external/faceshift/CMakeLists.txt b/interface/external/faceshift/CMakeLists.txt new file mode 100644 index 0000000000..d6c44c5cd8 --- /dev/null +++ b/interface/external/faceshift/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 2.8) + +set(TARGET_NAME faceshift) +project(${TARGET_NAME}) + +# grab the implemenation and header files +file(GLOB FACESHIFT_SRCS include/*.h src/*.cpp) + +include_directories(include) + +add_library(${TARGET_NAME} ${FACESHIFT_SRCS}) diff --git a/interface/external/faceshift/include/fsbinarystream.h b/interface/external/faceshift/include/fsbinarystream.h new file mode 100644 index 0000000000..8fa585397b --- /dev/null +++ b/interface/external/faceshift/include/fsbinarystream.h @@ -0,0 +1,410 @@ +#pragma once + +#ifndef FSBINARYSTREAM_H +#define FSBINARYSTREAM_H + +// ========================================================================== +// Copyright (C) 2012 faceshift AG, and/or its licensors. All rights reserved. +// +// the software is free to use and provided "as is", without warranty of any kind. +// faceshift AG does not make and hereby disclaims any express or implied +// warranties including, but not limited to, the warranties of +// non-infringement, merchantability or fitness for a particular purpose, +// or arising from a course of dealing, usage, or trade practice. in no +// event will faceshift AG and/or its licensors be liable for any lost +// revenues, data, or profits, or special, direct, indirect, or +// consequential damages, even if faceshift AG and/or its licensors has +// been advised of the possibility or probability of such damages. +// ========================================================================== + + +/** + * Define the HAVE_EIGEN preprocessor define, if you are using the Eigen library, it allows you to easily convert our tracked data from and to eigen + * See fsVector3f and fsQuaternionf for more details + **/ + +#ifdef HAVE_EIGEN +#include +#include +#endif + +#ifdef _MSC_VER +#include +#else +#include +#endif + +#include +#include +#include + +/******************************************************************************************* + * This first part of the file contains a definition of the datastructures holding the + * tracking results + ******************************************************************************************/ + +namespace fs { + +/** + * A floating point three-vector. + * + * To keep these networking classes as simple as possible, we do not implement the + * vector semantics here, use Eigen for that purpose. The class just holds three named floats, + * and you have to interpret them yourself. + **/ +struct fsVector3f { + float x,y,z; + + fsVector3f() {} +#ifdef HAVE_EIGEN + explicit fsVector3f(const Eigen::Matrix &v) : x(v[0]), y(v[1]), z(v[2]) {} + Eigen::Map< Eigen::Matrix > eigen() const { return Eigen::Map >((float*)this); } +#endif +}; + +/** + * An integer three-vector. + **/ +struct fsVector3i { + int32_t x,y,z; + + fsVector3i() {} +#ifdef HAVE_EIGEN + explicit fsVector3i(const Eigen::Matrix &v) : x(v[0]), y(v[1]), z(v[2]) {} + Eigen::Map > eigen() const { return Eigen::Map >((int32_t*)this); } +#endif +}; + +/** + * An integer four-vector. + **/ +struct fsVector4i { + int32_t x,y,z,w; + + fsVector4i() {} +#ifdef HAVE_EIGEN + explicit fsVector4i(const Eigen::Matrix &v) : x(v[0]), y(v[1]), z(v[2]), w(v[3]) {} + Eigen::Map > eigen() const { return Eigen::Map >((int32_t*)this); } +#endif +}; + +/** + * Structure holding the data of a quaternion. + * + *To keep these networking classes as simple as possible, we do not implement the + * quaternion semantics here. The class just holds four named floats, and you have to interpret them yourself. + * + * If you have Eigen you can just cast this class to an Eigen::Quaternionf and use it. + * + * The quaternion is defined as w+xi+yj+zk + **/ +struct fsQuaternionf { + float x,y,z,w; + + fsQuaternionf() {} +#ifdef HAVE_EIGEN + explicit fsQuaternionf(const Eigen::Quaternionf &q) : x(q.x()), y(q.y()), z(q.z()), w(q.w()) {} + Eigen::Quaternionf eigen() const { return Eigen::Quaternionf(w,x,y,z); } +#endif +}; + +/** + * A structure containing the data tracked for a single frame. + **/ +class fsTrackingData { + public: + //! time stamp in ms + double m_timestamp; + + //! flag whether tracking was successful [0,1] + bool m_trackingSuccessful; + + //! head pose + fsQuaternionf m_headRotation; + fsVector3f m_headTranslation; + + //! eye gaze in degrees + float m_eyeGazeLeftPitch; + float m_eyeGazeLeftYaw; + float m_eyeGazeRightPitch; + float m_eyeGazeRightYaw; + + //! blendshape coefficients + std::vector m_coeffs; + + //! marker positions - format specified in faceshift + std::vector< fsVector3f > m_markers; +}; + +/** + * A structure containing vertex information + */ +class fsVertexData { +public: + //! vertex data + std::vector m_vertices; + +#ifdef HAVE_EIGEN + Eigen::Map > eigen() { return Eigen::Map >((float*)m_vertices.data(),3,m_vertices.size()); } +#endif +}; + +/** + * A strucutre containing mesh information + */ +class fsMeshData { +public: + //! topology (quads) + std::vector m_quads; + + //! topology (triangles) + std::vector m_tris; + + //! vertex data + fsVertexData m_vertex_data; + +#ifdef HAVE_EIGEN + Eigen::Map > quads_eigen() { return Eigen::Map >((int32_t*)m_quads.data(),4,m_quads.size()); } + Eigen::Map > tris_eigen() { return Eigen::Map >((int32_t*)m_tris.data(),3,m_tris.size()); } + Eigen::Map > vertices_eigen() { return m_vertex_data.eigen(); } +#endif + +}; + +/******************************************************************************************* + * Now follows a definition of datastructures encapsulating the network messages + ******************************************************************************************/ + +/** Predeclaration of the message types available in faceshift **/ + +// Inbound +class fsMsgStartCapturing; +class fsMsgStopCapturing; +class fsMsgCalibrateNeutral; +class fsMsgSendMarkerNames; +class fsMsgSendBlendshapeNames; +class fsMsgSendRig; + +// Outbound +class fsMsgTrackingState; +class fsMsgMarkerNames; +class fsMsgBlendshapeNames; +class fsMsgRig; + +/** + * Base class of all message that faceshift is sending. + * A class can be queried for its type, using the id() function for use in a switch statement, or by using a dynamic_cast. + **/ +class fsMsg { +public: + virtual ~fsMsg() {} + + enum MessageType { + // Messages to control faceshift via the network + // These are sent from the client to faceshift + MSG_IN_START_TRACKING = 44344, + MSG_IN_STOP_TRACKING = 44444, + MSG_IN_CALIBRATE_NEUTRAL = 44544, + MSG_IN_SEND_MARKER_NAMES = 44644, + MSG_IN_SEND_BLENDSHAPE_NAMES = 44744, + MSG_IN_SEND_RIG = 44844, + MSG_IN_HEADPOSE_RELATIVE = 44944, + MSG_IN_HEADPOSE_ABSOLUTE = 44945, + + // Messages containing tracking information + // These are sent form faceshift to the client application + MSG_OUT_TRACKING_STATE = 33433, + MSG_OUT_MARKER_NAMES = 33533, + MSG_OUT_BLENDSHAPE_NAMES = 33633, + MSG_OUT_RIG = 33733 + }; + + virtual MessageType id() const = 0; +}; +typedef std::tr1::shared_ptr fsMsgPtr; + + +/************* + * Inbound + ***********/ +class fsMsgStartCapturing : public fsMsg { +public: + virtual ~fsMsgStartCapturing() {} + virtual MessageType id() const { return MSG_IN_START_TRACKING; } +}; +class fsMsgStopCapturing : public fsMsg { +public: + virtual ~fsMsgStopCapturing() {} + virtual MessageType id() const { return MSG_IN_STOP_TRACKING; } +}; +class fsMsgCalibrateNeutral : public fsMsg { +public: + virtual ~fsMsgCalibrateNeutral() {} + virtual MessageType id() const { return MSG_IN_CALIBRATE_NEUTRAL; } +}; +class fsMsgSendMarkerNames : public fsMsg { +public: + virtual ~fsMsgSendMarkerNames() {} + virtual MessageType id() const { return MSG_IN_SEND_MARKER_NAMES; } +}; +class fsMsgSendBlendshapeNames : public fsMsg { +public: + virtual ~fsMsgSendBlendshapeNames() {} + virtual MessageType id() const { return MSG_IN_SEND_BLENDSHAPE_NAMES; } +}; +class fsMsgSendRig : public fsMsg { +public: + virtual ~fsMsgSendRig() {} + virtual MessageType id() const { return MSG_IN_SEND_RIG; } +}; +class fsMsgHeadPoseRelative : public fsMsg { +public: + virtual ~fsMsgHeadPoseRelative() {} + virtual MessageType id() const { return MSG_IN_HEADPOSE_RELATIVE; } +}; +class fsMsgHeadPoseAbsolute : public fsMsg { +public: + virtual ~fsMsgHeadPoseAbsolute() {} + virtual MessageType id() const { return MSG_IN_HEADPOSE_ABSOLUTE; } +}; + +/************* + * Outbound + ***********/ +class fsMsgTrackingState : public fsMsg { +public: + virtual ~fsMsgTrackingState() {} + + /* */ fsTrackingData & tracking_data() /* */ { return m_tracking_data; } + const fsTrackingData & tracking_data() const { return m_tracking_data; } + + virtual MessageType id() const { return MSG_OUT_TRACKING_STATE; } + +private: + fsTrackingData m_tracking_data; +}; +class fsMsgMarkerNames : public fsMsg { +public: + virtual ~fsMsgMarkerNames() {} + + /* */ std::vector & marker_names() /* */ { return m_marker_names; } + const std::vector & marker_names() const { return m_marker_names; } + + virtual MessageType id() const { return MSG_OUT_MARKER_NAMES; } +private: + std::vector m_marker_names; +}; +class fsMsgBlendshapeNames : public fsMsg { +public: + virtual ~fsMsgBlendshapeNames() {} + + /* */ std::vector & blendshape_names() /* */ { return m_blendshape_names; } + const std::vector & blendshape_names() const { return m_blendshape_names; } + + virtual MessageType id() const { return MSG_OUT_BLENDSHAPE_NAMES; } +private: + std::vector m_blendshape_names; +}; +class fsMsgRig : public fsMsg { +public: + virtual ~fsMsgRig() {} + + virtual MessageType id() const { return MSG_OUT_RIG; } + + /* */ fsMeshData & mesh() /* */ { return m_mesh; } + const fsMeshData & mesh() const { return m_mesh; } + + /* */ std::vector & blendshape_names() /* */ { return m_blendshape_names; } + const std::vector & blendshape_names() const { return m_blendshape_names; } + + /* */ std::vector & blendshapes() /* */ { return m_blendshapes; } + const std::vector & blendshapes() const { return m_blendshapes; } + +private: + //! neutral mesh + fsMeshData m_mesh; + //! blendshape names + std::vector m_blendshape_names; + //! blendshapes + std::vector m_blendshapes; +}; +class fsMsgSignal : public fsMsg { + MessageType m_id; +public: + explicit fsMsgSignal(MessageType id) : m_id(id) {} + virtual ~fsMsgSignal() {} + virtual MessageType id() const { return m_id; } +}; + +/** + * Class to parse a faceshift data stream, and to create message to write into such a stream + * + * This needs to be connected with your networking methods by calling + * + * void received(int, const char *); + * + * whenever new data is available. After adding received data to the parser you can parse faceshift messages using the + * + * std::tr1::shared_ptr get_message(); + * + * to get the next message, if a full block of data has been received. This should be iterated until no more messages are in the buffer. + * + * You can also use this to encode messages to send back to faceshift. This works by calling the + * + * void encode_message(std::string &msg_out, const fsMsg &msg); + * + * methods (actually the specializations existing for each of our message types). This will encode the message into a + * binary string in msg_out. You then only need to push the resulting string over the network to faceshift. + * + * This class does not handle differences in endianness or other strange things that can happen when pushing data over the network. + * Should you have to adapt this to such a system, then it should be possible to do this by changing only the write_... and read_... + * functions in the accompanying cpp file, but so far there was no need for it. + **/ +class fsBinaryStream { +public: + fsBinaryStream(); + + /** + * Use to push data into the parser. Typically called inside of your network receiver routine + **/ + void received(long int, const char *); + /** + * After pushing data, you can try to extract messages from the stream. Process messages until a null pointer is returned. + **/ + fsMsgPtr get_message(); + /** + * When an invalid message is received, the valid field is set to false. No attempt is made to recover from the problem, you will have to disconnect. + **/ + bool valid() const { return m_valid; } + void clear() { m_start = 0; m_end = 0; m_valid=true; } + + // Inbound + static void encode_message(std::string &msg_out, const fsMsgTrackingState &msg); + static void encode_message(std::string &msg_out, const fsMsgStartCapturing &msg); + static void encode_message(std::string &msg_out, const fsMsgStopCapturing &msg); + static void encode_message(std::string &msg_out, const fsMsgCalibrateNeutral &msg); + static void encode_message(std::string &msg_out, const fsMsgSendMarkerNames &msg); + static void encode_message(std::string &msg_out, const fsMsgSendBlendshapeNames &msg); + static void encode_message(std::string &msg_out, const fsMsgSendRig &msg); + static void encode_message(std::string &msg_out, const fsMsgHeadPoseRelative &msg); + static void encode_message(std::string &msg_out, const fsMsgHeadPoseAbsolute &msg); + + // Outbound + static void encode_message(std::string &msg_out, const fsTrackingData &msg); + static void encode_message(std::string &msg_out, const fsMsgMarkerNames &msg); + static void encode_message(std::string &msg_out, const fsMsgBlendshapeNames &msg); + static void encode_message(std::string &msg_out, const fsMsgRig &msg); + static void encode_message(std::string &msg_out, const fsMsgSignal &msg); // Generic Signal + +private: + std::string m_buffer; + long int m_start; + long int m_end; + bool m_valid; + +}; + +} + + +#endif // FSBINARYSTREAM_H diff --git a/interface/external/faceshift/lib/UNIX/libfaceshift.a b/interface/external/faceshift/lib/UNIX/libfaceshift.a new file mode 100644 index 0000000000000000000000000000000000000000..4d53de7ab5602a22650db8fb82253c60028eb323 GIT binary patch literal 523750 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 NKk +#include +#include + +#define FSNETWORKVERSION 1 + +#ifdef FS_INTERNAL +#include +#else +#define LOG_RELEASE_ERROR(...) { printf("ERROR: %20s:%6d", __FILE__, __LINE__); printf(__VA_ARGS__); } +#define LOG_RELEASE_WARNING(...) { printf("WARNING: %20s:%6d", __FILE__, __LINE__); printf(__VA_ARGS__); } +#define LOG_RELEASE_INFO(...) { printf("INFO: %20s:%6d", __FILE__, __LINE__); printf(__VA_ARGS__); } +#endif + + +namespace fs { + +// Ids of the submessages for the tracking state +enum BlockId { + BLOCKID_INFO = 101, + BLOCKID_POSE = 102, + BLOCKID_BLENDSHAPES = 103, + BLOCKID_EYES = 104, + BLOCKID_MARKERS = 105 +}; + + +typedef long int Size; + +struct BlockHeader { + uint16_t id; + uint16_t version; + uint32_t size; + BlockHeader(uint16_t _id=0, + uint32_t _size=0, + uint16_t _version=FSNETWORKVERSION + ) : id(_id), version(_version), size(_size) {} +}; + +// Interprets the data at the position start in buffer as a T and increments start by sizeof(T) +// It should be sufficient to change/overload this function when you are on a wierd endian system +template bool read_pod(T &value, const std::string &buffer, Size &start) { + if(start+sizeof(T) > buffer.size()) return false; + value = *(const T*)(&buffer[start]); + start += sizeof(T); + return true; +} +bool read_pod(std::string &value, const std::string &buffer, Size &start) { + uint16_t len = 0; + if(!read_pod(len, buffer, start)) return false; + if(start+len>Size(buffer.size())) return false; // check whether we have enough data available + value.resize(len); + memcpy(&(value[0]), &buffer[start], len); + start+=len; + return true; +} +template bool read_vector(std::vector & values, const std::string & buffer, Size & start) { + uint32_t len = 0; + if( !read_pod(len, buffer, start)) return false; + if( start+len*sizeof(T) > buffer.size() ) return false; + values.resize(len); + for(uint32_t i = 0; i < len; ++i) { + read_pod(values[i],buffer,start); + } + return true; +} +template bool read_small_vector(std::vector & values, const std::string & buffer, Size & start) { + uint16_t len = 0; + if( !read_pod(len, buffer, start)) return false; + if( start+len*sizeof(T) > buffer.size() ) return false; + values.resize(len); + bool success = true; + for(uint16_t i = 0; i < len; ++i) { + success &= read_pod(values[i],buffer,start); + } + return success; +} + +// Adds the bitpattern of the data to the end of the buffer. +// It should be sufficient to change/overload this function when you are on a wierd endian system +template +void write_pod(std::string &buffer, const T &value) { + Size start = buffer.size(); + buffer.resize(start + sizeof(T)); + *(T*)(&buffer[start]) = value; + start += sizeof(T); +} +// special write function for strings +void write_pod(std::string &buffer, const std::string &value) { + uint16_t len = uint16_t(value.size()); write_pod(buffer, len); + buffer.append(value); +} +template void write_vector(std::string & buffer, const std::vector & values) { + uint32_t len = values.size(); + write_pod(buffer,len); + for(uint32_t i = 0; i < len; ++i) + write_pod(buffer,values[i]); +} +template void write_small_vector(std::string & buffer, const std::vector & values) { + uint16_t len = values.size(); + write_pod(buffer,len); + for(uint16_t i = 0; i < len; ++i) + write_pod(buffer,values[i]); +} +void update_msg_size(std::string &buffer, Size start) { + *(uint32_t*)(&buffer[start+4]) = buffer.size() - sizeof(BlockHeader) - start; +} +void update_msg_size(std::string &buffer) { + *(uint32_t*)(&buffer[4]) = buffer.size() - sizeof(BlockHeader); +} + +static void skipHeader( Size &start) { + start += sizeof(BlockHeader); +} + +//! returns whether @param data contains enough data to read the block header +static bool headerAvailable(BlockHeader &header, const std::string &buffer, Size &start, const Size &end) { + if (end-start >= Size(sizeof(BlockHeader))) { + header = *(BlockHeader*)(&buffer[start]); + return true; + } else { + return false; + } +} + +//! returns whether @param data contains data for a full block +static bool blockAvailable(const std::string &buffer, Size &start, const Size &end) { + BlockHeader header; + if (!headerAvailable(header, buffer, start, end)) return false; + return end-start >= Size(sizeof(header)+header.size); +} + +fsBinaryStream::fsBinaryStream() : m_buffer(), m_start(0), m_end(0), m_valid(true) { m_buffer.resize(64*1024); } // Use a 64kb buffer by default + +void fsBinaryStream::received(long int sz, const char *data) { + + long int new_end = m_end + sz; + if (new_end > Size(m_buffer.size()) && m_start>0) { + // If newly received block is too large to fit into the buffer, but we already have processed data from the start of the buffer, then + // move memory to the front of the buffer + // The buffer only grows, such that it is always large enough to contain the largest message seen so far. + if (m_end>m_start) memmove(&m_buffer[0], &m_buffer[0] + m_start, m_end - m_start); + m_end = m_end - m_start; + m_start = 0; + new_end = m_end + sz; + } + + if (new_end > Size(m_buffer.size())) m_buffer.resize(1.5*new_end); + + memcpy(&m_buffer[0] + m_end, data, sz); + m_end += sz; + +} + +static bool decodeInfo(fsTrackingData & _trackingData, const std::string &buffer, Size &start) { + bool success = true; + success &= read_pod(_trackingData.m_timestamp, buffer, start); + unsigned char tracking_successfull = 0; + success &= read_pod( tracking_successfull, buffer, start ); + _trackingData.m_trackingSuccessful = bool(tracking_successfull); + return success; +} + +static bool decodePose(fsTrackingData & _trackingData, const std::string &buffer, Size &start) { + bool success = true; + success &= read_pod(_trackingData.m_headRotation.x, buffer, start); + success &= read_pod(_trackingData.m_headRotation.y, buffer, start); + success &= read_pod(_trackingData.m_headRotation.z, buffer, start); + success &= read_pod(_trackingData.m_headRotation.w, buffer, start); + success &= read_pod(_trackingData.m_headTranslation.x, buffer, start); + success &= read_pod(_trackingData.m_headTranslation.y, buffer, start); + success &= read_pod(_trackingData.m_headTranslation.z, buffer, start); + return success; +} + +static bool decodeBlendshapes(fsTrackingData & _trackingData, const std::string &buffer, Size &start) { + return read_vector(_trackingData.m_coeffs, buffer, start); +} + +static bool decodeEyeGaze(fsTrackingData & _trackingData, const std::string &buffer, Size &start) { + bool success = true; + success &= read_pod(_trackingData.m_eyeGazeLeftPitch , buffer, start); + success &= read_pod(_trackingData.m_eyeGazeLeftYaw , buffer, start); + success &= read_pod(_trackingData.m_eyeGazeRightPitch, buffer, start); + success &= read_pod(_trackingData.m_eyeGazeRightYaw , buffer, start); + return success; +} + +static bool decodeMarkers(fsTrackingData & _trackingData, const std::string &buffer, Size &start) { + return read_small_vector( _trackingData.m_markers, buffer, start ); +} + +static bool decodeMarkerNames(fsMsgMarkerNames &_msg, const std::string &buffer, Size &start) { + return read_small_vector(_msg.marker_names(), buffer, start); +} +static bool decodeBlendshapeNames(fsMsgBlendshapeNames &_msg, const std::string &buffer, Size &start) { + return read_small_vector(_msg.blendshape_names(), buffer, start); +} +static bool decodeRig(fsMsgRig &_msg, const std::string &buffer, Size &start) { + bool success = true; + success &= read_vector(_msg.mesh().m_quads,buffer,start); // read quads + success &= read_vector(_msg.mesh().m_tris,buffer,start); // read triangles + success &= read_vector(_msg.mesh().m_vertex_data.m_vertices,buffer,start);// read neutral vertices + success &= read_small_vector(_msg.blendshape_names(),buffer,start); // read names + uint16_t bsize = 0; + success &= read_pod(bsize,buffer,start); + _msg.blendshapes().resize(bsize); + for(uint16_t i = 0;i < bsize; i++) + success &= read_vector(_msg.blendshapes()[i].m_vertices,buffer,start); // read blendshapes + return success; +} + +bool is_valid_msg(int id) { + switch(id) { + case fsMsg::MSG_IN_START_TRACKING : + case fsMsg::MSG_IN_STOP_TRACKING : + case fsMsg::MSG_IN_CALIBRATE_NEUTRAL : + case fsMsg::MSG_IN_SEND_MARKER_NAMES : + case fsMsg::MSG_IN_SEND_BLENDSHAPE_NAMES: + case fsMsg::MSG_IN_SEND_RIG : + case fsMsg::MSG_IN_HEADPOSE_RELATIVE : + case fsMsg::MSG_IN_HEADPOSE_ABSOLUTE : + case fsMsg::MSG_OUT_TRACKING_STATE : + case fsMsg::MSG_OUT_MARKER_NAMES : + case fsMsg::MSG_OUT_BLENDSHAPE_NAMES : + case fsMsg::MSG_OUT_RIG : return true; + default: + LOG_RELEASE_ERROR("Invalid Message ID %d", id); + return false; + } +} + +fsMsgPtr fsBinaryStream::get_message() { + BlockHeader super_block; + if( !headerAvailable(super_block, m_buffer, m_start, m_end) ) return fsMsgPtr(); + if (!is_valid_msg(super_block.id)) { LOG_RELEASE_ERROR("Invalid superblock id"); m_valid = false; return fsMsgPtr(); } + if( !blockAvailable( m_buffer, m_start, m_end) ) return fsMsgPtr(); + skipHeader(m_start); + long super_block_data_start = m_start; + switch (super_block.id) { + case fsMsg::MSG_IN_START_TRACKING: { + if (super_block.size > 0) { LOG_RELEASE_ERROR("Expected Size to be 0, not %d", super_block.size); m_valid = false; return fsMsgPtr(); } + return fsMsgPtr(new fsMsgStartCapturing() ); + }; break; + case fsMsg::MSG_IN_STOP_TRACKING: { + if (super_block.size > 0) { LOG_RELEASE_ERROR("Expected Size to be 0, not %d", super_block.size); m_valid = false; return fsMsgPtr(); } + return fsMsgPtr(new fsMsgStopCapturing() ); + }; break; + case fsMsg::MSG_IN_CALIBRATE_NEUTRAL: { + if (super_block.size > 0) { LOG_RELEASE_ERROR("Expected Size to be 0, not %d", super_block.size); m_valid = false; return fsMsgPtr(); } + return fsMsgPtr(new fsMsgCalibrateNeutral() ); + }; break; + case fsMsg::MSG_IN_SEND_MARKER_NAMES: { + if (super_block.size > 0) { LOG_RELEASE_ERROR("Expected Size to be 0, not %d", super_block.size); m_valid = false; return fsMsgPtr(); } + return fsMsgPtr(new fsMsgSendMarkerNames() ); + }; break; + case fsMsg::MSG_IN_SEND_BLENDSHAPE_NAMES: { + if (super_block.size > 0) { LOG_RELEASE_ERROR("Expected Size to be 0, not %d", super_block.size); m_valid = false; return fsMsgPtr(); } + return fsMsgPtr(new fsMsgSendBlendshapeNames() ); + }; break; + case fsMsg::MSG_IN_SEND_RIG: { + if (super_block.size > 0) { LOG_RELEASE_ERROR("Expected Size to be 0, not %d", super_block.size); m_valid = false; return fsMsgPtr(); } + return fsMsgPtr(new fsMsgSendRig() ); + }; break; + case fsMsg::MSG_IN_HEADPOSE_RELATIVE: { + if (super_block.size > 0) { LOG_RELEASE_ERROR("Expected Size to be 0, not %d", super_block.size); m_valid = false; return fsMsgPtr(); } + return fsMsgPtr(new fsMsgHeadPoseRelative() ); + }; break; + case fsMsg::MSG_IN_HEADPOSE_ABSOLUTE: { + if (super_block.size > 0) { LOG_RELEASE_ERROR("Expected Size to be 0, not %d", super_block.size); m_valid = false; return fsMsgPtr(); } + return fsMsgPtr(new fsMsgHeadPoseAbsolute() ); + }; break; + case fsMsg::MSG_OUT_MARKER_NAMES: { + std::tr1::shared_ptr< fsMsgMarkerNames > msg(new fsMsgMarkerNames()); + if( !decodeMarkerNames(*msg, m_buffer, m_start )) { LOG_RELEASE_ERROR("Could not decode marker names"); m_valid = false; return fsMsgPtr(); } + uint64_t actual_size = m_start-super_block_data_start; + if( actual_size != super_block.size ) { LOG_RELEASE_ERROR("Block was promised to be of size %d, not %d", super_block.size, actual_size); m_valid = false; return fsMsgPtr(); } + return msg; + }; break; + case fsMsg::MSG_OUT_BLENDSHAPE_NAMES: { + std::tr1::shared_ptr< fsMsgBlendshapeNames > msg(new fsMsgBlendshapeNames() ); + if( !decodeBlendshapeNames(*msg, m_buffer, m_start) ) { LOG_RELEASE_ERROR("Could not decode blendshape names"); m_valid = false; return fsMsgPtr(); } + uint64_t actual_size = m_start-super_block_data_start; + if( actual_size != super_block.size ) { LOG_RELEASE_ERROR("Block was promised to be of size %d, not %d", super_block.size, actual_size); m_valid = false; return fsMsgPtr(); } + return msg; + }; break; + case fsMsg::MSG_OUT_TRACKING_STATE: { + BlockHeader sub_block; + uint16_t num_blocks = 0; + if( !read_pod(num_blocks, m_buffer, m_start) ) { LOG_RELEASE_ERROR("Could not read num_blocks"); m_valid = false; return fsMsgPtr(); } + std::tr1::shared_ptr msg = std::tr1::shared_ptr(new fsMsgTrackingState()); + for(int i = 0; i < num_blocks; i++) { + if( !headerAvailable(sub_block, m_buffer, m_start, m_end) ) { LOG_RELEASE_ERROR("could not read sub-header %d", i); m_valid = false; return fsMsgPtr(); } + if( !blockAvailable( m_buffer, m_start, m_end) ) { LOG_RELEASE_ERROR("could not read sub-block %d", i); m_valid = false; return fsMsgPtr(); } + skipHeader(m_start); + long sub_block_data_start = m_start; + bool success = true; + switch(sub_block.id) { + case BLOCKID_INFO: success &= decodeInfo( msg->tracking_data(), m_buffer, m_start); break; + case BLOCKID_POSE: success &= decodePose( msg->tracking_data(), m_buffer, m_start); break; + case BLOCKID_BLENDSHAPES: success &= decodeBlendshapes(msg->tracking_data(), m_buffer, m_start); break; + case BLOCKID_EYES: success &= decodeEyeGaze( msg->tracking_data(), m_buffer, m_start); break; + case BLOCKID_MARKERS: success &= decodeMarkers( msg->tracking_data(), m_buffer, m_start); break; + default: + LOG_RELEASE_ERROR("Unexpected subblock id %d", sub_block.id); + m_valid = false; return msg; + break; + } + if(!success) { + LOG_RELEASE_ERROR("Could not decode subblock with id %d", sub_block.id); + m_valid = false; return fsMsgPtr(); + } + uint64_t actual_size = m_start-sub_block_data_start; + if( actual_size != sub_block.size ) { + LOG_RELEASE_ERROR("Unexpected number of bytes consumed %d instead of %d for subblock %d id:%d", actual_size, sub_block.size, i, sub_block.id); + m_valid = false; return fsMsgPtr(); + } + } + uint64_t actual_size = m_start-super_block_data_start; + if( actual_size != super_block.size ) { + LOG_RELEASE_ERROR("Unexpected number of bytes consumed %d instead of %d", actual_size, super_block.size); + m_valid = false; return fsMsgPtr(); + } + return msg; + }; break; + case fsMsg::MSG_OUT_RIG: { + std::tr1::shared_ptr< fsMsgRig > msg(new fsMsgRig() ); + if( !decodeRig(*msg, m_buffer, m_start) ) { LOG_RELEASE_ERROR("Could not decode rig"); m_valid = false; return fsMsgPtr(); } + if( m_start-super_block_data_start != super_block.size ) { LOG_RELEASE_ERROR("Could not decode rig unexpected size"); m_valid = false; return fsMsgPtr(); } + return msg; + }; break; + default: { + LOG_RELEASE_ERROR("Unexpected superblock id %d", super_block.id); + m_valid = false; return fsMsgPtr(); + }; break; + } + return fsMsgPtr(); +} + +static void encodeInfo(std::string &buffer, const fsTrackingData & _trackingData) { + BlockHeader header(BLOCKID_INFO, sizeof(double) + 1); + write_pod(buffer, header); + + write_pod(buffer, _trackingData.m_timestamp); + unsigned char tracking_successfull = _trackingData.m_trackingSuccessful; + write_pod( buffer, tracking_successfull ); +} + +static void encodePose(std::string &buffer, const fsTrackingData & _trackingData) { + BlockHeader header(BLOCKID_POSE, sizeof(float)*7); + write_pod(buffer, header); + + write_pod(buffer, _trackingData.m_headRotation.x); + write_pod(buffer, _trackingData.m_headRotation.y); + write_pod(buffer, _trackingData.m_headRotation.z); + write_pod(buffer, _trackingData.m_headRotation.w); + write_pod(buffer, _trackingData.m_headTranslation.x); + write_pod(buffer, _trackingData.m_headTranslation.y); + write_pod(buffer, _trackingData.m_headTranslation.z); +} + +static void encodeBlendshapes(std::string &buffer, const fsTrackingData & _trackingData) { + uint32_t num_parameters = _trackingData.m_coeffs.size(); + BlockHeader header(BLOCKID_BLENDSHAPES, sizeof(uint32_t) + sizeof(float)*num_parameters); + write_pod(buffer, header); + write_pod(buffer, num_parameters); + for(uint32_t i = 0; i < num_parameters; i++) + write_pod(buffer, _trackingData.m_coeffs[i]); +} + +static void encodeEyeGaze(std::string &buffer, const fsTrackingData & _trackingData) { + BlockHeader header(BLOCKID_EYES, sizeof(float)*4); + write_pod(buffer, header); + write_pod(buffer, _trackingData.m_eyeGazeLeftPitch ); + write_pod(buffer, _trackingData.m_eyeGazeLeftYaw ); + write_pod(buffer, _trackingData.m_eyeGazeRightPitch); + write_pod(buffer, _trackingData.m_eyeGazeRightYaw ); +} + +static void encodeMarkers(std::string &buffer, const fsTrackingData & _trackingData) { + uint16_t numMarkers = _trackingData.m_markers.size(); + BlockHeader header(BLOCKID_MARKERS, sizeof(uint16_t) + sizeof(float)*3*numMarkers); + write_pod(buffer, header); + write_pod(buffer, numMarkers); + for(int i = 0; i < numMarkers; i++) { + write_pod(buffer, _trackingData.m_markers[i].x); + write_pod(buffer, _trackingData.m_markers[i].y); + write_pod(buffer, _trackingData.m_markers[i].z); + } +} + +// Inbound +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgTrackingState &msg) { + encode_message(msg_out, msg.tracking_data()); +} + +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgStartCapturing &msg) { + BlockHeader header(msg.id()); + write_pod(msg_out, header); +} +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgStopCapturing &msg) { + BlockHeader header(msg.id()); + write_pod(msg_out, header); +} +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgCalibrateNeutral &msg) { + BlockHeader header(msg.id()); + write_pod(msg_out, header); +} +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgSendMarkerNames &msg) { + BlockHeader header(msg.id()); + write_pod(msg_out, header); +} +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgSendBlendshapeNames &msg) { + BlockHeader header(msg.id()); + write_pod(msg_out, header); +} +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgSendRig &msg) { + BlockHeader header(msg.id()); + write_pod(msg_out, header); +} +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgHeadPoseRelative &msg) { + BlockHeader header(msg.id()); + write_pod(msg_out, header); +} +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgHeadPoseAbsolute &msg) { + BlockHeader header(msg.id()); + write_pod(msg_out, header); +} + +// Outbound +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgSignal &msg) { + BlockHeader header(msg.id()); + write_pod(msg_out, header); +} +void fsBinaryStream::encode_message(std::string &msg_out, const fsTrackingData &tracking_data) { + Size start = msg_out.size(); + + BlockHeader header(fsMsg::MSG_OUT_TRACKING_STATE); + write_pod(msg_out, header); + + uint16_t N_blocks = 5; + write_pod(msg_out, N_blocks); + encodeInfo( msg_out, tracking_data); + encodePose( msg_out, tracking_data); + encodeBlendshapes(msg_out, tracking_data); + encodeEyeGaze( msg_out, tracking_data); + encodeMarkers( msg_out, tracking_data); + + update_msg_size(msg_out, start); +} +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgMarkerNames &msg) { + Size start = msg_out.size(); + + BlockHeader header(msg.id()); + write_pod(msg_out, header); + + write_small_vector(msg_out,msg.marker_names()); + + update_msg_size(msg_out, start); +} +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgBlendshapeNames &msg) { + Size start = msg_out.size(); + + BlockHeader header(msg.id()); + write_pod(msg_out, header); + + write_small_vector(msg_out,msg.blendshape_names()); + + update_msg_size(msg_out, start); +} +void fsBinaryStream::encode_message(std::string &msg_out, const fsMsgRig &msg) { + Size start = msg_out.size(); + + BlockHeader header(msg.id()); + write_pod(msg_out, header); + + write_vector(msg_out, msg.mesh().m_quads); // write quads + write_vector(msg_out, msg.mesh().m_tris);// write triangles + write_vector(msg_out, msg.mesh().m_vertex_data.m_vertices);// write neutral vertices + write_small_vector(msg_out, msg.blendshape_names());// write names + write_pod(msg_out,uint16_t(msg.blendshapes().size())); + for(uint16_t i = 0;i < uint16_t(msg.blendshapes().size()); i++) + write_vector(msg_out, msg.blendshapes()[i].m_vertices); // write blendshapes + + update_msg_size(msg_out, start); +} +} From cb3b7ca7468ce2edea822fe5a0d5108c79b59171 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 30 Aug 2013 17:30:12 -0700 Subject: [PATCH 16/31] OS X build of Faceshift library. --- .../faceshift/lib/MacOS/libfaceshift.a | Bin 0 -> 370592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 interface/external/faceshift/lib/MacOS/libfaceshift.a diff --git a/interface/external/faceshift/lib/MacOS/libfaceshift.a b/interface/external/faceshift/lib/MacOS/libfaceshift.a new file mode 100644 index 0000000000000000000000000000000000000000..bd9dffa6fb941b71b312a7436c36e536d6078c22 GIT binary patch literal 370592 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%0D)oMN3>9fDVZUHoPb+;oJf;Tv zY4t`6d}l`F?7edEC8vYCF{zL7LQ>r?weaEok9^R;%ukKVOYma%|K-}x^Zt6T_Ozg< z(R}Ij>W8p=4U`GK^#y!tAL^>JZ~B0=_Vv7Lzh?QY@zsy0a|P56%)@oc`ST;arfDr~ zY01wS?3y(28I3Lvt^TN$=*yin4(g=Z)ZD>KKrYW}uKi14`v?2}o%98;F(^gf;o`qS z^bf=XHa_+Czn$J(`U4blAN_&a@0zma*`K@U2b!P`{5l#sAYZsU^0~qFPgF#HFeuq$ zI(Yvf!r7&ytrqt4%AM?-@+jbBREatFfBEIhhd$CoIZCwn$+G`d{R@g;?&HYKtPF>D z9ti^t^vHg|qch2S7^rx){ia)^{iFA)A#kg`#D9Ap&w1!ot? zM&UTIu#YX+sVg6KxLefu89mFs%&I>~h238kxw!&=S+ESW`9&wGHi0iTL z^{zgIoh=VZ%#?6C#l8P1Dq1+AXAj5|jw1XRpwIXU{(Ni5UvNHuzCDMw0!d0fVaPz0 zU=)u^koTi}q>thSG7pjkF6`jqUlWKNzFtU;rE6Qb%fb2@G=f`zc_JY$$ktP#p;km>~+MvQNnDCsqJ*uobDvVfdxL;AGF48zV<~ z@v>g_?19F>#$}{51W6tDdWgl<-Y{Q-J`|qvg0+#-QKCMSDYSH<{`0*j|HnZ`Y`)ou zgEWrH0r^$u!T1b{YMdVMMJxE*en{k3KAfMLwjz7A|E}1pJzl|U0vJN!1rq4!gHneB zPO}TSvv`eX9e6tVQMHe1n_xw;wSFy4zwDU*3SNKP*Izh)(wfb+*3L2C=1uQ-&zReH zzqQu0tyk!Tn0S=eE4Gs(TCd;)q32q!01x=CS2Ua=P}X(^u3L_xL_1CYpz0y;0RNzN zUbL@W=m-qv_UM>2j>G+){(9s3YW>D)n2oO}djI0-?|?lI@t2~H){5kVZT7Qr@b8uK zN47(NU%`S{_H-YFc8}b$gjxZp9|!978snqY zTGd+t+QHEW%w@UX_-(bnx##ofS282={JKcL+SB1=V#x!JSpn~TVE;fL%~EI9v-r&c zb-%i>{kUq2VE!#tKVbD2$@n;mGVO6;g#bE{T<4pm=C|Ijqw?_uFGt^R)E>g|e3SN& z$p!|FFx~rk^0OWy)U};F_`^v`#%4uB!8T60OU)AdRbOfQM{f$+$EtaOR(F^c#oj(I zY|S?>Y?Zxi?PbmjPHQi(JB6+iH+1KoBqTlO)(|zOdOYa2tWa?Nqze4UKDM{qC1b9K)keZmp!kYpn{-V500xBue#O+A5a>JMD{MVCIR zRC^E}PlEKN0c!#K`mNOUn9`nv)gLLj^XQKzWj&_!fnnDdg{JR0v>Euu9M(ah>v&ib zw7_bIMkAQ1WU#3x%%KZR$f4JiJ%F}N4QgOHy-ym^0bGDR0>%?0gSIUvy!YzfazcB< zKE{8`3D)aiwz&~D*@UlW4^+u^Yf)`IN%I0;_$i^m8&;%{$yBy?aF(p&aSW!lou63t zs%n0M=~;hF>PErumLq*EjH6$nxpey!$NsjsL(WdrVa zolj%1DsmC-b7^;I$%C5jgY6kD`qdoHPuSmD(!7e#)K0FIIn>BV{*Rt zV(F7{-7{^kWwjygZHf7D9QUqA9qsLFuho&?UJJB`kqvvQ@hlcTq#sh9Fl+09$C?C5@(iWTCYl zwUmk=vNqBmMwZqek+UC@I=-!f^JJD3iumpdHJIK9Dn%v;Ml&!HA~UW&OKWHl3Ij7r zqaV=(s}O`X>3OWEFE9fp;TEU2=g1;$&$>n?c&V*{4(2R?IXJ^ z=FDop_6F-O)dFYgp!8$0DmbtM}L*i$rzw zcQbI?%U^gz_FD*Yh%g>Hm`4fnOl@a)zW3@XbY6b7OI9?NQ+p9ft6`5=N#{-{d)Gmn#Xo# zC-pwCB$K*j9p-!fue5?5eV)T!N?AyG{2iM5jhyxqg|F{$lUL$>r&p_e6zF%R?VHfg zpzr%@cKw%Wo8ogniZAGU?nf#8gee;?eUcr&$^QlE_rMHc@L-jxig((>>;!Y6iHV6* zV5j4w-^;&$_R&A)luv6vs`Le=*f(*z{;#rcGI`0@zNyguaHKC)AL}?}v^22eP7N%5 z7N&tU&(mM+`d8Jvq<_uN-&3uB1%14A77KP1#__UeH$IaMD`B1YRA@`HaJS9D#yfFs z1O$U+SRJlmU?Z)K;q%#ec-ym=-rKHoxyDfTck2GOo*9e0R@-+ta$FQlA>k?AgNC*r zm93`n?Ev3Gk3A`8k=IQp=ZkEsFTo!wHul+PyaflU+qa7(Q1 zlxIdq?A~aatQ*9DQ11h4MwUP*0Ja`>%f@{Wl!~&YAzYXwjU-)W8T|~jO1Aqh+`eto zqqiff(W{TJZQ4vl+%PHP@MbAC6x=>l(!bSgDC@3i7K*3z#&*1_YP*TbTTiFrjofZn zT}T%q?{VHlMNJ!A$t9%?ns>sbRwSw)^_Kc}P*^J3wwSbmu989x%3@$bxR8Z<9a{pu z4>X2~O1>ydoj9DQ6S3ZKMUxNn`CcRG(X3XUL01+@k7fn`X@(G^zbTil4D_8`W72(= zTUSQvR#R67_g<;d40;b8cHwqi8C{$0H!sG*{V+3+T~|gb(>_aOYN)%lq;EwO(!iJ1 zsfM~ur)uEeO2HRKS4MB1gHnR7#`Xu?e6{JyQ2D6ijaJ1~h{Yfj2BMHg7W9djfY~WP zSB5tgcbYU?`ZBKq5^gFI``h(pq=$q%z<5uTu7#?$0i7b3Z_}4SC|?#;RX{A}eNeh_ zngz|H!US4HRTI>_85rcRF9XK*=*ui_ck9cn7`ML6i~)mWrqxGZMvBfyU=YTtp+Zwp z4oXYa29~}ILiskTY6HY#UN{APCB~>)ht-$C8}sJas%m8F%gh>CxJUntV?PgVN?%5L zP+(2agA$E)eHq40i8Wr2=LU8(n*+SwxQi){Kcr-6LF`-Y|36NlI@I`)^e2F$4+aIKG(1!2JXtgn6~ zCw^-^S`PgKt)5>#{exPcV_nqUsCUpt0KA}#1e^DcJ~S~gfs6GGxCARw-|Fb!y!_yx z*KW&|(_WcPiAJP8D0>E?WJ`fgS^$_klq>-y8vhmI0~rA_pAV*d4G99wQuuiX+Rm|(#BFasy}zB_-wK(X-rw4k6w~`#TBMVYr|JFe z^#0au4`OY1ruVn%E(LBsr}wvL7V@&~Z$s}l!`>&ze;)4Izve#AFL?ir<-dac>A-q+ z+Fqn|dlA+vcUYY(A?Ea$B<2$PD*hkkRaMBI#IQSSMItZH;31_Ct1vNRE_S|pJ%`buW8DxW> z)Bc6+p@7gF_8HPxR4w)Q*(@{=wV#ECkN*@kULfzP`(ZPJ-y`z!zcv0&>(`z#?K2Bm zAC^hKc6gS}D#3nVYzEyId%G<-l$RXQrhlyLbPiqMs_!GklTL3w5m zf+>PQzD13G#8Zsr2%xlUWPHs=)FV$t*^IzRi9+DC8QJV_GxD#m6q}Jj5O0_B@>iD0 zh`aYf>eF7JD^+fLk*C03vm6-hu-^`*1S-ElOiqwbU=JCXb7_9d_;K6+VZ3)NDd#vyUP~U?&d_5KU(K z30g?m{ec%g9yt25c>Aas&bw6?s`fyOs4~GKX0fP?fW$#c8609$fy#w$<^L~1E z4W7^ACxPgcUfU;o2Uf^zbOdh!fF)T}U4U|X+yf7qta|I-Cwqskl3DPBY(Cqn;^4_v z`e2({xYedI>OPmLG0|5JZ3uG|WWcrBmUxxeR0K)$>MB7ZpUVq8vfW zgA4dTX`;Id%)bUW_7_To)nE(GAj9#ux{n)H-=or-YcH3)sQ7Z@%U51dyjOm6%ey(= zjl&pA{mG1GIby{t#a0i)5Tlko%JUif1K9cFUh=MH`Nt*ye%x=p12qyoa?0L;tH==- zd7ZWj1@860Sdz;0vjc?77e@4x12&wTOhUlKSD{xn(F&T%I>y4`$<(x0=e z#7@qFkx8*fIF9kr@2l}7NWSyOzxw!VMj2idw){Wm~kD&ig_nf4=W|(C&ls7Y;|V z7h9<5VKjFIPOFIgV0e>u|7jIVBR?2e{MO;cJ@DGAi1ZJfR*5^+dA6zt7=F4f>=x9663#xGtBEACJDA-hVk@ z@6RFa0BIm{bf|81s1ZFN2cM{v;0**jlUDy!A3q#9PCH~%qV6Z} zMT*?ae!KUgedy2!9gtV%F^+>*5tVVAdI=|b2D&GWc-%AFm{Pvcqgo1|kc* zct9F9-wEJz9G`1fJcORb85=w+oq^|E_Q~@zmiHkpiam>iFKhDCi-uq{j%TAgO>Z@p zBO-QgK6tQF`TbCqWaI<{-(%#bqY01R2M&wW+K?6AmJcGLVR*qa_)QH@oQZMfN_FB}=lMxZ1o+kKTc(M&C`AWJIFr zeTdzFllvlQI?JbeHoDDhGn-E$D4xSgpi zgwEE1pnmc}v>iMNslK$lB3b^349a3)Lh#Q)o`b zoZSwQmvyUoA15<1@PHMsK6jZWMxpF@@tcmw|?pF`lo7~4wUju$oc ze()Q_)#?NPL(By1{}-HJlf!m&NW|1rSq7mn5QQ|dU~8r@frBE#WZ>UOan|-;D|)Fh zJ#!ODyU5i$xO5@EL;c`%NTIacv)dgap`&)a51bAu;t-2LC=5gaFU+b0TtB;?aOCs# zG7D~o0GfJvbYi5cf&R~dm6+MU#}GV{x(mIZqk|!GHMBj*<+?MpC&a}6bY`_lB&ZLvFh7c%{Hn=#T*9@~s=a2gLj zZ@ACMn>x4_A}2%JgS@F*?#P3^siSuyqu=939rAMbg2VbIheTipVeIG}M-#Z6C7IQs z#IR@^eG1orJiIAeF>q~!!`F838bk$W>D^;=#Z8XaP1i!0+`zQ#OWi6!X6=5FNcL25 zPFt}`o2}SJvwbVj|2FyIrkW4JTF&86$n>3LR?y*42nMo02g8xGSga#nc*%?8Pc*9qX~lQRmCnemtE0j!XYz9a`3)J7&-RZ2EpB?*8!1 zIdFesJMZ`Dk0ShMSK2zp&))03tT}V{muZ3!=wIH4J`ZnM`Y5`81n(k``STat0Ko%q zUw+)H<>6l&uftn9ha)wWuormJe8lT!SY{oL)M!tFtrTzT$IORe!RhC?oJmf6weitC zWME{T_XpxPFE`fu;J0J^Ioo6@eQTlZ=SZKoJ)pgJ0>i=2MZN7M!b-Hxg2#u>)DEgm z22=^Q*Mq;W#P`tP>Dj&D7MVEMLhH(>@1q_>^_EBeUE@WW^_AQ|x#cG(e>&PXV63$B zvm<@M_u=jHS-A1mU0#9XIlQ;3d?#t+pg|d&sFZ_GSCZ|(W8e{+`WLMgolL?LJdRyT zJ_Gb^==d8IR*GNXZF+}4Z9je6v#f7>)}e1BFXBEvwLRli;QdBWYUQEF&%gI3^zXfq zZ!?1K4-xs%!sjQf4>PUA?DlU5E&DgHQ`@fwWm?Fi$_iJ+V0#8Oa@Y~H8btY3)<~$l zQ1?z<=UKVsOX>5?wUbN!Onxkrz0|EYg|?Tf_P1WK78Fh^sIWFDZvAk+_5pq#muOD| zS4n;BX=X0Kv6KwE{B39!D6;)K&oZhibbM6)<6lkoRh6$OdjUER#{NIXE~99^+B@(j z)wBG)fK+REbz9v7$8+Q1+=s;jvUg}jp@+pmN5k;Mn{@_kKU?;WAU-F*4u6vQVNZ(H@VCE-*%qst#9u3v9)ig^F*X@7XI^~eu4iD* z!l!L`VdA(-fOa0U)M{@%tR`O;dHVF7?)9L_ue9s%tVhlGe~S8TewEkntXFychQIsj z=FgT_dHuwzyndhm&a0b07Zg^%q&}QntdGI6Hn~*S{;D^PC3Wo?SRN(~?XO9FR?^U( zN$M9?8qqQRk{MS#AjHIN_lc^xFav_Z2Y>Jf9LYuD>oOq(Sk`q4Au3`*2+-)_4^%&p z|KXd~i>e=2!bc0FRDZl4avvpxxZy}4PT>e~>ybj-A%y6FnCp)s^eQSq&#gy>c= z?>JfrFq*?(=4gS~)_`|3<7gYUZyin8dLU-gF*a;JJch7g%z|Tum`n5u(fS&~c11)8 zanoyrSggu<;59;kt=V-Dvp!bL{zM_Zpz^3TT~>ue{auB_AIAwH`XIs46+-l?m|H4{ z%pX<=QSrG7AwZ9aKZQ&6?^!AW{(y^d?}va!?%#Ik-;en9@2}Rc7gZOZEQEj?P>`hJ zJR!ufLWry311>LLpm0O;L4Cgl0nkWrPwJ{YYrW8M7_+RJmZ3;n ze>EjL1D2e3RtvF9VYBsl120@xLubwxLco({`1=^IosovGaIyhN_6@)F?G;rYhHSs) z-1r9&xe5w9EQC0MN)_Vy3fdr?1bs8JQV6`O0>9r@Qp=nKeFF~*U!|~3-1a}~1(g7c zaQwj^z(K(16|l*G4_JFw+$@Axp_2VdP&51m0$$>Jyba`_szQi4Dp^5o@@2^NEyjgw z{yT*b|KfuSZfQc>{wL6Nqp%6y_qlzs#eT=QW&mAJ2jI%zSK;XbQbEM=2Y)OPLL7uI z*s?-PK7&y_ZoCQTI3s{!HOBO_T)z!sesF{kpH--rvs^zt zO9*i*%k^L218%*5Tw}~pEZ0ceWh~df2J!Ex7UDY!TR}V=Qm&U(m5=XWUdYw|>np4N zhIROxsuBDF{XGQ1CshbhO?9MBhjIL!(5v_Hz_AoQU=%r3C7Y6RD71ZxEA{Eu2}*{0sIlc6XDuBWZzA<_tduc%OWc{Xu}WwfGGm>W8(MFs70vVgbZNSynJ+567&8 zn5XQr@^36FuLtoMW?4B6KK^k8jdK{YkY!~d$O^1^Uw)r)m-f&sfV(3lKxVAw)n17+cJ_#S- z*bZ9{K43F&sSx6S0Q)|_b^BEA0hU!QhFuoWErB{v)dHzW_y8?E*f!zsgF*=KeAy3l z-!v6;znS+gi=ccU%(gd~;x;Plo-9wj&S0%H>gYk?4a-1Sriv^tPalGhUkD*!SqsbO ziZQ?b$j7sx=cV5u0|Y~~<(8K`JPcGV zXX5Y=P-tv}fKM<(-wz*;a=rfqbOV=p=*G5!0RRvrAkTH2=RY71s6{+^^6`B!?f*)l zBVXNK-SO6M99~0$)z%Vi<;qJ|o>yC2dwN4%`n=QUwTN??(uvOWIq{aYskU=k64#!S zxPD$DeNL*aE76&ZHz&?Xtm{g2w#8e|fv@ycsbtqV>CWbJlIhi{ws`0D>8{R1eC^rI z9UWp>+qLo5R7-6%kxs|gBx;u}tv$WP3{7V`5}m7C+ncYcO|=MZT}Pt1E74Nhn7ScR z+tpsXI#FA9R&85*7vx#eo@s5tkCsGpdrP8rZM^fEL}zVVd~G5vE!I)OJCfZukSH(LLjLj;mGZAm8ZOg2UA(d&-Y*K+S>CEbPUX_Tq zBszgapGd0Q=`EtEEupHH&a75-_N|y1e_g8Us@fDLvSn|}tX-Yxgo>@czAKTgZEkN% zXVxYlS#7E<-31i4C*dpE-f1C&*p~C0l!QiQYY}e!&eR&;z}k$hqNy7acFG;y4OPE3 z(cIPEdER-kXe^m(ZH=Yc(uvM4i%E<6!E)8_7Zk33LGeN1FZVBquAuNze?fSI!b|-H z;SCBe^%sOUD7@5P5Z<8hQhz~sgThPw1>p?}FZCCMHz>T+Ul87)@KS$4c!R=A{RQC- z3J?1W${!RyRljnVKaH=n-*o$x_M7VVySTAyPFH7rLw&5VBi7uWY3oX~#8$`CiCAiF zN9(c`7dIr+3zF$*ddrn2hS*kQIZ(C1r-gH(I{a`tCK9d_si<_79w*o_rSvGW&5 ze?Xz`8iZ=^c}Ra0w~{5&9!RfGAwX*w`WotyKADKOtY}Xs7OzgXw`PiG3CAyx-k?HV z4O|5MD-*5puGFGdr{(}Z(!$Io+- z9$c6NPOZ$85Fapg5x>FI?46G|x1jt1^8vOW;CIHGL!XLw4K*fi6`w=2?ex3l)yf~h znhL)!D`0M_pMTR+f)hAh#SJS(#k!h`nqy{F!{gv!kO=2^i@y5O1pFT3bYk&qYHqGv zax5HY!4J~GVbT6yQ93w+#h*{QRRf zLWprL_dPk%!O>yO?Nlzmrb!gulBZke7tNp$0YbeEw!N zNKW3kB;_q=08#aKxhHQc8$f0f{@S~Oqsr4^zus~DuzhE~>g{0fmA1cn#>tFS$o;$I zBlcu#J-p$mFhhBL9P^SL?`Ne#k&gF1c}r7a-p<71_(m&FdA_{uNm5>uhKc3b`8AXZ zsp?0$`kOj-bM^dG-~4VL2Op_+`ugYMhLD?t-|K40^ZC8IA!O^k8GEFsp8SOx!ARxB z?rX1ym(>WW-j}@i>T6shSgrD|(DJHJ-hxI@rt&WJ@K!d0@+9PKZv<(Z#6F+i!;K(6 z30_8HC`p1hyfN%cf;X!%WWSNvzNL*}UJ|^GjiD?F-k!#gx>@vA-v;XF?@gLOp7M@* z^3kseWNs08fAjDrHGwka`Np?}O<>?wk!PPptNztfe`}gR?mvVVd-$my-p(dazC(ES zsT!BOqfH?Cp74D2+@&eZNJ8)ErckANee&iuh4m`$36H2>+Lu9iICI)8U9`+V|3&7fTM z+Q+e6db65AnvPe~JiKwuAYXYt`xZ2V0_FMavv1v)k%Zpu&7d?1y@#8@dgUEEo~^GR zWu(=}^Bso|PlF>#=$&ORp6UGN)4McDc^hlVi`{fu&v>vW4N_Ha>`Bdfcukr^b`tY% zzveJddDZun+Fw2LCN+lwQ5FhhCA&hOM` z-%)Fy@_fhDU0Oi7=GPTxyDrLT4i(C?4~W`d>Rqoo*F_6jKs1SXuWSLC$~)OJUT$vz z*}DGqt&0v@dCH4D;#p7rGFn2e@_gsDhPQ-)I&azshO2+|$eYy?3Y2GG4&&l2Z3+3x zi#={%N1l!M;#&S5d$cxn{436QA8ZMg>W^<+N>7J<%JUs}4zb0h=XvbIoGyQ+r9-Cj ztlci&;&jMPaNfwaU)ZunUiHhFY@Kab@AP|5I*e4g_Q~_=U!8c`AWwPKuY$6_ zdU*X>!3^ca9zw2%H>nkrD$kY2>hpiCV7>Bub-SjPJp0R&y2cea9M3FO&0Zcm3| z<;CK7|I5xeW*uMSk82#T{){`m#vk8!yWn`JRQ)b_*3P-DV4w10<4R+d#rn@re{=ia8mbb=t@gh)98sRH|14+?Y4*cfzy0RzKPy|qK;^mW!}_!Q zc*s^>ET6Zy{W;ni@|5rE7hT#wzVdwaKDrH*B(T%QXKouT@(gw;C z$g}a;*cvL77ptRNUUKF;qb)=~mi+kYD7_72Dlb+?SGeWQZ40{+$gTFjEmSJcS4Ugg z!V%^9#^Hn3pHIXeUmfjf18tNStD~RRIpZ|^1Q@7%UmeXl0dke+tD~hSz@h|p+W2fd z0g9C8tD`-&Xy5) z9h4=IYvZ)19h582S4T}Upi+6hI_j4Jsk;-`QI;(-<;Cjg3b#LtGa#ybUmdN>fK282 z>S%Wc6eh6K#wXMsW+=~9M>g+A+ttX6<^AOsoOz$r9?Dd%Z~PdS0p-ez<^6HD+;#0? z-e>-LvVQGu4@JuJ32*>;Suz@5}p|4p6B) zU*30ifXsjS>(j>PXa|TY&zJYZ?Q7)4@_y4=XWqwkgn260m-o>fph$VKyiaw@UDFY= zKTn+ZogHDI@_cze+7a@U=gWJSD9lryFYg&0p-_3Ty#G+@jMIWB6f57C_mxptraWKX zw@0Beft@z*hoi7ddA__K>{uf&miN=${*3Mf*_HnM*l}cd6b33UmiKOMxhp%tz65fu zU)wuDmGXRfKimmYzYsfpdCxczvX$q{dwM6xR9-CaFFx{FgE?@c;GrSf8VzrpR#ENkpn;*T%yOS?d-@_c#U z*ah;G=d#nP+|va{D$keq-JNUX#q$29f%1HL-_sT5DbJVpCMQ9O@?7z(UjKH5Wy*`?eX!e~X(z#Y<@@r!_#`M(o-gm~ zPJ-0E5+9eHHt)ObNf+h$^1iccjl5Xi?^)x_d%tdwuX25P&pHVTlo!kU9Jk!X-5~Aj z#Cc!W4caKrm-pS>AX|C9yoWL&UwN*0Rx@p*(?H z>(`p@P@z0u-gkC~eaiFY{iyZFezN|z-@JK0oC&GQi{*Xu)6Tq)I~g*S@5}pwlObDq zzPztI8RjLh)5d4}$xx^~U*5NLuaOtadrP-JU3x&d%Jud4jFX{4d9l3z`jj(H3wl7& zet$h#zgG5uMauK#eR~fmQJydFhpj)#^Y!v*#%D_wWG2Bom<3Vg`L4I7o8;sTF*Wk+haz0(ThyvL}=ukUH?)SFyb(lqt`?($^*Lu=V$#$g>}jsE=pI@q-46zZ2e6<)yCrtFu3k z?gbU!C$@8LFDOrfx4ajWDbK!Rw>~?o>%SL_{6X~k@^Y{jkU*w?FBO_r`1}OTx}Q*-)MYuSsvHP@eBTy?(uGv&*S4@;BkTj+?6e9|U>IJLTAVd8%9Pf>WUY2~X^EZT0xI z@>H0oJiBD5{?)_VeyV-(s>t(=6NgW=Z~YXW{qCDfUdCYi-fZFd=E32EVW0AR^TsTj zWgz1jI{<6wv;;Mu&aI~6jO7dtP}_fb($PaM;Sz%J$M3oYa3VXJ@0 z5U5I^-}0snfg{TEjkk;K!>0i;>)5s4jkmjpK%w$n^RKlplv5+mzCYJh z4_P@-s`7k(jLU)b%Ja=b3v!?&3Es*aSfo5(T(;*xmGZpxp985uiLNnThUp?d2_FTvgOZgmVjsV@3HY#Ue>X? z{pP`X^;>@WQ2W^if$b;1>@PLwws-CsFtCB}ef7Ki49HfV_x#rxuwHpC|E--Ht^dk9 zHjbv=Rli?$UdR{*(NwY1Hy#Wh1_PDno&SeHp7LCFTD?n$LB8@_ovPRVS>x;{{5jFn$;<8{wjeWj(Y2^r5lti4jkAPz3`Hu4r zkAU^c^Bw19oC#$~=pBA0lq=6ySF_H9O6B?TyYx)hrMzS3)9M@VH=YS;Eu@}(^U-C)sh!G&RezA+3%*E1*=uAZyZ^C7L+N^cbvEGEZCR8PTN0s zp9NLQ^VM}|Bpgwm?|Ip*k&xF?-1Oyd+(;OyJm37fU?gNK&o{rW90>!J=j+GYM?zG2 zzWi-D3o?}#n_v6h@62D9QLsz-)nB}{zk1dmqenq@x`=tHnp8h;?kFf%o-dBeM?snL zVjmQ#NAH$Vkl9M~`o^1s);{IA@?z_I&q!#ayx2TB`95cShMx`dl<%ralO3l@}GgzW%Vr+NV5Uf7m%1W+=~>m!sA`<@x%<;d3Bgd9nWR zda)Q>k3Zwih4spJ^#>d81?NJA@_g&YmFL1f<@x;CelAog&$n*eGP*`ytRFUW`_tt- z$m}le`ugGM^B_-ozJ4?JJjhp`&!6SzL4opo^Y)fn@_hAku$FzkaU%VEn4x-o^UaX+ zYy9<%C)3Wa@z)oZ#pl=f>x;{}TK4(Yjl0i>q9o!R%7aD9^Q{}R@}N|CzIEfcJXoKE z-UWG3raa%eab+ItQl6`y+Wxe?mOS5ijKg_wMCJL`jTvL0N_oEe8$Jg1DbJPP>iI9P zMqX@uf8>wOab)=zC_Y(W-}#^|V_>!NeDlD;F;JmAmz_3_=@-B*<@xG*$OTZTJl{Mp z?E)CtL;Udd&&3x&uJU|&UUvaxD$loW+-DLt~+h@?3GT`8zrWqRNY{8@nxb z=5Nwis8If2ee>T~$j?d~pEYA4PkHt`>(#${=AWHopsapb7APkF9*TfYxq0PB?( zoBwXQ$LaUDanQyH?4AF{LALU|^WQiasXU)Q+s8qk^1Sok*cy4GkF7(u{lV!^mkVJ= ze}R4D)aVPLNO`V0vi)oBg|J9@K7W>92*t|tjXzsz$@BHEgSG7Q&VLudGS%zrH$yI} z@z*y_OuMMYUte4nUsU6-FD~n9+2@`AE`rh|;v5y_u7|Heb5^1Sokc-WVO-UZ{K zN_oDzS~(t4Pm%g__0#J4ua-RDd6UEAAyegf=f8Z2D$iGc!}Fny@?80?p8v+z$cxQ? z;}<#m-ST`WQ@OtP>}<)0eaiE_2l!wtE<3B|zX{MrdA_^PM-DHUZ`- z&(}W}Pk=(@`SQGO0^}>tJO5390_FMoap+0Adh=)bS#}XN zP~g*!T}Sw>e!h+S;)@||knru-+g$&r_K8rXJYW4Bo(Q`V$g}x7crjEcFLwX`J$KZbZ=1i-lWO!gbL7v; z9bQYn{Y0?+rH*yWT{#JsCCImxyL=)PD=&8c@LBcCwR{_|=&9mP?Ec)T_4BQNS(iY8 z@_l(2cM0St;931PUU|wpc6}=~=+?jH5~xTZ*V?)B5-3l=tM-2qlqoM3uXh*Ln_tU6 zYV92?ur1x{yd9{YZ|#_LDHJK+7q5kv!n_1LtAD{IP^i3^z4z3w-|}s|7*Z#{tLo=l zdz(y#T;==h?Kc?)Cg559S(ie#@?!R$Rlk1AxA7`dek{II>*rg0H%^AC1a?^7p2@H; z30{)|s7!*_uK;!_&o|CYDyZ?tH%~1rfO3`R8>iP4Kt&R~owe+9-A8QWa0Ue z_FSaXI&qj2Kfo(R@eV@$Wxy0Ikw@`Yvdif@3p@3GP9;b z;pt-5LY+VB<1L*IyOihq?#jmLP@F6Bp76-qGacFt_4i{N$KBH)s=U~Fo@;*ZjN_2Y zp-B0@^E}fohh@t1o#$D6Ig}=_)8=K}<*;6PzVke5rq{^pdh9&U>o+_7X>tXWjuO~+ zJl^jLC|92Ac*)v1=?bV&p0D35yaIM9&v%@?rj|V4c(=2beZKQNN3VcN)$2Px>@uUq zU*GZY=ovNs`rrv&Z>!@FCS}WL9W{8tCyX#V4m`P^X$=CP^moEeLZ$wJZ$qjTJk$j>mc=}dGbd+ z*I&k64U3fTtA_a^FxzL<=)eA$U;nwm=~ve8 zpdtx-yIc+B%De8E-1psb7yb^~oa^sDHV!skY08ToPj9GSuH~<}8cLKOJD&cie!iWr zn_L4G3GA@Ee%C;G0-n`x<5i}-{~n9O4Q~C5uYuz8{BfvW|6Bu$l;@jwb|)z>R9GX= zSFc%xP^5Z&MlKd$^)xf=_iN_nwy<}HpY-b!>$m<4xxOafK7XcN z4au6eQu#o+R{Jf12D-lW(6t{ceE#B>c&`z9!x=f5y&s=4bH@ zP?&^2>yprK{n>p(jeeg$p?NSP34e~(ig(PPCkmbZOqvHpN%*rc3H{ceHS=on?ek~n zJeZe+Kig}?JLb<%*Es#jnh(WE_%m*PV*S>i1@mk2?ek~ld{~r(Kg(;yJLbpyy*NE}|I|LAw3@(FTUp&rfCByArhex+u6>ohk3qlh9FK=3 z0Qz0$nEZVzzfAqt?>NV`FVPz-^!v?mIC_HC*XDjZqKQBT1 zq6F*9R$PzM;@VfLeElwQ9DdAj{Z3vSo~MrK zcZlQgVmncRvwdlT_*5o{52uQJSA4S-cjc#2?bGiH$Hw;|-NltWZSRV2VFLXn3G|oh z1rHrY;^J4OxPB)%4lh&ruKbqhhY$$tAv6W~!jQSQ=Tthj#1H!eQa2k!RX7BM3pHv;H)d*kpj#a;coKu_rDcY5R6 z7wZXp{XTCTo~sv%^?unnyi9Rd{bXm0{Vw^H3EEd^d;RWioc?1AC;dKd9A2agE0=vG zio4oJbHsiZUZS{5epv#%QgJQQIQ!FdVe1;7aus*AFH~IL6C5YMQgQu0ZXBMc4>Gy* z7b))2U#hrEepD~qyX5C7uJ;YZ*;lBz>-eEEf&4Tbv0d`B6?fTJptyFkIQxnf*YB9c z;gt#Gr|HD0w7B*Kit8{PhZpG!*j)2(h2r{s+PL6*XG6jwLm+A~7>zw7umstxr!vT^MT71!^_ z#^J?^>ogsYtA4$IKNg>IjZdK#mMcCbio4dYm5OT^h@aDz5LPkHbq9cg43-ahLtaPCV%KvpD%_dSb%W zK3nm+{ts9GixhYDk5a{5;|FI;E?n)i6|dXBrm6l?#a;DNsd(M`TdD2KwD4W+s}y(D zU$(wD#Z`Yrio4=lqIli-(nC^JrMRoUqT0|^KgEi>^p`5`ihrfzuKZ=|1NyG^1&Y_r zUsV08RNNK+wDYAt>gKQDSbP+BwU6dWdsqGn6nEvXRB>1QDiqf+#m0w?YF~7W=y&BW zPjOfN$`p6$uT|FjE4zbk(Qio4nuD_%E$yVbumeW9Z({<(_R&0mJT=q)-<>~pox zQ{0umV#Qtgt5n<-KQ0vgb@Mk*?aNc#mA@jzUHPk0+@(M5BGK=Pf3D(nA?q)e|?a_~j{HH~x9rK3i|>bG0u} z+!g;4#a;2QQrs2)H2X{t#KnJ&+E<{sEB?ibyW-Cjk?+zURooT-JjLtAf4kaOp|~r4 z#}3@<#=l7~$zP=&*t^=N*=I)VW4*G8>tA__yW(G>xXb@C#p~Ap47D#!5By#6&sE$N z|5C+W`YRN7#h>Zoc-{CfQu~S&cg3$%@w)LZ*Y?GF;=$FvOmSEIkDWMl#XnC^EV}$J zRJ?BdyXb|}GR0l-uTtC<{{jVE`im5I#lKYXy73>b_GRmXysr2aC|)=I3$=Z&ekjY; zzEE*j{7V&g#UFjaxXb^j;&tO+srD5r?uvhj;;#6o&64_c>CaZ&75@Uo>&BnnI({hb zieK8*qQ7qZbG3by;;!~lJ@M^|e}Uqz_?If~^1njyy76DG_C>D|`(5$RQ`{B*GR0l` zD;0OeKTR*(*Ny*1wXaxlSNzHpuN(iP+P-AA_~&Y0p|~slY1c}7SNsbUcllqWc-{Et z>jk|E#a;2o-UA8F_!lbf(qEhaFIC)ie!4P2d*+IL`n|xo`7^4x4kK}RvEr`t%cY9D z@>iKaexBa2?>fI5Z)Vy}x)|FO0aZe`G7J-}j4apQ{gO=y(0% z@VxmFfBl|c?EJtyt*^2h1$XJMOn`HfwAc4J#OW_p+!eoy1bCI=uKY%IU~tLLO@J3D zuFqG*;!2Tn{s2@z2)y z=jwx0F8zgyyW(G*054VCWnY=%uJ~0c?y@hc5Bjc6Fm>-V%`@foT1Rjd{LuK7Lf1;L&9SKMV^q2jLi7c1_n z?^4BG^JkUfuKGnk)ZyxX*^0aTD^T3!U$Nq@@v$O7d)7&OUF+LC#a-*$BE?1QD;2N1zAYXorj+W9->&{urMPQ-oBOiA zz6%w1)lZ4ydKx4yKGF3e-xc3n#a-ifp5m_YyGU_Y{TD0lif@_Xb;s}B8sCyv#C|=^ z5bxisf@_-M@Jhv9{vSIrr-wOl?ThuqovXgf6nDk1Lh-oqd61|o*eLe9>Ze3;{jN;S z_^IvFwn}@~`GW$*UE^1Y;;#CtP~4S2{vrBZ@yS-))xJRSy8UbPsS=;;H$}eQh8mZj zBE?$1b5X}p5m_hs#M$+zqEgfd{=!%6|Y-g%QSwa zio5EoN^#fxpS?}=yX-4a+%^9fDPFgJ*QG=cm|#a;7% zR3BV-)pxey&f_D+UGsmb;;#HxDDH|syCi;f$ES^hWhkgn+;x1IrW2cM{3=k~rN3Bl zSN)VJ?i#=HJ`wv}^A4JbJf1=Z^S-V|14D8mA?|jUG`Nd?&^P4io5DFZJ*fZ8h`T?chz5k;;#Nvthmd+ zGR0l~RVnTof3x@d>#IO<*Z5nixNH2aOdvn)TYrA@6nE8kf#R;?uQJ75<8P(nuKaUA z>~qCGTk*Q%@4OuC$_J&r7Is|!&Qn|;(}}}N6nE*bP~25Nd?)%{$6rN?yXw0{aaa6H z6?culm5RISze;hJf6?#7{<`DumSge#L2&)vOl-*p0;#;M-h9Q=p1;^U| z==YzW1lPmVSo?BqU#587`$N)C7gMqi`{m~)zzY@E^Pe&O{ZxKbZ)|eqFE;^Rpm<&X zijK+ux7e?b8N~E&SG-7Zm;RCjc$wmL^&dPY|39L?ZhQykN_?vncj-?%;*Wo};&t`s zACs?mUH?}qp7)DieqjQ5DB~`l}Ro`JeVbzx}z2yV@5dz>5@j z#kW{-SA5G9clwtAkN%(d=ZbHk;x7M+6?fTRqPWZc3dLRa^P4~Z*^0aDFH+oPe~IFC z=clxxIzA<6pQclm>;BbT#r1Mo-27IQB7f<7$K&v{fc&M0@o{*S;(D1d4lfDHU#|F8 zC|>vYE?fOC2uXX_@m-1HuH(B(#a;T-^u}D*@m;p!`aOZU4i1R_Qi_3 zj_)cJcOBoQ>A>xhpR2elK6#3}j^9cZcOAb~DDKK{mEx}WM)k%DSAOym;6;k-V}_E9 zTE9xgb$p7$vs=htF8c};ciC60xZZ{uCqLR!{&Lp0;;!*OPjT1yU!=Gzzr~8X{3}a< zS1Io5f7$5?;-|PXeu}&DSEaaXd@pJx`d!!mI9_mlOd&S@jXOizDelToNdmk~@w)Th zB9))kTJ*S%&!UR!V;V90R%`nb#a;H5DXx!!#Mm8N;Mqw5@FG zuK#B!UZl87en|qnOmSTX$LwFE^2<)}+h3`;Yy3-V=Wm~#0MAq0)jtaqclFO=#a;H5 zCBUl`cdgHIGsHiaeR+zz@>8g|YyDNCxa;|xGR5^aw^;smYW@n^`{Q4vc-{V!I$YXS zDDLV%m5SHxKbhLTphE)vio5(TRovx&h2nMnpQQQ=JNoq(D_+WnZ@9b?sZI_7y4avadLS z{8GhT_EjZlpLUYiU)R2sYF~lkF8c}S$(ltNLKy&1j1I$p2UNfsQGdbNawHU{3D? zTLYNBePDb5b6g+T6u?Auzp6L9or3wKxBUNJZ{hvDH=LD%d7(E<2w)!TE%FxkhD!pN zdA(sjn9Ftlg5K4a!pxbv-={ZhPQi4wa>4A)hEF1x*R#dG7qUhEU$bFU0CQiq@bAbL z{s`rFRDKKPN3tO&fcdeP@b~r-{*%2#{#Cu;%@j<2FL*J4IaT*Fb-$7B|6fnR|JhUg zf32s;EA1)rZs`fn1TYu$6nR5*dy;O`bo*Z;?Y=Y8Zn+VDv4I@{Oo4%IDVVX!J5zZb z4ZLmr$P)gCx_wo*D|Nd_x3hGcr`uDrM6byby@z|iTPc{2dcf8c%s+bw{#p;gZ`S=n z-EXSKs6`6uxt@{PK|G#d6S9KHo z-fmI{w{^pQE5cmYt@<5JW=1#cTE|T62A#m1*RA>$>1Jp*$x~02(@EuoyGh=6p9KE_ zQ*jau3t(2B1bZWx2Ty{JBbYfS!RNOBoCI6JL{EY{!kF}v;Gq=E&s_!orYqbO#(dZn zdri?i(^c??yTU6en47vnuN2J8uJA|-=Hjj*@64|7XbPr(S9lXlC*4o$3U7fy_cvQ- zQ!sg5;KdY7Q{Dcvv+Uoc+rFJ)8<_1U%Kx|NcA##5?j-*|quVLEZLQmfqwrD+W_lFX zreL~9#qPFI7@LCG-%<8I?g*W1eRhNpnA18+e!l4dpGGjZb&xn-(?Q~RQ3u!*#0=^H zKSnT_9k9=awC|+*tvWye+8@`U`emN&|JNRV31arPhmV7pP3@)q2HW4M z`?t22_BXbd_M^3ZpZ2gXf@z}dzs->Ldo!xvUDbYv?r+bK_FFTg{X-epcgLDp8L-Os z#|-#6h&e3-9#6qM(GK=UF!#2Dmx7oZ+re8w%!GDg=YV#ir%yX*3+>Z&zgaucliE)7 zJadBZ?><5J3s1nV$=hG5`x8zO{skupzk%}iwiSM5TkJbr+n4M9=C;D$*jD((ZN;vu z6rbJ}j)(T=>i$UO4^{qu+hE@*Vcu>7gHtdswSgxBm^p2*=gQ2P`hQRT|6prq3Fh_I z!e7-|_|scM3orxq|ET`|uj8@j)XduBh5yj;!XI)xGyv1)c!|fqTZw;%T0t7L|3vpc zXeIu=(@OmNODl-jaiEpt;nG%;?}4pE&hgr=c`NK(+y1}lBKPNXk@J1J$ay1O>iG6_ zSR2IrE*)}GFkiKlJnw8N@?LH!?VoOmJvY_<3Eh9NrN}F8De`V@DgFLp+YZd2mhf5- z)1@W69mE7x-WM%I-tHFIcOAFir289Nh`g6th`d``Nc$Nr#J@9Ih&@r|w`&1Sp?w40 z4`};en@jt5n?n;Ye`yX+1~7ka4zC0;H#CO}Q!qoCi=Uaz;n^UjWpmgX#Qc~hdMeVy z&TVO8XQ}Q#lO}eqOcOo7OOy8F)5Ol++P<0c8>L~7>D&LinaDfTOxl0jOxpjw88ig5 zq#3LTVD4xJPX#fTH-pY8m>$i<&WvX8eh|~J8O%w+{O>q86v2FcoZ#Dz6T6=~PV_u> z9Hc_~2X+6R<3#Vm<3#Vw;5NAMc(^O zMc%7TVRI0(swws!9`i_3vG3-lV&9cb#lF$neu&EJulwCq-ia!&VN>`hi1~LDxGM$o zZ4;5Vy@|+M-$dj+(nRdLr-|6NQ1|CG5&LF05&QC+z`H@rh$b*O1v5zHwNrTwRNl9Z zMcyZkMc(_m|7K&6SJqhMt!fNAf|#X^;mQ=uqQ+w1RgJ~IiyDi4gS35jm3N}vwxPd3OZUFh$m5_VyI} ze<@13$u&6wIa{rTs%cO8ep;rTulfo%SOPNWl#K5eB7TTKou`BIGpI z_Fo*5_OBe0_N9lU{R6sPcnGdZ!Av;>vr{mA4#7VnG$L{Rg_;@&o+Q#_0#_x53P{KfqfN za%TP@?T7v#?b~YmG;R0u_abM{_wYWLP2a-@VD9@KUXPG-&-c=P%J?2cWyHhXd8G zW?-@vLcrujG6TPDrFzZSdpe=T->_%%Eg#=QMC zd=|j`?Q3Wo#5|_^#b3kVFy{8J;iCZN3dJXU4NrzK!@q`>K}?UYvCq1hc3)TDdTI{s zg?2&Aj=gYe5cBe0(f8b5xFv}Bvu+pdh1-Le@q5Ltvz0esFWeo(^xP}$sTwnjG4VhhrWanDVQB!LhBUFx-Uik zsxL+U(l15+4PQbldtT#9kvrx~$$RrJWxPG`g^aJaej)o4b-(c!GCypsgpOg%l1g|z zjG0gge+*(eRl;C94}1>oBA9ibLpXq${W&xYV0wJ6?f(Uj*tq=*ehOn=`V5{AV{ZKn zMny1VK7+my%$K`mUVm>ld=EE@+Rzp?;@DXJ`{ic^r5uB z`9tUy!A$y4czu-@RJ`&7k@NZog8%sgk#pGxA}8kqm>fXzZUM}W3V0-pSz00WaZiQFyF#}UDx@w(R*2mG6(aYocVR^s z^VfGJK1<#e`M19dkJ|pF|Nq}Ra-Qb?6v*#pU~gVEB}PP zLCoQ|;PfD7?OX793TDY$@Qv;7Z^8I5X6Re+QvlQUEtna`94(jr_(Qqax4Rs!4rAUb zhY4ZK3+1psfO(`Gehy%MZ+T#5>;F^B;leQHqH_4kp5IkoRykZ5#xyC19|D-oZ$iT$ z=I%FPrahnYCR`cB^m`Mo3t|ra1OAtSdC_hInEC&J2|>)Lf53QqeE$!a8pM3D6@Ieo zi>=Tsh*_}}t_fo9+A8+{ek)vUk85?CzZGT&G2OO`pC>4FK z!*zQ~8B7jh{`0!%`Sx|;ee^m^4`SYV9a4gr)w=(vsM#Poh0 zZVzKJR9-Wccl7Vl-}e6<)`EHQ@3LN8s{h}v|Nl<^pYV6M)vka44%_Vd_wQm?+TW!= z|NNS4_q+yA+WLGAR@wgdTJ(}KQWc_;626z@s{s!@HjK%Hw^9`aubA#w_yg}yQ1Fy=w`^Bp;IfAKp zRp!1dXb;CUgWo2FYzvj zS>#u~Eb^atS>#W78B7H8Rc?rHs z!QB0l$i4I>*qefB`;xS4{u108z#Li!zu5K5I_Mh4%wGp9f|#M}q#yqBqKwl!UW6~g zm+E?o-OhMX{JQW(@oUhF($9W+LHg(R7i53K3)1gaydeE<(F@Y=E`CAcaG~;y z#Uq%`+P<0o|9@+x{hqbbe#2U6zj&?0>-M#x=Stn4uiI|AO<4;KY`-s+_Ai%8`)fYXQuw&&fLH zFVD$3=h5e6owG>sdC$Ry0OqpiME*It?e`oEjbOSwCvsEt|AWtp{%y~S{+FH=`|f;J zc$1zLIp;kqeq<}}1m*2tBlvr3ME=?}((W}jcw!B30+*1B!@wB+^3QzCcoQzCcJ zQ?f2;{FJOWcC3ci!K_&g)5Dm%S4-UHt`@zQuZAghov;6gS4&+UStWWuT_tt4VU^U^ z+Ep+yf+<-g^)q!9ycouGS_Libdi1aGnLQ8kSNOtSr+X6ax9gH8;XoME@JW$>bfw7u zYNg2Ewo>G8S}F2NSBm^OD@A_qmC(apkA6b>^9xT%e_rwgObK9yJpt$1^IT8B)-Y!G zU!*@j{TKK+1vB9(Jvee?RlM%=0(v{;bDk{=VRGnZE};F7tQe z$0ZIC<^Ah1iO0^zqxzFkU4?QgU z=RGX;UG}ip*H?L|%KPs_;>YfXg!hk!g!jNhBLDXfiTq0*68UHA_SA=9hh4wx{{Cgc z-?>cmzPL=}J-STfja~*OfEm0D&bR&SL1-JmjDHYX+vDN~;hZpL&!4ql{aNI_tlOvl zEO}k_XOVlypGEHe2c$o5ctHB|tq)YcLdZ;b0LFwdr#v8W>-2!=ZTSG4YsZtN@_(zP zu*9y5?}z=<^x)LoDJrQSN-591@4wELx=-oFpl+Vc?iLH7t|!hJ9~ zfEjZi+#JC4z7Kv5W5V}ACwpA51Rf1yu3G}v1TfQeo4Z8xnkAyQ`4TxVwXGPs1u>5n z%l_hGm|)MJ7sChkIHwr8+w)MxV&_T9`@eg|-oy7w`^tNz{k!){`HT)kNON50}C7t8%EeHM%S zcDfBMhMWlI@I4~;oqGg->jc2wtK|BvC7L--U;`JA4mTnys!TtyqEqUavuAG z$iMjyBL6DgUi=4mH;n0}`wf+U_-@hr{@o()wYx>$th=E{05kD!=o`lDSOmr%$1jq4 zxMz{*yF#}U7D*mQE)qHY7m1vQ?vnm6^DgNRz3&3M)N6bf3aMeg88@zwuLck ziex^#u?YTS?=LA5`Tutd{E~us8jg?_Wuax-#3eYpWH0R6-#fHm+NZh(8uaR!;_ZRGSg>v2g z; z<^Q{`6}fY+h0Z}tuWMx-PQMm91~L0)OWp38ExfI>p;r*Iakh+4_v`k$*^m;!6wH?K zY1nKTpE}NlrvjMpY(uI`(() zLm2b=)w+JZTG!84%Y5GIYM5#5*6nAr#GZ;-V$X}S#Gd}>Na{6vKcT)1kRtFHDEe zY(A&MJweO>`+opqzc**Mo2J3NLClrYV5~hpod%5qn6IYF_JygU|M97!|CXtuf7Vpd zf7w)7=bSxN^begX^_Z^Pz*MQn?=F*ithh|-@tMn{zR$Q!U7u?7!<*D)%}RJ`=LPEe^CGzMlf#|Nc)!yr2VP_nSUQFfB_NA?FG<3 zjG0p){p0ci=^tYYM9uW^C+`PpQV|L$a6&rKHjk53l)H%x}!_PqUMkvDp>$jh7z z&j&C~CyU%|m%>?L%uSa{e;9fx_StRI@KTBQ(Mu#QUtJ>c-gb$^d($P--%2l$c+a^+ z`djZypqD)kouvI}lJ=uXFx_6Coh0Wo`%Z!}5zH?W;jJ)c`$U<4AD#%`rC>%)6!{${ z!Vf8!hc1@(OD=}n?e)Bi;ny%`#Kq7pjQM2(JYkQcC&+oy`4i+k_?QXsm0f>NfE#Ro z%a`q2`S2;2)%oJ*1Nq|T{Cqj@cXGa*M?E@T_79Ag^P@Y)%lXmuc#*{K>5D{9v2L%|?RmP*ya*abFbyse{r|d9+COxm z_;dY*(ti4d&^dw`exdN%DR196!QU7sa#oBJeBL;bGjyEDIer{m7{nYM3!4L&cgD&* z{I{_(53e07^YC)T7mttj0=Rn2*kbjseVy^I%RGGwnR7n~CR%+yT1n zexB4zoAX3|_&kyS;<@m#y*_g;bh6iH&Xsw!%em0Qt{+Fk2lo8;X!taMd1y489>&ZY zEqbmQE&YDHZbyxl`xJ(ZmirWbI!ES%SI&XWVa&pF;C*}F;5pL2nx6w7*?I76_}pF( zIUBkLF;AZj@7nvT&lWwC&X)OqxNc85TgIJ3qomzaqtuU4GVYHbCH?4Z`+o#8V3e#2 zdX5r%Q+5Bhk+Oeiq>NjYBW2v$JW|H3B_m;kowrBIeF~jM!WZ@ybK4HguCrjiyO2J%yCOl-XN9g~(&xCu!nCO{u z|Jb)9rnm+POShr_e>{?*}dNf^_9 zIE)QrejNs{*!!4=3ICB{!p|56&!=FT41?KW%y(zN6?R>A2D}F5=`)0X(;31ac!pdb zZGHxH4PibQD)*PlqJ|OvBS9&i2Kd_P(*br^$BbY4D=G@8~pm zDTJAG8tjOWGwU?CGKBdh2cEa~=SaTFb41SbIWn#;&yn%;z8ukWYmSVU^KwM~upD?c zgz1_i^UeMtaDE6=K1Aeg7$Wjk50QA?GX&a(F>{AN-wcSD#JePKrkGqo?gA0cN_U-55$AJ}H?>m&Bq=hUo!kM|M( z7WWbVZtEj@X7>^QX7mv~efz*$Axx`2;@`);VQdJqzPHGGrnksj)?56$skitytv3t^ zVfyuk*0#QSi+`)K#lL&9;qC}Iv$Nq*dmlr#$jQwXy#lQD@ik?k9#lQ7EMbGU$A;q2t=_&qo?+Ft_ znADyk?^h%8_8IYSn-Txk88|(JS!kf6tuG_~{U=NO`!ow~iIB4?3+}e_V3x=!&Jw-X z>2_Kc+!VqL&4Qalm|zyX9Uvq$uK zvqdL~e=|=K|HhpJIU!7!lc0SFbEvEM_h47?&puanSA?7?_Ia}aW{`dUEP~1IDtcS# zHq;dk*m6tz(FHz;kh8pt_}9G)d}HnFBK{reEOI{YEdFipEdIUGS@f*!EdH(T zEPCd2hP@%og`FjTZ9BvG5a!5KKPH;~Eb9X0^Q`AZHPS@?YPH=Y!)7$bwn4hBXPK2C;QSonn6z;J0Ma7 z9qs@ogxS&o{ujh7?f~8FeAoe++WD}9`1^8u@%NGTu+Ywj?cshqAKK^NBA7gVj;+6L zyV&R1?0D54Hrx3y1KzXqVTSlSDg$1#^I?YA*Cs>c{MJtVJ=jkC{j#0t+0jn?EpI3O z-ro*3*!f1Ea~sqS#@P9=oycq4PUIaqLHzyn1o3yv3DDophbKTdgqd`L_?vzLw6gWr zR{XuIt@t~$E!-9%XG~kT*WPc^R^&$8iXCCy9&Q6qhA=zYz^V}D{x(n%A!l(L@waUo zSP{bf+*<70*IMMf+gkkH*joI3p|$8)(OUdn)>`yTZ4HlyFe6$^ejB!ib3>SY$BVpA zj~996$BVzu954PZJsx_8Fw>5Qln^HCc=2~nD`+0VywXbAmFjb859oGbE4bRO7g|X? zdh2#oy2Rz4bohf^_oc(*cHNgQa_mER)$6{NvbE2pwX*l)w1n1S%ndDJdxV@}En!!L zoFOeGK5w;v=3&f}EySN?EkvGuuFQ^Kvsy@eE^8tB#yT|DyNbB|5`(M&E9X{P@ea`tD!uPeO*I2pOe!NUQfaFZV0c~`$84} zIaRKAKAH-f?0r?K@VdRPJyqIYm@4-@eANIp*ypYrz&5)sY#`6YJkUVS4^L{K_BK#^ z8;D(pBJgVv^Hv0YviGS)_vxiqwGo0_6S>0J@J@#wuwf9+jZ}TyUEAScfH;E(j8S|j*i}M-tOZo1z z%$Sp9zsxdY?q6z-JbRfjUzK>xWybtQ=E1GTyh*;hv>Nk}#L=}H^BGwmY&GV8B+k3l zm_u94k$+!m%qJz@eyK5ATg;L7E;Z&4B7dndSBZSteD*J`xS~A%$bT<2m!+?XF2 zuwI9g8eYr80 z`HVjW-QaR#a-uhMk>$pmy~OxeEjQ+p7UK^uxAp<~dj$H7KL}m>a${~0y`hs$!zV8> z{yjcp4r?*~d7rhf$G_WW%(&0^=X|Q?PMGCt!E`HcC!&-goh#@s0F z!e_-z``h6so0X*wLz)eVH*2ivI9TEi>kmt;RpN%$Uus#vfm1%)PC~ z-?z+|&$b$WMC?MV@vmBD%%teQ%$OabKYWAmZC_^015)nrl}(K|v>N}OR^>b2YRvVm z#=pDOn44RTKi_K2*ZsR%jd`Dxd#f?``HX+2Rr5F9s`~C|HRel_pH?l0$yQ??Z#Dky z@XfXw^TSr-pJ+Aa$yVb}wOaFJ|6r>zH%dEiHRe-3<6i~eFnoJjjrpSF2fo2pW4_gD z{O#}!v>NkBtMRwB8uM7I@yq7O?@NA`8uJ0EkEO;;OMNV*K9;J!JC_>sWy#M{+R;*D z{@rT)JC#^T$@>pIKte-&&1-dWkg;^6yw;%*|paml*SDX;<*g z!gpecF%L<8;2U3J%rB)~!8fwRn4e3#50lAk5U+$8;Zi81&1jK3Ye0r<8p zG3N7s}|Ka-(t*Ft;WBr#h7=s8vk62<@ft{wit80*r66t9+ZC4V$4^ipR`#1wZE^$nD>j_Z!zXxpYhA} zY|=kN@aqFXz?YE%`CV+#vnjsJ)QuwxoZ~ zl9ub?E|&T*mY?XC>m;Rr%Y9m(mHIHMhnx>7zsot0Uy=GSn!mU)=1QOO_Zee8C+&*g z}C^A>BLvTyn=)_eXOzh{AQU>Fz#wnG z?_ppNXn^?(mEW!lm2U1rrJLpV954%v1H-@`=t9s1`CSg0waViH^abXCQx~Yb$qQ6o zmfz#RFfa&|gDHjIp7WJ%{(Pm&pAUav4wwbTfql?LpbPVR5NLpVA+Ha4eaHjOfS(4RBU*2Ph}N6o_xxG#1LlBP;KW%Km*Lb5qe+_m<7gxeQ#7fB5zbZ!u;OD?;(B<^1A`%S1bQrtCfCkwbIY> z`wYKN^Lvipv%om8kM|&+qb`Sl-J5VJO zDkpV@%8B!PAHPTVJUQ zPE`G;PgMPL{GJ8Ifni_}*bZF)I>Ybz6W|Zb0kgn3FboU=v-z#cKm*)!obs7JPWj}ILqFgQ_-XJte$N8qz%Vcfl!I91 zEHDlX1A{=h=)&T6EcyZEJH7m#1x_5Ra#F{toH)OSfkB`F=2yZGm;+{kabOr21j@m# zmOjTo56l6xz&J1r3ObAB`sesP3ycH9z#y<4x&U;B-}5Wr56l6l zR;ZlG6)Gpo?-Tr<;`cbehk-$0JMRT}PcCY(^3#PpU=ElC#(`mA5NLq;PUwL-U=|n$ z_I0ZMkxtb=%&S1LlBPU>qp%0-}EqXn^^np$F!GSzsI(1_pt0@q|?#M?nwF z0jG|l{Ewpi`8^H{1A{<0=-=XZB>aFmU=}Fn(#m^%M^ZjVQa=111h#_@fH(Y}e?9zx zIpEam(f9S}%kObu7#IW^VEzdB0dv4CFb)g@gFrdI*2?eepa&ZyAD&nbB8J4S$@v}v%oko3=9J0uxN|lYZcA{bHFSx4(xla%8R^K<%RiO&d(J- zau~J6?@;&wXAV`m=|hz+$M0ES92f=$f%4JN;`bW(0dv5q*C^k~*C^jCzsG@LU=S#W z3tRkN4L@KGm<7gxVPFs_7h_oTuYw+!17?A7U>GRp^jiMft5nY*zsupq7QaK_2h0Jd z4k3SskUxHp1N*>7z=!!g2$VyUE&f6H180KNN09p9_bf0D3HZAio=6Ue6Ks%>r}4se>q=gD4+pW`S5!Vm<7gxVPFq*A?SkqE{8^2{Pp~0-z+c(oZ6rA z*`M;^_c*W*d<1-$--AFoblT#t=Q8_dfjM9nIPnsdlX{8DiSv6H7z9e3jKxpSW%liQ zG3D`M%7fo?z$x%@e)Ipy_fvk41H-@|P!7Ge`0F{#zFFYRekx~rKjo9-_bf0D3v^cJlo>GufkpXqW`|=&+j>478nQi@qUE&!~7lu$|2kqe?7n1Hw(-Gv%om8 zFQD=x0hJ%-_aIOX>9+XkdCtCBU=BF-BBh^v5#`J8aXt6h7X}7_a#**;PtR%g%>r}4 zEO5fFd{chq8|U{hFsSD{%NIrY6z9w9dCI<7U{23j_GN)_U>Fz#%Hi6UK6;L_Zx)yX zW`S{F7#IY~;oBCyo^$MzbCV^V17?A7U>Fz#%HiM^y`F3An+4{8Sv}|27bhJi9V9J> zZd>$vez8x^HJ0*_xEO&F2O}^JlsFejOI(XUiDwZg@hbvnffAo0X^BS>C~+tPCGJFE zuubJkT#2M5jzsj3_z_7e_a*Mb%8#C>?31_- zNlP4uK#AXw_at6JU>GRz7#2P6N!*3JFYy)fyTnyk^gxN1kaQR*@eq=h_y>7k;v6h` zpu{m)^uRb!;u7S2i9@jHff8q6(E}xZz@i6Ae1N1S9zdXc_ZKLIB2d2T3zYBp0&_t5 zPA_TsE-z5NzYCP_?gHgMf$}|F^qK|A_j37NzK_fA@*P~#@*Q02A)?=Zt?%Jhet1t3 zl9un>@}7L>7CV|Z@CV8{&+?vpx3>5j+(r+hyD(UMa-C~UR+wwe2TKsl- z{y^HknD5YA^!*4I^_@pr=0WmYFX?5*yaoOrBt1d;qoi*my@T{_(w~)di!n0qlIO?Z zU%5*2^9#~lr2m)nn@Imf(o2omP5mzau%=HuTX_^1aGhwHX)byuG z%ld^pdD61(A4!;gB%LLF6X}~s&y(IkI&hTI zKTJA8dXDs`NdJoTqontce!mqCcTsND$=i!03Tz_g``g>og#fL z>1#>nNl%eBol5^H(qYoGq(4IXN2DJny_@v2q@N|-Cjqa&7=cwQ2bj- zUr72Lq%S9Z3+bJt?<4(^la>B!q`OH!PC85ax1=8>{ZG=rC%x|-nx7sCfHTG%O1h8q zais4ey^i!ZNGC`KxR-q^Y5N}ecayg7ZNH7QeUI^HN!#}hKSJ8RNBGC2?R$a$K-#|N zw?%?ymzu)8zWbB5@8Lb1w0*DZNu=$2TF)kJ-@BS5ZQp}>DQWv&)9Xpw_mqB&w0*DW z1ElSHJikrazBluyr0shupC)bJ3%Tqr&5wN#;{l}Ydk>E!ZQnC^ilp0&$z7=Rw}$lG zn>Bq7>78%U^!rG+$29$U(qYoyC*8A2@h`Yr`FCvA^x34_`!)Rz(vzgWNP33!Ur6Uk zAMtVJ*OpZJO{C{YUr#zKfttpcuae$D`f2-p(yzNm`RyScBOM%4`kUiZ|& zqxk9nr}%YOD1Q4_6yE|q{Iudv*rxdKjN+dLpZbg9|9pw!+rOmv7Yr)@{FfBpeZJxY zA6NV*->3LlF3$c8`0d~awkSUIZKdB3Q~Kb&O5c8?;sf7N{A1v!z|RgSKKm2J4_*Yl z1nwGRT5nT)>ZgkTCHN`uziLsue9x38_+Q2hE0l>c{RAJdYo z33GA!dpZ?Ad7a|l2|jd*@;?pyAb7cVOX%fXDS3V#Qu=oAKM#OkqWqq^TJfQ;Yx>Mq z#mjeLdH(yys(&z{c-hY+{O$XB|MLS%-}ax1KYmv6_WiprxKZ(a;CYQ*sfH#QYww{39Pw`Oz0Ee)?^Szo|>{@%JizHm&&JU5dXi0{(Wz ze{D$d`KuKFHiIW7x=3#QTq4?6z@Mp`44_j z`QIk(r^T50rO1Cm%HNn(w4at&p#MpVf9J2Yex~I@BZ;wox#A^GL!L1B?cm=AUg9z2 z`Pr|Oz6bo~hm_vF|2=TM>eun8^8W;Q`~LIw?^FEbV~YQ>t^YFR|5x-+q5s6oG=K7) zQ=ZrTQuEh`{rmV!6+d^l;?Mep;`3XS|D!LVd|s>gqw|W-4JiJRcEwK~t@vU1ufm>Q zBlqmJ7!#0ya_OHxSNbsg``|wf|EK;(@e}Yr5qkM9DbL-=p9lY`1C)PA0@sZ(2V3Q9 z%q;TvBY*P!RGtTrKZ*Qr!(YCi$}zgO{lzN_hpFVX&A1|IsI-_i6< z;Ag=Hpq~f-3-p)uC3!ZYUl#qo0iNlUk$Xy{fA)dz@T+|L{`xB?RloT6mEpE!ik~}H z`M>N*q0gEC{>t;9x9_+AB=p;%KkF+>Z{J`42k?3DF9x6De)>nj&w%d)Z{J_v`F_=} zM>eV$V_v$i@(;`EmNDiM#)tgZG`#`+B60zmG3E!1KQoLkw=Ge6i35`7pk123JpS1U z&sV&CzxHQtR{5(QSN^~Ky5jBov!4M!1^!;}_Wj%c2|fk>+u-f{vrqb<@}CF)!#3qV z$;H_R|4j88#J~I9a>Yl$-}zI;&*MM#GkzM{=OMJOS=#4HyM2LwC-^Y!>#wxm9Qe)P z+rhsS`XKG|d*G+QcZ+=y|KJ2I-}^25mNhBt%Sy^WLix-6Jkq`SzIq5SRp!FPZk1iuEneZTm%;KSgD!Q1zP{|Wpo_|X?B|J;FE-z_(2{j_~g)1UAu zeo{678Dq}k=JPYO7MHYN5F@`p9g*p z{5be&@ZSL+`H}Klf`0SV|9jCdgnoC@|K_0oBlHu{pGyA;alicu^sk-ZpGAHE{Qlr` z;17m=68yWt2kC#$$NovYqCEHix7P10{^h4$sO6KAg?nSn2JF)y_URey*BtoU!OwvI z4ESmA6W}Mo|0np+A+y4gt zAovd2*UjJ~;NJ&c4v&-PA@FnHpHKVAf`2XZ$1wB981u&t=8tkojFfLq90eIavf4l5 zj4%H;t@teXhr#!NKY;mTocZOs%r6I-U#`GE&tIned+@(yoR`NBeinQH{BFjt*V*$Y z=2snzZ(;E3_Mv?oul!$V`G;AvgZcmd(930m@|;Qj_k2O+uh^mVljwg9`p=;MKHyW} zF9tse{vh!4;2!`V_@eTc|HVJEp9db7{$nqnXzc$RD&tm@E!~FSY%%3C7pGTM< zuVQ}O13t(6_!{QFtC;^@%lJ6|5>?`-@6q~AF+P4Be6~gL-@w0UWBz%kjo)AK|Au}F z`j0U`&wWvI_~d!YfA(&D|3JzAlB|(q9?Yvm||L{P?PoJ##Ex%EGcv$gru8q*ALqmJhJ9UVg07Z@8GU!v#w?f|m z{f&3R|F=qiQVRXg;8qqi~Uu#*+P>So2i@*RpdcPjqB z&Qg3{7B*zB_NNp-{ddK`^lgftdsy*LU90%nI~2bS`bp@QzgO{*&nv$5T&179L+QVK zo#F!zDE=<+p*t0S$MuTO$_930Ob_-WjQx1=eaQcZ;HFjW0b|V5tiMEL zB5jO$E9)~mS)X|$^kL{j(6>Q<>f7LdyG3qJd!zCX-KzXX>A%z9KMcMP{PZPf4*1wM{ZI6V{$Ko*bne4&rtjX_=AxjNB+~R72k&Z$W2Ng`mXYS zD6aVMCzSu4;N#%;O(=d4{E4?K{p8P-{s$LB|5>HK0sJiZ!-k;;e?04t({ES$w;iYW zIk{L|&P)KG2meFxd%!>aE|njb0~3rfw{KATHVJGo#;n+me&BxzJ_P@kzuv_9xa@C~=QYsBp*M_Ali0uI>$Uyu zr2Q?yznH|oI0OG;693}=?x6k|-!Ei*pJaT0JLBsl5pjs&OKB0pU3{ju9e|?|+ z7pDK+PXCiQCwV@1v(g72SNRuiR{7JPRQ&B9ReVGapfSdbkAnZ4;=hi4nY&)`XPv0{ z!5bBS(7Tm>{&R}|)JDZm$U$w!n6}R=K5GTN8S}?$6+iV2#lPU4$p5&~kDaY}iEEMP zp(({rQ@`(|erKrPd#K+$_4^U(cZT{sj{2RZe($4xr>NgP>UWa*ZF`f-&r-jyqkiY8 z-vi!7{;1!#_o{yJnCgFXK=FfDs{VI(lfR9MzsA5{4jK}F?n1=}M-_kUYZbrhor=Gu zL-F%#6u;#!Y9Hs*ivLPN@pErg{QHhj`tWwe%l<#He{wE`JeU7N>3g;*erH7S@*P&5 z#4D7(@9m1e^jyWKE>gU#1Brc(T&wu5;}tLGtH|@jGR03^rug5VtoZ2*6ud>iYZ5!OF%V*Me)_;D!f4~FrhYeMy#{gCQ+`l+ga_(Ph%LFRw5 ze_Wm{^S=q~XAAUM=mXHVL;o1xF9RP``b~VloaOuFx=EF9zNz?SZ&vxc@GoSaiqwCE z`Ex7t<9Xz7WBx4r=jG`J-_HE`3m;VaN$f{gzw+;3e)nFGC-oe%3=DfqsnfJ#dQBpO5^u5vBh*_Ah?F;v?rM|9R}+4bX?6zXJLY^!rjj zldLcNn)QVa)(^gp{hY^sUiS{=A7Fm74*F^6eXKu3pr1Tc^FO^x^*du;%WuzHRsS0= zqJFMW{PYUtpS@o3SB@xN{5*M%V11y6^?^-tf0Ec2))#)s`anDD3(rD73H@5=j%GrJ`R0=^@l#_zl;1?Oal;!UonCBCIcb5dBlEPYkd=v5NJH2a!LI{FgEQ z$C3X9_-En&_cZ#$zi*8C$3M981jX-U{&6Gg(+22%#Zrff1366 z&!K+~{cn7a>K|c!;?=Bgw6ngELH``<8|SdTF~$1E^~j$@{uuH*upf8BKM4Qbtbgoe z{bMup(>dmEL-<#Fn7>`g`bB{Ci?dk2m|*>4_yP21{i2WcixBGk*CT><|f{11kI{#)Aq-+meOZ?8YF{*h(<;hn5c2Cvrkzh4jSkNN#~!AHQK z555EZQ`nCz_TypfO9cCI5$l^d@N#&sHGc+w2lOHETcPg&zYhDA#eO}G{oH~5It%^g zv46X;f4i`MQT*R&IjC61U;N)Z@lih8{=xrb+ze1Pv`=h^cAs`>xHgywG#@s%G2p98-?`UTPNCD_+E z>(hTlznty=V4vr(&);Kw=)pc8iGFj`-=CF^eg1ps zH;8_(Vth$4zN}+jR}r#<+mCp@h6 zJIna-3&xKy@r$G2=fSrV-#7?<8RJiu__OPYAKO9vSvU9`_^*PW125kt#6O)xzoo3t zY)8MN8NYN>o-tQ2egzr7UW$H`==UY`Z%4mh+3`cjA42{F@}FRRe(uBCzW#Z-+J{|o z0I@OVuY5lU@%{D)zQ68a{C$S+2LaZ9-pl%Zj`gFb-mm$~e@ppKzeV}a$$`h#{x-`0 z$BLi8|J?Oy#sBLr#T)#)_u=2|dO-1)v;G#qe|n1bw=n+GtNA{YdrqzZu5=tC_!M(SLvR&!Ycs#*aAq|8$GeZ%6+hVLx-fSNVS)2ao=7{J&|| z56?vZdDhoHPyY#{|BKK+g8tXhenRN~M)V&<|C?BUkaNN0`7!Y;yNO@Ui9%=(L*-zHB7`sdL9kI+YmKO4qA%+UY-E%B{OvPRA~ljjY@ z-^~*rn;^bun)sb5;&=8CzY|0M0Q%oR`EN)6FB9LFg8vinpCP_a_Q8vMIj>Hhvnan1 z<@YA&=M?SpKPVqLpH7~?Q9i4{KMQ|3mrkBT!0&+n^X&ZOTLm{$77 zj?(@af4A1ZoR=WwC+Bs`bBkZ`lUFGIeYAf$$5)=O|5MvfAN}XA@DB|u{*|v$`qVp= zUe3P|{&L=`JX23A{mya4-vE7nOz~rfEB)ZR(2w>vy-x8T{hQJUXusdI$DSqM_#rI<$0_Q6JzNL!K$pt6In7>m#a{jhFuii)TQ*TrJ(cps# z#ozg2#c#h#@&8Nwm7LQl&uy$f^|Ai6iS{e!Gs?4z_M4^s{*v;WJXP@>ztjBB+WVoP zmvbQHnW6q8)c=w8_c!8W23Vik$@^ z<$PXwZo|HXv2Xhk{}v#=`e5RpX0TuX{d%RBbCKow1omk=_4_f#{}lG;9_&vZ`*TxL z>8EMmduZR=Y2QCOP4RM0y*y9SzGrCPSCRh!`45o)Ecs8IkAAf8B<*{O_B}-Y<$Pgz zcGJG+Y2SZ|DSbdL0+9NleNWQ92gtvi`z+7XwC^7B|Bv-bFZYGZ^Df%=4%+uU8x=oI z`~DN{J4gGLaY6c@oOdqI9@_U#+V{)fpm;fNUY=3f_bl!EfYpkR(7vCheNWTAk0gFf z&ZU*-7qssz?R(2XN-yW*%JXL0?{3=fn}~1A(>`yaea_Q9Z?fal;J@~_+WvRI|4HgU zPyL_D`t3CJ-*vjmmwOH5Iq^l>KX+6At5+yK5LNs^|5pBn??aDJ{}RU~&!g1;PU`>I z!<2rS_WvyHKSldLFRu7W>Oc58l|M`U_Z+EsIY(Tc=l@;h&rttz{8 zdGNQKq5S8eKOOuG_%`Sb{ks$U9!I}Jj#c`|Ihw!ME>Zp+*pJ)6%Y7H}d>niP{E>Wr zmwPGXY2*8QAN})Z;3vU<75onHpI)i*NgGkPtZ?+e+2w2`uz?4!svI%5lTP5R`b8#U$Ji)wV!f6 zue3k8M?#*Pz|Vp|nD_^||3RMLVIOy5AAba1?vIe?ZsH@_q5stDl)v0VA@{N`KOkT0gsqzjze9!9SPkW!ef&cJ^#Ir`&2u0KifA@KXP zDSj3B2k>v^-lXMoF7Znd{2N)(>>yOHOc_@~?PkFUr7lzVgJxfuU*7x#Ha30d~AsQ3D>ZH zp@Z)m=dyo$lJ6t$=ljSi_NV=r?*}2iA3Q<)TOZ#K!o-gT_N$rC@?!S{n7^8FyculhT$nRjiv3Fw z&WHIP=fi9#{^3#RXQ98F_=#!ge*%3L`WE6RrlEg^^I^iA4|5>#8HV_YLx|sqgI@!_ z1N?2oU*@R)qbc7>%D0dFPf|Zw>L*V5{FeBJEc;`&u)kxH{T=H+p!E~ve5Bp%|D0w2 z=X=4=gMSbEZ`z^%68LHGhl8Jo{z~?brsP6VW6UeyzlZ*@lK8Ly@f8pAeY%bKiqC_e z1V2Rl#SZY>_A3Fg)0=?|J6#d$v|2X&n_>;lUL%)mlp}8kDf1U8( zL;T)uzMsz$pC{v*@SoxP`2gjYBR=D+#Ai$qpK%A}6Q=x5qWn^n-#@^|!4HF<0{;Ta zFHHVFkA5li{{it4<{H)i2=)gBiH~@i_M0Pq>JH+kBE(O1(tlFyuaa{TC4W1>C%}im z{~G!%_z}K87|w6Hfc@V+><_=6__{gb>%KvJ-A>}`t|5MImiUJV@f&-HKZx1;Oa7$! zdyM@hVd6Kg2XBb)_!sy=@Lxv1dGz}p`c0$XM~I)8W`9@*`=|Pd54_Iazi^Mr{}=XW zJNp-Azz3nfg8gNA@K1oxLw^+b8StN@ep2j@_#*t}Tu*t@lwX$eyMgkXp!_ycesjd< zOcLL*oA{SsLmwnQ^FYd{o&7nFLmwjk;|$_I_RxPHx9var{}1}fxuWviNqj|!{c)GF zzifi|#Eug}RsK=LcSMM9d<1-)_>Wt_&x4;P{v+J3^hXo_5hVWOJK*Es*MeUK{#|zd$p0zm zmqovuiJ!>gKm3;cC++wT(^shdnP|CzMoKk$FD z_&+`PKWiEP=H9RLAHx5UeRlGEll{Td_&*Qh{}}wAA^g8A{@*LvzqAwo?&s)l@c-80 z{~7RI_K|Cl5`{depi%M+h?3;WmG z*O$M27S*@k~OV8{0n zU!BB%9mIbP+wo(>FHaG_oFzW%UgEE&nSaQ+d)D}ffAu*1Z<6^(5Aj#i^uMps|K{j_ z>xsXbrvIHp{8gU#rMrk<{y*TkNwd<#QvcZ znLioyznuBg9Q*I)*ngL&e+)8zGXJaf_Z0i@^7M~Ac6>kk@BYsIyIJ<%9nSu{Y4+cp ziv8HZ{=0ut{`2&Yv$20U`iGn=BlG6~?W>FSwS)H613p6g+RgkXMEe?Ke`J{ca|ZFp zyYa6#(tlFK-(E=k?QZj3@dSNIRp_z!dJzs?cAe-!;^ zj`3|P{b!Q=9mW3ZJ>>5#^q*<+m!bb;$=`0`|K{mGKcWBZr2qVg^C@KBFVCOoKRfOH z9n7!7^q+I-KOuX5M*QC_{pV)-&m{e4iulG^`p>uMKRf6@hWYy(_U!@e+cfrV2>TW% zzWo>2H$(jRImCBQV&C>)-wg57S7F}*#5eyK`!<7pyBqr!BEEND;%9ea-`MjKcDe?j{5&0<6DmUe+Tuyo%%nK`VUe6pQHYFF}`(B|8eSn zC-uLJ@hw9A=NaGbq5dbS|LxSjq5hv{e9KY)qtyQ-_1{VT&r<(eDc=a?`%TJsp7QO( z|Cq!7_#FPn4F1RAv=2GASe_^GKf>78{b?T&{Ew&cKf>78`)MBm?CZ1mA3@s32ZK2+{I`pkABr89X9xBp4E`bZj|UjPF2cXtMf`b&`B9ws z^#7rK#lc@h|DH$wdywBo|33If#=OXvHIs4?kHk0qP}8}yG<}Gix8=*4xwAF>)Bn`8 z0UrY&+@SbZKpz2rEBr$?J?Sjz5Apsy?>|X8j6P@cexCHB$RAv<@@^!ZC4Jx{s^3oT zpZKlBFZi-%&l-LI^%76y%bFc;)bv;6`?xP_X4YvsB=Ky%tl7C*({GmeEML}i!2e8% zxASF95AHTe({v8`hkjGj)9@Srfu;lG=d^EWItcwf-_!Ig z`W*RvO^4uj)weZmkpJ=TXgWpu$B$|{0{su<{1dsI3Hd!zpUbl*e}R_QWm4bEvnESA zEbVD|*2Fpg?OCbcTpLytij6QSdvlIDq$iGF}o8%vUzahV}t}V|O$!`GqtH@6TeeRO> zwmfSB@cS#}mm)u}BtIeaiISg5%KLcq52ODdCVhmgmx(^e?~(O2U)F?4-!0$Ud|4BsKOG_QQSv_h?_7x|@?}kg{`?b(-;w;& zf7eUAisT>qtrFiM`2~NT#6$S9CJ+88N})EldCA zy`^{wLv+6X6i0XnN&%(n^nz9-83KHBtT^NseqzU1~Ew?vnALOwuX1 z5+icAS!!*J*Q7{nv61wan5D#qXu6OKS8Rc_dmw%4&}d@( z(r9KpnHV{(lMHnYq$_Y;h0GRSi>6OkigQLU85-@6ZAhlKjwFS9G;>@gIX*Hpn#d%z zj9gguj3+lIhqfjAdxs-yHXA9Tk+E$Sz4dz6P&&3PF+9{SS?e7#vDEm`Xl9_A_MM$u zl9|{@GM!Frv1>-8cAh#sw)x^!$wYs0{It&A%taZCLo~A@Gv3)5N)IN+ll`$&W_-20 ze#}66U3yDzubs)!OGYfs(n%!~j>lQ=L^Fx;%xQ^KX6yLS=ob6^h54>tAMK2}^62gD zjZoJjrUx-&sfMMvuqYMM)oBSiEipW_X*`ihu1{{wj3ltI%QLOYuk^NRP~fy6%O9YubM5a!nEG9tH#Ey z{IG-@#}k_`9vT(fy>Q!hqp2*rYHdSJZ;3!h4r{blH^SQGoys`IZ5Q&+6&dm!>rZ47 z_CW65sEY%j4pc7u+?%Mr=)nLMUaTz4>cvtds$CX#->p_JMLzX5HwUF_3oG$6x;*ge+9H@Lbdb&AoZGb(ktJHN6XS-q?E--e*c6Nx(ox|E>%tIkq%f1`8drh=1RQkqq=Q5c`pAi6X4UMkh-7b+R|anYq*++K8+ zN?{3Nb$`V=gL8#q^|mo~gU8tV)Vq|dcl^ooIAw-&u3 zCIe$(4aHmixfPH1+H>w zR?(uJy5c2;Q}U`~cIAOzVHM6>Q!M1)d;4>c6dIiLM0pY1JMimXeXn?G6*T5nO6x}^5> z*fWIg#(`);(9T8vt^p_Kix#9U7#{~)tKT?>QQPu41$lYDz`U-GSm`_# zO-%)^s)yU7-!3X#T>Vy2EJJm3#VJrJMNBj;`A(OXuN%IhY*z8;3R!i>*BbLYTbJT+ zP>@#msoZhs(qhCltMnY)i?iDJ({-D}32 zK7^-E;!aU$Kji`neXx7T6MMJ!3(QL`L$#XZ!%kIAR@AA9<9#ohI2uIp6O+SKmZu1-}ht}`90y(q_At9ABVddbi2d-X)>9CuG+fM1l% zn%&1E=lX`*^JR+QKDN5ba}2Zgxb_?=?Ho}H1J!dQ+w-I_PWS{q4Kj${eyxFkLPeeaFJeVftorPEJ9+<{^p6V2V3e` zdaE%AxJava0B~VZS@a$huHX1KNwR0d-z0hU82x66ti#|pOJqI%f5m6xhMG`ghQ3+C z7iHvKlqhMJi!|_MAsK~8isHT(77Ob9_`&C(YV0fRKvPZdV#U_9!`_*Dux;LqaW#?k zTI6bcDyzeT>h+uDW{UT0o0}=L9s}J}xpi3Srpm3uR5w#@WA?hK8Z63aSJR}V)i&31 z7lP^bj^!?}uQU~V({k4>wjRq}gSiLu*O28dh+L%QF7T7C zxfdt5am&3pxs6-yMagZ71H(72yM!MOe>oC~O7F~z&ZnEgc40*GaSd>w(p-f3TUaWz4-vHSxd){ zu?-sSCMvKvgYB+JNqb$av35U2scF`_3j0bM(Oh%ANU^o;wP)rY?4C!1T}5Qw7P|_c z%Iff-dIKiAiQ>K6>?R7W%V;-KZar4JnR4qf+f9_)pxth!28%P?RWvDSwTrae#Fm#H zd(tE)+m&~vk!Y&TUX;w52HPWN4<^r>wRV+Puc>xrQdtEal&;@SH%+!@Bi%HK^;qcU z39Z9CH&19Cwz+9S8#Bz!6Tc{{?5ajd8(pkPc0JIlSvJ{~d8I{Yu2Ei;(ArknBWn*9 z&ZAj&6hP zM@dU<+&-5a+)#H+^rEQCWLNIJFCM*9dwWfxDa&y^_Wd++;9S`yG;#u%<6Nq0M{PP@ zDI9fGanNS*Y_@W?1sdleQ$@AJB0441)^f$dS~`xk@~owU#)FoesakTOPf1y>7+A0@ zR}2)&#k-b88ZTPvsjVei7Hg?+f?iR&-sN!AFRL>`czpS3QgBhIgSCWM1R+Ji@mtSK#O{&mdI*;Sjl#Qua zVt9CLv*gb%u|y@E>FT6M;Hw{4ViiY8WX48<7z-IzKu=~Zz% zueEhR0X@~rO|qV~#sR;sZ`gv?M?LG-xgxeM)}N$2lUAGME-vpp3Kb})XcY>`aZ${Q z(c~q?I&-P2&QOUCU5nMhZe8{0&=8UH0K90Xb44u1SwgYI)(MxEW;Zh_+}J>3IGv2i zxj$0t&gN0k33|8L7hJj4d_8toyM=YUn6#7yyQ*fgwb^jMV^QSD5S_fN&2Iy5?z8A=QfU6$;( z4$F)s+!g4GNn6+$ifxQVyX}#!laXyrF#~S;c&~_V=``B;#rnzVov}@c%@<#i81Hvy z(^aCB+P!XRR_?DC2IdWGI9)TE>FO>o2&-9+Rw)WO6dyhtL&B%i8s)p}vMAP7Wvv%l zf6qu_LNAH5EL}9y6^g~IV;N(^$XF_JYc|HDDs1cN zYHrG!4rlszcFM7+>gOcw`!VaGuR5|AnenZgGp+_m+EGMiv-au272P)xm64)w_HfBq?l$+*_ZdCQ&7T`V&*nrb zv3V$CACT#V<{0bXTl_*V1l8xiZYp)j*}itej3wkq+_BW9tDViwWuT476xiz9y*ks` z7>k~&?X+wZaFglGQZ&=;O`ug>CEN-Xq|;5*k-KeIN93m-Io(+;*Xgn7X`a<97GTLt z!qEZUO2n&vqNa4~*p$Q8D$ zbDFd=Sy{0CuLbI)rkDyhxZ7$Wd!x>&aLbOX4YmelU1D0jc2j4s{Oj#uC>~v0`5j~V z_s3XgsjrW&klt`iEP9Hbt#0|TXT+kts?7ogSgTQ2lwaZedzrnLW)Nqim_ZlLE`CN$ zAF8C4y7rO^_tsLXILy@G;B|~CVNpK9L}gH_Ln!TIrP5fgRW#GJay&8GKQ`u}or1ui8$R!j#(Ax;{1*UD?#ScFyjc{*#+g^x6R()1L)-dTf&pmgV(Osg*45 zx;wen;3S^1x0-NcU2NFL$)F^)T@>qi^?a z(#sQEs#mAmz4p+$P-E~|afSPHn`4_&sme7qm%O;toO^j_``59;siK=x?z9fQZtlP9 z6w%4GZcUwSK?YA+wKZ~T%d24)_nKr6&9SoQxR<|&_NWUz3${nE61_G^ulx!v(%E4a zZa(D;C!NPJw%F@Lj*+c&0_8F~iW_oF8NC-XXOG~UupecD78rKiGFi%@xOm~{{T|4i zqmtXgR@um3DzvP8TpMe#F;S`b@KtpyEBcTZmdPwTS2 zWEK|GY;_ACcHO?iR{g|VoHIE}s=&(gC)Tu#!$5_EWz9mc?sn7a!00*F zS)%D^+8QNw`O@)`GMaW<$}ftnd*Qx3JB~K(X4s?YVk;|1E%|KGAP383vlk81S});!fYw{u!Y@1<4h_LVQBTOyqTbx36W($O*s zt3+Y?Z&fZkN8rx^j8ASW1m8 z21nBs^;YaT<>QmQY_F*Gx-Rt=43MR6O-^MCxrI6{|3F?~={9;f*EHnC!YXa;wb+W% zb?05O@~YjTWSupd)`e2b;zX?f$cX`m0JI>St-6)4~6f<4sEK< z6eKjzW#sVomAGQ;sLY_-qLcGeSc!@aeSmdxsPprylhpPcE5{!B7H+lE!X%=wC|Ugj zVF~Nv>V~J!gC(@}e;jm@mwy+Mel4ps3w~az#l)>=R<&7Pnnbf^T27^1=UG(^Krw>0 z5%W=`cJ=YgilF#*(~QlK!`PI~2C53LHOyB=UJaEx+0N0jyo!ewhzu&$(oprdAm>YV z*KDqpbTfM{St%HA=Yn6!G`47X7sb-KW?W?HV#|m2iY;Ft;-{oOYJM+UXiieuAYF^N z_9Uf}iT8oV**-naVJgHmJkS(7f8{{qx#xP0)+wnq`zvL|B&bs(V5`l7Dl+d<)>SM< z$)vg>np3*J9X#7$98O6F8y;qdtS`>il*)Syj#ISTFzYhKsw_#nP8yqXRq(55R7FEl zJ_B>1tvw;DB=8=2<1DN4=^F9Zm7ja6N_6)>ipy6nntGhgRZx~!th?xv*w{M;` z)}@_g<=WF0J>9F+uz@vcr<%oXTu*y*j(D}_hLsAJT6?!zsp{)>7fo;jzMg~HgRkd! zyIHc#nb!l~5KTI+)D7Ne%GXLbK(`Ff&OqrRTdE@DZA=HOvDy}D&C9;j=dIbJ-(h0G~nwAS=rLOS6{Cv(QRmVEZQ<)>uGP!5w9L!ud-a+8iZ7(-qdWbgKa!lek*R~x9pY#v&$)t$@Jjq ziHz*B(w&52rPDIblPtOcX+_}!*ir9HWJ?1i+~_TdrLWl|f_fFzBMSIcs9@~D*`i-Db=DkW4y0Xr8J`L$`B)#)sRLLIxm zX`lVE#!25bRM+nJg1BPKs8JFHJ*<{m5ohAw92z1jt{0W;g|0_#84J6x%QEVm>)8#s zTA>u)m9C5Xv*5dqWw8ZyaPlqcTg-^d*4o996j^&HX1DB@G4QCgdzln@x#ZJ9Qhcu- zrK>Zjs&v7GczDIDQU#u61hx_jj69`XOvWDDkQMnAORw~8yUCW?vpNWiBCNgJ*0B(_ zS!|q*)4EpAeW}$hYEl{&R%+|3zE&hJX2c#Kyb_g-DzZL_&mukH%&3pMqEajL3GdBs+U zuq(r@b<}L;ZnDdmILpLquFDF($g$YL3z8kfK#8mca_t~1vM=@tZ@I3{#F^`YA6pnP zi@(^O3|9$P9$m$eiW|ohn=c+3-4e|tGD)weSQR_KKs2#>ptpEr(ST0*3r!ll%Xvj$ zTozTU@Niu~wdH#}_+db1ToMgy%P3Zn)dFm#YD~JV468XHKBD+3Au=#vO?@lkU(U8&AhORSg3W6YlZ_ON@0gsRta#WeUvZURM;Q89eMfAYrk8*(l<~uvehm3 zbOfs{6c+jhoPJ%skx&D&dRV?xE4HR^FhXbHV1$7Idy&am=2c=^2S%)JX&1L;qADX= z?V_pC_npBCWm_l)D(cD6u`1uKy`W}W5lhord`cJ03YAzM%4+kAW;#1#vCQ~Z=h05J z!@J7L&XvPT`?Lvpd83?YQc~2i0B0XaVeQH-d0z-tln_)64s&mYu8Urs3+*l}Zo3(< zPWg?jk&Rv}Jw&rQ+IX$1aazgRYgpykaxG)_rIx?TaO-7%uxs}!-^f{oe{lotf(5IJ zx)!RgIk`F>Rz76$s(T=9yCfy6Zf-c+=M|hT?AWB1H{d&H>Y`)ce!IEMq$%99dQYkAk z<8FuHa$(S^!^zS9^k5>DT%Q<8rae03+Oevzu)CH!y3pXd2CBVbmv!|woE^noOtGs} zpOCB^NUuw8S(g~UI5}Q_?p?_3oL8cpxpu)ia_oY2=C%@D$SIYg_lc}ZCi){|>EtP! z(qqG0Gs%K(Rq##)tG(jTvf3+7Rja&HTi?QE+;&-(ak23=^ewznUEjhhHT1RLDbrUL zZ6j_1&*3E2!>@E>YY44X4WPZ1bZY8~cc|%#ck1Z&Lb-yj2e%hAbiM67R zt(oM6cS^e`>!&<-y>;r)7#(6&xlW!gNPNT;pifn!6X17oUMK|o_UE0rRLSf+>E z1=)2~TGis86kCE-T(Qj)iCAjucrvzaXgsquF)S^+pl8iCMdvA*Q0(pIwU=a87L%(W z8kk*+HA@F;T{f2O?ef`cQY%ZSil}H4w zyUFT^y%)q*mQXcWizp`B{e6y$sfl72ET)32dWxykGvC{#({yQ-g;FM>p0cR!^6%|( z*{hN(%czF9y2{IS&av0aYHvw(DJOSPb(N9(L}#xT(q0qmQc6{1)l*DvbFIByMtezS zWdXSgs;hL|CV6|kc=nP|m-2CyR4Aa5d5JXxiB|i~5s$mkn$T4I;Oe>;nN~+z?5eo6 zMfzGVXn0TMDs7Rv8d7Cfalne?HQ#XKQScb5@+f^nb5OZrs1@RdP^nc{@0@}ccu6_A z8)S$2HC}L*z-fu$p-tn7Omcm4Yi2w#>|{~JoLNG&1az8QxhzFFR7ESbYiloNp(;MM zWnv02=(@yrTFE|-=52Q~W8cmE}8qJI+ z6C?5stQNYfCc0-lxj8wsE!p2Y99gs3$){^59ov=|9_klOdxxB;G7UOAw;iWJ%l*_5|-l7E-zWpsA(0+S88BeFxW)f;2Iy$f)2BbSd93y{%Q%9k*!6rcOs-ipwjc7d-F z^%r9mG4&K>H5ML<6T@1%US6dz-QM+9lx~y@6l5w>Yzi{i=m1cnXXlCxQP8peL?%&u z-(&lAVOdwPyEE`m=qgygdsivx!VBJtxA6J`Wmmi4 zCkrl*<>kqevhpaY?}A0Ci&0q&SAj{j=&-7-sxGN+zEMGR*MP(GweD?pI(NP9&TgG0 zS2|0ujcPr4sNw}R#>>^iX^d4>&!r)DrQS(HEUGvj&ERGG6b7-j)}coPS|+DcRfHJ(5fh zmKIPA39{47gMqEcbMahVDZmW5ZGEEe)6yR2o5Vz;m5DOK+0W0j_M z3{fo^YopzQCQw-jENO7UBRT@up(<~VWPi3Hl7@? zj$|&HgDO?wDErg8DrV6TRTh1bY{AmE;?W#4^Tx`jQ%^fA%B_r@SgDuO$;KMnIH{}^ zU9nhlG_h$oY1iw(_}EA+GdPrv4JL?d+V^wTOzU8O>6w>K6`qJgrlXwTd27Xd3qV zPf6^g3{6Ejy_<)7Bhjt`iB`|vRI;l~Ng71G_32z~-|I?OHsHNjLJ~+@+CKN9Fsobi z52aIMa;=;L2gak)^4wgsTd5 zcohu}jEfap(^hz7-pt|BRb&Grrz@Yz>hPet`yl$sU}1qChk|O1>KP2G@OW;AgDOf?TA-@a%mxR9 z#S5<4(LA&FU|BuSp{fXNU|6Wasj^x;sNcZA&_wZd4h_weT;Jf(M9B>g56#rU+YHFV z0MSIH>K`I1Kl$v%;i0xLk3)kSlX`{)7y9RRNN`c0((W{SI9Qy}nnQsn)*kGr=V8D_ zU;{&d3zy1j@St`B=D$gz>oon%l3JhHZ<5r8O@6cFH_hBPNul~peOXXMA&{XYh?1q@ z%1<-(d=;xlQk~xc_+V2_LS+wbwjphT(T8&j@)p*E21ID$P z^6Rv)O%+|AnQf-%hHY(Am1vs5ZKhU>wz}@I@_WPT)|Au~t6Pvz*|qnw)ony_Jyy2{ ze-B2hQL9^!x_GNwU{zT)9x||atGg)qi?h0m6TK*_yC~7kvbu{?VsBa9MQK&P)h&x5 zZoZ4_K|o_Rwv2IQ|82HuZCqeYi@G3N4|d42AuSWufc-4vP+9CA)NR0QHbHEi*0LEA z>obx~kl3(oY=-QnnZzb2Q@$SX1lw7CPZJy-D zEp8KaXr8rgo>C21+A0S*?FCa?holB=Z50WXy}IefcJY#HTU*ckJ=iPn=C+E|M(k}B zR+UxbAp?yV+$PGe*Wxx)be$%*iJ}{~xy@9fc}BO1TGelL34AU+_M-VF*Rz<$?5!*N z${yWpW4lbcAT@rMV{3U;^777F+8RG`z!gEzA#w%0&#EU+ZY{ZWw=yhW!)6tuf3DQ$xM#c z>a-5au6hF4rVYhIsEP-sRUWEs-AK4$$n6NQ>JqHuojgjY#b%&aPV)AoXy>t3ek%@# zt0WgIUp=3-ju3O6bW-I6v7)Y4CAc3TwsJga9U(Rn?JS%aVV#ENJZ{W+7H0*rbR<}5 z*K);sg)_^_ns%vT&ZbkA0k@;cY6y3Iz1VBzj_=d;JP$qcrj(#F*1OHR=UcA255)>+ z(M1!L=cI9lRIvanuGzIp!%d+ocemT;dbmm}&2#a#-%W{hvbrQk*6Y!4MQmNHKUui^ zJreDV70!;UBby~x5;vBCG9Ja6tWrvj7pmX6Tv0J4BaWZE=MuBHW};0uws*` zM^xEqW3^i{&6pMywJu)g9Gh5T>x4_&w;R4Rt=K?fIGwcaO)PLI_M6Jm+vYeatil2~ z$4n1>TTfcMo5f#PdS%We@6*=)>)2);1 zlH-YVGM321lB4~Wr6`y&FDzCLji!_1nO49A8t$?=4ovMLtHI#6|F z!x~5b@g!n}C{{48jxOgWr?UO_7KKE+tb<);{H{1buxWfN4i)rL5i&&>7#bdqEvh&x z`);Q=D#fGuikTc4!iJQ%3{}d?n(4Zfwsq`ZZ|5=nLjwcJ@#N@cwK*d5lvwn1yCp?4 zU0t!**3qHSq0CTXc<8cZzjaPxEa9$0S4;--jiK1aShQQ3jg01K@1QQX)C$ zw=>r3bwn>Ec8=*{qBoDFE{$zUY`*xC#CU(TtU2>#_p~*|dT}de?~S@!yoo|ziDtT` zO}Wz-T%`?bif1=QGhN-~HDxu+|KHp7HMfo9@ILp$+*tqZ$F}R+?KJJ=qRhT(ltfB= zO`J@m?(N;DzX2(dAOK2~o%X4_F#v)92>t*{)G^>I0HBko>kWuXl(SOjEL27D__X%l zKDB@APvV$`UXR&R`}gOk{neN220~&fOZ&dDj!l}cjRism1o6$6a9it8AStG2!faw{ z4uv6)XUFb8M#X3SDI6j(3{#dS5{Dyv{Z}Fh%8anYx=C)Gn=ZeYF!0t!gr#^OK?J(J z(!p(CZ|k((bamhLi}MKz%5>xBF}*s(B?fB)Y-tvTg<@$LT4NU3`N~mAYC;moOE%4? z=i@NG{f~7 zm!=JgXM5Yew6}-RyV}v)Z6&T0?p-yZ*0e{x7k$RTDr|L^09%v+_PU(9Zt9Q5rBT`z zH{Jc=XhSKA$Adn2tuJZI?Q9LB_tR-A@DEhXJCf+?F^Ao3ahxx)1HosyK3~GvTR>lW zbN{WWKUo_(4Y|xIqf!$_un-ICSqbJzrHy$JxOnlk09)!Y+4{LdS$KsmY-?74!zB%( z4MrL(GKMEb&KF76=&?1-S9wF_j;;B-G%ffzfcATd?gXwOX|0%v!AvS^DGS2L^_Z5g z?eXsUscCO-yJ4`UM>Dqf`=Hwkt5em~r4^1gE-4rA0ClVD%o4-1zATj$7~CDn{^BOt zBX=y#Xm%YJkQ|h$pc2Sn9T!dgLErqXQ2IZcdT)$J9m+*RF(^cWx7AB#=aOU#`6zf% zY@|1d)KlV*Pv8=*iXG%e3ldPj=8R}mwbPXAr$lc@fUv@;jd4f;R2KpUS8bxzaWu^ifkBN;pfJyZzfhH87EeT(4GxV z)e|VVf-QivRL1eG&_!l4%O}8Re&Zmp1HY*PQ(jY}+m$x6bN{r#qxXQ|Q zP?3lb(Jw6ef{2I#TLuK)Oxc}`6@Xmk7{I4qbFeeR84#|sT=a1c8Grz>Kz7ma!e^E3 zEj8Z=!k-lrTJfwr*NVHYu-jQU6q>TS-ez2R9>c|m)nCU6bEKnF+S zo$ucuz3r!y+*!K))V7!IE>^)T)As_yrZ1|*z%m~3ir00&Vv zx|X6=Nka$l!fAuj32S5u`6jB)Pt%c60Zd)r7m}FP!CUr&-6mUT=F4- zwTZZ2k`!WzL#WeLQCRh*roI!)TKI!GLO?M{;X^&phP~y&J-kb6N(&TeN#;lb$sNlB zaZ%0pdRUKVfQJJZo|v8KPHd#BU^$^}&iH~B^P1#3FymU{idIE`=0*PrIIbSlDKt9c zbTJ^qw&40cAEniU>JVsU3OI8$xnoK^Q4eF(g9p~845&5)E6?R^Va<@uBZQ|yhLCcQ zgHMbr5FUVlw(3E^m*+e9Oy`3f%Q#sJy`*!AArb5ZXg|QEPN_v7EtRzl0;lUHvFc{ppcbT;RkUKzyb#ww z$3g>EK{L%KjsOV21+~B$RbO+y(;jW}<|zaRufEyH)=jg^IU1AcNCyj=MvgW7Don=Z z1l{3P>wJFl11iA>+OSD~8jd@hb|&NLCg;ZUYkh9P5RPN7WgJ$W#^Bk|#sE`-*8Zz9 z2SE*?z5yFlw()!$VM$0JjDuBj?y~i#lR3>vIP#jrTEn{W$@94q*%9NTqv8|A1NMq7i5 z^^}CR@Jdu1bWRj12JW*_aefvmMm86K&q5i4m{+4>Z|>_*F&_3UP%#8g@cmSbQ^~Fb zeCtrL;|T0ucr>;+B2S$-- z^0QDesC8C%69oJkR171^H9-CA-_ z88-*vF*`iSNnB2PoSm&T(8xrFr^jN)-zRsXgvp}b>S<^dDwZmlKWN`l zfuV@>i4LBgM>^jjxGt6`{zw?p8wKmOkl;Ip^ZJy9w=se!cV@2_z=(ib6$2CG*<=9mRAl zq;2i@{24;w=19gbbkoCLRI@+;s67=<^>G4zh&~o&zI_f7gnxj^}&l*$AcC%oQ>TzKqyh8xm zU}a3SE7Bi8EM8esy57gU}OX+1r?G`JoA`@lrF`h@`FG zKH`mmo-}$b4{p3(8r#`3clNM@p{U0sX`1o%hj$4eigfxsM!&up+e?P#%~xZV+29Pf zTKgw8C*yoO_4&sQ2$yV*dMwTDJwSgA7g>>UzJdMC9Y5+y1&;-IGJ(fEevsJvbb7d9 zm_3wx!b79I+HqFAIS^Xa^1}ql(p?(v_0C@1t$+Ci-oh@)YXiMkak~`D1CJD${RF*? zt7m!1_7G}v1}re&)TB(IAe!zLq2wla>bpDDiwHBfrOY_hyB#GwN~zz}RZ$vs8%of7 z&?mIO=q><^9K}O`Lc)3&yaT$*`@z6S+S7%fb%WDTD43mjV8e?l;w0GD+ILpsPAYNDpED)R$R5{OdW0WVEWo^~5|d>t+c|5xMWhxy1 z0VgD-y50V3`})0mUE=_Oah35of08~aEt;c>1KaC>;9^=5gL496Y=M9nnp03Ca|&!= z@TmSC+aFzfclkVYA8v-{#~&lzGXhjZ;Sd$kIzUEM50K64f7Ly<2`di8!J+L9lMlK!t$H~&|nQur&>B!a_)MAaEWqIQ^) zP9aQ5rz3vml}<$@qSKIx=oCaMroKo;RhK`ONOhN~sPX|Ss=Z9c)R)PK{u99SKDS7e zmq>`_A_-AjqMHfa$UdCo@Zyvkq-9__{VSJtbNv40(cTJ<7khn;S2PNXeU+v)$IQCcSYgEF^yN)s)KA~ldUEj{*=Qg@PMY_*;Ke>eS`Z0r#5lprYk`Pe^eElDwAbab=V46x6} zF-+!gnhtS%nI35W-|qMJf01KNJlgd0)Rk=FcE7k4OGsZZR zEaSnXG=HgB{3aXrr=mjE?T*eJ^`NgVq%L9P+8N8_m6qf`3!NziGj3l?pyjOTkv9V5?HFK_%E~ z$LE#_{>TJ>WP+Wu)a-!LkAfjHVUk(Not2e>qCya7LeS+x;N%H6WS;Ob@`M{VPX*qb z8#fo6nhOrhg+-eSi#FeKyX0FQ#KndOXCe5b5RzGu@`YR!e7=R?k3yJ7QSrboDsI!F z;sIM!f-XWrDkK|hg)xLARqVJ;iyil6A;eX=;rf*+w|$v$AC);Dw-o$aZn-X{FyC@3 zj4RBy6y{qB^R0v!s)YGg!aAxFKHo|JW>v!NSEYOnR)S3{A(vD_Qm%xgT;<$WRUwR9 zRPXkGH2rJ))NSm4B@PR&se8;3Q`!#~;gZLr@~a;2r?mwSWQ@8knwod z)iNGyd^sbrStt#)EUgFpOUA&b3k8ceLR85iam-g89>auFhk66z3%DpZOHw2PI8z!NM!1looA132EjfB*jd{{hyYG8_N^ literal 0 HcmV?d00001 From 6f08df9e83bc04a33692dbe3b8112eb6551b7934 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 3 Sep 2013 10:03:15 -0700 Subject: [PATCH 17/31] Link in Faceshift. --- interface/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index b1333f5a6b..12ee274053 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -8,6 +8,7 @@ project(${TARGET_NAME}) # setup for find modules set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/") +set(FACESHIFT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/faceshift) set(LIBOVR_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/LibOVR) set(LIBVPX_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/LibVPX) set(LEAP_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/Leap) @@ -90,6 +91,7 @@ link_hifi_library(avatars ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR}) # find required libraries +find_package(Faceshift) find_package(GLM REQUIRED) find_package(LibOVR) find_package(LibVPX) @@ -120,6 +122,7 @@ include_directories( # use system flag so warnings are supressed include_directories( SYSTEM + ${FACESHIFT_INCLUDE_DIRS} ${GLM_INCLUDE_DIRS} ${LIBOVR_INCLUDE_DIRS} ${LIBVPX_INCLUDE_DIRS} @@ -131,6 +134,7 @@ include_directories( SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${OPENCV_INCLUDE_DIRS}") target_link_libraries( ${TARGET_NAME} + ${FACESHIFT_LIBRARIES} ${LIBVPX_LIBRARIES} ${MOTIONDRIVER_LIBRARIES} ${OPENCV_LIBRARIES} From 213a8fcd639ef6955dcbb17a0032ad6fc03a53ba Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 3 Sep 2013 11:52:32 -0700 Subject: [PATCH 18/31] Working on Faceshift integration. --- cmake/modules/FindFaceshift.cmake | 44 +++++++++++++++++++++++++++++++ interface/CMakeLists.txt | 2 +- interface/src/Application.h | 4 +++ interface/src/Menu.cpp | 7 +++++ interface/src/Menu.h | 1 + 5 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 cmake/modules/FindFaceshift.cmake diff --git a/cmake/modules/FindFaceshift.cmake b/cmake/modules/FindFaceshift.cmake new file mode 100644 index 0000000000..64151e6b1b --- /dev/null +++ b/cmake/modules/FindFaceshift.cmake @@ -0,0 +1,44 @@ +# Try to find the Faceshift networking library +# +# You must provide a FACESHIFT_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# FACESHIFT_FOUND - system found Faceshift +# FACESHIFT_INCLUDE_DIRS - the Faceshift include directory +# FACESHIFT_LIBRARIES - Link this to use Faceshift +# +# Created on 8/30/2013 by Andrzej Kapolka +# Copyright (c) 2013 High Fidelity +# + +if (FACESHIFT_LIBRARIES AND FACESHIFT_INCLUDE_DIRS) + # in cache already + set(FACESHIFT_FOUND TRUE) +else (FACESHIFT_LIBRARIES AND FACESHIFT_INCLUDE_DIRS) + find_path(FACESHIFT_INCLUDE_DIRS fsbinarystream.h ${FACESHIFT_ROOT_DIR}/include) + + if (APPLE) + find_library(FACESHIFT_LIBRARIES libfaceshift.a ${FACESHIFT_ROOT_DIR}/lib/MacOS/) + elseif (UNIX) + find_library(FACESHIFT_LIBRARIES libfaceshift.a ${FACESHIFT_ROOT_DIR}/lib/UNIX/) + endif () + + if (FACESHIFT_INCLUDE_DIRS AND FACESHIFT_LIBRARIES) + set(FACESHIFT_FOUND TRUE) + endif (FACESHIFT_INCLUDE_DIRS AND FACESHIFT_LIBRARIES) + + if (FACESHIFT_FOUND) + if (NOT FACESHIFT_FIND_QUIETLY) + message(STATUS "Found Faceshift: ${FACESHIFT_LIBRARIES}") + endif (NOT FACESHIFT_FIND_QUIETLY) + else (FACESHIFT_FOUND) + if (FACESHIFT_FIND_REQUIRED) + message(FATAL_ERROR "Could not find Faceshift") + endif (FACESHIFT_FIND_REQUIRED) + endif (FACESHIFT_FOUND) + + # show the FACESHIFT_INCLUDE_DIRS and FACESHIFT_LIBRARIES variables only in the advanced view + mark_as_advanced(FACESHIFT_INCLUDE_DIRS FACESHIFT_LIBRARIES) + +endif (FACESHIFT_LIBRARIES AND FACESHIFT_INCLUDE_DIRS) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 12ee274053..d849114dd7 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -38,7 +38,7 @@ configure_file(InterfaceConfig.h.in ${PROJECT_BINARY_DIR}/includes/InterfaceConf # grab the implementation and header files from src dirs file(GLOB INTERFACE_SRCS src/*.cpp src/*.h) -foreach(SUBDIR avatar ui renderer) +foreach(SUBDIR avatar devices renderer ui) file(GLOB SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h) set(INTERFACE_SRCS ${INTERFACE_SRCS} ${SUBDIR_SRCS}) endforeach(SUBDIR) diff --git a/interface/src/Application.h b/interface/src/Application.h index dfb6079e81..01d2104438 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -47,6 +47,7 @@ #include "avatar/Avatar.h" #include "avatar/MyAvatar.h" #include "avatar/HandControl.h" +#include "devices/Faceshift.h" #include "renderer/AmbientOcclusionEffect.h" #include "renderer/GeometryCache.h" #include "renderer/GlowEffect.h" @@ -117,6 +118,7 @@ public: Environment* getEnvironment() { return &_environment; } SerialInterface* getSerialHeadSensor() { return &_serialHeadSensor; } Webcam* getWebcam() { return &_webcam; } + Faceshift* getFaceshift() { return &_faceshift; } BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; } QSettings* getSettings() { return _settings; } Swatch* getSwatch() { return &_swatch; } @@ -259,6 +261,8 @@ private: Webcam _webcam; // The webcam interface + Faceshift _faceshift; + Camera _myCamera; // My view onto the world Camera _viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index b48357218c..c3882bdcd9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -360,6 +360,13 @@ Menu::Menu() : appInstance->getWebcam()->getGrabber(), SLOT(setDepthOnly(bool))); + addCheckableActionToQMenuAndActionHash(developerMenu, + MenuOption::Faceshift, + 0, + false, + appInstance->getFaceshift(), + SLOT(setEnabled(bool))); + QMenu* audioDebugMenu = developerMenu->addMenu("Audio Debugging Tools"); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoAudio); addActionToQMenuAndActionHash(audioDebugMenu, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index d76cf0909b..f93101de87 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -133,6 +133,7 @@ namespace MenuOption { const QString ExportVoxels = "Export Voxels"; const QString HeadMouse = "Head Mouse"; const QString FaceMode = "Cycle Face Mode"; + const QString Faceshift = "Faceshift"; const QString FalseColorByDistance = "FALSE Color By Distance"; const QString FalseColorBySource = "FALSE Color By Source"; const QString FalseColorEveryOtherVoxel = "FALSE Color Every Other Randomly"; From 734cb83e819a51b7cc2dda3eaaaf50c424306ee8 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 3 Sep 2013 13:56:04 -0700 Subject: [PATCH 19/31] Read Faceshift head translation/rotation. --- interface/src/avatar/MyAvatar.cpp | 11 ++++++- interface/src/devices/Faceshift.cpp | 51 +++++++++++++++++++++++++++++ interface/src/devices/Faceshift.h | 51 +++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 interface/src/devices/Faceshift.cpp create mode 100644 interface/src/devices/Faceshift.h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 34f4d5caa4..79dedc45b1 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -333,16 +333,25 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter, float gyroCam // Update avatar head rotation with sensor data void MyAvatar::updateFromGyrosAndOrWebcam(bool gyroLook, float pitchFromTouch) { + Faceshift* faceshift = Application::getInstance()->getFaceshift(); SerialInterface* gyros = Application::getInstance()->getSerialHeadSensor(); Webcam* webcam = Application::getInstance()->getWebcam(); glm::vec3 estimatedPosition, estimatedRotation; - if (gyros->isActive()) { + + if (faceshift->isActive()) { + estimatedPosition = faceshift->getHeadTranslation(); + estimatedRotation = safeEulerAngles(faceshift->getHeadRotation()); + + } else if (gyros->isActive()) { estimatedRotation = gyros->getEstimatedRotation(); + } else if (webcam->isActive()) { estimatedRotation = webcam->getEstimatedRotation(); + } else if (_leadingAvatar) { _head.getFace().clearFrame(); return; + } else { _head.setMousePitch(pitchFromTouch); _head.setPitch(pitchFromTouch); diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp new file mode 100644 index 0000000000..f9f2a64d46 --- /dev/null +++ b/interface/src/devices/Faceshift.cpp @@ -0,0 +1,51 @@ +// +// Faceshift.cpp +// interface +// +// Created by Andrzej Kapolka on 9/3/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include "Faceshift.h" + +using namespace fs; + +Faceshift::Faceshift() : _enabled(false) { + connect(&_socket, SIGNAL(readyRead()), SLOT(readFromSocket())); + connect(&_socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(connectSocket())); +} + +void Faceshift::setEnabled(bool enabled) { + if (_enabled = enabled) { + connectSocket(); + + } else { + _socket.disconnectFromHost(); + } +} + +void Faceshift::connectSocket() { + if (_enabled) { + const quint16 FACESHIFT_PORT = 33433; + _socket.connectToHost("localhost", FACESHIFT_PORT); + } +} + +void Faceshift::readFromSocket() { + QByteArray buffer = _socket.readAll(); + _stream.received(buffer.size(), buffer.constData()); + fsMsgPtr msg; + for (fsMsgPtr msg; msg = _stream.get_message(); ) { + switch (msg->id()) { + case fsMsg::MSG_OUT_TRACKING_STATE: + const fsTrackingData& data = static_cast(msg.get())->tracking_data(); + if (data.m_trackingSuccessful) { + _headRotation = glm::quat(data.m_headRotation.w, data.m_headRotation.x, + data.m_headRotation.y, data.m_headRotation.z); + _headTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y, data.m_headTranslation.z); + } + break; + } + } +} + diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h new file mode 100644 index 0000000000..c8cc07951a --- /dev/null +++ b/interface/src/devices/Faceshift.h @@ -0,0 +1,51 @@ +// +// Faceshift.h +// interface +// +// Created by Andrzej Kapolka on 9/3/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__Faceshift__ +#define __interface__Faceshift__ + +#include + +#include +#include + +#include + +/// Handles interaction with the Faceshift software, which provides head position/orientation and facial features. +class Faceshift : public QObject { + Q_OBJECT + +public: + + Faceshift(); + + bool isActive() const { return _socket.state() == QAbstractSocket::ConnectedState; } + + const glm::quat& getHeadRotation() const { return _headRotation; } + const glm::vec3& getHeadTranslation() const { return _headTranslation; } + +public slots: + + void setEnabled(bool enabled); + +private slots: + + void connectSocket(); + void readFromSocket(); + +private: + + QTcpSocket _socket; + fs::fsBinaryStream _stream; + bool _enabled; + + glm::quat _headRotation; + glm::vec3 _headTranslation; +}; + +#endif /* defined(__interface__Faceshift__) */ From b406dc7311c362d867662be0bfc43d7b3d5b58a0 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 3 Sep 2013 14:20:47 -0700 Subject: [PATCH 20/31] Log when attempting to connect and on failure. Wait a second before attempting to reconnect. --- interface/src/devices/Faceshift.cpp | 20 +++++++++++++++++++- interface/src/devices/Faceshift.h | 2 ++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index f9f2a64d46..9d75418e5e 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -6,13 +6,16 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // +#include + #include "Faceshift.h" using namespace fs; Faceshift::Faceshift() : _enabled(false) { + connect(&_socket, SIGNAL(connected()), SLOT(noteConnected())); + connect(&_socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(noteError(QAbstractSocket::SocketError))); connect(&_socket, SIGNAL(readyRead()), SLOT(readFromSocket())); - connect(&_socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(connectSocket())); } void Faceshift::setEnabled(bool enabled) { @@ -26,11 +29,26 @@ void Faceshift::setEnabled(bool enabled) { void Faceshift::connectSocket() { if (_enabled) { + qDebug("Faceshift: Connecting...\n"); + const quint16 FACESHIFT_PORT = 33433; _socket.connectToHost("localhost", FACESHIFT_PORT); } } +void Faceshift::noteConnected() { + qDebug("Faceshift: Connected.\n"); +} + +void Faceshift::noteError(QAbstractSocket::SocketError error) { + qDebug() << "Faceshift: " << _socket.errorString() << "\n"; + + // reconnect after a delay + if (_enabled) { + QTimer::singleShot(1000, this, SLOT(connectSocket())); + } +} + void Faceshift::readFromSocket() { QByteArray buffer = _socket.readAll(); _stream.received(buffer.size(), buffer.constData()); diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h index c8cc07951a..d89e68b04a 100644 --- a/interface/src/devices/Faceshift.h +++ b/interface/src/devices/Faceshift.h @@ -36,6 +36,8 @@ public slots: private slots: void connectSocket(); + void noteConnected(); + void noteError(QAbstractSocket::SocketError error); void readFromSocket(); private: From 6a3b2c7ad0be932e41a84a8dc73ec5c8866e9e1b Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 3 Sep 2013 16:44:06 -0700 Subject: [PATCH 21/31] Get the blendshape names and read the blink amounts. --- interface/src/devices/Faceshift.cpp | 39 +++++++++++++++++++++++++++-- interface/src/devices/Faceshift.h | 23 +++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 9d75418e5e..f87b04bcb2 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -11,8 +11,11 @@ #include "Faceshift.h" using namespace fs; +using namespace std; -Faceshift::Faceshift() : _enabled(false) { +Faceshift::Faceshift() : _enabled(false), _eyeGazeLeftPitch(0.0f), _eyeGazeLeftYaw(0.0f), + _eyeGazeRightPitch(0.0f), _eyeGazeRightYaw(0.0f), _leftBlink(0.0f), _rightBlink(0.0f), + _leftBlinkIndex(-1), _rightBlinkIndex(-1) { connect(&_socket, SIGNAL(connected()), SLOT(noteConnected())); connect(&_socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(noteError(QAbstractSocket::SocketError))); connect(&_socket, SIGNAL(readyRead()), SLOT(readFromSocket())); @@ -38,6 +41,11 @@ void Faceshift::connectSocket() { void Faceshift::noteConnected() { qDebug("Faceshift: Connected.\n"); + + // request the list of blendshape names + string message; + fsBinaryStream::encode_message(message, fsMsgSendBlendshapeNames()); + send(message); } void Faceshift::noteError(QAbstractSocket::SocketError error) { @@ -55,15 +63,42 @@ void Faceshift::readFromSocket() { fsMsgPtr msg; for (fsMsgPtr msg; msg = _stream.get_message(); ) { switch (msg->id()) { - case fsMsg::MSG_OUT_TRACKING_STATE: + case fsMsg::MSG_OUT_TRACKING_STATE: { const fsTrackingData& data = static_cast(msg.get())->tracking_data(); if (data.m_trackingSuccessful) { _headRotation = glm::quat(data.m_headRotation.w, data.m_headRotation.x, data.m_headRotation.y, data.m_headRotation.z); _headTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y, data.m_headTranslation.z); + _eyeGazeLeftPitch = data.m_eyeGazeLeftPitch; + _eyeGazeLeftYaw = data.m_eyeGazeLeftYaw; + _eyeGazeRightPitch = data.m_eyeGazeRightPitch; + _eyeGazeRightYaw = data.m_eyeGazeRightYaw; + + if (_leftBlinkIndex != -1) { + _leftBlink = data.m_coeffs[_leftBlinkIndex]; + } + if (_rightBlinkIndex != -1) { + _rightBlink = data.m_coeffs[_rightBlinkIndex]; + } } break; + } + case fsMsg::MSG_OUT_BLENDSHAPE_NAMES: { + const vector& names = static_cast(msg.get())->blendshape_names(); + for (int i = 0; i < names.size(); i++) { + if (names[i] == "EyeBlink_L") { + _leftBlinkIndex = i; + + } else if (names[i] == "EyeBlink_R") { + _rightBlinkIndex = i; + } + } + break; + } } } } +void Faceshift::send(const std::string& message) { + _socket.write(message.data(), message.size()); +} diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h index d89e68b04a..88f115564c 100644 --- a/interface/src/devices/Faceshift.h +++ b/interface/src/devices/Faceshift.h @@ -29,6 +29,15 @@ public: const glm::quat& getHeadRotation() const { return _headRotation; } const glm::vec3& getHeadTranslation() const { return _headTranslation; } + float getEyeGazeLeftPitch() const { return _eyeGazeLeftPitch; } + float getEyeGazeLeftYaw() const { return _eyeGazeLeftYaw; } + + float getEyeGazeRightPitch() const { return _eyeGazeRightPitch; } + float getEyeGazeRightYaw() const { return _eyeGazeRightYaw; } + + float getLeftBlink() const { return _leftBlink; } + float getRightBlink() const { return _rightBlink; } + public slots: void setEnabled(bool enabled); @@ -42,12 +51,26 @@ private slots: private: + void send(const std::string& message); + QTcpSocket _socket; fs::fsBinaryStream _stream; bool _enabled; glm::quat _headRotation; glm::vec3 _headTranslation; + + float _eyeGazeLeftPitch; + float _eyeGazeLeftYaw; + + float _eyeGazeRightPitch; + float _eyeGazeRightYaw; + + float _leftBlink; + float _rightBlink; + + int _leftBlinkIndex; + int _rightBlinkIndex; }; #endif /* defined(__interface__Faceshift__) */ From 650e9c4267f98f1e166ece158ca6c807064b698c Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 3 Sep 2013 18:15:51 -0700 Subject: [PATCH 22/31] Silencio! --- interface/src/devices/Faceshift.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index f87b04bcb2..e1d1670eeb 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -22,7 +22,7 @@ Faceshift::Faceshift() : _enabled(false), _eyeGazeLeftPitch(0.0f), _eyeGazeLeftY } void Faceshift::setEnabled(bool enabled) { - if (_enabled = enabled) { + if ((_enabled = enabled)) { connectSocket(); } else { @@ -61,7 +61,7 @@ void Faceshift::readFromSocket() { QByteArray buffer = _socket.readAll(); _stream.received(buffer.size(), buffer.constData()); fsMsgPtr msg; - for (fsMsgPtr msg; msg = _stream.get_message(); ) { + for (fsMsgPtr msg; (msg = _stream.get_message()); ) { switch (msg->id()) { case fsMsg::MSG_OUT_TRACKING_STATE: { const fsTrackingData& data = static_cast(msg.get())->tracking_data(); @@ -95,6 +95,8 @@ void Faceshift::readFromSocket() { } break; } + default: + break; } } } From 34230536121aeaebc9e0dc38c6d777c6991b158a Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 3 Sep 2013 18:23:02 -0700 Subject: [PATCH 23/31] Coordinate system tweaks. --- interface/src/devices/Faceshift.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index e1d1670eeb..d41d4084a1 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -66,9 +66,11 @@ void Faceshift::readFromSocket() { case fsMsg::MSG_OUT_TRACKING_STATE: { const fsTrackingData& data = static_cast(msg.get())->tracking_data(); if (data.m_trackingSuccessful) { - _headRotation = glm::quat(data.m_headRotation.w, data.m_headRotation.x, - data.m_headRotation.y, data.m_headRotation.z); - _headTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y, data.m_headTranslation.z); + _headRotation = glm::quat(data.m_headRotation.w, -data.m_headRotation.x, + data.m_headRotation.y, -data.m_headRotation.z); + const float TRANSLATION_SCALE = 0.1f; + _headTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y, + -data.m_headTranslation.z) * TRANSLATION_SCALE; _eyeGazeLeftPitch = data.m_eyeGazeLeftPitch; _eyeGazeLeftYaw = data.m_eyeGazeLeftYaw; _eyeGazeRightPitch = data.m_eyeGazeRightPitch; From 697484d0bbec820c0897de3ddc6dfc0fab3298e8 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 3 Sep 2013 18:29:46 -0700 Subject: [PATCH 24/31] Another coordinate tweak. --- interface/src/devices/Faceshift.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index d41d4084a1..50fac303da 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -68,7 +68,7 @@ void Faceshift::readFromSocket() { if (data.m_trackingSuccessful) { _headRotation = glm::quat(data.m_headRotation.w, -data.m_headRotation.x, data.m_headRotation.y, -data.m_headRotation.z); - const float TRANSLATION_SCALE = 0.1f; + const float TRANSLATION_SCALE = 0.02f; _headTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y, -data.m_headTranslation.z) * TRANSLATION_SCALE; _eyeGazeLeftPitch = data.m_eyeGazeLeftPitch; From 0d593c4a3f09e07c7eddc72f793553f0bcf888da Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 4 Sep 2013 10:46:11 -0700 Subject: [PATCH 25/31] When Faceshift is active, use its eye directions rather than the mouse ray. --- interface/src/Application.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3f0d418fd3..80e4995c61 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1518,7 +1518,17 @@ void Application::update(float deltaTime) { // Set where I am looking based on my mouse ray (so that other people can see) glm::vec3 lookAtSpot; - _isLookingAtOtherAvatar = isLookingAtOtherAvatar(mouseRayOrigin, mouseRayDirection, lookAtSpot); + // if we have faceshift, use that to compute the lookat direction + glm::vec3 lookAtRayOrigin = mouseRayOrigin, lookAtRayDirection = mouseRayDirection; + if (_faceshift.isActive()) { + lookAtRayOrigin = _myAvatar.getHead().calculateAverageEyePosition(); + float averagePitch = (_faceshift.getEyeGazeLeftPitch() + _faceshift.getEyeGazeRightPitch()) / 2.0f; + float averageYaw = (_faceshift.getEyeGazeLeftYaw() + _faceshift.getEyeGazeRightYaw()) / 2.0f; + lookAtRayDirection = _myAvatar.getHead().getOrientation() * + glm::quat(glm::vec3(averagePitch, averageYaw, 0.0f)) * glm::vec3(0.0f, 0.0f, -1.0f); + } + + _isLookingAtOtherAvatar = isLookingAtOtherAvatar(lookAtRayOrigin, lookAtRayDirection, lookAtSpot); if (_isLookingAtOtherAvatar) { // If the mouse is over another avatar's head... _myAvatar.getHead().setLookAtPosition(lookAtSpot); From 2c2f2fad5a45c3311c04c9c7ad151d369b893bd6 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 4 Sep 2013 10:53:07 -0700 Subject: [PATCH 26/31] Lookat adjustment. --- interface/src/Application.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 80e4995c61..7756ddeb32 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1525,21 +1525,21 @@ void Application::update(float deltaTime) { float averagePitch = (_faceshift.getEyeGazeLeftPitch() + _faceshift.getEyeGazeRightPitch()) / 2.0f; float averageYaw = (_faceshift.getEyeGazeLeftYaw() + _faceshift.getEyeGazeRightYaw()) / 2.0f; lookAtRayDirection = _myAvatar.getHead().getOrientation() * - glm::quat(glm::vec3(averagePitch, averageYaw, 0.0f)) * glm::vec3(0.0f, 0.0f, -1.0f); + glm::quat(glm::radians(glm::vec3(averagePitch, averageYaw, 0.0f))) * glm::vec3(0.0f, 0.0f, -1.0f); } _isLookingAtOtherAvatar = isLookingAtOtherAvatar(lookAtRayOrigin, lookAtRayDirection, lookAtSpot); if (_isLookingAtOtherAvatar) { // If the mouse is over another avatar's head... _myAvatar.getHead().setLookAtPosition(lookAtSpot); - } else if (_isHoverVoxel) { + } else if (_isHoverVoxel && !_faceshift.isActive()) { // Look at the hovered voxel lookAtSpot = getMouseVoxelWorldCoordinates(_hoverVoxel); _myAvatar.getHead().setLookAtPosition(lookAtSpot); } else { // Just look in direction of the mouse ray const float FAR_AWAY_STARE = TREE_SCALE; - lookAtSpot = mouseRayOrigin + mouseRayDirection * FAR_AWAY_STARE; + lookAtSpot = lookAtRayOrigin + lookAtRayDirection * FAR_AWAY_STARE; _myAvatar.getHead().setLookAtPosition(lookAtSpot); } From f84d6f4f372da76e56df2b530a46cf16258516ce Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 4 Sep 2013 10:59:53 -0700 Subject: [PATCH 27/31] Fix for eye pitch, track blinking locally. --- interface/src/avatar/Head.cpp | 61 ++++++++++++++++------------- interface/src/devices/Faceshift.cpp | 4 +- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 7ee9a7f245..d93c4eeb32 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -192,36 +192,43 @@ void Head::simulate(float deltaTime, bool isMine, float gyroCameraSensitivity) { _browAudioLift *= 0.7f; // update eyelid blinking - const float BLINK_SPEED = 10.0f; - const float FULLY_OPEN = 0.0f; - const float FULLY_CLOSED = 1.0f; - if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) { - // no blinking when brows are raised; blink less with increasing loudness - const float BASE_BLINK_RATE = 15.0f / 60.0f; - const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; - if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(_averageLoudness) * - ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { - _leftEyeBlinkVelocity = BLINK_SPEED; - _rightEyeBlinkVelocity = BLINK_SPEED; - } + Faceshift* faceshift = Application::getInstance()->getFaceshift(); + if (isMine && faceshift->isActive()) { + _leftEyeBlink = faceshift->getLeftBlink(); + _rightEyeBlink = faceshift->getRightBlink(); + } else { - _leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); - _rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); - - if (_leftEyeBlink == FULLY_CLOSED) { - _leftEyeBlinkVelocity = -BLINK_SPEED; - - } else if (_leftEyeBlink == FULLY_OPEN) { - _leftEyeBlinkVelocity = 0.0f; - } - if (_rightEyeBlink == FULLY_CLOSED) { - _rightEyeBlinkVelocity = -BLINK_SPEED; - - } else if (_rightEyeBlink == FULLY_OPEN) { - _rightEyeBlinkVelocity = 0.0f; + const float BLINK_SPEED = 10.0f; + const float FULLY_OPEN = 0.0f; + const float FULLY_CLOSED = 1.0f; + if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) { + // no blinking when brows are raised; blink less with increasing loudness + const float BASE_BLINK_RATE = 15.0f / 60.0f; + const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; + if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(_averageLoudness) * + ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { + _leftEyeBlinkVelocity = BLINK_SPEED; + _rightEyeBlinkVelocity = BLINK_SPEED; + } + } else { + _leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); + _rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); + + if (_leftEyeBlink == FULLY_CLOSED) { + _leftEyeBlinkVelocity = -BLINK_SPEED; + + } else if (_leftEyeBlink == FULLY_OPEN) { + _leftEyeBlinkVelocity = 0.0f; + } + if (_rightEyeBlink == FULLY_CLOSED) { + _rightEyeBlinkVelocity = -BLINK_SPEED; + + } else if (_rightEyeBlink == FULLY_OPEN) { + _rightEyeBlinkVelocity = 0.0f; + } } } - + // based on the nature of the lookat position, determine if the eyes can look / are looking at it. if (USING_PHYSICAL_MOHAWK) { updateHairPhysics(deltaTime); diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 50fac303da..7db87993cd 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -71,9 +71,9 @@ void Faceshift::readFromSocket() { const float TRANSLATION_SCALE = 0.02f; _headTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y, -data.m_headTranslation.z) * TRANSLATION_SCALE; - _eyeGazeLeftPitch = data.m_eyeGazeLeftPitch; + _eyeGazeLeftPitch = -data.m_eyeGazeLeftPitch; _eyeGazeLeftYaw = data.m_eyeGazeLeftYaw; - _eyeGazeRightPitch = data.m_eyeGazeRightPitch; + _eyeGazeRightPitch = -data.m_eyeGazeRightPitch; _eyeGazeRightYaw = data.m_eyeGazeRightYaw; if (_leftBlinkIndex != -1) { From 679926ce4267664197ce5f2d48d1f61df631d8a7 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 4 Sep 2013 11:40:53 -0700 Subject: [PATCH 28/31] Working on brow/mouth mapping. --- interface/src/Application.cpp | 2 +- interface/src/avatar/Head.cpp | 64 +++++++++++++++-------------- interface/src/devices/Faceshift.cpp | 18 ++++++-- interface/src/devices/Faceshift.h | 12 ++++++ 4 files changed, 62 insertions(+), 34 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7756ddeb32..c966ed4bf2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2276,7 +2276,7 @@ void Application::displaySide(Camera& whichCamera) { } // Render my own Avatar - if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { + if (_myCamera.getMode() == CAMERA_MODE_MIRROR && !_faceshift.isActive()) { _myAvatar.getHead().setLookAtPosition(_myCamera.getPosition()); } _myAvatar.render(Menu::getInstance()->isOptionChecked(MenuOption::Mirror), diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index d93c4eeb32..19ef6a8505 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -163,41 +163,45 @@ void Head::simulate(float deltaTime, bool isMine, float gyroCameraSensitivity) { _saccade += (_saccadeTarget - _saccade) * 0.50f; // Update audio trailing average for rendering facial animations - const float AUDIO_AVERAGING_SECS = 0.05; - _averageLoudness = (1.f - deltaTime / AUDIO_AVERAGING_SECS) * _averageLoudness + - (deltaTime / AUDIO_AVERAGING_SECS) * _audioLoudness; - - // Detect transition from talking to not; force blink after that and a delay - bool forceBlink = false; - const float TALKING_LOUDNESS = 100.0f; - const float BLINK_AFTER_TALKING = 0.25f; - if (_averageLoudness > TALKING_LOUDNESS) { - _timeWithoutTalking = 0.0f; - - } else if (_timeWithoutTalking < BLINK_AFTER_TALKING && (_timeWithoutTalking += deltaTime) >= BLINK_AFTER_TALKING) { - forceBlink = true; - } - - // Update audio attack data for facial animation (eyebrows and mouth) - _audioAttack = 0.9 * _audioAttack + 0.1 * fabs(_audioLoudness - _lastLoudness); - _lastLoudness = _audioLoudness; - - const float BROW_LIFT_THRESHOLD = 100; - if (_audioAttack > BROW_LIFT_THRESHOLD) - _browAudioLift += sqrt(_audioAttack) * 0.00005; - - float clamp = 0.01; - if (_browAudioLift > clamp) { _browAudioLift = clamp; } - - _browAudioLift *= 0.7f; - - // update eyelid blinking Faceshift* faceshift = Application::getInstance()->getFaceshift(); if (isMine && faceshift->isActive()) { _leftEyeBlink = faceshift->getLeftBlink(); _rightEyeBlink = faceshift->getRightBlink(); - + + // set these values based on how they'll be used. if we use faceshift in the long term, we'll want a complete + // mapping between their blendshape coefficients and our avatar features + _averageLoudness = faceshift->getMouthSize(); + _browAudioLift = faceshift->getBrowHeight(); + } else { + const float AUDIO_AVERAGING_SECS = 0.05; + _averageLoudness = (1.f - deltaTime / AUDIO_AVERAGING_SECS) * _averageLoudness + + (deltaTime / AUDIO_AVERAGING_SECS) * _audioLoudness; + + // Detect transition from talking to not; force blink after that and a delay + bool forceBlink = false; + const float TALKING_LOUDNESS = 100.0f; + const float BLINK_AFTER_TALKING = 0.25f; + if (_averageLoudness > TALKING_LOUDNESS) { + _timeWithoutTalking = 0.0f; + + } else if (_timeWithoutTalking < BLINK_AFTER_TALKING && (_timeWithoutTalking += deltaTime) >= BLINK_AFTER_TALKING) { + forceBlink = true; + } + + // Update audio attack data for facial animation (eyebrows and mouth) + _audioAttack = 0.9 * _audioAttack + 0.1 * fabs(_audioLoudness - _lastLoudness); + _lastLoudness = _audioLoudness; + + const float BROW_LIFT_THRESHOLD = 100; + if (_audioAttack > BROW_LIFT_THRESHOLD) + _browAudioLift += sqrt(_audioAttack) * 0.00005; + + float clamp = 0.01; + if (_browAudioLift > clamp) { _browAudioLift = clamp; } + + _browAudioLift *= 0.7f; + const float BLINK_SPEED = 10.0f; const float FULLY_OPEN = 0.0f; const float FULLY_CLOSED = 1.0f; diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 7db87993cd..fd24696b14 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -13,9 +13,9 @@ using namespace fs; using namespace std; -Faceshift::Faceshift() : _enabled(false), _eyeGazeLeftPitch(0.0f), _eyeGazeLeftYaw(0.0f), - _eyeGazeRightPitch(0.0f), _eyeGazeRightYaw(0.0f), _leftBlink(0.0f), _rightBlink(0.0f), - _leftBlinkIndex(-1), _rightBlinkIndex(-1) { +Faceshift::Faceshift() : _enabled(false), _eyeGazeLeftPitch(0.0f), _eyeGazeLeftYaw(0.0f), _eyeGazeRightPitch(0.0f), + _eyeGazeRightYaw(0.0f), _leftBlink(0.0f), _rightBlink(0.0f), _leftBlinkIndex(-1), _rightBlinkIndex(-1), + _browHeight(0.0f), _browUpCenterIndex(-1), _mouthSize(0.0f), _jawOpenIndex(-1) { connect(&_socket, SIGNAL(connected()), SLOT(noteConnected())); connect(&_socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(noteError(QAbstractSocket::SocketError))); connect(&_socket, SIGNAL(readyRead()), SLOT(readFromSocket())); @@ -82,6 +82,12 @@ void Faceshift::readFromSocket() { if (_rightBlinkIndex != -1) { _rightBlink = data.m_coeffs[_rightBlinkIndex]; } + if (_browUpCenterIndex != -1) { + _browHeight = data.m_coeffs[_browUpCenterIndex]; + } + if (_jawOpenIndex != -1) { + _mouthSize = data.m_coeffs[_jawOpenIndex]; + } } break; } @@ -93,6 +99,12 @@ void Faceshift::readFromSocket() { } else if (names[i] == "EyeBlink_R") { _rightBlinkIndex = i; + + } else if (names[i] == "BrowsU_C") { + _browUpCenterIndex = i; + + } else if (names[i] == "JawOpen") { + _jawOpenIndex = i; } } break; diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h index 88f115564c..1b6914b8cf 100644 --- a/interface/src/devices/Faceshift.h +++ b/interface/src/devices/Faceshift.h @@ -38,6 +38,10 @@ public: float getLeftBlink() const { return _leftBlink; } float getRightBlink() const { return _rightBlink; } + float getBrowHeight() const { return _browHeight; } + + float getMouthSize() const { return _mouthSize; } + public slots: void setEnabled(bool enabled); @@ -71,6 +75,14 @@ private: int _leftBlinkIndex; int _rightBlinkIndex; + + float _browHeight; + + int _browUpCenterIndex; + + float _mouthSize; + + int _jawOpenIndex; }; #endif /* defined(__interface__Faceshift__) */ From 5e23c7919275a8f824a5a512a349d8f05656b941 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 4 Sep 2013 11:55:55 -0700 Subject: [PATCH 29/31] Tweaked feature scales, send recalibrate request on reset. --- interface/src/Application.cpp | 1 + interface/src/avatar/Head.cpp | 6 ++++-- interface/src/devices/Faceshift.cpp | 8 ++++++++ interface/src/devices/Faceshift.h | 2 ++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c966ed4bf2..a50663b316 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3077,6 +3077,7 @@ void Application::resetSensors() { _serialHeadSensor.resetAverages(); } _webcam.reset(); + _faceshift.reset(); QCursor::setPos(_headMouseX, _headMouseY); _myAvatar.reset(); _myTransmitter.resetLevels(); diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 19ef6a8505..e7bb19bee6 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -170,8 +170,10 @@ void Head::simulate(float deltaTime, bool isMine, float gyroCameraSensitivity) { // set these values based on how they'll be used. if we use faceshift in the long term, we'll want a complete // mapping between their blendshape coefficients and our avatar features - _averageLoudness = faceshift->getMouthSize(); - _browAudioLift = faceshift->getBrowHeight(); + const float MOUTH_SIZE_SCALE = 2500.0f; + _averageLoudness = faceshift->getMouthSize() * faceshift->getMouthSize() * MOUTH_SIZE_SCALE; + const float BROW_HEIGHT_SCALE = 0.005f; + _browAudioLift = faceshift->getBrowHeight() * BROW_HEIGHT_SCALE; } else { const float AUDIO_AVERAGING_SECS = 0.05; diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index fd24696b14..259120e697 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -21,6 +21,14 @@ Faceshift::Faceshift() : _enabled(false), _eyeGazeLeftPitch(0.0f), _eyeGazeLeftY connect(&_socket, SIGNAL(readyRead()), SLOT(readFromSocket())); } +void Faceshift::reset() { + if (isActive()) { + string message; + fsBinaryStream::encode_message(message, fsMsgCalibrateNeutral()); + send(message); + } +} + void Faceshift::setEnabled(bool enabled) { if ((_enabled = enabled)) { connectSocket(); diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h index 1b6914b8cf..286a7a56a1 100644 --- a/interface/src/devices/Faceshift.h +++ b/interface/src/devices/Faceshift.h @@ -42,6 +42,8 @@ public: float getMouthSize() const { return _mouthSize; } + void reset(); + public slots: void setEnabled(bool enabled); From 0a9db6a5eea72851e953458e658855520f2ace25 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 4 Sep 2013 12:15:36 -0700 Subject: [PATCH 30/31] Moved device-related files to "devices" folder. --- interface/src/Application.cpp | 6 +++--- interface/src/Application.h | 4 ++-- interface/src/avatar/Avatar.cpp | 2 +- interface/src/avatar/Avatar.h | 5 ++--- interface/src/avatar/Face.cpp | 1 - interface/src/avatar/Hand.h | 13 +++++++++---- interface/src/avatar/Head.h | 4 ++-- interface/src/avatar/MyAvatar.cpp | 5 ++--- interface/src/{ => devices}/LeapManager.cpp | 0 interface/src/{ => devices}/LeapManager.h | 0 interface/src/{ => devices}/OculusManager.cpp | 0 interface/src/{ => devices}/OculusManager.h | 0 interface/src/{ => devices}/SerialInterface.cpp | 0 interface/src/{ => devices}/SerialInterface.h | 0 interface/src/{ => devices}/Transmitter.cpp | 0 interface/src/{ => devices}/Transmitter.h | 0 interface/src/{ => devices}/Webcam.cpp | 0 interface/src/{ => devices}/Webcam.h | 0 18 files changed, 21 insertions(+), 19 deletions(-) rename interface/src/{ => devices}/LeapManager.cpp (100%) rename interface/src/{ => devices}/LeapManager.h (100%) rename interface/src/{ => devices}/OculusManager.cpp (100%) rename interface/src/{ => devices}/OculusManager.h (100%) rename interface/src/{ => devices}/SerialInterface.cpp (100%) rename interface/src/{ => devices}/SerialInterface.h (100%) rename interface/src/{ => devices}/Transmitter.cpp (100%) rename interface/src/{ => devices}/Transmitter.h (100%) rename interface/src/{ => devices}/Webcam.cpp (100%) rename interface/src/{ => devices}/Webcam.h (100%) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a50663b316..be26fb04ef 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -58,13 +58,13 @@ #include "Application.h" #include "LogDisplay.h" -#include "LeapManager.h" #include "Menu.h" -#include "OculusManager.h" +#include "Swatch.h" #include "Util.h" +#include "devices/LeapManager.h" +#include "devices/OculusManager.h" #include "renderer/ProgramObject.h" #include "ui/TextRenderer.h" -#include "Swatch.h" using namespace std; diff --git a/interface/src/Application.h b/interface/src/Application.h index 01d2104438..2d88bca73c 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -33,7 +33,6 @@ #include "GLCanvas.h" #include "PacketHeaders.h" #include "PieMenu.h" -#include "SerialInterface.h" #include "Stars.h" #include "Swatch.h" #include "ToolsPalette.h" @@ -43,11 +42,12 @@ #include "VoxelPacketProcessor.h" #include "VoxelSystem.h" #include "VoxelImporter.h" -#include "Webcam.h" #include "avatar/Avatar.h" #include "avatar/MyAvatar.h" #include "avatar/HandControl.h" #include "devices/Faceshift.h" +#include "devices/SerialInterface.h" +#include "devices/Webcam.h" #include "renderer/AmbientOcclusionEffect.h" #include "renderer/GeometryCache.h" #include "renderer/GlowEffect.h" diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 8b6eea4ee4..084b1f8c85 100755 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -13,7 +13,6 @@ #include #include -#include #include #include @@ -23,6 +22,7 @@ #include "Head.h" #include "Physics.h" #include "world.h" +#include "devices/OculusManager.h" #include "ui/TextRenderer.h" using namespace std; diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index b04f9f04b8..3aee9599d2 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -22,10 +22,9 @@ #include "Head.h" #include "InterfaceConfig.h" #include "Skeleton.h" -#include "SerialInterface.h" -#include "Transmitter.h" #include "world.h" - +#include "devices/SerialInterface.h" +#include "devices/Transmitter.h" static const float MAX_SCALE = 1000.f; static const float MIN_SCALE = .005f; diff --git a/interface/src/avatar/Face.cpp b/interface/src/avatar/Face.cpp index 5bcfba2334..47a37d6973 100644 --- a/interface/src/avatar/Face.cpp +++ b/interface/src/avatar/Face.cpp @@ -17,7 +17,6 @@ #include "Avatar.h" #include "Head.h" #include "Face.h" -#include "Webcam.h" #include "renderer/ProgramObject.h" using namespace cv; diff --git a/interface/src/avatar/Hand.h b/interface/src/avatar/Hand.h index c4bc428456..145ec67c15 100755 --- a/interface/src/avatar/Hand.h +++ b/interface/src/avatar/Hand.h @@ -8,17 +8,22 @@ #ifndef hifi_Hand_h #define hifi_Hand_h +#include + #include + #include + +#include + #include #include + #include "Balls.h" -#include "world.h" #include "InterfaceConfig.h" -#include "SerialInterface.h" #include "ParticleSystem.h" -#include -#include +#include "world.h" +#include "devices/SerialInterface.h" enum RaveLightsSetting { RAVE_LIGHTS_AVATAR = 0, diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index c6fcac0bd7..a1075f3ba2 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -17,11 +17,11 @@ #include -#include "Face.h" #include "BendyLine.h" +#include "Face.h" #include "InterfaceConfig.h" -#include "SerialInterface.h" #include "world.h" +#include "devices/SerialInterface.h" enum eyeContactTargets { LEFT_EYE, diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 79dedc45b1..4b7f64cb6f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -6,20 +6,19 @@ // Copyright (c) 2012 High Fidelity, Inc. All rights reserved. // -#include "MyAvatar.h" - #include #include #include #include -#include #include #include #include "Application.h" +#include "MyAvatar.h" #include "Physics.h" +#include "devices/OculusManager.h" #include "ui/TextRenderer.h" using namespace std; diff --git a/interface/src/LeapManager.cpp b/interface/src/devices/LeapManager.cpp similarity index 100% rename from interface/src/LeapManager.cpp rename to interface/src/devices/LeapManager.cpp diff --git a/interface/src/LeapManager.h b/interface/src/devices/LeapManager.h similarity index 100% rename from interface/src/LeapManager.h rename to interface/src/devices/LeapManager.h diff --git a/interface/src/OculusManager.cpp b/interface/src/devices/OculusManager.cpp similarity index 100% rename from interface/src/OculusManager.cpp rename to interface/src/devices/OculusManager.cpp diff --git a/interface/src/OculusManager.h b/interface/src/devices/OculusManager.h similarity index 100% rename from interface/src/OculusManager.h rename to interface/src/devices/OculusManager.h diff --git a/interface/src/SerialInterface.cpp b/interface/src/devices/SerialInterface.cpp similarity index 100% rename from interface/src/SerialInterface.cpp rename to interface/src/devices/SerialInterface.cpp diff --git a/interface/src/SerialInterface.h b/interface/src/devices/SerialInterface.h similarity index 100% rename from interface/src/SerialInterface.h rename to interface/src/devices/SerialInterface.h diff --git a/interface/src/Transmitter.cpp b/interface/src/devices/Transmitter.cpp similarity index 100% rename from interface/src/Transmitter.cpp rename to interface/src/devices/Transmitter.cpp diff --git a/interface/src/Transmitter.h b/interface/src/devices/Transmitter.h similarity index 100% rename from interface/src/Transmitter.h rename to interface/src/devices/Transmitter.h diff --git a/interface/src/Webcam.cpp b/interface/src/devices/Webcam.cpp similarity index 100% rename from interface/src/Webcam.cpp rename to interface/src/devices/Webcam.cpp diff --git a/interface/src/Webcam.h b/interface/src/devices/Webcam.h similarity index 100% rename from interface/src/Webcam.h rename to interface/src/devices/Webcam.h From 03ead34642c77bdbccf4620f1d6e08dc88c1a751 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 5 Sep 2013 10:34:43 -0700 Subject: [PATCH 31/31] Code review cleanup. --- interface/src/avatar/Head.cpp | 17 ++++++++++------- interface/src/devices/Faceshift.cpp | 18 +++++++++++++++--- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 6528838e74..08efb32062 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -189,15 +189,18 @@ void Head::simulate(float deltaTime, bool isMine, float gyroCameraSensitivity) { } // Update audio attack data for facial animation (eyebrows and mouth) - _audioAttack = 0.9 * _audioAttack + 0.1 * fabs(_audioLoudness - _lastLoudness); + _audioAttack = 0.9f * _audioAttack + 0.1f * fabs(_audioLoudness - _lastLoudness); _lastLoudness = _audioLoudness; - const float BROW_LIFT_THRESHOLD = 100; - if (_audioAttack > BROW_LIFT_THRESHOLD) - _browAudioLift += sqrt(_audioAttack) * 0.00005; - - float clamp = 0.01; - if (_browAudioLift > clamp) { _browAudioLift = clamp; } + const float BROW_LIFT_THRESHOLD = 100.0f; + if (_audioAttack > BROW_LIFT_THRESHOLD) { + _browAudioLift += sqrtf(_audioAttack) * 0.00005f; + } + + const float CLAMP = 0.01f; + if (_browAudioLift > CLAMP) { + _browAudioLift = CLAMP; + } _browAudioLift *= 0.7f; diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 259120e697..4418d19223 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -13,9 +13,21 @@ using namespace fs; using namespace std; -Faceshift::Faceshift() : _enabled(false), _eyeGazeLeftPitch(0.0f), _eyeGazeLeftYaw(0.0f), _eyeGazeRightPitch(0.0f), - _eyeGazeRightYaw(0.0f), _leftBlink(0.0f), _rightBlink(0.0f), _leftBlinkIndex(-1), _rightBlinkIndex(-1), - _browHeight(0.0f), _browUpCenterIndex(-1), _mouthSize(0.0f), _jawOpenIndex(-1) { +Faceshift::Faceshift() : + _enabled(false), + _eyeGazeLeftPitch(0.0f), + _eyeGazeLeftYaw(0.0f), + _eyeGazeRightPitch(0.0f), + _eyeGazeRightYaw(0.0f), + _leftBlink(0.0f), + _rightBlink(0.0f), + _leftBlinkIndex(-1), + _rightBlinkIndex(-1), + _browHeight(0.0f), + _browUpCenterIndex(-1), + _mouthSize(0.0f), + _jawOpenIndex(-1) +{ connect(&_socket, SIGNAL(connected()), SLOT(noteConnected())); connect(&_socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(noteError(QAbstractSocket::SocketError))); connect(&_socket, SIGNAL(readyRead()), SLOT(readFromSocket()));