#include "stdafx.h" #include "Configuration.h" #include #include #include #include #include #include static picojson::array FloatArray(const float *buf, int numFloats) { picojson::array arr; for (int i = 0; i < numFloats; i++) arr.push_back(picojson::value(double(buf[i]))); return arr; } static void LoadFloatArray(const picojson::value &obj, float *buf, int numFloats) { if (!obj.is()) throw std::runtime_error("expected array, got " + obj.to_str()); auto &arr = obj.get(); if (arr.size() != numFloats) throw std::runtime_error("wrong buffer size"); for (int i = 0; i < numFloats; i++) buf[i] = (float) arr[i].get(); } static void LoadStandby(StandbyDevice& device, picojson::value& value) { if (!value.is()) return; auto& obj = value.get(); const auto &system = obj["tracking_system"]; if (system.is()) device.trackingSystem = system.get(); const auto& model = obj["model"]; if (model.is()) device.model = model.get(); const auto& serial = obj["serial"]; if (serial.is()) device.serial = serial.get(); } static void VisitAlignmentParams(CalibrationContext& ctx, std::function MapParam) { #define P(s) MapParam(#s, ctx.alignmentSpeedParams.s) P(align_speed_tiny); P(align_speed_small); P(align_speed_large); P(thr_trans_tiny); P(thr_trans_small); P(thr_trans_large); P(thr_rot_tiny); P(thr_rot_small); P(thr_rot_large); // Convert to double and back double tmp = ctx.continuousCalibrationThreshold; MapParam("continuousCalibrationThreshold", tmp); ctx.continuousCalibrationThreshold = (float)tmp; } static void LoadAlignmentParams(CalibrationContext& ctx, picojson::value& value) { ctx.ResetConfig(); if (!value.is()) return; auto& obj = value.get(); VisitAlignmentParams(ctx, [&](auto name, auto& param) { const picojson::value& node = obj[name]; if (node.is()) { param = (float)node.get(); } }); } static picojson::object SaveAlignmentParams(CalibrationContext& ctx) { picojson::object obj; VisitAlignmentParams(ctx, [&](auto name, auto& param) { obj[name].set(param); }); return obj; } static void ParseProfile(CalibrationContext &ctx, std::istream &stream) { picojson::value v; std::string err = picojson::parse(v, stream); if (!err.empty()) throw std::runtime_error(err); auto arr = v.get(); if (arr.size() < 1) throw std::runtime_error("no profiles in file"); auto obj = arr[0].get(); LoadAlignmentParams(ctx, obj["alignment_params"]); ctx.referenceTrackingSystem = obj["reference_tracking_system"].get(); ctx.targetTrackingSystem = obj["target_tracking_system"].get(); ctx.calibratedRotation(0) = obj["roll"].get(); ctx.calibratedRotation(1) = obj["yaw"].get(); ctx.calibratedRotation(2) = obj["pitch"].get(); ctx.calibratedTranslation(0) = obj["x"].get(); ctx.calibratedTranslation(1) = obj["y"].get(); ctx.calibratedTranslation(2) = obj["z"].get(); LoadStandby(ctx.referenceStandby, obj["reference_device"]); LoadStandby(ctx.targetStandby, obj["target_device"]); if (obj["autostart_continuous_calibration"].evaluate_as_boolean()) { ctx.state = CalibrationState::ContinuousStandby; } ctx.quashTargetInContinuous = obj["quash_target_in_continuous"].evaluate_as_boolean(); ctx.requireTriggerPressToApply = obj["require_trigger_press_to_apply"].evaluate_as_boolean(); ctx.continuousCalibrationOffset(0) = obj["continuous_calibration_target_offset_x"].get(); ctx.continuousCalibrationOffset(1) = obj["continuous_calibration_target_offset_y"].get(); ctx.continuousCalibrationOffset(2) = obj["continuous_calibration_target_offset_z"].get(); if (obj["scale"].is()) ctx.calibratedScale = obj["scale"].get(); else ctx.calibratedScale = 1.0; if (obj["calibration_speed"].is()) ctx.calibrationSpeed = (CalibrationContext::Speed)(int) obj["calibration_speed"].get(); if (obj["chaperone"].is()) { auto chaperone = obj["chaperone"].get(); ctx.chaperone.autoApply = chaperone["auto_apply"].get(); LoadFloatArray(chaperone["play_space_size"], ctx.chaperone.playSpaceSize.v, 2); LoadFloatArray( chaperone["standing_center"], (float *) ctx.chaperone.standingCenter.m, sizeof(ctx.chaperone.standingCenter.m) / sizeof(float) ); if (!chaperone["geometry"].is()) throw std::runtime_error("chaperone geometry is not an array"); auto &geometry = chaperone["geometry"].get(); if (geometry.size() > 0) { ctx.chaperone.geometry.resize(geometry.size() * sizeof(float) / sizeof(ctx.chaperone.geometry[0])); LoadFloatArray(chaperone["geometry"], (float *) ctx.chaperone.geometry.data(), geometry.size()); ctx.chaperone.valid = true; } } if (obj["relative_pos_calibrated"].is()) { ctx.relativePosCalibrated = obj["relative_pos_calibrated"].get(); } if (obj["lock_relative_position"].is()) { ctx.lockRelativePosition = obj["lock_relative_position"].get(); } if (obj["relative_transform"].is()) { auto relTransform = obj["relative_transform"].get(); Eigen::Vector3d refToTragetRoation; Eigen::Vector3d refToTargetTranslation; refToTragetRoation(0) = relTransform["roll"].get(); refToTragetRoation(1) = relTransform["yaw"].get(); refToTragetRoation(2) = relTransform["pitch"].get(); refToTargetTranslation(0) = relTransform["x"].get(); refToTargetTranslation(1) = relTransform["y"].get(); refToTargetTranslation(2) = relTransform["z"].get(); Eigen::Matrix3d rotationMatrix; rotationMatrix = Eigen::AngleAxisd(refToTragetRoation[0], Eigen::Vector3d::UnitX()) * Eigen::AngleAxisd(refToTragetRoation[1], Eigen::Vector3d::UnitY()) * Eigen::AngleAxisd(refToTragetRoation[2], Eigen::Vector3d::UnitZ()); ctx.refToTargetPose = Eigen::AffineCompact3d::Identity(); ctx.refToTargetPose.linear() = rotationMatrix; ctx.refToTargetPose.translation() = refToTargetTranslation; } ctx.validProfile = true; } static void WriteStandby(StandbyDevice& device, picojson::value& value) { auto obj = picojson::object(); obj["tracking_system"].set(device.trackingSystem); obj["model"].set(device.model); obj["serial"].set(device.serial); value.set(obj); } static void WriteProfile(CalibrationContext &ctx, std::ostream &out) { if (!ctx.validProfile) return; picojson::object profile; profile["alignment_params"].set(SaveAlignmentParams(ctx)); profile["reference_tracking_system"].set(ctx.referenceTrackingSystem); profile["target_tracking_system"].set(ctx.targetTrackingSystem); profile["roll"].set(ctx.calibratedRotation(0)); profile["yaw"].set(ctx.calibratedRotation(1)); profile["pitch"].set(ctx.calibratedRotation(2)); profile["x"].set(ctx.calibratedTranslation(0)); profile["y"].set(ctx.calibratedTranslation(1)); profile["z"].set(ctx.calibratedTranslation(2)); profile["scale"].set(ctx.calibratedScale); WriteStandby(ctx.referenceStandby, profile["reference_device"]); WriteStandby(ctx.targetStandby, profile["target_device"]); bool isInContinuousCalibrationMode = ctx.state == CalibrationState::Continuous || ctx.state == CalibrationState::ContinuousStandby; profile["autostart_continuous_calibration"].set(isInContinuousCalibrationMode); profile["quash_target_in_continuous"].set(ctx.quashTargetInContinuous); profile["require_trigger_press_to_apply"].set(ctx.requireTriggerPressToApply); profile["continuous_calibration_target_offset_x"].set(ctx.continuousCalibrationOffset(0)); profile["continuous_calibration_target_offset_y"].set(ctx.continuousCalibrationOffset(1)); profile["continuous_calibration_target_offset_z"].set(ctx.continuousCalibrationOffset(2)); double speed = (int) ctx.calibrationSpeed; profile["calibration_speed"].set(speed); if (ctx.chaperone.valid) { picojson::object chaperone; chaperone["auto_apply"].set(ctx.chaperone.autoApply); chaperone["play_space_size"].set(FloatArray(ctx.chaperone.playSpaceSize.v, 2)); chaperone["standing_center"].set(FloatArray( (float *) ctx.chaperone.standingCenter.m, sizeof(ctx.chaperone.standingCenter.m) / sizeof(float) )); chaperone["geometry"].set(FloatArray( (float *) ctx.chaperone.geometry.data(), sizeof(ctx.chaperone.geometry[0]) / sizeof(float) * ctx.chaperone.geometry.size() )); profile["chaperone"].set(chaperone); } Eigen::Vector3d refToTragetRoation = ctx.refToTargetPose.rotation().eulerAngles(0, 1, 2); Eigen::Vector3d refToTargetTranslation = ctx.refToTargetPose.translation(); picojson::object refToTarget; refToTarget["x"].set(refToTargetTranslation(0)); refToTarget["y"].set(refToTargetTranslation(1)); refToTarget["z"].set(refToTargetTranslation(2)); refToTarget["roll"].set(refToTragetRoation(0)); refToTarget["yaw"].set(refToTragetRoation(1)); refToTarget["pitch"].set(refToTragetRoation(2)); profile["relative_pos_calibrated"].set(ctx.relativePosCalibrated); profile["lock_relative_position"].set(ctx.lockRelativePosition); profile["relative_transform"].set(refToTarget); picojson::value profileV; profileV.set(profile); picojson::array profiles; profiles.push_back(profileV); picojson::value profilesV; profilesV.set(profiles); out << profilesV.serialize(true); } 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(str.c_str()), size); if (result != ERROR_SUCCESS) LogRegistryResult(result); RegCloseKey(hkey); } void LoadProfile(CalibrationContext &ctx) { // @TODO: Rewrite this to migrate configs from the registry to the spacecal directory // I don't know why whoever wrote this thought writing to the registry in the 2020s was a good idea... // NOTE: HKEY_CURRENT_USER_LOCAL_SETTINGS evaluates to HKCU\Software\Classes\Local Settings // Settings are currently stored at HKCU\Software\Classes\Local Settings\Software\OpenVR-SpaceCalibrator ctx.validProfile = false; auto str = ReadRegistryKey(); if (str == "") { std::cout << "Profile is empty" << std::endl; ctx.Clear(); return; } 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; } } void SaveProfile(CalibrationContext &ctx) { std::cout << "Saving profile to registry" << std::endl; std::stringstream io; WriteProfile(ctx, io); WriteRegistryKey(io.str()); }