mirror of
https://github.com/hyblocker/OpenVR-SpaceCalibrator.git
synced 2026-10-07 05:00:20 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e6796a9b5 | ||
|
|
6389d745ae | ||
|
|
326b54ba74 | ||
|
|
98b23ad5bb | ||
|
|
7788c2af38 | ||
|
|
5334084508 | ||
|
|
9752a3be05 | ||
|
|
8323d64b2d | ||
|
|
b04615dcb6 | ||
|
|
9d1fa11f98 |
No files matched your search
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Something is broken
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
If you are having trouble setting this up with SteamVR and would like help, the fastest way is to ask in the Discord group. https://discord.com/invite/m7g2Wyj
|
||||
|
||||
If you are having issues after updating SteamVR, please check for a new release at https://github.com/pushrax/OpenVR-SpaceCalibrator/releases before posting an issue.
|
||||
-->
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Justin Li
|
||||
Copyright (c) 2020 Justin Li
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -113,7 +113,7 @@ Eigen::Vector3d CalibrateRotation(const std::vector<Sample> &samples)
|
||||
}
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof buf, "Got %zd samples with %zd delta samples\n", samples.size(), deltas.size());
|
||||
CalCtx.Message(buf);
|
||||
CalCtx.Log(buf);
|
||||
|
||||
// Kabsch algorithm
|
||||
|
||||
@@ -155,7 +155,7 @@ Eigen::Vector3d CalibrateRotation(const std::vector<Sample> &samples)
|
||||
Eigen::Vector3d euler = rot.eulerAngles(2, 1, 0) * 180.0 / EIGEN_PI;
|
||||
|
||||
snprintf(buf, sizeof buf, "Calibrated rotation: yaw=%.2f pitch=%.2f roll=%.2f\n", euler[1], euler[2], euler[0]);
|
||||
CalCtx.Message(buf);
|
||||
CalCtx.Log(buf);
|
||||
return euler;
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ Eigen::Vector3d CalibrateTranslation(const std::vector<Sample> &samples)
|
||||
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof buf, "Calibrated translation x=%.2f y=%.2f z=%.2f\n", transcm[0], transcm[1], transcm[2]);
|
||||
CalCtx.Message(buf);
|
||||
CalCtx.Log(buf);
|
||||
return transcm;
|
||||
}
|
||||
|
||||
@@ -214,15 +214,15 @@ Sample CollectSample(const CalibrationContext &ctx)
|
||||
bool ok = true;
|
||||
if (!reference.bPoseIsValid)
|
||||
{
|
||||
CalCtx.Message("Reference device is not tracking\n"); ok = false;
|
||||
CalCtx.Log("Reference device is not tracking\n"); ok = false;
|
||||
}
|
||||
if (!target.bPoseIsValid)
|
||||
{
|
||||
CalCtx.Message("Target device is not tracking\n"); ok = false;
|
||||
CalCtx.Log("Target device is not tracking\n"); ok = false;
|
||||
}
|
||||
if (!ok)
|
||||
{
|
||||
CalCtx.Message("Aborting calibration!\n");
|
||||
CalCtx.Log("Aborting calibration!\n");
|
||||
CalCtx.state = CalibrationState::None;
|
||||
return Sample();
|
||||
}
|
||||
@@ -361,7 +361,7 @@ void StartCalibration()
|
||||
{
|
||||
CalCtx.state = CalibrationState::Begin;
|
||||
CalCtx.wantedUpdateInterval = 0.0;
|
||||
CalCtx.messages = "";
|
||||
CalCtx.messages.clear();
|
||||
}
|
||||
|
||||
void CalibrationTick(double time)
|
||||
@@ -404,28 +404,38 @@ void CalibrationTick(double time)
|
||||
{
|
||||
bool ok = true;
|
||||
|
||||
char referenceSerial[256], targetSerial[256];
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(ctx.referenceID, vr::Prop_SerialNumber_String, referenceSerial, 256);
|
||||
vr::VRSystem()->GetStringTrackedDeviceProperty(ctx.targetID, vr::Prop_SerialNumber_String, targetSerial, 256);
|
||||
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof buf, "Reference device ID: %d, serial: %s\n", ctx.referenceID, referenceSerial);
|
||||
CalCtx.Log(buf);
|
||||
snprintf(buf, sizeof buf, "Target device ID: %d, serial %s\n", ctx.targetID, targetSerial);
|
||||
CalCtx.Log(buf);
|
||||
|
||||
if (ctx.referenceID == -1)
|
||||
{
|
||||
CalCtx.Message("Missing reference device\n"); ok = false;
|
||||
CalCtx.Log("Missing reference device\n"); ok = false;
|
||||
}
|
||||
else if (!ctx.devicePoses[ctx.referenceID].bPoseIsValid)
|
||||
{
|
||||
CalCtx.Message("Reference device is not tracking\n"); ok = false;
|
||||
CalCtx.Log("Reference device is not tracking\n"); ok = false;
|
||||
}
|
||||
|
||||
if (ctx.targetID == -1)
|
||||
{
|
||||
CalCtx.Message("Missing target device\n"); ok = false;
|
||||
CalCtx.Log("Missing target device\n"); ok = false;
|
||||
}
|
||||
else if (!ctx.devicePoses[ctx.targetID].bPoseIsValid)
|
||||
{
|
||||
CalCtx.Message("Target device is not tracking\n"); ok = false;
|
||||
CalCtx.Log("Target device is not tracking\n"); ok = false;
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
ctx.state = CalibrationState::None;
|
||||
CalCtx.Message("Aborting calibration!\n");
|
||||
CalCtx.Log("Aborting calibration!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -433,9 +443,7 @@ void CalibrationTick(double time)
|
||||
ctx.state = CalibrationState::Rotation;
|
||||
ctx.wantedUpdateInterval = 0.0;
|
||||
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof buf, "Starting calibration, referenceID=%d targetID=%d\n", ctx.referenceID, ctx.targetID);
|
||||
CalCtx.Message(buf);
|
||||
CalCtx.Log("Starting calibration...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -444,15 +452,15 @@ void CalibrationTick(double time)
|
||||
{
|
||||
return;
|
||||
}
|
||||
CalCtx.Message(".");
|
||||
|
||||
const int totalSamples = 100;
|
||||
static std::vector<Sample> samples;
|
||||
samples.push_back(sample);
|
||||
|
||||
if (samples.size() == totalSamples)
|
||||
CalCtx.Progress(samples.size(), CalCtx.SampleCount());
|
||||
|
||||
if (samples.size() == CalCtx.SampleCount())
|
||||
{
|
||||
CalCtx.Message("\n");
|
||||
CalCtx.Log("\n");
|
||||
if (ctx.state == CalibrationState::Rotation)
|
||||
{
|
||||
ctx.calibratedRotation = CalibrateRotation(samples);
|
||||
@@ -477,7 +485,7 @@ void CalibrationTick(double time)
|
||||
|
||||
ctx.validProfile = true;
|
||||
SaveProfile(ctx);
|
||||
CalCtx.Message("Finished calibration, profile saved\n");
|
||||
CalCtx.Log("Finished calibration, profile saved\n");
|
||||
|
||||
ctx.state = CalibrationState::None;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,14 @@ struct CalibrationContext
|
||||
double timeLastTick = 0, timeLastScan = 0;
|
||||
double wantedUpdateInterval = 1.0;
|
||||
|
||||
enum Speed
|
||||
{
|
||||
FAST = 0,
|
||||
SLOW = 1,
|
||||
VERY_SLOW = 2
|
||||
};
|
||||
Speed calibrationSpeed = FAST;
|
||||
|
||||
vr::TrackedDevicePose_t devicePoses[vr::k_unMaxTrackedDeviceCount];
|
||||
|
||||
struct Chaperone
|
||||
@@ -40,14 +48,6 @@ struct CalibrationContext
|
||||
vr::HmdVector2_t playSpaceSize;
|
||||
} chaperone;
|
||||
|
||||
std::string messages;
|
||||
|
||||
void Message(const std::string &msg)
|
||||
{
|
||||
messages += msg;
|
||||
std::cerr << msg;
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
chaperone.geometry.clear();
|
||||
@@ -62,6 +62,54 @@ struct CalibrationContext
|
||||
enabled = false;
|
||||
validProfile = false;
|
||||
}
|
||||
|
||||
size_t SampleCount()
|
||||
{
|
||||
switch (calibrationSpeed)
|
||||
{
|
||||
case FAST:
|
||||
return 100;
|
||||
case SLOW:
|
||||
return 250;
|
||||
case VERY_SLOW:
|
||||
return 500;
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
|
||||
struct Message
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
String,
|
||||
Progress
|
||||
} type = String;
|
||||
|
||||
Message(Type type) : type(type) { }
|
||||
|
||||
std::string str;
|
||||
int progress, target;
|
||||
};
|
||||
|
||||
std::vector<Message> messages;
|
||||
|
||||
void Log(const std::string &msg)
|
||||
{
|
||||
if (messages.empty() || messages.back().type == Message::Progress)
|
||||
messages.push_back(Message(Message::String));
|
||||
|
||||
messages.back().str += msg;
|
||||
std::cerr << msg;
|
||||
}
|
||||
|
||||
void Progress(int current, int target)
|
||||
{
|
||||
if (messages.empty() || messages.back().type == Message::String)
|
||||
messages.push_back(Message(Message::Progress));
|
||||
|
||||
messages.back().progress = current;
|
||||
messages.back().target = target;
|
||||
}
|
||||
};
|
||||
|
||||
extern CalibrationContext CalCtx;
|
||||
|
||||
@@ -9,19 +9,7 @@
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
|
||||
static void UpgradeProfileV1(CalibrationContext &ctx);
|
||||
static void ParseProfileV2(CalibrationContext &ctx, std::istream &stream);
|
||||
|
||||
static std::string ConfigFileName()
|
||||
{
|
||||
char cruntimePath[MAX_PATH] = { 0 };
|
||||
unsigned int pathLen;
|
||||
vr::VR_GetRuntimePath(cruntimePath, MAX_PATH, &pathLen);
|
||||
std::string vrRuntimeConfigName(cruntimePath);
|
||||
return vrRuntimeConfigName + "\\..\\..\\..\\config\\01spacecalibrator\\calibration.json";
|
||||
}
|
||||
|
||||
picojson::array FloatArray(const float *buf, int numFloats)
|
||||
static picojson::array FloatArray(const float *buf, int numFloats)
|
||||
{
|
||||
picojson::array arr;
|
||||
|
||||
@@ -31,7 +19,7 @@ picojson::array FloatArray(const float *buf, int numFloats)
|
||||
return arr;
|
||||
}
|
||||
|
||||
void LoadFloatArray(const picojson::value &obj, float *buf, int numFloats)
|
||||
static void LoadFloatArray(const picojson::value &obj, float *buf, int numFloats)
|
||||
{
|
||||
if (!obj.is<picojson::array>())
|
||||
throw std::runtime_error("expected array, got " + obj.to_str());
|
||||
@@ -44,38 +32,7 @@ void LoadFloatArray(const picojson::value &obj, float *buf, int numFloats)
|
||||
buf[i] = (float) arr[i].get<double>();
|
||||
}
|
||||
|
||||
void LoadProfile(CalibrationContext &ctx)
|
||||
{
|
||||
ctx.validProfile = false;
|
||||
|
||||
std::ifstream file(ConfigFileName());
|
||||
if (!file.good())
|
||||
{
|
||||
std::cout << "Profile missing, trying fallback path" << std::endl;
|
||||
// Fallback to working directory, which was the default for a long time.
|
||||
file = std::ifstream("openvr_space_calibration.json");
|
||||
}
|
||||
|
||||
|
||||
if (!file.good())
|
||||
{
|
||||
std::cout << "Fallback profile missing, trying V1 path" << std::endl;
|
||||
UpgradeProfileV1(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ParseProfileV2(ctx, file);
|
||||
std::cout << "Loaded profile" << std::endl;
|
||||
}
|
||||
catch (const std::runtime_error &e)
|
||||
{
|
||||
std::cerr << "Error loading profile: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
static void ParseProfileV2(CalibrationContext &ctx, std::istream &stream)
|
||||
static void ParseProfile(CalibrationContext &ctx, std::istream &stream)
|
||||
{
|
||||
picojson::value v;
|
||||
std::string err = picojson::parse(v, stream);
|
||||
@@ -97,6 +54,9 @@ static void ParseProfileV2(CalibrationContext &ctx, std::istream &stream)
|
||||
ctx.calibratedTranslation(1) = obj["y"].get<double>();
|
||||
ctx.calibratedTranslation(2) = obj["z"].get<double>();
|
||||
|
||||
if (obj["calibration_speed"].is<double>())
|
||||
ctx.calibrationSpeed = (CalibrationContext::Speed)(int) obj["calibration_speed"].get<double>();
|
||||
|
||||
if (obj["chaperone"].is<picojson::object>())
|
||||
{
|
||||
auto chaperone = obj["chaperone"].get<picojson::object>();
|
||||
@@ -127,17 +87,10 @@ static void ParseProfileV2(CalibrationContext &ctx, std::istream &stream)
|
||||
ctx.validProfile = true;
|
||||
}
|
||||
|
||||
void SaveProfile(CalibrationContext &ctx)
|
||||
static void WriteProfile(CalibrationContext &ctx, std::ostream &out)
|
||||
{
|
||||
std::cout << "Saving profile to " << ConfigFileName() << std::endl;
|
||||
|
||||
std::ofstream file(ConfigFileName());
|
||||
|
||||
if (!ctx.validProfile)
|
||||
{
|
||||
file << "[]";
|
||||
return;
|
||||
}
|
||||
|
||||
picojson::object profile;
|
||||
profile["reference_tracking_system"].set<std::string>(ctx.referenceTrackingSystem);
|
||||
@@ -149,6 +102,9 @@ void SaveProfile(CalibrationContext &ctx)
|
||||
profile["y"].set<double>(ctx.calibratedTranslation(1));
|
||||
profile["z"].set<double>(ctx.calibratedTranslation(2));
|
||||
|
||||
double speed = (int) ctx.calibrationSpeed;
|
||||
profile["calibration_speed"].set<double>(speed);
|
||||
|
||||
if (ctx.chaperone.valid)
|
||||
{
|
||||
picojson::object chaperone;
|
||||
@@ -177,30 +133,89 @@ void SaveProfile(CalibrationContext &ctx)
|
||||
picojson::value profilesV;
|
||||
profilesV.set<picojson::array>(profiles);
|
||||
|
||||
file << profilesV.serialize(true);
|
||||
out << profilesV.serialize(true);
|
||||
}
|
||||
|
||||
static void UpgradeProfileV1(CalibrationContext &ctx)
|
||||
static void LogRegistryResult(LSTATUS result)
|
||||
{
|
||||
char *message;
|
||||
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, result, LANG_USER_DEFAULT, (LPSTR)&message, 0, NULL);
|
||||
std::cerr << "Opening registry key: " << message << std::endl;
|
||||
}
|
||||
|
||||
static const char *RegistryKey = "Software\\OpenVR-SpaceCalibrator";
|
||||
|
||||
static std::string ReadRegistryKey()
|
||||
{
|
||||
DWORD size = 0;
|
||||
auto result = RegGetValueA(HKEY_CURRENT_USER_LOCAL_SETTINGS, RegistryKey, "Config", RRF_RT_REG_SZ, 0, 0, &size);
|
||||
if (result != ERROR_SUCCESS)
|
||||
{
|
||||
LogRegistryResult(result);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string str;
|
||||
str.resize(size);
|
||||
|
||||
result = RegGetValueA(HKEY_CURRENT_USER_LOCAL_SETTINGS, RegistryKey, "Config", RRF_RT_REG_SZ, 0, &str[0], &size);
|
||||
if (result != ERROR_SUCCESS)
|
||||
{
|
||||
LogRegistryResult(result);
|
||||
return "";
|
||||
}
|
||||
|
||||
str.resize(size - 1);
|
||||
return str;
|
||||
}
|
||||
|
||||
static void WriteRegistryKey(std::string str)
|
||||
{
|
||||
HKEY hkey;
|
||||
auto result = RegCreateKeyExA(HKEY_CURRENT_USER_LOCAL_SETTINGS, RegistryKey, 0, REG_NONE, 0, KEY_ALL_ACCESS, 0, &hkey, 0);
|
||||
if (result != ERROR_SUCCESS)
|
||||
{
|
||||
LogRegistryResult(result);
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD size = str.size() + 1;
|
||||
|
||||
result = RegSetValueExA(hkey, "Config", 0, REG_SZ, reinterpret_cast<const BYTE*>(str.c_str()), size);
|
||||
if (result != ERROR_SUCCESS)
|
||||
LogRegistryResult(result);
|
||||
|
||||
RegCloseKey(hkey);
|
||||
}
|
||||
|
||||
void LoadProfile(CalibrationContext &ctx)
|
||||
{
|
||||
std::ifstream file("openvr_space_calibration.txt");
|
||||
ctx.validProfile = false;
|
||||
|
||||
if (!file.good())
|
||||
auto str = ReadRegistryKey();
|
||||
if (str == "")
|
||||
{
|
||||
std::cout << "Profile is empty" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
file
|
||||
>> ctx.targetTrackingSystem
|
||||
>> ctx.calibratedRotation(1) // yaw
|
||||
>> ctx.calibratedRotation(2) // pitch
|
||||
>> ctx.calibratedRotation(0) // roll
|
||||
>> ctx.calibratedTranslation(0) // x
|
||||
>> ctx.calibratedTranslation(1) // y
|
||||
>> ctx.calibratedTranslation(2); // z
|
||||
try
|
||||
{
|
||||
std::stringstream io(str);
|
||||
ParseProfile(ctx, io);
|
||||
std::cout << "Loaded profile" << std::endl;
|
||||
}
|
||||
catch (const std::runtime_error &e)
|
||||
{
|
||||
std::cerr << "Error loading profile: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.validProfile = true;
|
||||
void SaveProfile(CalibrationContext &ctx)
|
||||
{
|
||||
std::cout << "Saving profile to registry" << std::endl;
|
||||
|
||||
SaveProfile(ctx);
|
||||
|
||||
file.close();
|
||||
std::remove("openvr_space_calibration.txt");
|
||||
std::stringstream io;
|
||||
WriteProfile(ctx, io);
|
||||
WriteRegistryKey(io.str());
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <algorithm>
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#define VERSION_STRING "0.9.0"
|
||||
#define VERSION_STRING "1.2"
|
||||
|
||||
struct VRDevice
|
||||
{
|
||||
@@ -33,31 +33,41 @@ 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)
|
||||
{
|
||||
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))
|
||||
if (!ImGui::Begin("MainWindow", nullptr, bareWindowFlags))
|
||||
{
|
||||
ImGui::End();
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
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)
|
||||
@@ -125,6 +135,26 @@ void BuildMainWindow(bool runningInOverlay)
|
||||
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)
|
||||
{
|
||||
@@ -141,22 +171,6 @@ void BuildMainWindow(bool runningInOverlay)
|
||||
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");
|
||||
@@ -167,7 +181,38 @@ void BuildMainWindow(bool runningInOverlay)
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::End();
|
||||
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)
|
||||
@@ -233,13 +278,14 @@ void BuildSystemSelection(const VRState &state)
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
void AppendSeparated(std::string &buffer, const std::string &suffix)
|
||||
@@ -428,10 +474,8 @@ void BuildProfileEditor()
|
||||
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);
|
||||
@@ -440,14 +484,12 @@ void BuildProfileEditor()
|
||||
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");
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
void TextWithWidth(const char *label, const char *text, float width)
|
||||
|
||||
@@ -9,15 +9,28 @@ static Hook<void*(*)(vr::IVRDriverContext *, const char *, vr::EVRInitError *)>
|
||||
GetGenericInterfaceHook("IVRDriverContext::GetGenericInterface");
|
||||
|
||||
static Hook<void(*)(vr::IVRServerDriverHost *, uint32_t, const vr::DriverPose_t &, uint32_t)>
|
||||
TrackedDevicePoseUpdatedHook("IVRServerDriverHost005::TrackedDevicePoseUpdated");
|
||||
TrackedDevicePoseUpdatedHook005("IVRServerDriverHost005::TrackedDevicePoseUpdated");
|
||||
|
||||
static void DetourTrackedDevicePoseUpdated(vr::IVRServerDriverHost *_this, uint32_t unWhichDevice, const vr::DriverPose_t &newPose, uint32_t unPoseStructSize)
|
||||
static Hook<void(*)(vr::IVRServerDriverHost *, uint32_t, const vr::DriverPose_t &, uint32_t)>
|
||||
TrackedDevicePoseUpdatedHook006("IVRServerDriverHost006::TrackedDevicePoseUpdated");
|
||||
|
||||
static void DetourTrackedDevicePoseUpdated005(vr::IVRServerDriverHost *_this, uint32_t unWhichDevice, const vr::DriverPose_t &newPose, uint32_t unPoseStructSize)
|
||||
{
|
||||
//TRACE("ServerTrackedDeviceProvider::DetourTrackedDevicePoseUpdated(%d)", unWhichDevice);
|
||||
auto pose = newPose;
|
||||
if (Driver->HandleDevicePoseUpdated(unWhichDevice, pose))
|
||||
{
|
||||
TrackedDevicePoseUpdatedHook.originalFunc(_this, unWhichDevice, pose, unPoseStructSize);
|
||||
TrackedDevicePoseUpdatedHook005.originalFunc(_this, unWhichDevice, pose, unPoseStructSize);
|
||||
}
|
||||
}
|
||||
|
||||
static void DetourTrackedDevicePoseUpdated006(vr::IVRServerDriverHost *_this, uint32_t unWhichDevice, const vr::DriverPose_t &newPose, uint32_t unPoseStructSize)
|
||||
{
|
||||
//TRACE("ServerTrackedDeviceProvider::DetourTrackedDevicePoseUpdated(%d)", unWhichDevice);
|
||||
auto pose = newPose;
|
||||
if (Driver->HandleDevicePoseUpdated(unWhichDevice, pose))
|
||||
{
|
||||
TrackedDevicePoseUpdatedHook006.originalFunc(_this, unWhichDevice, pose, unPoseStructSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +42,18 @@ static void *DetourGetGenericInterface(vr::IVRDriverContext *_this, const char *
|
||||
std::string iface(pchInterfaceVersion);
|
||||
if (iface == "IVRServerDriverHost_005")
|
||||
{
|
||||
if (!IHook::Exists(TrackedDevicePoseUpdatedHook.name))
|
||||
if (!IHook::Exists(TrackedDevicePoseUpdatedHook005.name))
|
||||
{
|
||||
TrackedDevicePoseUpdatedHook.CreateHookInObjectVTable(originalInterface, 1, &DetourTrackedDevicePoseUpdated);
|
||||
IHook::Register(&TrackedDevicePoseUpdatedHook);
|
||||
TrackedDevicePoseUpdatedHook005.CreateHookInObjectVTable(originalInterface, 1, &DetourTrackedDevicePoseUpdated005);
|
||||
IHook::Register(&TrackedDevicePoseUpdatedHook005);
|
||||
}
|
||||
}
|
||||
else if (iface == "IVRServerDriverHost_006")
|
||||
{
|
||||
if (!IHook::Exists(TrackedDevicePoseUpdatedHook006.name))
|
||||
{
|
||||
TrackedDevicePoseUpdatedHook006.CreateHookInObjectVTable(originalInterface, 1, &DetourTrackedDevicePoseUpdated006);
|
||||
IHook::Register(&TrackedDevicePoseUpdatedHook006);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
# OpenVR Space Calibrator
|
||||
|
||||
Use tracked VR devices from one company with any other.
|
||||
This helps you use tracked VR devices from one company with any other. It aligns multiple tracking systems with a quick calibration step. It may not work for your setup, but there are many cases that work to a degree, and some work very well.
|
||||
|
||||
This is **experimental software** and may not work for your setup. It's well-tested for the Rift with Vive trackers though, with a community of a few hundred users ([Discord][]). It also seems to work for various HMDs (Windows MR, Rift) with Vive controllers and Knuckles prototypes. A quick video of how this works using an old version (~v0.3) is available at https://www.youtube.com/watch?v=W3TnQd9JMl4.
|
||||
- Rift CV1 x Vive devices: works very well with the v2 (blue logo) trackers, v1 trackers (grey logo, not in production) have major interference issues in the IR spectrum, controller wands (both gen) and Index controllers work very well.
|
||||
- Rift S, Quest, Windows MR, other SLAM inside-out tracked HMDs x Vive devices: works very well when you aren't moving around the room far (e.g. Beat Saber) but a lot of walking around causes a nontrivial amount of drift between systems. Your results may vary depending on your space. It's possible some of this can be fixed in software with a better calibration algorithm.
|
||||
- Quest wireless streaming is particularly bad right now and requires frequent recalibration, but it does work for a short time until one of many factors causes it to drift. With wireless devices, moving slowly when calibrating and using the Slow or Very Slow calibration modes is effective at reducing the initial error.
|
||||
- Any non-Rift HMD x Touch controllers: does not work, the Oculus driver requires the HMD is a Rift. It's theoretically possible to work around this in software but as far as I know it hasn't been done as it would require a fair amount of reverse engineering effort.
|
||||
|
||||
There is a community of a few thousand users on [Discord][].
|
||||
|
||||
A quick video of how this works using an old version (~v0.3) is available at https://www.youtube.com/watch?v=W3TnQd9JMl4. The user interface has been upgraded since then; the calibration is now done via a SteamVR dashboard menu, and there's much more configurability.
|
||||
|
||||
[Discord]: https://discord.gg/m7g2Wyj
|
||||
|
||||
### Install
|
||||
|
||||
Before following the directions below, download and run the installer for the [latest release](https://github.com/pushrax/OpenVR-SpaceCalibrator/releases). This will automatically set up SteamVR for use with multiple tracking systems (`activateMultipleDrivers: true`).
|
||||
Before following the directions below, download and run the installer for the [latest release](https://github.com/pushrax/OpenVR-SpaceCalibrator/releases). This will automatically set up SteamVR for use with multiple tracking systems (`activateMultipleDrivers: true`). There are many guides that say you need to edit the SteamVR config manually. You do not.
|
||||
|
||||
### Usage
|
||||
|
||||
|
||||
Reference in new issue
Block a user