mirror of
https://github.com/hyblocker/OpenVR-SpaceCalibrator.git
synced 2026-10-27 05:01:03 +01:00
Compare commits
37
Commits
v1.4-bd_+af-r4
...
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 |
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,11 +381,17 @@ void StartCalibration()
|
||||
}
|
||||
|
||||
void StartContinuousCalibration() {
|
||||
CalCtx.calibrationSpeed = CalibrationContext::SLOW;
|
||||
AssignTargets();
|
||||
StartCalibration();
|
||||
CalCtx.state = CalibrationState::Continuous;
|
||||
calibration.setRelativeTransformation(CalCtx.refToTargetPose, CalCtx.relativePosCalibrated);
|
||||
CalCtx.Log("Collecting initial samples...");
|
||||
calibration.lockRelativePosition = CalCtx.lockRelativePosition;
|
||||
if (CalCtx.lockRelativePosition) {
|
||||
CalCtx.Log("Relative position locked");
|
||||
}
|
||||
else {
|
||||
CalCtx.Log("Collecting initial samples...");
|
||||
}
|
||||
Metrics::WriteLogAnnotation("StartContinuousCalibration");
|
||||
}
|
||||
|
||||
@@ -365,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()))
|
||||
{
|
||||
@@ -461,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;
|
||||
@@ -480,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 {
|
||||
@@ -502,9 +591,7 @@ void CalibrationTick(double time)
|
||||
ScanAndApplyProfile(ctx);
|
||||
|
||||
CalCtx.Log("Finished calibration, profile saved\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
CalCtx.Log("Calibration failed.\n");
|
||||
}
|
||||
|
||||
@@ -522,7 +609,10 @@ void CalibrationTick(double time)
|
||||
calibration.Clear();
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < 50; i++) calibration.ShiftSample();
|
||||
size_t drop_samples = CalCtx.SampleCount() / 10;
|
||||
for (int i = 0; i < drop_samples; i++) {
|
||||
calibration.ShiftSample();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,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;
|
||||
@@ -43,13 +46,18 @@ 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;
|
||||
@@ -85,6 +93,8 @@ struct CalibrationContext
|
||||
|
||||
continuousCalibrationThreshold = 1.5f;
|
||||
|
||||
continuousCalibrationOffset = Eigen::Vector3d::Zero();
|
||||
|
||||
enableStaticRecalibration = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -139,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);
|
||||
@@ -232,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) {
|
||||
@@ -495,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;
|
||||
}
|
||||
@@ -523,6 +528,20 @@ bool CalibrationCalc::ComputeIncremental(bool &lerp, double threshold) {
|
||||
|
||||
Metrics::RecordTimestamp();
|
||||
|
||||
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);
|
||||
|
||||
m_isValid = true;
|
||||
m_estimatedTransformation = byRelPose;
|
||||
return true;
|
||||
}
|
||||
|
||||
double priorCalibrationError = INFINITY;
|
||||
Eigen::Vector3d priorPosOffset;
|
||||
if (m_isValid) {
|
||||
@@ -530,6 +549,7 @@ bool CalibrationCalc::ComputeIncremental(bool &lerp, double threshold) {
|
||||
|
||||
Metrics::posOffset_currentCal.Push(priorPosOffset * 1000);
|
||||
Metrics::error_currentCal.Push(priorCalibrationError * 1000);
|
||||
|
||||
if (priorCalibrationError < 0.005) {
|
||||
return false;
|
||||
}
|
||||
@@ -546,8 +566,8 @@ bool CalibrationCalc::ComputeIncremental(bool &lerp, double threshold) {
|
||||
ValidateCalibration(byRelPose, &relPoseError, &relPosOffset);
|
||||
Metrics::posOffset_byRelPose.Push(relPosOffset * 1000);
|
||||
Metrics::error_byRelPose.Push(relPoseError * 1000);
|
||||
|
||||
if (relPoseError < 0.015 || m_relativePosCalibrated && relPoseError < 0.035) {
|
||||
|
||||
if (relPoseError < 0.010 || m_relativePosCalibrated && relPoseError < 0.025) {
|
||||
newCalibrationValid = true;
|
||||
usingRelPose = true;
|
||||
newError = relPoseError;
|
||||
@@ -621,7 +641,7 @@ bool CalibrationCalc::ComputeIncremental(bool &lerp, double threshold) {
|
||||
}
|
||||
|
||||
m_isValid = true;
|
||||
m_estimatedTransformation = calibration;
|
||||
m_estimatedTransformation = calibration; // @NOTE: Continuous calibration
|
||||
m_axisVariance = newVariance;
|
||||
|
||||
if (!usingRelPose) {
|
||||
|
||||
@@ -59,6 +59,7 @@ public:
|
||||
static const double AxisVarianceThreshold;
|
||||
|
||||
bool enableStaticRecalibration;
|
||||
bool lockRelativePosition = false;
|
||||
|
||||
const Eigen::AffineCompact3d Transformation() const
|
||||
{
|
||||
@@ -126,6 +127,7 @@ private:
|
||||
|
||||
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>();
|
||||
@@ -154,6 +158,9 @@ static void ParseProfile(CalibrationContext &ctx, std::istream &stream)
|
||||
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;
|
||||
@@ -214,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);
|
||||
@@ -247,6 +258,7 @@ static void WriteProfile(CalibrationContext &ctx, std::ostream &out)
|
||||
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;
|
||||
@@ -315,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_-af-r3"
|
||||
#define SPACECAL_VERSION_STRING "1.4-contcal-r8"
|
||||
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user