#include "stdafx.h" #include "UserInterface.h" #include "Calibration.h" #include "Configuration.h" #include #include #include #include #include #define VERSION_STRING "1.1" 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 trackingSystems; std::vector 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 BuildMenu(bool runningInOverlay); static const ImGuiWindowFlags bareWindowFlags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; void BuildMainWindow(bool runningInOverlay) { auto &io = ImGui::GetIO(); ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f), ImGuiSetCond_Always); ImGui::SetNextWindowSize(io.DisplaySize, ImGuiSetCond_Always); if (!ImGui::Begin("MainWindow", nullptr, bareWindowFlags)) { ImGui::End(); return; } ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImGui::GetStyleColorVec4(ImGuiCol_Button)); auto state = LoadVRState(); BuildSystemSelection(state); BuildDeviceSelections(state); BuildMenu(runningInOverlay); ImGui::PopStyleColor(); ImGui::End(); } void BuildMenu(bool runningInOverlay) { auto &io = ImGui::GetIO(); ImGuiStyle &style = ImGui::GetStyle(); 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); } } ImGui::Text(""); auto speed = CalCtx.calibrationSpeed; ImGui::Columns(4, NULL, false); ImGui::Text("Calibration Speed"); ImGui::NextColumn(); if (ImGui::RadioButton(" Fast ", speed == CalibrationContext::FAST)) CalCtx.calibrationSpeed = CalibrationContext::FAST; ImGui::NextColumn(); if (ImGui::RadioButton(" Slow ", speed == CalibrationContext::SLOW)) CalCtx.calibrationSpeed = CalibrationContext::SLOW; ImGui::NextColumn(); if (ImGui::RadioButton(" Very Slow ", speed == CalibrationContext::VERY_SLOW)) CalCtx.calibrationSpeed = CalibrationContext::VERY_SLOW; ImGui::Columns(1); } 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(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::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, bareWindowFlags)) { ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor(0, 0, 0)); for (auto &message : CalCtx.messages) { switch (message.type) { case CalibrationContext::Message::String: ImGui::TextWrapped(message.str.c_str()); break; case CalibrationContext::Message::Progress: float fraction = (float)message.progress / (float)message.target; ImGui::Text(""); ImGui::ProgressBar(fraction, ImVec2(-1.0f, 0.0f), ""); ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFontSize() - style.FramePadding.y * 2); ImGui::Text(" %d%%", (int)(fraction * 100)); break; } } ImGui::PopStyleColor(); if (CalCtx.state == CalibrationState::None) { ImGui::Text(""); if (ImGui::Button("Close", ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetTextLineHeight() * 2))) ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } } 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 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 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::Combo("##TargetTrackingSystem", ¤tTargetSystem, &targetSystems[0], (int) targetSystems.size()); if (currentTargetSystem != -1 && currentTargetSystem < targetSystems.size()) { CalCtx.targetTrackingSystem = std::string(targetSystems[currentTargetSystem]); } ImGui::PopItemWidth(); } 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::InputDouble("##Pitch", &CalCtx.calibratedRotation(2), 0.1, 1.0, "%.8f"); ImGui::SameLine(); 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::InputDouble("##X", &CalCtx.calibratedTranslation(0), 1.0, 10.0, "%.8f"); ImGui::SameLine(); ImGui::InputDouble("##Y", &CalCtx.calibratedTranslation(1), 1.0, 10.0, "%.8f"); ImGui::SameLine(); ImGui::InputDouble("##Z", &CalCtx.calibratedTranslation(2), 1.0, 10.0, "%.8f"); ImGui::PopItemWidth(); } void TextWithWidth(const char *label, const char *text, float width) { ImGui::BeginChild(label, ImVec2(width, ImGui::GetTextLineHeightWithSpacing())); ImGui::Text(text); ImGui::EndChild(); }