Compare commits

...
39 Commits
Author SHA1 Message Date
Hyblocker 8100d776b8 Merge pull request #14 from PiMaker/pi-adds-require-trigger-press-mode
adds require trigger press mode
2024-07-14 17:33:06 +02:00
Hyblocker 137d8916af Merge pull request #13 from PiMaker/pi-does-dynamic-calibration-speed-in-continuous
dynamic calibration speed in continuous, merge arcticfox's relative pose lock
2024-07-14 17:32:56 +02:00
Hyblocker ceeab0e6e2 Merge pull request #12 from PiMaker/pi-falls-back-to-exclude-serial-for-hmd
fall back to serial-excluded matching for HMD finding
2024-07-14 17:32:45 +02:00
Hyblocker 797f65ad0d Merge pull request #11 from PiMaker/pi-pause-on-tracking-lost
detect when headset does not send valid tracking data and pause
2024-07-14 17:32:32 +02:00
pi 3460437924 fall back to serial-excluded matching for HMD finding
this allows keeping settings working between different tracking
methods (e.g. switching from Virtual Desktop to Oculus Link will
not break detecting the same HMD)

for some reason serial number changes between link methods...
2024-07-06 18:51:14 +02:00
pi 67f7c820f5 move trigger detection earlier 2024-07-06 16:56:05 +02:00
pi 3ac6dfc208 add "require trigger press to apply calibration" mode 2024-07-06 16:55:28 +02:00
pi 8b86898417 allow calibration speed setting to be used for continuous 2024-07-06 16:52:52 +02:00
pi d9fb08560b dynamically calculate dropping frames in continuous 2024-07-06 16:51:03 +02:00
pi 0ac3ab3ad8 Merge remote-tracking branch 'arcticfox/Branch_v1.4-bd_-r2' into pi-merges-relative-pose-lock 2024-07-06 16:50:05 +02:00
pi b0fe076845 detect when headset does not send valid tracking data and pause
this happens when a Quest enters out-of-bounds passthrough mode or
standby for example, in which case it's almost always cleaner to keep
the current calibration alive instead of calibrating towards a zero
location
2024-07-06 16:47:27 +02:00
Hyblocker 5fba766d4d Merge pull request #10 from Tropingenie/Branch_v1.4-bd_-r2
Update README.md
2024-06-18 01:36:29 +02:00
Tropingenie 9da1c41927 Update README.md
Updates "latest release" hyperlink. Now correctly opens latest release, and points to hyblocker repo instead of pushrax.
2024-06-16 20:26:16 -07:00
Hyblocker 31fb1d15f9 Add preliminary support for playspace scale 2024-05-22 20:17:32 +02:00
Hyblocker 16625138e1 Return more verbose error on startup fail 2024-05-22 20:16:30 +02:00
Evgeny Zuev d0159852e1 Add relative pose lock, use slow preset again 2024-05-21 20:36:15 +03:00
Hyblocker d22b2d1c69 Make settings UI more coherent 2024-02-18 19:04:23 +01:00
Hyblocker 308641ebf2 Add tracker offsets 2024-02-18 19:01:21 +01:00
Hyblocker 19ed75c378 Add tracker offset UI to settings 2024-02-18 05:23:09 +01:00
Hyblocker 4dd9ccfc79 Group settings into groups 2024-02-18 05:22:53 +01:00
Hyblocker f578999f0e Rename alignment speeds to settings 2024-02-18 05:20:57 +01:00
Hyblocker 7a4dc9007d Save cc tracker offset to config 2024-02-18 05:20:21 +01:00
Hyblocker 02f4aa22dc Add fields for cc tracker offset 2024-02-18 05:20:03 +01:00
Hyblocker 89d231472a Fix crash with keyboard input 2024-02-18 05:19:40 +01:00
Hyblocker 1515312e76 Add Extension to ImGui for box style containers 2024-02-18 05:19:22 +01:00
Hyblocker 7349f510ef Add instructions for continuous calibration 2024-02-17 17:44:12 +01:00
Hyblocker 71b82d0641 Cleanup installer dir 2024-02-17 17:36:57 +01:00
Hyblocker 68476caafc Bump version 2024-02-17 17:35:33 +01:00
Hyblocker 011158cd2c Add special handling for Pimax Crystal
Pimax please don't touch other drivers and please don't lie about what you are 😭
2024-02-17 17:34:17 +01:00
Hyblocker a8c48136c0 Make IPC errors work on non latin machines 2024-02-17 17:33:32 +01:00
Hyblocker dc9ada1a60 Remove missing file from project 2024-02-17 17:32:50 +01:00
Hyblocker 3acdbe5654 Bump version 2023-12-06 12:06:13 +01:00
Hyblocker de0d3425fa Merge pull request #1 from TCL987/limit-minimized-frame-rate
Limit the frame rate when minimized
2023-12-06 11:51:48 +01:00
ArcticFox8515 3b8ea60c8a Revert back to fast continuous calibration 2023-10-23 17:53:08 +03:00
ArcticFox8515 56531e0432 Optimize resource usage when running in background
Draw GUI only when window or overlay is visible.
Set minimal update time to 1/90 seconds,
so application won't "busy loop".
2023-10-22 18:00:47 +03:00
TCL987 1c18cd5445 Limit the frame rate when minimized
This avoids high CPU usage when minimized.
2023-10-10 23:42:13 -07:00
ArcticFox8515 e371b1d13f Use only yaw rotation for calibration
Pitch and roll offsets usually should be 0
because "up" vector in all VR tracking systems
is the same.
2023-09-23 19:29:18 +03:00
ArcticFox8515 cc4ffa774d Use slow preset; save relative transform
1. Use SLOW preset for continuous calibration. This should
   increase its accuracy with expense of increased CPU usage
   before fully calibrated.

2. Save/load relative transform, so new calibration doesn't have
  to start from scratch.
2023-08-20 19:10:25 +03:00
ArcticFox8515 9a0204466a Continuous calibration improvements
Use best effort calibration while nothing better is available,
this makes calibration converge within seconds.

Use relative pose for quick re-calibration until it provides
sensible quality result. This saves a lot of CPU load after
pose is calibrated.
2023-05-30 20:47:02 +03:00
19 changed files with 839 additions and 249 deletions

No files matched your search

+104 -10
View File
@@ -184,6 +184,13 @@ namespace {
return false;
}
// Apply tracker offsets
if (CalCtx.state == CalibrationState::Continuous || CalCtx.state == CalibrationState::ContinuousStandby) {
reference.vecPosition[0] += ctx.continuousCalibrationOffset.x();
reference.vecPosition[1] += ctx.continuousCalibrationOffset.y();
reference.vecPosition[2] += ctx.continuousCalibrationOffset.z();
}
calibration.PushSample(Sample(
ConvertPose(reference),
ConvertPose(target)
@@ -203,6 +210,18 @@ namespace {
CalCtx.targetID = state.FindDevice(CalCtx.targetStandby.trackingSystem, CalCtx.targetStandby.model, CalCtx.targetStandby.serial);
}
for (int i = 0; i < CalCtx.MAX_CONTROLLERS; i++) {
if (i < state.devices.size()
&& state.devices[i].trackingSystem == CalCtx.targetTrackingSystem
&& state.devices[i].deviceClass == vr::TrackedDeviceClass_Controller
&& (state.devices[i].controllerRole == vr::TrackedControllerRole_LeftHand || state.devices[i].controllerRole == vr::TrackedControllerRole_RightHand))
{
CalCtx.controllerIDs[i] = state.devices[i].id;
} else {
CalCtx.controllerIDs[i] = -1;
}
}
return CalCtx.referenceID >= 0 && CalCtx.targetID >= 0;
}
}
@@ -230,8 +249,7 @@ static_assert(vr::k_unTrackedDeviceIndex_Hmd == 0, "HMD index expected to be 0")
void ScanAndApplyProfile(CalibrationContext &ctx)
{
std::unique_ptr<char[]> buffer_array(new char
[vr::k_unMaxPropertyStringSize]);
std::unique_ptr<char[]> buffer_array(new char [vr::k_unMaxPropertyStringSize]);
char* buffer = buffer_array.get();
ctx.enabled = ctx.validProfile;
@@ -276,6 +294,22 @@ void ScanAndApplyProfile(CalibrationContext &ctx)
//auto p = ctx.devicePoses[id].mDeviceToAbsoluteTracking.m;
//printf("HMD %d: %f %f %f\n", id, p[0][3], p[1][3], p[2][3]);
// Check if the current HMD is a Pimax crystal
if (trackingSystem == "aapvr") {
// HMD is a Pimax HMD
vr::HmdMatrix34_t eyeToHeadLeft = vr::VRSystem()->GetEyeToHeadTransform(vr::Eye_Left);
// Crystal's projection matrix is constant 0s or 1s except for [0][3], which stores the IPD offset from the nose
bool isCrystalHmd =
eyeToHeadLeft.m[0][0] == 1 && eyeToHeadLeft.m[0][1] == 0 && eyeToHeadLeft.m[0][2] == 0 && // IPD
eyeToHeadLeft.m[1][0] == 0 && eyeToHeadLeft.m[1][1] == 1 && eyeToHeadLeft.m[1][2] == 0 && eyeToHeadLeft.m[1][3] == 0 &&
eyeToHeadLeft.m[2][0] == 0 && eyeToHeadLeft.m[2][1] == 0 && eyeToHeadLeft.m[2][2] == 1 && eyeToHeadLeft.m[2][3] == 0;
if (isCrystalHmd) {
// Move it outside the aapvr system ; we treat aapvr as if it were lighthouse
trackingSystem = "Pimax Crystal HMD";
}
}
if (trackingSystem != ctx.referenceTrackingSystem)
{
// Currently using an HMD with a different tracking system than the calibration.
@@ -286,6 +320,23 @@ void ScanAndApplyProfile(CalibrationContext &ctx)
continue;
}
// Detect Pimax crystal controllers and separate them too
if (deviceClass == vr::TrackedDeviceClass_Controller) {
if (trackingSystem == "oculus") {
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_RenderModelName_String, buffer, vr::k_unMaxPropertyStringSize, &err);
std::string renderModel(buffer);
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_ConnectedWirelessDongle_String, buffer, vr::k_unMaxPropertyStringSize, &err);
std::string connectedWirelessDongle(buffer);
// Check if the controller claims its an oculus controller but also pimax
if (renderModel.find("{aapvr}") != std::string::npos &&
renderModel.find("crystal") != std::string::npos &&
connectedWirelessDongle.find("lighthouse") != std::string::npos) {
trackingSystem = "Pimax Crystal Controllers";
}
}
}
if (trackingSystem != ctx.targetTrackingSystem)
{
ResetAndDisableOffsets(id);
@@ -320,8 +371,8 @@ void ScanAndApplyProfile(CalibrationContext &ctx)
}
}
void StartCalibration()
{
void StartCalibration() {
AssignTargets();
CalCtx.state = CalibrationState::Begin;
CalCtx.wantedUpdateInterval = 0.0;
CalCtx.messages.clear();
@@ -330,15 +381,23 @@ void StartCalibration()
}
void StartContinuousCalibration() {
CalCtx.calibrationSpeed = CalibrationContext::FAST;
AssignTargets();
StartCalibration();
CalCtx.state = CalibrationState::Continuous;
CalCtx.Log("Collecting initial samples...");
calibration.setRelativeTransformation(CalCtx.refToTargetPose, CalCtx.relativePosCalibrated);
calibration.lockRelativePosition = CalCtx.lockRelativePosition;
if (CalCtx.lockRelativePosition) {
CalCtx.Log("Relative position locked");
}
else {
CalCtx.Log("Collecting initial samples...");
}
Metrics::WriteLogAnnotation("StartContinuousCalibration");
}
void EndContinuousCalibration() {
CalCtx.state = CalibrationState::None;
CalCtx.relativePosCalibrated = false;
SaveProfile(CalCtx);
Metrics::WriteLogAnnotation("EndContinuousCalibration");
}
@@ -363,6 +422,16 @@ void CalibrationTick(double time)
}
});
// check for non-updating headset tracking space (caused by quest out of bounds or taken off head for example) and abort everything for this tick
auto p = ctx.devicePoses[vr::k_unTrackedDeviceIndex_Hmd].vecPosition;
if ((p[0] == 0.0 && p[1] == 0.0 && p[2] == 0.0) || (ctx.xprev == p[0] && ctx.yprev == p[1] && ctx.zprev == p[2])) {
// std::cerr << "HMD tracking didn't update, skipping update" << std::endl;
return;
}
ctx.xprev = p[0];
ctx.yprev = p[1];
ctx.zprev = p[2];
if (ctx.state == CalibrationState::None || ctx.state == CalibrationState::ContinuousStandby
|| (ctx.state == CalibrationState::Continuous && !calibration.isValid()))
{
@@ -459,6 +528,27 @@ void CalibrationTick(double time)
return;
}
if (CalCtx.state == CalibrationState::Continuous && CalCtx.requireTriggerPressToApply) {
bool triggerPressed = true;
vr::VRControllerState_t state;
for (int i = 0; i < CalCtx.MAX_CONTROLLERS; i++) {
if (CalCtx.controllerIDs[i] >= 0) {
vr::VRSystem()->GetControllerState(CalCtx.controllerIDs[i], &state, sizeof(state));
triggerPressed &= state.rAxis[vr::k_eControllerAxis_TrackPad /* matches trigger on Index controllers?? */].x > 0.75f
|| state.rAxis[vr::k_eControllerAxis_Trigger].x > 0.75f;
//printf("Controller %d tracpad: %f\n", i, state.rAxis[vr::k_eControllerAxis_TrackPad].x);
//printf("Controller %d trigger: %f\n", i, state.rAxis[vr::k_eControllerAxis_Trigger].x);
if (!triggerPressed) break;
}
}
if (!triggerPressed) {
ctx.wantedUpdateInterval = 0.5;
CalCtx.Log("Waiting for trigger press...\n");
return;
}
}
if (!CollectSample(ctx))
{
return;
@@ -478,6 +568,7 @@ void CalibrationTick(double time)
if (CalCtx.state == CalibrationState::Continuous) {
CalCtx.messages.clear();
calibration.enableStaticRecalibration = CalCtx.enableStaticRecalibration;
calibration.lockRelativePosition = CalCtx.lockRelativePosition;
ok = calibration.ComputeIncremental(lerp, CalCtx.continuousCalibrationThreshold);
}
else {
@@ -488,6 +579,8 @@ void CalibrationTick(double time)
if (calibration.isValid()) {
ctx.calibratedRotation = calibration.EulerRotation();
ctx.calibratedTranslation = calibration.Transformation().translation() * 100.0; // convert to cm units for profile storage
ctx.refToTargetPose = calibration.RelativeTransformation();
ctx.relativePosCalibrated = calibration.isRelativeTransformationCalibrated();
auto vrTrans = VRTranslationVec(ctx.calibratedTranslation);
auto vrRot = VRRotationQuat(Eigen::Quaterniond(calibration.Transformation().rotation()));
@@ -498,9 +591,7 @@ void CalibrationTick(double time)
ScanAndApplyProfile(ctx);
CalCtx.Log("Finished calibration, profile saved\n");
}
else
{
} else {
CalCtx.Log("Calibration failed.\n");
}
@@ -518,7 +609,10 @@ void CalibrationTick(double time)
calibration.Clear();
}
else {
for (int i = 0; i < 10; i++) calibration.ShiftSample();
size_t drop_samples = CalCtx.SampleCount() / 10;
for (int i = 0; i < drop_samples; i++) {
calibration.ShiftSample();
}
}
}
}
+16
View File
@@ -1,6 +1,7 @@
#pragma once
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <Windows.h>
#include <openvr.h>
#include <vector>
@@ -29,6 +30,9 @@ struct CalibrationContext
CalibrationState state = CalibrationState::None;
int32_t referenceID = -1, targetID = -1;
static const size_t MAX_CONTROLLERS = 8;
int32_t controllerIDs[MAX_CONTROLLERS];
StandbyDevice targetStandby, referenceStandby;
Eigen::Vector3d calibratedRotation;
@@ -42,13 +46,21 @@ struct CalibrationContext
bool validProfile = false;
bool clearOnLog = false;
bool quashTargetInContinuous = false;
bool requireTriggerPressToApply = false;
double timeLastTick = 0, timeLastScan = 0;
double wantedUpdateInterval = 1.0;
float xprev, yprev, zprev;
float continuousCalibrationThreshold;
Eigen::Vector3d continuousCalibrationOffset;
protocol::AlignmentSpeedParams alignmentSpeedParams;
bool enableStaticRecalibration;
bool lockRelativePosition = false;
Eigen::AffineCompact3d refToTargetPose = Eigen::AffineCompact3d::Identity();
bool relativePosCalibrated = false;
enum Speed
{
@@ -81,6 +93,8 @@ struct CalibrationContext
continuousCalibrationThreshold = 1.5f;
continuousCalibrationOffset = Eigen::Vector3d::Zero();
enableStaticRecalibration = true;
}
@@ -111,6 +125,8 @@ struct CalibrationContext
targetTrackingSystem = "";
enabled = false;
validProfile = false;
refToTargetPose = Eigen::AffineCompact3d::Identity();
relativePosCalibrated = true;
}
size_t SampleCount()
+128 -91
View File
@@ -116,6 +116,9 @@ void CalibrationCalc::Clear() {
m_estimatedTransformation.setIdentity();
m_isValid = false;
m_samples.clear();
m_axisVariance = 0.0;
m_refToTargetPose = Eigen::AffineCompact3d::Identity();
m_relativePosCalibrated = false;
}
Eigen::Vector3d CalibrationCalc::CalibrateRotation() const {
@@ -136,42 +139,43 @@ Eigen::Vector3d CalibrationCalc::CalibrateRotation() const {
// Kabsch algorithm
Eigen::MatrixXd refPoints(deltas.size(), 3), targetPoints(deltas.size(), 3);
Eigen::Vector3d refCentroid(0, 0, 0), targetCentroid(0, 0, 0);
// Initialize 2D points and centroids
Eigen::MatrixXd refPoints(deltas.size(), 2), targetPoints(deltas.size(), 2);
Eigen::Vector2d refCentroid(0, 0), targetCentroid(0, 0);
for (size_t i = 0; i < deltas.size(); i++)
{
refPoints.row(i) = deltas[i].ref;
refCentroid += deltas[i].ref;
// Fill matrices and calculate centroids
for (size_t i = 0; i < deltas.size(); i++) {
refPoints.row(i) << deltas[i].ref[0], deltas[i].ref[2]; // Take only the x and z components
refCentroid += refPoints.row(i);
targetPoints.row(i) = deltas[i].target;
targetCentroid += deltas[i].target;
}
targetPoints.row(i) << deltas[i].target[0], deltas[i].target[2]; // Take only the x and z components
targetCentroid += targetPoints.row(i);
}
refCentroid /= (double)deltas.size();
targetCentroid /= (double)deltas.size();
refCentroid /= (double)deltas.size();
targetCentroid /= (double)deltas.size();
for (size_t i = 0; i < deltas.size(); i++)
{
refPoints.row(i) -= refCentroid;
targetPoints.row(i) -= targetCentroid;
}
// Center the points
for (size_t i = 0; i < deltas.size(); i++) {
refPoints.row(i) -= refCentroid;
targetPoints.row(i) -= targetCentroid;
}
auto crossCV = refPoints.transpose() * targetPoints;
// Calculate cross-covariance matrix
auto crossCV = refPoints.transpose() * targetPoints;
Eigen::BDCSVD<Eigen::MatrixXd> bdcsvd;
auto svd = bdcsvd.compute(crossCV, Eigen::ComputeThinU | Eigen::ComputeThinV);
// Singular Value Decomposition (SVD)
Eigen::JacobiSVD<Eigen::MatrixXd> svd(crossCV, Eigen::ComputeThinU | Eigen::ComputeThinV);
Eigen::Matrix3d i = Eigen::Matrix3d::Identity();
if ((svd.matrixU() * svd.matrixV().transpose()).determinant() < 0)
{
i(2, 2) = -1;
}
// Calculate 2D rotation matrix
Eigen::Matrix2d i = Eigen::Matrix2d::Identity();
Eigen::Matrix2d rot = svd.matrixV() * i * svd.matrixU().transpose();
Eigen::Matrix3d rot = svd.matrixV() * i * svd.matrixU().transpose();
rot.transposeInPlace();
// Calculate yaw angle in radians
double yaw = std::atan2(rot(1, 0), rot(0, 0));
Eigen::Vector3d euler = rot.eulerAngles(2, 1, 0) * 180.0 / EIGEN_PI;
// Convert to degrees
Eigen::Vector3d euler(0.0, yaw * 180.0 / EIGEN_PI, 0.0);
//snprintf(buf, sizeof buf, "Calibrated rotation: yaw=%.2f pitch=%.2f roll=%.2f\n", euler[1], euler[2], euler[0]);
//CalCtx.Log(buf);
@@ -229,6 +233,10 @@ Eigen::Vector3d CalibrationCalc::CalibrateTranslation(const Eigen::Matrix3d &rot
return trans;
}
void CalibrationCalc::CalibrateScaleOffset(const Eigen::Matrix3d& rotation, Eigen::Vector3d* out_scaleOffset, float* out_scaleFactor) const {
// @TODO: figure out where the target and ref
}
namespace {
Pose ApplyTransform(const Pose& originalPose, const Eigen::AffineCompact3d& transform) {
@@ -477,8 +485,6 @@ Eigen::AffineCompact3d CalibrationCalc::EstimateRefToTargetPose(const Eigen::Aff
*/
bool CalibrationCalc::CalibrateByRelPose(Eigen::AffineCompact3d &out) const {
// R * S * T^-1 = C
if (!m_refToTargetPoseValid) return false;
out = PoseAverager::AverageFor(m_samples, [&](const auto& sample) {
return Eigen::AffineCompact3d(sample.ref.ToAffine() * m_refToTargetPose * sample.target.ToAffine().inverse());
});
@@ -494,7 +500,7 @@ bool CalibrationCalc::ComputeOneshot() {
bool valid = ValidateCalibration(calibration);
if (valid) {
m_estimatedTransformation = calibration;
m_estimatedTransformation = calibration; // @NOTE: Normal calibration
m_isValid = true;
return true;
}
@@ -518,48 +524,88 @@ void CalibrationCalc::ComputeInstantOffset() {
}
bool CalibrationCalc::ComputeIncremental(bool &lerp, double threshold) {
Metrics::RecordTimestamp();
auto calibration = ComputeCalibration();
if (lockRelativePosition) {
Eigen::AffineCompact3d byRelPose;
double relPoseError = INFINITY;
Eigen::Vector3d relPosOffset;
CalibrateByRelPose(byRelPose);
ValidateCalibration(byRelPose, &relPoseError, &relPosOffset);
Metrics::posOffset_byRelPose.Push(relPosOffset * 1000);
Metrics::error_byRelPose.Push(relPoseError * 1000);
bool usingRelPose = false;
bool valid = true;
auto variance = ComputeAxisVariance(calibration);
//std::ostringstream oss;
//oss << "Axis variance: " << variance(0) << " " << variance(1) << " " << variance(2) << " " << variance(3) << "\n";
//CalCtx.Log(oss.str());
//oss.clear();
m_axisVariance = variance(1);
if (m_axisVariance < AxisVarianceThreshold) {
//CalCtx.Log("Calibration points are nearly coplanar. Try moving around more?\n");
valid = false;
}
Metrics::axisIndependence.Push(m_axisVariance);
double newError = INFINITY, priorCalibrationError = INFINITY;
if (valid) {
valid = ValidateCalibration(calibration, &newError, &m_posOffset);
m_newCalRMS = newError;
Metrics::posOffset_rawComputed.Push(m_posOffset * 1000);
m_isValid = true;
m_estimatedTransformation = byRelPose;
return true;
}
Metrics::error_rawComputed.Push(newError * 1000);
// Use stricter thresholds for continuous calibration to limit jitter
valid = valid && newError < 0.005;
double priorCalibrationError = INFINITY;
Eigen::Vector3d priorPosOffset;
ValidateCalibration(m_estimatedTransformation, &priorCalibrationError, &priorPosOffset);
m_oldCalRMS = priorCalibrationError;
Metrics::posOffset_currentCal.Push(priorPosOffset * 1000);
Metrics::error_currentCal.Push(priorCalibrationError * 1000);
if (m_isValid) {
ValidateCalibration(m_estimatedTransformation, &priorCalibrationError, &priorPosOffset);
Metrics::posOffset_currentCal.Push(priorPosOffset * 1000);
Metrics::error_currentCal.Push(priorCalibrationError * 1000);
if (priorCalibrationError < 0.005) {
return false;
}
}
double newError = INFINITY;
bool newCalibrationValid = false;
Eigen::AffineCompact3d byRelPose;
Eigen::AffineCompact3d calibration;
bool usingRelPose = false;
double relPoseError = INFINITY;
if (enableStaticRecalibration && CalibrateByRelPose(byRelPose)) {
Eigen::Vector3d relPosOffset;
ValidateCalibration(byRelPose, &relPoseError, &relPosOffset);
Metrics::posOffset_byRelPose.Push(relPosOffset * 1000);
Metrics::error_byRelPose.Push(relPoseError * 1000);
if (relPoseError < 0.010 || m_relativePosCalibrated && relPoseError < 0.025) {
newCalibrationValid = true;
usingRelPose = true;
newError = relPoseError;
calibration = byRelPose;
if (relPoseError * threshold >= priorCalibrationError) {
return false;
}
}
}
double newVariance = 0;
if (!newCalibrationValid) {
calibration = ComputeCalibration();
newVariance = ComputeAxisVariance(calibration)(1);
Metrics::axisIndependence.Push(newVariance);
if (newVariance < AxisVarianceThreshold && newVariance < m_axisVariance) {
newCalibrationValid = false;
} else {
newCalibrationValid = ValidateCalibration(calibration, &newError, &m_posOffset);
Metrics::posOffset_rawComputed.Push(m_posOffset * 1000);
}
if (m_isValid) {
if (priorCalibrationError < newError * threshold) {
// If we have a more noisy calibration than before, avoid updating.
newCalibrationValid = false;
}
}
Metrics::error_rawComputed.Push(newError * 1000);
ComputeInstantOffset();
}
ComputeInstantOffset();
bool ok = valid;
bool oldCalibrationBetter = !valid || (m_isValid && priorCalibrationError < newError * threshold);
#if 0
char tmp[256];
@@ -568,47 +614,38 @@ bool CalibrationCalc::ComputeIncremental(bool &lerp, double threshold) {
CalCtx.Log(tmp);
#endif
// If we have a more noisy calibration than before, avoid updating.
if (oldCalibrationBetter) ok = false;
// Now, can we use the relative pose to perform a rapid correction?
Eigen::AffineCompact3d byRelPose;
bool relPoseAvailable = CalibrateByRelPose(byRelPose);
double relPoseError = INFINITY, existingPoseErrorUsingRelPosition = 0;
Eigen::Vector3d relPosOffset;
bool relPoseValid;
if (relPoseAvailable && enableStaticRecalibration) {
relPoseValid = ValidateCalibration(byRelPose, &relPoseError, &relPosOffset);
Metrics::posOffset_byRelPose.Push(relPosOffset * 1000);
Metrics::error_byRelPose.Push(relPoseError * 1000);
existingPoseErrorUsingRelPosition = RetargetingErrorRMS(m_refToTargetPose.translation(), m_estimatedTransformation);
Metrics::error_currentCalRelPose.Push(existingPoseErrorUsingRelPosition * 1000);
}
else {
relPoseValid = false;
}
if (!ok && relPoseValid && relPoseError * threshold < existingPoseErrorUsingRelPosition) {
usingRelPose = true;
newError = relPoseError;
calibration = byRelPose;
ok = true;
}
if (!newCalibrationValid) {
double existingPoseErrorUsingRelPosition = RetargetingErrorRMS(m_refToTargetPose.translation(), m_estimatedTransformation);
Metrics::error_currentCalRelPose.Push(existingPoseErrorUsingRelPosition * 1000);
if (relPoseError * threshold < existingPoseErrorUsingRelPosition || newCalibrationValid && relPoseError < newError) {
newCalibrationValid = true;
usingRelPose = true;
newError = relPoseError;
calibration = byRelPose;
}
}
if (ok) {
if (newCalibrationValid) {
lerp = m_isValid;
m_relativePosCalibrated = m_relativePosCalibrated || newError < 0.005;
if (!m_isValid) {
CalCtx.Log("Applying initial transformation...");
}
else {
else if (m_relativePosCalibrated) {
CalCtx.Log("Applying updated transformation...");
} else {
CalCtx.Log("Applying temporary transformation...");
}
m_isValid = true;
m_estimatedTransformation = calibration;
m_estimatedTransformation = calibration; // @NOTE: Continuous calibration
m_axisVariance = newVariance;
if (!usingRelPose) {
m_refToTargetPose = EstimateRefToTargetPose(m_estimatedTransformation);
m_refToTargetPoseValid = true;
}
Metrics::calibrationApplied.Push(!usingRelPose);
+21 -3
View File
@@ -59,6 +59,7 @@ public:
static const double AxisVarianceThreshold;
bool enableStaticRecalibration;
bool lockRelativePosition = false;
const Eigen::AffineCompact3d Transformation() const
{
@@ -73,6 +74,22 @@ public:
bool isValid() const {
return m_isValid;
}
const Eigen::AffineCompact3d RelativeTransformation() const
{
return m_refToTargetPose;
}
bool isRelativeTransformationCalibrated() const
{
return m_relativePosCalibrated;
}
void setRelativeTransformation(const Eigen::AffineCompact3d transform, bool calibrated)
{
m_refToTargetPose = transform;
m_relativePosCalibrated = calibrated;
}
void PushSample(const Sample& sample);
void Clear();
@@ -92,24 +109,25 @@ public:
// Debug fields
Eigen::Vector3d m_posOffset;
double m_newCalRMS, m_oldCalRMS, m_axisVariance;
double m_axisVariance = 0.0;
long m_calcCycle;
private:
bool m_isValid;
Eigen::AffineCompact3d m_estimatedTransformation;
bool m_relativePosCalibrated = false;
/*
* This affine transform estimates the pose of the target within the reference device's local pose space.
* That is to say, it's given by transforming the target world pose by the inverse reference pose.
*/
Eigen::AffineCompact3d m_refToTargetPose;
bool m_refToTargetPoseValid;
Eigen::AffineCompact3d m_refToTargetPose = Eigen::AffineCompact3d::Identity();
std::deque<Sample> m_samples;
Eigen::Vector3d CalibrateRotation() const;
Eigen::Vector3d CalibrateTranslation(const Eigen::Matrix3d &rotation) const;
void CalibrateScaleOffset(const Eigen::Matrix3d &rotation, Eigen::Vector3d* out_scaleOffset, float* out_scaleFactor) const;
Eigen::AffineCompact3d ComputeCalibration() const;
@@ -376,8 +376,4 @@ void ShowCalibrationDebug(int rows, int cols) {
if (!wasHovered) {
lastMouseX = -INFINITY;
}
if (lastMouseX != initMouseX) {
RequestImmediateRedraw();
}
}
+54
View File
@@ -116,6 +116,10 @@ static void ParseProfile(CalibrationContext &ctx, std::istream &stream)
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<double>();
ctx.continuousCalibrationOffset(1) = obj["continuous_calibration_target_offset_y"].get<double>();
ctx.continuousCalibrationOffset(2) = obj["continuous_calibration_target_offset_z"].get<double>();
if (obj["scale"].is<double>())
ctx.calibratedScale = obj["scale"].get<double>();
@@ -151,6 +155,34 @@ static void ParseProfile(CalibrationContext &ctx, std::istream &stream)
ctx.chaperone.valid = true;
}
}
if (obj["relative_pos_calibrated"].is<bool>()) {
ctx.relativePosCalibrated = obj["relative_pos_calibrated"].get<bool>();
}
if (obj["lock_relative_position"].is<bool>()) {
ctx.lockRelativePosition = obj["lock_relative_position"].get<bool>();
}
if (obj["relative_transform"].is<picojson::object>()) {
auto relTransform = obj["relative_transform"].get<picojson::object>();
Eigen::Vector3d refToTragetRoation;
Eigen::Vector3d refToTargetTranslation;
refToTragetRoation(0) = relTransform["roll"].get<double>();
refToTragetRoation(1) = relTransform["yaw"].get<double>();
refToTragetRoation(2) = relTransform["pitch"].get<double>();
refToTargetTranslation(0) = relTransform["x"].get<double>();
refToTargetTranslation(1) = relTransform["y"].get<double>();
refToTargetTranslation(2) = relTransform["z"].get<double>();
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;
}
@@ -189,6 +221,10 @@ static void WriteProfile(CalibrationContext &ctx, std::ostream &out)
bool isInContinuousCalibrationMode = ctx.state == CalibrationState::Continuous || ctx.state == CalibrationState::ContinuousStandby;
profile["autostart_continuous_calibration"].set<bool>(isInContinuousCalibrationMode);
profile["quash_target_in_continuous"].set<bool>(ctx.quashTargetInContinuous);
profile["require_trigger_press_to_apply"].set<bool>(ctx.requireTriggerPressToApply);
profile["continuous_calibration_target_offset_x"].set<double>(ctx.continuousCalibrationOffset(0));
profile["continuous_calibration_target_offset_y"].set<double>(ctx.continuousCalibrationOffset(1));
profile["continuous_calibration_target_offset_z"].set<double>(ctx.continuousCalibrationOffset(2));
double speed = (int) ctx.calibrationSpeed;
profile["calibration_speed"].set<double>(speed);
@@ -212,6 +248,19 @@ static void WriteProfile(CalibrationContext &ctx, std::ostream &out)
profile["chaperone"].set<picojson::object>(chaperone);
}
Eigen::Vector3d refToTragetRoation = ctx.refToTargetPose.rotation().eulerAngles(0, 1, 2);
Eigen::Vector3d refToTargetTranslation = ctx.refToTargetPose.translation();
picojson::object refToTarget;
refToTarget["x"].set<double>(refToTargetTranslation(0));
refToTarget["y"].set<double>(refToTargetTranslation(1));
refToTarget["z"].set<double>(refToTargetTranslation(2));
refToTarget["roll"].set<double>(refToTragetRoation(0));
refToTarget["yaw"].set<double>(refToTragetRoation(1));
refToTarget["pitch"].set<double>(refToTragetRoation(2));
profile["relative_pos_calibrated"].set<bool>(ctx.relativePosCalibrated);
profile["lock_relative_position"].set<bool>(ctx.lockRelativePosition);
profile["relative_transform"].set<picojson::object>(refToTarget);
picojson::value profileV;
profileV.set<picojson::object>(profile);
@@ -278,6 +327,11 @@ static void WriteRegistryKey(std::string str)
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();
+19 -10
View File
@@ -3,17 +3,24 @@
#include <string>
std::string WStringToString(const std::wstring& wstr)
{
int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
std::string str_to(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &str_to[0], size_needed, NULL, NULL);
return str_to;
}
static std::string LastErrorString(DWORD lastError)
{
LPSTR buffer = nullptr;
size_t size = FormatMessageA(
LPWSTR buffer = nullptr;
size_t size = FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, lastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, NULL
NULL, lastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buffer, 0, NULL
);
std::string message(buffer, size);
std::wstring message(buffer, size);
LocalFree(buffer);
return message;
return WStringToString(message);
}
IPCClient::~IPCClient()
@@ -37,7 +44,8 @@ void IPCClient::Connect()
DWORD mode = PIPE_READMODE_MESSAGE;
if (!SetNamedPipeHandleState(pipe, &mode, 0, 0))
{
throw std::runtime_error("Couldn't set pipe mode. Error: " + LastErrorString(GetLastError()));
DWORD lastError = GetLastError();
throw std::runtime_error("Couldn't set pipe mode. Error " + std::to_string(lastError) + ": " + LastErrorString(lastError));
}
auto response = SendBlocking(protocol::Request(protocol::RequestHandshake));
@@ -65,7 +73,8 @@ void IPCClient::Send(const protocol::Request &request)
BOOL success = WriteFile(pipe, &request, sizeof request, &bytesWritten, 0);
if (!success)
{
throw std::runtime_error("Error writing IPC request. Error: " + LastErrorString(GetLastError()));
DWORD lastError = GetLastError();
throw std::runtime_error("Error writing IPC request. Error " + std::to_string(lastError) + ": " + LastErrorString(lastError));
}
}
@@ -80,13 +89,13 @@ protocol::Response IPCClient::Receive()
DWORD lastError = GetLastError();
if (lastError != ERROR_MORE_DATA)
{
throw std::runtime_error("Error reading IPC response. Error: " + LastErrorString(lastError));
throw std::runtime_error("Error reading IPC response. Error " + std::to_string(lastError) + ": " + LastErrorString(lastError));
}
}
if (bytesRead != sizeof response)
{
throw std::runtime_error("Invalid IPC response with size " + std::to_string(bytesRead));
throw std::runtime_error("Invalid IPC response. Error SIZE_MISMATCH, got size " + std::to_string(bytesRead));
}
return response;
@@ -13,6 +13,9 @@
#include <GLFW/glfw3.h>
#include <openvr.h>
#include <direct.h>
#include <chrono>
#include <thread>
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
@@ -57,6 +60,7 @@ static GLuint fboHandle = 0, fboTextureHandle = 0;
static int fboTextureWidth = 0, fboTextureHeight = 0;
static char cwd[MAX_PATH];
const float MINIMIZED_MAX_FPS = 60.0f;
void CreateGLFWWindow()
{
@@ -212,6 +216,7 @@ void RequestImmediateRedraw() {
immediateRedraw = true;
}
double lastFrameStartTime = glfwGetTime();
void RunLoop()
{
while (!glfwWindowShouldClose(glfwWindow))
@@ -224,6 +229,7 @@ void RunLoop()
bool dashboardVisible = false;
int width, height;
glfwGetFramebufferSize(glfwWindow, &width, &height);
const bool windowVisible = (width > 0 && height > 0);
if (overlayMainHandle && vr::VROverlay())
{
@@ -254,17 +260,19 @@ void RunLoop()
int id = ImGui::GetActiveID();
auto textInfo = ImGui::GetInputTextState(id);
textBuf[0] = 0;
int len = WideCharToMultiByte(CP_ACP, 0, (LPCWCH)textInfo->TextW.Data, textInfo->TextW.Size, textBuf, sizeof(textBuf), NULL, NULL);
textBuf[min(len, sizeof(textBuf) - 1)] = 0;
uint32_t unFlags = 0; // EKeyboardFlags
if (textInfo != nullptr) {
textBuf[0] = 0;
int len = WideCharToMultiByte(CP_ACP, 0, (LPCWCH)textInfo->TextW.Data, textInfo->TextW.Size, textBuf, sizeof(textBuf), NULL, NULL);
textBuf[min(len, sizeof(textBuf) - 1)] = 0;
vr::VROverlay()->ShowKeyboardForOverlay(
overlayMainHandle, vr::k_EGamepadTextInputModeNormal, vr::k_EGamepadTextInputLineModeSingleLine,
unFlags, "Space Calibrator Overlay", sizeof textBuf, textBuf, 0
);
keyboardOpen = true;
uint32_t unFlags = 0; // EKeyboardFlags
vr::VROverlay()->ShowKeyboardForOverlay(
overlayMainHandle, vr::k_EGamepadTextInputModeNormal, vr::k_EGamepadTextInputLineModeSingleLine,
unFlags, "Space Calibrator Overlay", sizeof textBuf, textBuf, 0
);
keyboardOpen = true;
}
}
vr::VREvent_t vrEvent;
@@ -306,59 +314,62 @@ void RunLoop()
}
}
}
auto &io = ImGui::GetIO();
io.DisplaySize = ImVec2((float) fboTextureWidth, (float) fboTextureHeight);
io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
io.ConfigFlags = io.ConfigFlags & ~ImGuiConfigFlags_NoMouseCursorChange;
if (dashboardVisible) {
io.ConfigFlags = io.ConfigFlags | ImGuiConfigFlags_NoMouseCursorChange;
}
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
BuildMainWindow(dashboardVisible);
ImGui::Render();
glBindFramebuffer(GL_FRAMEBUFFER, fboHandle);
glViewport(0, 0, fboTextureWidth, fboTextureHeight);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (width && height)
if (windowVisible || dashboardVisible)
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, fboHandle);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glfwSwapBuffers(glfwWindow);
}
auto &io = ImGui::GetIO();
io.DisplaySize = ImVec2((float) fboTextureWidth, (float) fboTextureHeight);
io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
if (dashboardVisible)
{
vr::Texture_t vrTex;
vrTex.eType = vr::TextureType_OpenGL;
vrTex.eColorSpace = vr::ColorSpace_Auto;
io.ConfigFlags = io.ConfigFlags & ~ImGuiConfigFlags_NoMouseCursorChange;
if (dashboardVisible) {
io.ConfigFlags = io.ConfigFlags | ImGuiConfigFlags_NoMouseCursorChange;
}
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
vrTex.handle = (void *)
BuildMainWindow(dashboardVisible);
ImGui::Render();
glBindFramebuffer(GL_FRAMEBUFFER, fboHandle);
glViewport(0, 0, fboTextureWidth, fboTextureHeight);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (width && height)
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, fboHandle);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glfwSwapBuffers(glfwWindow);
}
if (dashboardVisible)
{
vr::Texture_t vrTex;
vrTex.eType = vr::TextureType_OpenGL;
vrTex.eColorSpace = vr::ColorSpace_Auto;
vrTex.handle = (void *)
#if defined _WIN64 || defined _LP64
(uint64_t)
(uint64_t)
#endif
fboTextureHandle;
fboTextureHandle;
vr::HmdVector2_t mouseScale = { (float) fboTextureWidth, (float) fboTextureHeight };
vr::HmdVector2_t mouseScale = { (float) fboTextureWidth, (float) fboTextureHeight };
vr::VROverlay()->SetOverlayTexture(overlayMainHandle, &vrTex);
vr::VROverlay()->SetOverlayMouseScale(overlayMainHandle, &mouseScale);
vr::VROverlay()->SetOverlayTexture(overlayMainHandle, &vrTex);
vr::VROverlay()->SetOverlayMouseScale(overlayMainHandle, &mouseScale);
}
}
const double dashboardInterval = 1.0 / 90.0; // fps
double waitEventsTimeout = CalCtx.wantedUpdateInterval;
double waitEventsTimeout = max(CalCtx.wantedUpdateInterval, dashboardInterval);
if (dashboardVisible && waitEventsTimeout > dashboardInterval)
waitEventsTimeout = dashboardInterval;
@@ -369,6 +380,19 @@ void RunLoop()
}
glfwWaitEventsTimeout(waitEventsTimeout);
// If we're minimized rendering won't limit our frame rate so we need to do it ourselves.
if (glfwGetWindowAttrib(glfwWindow, GLFW_ICONIFIED))
{
double targetFrameTime = 1 / MINIMIZED_MAX_FPS;
double waitTime = targetFrameTime - (glfwGetTime() - lastFrameStartTime);
if (waitTime > 0)
{
std::this_thread::sleep_for(std::chrono::duration<double>(waitTime));
}
lastFrameStartTime += targetFrameTime;
}
}
}
@@ -93,8 +93,8 @@
<ClInclude Include="CalibrationCalc.h" />
<ClInclude Include="CalibrationMetrics.h" />
<ClInclude Include="Configuration.h" />
<ClInclude Include="ContinuousCalibration.h" />
<ClInclude Include="EmbeddedFiles.h" />
<ClInclude Include="imgui_extensions.h" />
<ClInclude Include="IPCClient.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
@@ -131,6 +131,7 @@
<ClCompile Include="EmbeddedFiles.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="imgui_extensions.cpp" />
<ClCompile Include="IPCClient.cpp" />
<ClCompile Include="OpenVR-SpaceCalibrator.cpp" />
<ClCompile Include="stdafx.cpp">
@@ -57,15 +57,15 @@
<ClInclude Include="..\lib\implot\implot_internal.h">
<Filter>Source Files\ImGui\ImPlot</Filter>
</ClInclude>
<ClInclude Include="ContinuousCalibration.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="VRState.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CalibrationMetrics.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="imgui_extensions.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
@@ -134,6 +134,9 @@
<ClCompile Include="CalibrationMetrics.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="imgui_extensions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Image Include="small.ico">
+198 -61
View File
@@ -11,6 +11,7 @@
#include <vector>
#include <algorithm>
#include <imgui/imgui.h>
#include "imgui_extensions.h"
void TextWithWidth(const char *label, const char *text, float width);
@@ -76,7 +77,7 @@ void ShowVersionLine() {
ImGui::EndChild();
return;
}
ImGui::Text("OpenVR Space Calibrator v" SPACECAL_VERSION_STRING " - by tach/pushrax/bd_");
ImGui::Text("OpenVR Space Calibrator v" SPACECAL_VERSION_STRING);
if (runningInOverlay)
{
ImGui::SameLine();
@@ -86,7 +87,7 @@ void ShowVersionLine() {
}
void CCal_BasicInfo();
void CCal_AlignParams();
void CCal_DrawSettings();
void BuildContinuousCalDisplay() {
ImGui::SetNextWindowPos(ImVec2(0, 0));
@@ -119,8 +120,8 @@ void BuildContinuousCalDisplay() {
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Alignment speeds")) {
CCal_AlignParams();
if (ImGui::BeginTabItem("Settings")) {
CCal_DrawSettings();
ImGui::EndTabItem();
}
@@ -136,74 +137,206 @@ void BuildContinuousCalDisplay() {
static void ScaledDragFloat(const char* label, double& f, double scale, double min, double max, int flags = ImGuiSliderFlags_AlwaysClamp) {
float v = (float) (f * scale);
ImGui::SliderFloat(label, &v, (float)min, (float)max, "%1.2f", flags);
std::string labelStr = std::string(label);
// If starts with ##, just do a normal SliderFloat
if (labelStr.size() > 2 && labelStr[0] == '#' && labelStr[1] == '#') {
ImGui::SliderFloat(label, &v, (float)min, (float)max, "%1.2f", flags);
} else {
// Otherwise do funny
ImGui::Text(label);
ImGui::SameLine();
ImGui::PushID((std::string(label) + "_id").c_str());
// Line up to a column, multiples of 100
constexpr uint32_t LABEL_CURSOR = 100;
uint32_t cursorPosX = ImGui::GetCursorPosX();
uint32_t roundedPosition = ((cursorPosX + LABEL_CURSOR / 2) / LABEL_CURSOR) * LABEL_CURSOR;
ImGui::SetCursorPosX(roundedPosition);
ImGui::SliderFloat((std::string("##") + label).c_str(), &v, (float)min, (float)max, "%1.2f", flags);
ImGui::PopID();
}
f = v / scale;
}
void CCal_AlignParams() {
void DrawVectorElement(std::string id, const char* text, double* value);
void CCal_DrawSettings() {
// panel size for boxes
ImVec2 panel_size { ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x, 0 };
ImGui::BeginGroupPanel("Tip", panel_size);
ImGui::Text("Hover over settings to learn more about them!");
ImGui::EndGroupPanel();
// @TODO: Group in UI
// Section: Alignment speeds
{
ImGui::BeginGroupPanel("Calibration speeds", panel_size);
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
ImGui::TextWrapped(
"SpaceCalibrator uses up to three different speeds at which it drags the calibration back into "
"position when drift occurs. These settings control how far off the calibration should be before going back to low speed (for "
"Decel) or going to higher speeds (for Slow and Fast)."
);
ImGui::PopStyleColor();
if (ImGui::BeginTable("SpeedThresholds", 3, 0)) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(1);
ImGui::Text("Translation (mm)");
ImGui::TableSetColumnIndex(2);
ImGui::Text("Rotation (degrees)");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Decel");
ImGui::TableSetColumnIndex(1);
ScaledDragFloat("##TransDecel", CalCtx.alignmentSpeedParams.thr_trans_tiny, 1000.0, 0, 20.0);
ImGui::TableSetColumnIndex(2);
ScaledDragFloat("##RotDecel", CalCtx.alignmentSpeedParams.thr_rot_tiny, 180.0 / EIGEN_PI, 0, 5.0);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Slow");
ImGui::TableSetColumnIndex(1);
ScaledDragFloat("##TransSlow", CalCtx.alignmentSpeedParams.thr_trans_small, 1000.0,
CalCtx.alignmentSpeedParams.thr_trans_tiny * 1000.0, 20.0);
ImGui::TableSetColumnIndex(2);
ScaledDragFloat("##RotSlow", CalCtx.alignmentSpeedParams.thr_rot_small, 180.0 / EIGEN_PI,
CalCtx.alignmentSpeedParams.thr_rot_tiny * (180.0 / EIGEN_PI), 10.0);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Fast");
ImGui::TableSetColumnIndex(1);
ScaledDragFloat("##TransFast", CalCtx.alignmentSpeedParams.thr_trans_large, 1000.0,
CalCtx.alignmentSpeedParams.thr_trans_small * 1000.0, 50.0);
ImGui::TableSetColumnIndex(2);
ScaledDragFloat("##RotFast", CalCtx.alignmentSpeedParams.thr_rot_large, 180.0 / EIGEN_PI,
CalCtx.alignmentSpeedParams.thr_rot_small * (180.0 / EIGEN_PI), 20.0);
ImGui::EndTable();
}
ImGui::EndGroupPanel();
}
// Section: Alignment speeds
{
ImGui::BeginGroupPanel("Alignment speeds", panel_size);
// ImGui::Separator();
// ImGui::Text("Alignment speeds");
ScaledDragFloat("Decel", CalCtx.alignmentSpeedParams.align_speed_tiny, 1.0, 0, 2.0, 0);
ScaledDragFloat("Slow", CalCtx.alignmentSpeedParams.align_speed_small, 1.0, 0, 2.0, 0);
ScaledDragFloat("Fast", CalCtx.alignmentSpeedParams.align_speed_large, 1.0, 0, 2.0, 0);
ImGui::EndGroupPanel();
}
// Section: Continuous Calibration settings
{
ImGui::BeginGroupPanel("Continuous calibration", panel_size);
{
// @TODO: Reduce code duplication (tooltips)
// Recalibration threshold
ImGui::Text("Recalibration threshold");
ImGui::SameLine();
ImGui::PushID("recalibration_threshold");
ImGui::SliderFloat("##recalibration_threshold_slider", &CalCtx.continuousCalibrationThreshold, 1.01f, 10.0f, "%1.1f", 0);
if (ImGui::IsItemHovered(0)) {
ImGui::SetTooltip("Controls how good the calibration must be before realigning the trackers.\n"
"Higher values cause calibration to happen less often, and may be useful for system with lots of tracking drift.");
}
ImGui::PopID();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
ImGui::TextWrapped("Controls how often SpaceCalibrator synchronises playspaces.");
ImGui::PopStyleColor();
if (ImGui::IsItemHovered(0)) {
ImGui::SetTooltip("Controls how good the calibration must be before realigning the trackers.\n"
"Higher values cause calibration to happen less often, and may be useful for system with lots of tracking drift.");
}
}
{
// Tracker offset
// ImVec2 panel_size_inner { ImGui::GetCurrentWindow()->DC.ItemWidth, 0};
ImVec2 panel_size_inner { panel_size.x - 11 * 2, 0};
ImGui::BeginGroupPanel("Tracker offset", panel_size_inner);
DrawVectorElement("cc_tracker_offset", "X", &CalCtx.continuousCalibrationOffset.x());
DrawVectorElement("cc_tracker_offset", "Y", &CalCtx.continuousCalibrationOffset.y());
DrawVectorElement("cc_tracker_offset", "Z", &CalCtx.continuousCalibrationOffset.z());
ImGui::EndGroupPanel();
}
{
// Playspace offset
ImVec2 panel_size_inner{ panel_size.x - 11 * 2, 0 };
ImGui::BeginGroupPanel("Playspace scale", panel_size_inner);
DrawVectorElement("cc_playspace_scale", "PLayspace Scale", &CalCtx.calibratedScale);
ImGui::EndGroupPanel();
}
ImGui::EndGroupPanel();
}
ImGui::NewLine();
ImGui::Indent();
if (ImGui::Button("Reset settings")) {
CalCtx.ResetConfig();
}
ImGui::Unindent();
ImGui::NewLine();
ImGui::SliderFloat("Recalibration threshold", &CalCtx.continuousCalibrationThreshold, 1.01f, 10.0f, "%1.1f", 0);
if (ImGui::IsItemHovered(0)) {
ImGui::SetTooltip("Controls how confident SpaceCalibrator must be in the new calibration before updating the calibration.\n"
"Higher values calibrate less frequently, and may be useful on systems with lots of tracker drift."
);
// Section: Contributors credits
{
ImGui::BeginGroupPanel("Credits", panel_size);
ImGui::TextDisabled("tach");
ImGui::TextDisabled("pushrax");
ImGui::TextDisabled("bd_");
ImGui::TextDisabled("ArcticFox");
ImGui::TextDisabled("hekky");
ImGui::EndGroupPanel();
}
}
ImGui::Separator();
ImGui::TextWrapped(
"Calibration speeds: SpaceCalibrator uses up to three different speeds at which it drags the calibration back into "
"position when drift occurs. These settings control how far off the calibration should be before going back to low speed (for "
"Decel) or going to higher speeds (for Slow and Fast)."
);
if (ImGui::BeginTable("SpeedThresholds", 3, 0)) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(1);
ImGui::Text("Translation (mm)");
ImGui::TableSetColumnIndex(2);
ImGui::Text("Rotation (degrees)");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Decel");
ImGui::TableSetColumnIndex(1);
ScaledDragFloat("##TransDecel", CalCtx.alignmentSpeedParams.thr_trans_tiny, 1000.0, 0, 20.0);
ImGui::TableSetColumnIndex(2);
ScaledDragFloat("##RotDecel", CalCtx.alignmentSpeedParams.thr_rot_tiny, 180.0 / EIGEN_PI, 0, 5.0);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Slow");
ImGui::TableSetColumnIndex(1);
ScaledDragFloat("##TransSlow", CalCtx.alignmentSpeedParams.thr_trans_small, 1000.0,
CalCtx.alignmentSpeedParams.thr_trans_tiny * 1000.0, 20.0);
ImGui::TableSetColumnIndex(2);
ScaledDragFloat("##RotSlow", CalCtx.alignmentSpeedParams.thr_rot_small, 180.0 / EIGEN_PI,
CalCtx.alignmentSpeedParams.thr_rot_tiny * (180.0 / EIGEN_PI), 10.0);
void DrawVectorElement(std::string id, const char* text, double* value) {
constexpr float CONTINUOUS_CALIBRATION_TRACKER_OFFSET_DELTA = 0.01f;
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("Fast");
ImGui::TableSetColumnIndex(1);
ScaledDragFloat("##TransFast", CalCtx.alignmentSpeedParams.thr_trans_large, 1000.0,
CalCtx.alignmentSpeedParams.thr_trans_small * 1000.0, 50.0);
ImGui::TableSetColumnIndex(2);
ScaledDragFloat("##RotFast", CalCtx.alignmentSpeedParams.thr_rot_large, 180.0 / EIGEN_PI,
CalCtx.alignmentSpeedParams.thr_rot_small * (180.0 / EIGEN_PI), 20.0);
ImGui::Text(text);
ImGui::EndTable();
ImGui::SameLine();
ImGui::PushID((id + text + "_btn_reset").c_str());
if (ImGui::Button(" 0 ")) {
*value *= 0;
}
ImGui::PopID();
ImGui::SameLine();
if (ImGui::ArrowButton((id + text + "_decrease").c_str(), ImGuiDir_Down)) {
*value -= CONTINUOUS_CALIBRATION_TRACKER_OFFSET_DELTA;
}
ImGui::SameLine();
ImGui::PushItemWidth(100);
ImGui::PushID((id + text + "_text_field").c_str());
ImGui::InputDouble("##label", value, 0, 0, "%.2f");
ImGui::PopID();
ImGui::PopItemWidth();
ImGui::SameLine();
if (ImGui::ArrowButton((id + text + "_increase").c_str(), ImGuiDir_Up)) {
*value += CONTINUOUS_CALIBRATION_TRACKER_OFFSET_DELTA;
}
ImGui::Separator();
ImGui::Text("Alignment speeds");
ScaledDragFloat("Decel", CalCtx.alignmentSpeedParams.align_speed_tiny, 1.0, 0, 2.0, 0);
ScaledDragFloat("Slow", CalCtx.alignmentSpeedParams.align_speed_small, 1.0, 0, 2.0, 0);
ScaledDragFloat("Fast", CalCtx.alignmentSpeedParams.align_speed_large, 1.0, 0, 2.0, 0);
}
void CCal_BasicInfo() {
@@ -283,11 +416,15 @@ void CCal_BasicInfo() {
ImGui::EndTable();
}
ImGui::Checkbox("Hide target device from application", &CalCtx.quashTargetInContinuous);
ImGui::Checkbox("Hide tracker", &CalCtx.quashTargetInContinuous);
ImGui::SameLine();
ImGui::Checkbox("Enable static recalibration", &CalCtx.enableStaticRecalibration);
ImGui::Checkbox("Static recalibration", &CalCtx.enableStaticRecalibration);
ImGui::SameLine();
ImGui::Checkbox("Enable debug logs", &Metrics::enableLogs);
ImGui::SameLine();
ImGui::Checkbox("Lock relative transform", &CalCtx.lockRelativePosition);
ImGui::SameLine();
ImGui::Checkbox("Require triggers", &CalCtx.requireTriggerPressToApply);
// Status field...
+36
View File
@@ -22,6 +22,35 @@ VRState VRState::Load()
if (err == vr::TrackedProp_Success)
{
std::string system(buffer);
// Check if the current HMD is a Pimax crystal
if (deviceClass == vr::TrackedDeviceClass_HMD && system == "aapvr") {
// HMD is a Pimax HMD
vr::HmdMatrix34_t eyeToHeadLeft = vr::VRSystem()->GetEyeToHeadTransform(vr::Eye_Left);
// Crystal's projection matrix is constant 0s or 1s except for [0][3], which stores the IPD offset from the nose
bool isCrystalHmd =
eyeToHeadLeft.m[0][0] == 1 && eyeToHeadLeft.m[0][1] == 0 && eyeToHeadLeft.m[0][2] == 0 && // IPD
eyeToHeadLeft.m[1][0] == 0 && eyeToHeadLeft.m[1][1] == 1 && eyeToHeadLeft.m[1][2] == 0 && eyeToHeadLeft.m[1][3] == 0 &&
eyeToHeadLeft.m[2][0] == 0 && eyeToHeadLeft.m[2][1] == 0 && eyeToHeadLeft.m[2][2] == 1 && eyeToHeadLeft.m[2][3] == 0;
if (isCrystalHmd) {
// Move it outside the aapvr system ; we treat aapvr as if it were lighthouse
system = "Pimax Crystal HMD";
}
} else if (deviceClass == vr::TrackedDeviceClass_Controller && system == "oculus") {
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_RenderModelName_String, buffer, vr::k_unMaxPropertyStringSize, &err);
std::string renderModel(buffer);
vr::VRSystem()->GetStringTrackedDeviceProperty(id, vr::Prop_ConnectedWirelessDongle_String, buffer, vr::k_unMaxPropertyStringSize, &err);
std::string connectedWirelessDongle(buffer);
// Check if the controller claims its an oculus controller but also pimax
if (renderModel.find("{aapvr}") != std::string::npos &&
renderModel.find("crystal") != std::string::npos &&
connectedWirelessDongle.find("lighthouse") != std::string::npos) {
system = "Pimax Crystal Controllers";
}
}
auto existing = std::find(trackingSystems.begin(), trackingSystems.end(), system);
if (existing != trackingSystems.end())
{
@@ -68,5 +97,12 @@ int VRState::FindDevice(const std::string& trackingSystem, const std::string& mo
if (device.trackingSystem == trackingSystem && device.model == model && device.serial == serial) return device.id;
}
// allow fallback excluding serial for HMD specifically
for (int i = 0; i < devices.size(); i++) {
const auto& device = devices[i];
if (device.trackingSystem == trackingSystem && device.model == model && device.deviceClass == vr::TrackedDeviceClass::TrackedDeviceClass_HMD) return device.id;
}
return -1;
}
+128
View File
@@ -0,0 +1,128 @@
#include "imgui_extensions.h"
#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui_internal.h"
static ImVector<ImRect> s_GroupPanelLabelStack;
void ImGui::BeginGroupPanel(const char* name, const ImVec2& size)
{
ImGui::BeginGroup();
auto cursorPos = ImGui::GetCursorScreenPos();
auto itemSpacing = ImGui::GetStyle().ItemSpacing;
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
auto frameHeight = ImGui::GetFrameHeight();
ImGui::BeginGroup();
ImVec2 effectiveSize = size;
if (size.x < 0.0f)
effectiveSize.x = ImGui::GetContentRegionAvail().x;
else
effectiveSize.x = size.x;
ImGui::Dummy(ImVec2(effectiveSize.x, 0.0f));
ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
ImGui::SameLine(0.0f, 0.0f);
ImGui::BeginGroup();
ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
ImGui::SameLine(0.0f, 0.0f);
ImGui::TextUnformatted(name);
auto labelMin = ImGui::GetItemRectMin();
auto labelMax = ImGui::GetItemRectMax();
ImGui::SameLine(0.0f, 0.0f);
ImGui::Dummy(ImVec2(0.0, frameHeight + itemSpacing.y));
ImGui::BeginGroup();
//ImGui::GetWindowDrawList()->AddRect(labelMin, labelMax, IM_COL32(255, 0, 255, 255));
ImGui::PopStyleVar(2);
#if IMGUI_VERSION_NUM >= 17301
ImGui::GetCurrentWindow()->ContentRegionRect.Max.x -= frameHeight * 0.5f;
ImGui::GetCurrentWindow()->WorkRect.Max.x -= frameHeight * 0.5f;
ImGui::GetCurrentWindow()->InnerRect.Max.x -= frameHeight * 0.5f;
#else
ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x -= frameHeight * 0.5f;
#endif
ImGui::GetCurrentWindow()->Size.x -= frameHeight;
auto itemWidth = ImGui::CalcItemWidth();
ImGui::PushItemWidth(ImMax(0.0f, itemWidth - frameHeight));
s_GroupPanelLabelStack.push_back(ImRect(labelMin, labelMax));
}
void ImGui::EndGroupPanel()
{
ImGui::PopItemWidth();
auto itemSpacing = ImGui::GetStyle().ItemSpacing;
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
auto frameHeight = ImGui::GetFrameHeight();
ImGui::EndGroup();
//ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(0, 255, 0, 64), 4.0f);
ImGui::EndGroup();
ImGui::SameLine(0.0f, 0.0f);
ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
ImGui::Dummy(ImVec2(0.0, frameHeight - frameHeight * 0.5f - itemSpacing.y));
ImGui::EndGroup();
auto itemMin = ImGui::GetItemRectMin();
auto itemMax = ImGui::GetItemRectMax();
//ImGui::GetWindowDrawList()->AddRectFilled(itemMin, itemMax, IM_COL32(255, 0, 0, 64), 4.0f);
auto labelRect = s_GroupPanelLabelStack.back();
s_GroupPanelLabelStack.pop_back();
ImVec2 halfFrame = ImVec2(frameHeight * 0.25f, frameHeight) * 0.5f;
ImRect frameRect = ImRect(itemMin + halfFrame, itemMax - ImVec2(halfFrame.x, 0.0f));
labelRect.Min.x -= itemSpacing.x;
labelRect.Max.x += itemSpacing.x;
for (int i = 0; i < 4; ++i)
{
switch (i)
{
// left half-plane
case 0: ImGui::PushClipRect(ImVec2(-FLT_MAX, -FLT_MAX), ImVec2(labelRect.Min.x, FLT_MAX), true); break;
// right half-plane
case 1: ImGui::PushClipRect(ImVec2(labelRect.Max.x, -FLT_MAX), ImVec2(FLT_MAX, FLT_MAX), true); break;
// top
case 2: ImGui::PushClipRect(ImVec2(labelRect.Min.x, -FLT_MAX), ImVec2(labelRect.Max.x, labelRect.Min.y), true); break;
// bottom
case 3: ImGui::PushClipRect(ImVec2(labelRect.Min.x, labelRect.Max.y), ImVec2(labelRect.Max.x, FLT_MAX), true); break;
}
ImGui::GetWindowDrawList()->AddRect(
frameRect.Min, frameRect.Max,
ImColor(ImGui::GetStyleColorVec4(ImGuiCol_Border)),
halfFrame.x);
ImGui::PopClipRect();
}
ImGui::PopStyleVar(2);
#if IMGUI_VERSION_NUM >= 17301
ImGui::GetCurrentWindow()->ContentRegionRect.Max.x += frameHeight * 0.5f;
ImGui::GetCurrentWindow()->WorkRect.Max.x += frameHeight * 0.5f;
ImGui::GetCurrentWindow()->InnerRect.Max.x += frameHeight * 0.5f;
#else
ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x += frameHeight * 0.5f;
#endif
ImGui::GetCurrentWindow()->Size.x += frameHeight;
ImGui::Dummy(ImVec2(0.0f, 0.0f));
ImGui::EndGroup();
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <imgui.h>
namespace ImGui
{
// Group panels taken from https://github.com/ocornut/imgui/issues/1496#issuecomment-655048353
// Licensed under CC0 license as per https://github.com/ocornut/imgui/issues/1496#issuecomment-1287772456
void BeginGroupPanel(const char* name, const ImVec2& size = ImVec2(0.0f, 0.0f));
void EndGroupPanel();
}
@@ -226,6 +226,7 @@ bool ServerTrackedDeviceProvider::HandleDevicePoseUpdated(uint32_t openVRID, vr:
pose.vecPosition[2] = -pose.vecWorldFromDriverTranslation[2];
} else if (tf.enabled)
{
// @TODO: Offset, scale, and re-offset
pose.vecPosition[0] *= tf.scale;
pose.vecPosition[1] *= tf.scale;
pose.vecPosition[2] *= tf.scale;
+36 -12
View File
@@ -13,7 +13,7 @@ A quick video of how this works using an old version (~v0.3) is available at htt
### 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`). There are many guides that say you need to edit the SteamVR config manually. You do not.
Before following the directions below, download and run the installer for the [latest release](https://github.com/hyblocker/OpenVR-SpaceCalibrator/releases/latest). 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
@@ -21,22 +21,46 @@ Once Space Calibrator has a calibration, it works in the background to keep your
### Calibration
This fork supports continuous calibration. This means that there are two ways to calibrate:
#### 1. Normal Calibration
> Use this if you do not have a tracker on your VR headset.
As part of first time setup, or when you make a change to your space (e.g. move a sensor), and occasionally as the calibration drifts over time (consumer VR tracking isn't perfectly stable), you'll need to run a calibration:
1. Copy the chaperone/guardian bounds from your HMD's play space. This doesn't need to be run if your HMD's play space hasn't changed since last time you copied it. __Example:__ if you're using the Rift with Vive trackers and you bump a Vive lighthouse, or if the calibration has just drifted a little, you likely don't need to run this step, but if you bump an Oculus sensor you will (after running Oculus guardian setup again).
1. Run SteamVR, with only devices from your HMD's tracking system powered on. __Example:__ for Rift with Vive trackers, don't turn on the trackers yet.
2. Confirm your chaperone/guardian is set up with the walls in the right place. If you change it later, you need to run step again.
3. Open SPACE CAL in the SteamVR dashboard overlay.
4. Click `Copy Chaperone Bounds to profile`
1. Run SteamVR, with only devices from your HMD's tracking system powered on. __Example:__ for Rift with Vive trackers, don't turn on the trackers yet.
2. Confirm your chaperone/guardian is set up with the walls in the right place. If you change it later, you need to run step again.
3. Open SPACE CAL in the SteamVR dashboard overlay.
4. Click `Copy Chaperone Bounds to profile`
2. Calibrate devices.
1. Open SteamVR if you haven't already. Turn on some or all your devices.
2. Open SPACE CAL in the SteamVR dashboard overlay.
3. Select one device from the reference space on the left and one device from the target space on the right. If you turned on multiple devices from one space and can't tell which one is selected, click "Identify selected devices" to blink an LED or vibrate it. __Example:__ for Rift with Vive trackers, you'll see the Touch controllers on the left, and Vive trackers on the right. __Pro tip:__ if you turn on just one Vive tracker, you don't have to figure out which one is selected.
4. Hold these two devices in one hand, like they're glued together. If they slip, calibration won't work as well.
5. Click `Start Calibration`
6. Move and rotate your hand around slowly a few times, like you're calibrating the compass on your phone. You want to sample as many orientations as possible.
7. Done! A profile will be saved automatically. If you haven't already, turn on all your devices. Space Calibrator will automatically apply the calibration to devices as they turn on.
1. Open SteamVR if you haven't already. Turn on some or all your devices.
2. Open SPACE CAL in the SteamVR dashboard overlay.
3. Select one device from the reference space on the left and one device from the target space on the right. If you turned on multiple devices from one space and can't tell which one is selected, click "Identify selected devices" to blink an LED or vibrate it. __Example:__ for Rift with Vive trackers, you'll see the Touch controllers on the left, and Vive trackers on the right. __Pro tip:__ if you turn on just one Vive tracker, you don't have to figure out which one is selected.
4. Hold these two devices in one hand, like they're glued together. If they slip, calibration won't work as well.
5. Click `Start Calibration`
6. Move and rotate your hand around slowly a few times, like you're calibrating the compass on your phone. You want to sample as many orientations as possible.
7. Done! A profile will be saved automatically. If you haven't already, turn on all your devices. Space Calibrator will automatically apply the calibration to devices as they turn on.
#### 2. Continuous Calibration
> Use this only if you have a tracker on your VR headset.
Continuous calibration allows you to calibrate playspaces continuously, eliminating the need to do any of the work above.
1. Start SteamVR with the VR headset you wish to use.
2. Turn on **ONLY** the tracker which is attached on the VR headset.
3. Select the VR headset and tracker and calibrate.
4. Turn on your other devices.
5. You should see them line up with you as you after moving around your playspace for a bit for an initial calibration.
### Calibration outside VR
+1 -1
View File
@@ -1,3 +1,3 @@
#pragma once
#define SPACECAL_VERSION_STRING "1.4-bd_-r2"
#define SPACECAL_VERSION_STRING "1.4-contcal-r8"
Binary file not shown.
Binary file not shown.