diff --git a/gui/public/i18n/en/translation.ftl b/gui/public/i18n/en/translation.ftl index d8efac7a8c..a126aea2a2 100644 --- a/gui/public/i18n/en/translation.ftl +++ b/gui/public/i18n/en/translation.ftl @@ -42,6 +42,7 @@ body_part-LEFT_HAND = Left hand body_part-LEFT_UPPER_LEG = Left thigh body_part-LEFT_LOWER_LEG = Left ankle body_part-LEFT_FOOT = Left foot +body_part-ACCESSORY = Accessory ## Proportions skeleton_bone-NONE = None @@ -453,6 +454,8 @@ settings-osc-vrchat = VRChat OSC Trackers settings-osc-vrchat-description = Change VRChat-specific settings to receive HMD data and send tracker data for FBT without SteamVR (ex. Quest standalone). + This also allows you to receive trackers under the OSCTrackers standard. + This will also send accessory trackers' rotations for custom avatars. settings-osc-vrchat-enable = Enable settings-osc-vrchat-enable-description = Toggle the sending and receiving of data. settings-osc-vrchat-enable-label = Enable @@ -480,7 +483,7 @@ settings-osc-vmc = Virtual Motion Capture # This cares about multilines settings-osc-vmc-description = Change settings specific to the VMC (Virtual Motion Capture) protocol - to send SlimeVR's bone data and receive bone data from other apps. + to send and received bone and tracker data from and to other apps. settings-osc-vmc-enable = Enable settings-osc-vmc-enable-description = Toggle the sending and receiving of data. settings-osc-vmc-enable-label = Enable diff --git a/gui/src/components/commons/BodyPartIcon.tsx b/gui/src/components/commons/BodyPartIcon.tsx index 7463f312c3..f692a80b84 100644 --- a/gui/src/components/commons/BodyPartIcon.tsx +++ b/gui/src/components/commons/BodyPartIcon.tsx @@ -84,6 +84,9 @@ export const mapPart: Record< ), [BodyPart.WAIST]: ({ width }) => , + [BodyPart.ACCESSORY]: ({ width }) => ( + + ), }; export function BodyPartIcon({ diff --git a/gui/src/components/onboarding/BodyAssignment.tsx b/gui/src/components/onboarding/BodyAssignment.tsx index 3ebe3fbc96..3c51f248ad 100644 --- a/gui/src/components/onboarding/BodyAssignment.tsx +++ b/gui/src/components/onboarding/BodyAssignment.tsx @@ -293,6 +293,19 @@ export function BodyAssignment({ direction="left" /> + + {advanced && ( +
+ onRoleSelected(BodyPart.ACCESSORY)} + direction="left" + /> +
+ )} } > diff --git a/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx b/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx index 2565c9452d..ca30277a40 100644 --- a/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx +++ b/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx @@ -53,6 +53,7 @@ export function ManualMountingPage() { assignreq.trackerId = td.tracker.trackerId; assignreq.allowDriftCompensation = td.tracker.info?.allowDriftCompensation ?? true; + assignreq.accessoryId = td.tracker.info?.accessoryId || 0; sendRPCPacket(RpcMessage.AssignTrackerRequest, assignreq); }); diff --git a/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx b/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx index 26596e9772..101f20b427 100644 --- a/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx +++ b/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx @@ -96,6 +96,7 @@ export const mapPart: Record< ), [BodyPart.WAIST]: ({ width }) => , + [BodyPart.ACCESSORY]: ({ width }) => , }; export function MountingBodyPartIcon({ diff --git a/gui/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx b/gui/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx index a410ce0977..40391a37a9 100644 --- a/gui/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx +++ b/gui/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx @@ -192,6 +192,7 @@ export function TrackersAssignPage() { assignreq.trackerId = trackerId; assignreq.allowDriftCompensation = tracker?.tracker?.info?.allowDriftCompensation ?? true; + assignreq.accessoryId = 1; // TODO sendRPCPacket(RpcMessage.AssignTrackerRequest, assignreq); }; diff --git a/gui/src/components/tracker/TrackerSettings.tsx b/gui/src/components/tracker/TrackerSettings.tsx index 0284167651..00777ba5c9 100644 --- a/gui/src/components/tracker/TrackerSettings.tsx +++ b/gui/src/components/tracker/TrackerSettings.tsx @@ -92,6 +92,7 @@ export function TrackerSettingsPage() { assignreq.trackerId = tracker?.tracker.trackerId; if (allowDriftCompensation != null) assignreq.allowDriftCompensation = allowDriftCompensation; + assignreq.accessoryId = 1; // TODO sendRPCPacket(RpcMessage.AssignTrackerRequest, assignreq); setSelectBodypart(false); }; diff --git a/server/core/src/main/java/dev/slimevr/VRServer.kt b/server/core/src/main/java/dev/slimevr/VRServer.kt index 8b7e2819aa..dc362ac9df 100644 --- a/server/core/src/main/java/dev/slimevr/VRServer.kt +++ b/server/core/src/main/java/dev/slimevr/VRServer.kt @@ -59,7 +59,6 @@ class VRServer @JvmOverloads constructor( val trackersServer: TrackersUDPServer private val bridges: MutableList = FastList() private val tasks: Queue = LinkedBlockingQueue() - private val newTrackersConsumers: MutableList> = FastList() private val onTick: MutableList = FastList() val oSCRouter: OSCRouter @@ -197,16 +196,6 @@ class VRServer @JvmOverloads constructor( onTick.add(runnable) } - @ThreadSafe - fun addNewTrackerConsumer(consumer: Consumer) { - queueTask { - newTrackersConsumers.add(consumer) - for (tracker in trackers) { - consumer.accept(tracker) - } - } - } - @ThreadSafe fun trackerUpdated(tracker: Tracker?) { queueTask { @@ -215,6 +204,11 @@ class VRServer @JvmOverloads constructor( refreshTrackersDriftCompensationEnabled() configManager.vrConfig.writeTrackerConfig(tracker) configManager.saveConfig() + if (tracker != null) { + if(tracker.trackerPosition == TrackerPosition.ACCESSORY){ + vrcOSCHandler.addAccessoryTracker(tracker) + } + } } } @@ -270,6 +264,9 @@ class VRServer @JvmOverloads constructor( if (tracker.isComputed && tracker.name != "HMD") { vMCHandler.addComputedTracker(tracker) } + if(tracker.trackerPosition == TrackerPosition.ACCESSORY){ + vrcOSCHandler.addAccessoryTracker(tracker) + } refreshTrackersDriftCompensationEnabled() } @@ -279,9 +276,6 @@ class VRServer @JvmOverloads constructor( queueTask { trackers.add(tracker) trackerAdded(tracker) - for (tc in newTrackersConsumers) { - tc.accept(tracker) - } } } diff --git a/server/core/src/main/java/dev/slimevr/config/TrackerConfig.java b/server/core/src/main/java/dev/slimevr/config/TrackerConfig.java deleted file mode 100644 index fd47a622c0..0000000000 --- a/server/core/src/main/java/dev/slimevr/config/TrackerConfig.java +++ /dev/null @@ -1,92 +0,0 @@ -package dev.slimevr.config; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; -import dev.slimevr.tracking.trackers.Tracker; -import io.github.axisangles.ktmath.Quaternion; - - -public class TrackerConfig { - - private String customName; - private String designation; - private boolean hide; - private Quaternion adjustment; - private Quaternion mountingOrientation; - private Boolean allowDriftCompensation; - - - public TrackerConfig() { - } - - public TrackerConfig(Tracker tracker) { - this.designation = tracker.getTrackerPosition() - != null ? tracker.getTrackerPosition().getDesignation() : null; - this.customName = tracker.getCustomName(); - allowDriftCompensation = tracker.isImu(); - } - - static JsonNode toV2(JsonNode v1, JsonNodeFactory factory) { - ObjectNode node = factory.objectNode(); - if (v1.has("customName")) - node.set("customName", v1.get("customName")); - if (v1.has("designation")) - node.set("designation", v1.get("designation")); - if (v1.has("hide")) - node.set("hide", v1.get("hide")); - if (v1.has("mountingRotation")) - node.set("mountingRotation", v1.get("mountingRotation")); - if (v1.has("adjustment")) - node.set("adjustment", v1.get("adjustment")); - return node; - } - - public String getCustomName() { - return customName; - } - - public void setCustomName(String customName) { - this.customName = customName; - } - - public String getDesignation() { - return designation; - } - - public void setDesignation(String designation) { - this.designation = designation; - } - - public boolean isHide() { - return hide; - } - - public void setHide(boolean hide) { - this.hide = hide; - } - - public Quaternion getAdjustment() { - return adjustment; - } - - public void setAdjustment(Quaternion adjustment) { - this.adjustment = adjustment; - } - - public Quaternion getMountingOrientation() { - return mountingOrientation; - } - - public void setMountingOrientation(Quaternion mountingOrientation) { - this.mountingOrientation = mountingOrientation; - } - - public Boolean getAllowDriftCompensation() { - return allowDriftCompensation; - } - - public void setAllowDriftCompensation(Boolean allowDriftCompensation) { - this.allowDriftCompensation = allowDriftCompensation; - } -} diff --git a/server/core/src/main/java/dev/slimevr/config/TrackerConfig.kt b/server/core/src/main/java/dev/slimevr/config/TrackerConfig.kt new file mode 100644 index 0000000000..0750a0741e --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/config/TrackerConfig.kt @@ -0,0 +1,38 @@ +package dev.slimevr.config + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.node.JsonNodeFactory +import dev.slimevr.tracking.trackers.Tracker +import io.github.axisangles.ktmath.Quaternion + +class TrackerConfig { + var customName: String? = null + var designation: String? = null + var isHide = false + var adjustment: Quaternion? = null + var mountingOrientation: Quaternion? = null + var allowDriftCompensation: Boolean? = null + var accessoryId: Int? = null + + constructor() + constructor(tracker: Tracker) { + designation = if (tracker.trackerPosition + != null + ) tracker.trackerPosition!!.designation else null + customName = tracker.customName + allowDriftCompensation = tracker.isImu() + } + + companion object { + @JvmStatic + fun toV2(v1: JsonNode, factory: JsonNodeFactory): JsonNode { + val node = factory.objectNode() + if (v1.has("customName")) node.set("customName", v1["customName"]) + if (v1.has("designation")) node.set("designation", v1["designation"]) + if (v1.has("hide")) node.set("hide", v1["hide"]) + if (v1.has("mountingRotation")) node.set("mountingRotation", v1["mountingRotation"]) + if (v1.has("adjustment")) node.set("adjustment", v1["adjustment"]) + return node + } + } +} diff --git a/server/core/src/main/java/dev/slimevr/osc/VRCOSCHandler.kt b/server/core/src/main/java/dev/slimevr/osc/VRCOSCHandler.kt index b9c1d1feb3..da4882f013 100644 --- a/server/core/src/main/java/dev/slimevr/osc/VRCOSCHandler.kt +++ b/server/core/src/main/java/dev/slimevr/osc/VRCOSCHandler.kt @@ -1,11 +1,6 @@ package dev.slimevr.osc -import com.illposed.osc.MessageSelector -import com.illposed.osc.OSCBundle -import com.illposed.osc.OSCMessage -import com.illposed.osc.OSCMessageEvent -import com.illposed.osc.OSCMessageListener -import com.illposed.osc.OSCSerializeException +import com.illposed.osc.* import com.illposed.osc.messageselector.OSCPatternAddressMessageSelector import com.illposed.osc.transport.OSCPortIn import com.illposed.osc.transport.OSCPortOut @@ -63,6 +58,7 @@ class VRCOSCHandler( private val postReceivingOffset = EulerAngles(EulerOrder.YXZ, 0f, FastMath.PI, 0f).toQuaternion() private var timeAtLastReceivedRotationOffset = System.currentTimeMillis() private var fpsTimer: NanoTimer? = null + private val accessoryTrackers: MutableList = FastList() init { refreshSettings(false) @@ -329,6 +325,7 @@ class VRCOSCHandler( // Create new bundle val bundle = OSCBundle() + // Send computed trackers as OSC Trackers for (i in computedTrackers.indices) { if (trackersEnabled[i]) { // Send regular trackers' positions @@ -353,12 +350,7 @@ class VRCOSCHandler( // Y quaternion represents a rotation from z to x // When we negate the z direction, X and Y quaternion // components must be negated. - val (_, x2, y2, z2) = Quaternion( - w, - -x1, - -y1, - z1 - ).toEulerAngles(EulerOrder.YXZ) + val (_, x2, y2, z2) = Quaternion(w, -x1, -y1, z1).toEulerAngles(EulerOrder.YXZ) oscArgs.clear() oscArgs.add(x2 * FastMath.RAD_TO_DEG) oscArgs.add(y2 * FastMath.RAD_TO_DEG) @@ -386,6 +378,24 @@ class VRCOSCHandler( } } + // Send accessory trackers for custom avatars + for (i in accessoryTrackers.indices) { + if (accessoryTrackers[i].trackerPosition == TrackerPosition.ACCESSORY && accessoryTrackers[i].status.sendData) { + val (w, x1, y1, z1) = accessoryTrackers[i].getRotation() + val (_, x2, y2, z2) = Quaternion(w, -x1, -y1, z1).toEulerAngles(EulerOrder.YXZ) + oscArgs.clear() + oscArgs.add(x2 * FastMath.RAD_TO_DEG) + oscArgs.add(y2 * FastMath.RAD_TO_DEG) + oscArgs.add(z2 * FastMath.RAD_TO_DEG) + bundle.addPacket( + OSCMessage( + "/avatar/parameters/accessory${accessoryTrackers[i].accessoryId}", + oscArgs.clone() + ) + ) + } + } + try { oscSender!!.send(bundle) } catch (e: IOException) { @@ -455,6 +465,17 @@ class VRCOSCHandler( } } + /** + * Adds an accessory tracker to the list of trackers to send. + * + * @param tracker the accessory tracker + */ + fun addAccessoryTracker(tracker: Tracker) { + if(!accessoryTrackers.contains(tracker)){ + accessoryTrackers.add(tracker) + } + } + override fun getOscSender(): OSCPortOut { return oscSender!! } diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.java b/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.java index 986582211e..85ccc65941 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.java +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.java @@ -270,6 +270,10 @@ public void onAssignTrackerRequest(GenericConnection conn, RpcMessageHeader mess tracker.getResetsHandler().setAllowDriftCompensation(req.allowDriftCompensation()); } + if (req.accessoryId() != 0) { + tracker.setAccessoryId(req.accessoryId()); + } + this.api.server.trackerUpdated(tracker); } diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt index 8df1511b35..229e901008 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt @@ -77,6 +77,7 @@ class Tracker @JvmOverloads constructor( var signalStrength: Int? = null var temperature: Float? = null var customName: String? = null + var accessoryId: Int? = null /** * If the tracker has gotten disconnected after it was initialized first time @@ -209,6 +210,9 @@ class Tracker @JvmOverloads constructor( config.customName?.let { customName = it } + config.accessoryId?.let { + accessoryId = it + } config.designation?.let { designation -> getByDesignation(designation)?.let { trackerPosition = it } } ?: run { trackerPosition = null } @@ -238,6 +242,7 @@ class Tracker @JvmOverloads constructor( fun writeConfig(config: TrackerConfig) { trackerPosition?.let { config.designation = it.designation } ?: run { config.designation = null } customName?.let { config.customName = it } + accessoryId?.let { config.accessoryId = it } if (needsMounting) { config.mountingOrientation = resetsHandler.mountingOrientation } diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerPosition.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerPosition.kt index e814ca65bf..46b4fb06d7 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerPosition.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerPosition.kt @@ -33,6 +33,7 @@ enum class TrackerPosition( RIGHT_HAND("body:right_hand", TrackerRole.RIGHT_HAND, BodyPart.RIGHT_HAND), LEFT_SHOULDER("body:left_shoulder", TrackerRole.LEFT_SHOULDER, BodyPart.LEFT_SHOULDER), RIGHT_SHOULDER("body:right_shoulder", TrackerRole.RIGHT_SHOULDER, BodyPart.RIGHT_SHOULDER), + ACCESSORY("accessory", null, BodyPart.ACCESSORY), ; companion object { diff --git a/solarxr-protocol b/solarxr-protocol index c22d4729ec..01631f99e4 160000 --- a/solarxr-protocol +++ b/solarxr-protocol @@ -1 +1 @@ -Subproject commit c22d4729ec45a9d4bd357458cfa4cc5cc65968ab +Subproject commit 01631f99e43698b8b3a3a9c3bb62c91845a04237