Compare commits

..

2 Commits

Author SHA1 Message Date
lucas lelievre
181c6599b7 Increment versionCodeOffset from 4 to 5 2026-03-31 21:10:00 +02:00
gorbit99
fb77d3cf8a Downgrade JavaOSC version (#1801) 2026-03-31 21:02:01 +02:00
28 changed files with 42 additions and 578 deletions

View File

@@ -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.

View File

@@ -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>

View File

@@ -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,
}
)}
>

View File

@@ -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,
}
)}
>

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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>)',

View File

@@ -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

View File

@@ -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")

View File

@@ -48,7 +48,6 @@ enum class MountingMethods(val id: Int) {
}
class ResetsConfig {
var stepMounting = false
// Always reset mounting for feet
var resetMountingFeet = false

View File

@@ -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)
}

View File

@@ -369,7 +369,6 @@ fun createArmsResetModeSettings(
resetsConfig.yawResetSmoothTime,
resetsConfig.saveMountingReset,
resetsConfig.resetHmdPitch,
resetsConfig.stepMounting,
)
fun createSettingsResponse(fbb: FlatBufferBuilder, server: VRServer): Int {

View File

@@ -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()
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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,
)

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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
*/

View File

@@ -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) {

View File

@@ -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()
}
}
}
}

View File

@@ -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 -> {

View File

@@ -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
}
}

View File

@@ -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),
)
}
}

View File

@@ -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

View File

@@ -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()
}
}
}