mirror of
https://github.com/hyblocker/OpenVR-SpaceCalibrator.git
synced 2026-10-24 00:01:04 +02:00
Compare commits
39
Commits
v1.4-bd_-r2
...
v1.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8100d776b8 | ||
|
|
137d8916af | ||
|
|
ceeab0e6e2 | ||
|
|
797f65ad0d | ||
|
|
3460437924 | ||
|
|
67f7c820f5 | ||
|
|
3ac6dfc208 | ||
|
|
8b86898417 | ||
|
|
d9fb08560b | ||
|
|
0ac3ab3ad8 | ||
|
|
b0fe076845 | ||
|
|
5fba766d4d | ||
|
|
9da1c41927 | ||
|
|
31fb1d15f9 | ||
|
|
16625138e1 | ||
|
|
d0159852e1 | ||
|
|
d22b2d1c69 | ||
|
|
308641ebf2 | ||
|
|
19ed75c378 | ||
|
|
4dd9ccfc79 | ||
|
|
f578999f0e | ||
|
|
7a4dc9007d | ||
|
|
02f4aa22dc | ||
|
|
89d231472a | ||
|
|
1515312e76 | ||
|
|
7349f510ef | ||
|
|
71b82d0641 | ||
|
|
68476caafc | ||
|
|
011158cd2c | ||
|
|
a8c48136c0 | ||
|
|
dc9ada1a60 | ||
|
|
3acdbe5654 | ||
|
|
de0d3425fa | ||
|
|
3b8ea60c8a | ||
|
|
56531e0432 | ||
|
|
1c18cd5445 | ||
|
|
e371b1d13f | ||
|
|
cc4ffa774d | ||
|
|
9a0204466a |
No files matched your search
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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...
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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.
Reference in new issue
Block a user