{"action":"created","comment":{"url":"https://api.github.com/repos/highfidelity/hifi/pulls/comments/29601195","id":29601195,"diff_hunk":"@@ -0,0 +1,270 @@\n+//\n+//  UserInputMapper.h\n+//  interface/src/ui\n+//\n+//  Created by Sam Gateau on 4/27/15.\n+//  Copyright 2015 High Fidelity, Inc.\n+//\n+//  Distributed under the Apache License, Version 2.0.\n+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html\n+//\n+\n+#ifndef hifi_UserInputMapper_h\n+#define hifi_UserInputMapper_h\n+\n+#include <QTouchEvent>\n+#include <glm/glm.hpp>\n+#include <RegisteredMetaTypes.h>\n+\n+#include <unordered_set>\n+#include <functional>\n+#include <memory>\n+#include <chrono>\n+\n+class UserInputMapper : public QObject {\n+    Q_OBJECT\n+public:\n+    typedef unsigned short uint16;\n+    typedef unsigned int uint32;\n+\n+    enum class ChannelType {\n+        UNKNOWN = 0,\n+        BUTTON = 1,\n+        AXIS,\n+        JOINT,\n+    };\n+\n+    // Input is the unique identifier to find a n input channel of a particular device\n+    // Devices are responsible for registering to the UseInputMapper so their input channels can be sued and mapped\n+    // to the Action channels\n+    class Input {\n+    public:\n+        union {\n+            struct {\n+                uint16 _device; // Up to 64K possible devices\n+                uint16 _channel : 14; // 2^14 possible channel per Device\n+                uint16 _type : 2; // 2 bits to store the Type directly in the ID\n+            };\n+            uint32 _id = 0; // by default Input is 0 meaning invalid\n+        };\n+        \n+        bool isValid() const { return (_id != 0); }\n+        \n+        uint16 getDevice() const { return _device; }\n+        uint16 getChannel() const { return _channel; }\n+        uint32 getID() const { return _id; }\n+\n+        ChannelType getType() const { return (ChannelType) _type; }\n+        bool isButton() const { return getType() == ChannelType::BUTTON; }\n+        bool isAxis() const { return getType() == ChannelType::AXIS; }\n+        bool isJoint() const { return getType() == ChannelType::JOINT; }\n+\n+        explicit Input() {}\n+        explicit Input(uint32 id) : _id(id) {}\n+        explicit Input(uint16 device, uint16 channel, ChannelType type) : _device(device), _channel(channel), _type(uint16(type)) {}\n+        Input(const Input& src) : _id(src._id) {}\n+        Input& operator = (const Input& src) { _id = src._id; return (*this); }\n+        bool operator < (const Input& src) const { return _id < src._id; }\n+    };\n+\n+    // Modifiers are just button inputID\n+    typedef std::vector< Input > Modifiers;\n+\n+    class JointValue {\n+    public:\n+        glm::vec3 translation{ 0.0f };\n+        glm::quat rotation;\n+\n+        JointValue() {};\n+        JointValue(const JointValue&) = default;\n+        JointValue& operator = (const JointValue&) = default;\n+    };\n+    \n+    typedef std::function<bool (const Input& input, int timestamp)> ButtonGetter;\n+    typedef std::function<float (const Input& input, int timestamp)> AxisGetter;\n+    typedef std::function<JointValue (const Input& input, int timestamp)> JointGetter;\n+\n+   class DeviceProxy {\n+    public:\n+        DeviceProxy() {}\n+        \n+        ButtonGetter getButton = [] (const Input& input, int timestamp) -> bool { return false; };\n+        AxisGetter getAxis = [] (const Input& input, int timestamp) -> bool { return 0.0f; };\n+        JointGetter getJoint = [] (const Input& input, int timestamp) -> JointValue { return JointValue(); };\n+        \n+        typedef std::shared_ptr<DeviceProxy> Pointer;\n+    };\n+    // GetFreeDeviceID should be called before registering a device to use an ID not used by a different device.\n+    uint16 getFreeDeviceID() { return _nextFreeDeviceID++; }\n+    bool registerDevice(uint16 deviceID, const DeviceProxy::Pointer& device);\n+    DeviceProxy::Pointer getDeviceProxy(const Input& input);\n+\n+\n+    // Actions are the output channels of the Mapper, that's what the InputChannel map to\n+    // For now the Actions are hardcoded, this is bad, but we will fix that in the near future\n+    enum Action {\n+        LONGITUDINAL_BACKWARD = 0,\n+        LONGITUDINAL_FORWARD,\n+\n+        LATERAL_LEFT,\n+        LATERAL_RIGHT,\n+\n+        VERTICAL_DOWN,\n+        VERTICAL_UP,\n+\n+        YAW_LEFT,\n+        YAW_RIGHT,\n+ \n+        PITCH_DOWN,\n+        PITCH_UP,\n+ \n+        BOOM_IN,\n+        BOOM_OUT,\n+\n+        NUM_ACTIONS,\n+    };\n+\n+    float getActionState(Action action) const { return _actionStates[action]; }\n+    void assignDefaulActionScales();\n+\n+    // Add input channel to the mapper and check that all the used channels are registered.\n+    // Return true if theinput channel is created correctly, false either\n+    bool addInputChannel(Action action, const Input& input, float scale = 1.0f);\n+    bool addInputChannel(Action action, const Input& input, const Input& modifer, float scale = 1.0f);\n+    \n+    // Under the hood, the input channels are organized in map sorted on the _output\n+    // The InputChannel class is just the full values describing the input channel in one object \n+    class InputChannel {\n+    public:\n+        Input _input;\n+        Input _modifier = Input(); // make it invalid by default, meaning no modifier\n+        Action _action = LONGITUDINAL_BACKWARD;\n+        float _scale = 0.0f;\n+\n+        InputChannel() {}\n+        InputChannel(const Input& input, const Input& modifier, Action action, float scale = 1.0f) :\n+            _input(input), _modifier(modifier), _action(action), _scale(scale) {}\n+        InputChannel(const InputChannel& src) : InputChannel(src._input, src._modifier, src._action, src._scale) {}\n+        InputChannel& operator = (const InputChannel& src) { _input = src._input; _modifier = src._modifier; _action = src._action; _scale = src._scale; return (*this); }\n+    \n+        bool hasModifier() { return _modifier.isValid(); }\n+    };\n+    typedef std::vector< InputChannel > InputChannels;\n+\n+    // Add a bunch of input channels, return the true number of channels that successfully were added\n+    int addInputChannels(const InputChannels& channels);\n+    //Grab all the input channels currently in use, return the number\n+    int getInputChannels(InputChannels& channels) const;\n+\n+    // Update means go grab all the device input channels and update the output channel values\n+    void update(float deltaTime);\n+\n+    // Default contruct allocate the poutput size with the current hardcoded action channels\n+    UserInputMapper() { assignDefaulActionScales(); }\n+\n+protected:\n+    typedef std::map<int, DeviceProxy::Pointer> DevicesMap;\n+    DevicesMap _registeredDevices;\n+    uint16 _nextFreeDeviceID = 1;\n+\n+    typedef std::map<int, Modifiers> InputToMoModifiersMap;\n+    InputToMoModifiersMap _inputToModifiersMap;\n+\n+    typedef std::multimap<Action, InputChannel> ActionToInputsMap;\n+    ActionToInputsMap _actionToInputsMap;\n+ \n+    std::vector<float> _actionStates = std::vector<float>(NUM_ACTIONS, 0.0f);\n+    std::vector<float> _actionScales = std::vector<float>(NUM_ACTIONS, 1.0f);\n+};\n+\n+\n+class KeyboardMouseDevice {\n+public:\n+\n+    enum KeyboardChannel {\n+        KEYBOARD_FIRST = 0,\n+        KEYBOARD_LAST = 255,\n+        KEYBOARD_MASK = 0x00FF,\n+    };\n+\n+    enum MouseButtonChannel {\n+        MOUSE_BUTTON_LEFT = KEYBOARD_LAST + 1,\n+        MOUSE_BUTTON_RIGHT,\n+        MOUSE_BUTTON_MIDDLE,\n+    };\n+\n+    enum MouseAxisChannel {\n+        MOUSE_AXIS_X_POS = MOUSE_BUTTON_MIDDLE + 1,\n+        MOUSE_AXIS_X_NEG,\n+        MOUSE_AXIS_Y_POS,\n+        MOUSE_AXIS_Y_NEG,\n+        MOUSE_AXIS_WHEEL_Y_POS,\n+        MOUSE_AXIS_WHEEL_Y_NEG,\n+        MOUSE_AXIS_WHEEL_X_POS,\n+        MOUSE_AXIS_WHEEL_X_NEG,\n+    };\n+        \n+    enum TouchAxisChannel {\n+        TOUCH_AXIS_X_POS = MOUSE_AXIS_WHEEL_X_NEG + 1,\n+        TOUCH_AXIS_X_NEG,\n+        TOUCH_AXIS_Y_POS,\n+        TOUCH_AXIS_Y_NEG,\n+    };\n+        \n+    enum TouchButtonChannel {\n+        TOUCH_BUTTON_PRESS = TOUCH_AXIS_Y_NEG + 1,\n+    };\n+\n+    typedef std::unordered_set<int> ButtonPressedMap;\n+    typedef std::map<int, float> AxisStateMap; // 8 axes\n+\n+    void focusOutEvent(QFocusEvent* event);\n+ \n+    void keyPressEvent(QKeyEvent* event);\n+    void keyReleaseEvent(QKeyEvent* event);\n+\n+    void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID = 0);\n+    void mousePressEvent(QMouseEvent* event, unsigned int deviceID = 0);\n+    void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID = 0);\n+\n+    void touchBeginEvent(const QTouchEvent* event);\n+    void touchEndEvent(const QTouchEvent* event);\n+    void touchUpdateEvent(const QTouchEvent* event);\n+\n+    void wheelEvent(QWheelEvent* event);\n+\n+    // Get current state for each channels\n+    float getButton(int channel) const;\n+    float getAxis(int channel) const;\n+\n+    // Let's make it easy for Qt because we assume we love Qt forever\n+    UserInputMapper::Input makeInput(Qt::Key code);\n+    UserInputMapper::Input makeInput(Qt::MouseButton code);\n+    UserInputMapper::Input makeInput(KeyboardMouseDevice::MouseAxisChannel axis);\n+    UserInputMapper::Input makeInput(KeyboardMouseDevice::TouchAxisChannel axis);\n+    UserInputMapper::Input makeInput(KeyboardMouseDevice::TouchButtonChannel button);\n+    \n+    KeyboardMouseDevice() {}\n+\n+    void registerToUserInputMapper(UserInputMapper& mapper);\n+    void assignDefaultInputMapping(UserInputMapper& mapper);\n+\n+    // Update call MUST be called once per simulation loop\n+    // It takes care of updating the action states and deltas\n+    void update();\n+\n+protected:\n+    ButtonPressedMap _buttonPressedMap;\n+    AxisStateMap _axisStateMap;\n+    \n+    int _deviceID = 0;\n+    QPoint _lastCursor;\n+    glm::vec2 _lastTouch;\n+    bool _isTouching = false;\n+    \n+    glm::vec2 evalAverageTouchPoints(const QList<QTouchEvent::TouchPoint>& points) const;\n+    std::chrono::high_resolution_clock _clock;\n+    std::chrono::high_resolution_clock::time_point _lastTouchTime;\n+};\n+\n+#endif // hifi_UserInputMapper_h","path":"interface/src/ui/UserInputMapper.h","position":270,"original_position":270,"commit_id":"3921156978061ad3ed71de84403cab4206eeccc3","original_commit_id":"3921156978061ad3ed71de84403cab4206eeccc3","user":{"login":"birarda","id":724069,"avatar_url":"https://avatars.githubusercontent.com/u/724069?v=3","gravatar_id":"","url":"https://api.github.com/users/birarda","html_url":"https://github.com/birarda","followers_url":"https://api.github.com/users/birarda/followers","following_url":"https://api.github.com/users/birarda/following{/other_user}","gists_url":"https://api.github.com/users/birarda/gists{/gist_id}","starred_url":"https://api.github.com/users/birarda/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/birarda/subscriptions","organizations_url":"https://api.github.com/users/birarda/orgs","repos_url":"https://api.github.com/users/birarda/repos","events_url":"https://api.github.com/users/birarda/events{/privacy}","received_events_url":"https://api.github.com/users/birarda/received_events","type":"User","site_admin":false},"body":"put this in a sep file","created_at":"2015-05-04T16:57:27Z","updated_at":"2015-05-04T16:57:27Z","html_url":"https://github.com/highfidelity/hifi/pull/4753#discussion_r29601195","pull_request_url":"https://api.github.com/repos/highfidelity/hifi/pulls/4753","_links":{"self":{"href":"https://api.github.com/repos/highfidelity/hifi/pulls/comments/29601195"},"html":{"href":"https://github.com/highfidelity/hifi/pull/4753#discussion_r29601195"},"pull_request":{"href":"https://api.github.com/repos/highfidelity/hifi/pulls/4753"}}},"pull_request":{"url":"https://api.github.com/repos/highfidelity/hifi/pulls/4753","id":34620899,"html_url":"https://github.com/highfidelity/hifi/pull/4753","diff_url":"https://github.com/highfidelity/hifi/pull/4753.diff","patch_url":"https://github.com/highfidelity/hifi/pull/4753.patch","issue_url":"https://api.github.com/repos/highfidelity/hifi/issues/4753","number":4753,"state":"open","locked":false,"title":"Introducing UserInputMapper","user":{"login":"samcake","id":2230265,"avatar_url":"https://avatars.githubusercontent.com/u/2230265?v=3","gravatar_id":"","url":"https://api.github.com/users/samcake","html_url":"https://github.com/samcake","followers_url":"https://api.github.com/users/samcake/followers","following_url":"https://api.github.com/users/samcake/following{/other_user}","gists_url":"https://api.github.com/users/samcake/gists{/gist_id}","starred_url":"https://api.github.com/users/samcake/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/samcake/subscriptions","organizations_url":"https://api.github.com/users/samcake/orgs","repos_url":"https://api.github.com/users/samcake/repos","events_url":"https://api.github.com/users/samcake/events{/privacy}","received_events_url":"https://api.github.com/users/samcake/received_events","type":"User","site_admin":false},"body":"- The class UserInputMapper is in charge of bindings keys and any device inputs identified with a unique Input to the actual few \"Actions\"\r\n\r\n- Replace all the code happening in Application::key*Event to control the _myAvatar driveKeys, instead it is happening in the Application::update right after the userINputMapper has been updated\r\n\r\n- For now the Actions of the UserInputMapper are hard coded, we ll need to change that soon and make the actions created at runtime","created_at":"2015-05-04T08:04:05Z","updated_at":"2015-05-04T16:57:27Z","closed_at":null,"merged_at":null,"merge_commit_sha":"aa1449d4f27dfd87b64c5e25d0e6b070c6c4fa52","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/highfidelity/hifi/pulls/4753/commits","review_comments_url":"https://api.github.com/repos/highfidelity/hifi/pulls/4753/comments","review_comment_url":"https://api.github.com/repos/highfidelity/hifi/pulls/comments{/number}","comments_url":"https://api.github.com/repos/highfidelity/hifi/issues/4753/comments","statuses_url":"https://api.github.com/repos/highfidelity/hifi/statuses/3921156978061ad3ed71de84403cab4206eeccc3","head":{"label":"samcake:orange","ref":"orange","sha":"3921156978061ad3ed71de84403cab4206eeccc3","user":{"login":"samcake","id":2230265,"avatar_url":"https://avatars.githubusercontent.com/u/2230265?v=3","gravatar_id":"","url":"https://api.github.com/users/samcake","html_url":"https://github.com/samcake","followers_url":"https://api.github.com/users/samcake/followers","following_url":"https://api.github.com/users/samcake/following{/other_user}","gists_url":"https://api.github.com/users/samcake/gists{/gist_id}","starred_url":"https://api.github.com/users/samcake/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/samcake/subscriptions","organizations_url":"https://api.github.com/users/samcake/orgs","repos_url":"https://api.github.com/users/samcake/repos","events_url":"https://api.github.com/users/samcake/events{/privacy}","received_events_url":"https://api.github.com/users/samcake/received_events","type":"User","site_admin":false},"repo":{"id":24736064,"name":"hifi","full_name":"samcake/hifi","owner":{"login":"samcake","id":2230265,"avatar_url":"https://avatars.githubusercontent.com/u/2230265?v=3","gravatar_id":"","url":"https://api.github.com/users/samcake","html_url":"https://github.com/samcake","followers_url":"https://api.github.com/users/samcake/followers","following_url":"https://api.github.com/users/samcake/following{/other_user}","gists_url":"https://api.github.com/users/samcake/gists{/gist_id}","starred_url":"https://api.github.com/users/samcake/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/samcake/subscriptions","organizations_url":"https://api.github.com/users/samcake/orgs","repos_url":"https://api.github.com/users/samcake/repos","events_url":"https://api.github.com/users/samcake/events{/privacy}","received_events_url":"https://api.github.com/users/samcake/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/samcake/hifi","description":"Open, decentralized virtual worlds using sensors to control avatars and dynamically assigned devices as servers.  San Francisco based startup, we are hiring: http://highfidelity.io/jobs  Contribute by reporting bugs & doing jobs on http://worklist.net","fork":true,"url":"https://api.github.com/repos/samcake/hifi","forks_url":"https://api.github.com/repos/samcake/hifi/forks","keys_url":"https://api.github.com/repos/samcake/hifi/keys{/key_id}","collaborators_url":"https://api.github.com/repos/samcake/hifi/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/samcake/hifi/teams","hooks_url":"https://api.github.com/repos/samcake/hifi/hooks","issue_events_url":"https://api.github.com/repos/samcake/hifi/issues/events{/number}","events_url":"https://api.github.com/repos/samcake/hifi/events","assignees_url":"https://api.github.com/repos/samcake/hifi/assignees{/user}","branches_url":"https://api.github.com/repos/samcake/hifi/branches{/branch}","tags_url":"https://api.github.com/repos/samcake/hifi/tags","blobs_url":"https://api.github.com/repos/samcake/hifi/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/samcake/hifi/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/samcake/hifi/git/refs{/sha}","trees_url":"https://api.github.com/repos/samcake/hifi/git/trees{/sha}","statuses_url":"https://api.github.com/repos/samcake/hifi/statuses/{sha}","languages_url":"https://api.github.com/repos/samcake/hifi/languages","stargazers_url":"https://api.github.com/repos/samcake/hifi/stargazers","contributors_url":"https://api.github.com/repos/samcake/hifi/contributors","subscribers_url":"https://api.github.com/repos/samcake/hifi/subscribers","subscription_url":"https://api.github.com/repos/samcake/hifi/subscription","commits_url":"https://api.github.com/repos/samcake/hifi/commits{/sha}","git_commits_url":"https://api.github.com/repos/samcake/hifi/git/commits{/sha}","comments_url":"https://api.github.com/repos/samcake/hifi/comments{/number}","issue_comment_url":"https://api.github.com/repos/samcake/hifi/issues/comments{/number}","contents_url":"https://api.github.com/repos/samcake/hifi/contents/{+path}","compare_url":"https://api.github.com/repos/samcake/hifi/compare/{base}...{head}","merges_url":"https://api.github.com/repos/samcake/hifi/merges","archive_url":"https://api.github.com/repos/samcake/hifi/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/samcake/hifi/downloads","issues_url":"https://api.github.com/repos/samcake/hifi/issues{/number}","pulls_url":"https://api.github.com/repos/samcake/hifi/pulls{/number}","milestones_url":"https://api.github.com/repos/samcake/hifi/milestones{/number}","notifications_url":"https://api.github.com/repos/samcake/hifi/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/samcake/hifi/labels{/name}","releases_url":"https://api.github.com/repos/samcake/hifi/releases{/id}","created_at":"2014-10-02T20:49:37Z","updated_at":"2015-04-02T23:12:49Z","pushed_at":"2015-05-04T08:09:38Z","git_url":"git://github.com/samcake/hifi.git","ssh_url":"git@github.com:samcake/hifi.git","clone_url":"https://github.com/samcake/hifi.git","svn_url":"https://github.com/samcake/hifi","homepage":"","size":154311,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"highfidelity:master","ref":"master","sha":"d7cc76e48bf6fec7ae4312176abf9397b6507561","user":{"login":"highfidelity","id":1565370,"avatar_url":"https://avatars.githubusercontent.com/u/1565370?v=3","gravatar_id":"","url":"https://api.github.com/users/highfidelity","html_url":"https://github.com/highfidelity","followers_url":"https://api.github.com/users/highfidelity/followers","following_url":"https://api.github.com/users/highfidelity/following{/other_user}","gists_url":"https://api.github.com/users/highfidelity/gists{/gist_id}","starred_url":"https://api.github.com/users/highfidelity/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/highfidelity/subscriptions","organizations_url":"https://api.github.com/users/highfidelity/orgs","repos_url":"https://api.github.com/users/highfidelity/repos","events_url":"https://api.github.com/users/highfidelity/events{/privacy}","received_events_url":"https://api.github.com/users/highfidelity/received_events","type":"Organization","site_admin":false},"repo":{"id":5372872,"name":"hifi","full_name":"highfidelity/hifi","owner":{"login":"highfidelity","id":1565370,"avatar_url":"https://avatars.githubusercontent.com/u/1565370?v=3","gravatar_id":"","url":"https://api.github.com/users/highfidelity","html_url":"https://github.com/highfidelity","followers_url":"https://api.github.com/users/highfidelity/followers","following_url":"https://api.github.com/users/highfidelity/following{/other_user}","gists_url":"https://api.github.com/users/highfidelity/gists{/gist_id}","starred_url":"https://api.github.com/users/highfidelity/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/highfidelity/subscriptions","organizations_url":"https://api.github.com/users/highfidelity/orgs","repos_url":"https://api.github.com/users/highfidelity/repos","events_url":"https://api.github.com/users/highfidelity/events{/privacy}","received_events_url":"https://api.github.com/users/highfidelity/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/highfidelity/hifi","description":"Open source software for shared virtual worlds using HMDs and dynamically assigned devices as servers.  San Francisco based startup, we are hiring: http://highfidelity.com/jobs  Documentation at http://docs.highfidelity.com  Contribute by reporting bugs & doing jobs on http://worklist.net","fork":false,"url":"https://api.github.com/repos/highfidelity/hifi","forks_url":"https://api.github.com/repos/highfidelity/hifi/forks","keys_url":"https://api.github.com/repos/highfidelity/hifi/keys{/key_id}","collaborators_url":"https://api.github.com/repos/highfidelity/hifi/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/highfidelity/hifi/teams","hooks_url":"https://api.github.com/repos/highfidelity/hifi/hooks","issue_events_url":"https://api.github.com/repos/highfidelity/hifi/issues/events{/number}","events_url":"https://api.github.com/repos/highfidelity/hifi/events","assignees_url":"https://api.github.com/repos/highfidelity/hifi/assignees{/user}","branches_url":"https://api.github.com/repos/highfidelity/hifi/branches{/branch}","tags_url":"https://api.github.com/repos/highfidelity/hifi/tags","blobs_url":"https://api.github.com/repos/highfidelity/hifi/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/highfidelity/hifi/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/highfidelity/hifi/git/refs{/sha}","trees_url":"https://api.github.com/repos/highfidelity/hifi/git/trees{/sha}","statuses_url":"https://api.github.com/repos/highfidelity/hifi/statuses/{sha}","languages_url":"https://api.github.com/repos/highfidelity/hifi/languages","stargazers_url":"https://api.github.com/repos/highfidelity/hifi/stargazers","contributors_url":"https://api.github.com/repos/highfidelity/hifi/contributors","subscribers_url":"https://api.github.com/repos/highfidelity/hifi/subscribers","subscription_url":"https://api.github.com/repos/highfidelity/hifi/subscription","commits_url":"https://api.github.com/repos/highfidelity/hifi/commits{/sha}","git_commits_url":"https://api.github.com/repos/highfidelity/hifi/git/commits{/sha}","comments_url":"https://api.github.com/repos/highfidelity/hifi/comments{/number}","issue_comment_url":"https://api.github.com/repos/highfidelity/hifi/issues/comments{/number}","contents_url":"https://api.github.com/repos/highfidelity/hifi/contents/{+path}","compare_url":"https://api.github.com/repos/highfidelity/hifi/compare/{base}...{head}","merges_url":"https://api.github.com/repos/highfidelity/hifi/merges","archive_url":"https://api.github.com/repos/highfidelity/hifi/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/highfidelity/hifi/downloads","issues_url":"https://api.github.com/repos/highfidelity/hifi/issues{/number}","pulls_url":"https://api.github.com/repos/highfidelity/hifi/pulls{/number}","milestones_url":"https://api.github.com/repos/highfidelity/hifi/milestones{/number}","notifications_url":"https://api.github.com/repos/highfidelity/hifi/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/highfidelity/hifi/labels{/name}","releases_url":"https://api.github.com/repos/highfidelity/hifi/releases{/id}","created_at":"2012-08-10T18:26:22Z","updated_at":"2015-05-04T16:46:26Z","pushed_at":"2015-05-04T16:55:36Z","git_url":"git://github.com/highfidelity/hifi.git","ssh_url":"git@github.com:highfidelity/hifi.git","clone_url":"https://github.com/highfidelity/hifi.git","svn_url":"https://github.com/highfidelity/hifi","homepage":"","size":207135,"stargazers_count":261,"watchers_count":261,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":149,"mirror_url":null,"open_issues_count":55,"forks":149,"open_issues":55,"watchers":261,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/highfidelity/hifi/pulls/4753"},"html":{"href":"https://github.com/highfidelity/hifi/pull/4753"},"issue":{"href":"https://api.github.com/repos/highfidelity/hifi/issues/4753"},"comments":{"href":"https://api.github.com/repos/highfidelity/hifi/issues/4753/comments"},"review_comments":{"href":"https://api.github.com/repos/highfidelity/hifi/pulls/4753/comments"},"review_comment":{"href":"https://api.github.com/repos/highfidelity/hifi/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/highfidelity/hifi/pulls/4753/commits"},"statuses":{"href":"https://api.github.com/repos/highfidelity/hifi/statuses/3921156978061ad3ed71de84403cab4206eeccc3"}}}}