mirror of
https://github.com/hyblocker/OpenVR-SpaceCalibrator.git
synced 2026-10-07 09:00:20 +02:00
459 lines
13 KiB
C++
459 lines
13 KiB
C++
#include "stdafx.h"
|
|
#include "UserInterface.h"
|
|
#include "Calibration.h"
|
|
#include "Configuration.h"
|
|
|
|
#include <thread>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <imgui/imgui.h>
|
|
|
|
#define VERSION_STRING "0.9.0"
|
|
|
|
struct VRDevice
|
|
{
|
|
int id = -1;
|
|
vr::TrackedDeviceClass deviceClass;
|
|
std::string model = "";
|
|
std::string serial = "";
|
|
std::string trackingSystem = "";
|
|
vr::ETrackedControllerRole controllerRole = vr::TrackedControllerRole_Invalid;
|
|
};
|
|
|
|
struct VRState
|
|
{
|
|
std::vector<std::string> trackingSystems;
|
|
std::vector<VRDevice> devices;
|
|
};
|
|
|
|
void TextWithWidth(const char *label, const char *text, float width);
|
|
|
|
VRState LoadVRState();
|
|
void BuildSystemSelection(const VRState &state);
|
|
void BuildDeviceSelections(const VRState &state);
|
|
void BuildProfileEditor();
|
|
|
|
void BuildMainWindow(bool runningInOverlay)
|
|
{
|
|
ImGuiWindowFlags windowFlags =
|
|
ImGuiWindowFlags_NoTitleBar |
|
|
ImGuiWindowFlags_NoResize |
|
|
ImGuiWindowFlags_NoMove;
|
|
|
|
auto &io = ImGui::GetIO();
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f), ImGuiSetCond_Always);
|
|
ImGui::SetNextWindowSize(io.DisplaySize, ImGuiSetCond_Always);
|
|
|
|
if (!ImGui::Begin("MainWindow", nullptr, windowFlags))
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
ImGuiStyle &style = ImGui::GetStyle();
|
|
|
|
auto state = LoadVRState();
|
|
BuildSystemSelection(state);
|
|
BuildDeviceSelections(state);
|
|
|
|
ImGui::Text("");
|
|
|
|
if (CalCtx.state == CalibrationState::None)
|
|
{
|
|
if (CalCtx.validProfile && !CalCtx.enabled)
|
|
{
|
|
ImGui::TextColored(ImColor(0.8f, 0.2f, 0.2f), "Reference (%s) HMD not detected, profile disabled", CalCtx.referenceTrackingSystem.c_str());
|
|
ImGui::Text("");
|
|
}
|
|
|
|
float width = ImGui::GetWindowContentRegionWidth(), scale = 1.0f;
|
|
if (CalCtx.validProfile)
|
|
{
|
|
width -= style.FramePadding.x * 4.0f;
|
|
scale = 1.0f / 3.0f;
|
|
}
|
|
|
|
if (ImGui::Button("Start Calibration", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2)))
|
|
{
|
|
ImGui::OpenPopup("Calibration Progress");
|
|
StartCalibration();
|
|
}
|
|
|
|
if (CalCtx.validProfile)
|
|
{
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Edit Calibration", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2)))
|
|
{
|
|
CalCtx.state = CalibrationState::Editing;
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Clear Calibration", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2)))
|
|
{
|
|
CalCtx.Clear();
|
|
SaveProfile(CalCtx);
|
|
}
|
|
}
|
|
|
|
width = ImGui::GetWindowContentRegionWidth();
|
|
scale = 1.0f;
|
|
if (CalCtx.chaperone.valid)
|
|
{
|
|
width -= style.FramePadding.x * 2.0f;
|
|
scale = 0.5;
|
|
}
|
|
|
|
ImGui::Text("");
|
|
if (ImGui::Button("Copy Chaperone Bounds to profile", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2)))
|
|
{
|
|
LoadChaperoneBounds();
|
|
SaveProfile(CalCtx);
|
|
}
|
|
|
|
if (CalCtx.chaperone.valid)
|
|
{
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Paste Chaperone Bounds", ImVec2(width * scale, ImGui::GetTextLineHeight() * 2)))
|
|
{
|
|
ApplyChaperoneBounds();
|
|
}
|
|
|
|
if (ImGui::Checkbox(" Paste Chaperone Bounds automatically when geometry resets", &CalCtx.chaperone.autoApply))
|
|
{
|
|
SaveProfile(CalCtx);
|
|
}
|
|
}
|
|
}
|
|
else if (CalCtx.state == CalibrationState::Editing)
|
|
{
|
|
BuildProfileEditor();
|
|
|
|
if (ImGui::Button("Save Profile", ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetTextLineHeight() * 2)))
|
|
{
|
|
SaveProfile(CalCtx);
|
|
CalCtx.state = CalibrationState::None;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImGui::Button("Calibration in progress...", ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetTextLineHeight() * 2));
|
|
}
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiSetCond_Always);
|
|
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x - 40.0f, io.DisplaySize.y - 40.0f), ImGuiSetCond_Always);
|
|
if (ImGui::BeginPopupModal("Calibration Progress", nullptr, windowFlags))
|
|
{
|
|
ImGui::TextWrapped(CalCtx.messages.c_str());
|
|
|
|
if (CalCtx.state == CalibrationState::None)
|
|
{
|
|
ImGui::Text("");
|
|
if (ImGui::Button("Close", ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetTextLineHeight() * 2)))
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(10.0f, ImGui::GetWindowHeight() - ImGui::GetItemsLineHeightWithSpacing()));
|
|
ImGui::BeginChild("bottom line", ImVec2(ImGui::GetWindowWidth() - 20.0f, ImGui::GetItemsLineHeightWithSpacing() * 2), false);
|
|
ImGui::Text("OpenVR Space Calibrator v" VERSION_STRING " - by tach/pushrax");
|
|
if (runningInOverlay)
|
|
{
|
|
ImGui::SameLine();
|
|
ImGui::Text("- close VR overlay to use mouse");
|
|
}
|
|
ImGui::EndChild();
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void BuildSystemSelection(const VRState &state)
|
|
{
|
|
if (state.trackingSystems.empty())
|
|
{
|
|
ImGui::Text("No tracked devices are present");
|
|
return;
|
|
}
|
|
|
|
ImGuiStyle &style = ImGui::GetStyle();
|
|
float paneWidth = ImGui::GetWindowContentRegionWidth() / 2 - style.FramePadding.x;
|
|
|
|
TextWithWidth("ReferenceSystemLabel", "Reference Space", paneWidth);
|
|
ImGui::SameLine();
|
|
TextWithWidth("TargetSystemLabel", "Target Space", paneWidth);
|
|
|
|
int currentReferenceSystem = -1;
|
|
int currentTargetSystem = -1;
|
|
int firstReferenceSystemNotTargetSystem = -1;
|
|
|
|
std::vector<const char *> referenceSystems;
|
|
for (auto &str : state.trackingSystems)
|
|
{
|
|
if (str == CalCtx.referenceTrackingSystem)
|
|
{
|
|
currentReferenceSystem = (int) referenceSystems.size();
|
|
}
|
|
else if (firstReferenceSystemNotTargetSystem == -1 && str != CalCtx.targetTrackingSystem)
|
|
{
|
|
firstReferenceSystemNotTargetSystem = (int) referenceSystems.size();
|
|
}
|
|
referenceSystems.push_back(str.c_str());
|
|
}
|
|
|
|
if (currentReferenceSystem == -1 && CalCtx.referenceTrackingSystem == "")
|
|
{
|
|
currentReferenceSystem = firstReferenceSystemNotTargetSystem;
|
|
}
|
|
|
|
ImGui::PushItemWidth(paneWidth);
|
|
ImGui::Combo("##ReferenceTrackingSystem", ¤tReferenceSystem, &referenceSystems[0], (int) referenceSystems.size());
|
|
|
|
if (currentReferenceSystem != -1 && currentReferenceSystem < (int) referenceSystems.size())
|
|
{
|
|
CalCtx.referenceTrackingSystem = std::string(referenceSystems[currentReferenceSystem]);
|
|
if (CalCtx.referenceTrackingSystem == CalCtx.targetTrackingSystem)
|
|
CalCtx.targetTrackingSystem = "";
|
|
}
|
|
|
|
if (CalCtx.targetTrackingSystem == "")
|
|
currentTargetSystem = 0;
|
|
|
|
std::vector<const char *> targetSystems;
|
|
for (auto &str : state.trackingSystems)
|
|
{
|
|
if (str != CalCtx.referenceTrackingSystem)
|
|
{
|
|
if (str != "" && str == CalCtx.targetTrackingSystem)
|
|
currentTargetSystem = (int) targetSystems.size();
|
|
targetSystems.push_back(str.c_str());
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
ImGui::PushItemWidth(paneWidth);
|
|
ImGui::Combo("##TargetTrackingSystem", ¤tTargetSystem, &targetSystems[0], (int) targetSystems.size());
|
|
|
|
if (currentTargetSystem != -1 && currentTargetSystem < targetSystems.size())
|
|
{
|
|
CalCtx.targetTrackingSystem = std::string(targetSystems[currentTargetSystem]);
|
|
}
|
|
}
|
|
|
|
void AppendSeparated(std::string &buffer, const std::string &suffix)
|
|
{
|
|
if (!buffer.empty())
|
|
buffer += " | ";
|
|
buffer += suffix;
|
|
}
|
|
|
|
std::string LabelString(const VRDevice &device)
|
|
{
|
|
std::string label;
|
|
|
|
/*if (device.controllerRole == vr::TrackedControllerRole_LeftHand)
|
|
label = "Left Controller";
|
|
else if (device.controllerRole == vr::TrackedControllerRole_RightHand)
|
|
label = "Right Controller";
|
|
else if (device.deviceClass == vr::TrackedDeviceClass_Controller)
|
|
label = "Controller";
|
|
else if (device.deviceClass == vr::TrackedDeviceClass_HMD)
|
|
label = "HMD";
|
|
else if (device.deviceClass == vr::TrackedDeviceClass_GenericTracker)
|
|
label = "Tracker";*/
|
|
|
|
AppendSeparated(label, device.model);
|
|
AppendSeparated(label, device.serial);
|
|
return label;
|
|
}
|
|
|
|
void BuildDeviceSelection(const VRState &state, int &selected, const std::string &system)
|
|
{
|
|
ImGui::TextColored(ImColor(0.5f, 0.5f, 0.5f), "Devices from: %s", system.c_str());
|
|
|
|
if (selected != -1)
|
|
{
|
|
bool matched = false;
|
|
for (auto &device : state.devices)
|
|
{
|
|
if (device.trackingSystem != system)
|
|
continue;
|
|
|
|
if (selected == device.id)
|
|
{
|
|
matched = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!matched)
|
|
{
|
|
// Device is no longer present.
|
|
selected = -1;
|
|
}
|
|
}
|
|
|
|
if (selected == -1)
|
|
{
|
|
for (auto &device : state.devices)
|
|
{
|
|
if (device.trackingSystem != system)
|
|
continue;
|
|
|
|
if (device.controllerRole == vr::TrackedControllerRole_LeftHand)
|
|
{
|
|
selected = device.id;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto &device : state.devices)
|
|
{
|
|
if (device.trackingSystem != system)
|
|
continue;
|
|
|
|
if (selected == -1)
|
|
selected = device.id;
|
|
|
|
auto label = LabelString(device);
|
|
if (ImGui::Selectable(label.c_str(), selected == device.id))
|
|
selected = device.id;
|
|
}
|
|
}
|
|
|
|
void BuildDeviceSelections(const VRState &state)
|
|
{
|
|
ImGuiStyle &style = ImGui::GetStyle();
|
|
ImVec2 paneSize(ImGui::GetWindowContentRegionWidth() / 2 - style.FramePadding.x, ImGui::GetTextLineHeightWithSpacing() * 5 + style.ItemSpacing.y * 4);
|
|
|
|
ImGui::BeginChild("left device pane", paneSize, true);
|
|
static int selectedRefDevice = -1;
|
|
BuildDeviceSelection(state, selectedRefDevice, CalCtx.referenceTrackingSystem);
|
|
CalCtx.referenceID = selectedRefDevice;
|
|
ImGui::EndChild();
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::BeginChild("right device pane", paneSize, true);
|
|
static int selectedCalDevice = -1;
|
|
BuildDeviceSelection(state, selectedCalDevice, CalCtx.targetTrackingSystem);
|
|
CalCtx.targetID = selectedCalDevice;
|
|
ImGui::EndChild();
|
|
|
|
if (ImGui::Button("Identify selected devices (blinks LED or vibrates)", ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetTextLineHeightWithSpacing() + 4.0f)))
|
|
{
|
|
for (unsigned i = 0; i < 100; ++i)
|
|
{
|
|
vr::VRSystem()->TriggerHapticPulse(CalCtx.targetID, 0, 2000);
|
|
vr::VRSystem()->TriggerHapticPulse(CalCtx.referenceID, 0, 2000);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
}
|
|
}
|
|
}
|
|
|
|
VRState LoadVRState()
|
|
{
|
|
VRState state;
|
|
auto &trackingSystems = state.trackingSystems;
|
|
|
|
char buffer[vr::k_unMaxPropertyStringSize];
|
|
|
|
for (uint32_t id = 0; id < vr::k_unMaxTrackedDeviceCount; ++id)
|
|
{
|
|
vr::ETrackedPropertyError err = vr::TrackedProp_Success;
|
|
auto deviceClass = vr::VRSystem()->GetTrackedDeviceClass(id);
|
|
if (deviceClass == vr::TrackedDeviceClass_Invalid)
|
|
continue;
|
|
|
|
if (deviceClass != vr::TrackedDeviceClass_TrackingReference)
|
|
{
|
|
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_TrackingSystemName_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
|
|
|
if (err == vr::TrackedProp_Success)
|
|
{
|
|
std::string system(buffer);
|
|
auto existing = std::find(trackingSystems.begin(), trackingSystems.end(), system);
|
|
if (existing != trackingSystems.end())
|
|
{
|
|
if (deviceClass == vr::TrackedDeviceClass_HMD)
|
|
{
|
|
trackingSystems.erase(existing);
|
|
trackingSystems.insert(trackingSystems.begin(), system);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trackingSystems.push_back(system);
|
|
}
|
|
|
|
VRDevice device;
|
|
device.id = id;
|
|
device.deviceClass = deviceClass;
|
|
device.trackingSystem = system;
|
|
|
|
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_ModelNumber_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
|
device.model = std::string(buffer);
|
|
|
|
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_SerialNumber_String, buffer, vr::k_unMaxPropertyStringSize, &err);
|
|
device.serial = std::string(buffer);
|
|
|
|
device.controllerRole = (vr::ETrackedControllerRole) vr::VRSystem()->GetInt32TrackedDeviceProperty(id, vr::Prop_ControllerRoleHint_Int32, &err);
|
|
state.devices.push_back(device);
|
|
}
|
|
else
|
|
{
|
|
printf("failed to get tracking system name for id %d\n", id);
|
|
}
|
|
}
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
void BuildProfileEditor()
|
|
{
|
|
ImGuiStyle &style = ImGui::GetStyle();
|
|
float width = ImGui::GetWindowContentRegionWidth() / 3.0f - style.FramePadding.x;
|
|
float widthF = width - style.FramePadding.x;
|
|
|
|
TextWithWidth("YawLabel", "Yaw", width);
|
|
ImGui::SameLine();
|
|
TextWithWidth("PitchLabel", "Pitch", width);
|
|
ImGui::SameLine();
|
|
TextWithWidth("RollLabel", "Roll", width);
|
|
|
|
ImGui::PushItemWidth(widthF);
|
|
ImGui::InputDouble("##Yaw", &CalCtx.calibratedRotation(1), 0.1, 1.0, "%.8f");
|
|
ImGui::SameLine();
|
|
ImGui::PushItemWidth(widthF);
|
|
ImGui::InputDouble("##Pitch", &CalCtx.calibratedRotation(2), 0.1, 1.0, "%.8f");
|
|
ImGui::SameLine();
|
|
ImGui::PushItemWidth(widthF);
|
|
ImGui::InputDouble("##Roll", &CalCtx.calibratedRotation(0), 0.1, 1.0, "%.8f");
|
|
|
|
TextWithWidth("XLabel", "X", width);
|
|
ImGui::SameLine();
|
|
TextWithWidth("YLabel", "Y", width);
|
|
ImGui::SameLine();
|
|
TextWithWidth("ZLabel", "Z", width);
|
|
|
|
ImGui::PushItemWidth(widthF);
|
|
ImGui::InputDouble("##X", &CalCtx.calibratedTranslation(0), 1.0, 10.0, "%.8f");
|
|
ImGui::SameLine();
|
|
ImGui::PushItemWidth(widthF);
|
|
ImGui::InputDouble("##Y", &CalCtx.calibratedTranslation(1), 1.0, 10.0, "%.8f");
|
|
ImGui::SameLine();
|
|
ImGui::PushItemWidth(widthF);
|
|
ImGui::InputDouble("##Z", &CalCtx.calibratedTranslation(2), 1.0, 10.0, "%.8f");
|
|
}
|
|
|
|
void TextWithWidth(const char *label, const char *text, float width)
|
|
{
|
|
ImGui::BeginChild(label, ImVec2(width, ImGui::GetTextLineHeightWithSpacing()));
|
|
ImGui::Text(text);
|
|
ImGui::EndChild();
|
|
}
|