mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Compare commits
2 Commits
bscotch/ac
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
181c6599b7 | ||
|
|
fb77d3cf8a |
@@ -637,8 +637,6 @@ settings-general-fk_settings-arm_fk = Arm tracking
|
||||
settings-general-fk_settings-arm_fk-description = Force arms to be tracked from the headset (HMD) even if positional hand data is available.
|
||||
settings-general-fk_settings-arm_fk-force_arms = Force arms from HMD
|
||||
settings-general-fk_settings-reset_settings = Reset settings
|
||||
settings-general-fk_settings-reset_settings-step_mounting-description = Use step mounting method instead of ski pose.
|
||||
settings-general-fk_settings-reset_settings-step_mounting = Step mounting
|
||||
settings-general-fk_settings-reset_settings-reset_hmd_pitch-description = Reset the HMD's pitch (vertical rotation) upon doing a full reset. Useful if wearing an HMD on the forehead for VTubing or mocap. Do not enable for VR.
|
||||
settings-general-fk_settings-reset_settings-reset_hmd_pitch = Reset HMD pitch
|
||||
settings-general-fk_settings-arm_fk-reset_mode-description = Change which arm pose is expected for mounting calibration.
|
||||
|
||||
@@ -850,64 +850,46 @@ export function GeneralSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col pt-2 pb-2">
|
||||
<div className="flex flex-col pt-2">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-fk_settings-reset_settings')}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-3 pb-3">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-reset_settings-step_mounting-description'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-2 pb-3">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="resetsSettings.stepMounting"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-reset_settings-step_mounting'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-3 pb-3">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-reset_settings-reset_hmd_pitch-description'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-3 pb-3">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="resetsSettings.resetHmdPitch"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-reset_settings-reset_hmd_pitch'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-3 pb-3">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-leg_fk-reset_mounting_feet-description-v1'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-3 pb-3">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="resetsSettings.resetMountingFeet"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-leg_fk-reset_mounting_feet-v1'
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col pt-2 pb-3">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-reset_settings-reset_hmd_pitch-description'
|
||||
)}
|
||||
</Typography>
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="resetsSettings.resetHmdPitch"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-reset_settings-reset_hmd_pitch'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 justify-end">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-leg_fk-reset_mounting_feet-description-v1'
|
||||
)}
|
||||
</Typography>
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="resetsSettings.resetMountingFeet"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-leg_fk-reset_mounting_feet-v1'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -100,9 +100,7 @@ function TrackerSmol({
|
||||
'border-[3px] border-opacity-80 rounded-md overflow-clip',
|
||||
{
|
||||
'border-status-warning': warning,
|
||||
'border-transparent':
|
||||
!warning && !tracker.accelRecordingInProgress,
|
||||
'border-status-recording': tracker.accelRecordingInProgress,
|
||||
'border-transparent': !warning,
|
||||
}
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -60,9 +60,7 @@ export function TrackerNameCell({
|
||||
'border-[2px] border-opacity-80 rounded-md overflow-clip',
|
||||
{
|
||||
'border-status-warning': warning,
|
||||
'border-transparent':
|
||||
!warning && !tracker.accelRecordingInProgress,
|
||||
'border-status-recording': tracker.accelRecordingInProgress,
|
||||
'border-transparent': !warning,
|
||||
}
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -19,7 +19,6 @@ export function useDataFeedConfig() {
|
||||
trackerData.tps = true;
|
||||
trackerData.rawMagneticVector = true;
|
||||
trackerData.stayAligned = true;
|
||||
trackerData.accelRecordingInProgress = true;
|
||||
|
||||
const dataMask = new DeviceDataMaskT();
|
||||
dataMask.deviceData = true;
|
||||
|
||||
@@ -14,7 +14,6 @@ export interface ResetSettingsForm {
|
||||
yawResetSmoothTime: number;
|
||||
saveMountingReset: boolean;
|
||||
resetHmdPitch: boolean;
|
||||
stepMounting: boolean;
|
||||
}
|
||||
|
||||
export const defaultResetSettings = {
|
||||
@@ -23,7 +22,6 @@ export const defaultResetSettings = {
|
||||
yawResetSmoothTime: 0.0,
|
||||
saveMountingReset: false,
|
||||
resetHmdPitch: false,
|
||||
stepMounting: false,
|
||||
};
|
||||
|
||||
export function loadResetSettings(resetSettingsForm: ResetSettingsForm) {
|
||||
@@ -33,7 +31,6 @@ export function loadResetSettings(resetSettingsForm: ResetSettingsForm) {
|
||||
resetsSettings.yawResetSmoothTime = resetSettingsForm.yawResetSmoothTime;
|
||||
resetsSettings.saveMountingReset = resetSettingsForm.saveMountingReset;
|
||||
resetsSettings.resetHmdPitch = resetSettingsForm.resetHmdPitch;
|
||||
resetsSettings.stepMounting = resetSettingsForm.stepMounting;
|
||||
|
||||
return resetsSettings;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ body {
|
||||
--warning: 255, 225, 53;
|
||||
--critical: 223, 109, 140;
|
||||
--special: 164, 79, 237;
|
||||
--recording: 255, 84, 84;
|
||||
--window-icon-stroke: 192, 161, 216;
|
||||
|
||||
--default-color: 255, 255, 255;
|
||||
|
||||
@@ -191,7 +191,6 @@ const config = {
|
||||
warning: 'rgb(var(--warning), <alpha-value>)',
|
||||
critical: 'rgb(var(--critical), <alpha-value>)',
|
||||
special: 'rgb(var(--special), <alpha-value>)',
|
||||
recording: 'rgb(var(--recording), <alpha-value>)',
|
||||
},
|
||||
window: {
|
||||
icon: 'rgb(var(--window-icon-stroke), <alpha-value>)',
|
||||
|
||||
@@ -160,7 +160,7 @@ android {
|
||||
|
||||
// adds an offset of the version code as we might do apk releases in the middle of actual
|
||||
// releases if we failed on bundling or stuff
|
||||
val versionCodeOffset = 4
|
||||
val versionCodeOffset = 5
|
||||
// Defines the version number of your app.
|
||||
versionCode = (extra["gitVersionCode"] as? Int)?.plus(versionCodeOffset) ?: 0
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ dependencies {
|
||||
implementation("org.apache.commons:commons-lang3:3.20.0")
|
||||
implementation("org.apache.commons:commons-collections4:4.5.0")
|
||||
|
||||
implementation("com.illposed.osc:javaosc-core:0.9")
|
||||
implementation("com.illposed.osc:javaosc-core:0.8")
|
||||
implementation("org.java-websocket:Java-WebSocket:1.+")
|
||||
implementation("com.melloware:jintellitype:1.+")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
|
||||
|
||||
@@ -48,7 +48,6 @@ enum class MountingMethods(val id: Int) {
|
||||
}
|
||||
|
||||
class ResetsConfig {
|
||||
var stepMounting = false
|
||||
|
||||
// Always reset mounting for feet
|
||||
var resetMountingFeet = false
|
||||
|
||||
@@ -275,9 +275,6 @@ fun createTrackerData(
|
||||
if (mask.stayAligned) {
|
||||
TrackerData.addStayAligned(fbb, stayAlignedOffset)
|
||||
}
|
||||
if (mask.accelRecordingInProgress) {
|
||||
TrackerData.addAccelRecordingInProgress(fbb, tracker.accelMountInProgress)
|
||||
}
|
||||
|
||||
return TrackerData.endTrackerData(fbb)
|
||||
}
|
||||
|
||||
@@ -369,7 +369,6 @@ fun createArmsResetModeSettings(
|
||||
resetsConfig.yawResetSmoothTime,
|
||||
resetsConfig.saveMountingReset,
|
||||
resetsConfig.resetHmdPitch,
|
||||
resetsConfig.stepMounting,
|
||||
)
|
||||
|
||||
fun createSettingsResponse(fbb: FlatBufferBuilder, server: VRServer): Int {
|
||||
|
||||
@@ -332,7 +332,6 @@ class RPCSettingsHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) {
|
||||
resetsConfig.saveMountingReset = req.resetsSettings().saveMountingReset()
|
||||
resetsConfig.yawResetSmoothTime = req.resetsSettings().yawResetSmoothTime()
|
||||
resetsConfig.resetHmdPitch = req.resetsSettings().resetHmdPitch()
|
||||
resetsConfig.stepMounting = req.resetsSettings().stepMounting()
|
||||
resetsConfig.updateTrackersResetsSettings()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
package dev.slimevr.reset.accel
|
||||
|
||||
import dev.slimevr.tracking.trackers.Tracker
|
||||
import dev.slimevr.util.AccelAccumulator
|
||||
import io.eiren.util.logging.LogManager
|
||||
import io.github.axisangles.ktmath.Vector3
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.schedule
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.TimeSource
|
||||
|
||||
// Handles recording and processing of acceleration-based session calibration
|
||||
class AccelResetHandler(val timeSource: TimeSource.WithComparableMarks = TimeSource.Monotonic) {
|
||||
var isRunning: Boolean = false
|
||||
private set
|
||||
var isDetecting: Boolean = false
|
||||
private set
|
||||
var isRecording: Boolean = false
|
||||
private set
|
||||
|
||||
private val recordingLock = ReentrantLock()
|
||||
|
||||
private var hmd: Tracker? = null
|
||||
private val trackers: MutableList<RecordingWrapper> = mutableListOf()
|
||||
|
||||
private val timeoutTimer = Timer()
|
||||
private var timerTask: TimerTask? = null
|
||||
|
||||
private var recStartTime = timeSource.markNow()
|
||||
|
||||
/**
|
||||
* Starts the accel reset process. performing rest detection on the trackers
|
||||
* provided to automatically control the recording period.
|
||||
*/
|
||||
fun start(hmd: Tracker, trackers: Iterable<Tracker>) = recordingLock.withLock {
|
||||
// Maybe should throw IllegalStateException? Or just restart?
|
||||
if (isRunning) return
|
||||
|
||||
// Nothing to do
|
||||
if (trackers.none()) return
|
||||
|
||||
// Initialize our state
|
||||
isRunning = true
|
||||
this.hmd = hmd
|
||||
|
||||
// Register our tracker event listener
|
||||
for (tracker in trackers) {
|
||||
val wrappedTracker = RecordingWrapper(tracker)
|
||||
this.trackers.add(wrappedTracker)
|
||||
tracker.accelTickCallback = {
|
||||
onAccelData(wrappedTracker)
|
||||
}
|
||||
}
|
||||
|
||||
// Start waiting for movement
|
||||
isDetecting = true
|
||||
timerTask?.cancel()
|
||||
timerTask = timeoutTimer.schedule(START_TIMEOUT.inWholeMilliseconds) {
|
||||
timeout()
|
||||
}
|
||||
|
||||
LogManager.info("[AccelResetHandler] Reset requested, detecting movement...")
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles rest detection and data collection.
|
||||
*/
|
||||
private fun onAccelData(tracker: RecordingWrapper) {
|
||||
if (!isDetecting) return
|
||||
|
||||
val sample = tracker.makeSample(timeSource.markNow(), hmd?.position ?: Vector3.NULL)
|
||||
|
||||
// Rest detection
|
||||
tracker.updateRestState(sample)
|
||||
tracker.addRestSample(sample)
|
||||
// TODO: This shouldn't be done like this
|
||||
tracker.tracker.accelMountInProgress = isRecording && tracker.moving
|
||||
|
||||
if (!isRecording) {
|
||||
// We haven't started moving yet, don't start recording
|
||||
if (!tracker.moving) return
|
||||
|
||||
// Start recording
|
||||
recordingLock.withLock {
|
||||
// Race condition
|
||||
if (isRecording) return@withLock
|
||||
|
||||
// Dump rest detection into the recording on tracker threads
|
||||
for (tracker in trackers) tracker.dumpRest = true
|
||||
|
||||
isRecording = true
|
||||
recStartTime = timeSource.markNow()
|
||||
timerTask?.cancel()
|
||||
timerTask = timeoutTimer.schedule(RECORD_TIMEOUT.inWholeMilliseconds) {
|
||||
timeout()
|
||||
}
|
||||
|
||||
LogManager.info("[AccelResetHandler] Movement detected, recording started!")
|
||||
}
|
||||
} else if (
|
||||
timeSource.markNow() - recStartTime > MINIMUM_DURATION &&
|
||||
trackers.none { it.moving }
|
||||
) {
|
||||
// We're recording, the minimum duration has passed, and no trackers are
|
||||
// moving, therefore we can stop the recording and process it
|
||||
recordingLock.withLock {
|
||||
// Race condition
|
||||
if (!isRecording) return
|
||||
// Let's not block the tracker thread while processing
|
||||
thread {
|
||||
process()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Take the latest sample or dump the rest detection samples into the recording
|
||||
if (!tracker.dumpRest) {
|
||||
tracker.recording.add(sample)
|
||||
} else {
|
||||
tracker.recording.addAll(tracker.restDetect)
|
||||
tracker.dumpRest = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops recording, processes the recorded data, then resets this handler.
|
||||
*/
|
||||
private fun process() {
|
||||
stop()
|
||||
|
||||
LogManager.info("[AccelResetHandler] Done recording, processing...")
|
||||
|
||||
for (tracker in trackers) {
|
||||
val firstSample = tracker.recording.first()
|
||||
val lastSample = tracker.recording.last()
|
||||
|
||||
// Compute the unbiased final velocity
|
||||
val calibAccum = AccelAccumulator()
|
||||
RecordingProcessor.processTimeline(calibAccum, tracker)
|
||||
|
||||
// Assume the final velocity is zero (at rest), we can divide our unbiased
|
||||
// final velocity (m/s) by the duration and get a static acceleration
|
||||
// offset (m/s^2)
|
||||
val duration = lastSample.time - firstSample.time
|
||||
val bias = calibAccum.velocity / duration.toDouble(DurationUnit.SECONDS).toFloat()
|
||||
|
||||
// Compute the biased final offset
|
||||
val finalAccum = AccelAccumulator()
|
||||
RecordingProcessor.processTimeline(finalAccum, tracker, accelBias = bias)
|
||||
|
||||
// Compute the final offsets
|
||||
val trackerOffset = finalAccum.offset
|
||||
val trackerXZ = Vector3(trackerOffset.x, 0f, trackerOffset.z)
|
||||
val hmdOffset = lastSample.hmdPos - firstSample.hmdPos
|
||||
val hmdXZ = Vector3(hmdOffset.x, 0f, hmdOffset.z)
|
||||
|
||||
// TODO: Fail on high error
|
||||
|
||||
// Compute mounting to fix the yaw offset from tracker to HMD
|
||||
val mountRot = RecordingProcessor.angle(trackerXZ.unit()) *
|
||||
RecordingProcessor.angle(hmdXZ.unit()).inv()
|
||||
|
||||
// Apply that mounting to the tracker
|
||||
val resetsHandler = tracker.tracker.resetsHandler
|
||||
val finalMounting = resetsHandler.mountingOrientation * resetsHandler.mountRotFix * mountRot
|
||||
resetsHandler.mountRotFix *= mountRot
|
||||
|
||||
LogManager.info(
|
||||
"[Accel] Tracker ${tracker.tracker.id} (${tracker.tracker.trackerPosition?.designation}):\n" +
|
||||
"Tracker offset: $trackerOffset\n" +
|
||||
"HMD offset: $hmdOffset\n" +
|
||||
"Error value (meters): ${trackerXZ.len() - hmdXZ.len()}\n" +
|
||||
"Resulting mounting: $finalMounting",
|
||||
)
|
||||
}
|
||||
|
||||
clean()
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops recording without clearing the recorded data.
|
||||
*/
|
||||
private fun stop() = recordingLock.withLock {
|
||||
// Cancel any pending timeouts
|
||||
timerTask?.cancel()
|
||||
timerTask = null
|
||||
|
||||
isDetecting = false
|
||||
isRecording = false
|
||||
|
||||
// Unregister our tracker event listener
|
||||
for (tracker in trackers) {
|
||||
tracker.tracker.accelTickCallback = null
|
||||
tracker.tracker.accelMountInProgress = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately stops execution and resets this handler.
|
||||
*/
|
||||
private fun clean() {
|
||||
stop()
|
||||
|
||||
// Reset data storage
|
||||
hmd = null
|
||||
trackers.clear()
|
||||
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the accel reset process and resets this handler.
|
||||
*/
|
||||
fun cancel() {
|
||||
clean()
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the process has timed out, then resets this handler.
|
||||
*/
|
||||
private fun timeout() {
|
||||
LogManager.warning("[AccelResetHandler] Reset timed out, aborting")
|
||||
clean()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val START_TIMEOUT = 8.seconds
|
||||
val MINIMUM_DURATION = 2.seconds
|
||||
val RECORD_TIMEOUT = 8.seconds
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package dev.slimevr.reset.accel
|
||||
|
||||
import dev.slimevr.util.AccelAccumulator
|
||||
import io.github.axisangles.ktmath.Quaternion
|
||||
import io.github.axisangles.ktmath.Vector3
|
||||
import kotlin.math.atan2
|
||||
import kotlin.time.ComparableTimeMark
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.DurationUnit
|
||||
|
||||
object RecordingProcessor {
|
||||
fun accumSample(
|
||||
accum: AccelAccumulator,
|
||||
sample: RecordingSample,
|
||||
lastSampleTime: ComparableTimeMark? = null,
|
||||
accelBias: Vector3 = Vector3.NULL,
|
||||
): Duration {
|
||||
val delta = lastSampleTime?.let { sample.time - it } ?: Duration.ZERO
|
||||
accum.dataTick(sample.accel - accelBias, delta.toDouble(DurationUnit.SECONDS).toFloat())
|
||||
|
||||
return delta
|
||||
}
|
||||
|
||||
fun processTimeline(
|
||||
accum: AccelAccumulator,
|
||||
wrapper: RecordingWrapper,
|
||||
lastSampleTime: ComparableTimeMark? = null,
|
||||
accelBias: Vector3 = Vector3.NULL,
|
||||
): ComparableTimeMark? {
|
||||
var lastTime = lastSampleTime
|
||||
|
||||
for (sample in wrapper.recording) {
|
||||
accumSample(accum, sample, lastTime, accelBias)
|
||||
lastTime = sample.time
|
||||
}
|
||||
|
||||
return lastTime
|
||||
}
|
||||
|
||||
fun angle(vector: Vector3): Quaternion {
|
||||
val yaw = atan2(vector.x, vector.z)
|
||||
return Quaternion.rotationAroundYAxis(yaw)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package dev.slimevr.reset.accel
|
||||
|
||||
import io.github.axisangles.ktmath.Quaternion
|
||||
import io.github.axisangles.ktmath.Vector3
|
||||
import kotlin.time.ComparableTimeMark
|
||||
|
||||
data class RecordingSample(
|
||||
val time: ComparableTimeMark,
|
||||
// Tracker
|
||||
val accel: Vector3,
|
||||
val rot: Quaternion,
|
||||
// HMD
|
||||
val hmdPos: Vector3,
|
||||
)
|
||||
@@ -1,63 +0,0 @@
|
||||
package dev.slimevr.reset.accel
|
||||
|
||||
import dev.slimevr.autobone.StatsCalculator
|
||||
import dev.slimevr.tracking.trackers.Tracker
|
||||
import io.github.axisangles.ktmath.Vector3
|
||||
import org.apache.commons.collections4.queue.CircularFifoQueue
|
||||
import kotlin.time.ComparableTimeMark
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
data class RecordingWrapper(val tracker: Tracker, var moving: Boolean = false) {
|
||||
// Buffer for performing rest detection
|
||||
val restDetect = CircularFifoQueue<RecordingSample>(8)
|
||||
|
||||
// List capacity assuming ~10 seconds at 100 TPS
|
||||
val recording: MutableList<RecordingSample> = ArrayList(1024)
|
||||
|
||||
// Whether to dump our rest detection into the recording on the next sample
|
||||
var dumpRest = false
|
||||
|
||||
fun makeSample(time: ComparableTimeMark, hmdPos: Vector3): RecordingSample = RecordingSample(
|
||||
time,
|
||||
tracker.getAcceleration(),
|
||||
tracker.getRotation(),
|
||||
hmdPos,
|
||||
)
|
||||
|
||||
fun addRestSample(sample: RecordingSample): Boolean {
|
||||
// Collect samples for rest detection at a constant-ish rate if possible
|
||||
return if (moving && restDetect.isNotEmpty()) {
|
||||
val lastSampleTime = restDetect.last().time
|
||||
// Try to have TPS at a lower rate
|
||||
if (sample.time - lastSampleTime > REST_INTERVAL) {
|
||||
restDetect.add(sample)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
restDetect.add(sample)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateRestState(new: RecordingSample): Boolean {
|
||||
if (restDetect.size < 4) return moving
|
||||
|
||||
val stats = StatsCalculator()
|
||||
for (sample in restDetect) {
|
||||
stats.addValue(sample.accel.len())
|
||||
}
|
||||
|
||||
// Conditions to start or remain moving
|
||||
// TODO: Add rotation as a rest metric
|
||||
moving = if (moving) {
|
||||
stats.mean >= 0.1f || stats.standardDeviation >= 0.2f
|
||||
} else {
|
||||
stats.mean >= 0.3f || new.accel.len() - stats.mean >= 0.6f
|
||||
}
|
||||
return moving
|
||||
}
|
||||
|
||||
companion object {
|
||||
val REST_INTERVAL = 100.milliseconds
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package dev.slimevr.tracking.processor.skeleton
|
||||
import dev.slimevr.VRServer
|
||||
import dev.slimevr.config.MountingMethods
|
||||
import dev.slimevr.config.StayAlignedConfig
|
||||
import dev.slimevr.reset.accel.AccelResetHandler
|
||||
import dev.slimevr.tracking.processor.Bone
|
||||
import dev.slimevr.tracking.processor.BoneType
|
||||
import dev.slimevr.tracking.processor.Constraint
|
||||
@@ -218,8 +217,6 @@ class HumanSkeleton(
|
||||
var ikSolver = IKSolver(headBone)
|
||||
var userHeightCalibration: UserHeightCalibration? = null
|
||||
|
||||
val accelResetHandler = AccelResetHandler()
|
||||
|
||||
// Stay Aligned
|
||||
var trackerSkeleton = TrackerSkeleton(this)
|
||||
var stayAlignedConfig = StayAlignedConfig()
|
||||
@@ -1624,29 +1621,6 @@ class HumanSkeleton(
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Make this not dumb
|
||||
if (headTracker?.resetsHandler?.stepMounting == true ||
|
||||
trackersToReset.any { it?.resetsHandler?.stepMounting == true }
|
||||
) {
|
||||
headTracker?.let { hmd ->
|
||||
// Make sure we have HMD position
|
||||
if (!hmd.hasPosition) {
|
||||
return@let
|
||||
}
|
||||
|
||||
// Start step mounting
|
||||
accelResetHandler.start(
|
||||
hmd,
|
||||
trackersToReset.filterNotNull().filter {
|
||||
it.allowMounting && (bodyParts.isEmpty() || bodyParts.contains(it.trackerPosition?.bodyPart))
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
LogManager.info("[HumanSkeleton] Reset: mounting ($resetSourceName) failed, HMD is not available")
|
||||
return
|
||||
}
|
||||
|
||||
// Resets the mounting orientation of the trackers with the HMD as reference.
|
||||
var referenceRotation = IDENTITY
|
||||
headTracker?.let {
|
||||
|
||||
@@ -173,11 +173,6 @@ class Tracker @JvmOverloads constructor(
|
||||
val stayAligned = StayAlignedTrackerState(this)
|
||||
val yawResetSmoothing = InterpolationHandler()
|
||||
|
||||
// Currently only used for accel resets, to add anything else, consider using a
|
||||
// subscribable event listener instead
|
||||
var accelTickCallback: ((tracker: Tracker) -> Unit)? = null
|
||||
var accelMountInProgress = false
|
||||
|
||||
init {
|
||||
// IMPORTANT: Look here for the required states of inputs
|
||||
require(!allowReset || (hasRotation && allowReset)) {
|
||||
@@ -283,13 +278,6 @@ class Tracker @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the tracker that it received new accel data
|
||||
* Accel may be (and usually is) desynced from rotation data, so if we want the
|
||||
* latest, we need to process it here
|
||||
*/
|
||||
fun accelDataTick() = accelTickCallback?.invoke(this)
|
||||
|
||||
/**
|
||||
* A way to delay the timeout of the tracker
|
||||
*/
|
||||
|
||||
@@ -39,7 +39,6 @@ class TrackerResetsHandler(val tracker: Tracker) {
|
||||
private var yawResetSmoothTime = 0.0f
|
||||
var saveMountingReset = false
|
||||
var resetHmdPitch = false
|
||||
var stepMounting = false
|
||||
var allowDriftCompensation = false
|
||||
var lastResetQuaternion: Quaternion? = null
|
||||
|
||||
@@ -86,6 +85,7 @@ class TrackerResetsHandler(val tracker: Tracker) {
|
||||
* [mountingOrientation] will apply.
|
||||
*/
|
||||
var mountRotFix = Quaternion.IDENTITY
|
||||
private set
|
||||
|
||||
/**
|
||||
* Yaw fix is set by yaw reset. This sets the current y rotation to match the
|
||||
@@ -165,7 +165,6 @@ class TrackerResetsHandler(val tracker: Tracker) {
|
||||
yawResetSmoothTime = config.yawResetSmoothTime
|
||||
saveMountingReset = config.saveMountingReset
|
||||
resetHmdPitch = config.resetHmdPitch
|
||||
stepMounting = config.stepMounting
|
||||
}
|
||||
|
||||
fun trySetMountingReset(quat: Quaternion) {
|
||||
|
||||
@@ -378,9 +378,6 @@ class HIDCommon {
|
||||
if (packetType == 1 || packetType == 2 || packetType == 4 || packetType == 7) {
|
||||
tracker.dataTick() // only data tick if there is rotation data
|
||||
}
|
||||
if (packetType == 1 || packetType == 2 || packetType == 7) {
|
||||
tracker.accelDataTick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,7 +424,6 @@ class TrackersUDPServer(private val port: Int, name: String, private val tracker
|
||||
} else {
|
||||
tracker.setAcceleration(SENSOR_OFFSET_CORRECTION.sandwich(packet.acceleration))
|
||||
}
|
||||
tracker.accelDataTick()
|
||||
}
|
||||
tracker.dataTick()
|
||||
}
|
||||
@@ -463,7 +462,6 @@ class TrackersUDPServer(private val port: Int, name: String, private val tracker
|
||||
} else {
|
||||
tracker.setAcceleration(SENSOR_OFFSET_CORRECTION.sandwich(packet.acceleration))
|
||||
}
|
||||
tracker.accelDataTick()
|
||||
}
|
||||
|
||||
is UDPPacket10PingPong -> {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package dev.slimevr.util
|
||||
|
||||
import com.jme3.system.NanoTimer
|
||||
import io.github.axisangles.ktmath.Vector3
|
||||
|
||||
class AccelAccumulator {
|
||||
var acceleration = Vector3.NULL
|
||||
private set
|
||||
var velocity = Vector3.NULL
|
||||
private set
|
||||
var offset = Vector3.NULL
|
||||
private set
|
||||
|
||||
val timer = NanoTimer()
|
||||
|
||||
fun dataTick(acceleration: Vector3, time: Float? = null) {
|
||||
timer.update()
|
||||
val deltaTime = time ?: timer.timePerFrame
|
||||
|
||||
this.acceleration = acceleration
|
||||
offset += (velocity * deltaTime) + ((acceleration * deltaTime * deltaTime) / 2f)
|
||||
velocity += acceleration * deltaTime
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package dev.slimevr.unit
|
||||
|
||||
import dev.slimevr.unit.TrackerTestUtils.assertVectorApproxEqual
|
||||
import io.github.axisangles.ktmath.Quaternion
|
||||
import io.github.axisangles.ktmath.Vector3
|
||||
import org.junit.jupiter.api.DynamicTest
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import kotlin.math.atan2
|
||||
|
||||
class AccelMountTests {
|
||||
@TestFactory
|
||||
fun testAccelAlignment(): List<DynamicTest> = testSet.map { t ->
|
||||
DynamicTest.dynamicTest(
|
||||
"Alignment of accel (Expected: ${t.expected}, reference: ${t.hmd})",
|
||||
) {
|
||||
checkAlignAccel(t.hmd, t.tracker, t.expected)
|
||||
}
|
||||
}
|
||||
|
||||
fun angle(vector: Vector3): Quaternion {
|
||||
val yaw = atan2(vector.x, vector.z)
|
||||
return Quaternion.rotationAroundYAxis(yaw)
|
||||
}
|
||||
|
||||
fun checkAlignAccel(hmd: Vector3, tracker: Vector3, expected: Vector3) {
|
||||
// All we really care about is the angle difference between hmdRot and trackerRot
|
||||
val hmdRot = angle(hmd.unit()).inv()
|
||||
val trackerRot = angle(tracker.unit())
|
||||
val result = (trackerRot * hmdRot).sandwichUnitZ()
|
||||
|
||||
assertVectorApproxEqual(
|
||||
expected,
|
||||
result,
|
||||
"Resulting vector is not equal to reference vector ($expected vs $result)",
|
||||
)
|
||||
}
|
||||
|
||||
data class AlignTest(val hmd: Vector3, val tracker: Vector3, val expected: Vector3)
|
||||
|
||||
companion object {
|
||||
val testSet = arrayOf(
|
||||
// Front mount
|
||||
AlignTest(Vector3.POS_X, Vector3.POS_X, Vector3.POS_Z),
|
||||
AlignTest(Vector3.NEG_X, Vector3.NEG_X, Vector3.POS_Z),
|
||||
AlignTest(Vector3.POS_Z, Vector3.POS_Z, Vector3.POS_Z),
|
||||
AlignTest(Vector3.NEG_Z, Vector3.NEG_Z, Vector3.POS_Z),
|
||||
// Right mount
|
||||
AlignTest(Vector3.POS_X, Vector3.NEG_Z, Vector3.POS_X),
|
||||
AlignTest(Vector3.NEG_X, Vector3.POS_Z, Vector3.POS_X),
|
||||
AlignTest(Vector3.POS_Z, Vector3.POS_X, Vector3.POS_X),
|
||||
AlignTest(Vector3.NEG_Z, Vector3.NEG_X, Vector3.POS_X),
|
||||
// Back mount
|
||||
AlignTest(Vector3.POS_X, Vector3.NEG_X, Vector3.NEG_Z),
|
||||
AlignTest(Vector3.NEG_X, Vector3.POS_X, Vector3.NEG_Z),
|
||||
AlignTest(Vector3.POS_Z, Vector3.NEG_Z, Vector3.NEG_Z),
|
||||
AlignTest(Vector3.NEG_Z, Vector3.POS_Z, Vector3.NEG_Z),
|
||||
// Left mount
|
||||
AlignTest(Vector3.POS_X, Vector3.POS_Z, Vector3.NEG_X),
|
||||
AlignTest(Vector3.NEG_X, Vector3.NEG_Z, Vector3.NEG_X),
|
||||
AlignTest(Vector3.POS_Z, Vector3.NEG_X, Vector3.NEG_X),
|
||||
AlignTest(Vector3.NEG_Z, Vector3.POS_X, Vector3.NEG_X),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -64,9 +64,6 @@ class SkeletonResetTests {
|
||||
|
||||
@Test
|
||||
fun testSkeletonMountReset() {
|
||||
// TODO: Failing because of changed default mounting reset
|
||||
return
|
||||
|
||||
val trackers = TestTrackerSet()
|
||||
|
||||
// Initialize skeleton and everything
|
||||
|
||||
@@ -84,11 +84,4 @@ object TrackerTestUtils {
|
||||
fun vectorApproxEqual(v1: Vector3, v2: Vector3, tolerance: Float = FastMath.ZERO_TOLERANCE): Boolean = FastMath.isApproxEqual(v1.x, v2.x, tolerance) &&
|
||||
FastMath.isApproxEqual(v1.y, v2.y, tolerance) &&
|
||||
FastMath.isApproxEqual(v1.z, v2.z, tolerance)
|
||||
|
||||
fun assertVectorApproxEqual(expected: Vector3, actual: Vector3, message: String? = null) {
|
||||
if (!vectorApproxEqual(expected, actual)) {
|
||||
AssertionFailureBuilder.assertionFailure().message(message)
|
||||
.expected(expected).actual(actual).buildAndThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Submodule solarxr-protocol updated: 941f262055...fa2895b19a
Reference in New Issue
Block a user