Skip to content

Commit b83d03d

Browse files
committed
Add Velocity transmission toggle and calculation logic.
1 parent 88adfce commit b83d03d

5 files changed

Lines changed: 114 additions & 0 deletions

File tree

server/core/src/main/java/dev/slimevr/VRServer.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,21 @@ class VRServer @JvmOverloads constructor(
182182
instance = this
183183
}
184184

185+
/**
186+
* TODO: The design of this method is chosen for future expandability in case we want to have more complex velocity policies that depend on tracker properties or other config values.
187+
* Initiates the velocity policy application process for the specified [Tracker] (or all trackers if null).
188+
*
189+
* This method serves as the initiator.
190+
* The actual policy logic is not handled here,
191+
* but is delegated to [dev.slimevr.config.VRConfig.applyVelocityPolicy].
192+
*/
193+
private fun applyVelocityPolicyTo(tracker: Tracker?) {
194+
val targets = tracker?.let { listOf(it) } ?: trackers
195+
for (t in targets) {
196+
configManager.vrConfig.applyVelocityPolicy(t)
197+
}
198+
}
199+
185200
fun hasBridge(bridgeClass: Class<out Bridge?>): Boolean {
186201
for (bridge in bridges) {
187202
if (bridgeClass.isAssignableFrom(bridge.javaClass)) {
@@ -226,6 +241,9 @@ class VRServer @JvmOverloads constructor(
226241
refreshTrackersDriftCompensationEnabled()
227242
configManager.vrConfig.writeTrackerConfig(tracker)
228243
configManager.saveConfig()
244+
245+
// Requires a fresh TrackerConfig on Update, so executed after we save a new state.
246+
applyVelocityPolicyTo(tracker)
229247
}
230248
}
231249

server/core/src/main/java/dev/slimevr/config/VRConfig.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class VRConfig {
5858

5959
val trackingChecklist: TrackingChecklistConfig = TrackingChecklistConfig()
6060

61+
var velocityConfig: VelocityConfig = VelocityConfig()
62+
6163
val vrcConfig: VRCConfig = VRCConfig()
6264

6365
init {
@@ -104,6 +106,17 @@ class VRConfig {
104106
return config
105107
}
106108

109+
/**
110+
* Applies the velocity policy to the given [Tracker].
111+
*
112+
* This method determines whether the tracker—be it a physical sensor or a computed virtual device—is
113+
* permitted to calculate and broadcast velocity data.
114+
* It resolves the active [VelocityConfig] value, updating [Tracker.allowVelocity] accordingly.
115+
*/
116+
fun applyVelocityPolicy(tracker: Tracker) {
117+
tracker.allowVelocity = velocityConfig.sendDerivedVelocity
118+
}
119+
107120
fun readTrackerConfig(tracker: Tracker) {
108121
if (tracker.userEditable) {
109122
val config = getTracker(tracker)
@@ -119,6 +132,7 @@ class VRConfig {
119132
.readFilteringConfig(filters, tracker.getRotation())
120133
}
121134
}
135+
applyVelocityPolicy(tracker)
122136
}
123137

124138
fun writeTrackerConfig(tracker: Tracker?) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.slimevr.config
2+
3+
/**
4+
* Allows to enable/disable sending of optional derived velocity data via Protobuf.
5+
* Enables Natural Locomotion Support
6+
* May create overprediction in certain titles causing excessive jitter when moving upper body.
7+
*/
8+
class VelocityConfig {
9+
var sendDerivedVelocity: Boolean = false // Disables derived velocity for all trackers. Driver zeroes out velocity if nothing is returned in protobuf message.
10+
}

server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,7 @@ class HumanSkeleton(
11711171
it.position = trackerBone.getTailPosition()
11721172
it.setRotation(trackerBone.getGlobalRotation() * trackerBone.rotationOffset.inv())
11731173
it.dataTick()
1174+
it.updateDerivedVelocity(System.nanoTime())
11741175
}
11751176
}
11761177

server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ class Tracker @JvmOverloads constructor(
7575
*/
7676
val allowMounting: Boolean = false,
7777

78+
/**
79+
* If true, the tracker will send Derived Velocity.
80+
*/
81+
var allowVelocity: Boolean = false,
82+
7883
val isHmd: Boolean = false,
7984

8085
/**
@@ -105,7 +110,19 @@ class Tracker @JvmOverloads constructor(
105110
// IMU: +z forward, +x left, +y up
106111
// SlimeVR: +z backward, +x right, +y up
107112
private var _acceleration = Vector3.NULL
113+
private var _velocity = Vector3.NULL
108114
private var _magVector = Vector3.NULL
115+
116+
/**
117+
* Velocity state server-side differentiation based on sent poses
118+
*/
119+
private data class VelocityState(
120+
var prevTimeNs: Long = 0L,
121+
var prevPos: Vector3 = Vector3(0f, 0f, 0f),
122+
)
123+
124+
private var velocityState: VelocityState? = null
125+
109126
var position = Vector3.NULL
110127
val resetsHandler: TrackerResetsHandler = TrackerResetsHandler(this)
111128
val filteringHandler: TrackerFilteringHandler = TrackerFilteringHandler()
@@ -329,6 +346,42 @@ class Tracker @JvmOverloads constructor(
329346
}
330347
}
331348

349+
/**
350+
* Updates the derived velocity of the tracker by differentiating position over time.
351+
*
352+
* This method enforces the [allowVelocity] policy and checks for valid position data before
353+
* proceeding. If conditions are met, it calculates velocity based on the displacement since the
354+
* last update, applying a sanity check on the time delta to filter out noise and ensure data stability.
355+
*
356+
*/
357+
fun updateDerivedVelocity(nowNs: Long) {
358+
if (!allowVelocity || !hasPosition) {
359+
velocityState = null
360+
_velocity = Vector3.NULL
361+
return
362+
}
363+
364+
val pos = position
365+
val state = velocityState ?: VelocityState().also {
366+
velocityState = it
367+
}
368+
369+
if (state.prevTimeNs != 0L) {
370+
val dt = (nowNs - state.prevTimeNs) * 1e-9
371+
if (dt in 1e-4..0.25) {
372+
_velocity = Vector3(
373+
((pos.x - state.prevPos.x) / dt).toFloat(),
374+
((pos.y - state.prevPos.y) / dt).toFloat(),
375+
((pos.z - state.prevPos.z) / dt).toFloat(),
376+
)
377+
} else {
378+
_velocity = Vector3.NULL
379+
}
380+
}
381+
state.prevTimeNs = nowNs
382+
state.prevPos = pos
383+
}
384+
332385
/**
333386
* Gets the identity-adjusted tracker rotation after the resetsHandler's corrections
334387
* (identity reset, drift and identity mounting).
@@ -405,6 +458,24 @@ class Tracker @JvmOverloads constructor(
405458
this._acceleration = vec
406459
}
407460

461+
/**
462+
* Sets the derived velocity of the tracker.
463+
*/
464+
fun setVelocity(vec: Vector3) {
465+
this._velocity = if (allowVelocity) vec else Vector3.NULL
466+
}
467+
468+
/**
469+
* Gets the derived velocity of the tracker.
470+
*/
471+
fun getVelocity(): Vector3 = _velocity
472+
473+
/**
474+
* True if the tracker has valid velocity data.
475+
*/
476+
val hasVelocity: Boolean
477+
get() = allowVelocity && _velocity != Vector3.NULL
478+
408479
/**
409480
* True if the raw rotation is coming directly from an IMU (no cameras or lighthouses)
410481
* For example, flex sensor trackers are not considered as IMU trackers (see TrackerDataType)

0 commit comments

Comments
 (0)